solution_223.py

#!/usr/bin/python3
# ====================================================================
# file organizer
#
# The definition of "file name" is ambiguous. In common use it can
# have several meanings. In this code a "file name" is a file
# specification made up of three parts. They are:
#
#   path  directory path to file
#   name  file name
#   type  file extension
#
# Note:
# 1. On windows, path includes a volume or dive.
# 2. this code does not copy or move directories (only files).
# 3. this code uses the default file mode (0o777).
# 4. I am not happy with using two modules (os and shutil).
#    modify the code to use only one of them if possible.
# ====================================================================
# links:
# datagy.io/python-copy-file/
# docs.python.org/3/library/shutil.html
# docs.python.org/3/library/pathlib.html
# stackoverflow.com/questions/17752078/difference-between-os-path-
#      exists-and-os-path-isfile
# ====================================================================
# THIS PROGRAM IS STRUCTURED AS A DEMONSTRATION PROGRAM AND SHOULD
# BE RE-STRUCTURED AS AN EFFICIENT "FILE ORGANIZER" PROGRAM.
# ====================================================================

import os
import sys
import shutil
import user_interface as ui

# -------------------------------------------------------------------
# ---- given a directory,
# ---- return a list of files and directories in it
# -------------------------------------------------------------------

def get_list_of_files_directories(dir):

    # ---- dir must end with '/' character

    if not dir.endswith('/'):
        dir = dir + '/'

    # --- get a list of entries in the directory

    entries = os.listdir(dir)

    # --- collect file names and file types

    dirs  = []
    files = []
    links = []
    
    for f in entries:

        ff = dir + f

        if os.path.islink(ff):
            link_count += 1
            continue

        if os.path.isfile(ff):
                files.append(ff)
                continue
            
        if os.path.isdir(ff):
                dirs.append(ff)
                continue

        print(f'we should never get here ({ff})')
        sys.exit()

    dirs.sort()
    files.sort()
    links.sort()

    return (files,dirs,links)

# -------------------------------------------------------------------
# ---- given a list of files (each has up to 3 parts)
# ---  split them and return them as a list of tuples
# ----
# ---- path  directory path to file
# ---- name  file name
# ---- type  file extension
# ----
# ---- for example, if file = '/a/b/c/xyz/xyz.txt'
# ----     path = '/a/b/c/'
# ----     name = 'xyz'
# ----     type = '.txt'
# -------------------------------------------------------------------

def split_file_paths(files):

    # --- collect file path, file names and file types

    ftypes    = []
    full_path = []
    
    for f in files:
       
        path,file = os.path.split(f)
        name,type = os.path.splitext(file)
        ftypes.append((path,name,type))
        
        ##print(f'path : {path}')
        ##print(f'file : {file}')
        ##print(f'name : {name}')
        ##print(f'type : {type}')

    # ---- sorted new list by file type
    #new_list = sorted(ftypes,key=lambda tup: tup[2])
    # ---- sort in place
    ftypes.sort(key=lambda tup:tup[2])

    return (ftypes)

# --------------------------------------------------------------------
# ---- delete a directory tree
# ----
# ---- path is a complete path to a tree root directory
# --------------------------------------------------------------------

def delete_directory_tree(path:str) -> bool:

    print(f'delete dir: {path}')
    
    if not os.path.exists(path):
        print(f'delete dir: directory does not exists')
        return True

    try: 
        shutil.rmtree(path)
        print(f'delete dir: directory tree deleted')
    except Exception as e:
        print(e)
        print(f'delete dir: unable to delete directory tree')
        return False

    return True

# --------------------------------------------------------------------
# ---- create a directory tree
# ----
# ---- path is a complete path to the new directory
# ---- return True if at the end a new directory
# ---- mkdir     create a directory,
# ----           if the directory already exists, do nothing
# ---- makedirs  will create any directories that do no exist
# --------------------------------------------------------------------

def create_directory_tree(path:str) -> bool:

    print(f'create dir tree: {path}')
    
    # ---- does the directory exist
    # ---- if it exists, do not delete files in it

    if os.path.exists(path):
        print(f'create dir tree: no existing file deleted')
        return True

    # create the directory tree if does not exist
        
    try:
        os.makedir(path)
        print(f'create dir tree: directory tree created')
    except Exception as e:
        ##print(e)
        print(f'create dir tree: unable to create directory tree')
        return False       
    return True

# --------------------------------------------------------------------
# ---- create a directory
# ----
# ---- path is a complete path to the new directory
# ----
# ---- mkdir     creats a directory if the path to it already exists
# ---- makedirs  will create any directories that do no exist
# --------------------------------------------------------------------

def create_directory(path:str) -> bool:

    print(f'create dir: {path}')

    # ---- does the directory exist
    # ---- if it exists, do not delete files in it

    if os.path.exists(path):
        print(f'create dir: directory already exists')
        print('create dir: no files have been deleted')
        return True

    # create the directory
        
    try:
        os.mkdir(path)
        print(f'create dir: directory created')
    except Exception as e:
        ##print(e)
        print(f'create dir: unable to create directory')
        return False       
    return True

# --------------------------------------------------------------------
# ---- test 1 - delete existing directories, then create directory
# --------------------------------------------------------------------

def test1(path):

    print()
    print('create and delete directories')
    print()
    
    # ---- create a new directory (not a directory tree))
    # ---- delete directories first

    tf = delete_directory_tree(path)
    if not tf:
        print('delete directory tree failed')
        return False

    tf = create_directory(path)
    if not tf:
        print('create directory failed')
        return False

    return True

# --------------------------------------------------------------------
# ---- test 2 - files and file types
# --------------------------------------------------------------------

def test2(directory):

    print()
    print(f'files and file types')
    print()

    # ---- list of files and directories
    
    files,dirs,links = get_list_of_files_directories(directory)

    print(f'directory             {directory}')
    print(f'number of files       {len(files)}')
    print(f'number of directories {len(dirs)}')
    print(f'number of links       {len(links)}')

    # --- count the number of each file type

    paths = split_file_paths(files)

    count_dict = {}
    
    for i,p in enumerate(paths,1):
        if p[2] in count_dict:
            count_dict[p[2]] += 1    # increment type count
        else:
            count_dict[p[2]] = 1     # add new type

    print()
    for k,v in sorted(count_dict.items()):
        print(f'{k:8} {v}')

    return True

# --------------------------------------------------------------------
# ---- test 3 - copy files
# --------------------------------------------------------------------

def test3(source,destination):

    # ---- directories must end with '/' character

    if not source.endswith('/'):
        source = source + '/'

    if not destination.endswith('/'):
        destination = destination + '/'

    print()
    print('copy files')
    print(f'source      is {source}')
    print(f'destination is {destination}')
    print()

    # ---- do the directories exist

    if not os.path.exists(source):        
        print(f'copy files: source directory does not exist')
        return False

    if not os.path.exists(destination):
        print(f'copy files: destination directory does not exist')
        return False

    # ---- get a list of files in the source directory
    
    files,dirs,links = get_list_of_files_directories(source)

    # ---- copy files to the destination directory

    c = 0
    for file in files:
        c += 1
        shutil.copy(file,destination)

    print(f'{c} files copied')
    print()

    return True

# --------------------------------------------------------------------
# ---- test 4 - move files
# --------------------------------------------------------------------

def test4(source,destination):

    # ---- directories must end with '/' character

    if not source.endswith('/'):
        source = source + '/'

    if not destination.endswith('/'):
        destination = destination + '/'

    print()
    print('move files')
    print(f'source      is {source}')
    print(f'destination is {destination}')
    print()

    files,dirs,links = get_list_of_files_directories(source)

    print(f'{len(files)} source files found')

   # ---- move files to the destination directory

    move_c = 0
    not_move_c = 0
    for file in files:
        move_c += 1
        if os.path.isfile(file):
            shutil.copy(file,destination)
            os.remove(file)
        else:
            not_move_c += 1
            
    print(f'{move_c} files moved')
    print(f'{not_move_c} files not moved')
    print()

    return True


# --------------------------------------------------------------------
# ---- main
# --------------------------------------------------------------------

if __name__ == '__main__':

    dir_1 = './zzzz'     # source only
    dir_2 = './aaaa'     # source and destination
    dir_3 = './bbbb'     # destination only

    menu = '''\

+------------------------------------------------+
| Test Functionality Needed for a File Organizer |
+------------------------------------------------+
|WARNING: do not use real files or directories   |
|         these directories are only for testing |
|                                                |
|  dir_1 = './zzzz'     # source only            |
|  dir_2 = './aaaa'     # source and destination |
|  dir_3 = './bbbb'     # destination only       |
+------------------------------------------------+

option description
------ ---------------------------------------
   0   exit program
   1   create directory (delete existing dir)
   2   delete directory
   3   get list of files and file types
   4   copy files
   5   move files'''

    while True:
        
        print(menu)
        
        print()
        s = ui.get_user_input('Select option: ')

        if not s: break

        tf,i = ui.is_integer(s)

        if not tf:
            print()
            print(' error: non integer entered')
            ui.pause()
            continue

        # --- option 0

        if i == 0: break

        # --- option 1 create and delete directories

        if i == 1: 
            test1(dir_2)
            ui.pause()
            continue

        # --- option 2 delete directory
 
        if i == 2:
            delete_directory_tree(dir_2)
            ui.pause()
            continue

        # --- option 3 files and file types

        if i == 3:
            test2(dir_1)
            ui.pause()
            continue

        # --- option 4 copy files

        if i == 4:
            test3(dir_1,dir_2)
            ui.pause()
            continue
        
        # --- option 5 move files
 
        if i == 5:
            test4(dir_2,dir_3)
            ui.pause()
            continue
        
        # --- selection error

        print()
        print(f'Error: illegal selection {i}')
        ui.pause()