Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added duplicate file remover and updated Dockerfile #106

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ FROM python:3
# Set the working directory to /code
WORKDIR /code

# Copy the current directory content into the container at /code
ADD . /code
# Copy the Pipfile and Pipfile.lock first to leverage Docker cache
COPY Pipfile Pipfile.lock /code/

# Udate and install all in the container
RUN apt-get update && \
apt-get install -y

# Install pipenv and install all dependency
# Install pipenv and dependencies
RUN pip install --upgrade pip
RUN pip install pipenv
COPY ./Pipfile /code/Pipfile

# Install the dependencies in the system (not in a virtualenv, since you're using --system)
RUN pipenv install --deploy --system --skip-lock --dev

# Set enviroment variables
# Copy the rest of the application code
COPY . /code

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PYTHONUNBUFFERED 1

# Run your application (replace 'your_app.py' with your entry point file)
CMD ["python", "."]
76 changes: 48 additions & 28 deletions organise_desktop/Clean.py
Original file line number Diff line number Diff line change
@@ -1,92 +1,112 @@
import sys
import json
import os
from .cronController import schedule_end, schedule_start
from .cronController import schedule_end, schedule_start
from .organiseDesktop import undo, organise_desktop
from .duplicateCleaner import clean_duplicates # Importing the duplicate cleaner

# Import Tkinter based on Python version
if sys.version_info >= (3,):
from tkinter import *
from tkinter import messagebox as tkMessageBox
else:
from tkinter import *
import tkMessageBox

# Get the directory of the current file
pwd = os.path.dirname(os.path.abspath(__file__))
Extensions = json.load(open(pwd+'/Extension.json', 'r'))
folders = [x for x in Extensions]
# Load extensions from the JSON file
with open(os.path.join(pwd, 'Extension.json'), 'r') as ext_file:
Extensions = json.load(ext_file)

folders = list(Extensions.keys())

class App(Frame):
"""define the GUI"""
"""Define the GUI for the desktop cleaner."""

def clean(self):
checked_extensions = {}
for x in folders:
checked_extensions[x] = Extensions[x]
"""Clean the desktop by organizing files with checked extensions."""
checked_extensions = {x: Extensions[x] for x in folders}
organise_desktop(checked_extensions)
tkMessageBox.showinfo('Complete', 'Desktop clean finished.')

def clean_duplicates_gui(self):
"""Clean duplicate files by using the duplicate cleaner."""
directory = tkMessageBox.askdirectory(title="Select Directory to Clean Duplicates")
if directory:
clean_duplicates(directory)
tkMessageBox.showinfo('Complete', 'Duplicate file cleanup finished.')

def quit_all(self):
"""Exit the application."""
sys.exit(0)

def check(self, item):
global folders
"""Toggle the selection of a file extension."""
if item in folders:
folders.remove(item)
else:
folders.append(item)

def on_schedule_start(self):
"""Start the scheduling process for organizing the desktop."""
schedule_start(folders)

def make_checkbutton(self, text):
"""Create a checkbutton for a file extension."""
cb = Checkbutton(self, text=text, command=lambda: self.check(text))
cb.select()
cb.pack({'side': 'top'})
cb.select() # Select the checkbutton by default
cb.pack(side='top')
return cb

def make_button(self, text, command):
"""Create a button with the specified command."""
btn = Button(self, text=text, command=command)
btn.pack({'side': 'left'})
btn.pack(side='left')
return btn

def create(self):
"""Create the GUI layout."""
self.winfo_toplevel().title('Desktop Cleaner')

# Create checkbuttons for each file extension
for ext in sorted(Extensions.keys()):
self.make_checkbutton(ext)

# buttons and their respective functions
buttons = {'Clean': self.clean,
'Exit': self.quit_all,
'Undo': undo,
'Schedule': self.on_schedule_start,
'Remove\nSchedule': schedule_end
}
# Create buttons and their respective functions
buttons = {
'Clean': self.clean,
'Exit': self.quit_all,
'Undo': undo,
'Schedule': self.on_schedule_start,
'Remove\nSchedule': schedule_end,
'Clean\nDuplicates': self.clean_duplicates_gui # Added button for cleaning duplicates
}

for key in buttons:
self.make_button(key, buttons[key])

def __init__(self, master=None):
"""Initialize the application."""
Frame.__init__(self, master)
self.pack()
self.create()

def main():
"""Main function to run the application."""
root = Tk()
# root.resizable = False # commenting this approach and applying the below one.
root.resizable(FALSE,FALSE) # To make the application's size constant and restore button in windows as greyed out(with width=350 and height=330 as mentioned below)
root.resizable(FALSE, FALSE) # Make the application's size constant
root.minsize(width=350, height=330)
root.maxsize(width=350, height=330)

'''Logic to launch the app in center - start'''
positionRight = int(root.winfo_screenwidth() / 2 - 330 / 2) #considering width=330
positionDown = int(root.winfo_screenheight() / 2 - 350 / 2) #considering height=350
root.geometry("+{}+{}".format(positionRight, positionDown))
'''Logic to launch the app in center - end'''

# Center the application window on the screen
positionRight = int(root.winfo_screenwidth() / 2 - 350 / 2)
positionDown = int(root.winfo_screenheight() / 2 - 330 / 2)
root.geometry(f"+{positionRight}+{positionDown}")

app = App(root)
root.protocol('WM_DELETE_WINDOW', app.quit_all)
app.mainloop()
root.destroy()

if __name__ == '__main__':
main()
main()
7 changes: 6 additions & 1 deletion organise_desktop/cronCleanUp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
import os
import sys

# Determine the file path separator based on the operating system
separator = ""
if sys.platform == 'win32':
separator = '\\'
else:
separator = '/'
with open(os.path.dirname(os.path.join(os.path.abspath(__file__), 'settings.txt'), 'rb') as setting_file:

# Correctly open the settings file using the right file path
settings_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings.txt')
with open(settings_file_path, 'rb') as setting_file:
folders = pickle.load(setting_file)

# Call the main function from the Clean module with the loaded folders
Clean.main(folders)
51 changes: 26 additions & 25 deletions organise_desktop/cronController.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,55 @@
__author__ = "Remigius Kalimba"
"""Add a timer so it does this automatically everyday at a set time"""
"""Add a timer so it does this automatically every day at a set time"""

import pickle
import crontab as CronTab
import json
import os
import sys
from subprocess import call

import getpass

from subprocess import call
from crontab import CronTab

pwd = os.path.dirname(os.path.abspath(__file__))


def schedule_start(folders):
"""Starts a schedule to organize the desktop once a day"""

"""Starts a schedule to organise the desktop once a day"""

# Save the folder settings
with open('./settings.txt', 'wb') as setting_file:
pickle.dump(folders, setting_file)

if sys.platform == 'darwin' or sys.platform == 'linux':

# Schedule the job based on the operating system
if sys.platform in ['darwin', 'linux']:
my_cron = CronTab(user=getpass.getuser())

job = my_cron.new(command=str(sys.executable + ' ' + pwd + '/cronCleanUp.py'),
comment='OrganiseDesktop')
# Create a new cron job
job = my_cron.new(command=f"{sys.executable} {pwd}/cronCleanUp.py", comment='OrganiseDesktop')

job.day.every(1)
# Set the job to run every day at a specific time (e.g., 2:00 AM)
job.setall("0 2 * * *") # This sets the time to 2:00 AM every day

my_cron.write()
else:
if not os.path.isfile(pwd + '\\cronCleanUp.pyw'):
call('copy' + pwd + '\\cronCleanUp.py' + pwd + '\\cronCleanUp.pyw', shell=True)

call('SCHTASKS /Create /SC DAILY /TN OrganiseDesktop /TR' + pwd + '\\cronCleanUp.pyw /F',
else: # For Windows
# Copy the script if it doesn't exist
if not os.path.isfile(os.path.join(pwd, 'cronCleanUp.pyw')):
call(f'copy {os.path.join(pwd, "cronCleanUp.py")} {os.path.join(pwd, "cronCleanUp.pyw")}', shell=True)

# Create a new scheduled task
call(f'SCHTASKS /Create /SC DAILY /TN OrganiseDesktop /TR "{os.path.join(pwd, "cronCleanUp.pyw")}" /F',
shell=True)


def schedule_end():

"""Removes the schedule if one is defined"""

os.remove('./settings.txt')
# Remove the settings file
if os.path.isfile('./settings.txt'):
os.remove('./settings.txt')

if sys.platform == 'darwin' or sys.platform == 'linux':
# Remove the scheduled job based on the operating system
if sys.platform in ['darwin', 'linux']:
my_cron = CronTab(user=getpass.getuser())
my_cron.remove_all(comment='OrganiseDesktop')
my_cron.write()

else:
call('SCHTASKS /Delete /TN OrganiseDesktop /F',
shell=True)
else: # For Windows
call('SCHTASKS /Delete /TN OrganiseDesktop /F', shell=True)
62 changes: 62 additions & 0 deletions organise_desktop/duplicateCleaner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import os
import hashlib

def calculate_file_hash(file_path, block_size=65536):
"""
Calculate and return MD5 hash of a file for duplicate detection.
"""
hash_md5 = hashlib.md5()
with open(file_path, "rb") as f:
for block in iter(lambda: f.read(block_size), b""):
hash_md5.update(block)
return hash_md5.hexdigest()

def find_duplicate_files(directory):
"""
Find and return a dictionary of duplicate files in the directory.
"""
files_hash_map = {}
for root, _, files in os.walk(directory):
for file in files:
file_path = os.path.join(root, file)
file_hash = calculate_file_hash(file_path)

if file_hash in files_hash_map:
files_hash_map[file_hash].append(file_path)
else:
files_hash_map[file_hash] = [file_path]

duplicates = {hash: paths for hash, paths in files_hash_map.items() if len(paths) > 1}
return duplicates

def handle_duplicates(duplicates):
"""
Display duplicate files and ask the user whether to delete them.
"""
if not duplicates:
print("No duplicates found.")
return

print("Duplicate files found:")
for hash_value, file_paths in duplicates.items():
print(f"\nHash: {hash_value}")
for i, file_path in enumerate(file_paths, 1):
print(f"{i}. {file_path}")

delete = input(f"\nDo you want to delete duplicates for hash {hash_value}? (yes/no): ").strip().lower()
if delete == 'yes':
for file_path in file_paths[1:]:
try:
print(f"Deleting {file_path}...")
os.remove(file_path)
except Exception as e:
print(f"Error deleting {file_path}: {e}")

print("Duplicate file cleanup complete.")

def clean_duplicates(directory):
"""
Main function to clean duplicates in the given directory.
"""
duplicates = find_duplicate_files(directory)
handle_duplicates(duplicates)
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.