File Management just got Easier

File Management just got Easier

People ask me, "how are you able to keep the files on your drive so arranged? Like Bro, everything is so findable on the spot." Well, There are two ways to achieve this.

The first way is the usual human way. You sit down one faithful evening and just start making folders and begin to drag and drop files in to these folders. I did this once or maybe twice {who knows how many people do this everyday}.

The second way, this is the less stressful, less time-consuming process and I will show you how to achieve this in a few moments. We'll be using Python, because I love Python and it's simple to understand {at least πŸ˜‚}.

PREREQUISITES

Well, before I started this project, I went out to explore and meet an "friend", and I asked, "Do we need anything before we can create the project?"

It replied me with a list of items to make sure you have before you can successfully execute the code.

  • Python 3.7 or higher installed

  • A Code Editor/IDE to help you write the code well and ensure you take note of the indentation and see any errors in spellings. The Editor/IDE that I'd be using is VS Code. Visual Studio Code is a wonderful and popular editor which allows for seamless integration with a variety of programming languages, extensive customization through plugins and themes, and efficient code editing with features like IntelliSense… bla bla bla. What I'm trying to say is that It's good so use it 😏

  • A really fancy naming system. So I went with the name "Arrange" but it felt too ordinary, and I decided to take it up a notch {😁}. From Arrange, It became "@rrang3". Looks cool right? Yeah, thought so at first. If you like the name, you can "borrow" it πŸ™ƒ or you know, maybe come up with your own name.

Once you have all these prerequisites set, we can begin.

SETUP

First of all, I love being arranged even without the python script we're about to write so, It would be a good step to create a new folder to handle all the files of the project. Give the folder the name of your project, which is "@rrang3" in my case.

Once you're done with that, you can open that folder in your chosen code editor/IDE and create a new python file with a good name for the file, like "app.py", "index.py", "project.py", or anything as long as you are able to run the project.

I'm naming my project as "app.py". Once you've done that, you can move on to the next part of this article.

Also, to check if you have Python installed and the version you are running, you can run python --version in your terminal.

IDEATION and SCRIPTING

STEP 1: After the research I carried out, My "Friend" confirmed my hypothesis and told me that I needed three modules to be imported into my code. I was happy, because I was sure about the os module, but I didn't think of shutil.

  • os: This module allows us to carry out operations that interact with the operating system, such as listing file directories and creating folders.

  • shutil: This allows us to carry out the file processes like copying, and moving files.

  • time: I added this one because I didn't like the fact everything happened so fast, I wanted to have some delay in some parts of the process. {In shorter words, I like suspense in my code πŸ˜€}

We begin by adding these three modules to the code. Once you have added these, you can move on to continue writing the script.

import os
import shutil
import time

In a script like this, you'd want it to run with a sense of direction right? Like, you'd want to be specific so that you don't go off arranging the wrong folder right πŸ˜‚

The first thing in the script is to ask for the directory you want to arrange. I handled that by creating a simple function that asks for the directory you want to arrange.

# Checks Work Directory
def check_cwd():
    while True:
        current_cwd = input("Enter Directory: ")
        if os.path.isdir(current_cwd):
            return current_cwd.lower()
        else: print("Invalid Directory. Please enter a valid directory")

This is a simple function. A loop is initiated to ensure that the path inputted is true. In the loop, the input() function is used to prompt the user to enter the directory they would like to work on and then their answer is saved in a variable, current_cwd. An if...else block then checks if the path inputted is an existing directory and if this is true, the variable is then returned as the value of this function, else, a message is returned prompting the user to enter a valid directory. {Simple logic right? πŸ˜…}

STEP 2: The next thing is to list the files in the chosen directory.

# Lists the files in the chosen directory
def list_files(directory):
    try:
        cwd_files = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))]
        file_num = len(cwd_files)

        if file_num >= 1:
            return {"cwd_files": cwd_files, "file_num": file_num}
        else:
            print("File number in the chosen directory is too low to continue.")
            return {"cwd_files": [], "file_num": 0}

    except OSError as e:
        print(f"Error occured: {e}")
        return {"cwd_files": [], "file_num": 0}

I used list comprehension to create a list of files in the given directory (directory).

  • try...except: This allows us to handle errors ourselves and choose what to do when these errors happen in the code {Makes the script better for both the user and the developer if used properly}.

  • os.listdir(directory): This part fetches a list of all items (both files and directories) in the specified directory (directory). It's like listing all the contents of a folder.

  • os.path.join(directory, f): Here, this function creates the full path to each item in the directory. It concatenates directory and f to form the complete path to each file in the directory. For example, if directory = "/path/to/directory" and f = "file.txt", os.path.join(directory, f) will return "/path/to/directory/file.txt".

  • os.path.isfile(): This part checks if a given path corresponds to a regular file. In our case, it checks if the paths obtained in the step above (os.path.join(directory, f)) are files.

  • [f for f in ... if ...]: This is the list comprehension itself. It iterates over each item (f) obtained from os.listdir(directory), applies the condition (os.path.isfile(os.path.join(directory, f))), and includes only those items for which the condition is True in the final list (cwd_files).

  • In short and simpler terms, the comprehension selects only the items from the directory that are regular files (not directories or other types of items) and stores their names in the list cwd_files.

  • Then, the file_num variable stores the number of files from the list(cwd_files) by using the len() function.

  • I then decided to limit the number of files that can be arranged to 1 using an if statement which then returns the two variables defined (cwd_files and file_num) in a dictionary so that their values can be later accessed after the function has been called that is if the number of files in the cwd_files variable is greater than or equal to 1.

  • If it is not, it prints a message saying the file number in the chosen directory is too low to continue and then it returns a dictionary with which the values of the cwd_files variable is an empty list and that of the file_num variable is 0.

  • Outside of this if statement, the except block watches for an error defined as an OSError. This error occurs due to possible errors from the os operations saved in the cwd_files variable. To handle it, we accept it as e and then print a message telling the user of what error happened, and then return a dictionary with empty values for the variables defined {Just like I explained earlier in the last point πŸ˜ƒ}

STEP 3: Files can be arranged and sorted and so on... but if there are no folders to keep them, to me, not much work has been done. The main aim of this script is to arrange the files and put them into folders based on their file types. We achieve this by creating another function.

# Static Folders
def create_static_folders(directory):
    try:
        # Creates folders for file grouping
        image_folder = os.path.join(directory, "Images")
        audio_folder = os.path.join(directory, "Audio")
        document_folder = os.path.join(directory, "Docs")
        others_folder = os.path.join(directory, "Others")

        # Checks folder existence
        os.makedirs(image_folder, exist_ok=True)
        os.makedirs(audio_folder, exist_ok=True)
        os.makedirs(document_folder, exist_ok=True)
        os.makedirs(others_folder, exist_ok=True)

        return {"image_folder": image_folder, "audio_folder": audio_folder, "document_folder": document_folder, "others": others_folder}
    except OSError as e:
        print(f"Error occured: {e}")
        return {"image_folder": None, "audio_folder": None, "document_folder": None, "others": None}

This function (create_static_folders()) has one argument (directory) which will be passed when the script is being run. It creates folders for the file grouping process in the chosen directory. Just in case, the others folder is created to keep files which are not recognized.

image_folder = os.path.join(directory, "Images")
audio_folder = os.path.join(directory, "Audio")
document_folder = os.path.join(directory, "Docs")
others_folder = os.path.join(directory, "Others")

It then verifies the existence of these folders and ensures they have been created. Once the verification is true, it then returns the variables storing the folders as a dictionary.

# Checks folder existence
os.makedirs(image_folder, exist_ok=True)
os.makedirs(audio_folder, exist_ok=True)
os.makedirs(document_folder, exist_ok=True)
os.makedirs(others_folder, exist_ok=True)

return {"image_folder": image_folder, "audio_folder": audio_folder, "document_folder": document_folder, "others": others_folder}

The content of the function is wrapped in a try...except block to help in catching and handling errors and as so, if an OSError occurs, the script will throw an error message on the screen and return None to as the values of the variables in the dictionary to be returned as the value of the code.

except OSError as e:
    print(f"Error occured: {e}")
    return {"image_folder": None, "audio_folder": None, "document_folder": None, "others": None}

In this step 3, we have created the image folder, audio folder, document folder, and an extra folder (others) to handle files that are not recognised, and also made it able to handle errors in cases they occur. {Oh Yes, we're almost at the core of the script. Nice work getting to this point πŸ‘}

STEP 4: After the creation of these folders in the create_static_folders() function. The next would be to sort and move the files. A function sort_files() is created. It would be accepting two arguments (directory and folders). A variable is then created to use the list_files() function on the directory argument.

I then thought. You know, It would be nice to know files that were Organized and those that were not. So, I created two variables (organized_files and unorganized_files) and equated them both to zero (0). This would help us keep count.

def sort_files(directory, folders):
    try:
        files = list_files(directory)
        organized_files = 0
        unorganized_files = 0

        for file in files["cwd_files"]:
            file_path = os.path.join(directory, file)
            file_extension = os.path.splitext(file)[-1]

            if file_extension.lower() in [".jpg", ".jpeg", ".png", ".gif"]:
                shutil.move(file_path, folders["image_folder"])
                organized_files += 1
            elif file_extension.lower() in [".mp3", ".wav", ".wma"]:
                shutil.move(file_path, folders["audio_folder"])
                organized_files += 1
            elif file_extension.lower() in [".pdf", ".doc", ".docx", ".txt"]:
                shutil.move(file_path, folders["document_folder"])
                organized_files += 1
            else:
                print(f'- Could not sort file: {file} as its type is not recognized.')
                shutil.move(file_path, folders["others"])

                unorganized_files += 1
        return {"organized_files": organized_files, "unorganized_files": unorganized_files}

    except Exception as e:
        print(f'Error occured: {e}')
        return {"organized_files": 0, "unorganized_files": 0}

Once I created the variables (files, organized_files and unorganized_files), I began a for loop. It iterates through the cwd_files variables we created earlier in the list_files() function to identify the files in the chosen directory.

In the for loop, two variables are defined.

  1. file_path is the variable created to store the complete path of the current file gotten from the combination of the directory path and the filename from files["cwd_files"] using os.path.join(directory, file).

  2. The file extension from the file is extracted using os.path.splitext(file_path)[-1] in the code. The os.path.splitext() function splits the filename into its base name and extension, and the [-1] index selects the extension from the resulting tuple. This selected extension is then saved in a variable, file_extension.

In the for loop, after the file_path and file_extension have been defined, an if ... elif block is used to check the file extension and move the file to the corresponding folder. For instance, if the extension is .jpg or .jpeg, the file is moved to the image_folder using the shutil.move() function. Similar logic applies for other extensions like .mp3, .wav, .pdf, and .doc.

Once the shutil.move() function runs and moves a file into the image, audio or document folder, the organized_files variable is upgraded with one. Same logic in the else block. The unorganized_files variable is upgraded with one for every time a file is moved into the others folder. Once the loop is exhausted, the function returns a dictionary with the organized files and the unorganized files.

In cases where an error occurs, a dictionary with 0 as values is returned and an error is thrown {Been a common logic through out this whole process. I hope you do get it πŸ˜ƒ}


At this point, we have successfully written the logic for the script. You can start warming up your dancing feet because you just wrote your own code to help you arrange your files. Amazing right? If you got to this point, high five to you πŸ–πŸ‘Œ. \

If you got confused along the way, don't worry. You can go to the end of this article and a link to a well commented version of the code on my Github account will be there. You can open the file and go through it to see where you had problems. Don't forget to give the repository a star.


Alright. Back to the script. We've defined all the functions. Now we initiate them.

To do this, we create a main() function which will run all the other functions.

def main():
    try:
        cwd = check_cwd()
        print(f"Chosen Work Directory: \"{cwd}\"")
        print("----------------------------------------------")

        files = list_files(cwd)
        print(f"Number of files:", files["file_num"])
        time.sleep(1)
        print("----------------------------------------------")

        print("Process Initiating...")
        time.sleep(2)

        folders = create_static_folders(cwd)
        if None in folders.values():
            print("Error creating folders. Exiting.")
            return
        print("----------------------------------------------")

        sort_function = sort_files(cwd, folders)
        print("----------------------------------------------")

        time.sleep(2)
        print("Total Number of Organized Files:", sort_function["organized_files"])
        print("Total Number of Unorganized Files:", sort_function["unorganized_files"])
        print("----------------------------------------------")

    except Exception as e:
        print(f"An unexpected error occured: {e}")

    except KeyboardInterrupt:
        print(f"\nProcess interrupted by user.")

In the function we save the value of the check_cwd() function in a variable (cwd).

I then added a print statement to show the user the directory which has been entered.

The same logic was applied to the other variables present (files, folders and sort_function). These variables hold the value of the functions created earlier. Once this has been done, the values can then be used and manipulated without having to call any of the function all over again.

Then I used the time.sleep() function to add some delay to the script {It makes it look like some progress is actually happening πŸ˜‰}, and also some separators to separate the "progress" texts in the scripts. All these are additional "candies" I add to make my programs "sweet" and enjoyable. You could add them too, or choose not to. A personal choice up to you.

Once the folders variable is created and the function is declared in it, an if statement is brought up to check if None is a value in the dictionary being returned by the create_static_folders(cwd) function. Once it finds that None is present, a message is printed of an error which has occured which brings an end to the script process.

Well. Once you're done with this function, you can call it and test it. {Don't forget to call the function at the end of the code unless it won't run}

You call the function by doing this:

if __name__ == "__main__":      
    main()

That should initiate the process. I would love to hear how everything went in the comments section and be careful. With great power, comes great responsibility, so right now, you should be careful of the directory you input. {I am not the cause of a wrong folder being arranged πŸ˜‚}.

Anyways, that's all for this one. A round of applause to My "Friend", starred by the Internet🌐 AKA www.google.com {yo yo βœŒπŸŽ‰}. Here's one to improved internet access and more resources {Mostly you, AI}.

What have we learnt today, There are two ways to arrange your files. You just learnt the simple, fast, and easy way to do it. If you have questions, the comments section is wide open. So ask, and answers could come {as long as they have answers πŸ˜‚}.

I've said too much. Bye for now. Catch you in the next one.

Github Link: Link to GitHub Repository

Β