Skip to content

Commit 6879660

Browse files
Merge: First version
Merge first version into main
2 parents e8e4c09 + 19bbbeb commit 6879660

85 files changed

Lines changed: 7903 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

BatchCleaner.py

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import platform
2+
import ctypes
3+
import sys
4+
from bottle import load
5+
import eel
6+
import os
7+
import random
8+
import string
9+
import jsonpickle
10+
import webbrowser
11+
from pathlib import Path
12+
13+
from app.model import DirectoryListItem
14+
15+
eel.init("web")
16+
17+
file_sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
18+
paths = {}
19+
stats = {"total": 0, "individual": {}}
20+
21+
cleaner_dir = os.path.join(os.path.abspath(Path.home()), ".BatchCleaner")
22+
path_file = os.path.join(cleaner_dir, "paths.json")
23+
stats_file = os.path.join(cleaner_dir, "stats.json")
24+
25+
26+
def get_dir_size(d):
27+
total_size = 0
28+
for entry in os.scandir(d):
29+
if entry.is_file():
30+
total_size += entry.stat().st_size
31+
elif entry.is_dir():
32+
total_size += get_dir_size(entry.path)
33+
return total_size
34+
35+
36+
def size_to_string(s):
37+
size = float(s)
38+
unit_counter = 0
39+
while size > 1024:
40+
size = size / 1024.0
41+
unit_counter += 1
42+
return "" + str(round(size, ndigits=2)) + " " + file_sizes[unit_counter]
43+
44+
45+
def object_to_json(obj, unpicklable=True )-> str:
46+
jsonpickle.set_encoder_options('json', sort_keys=True, indent=4)
47+
return jsonpickle.dumps(obj, indent=4, unpicklable=unpicklable)
48+
49+
50+
def object_to_json_file(obj, path: str) -> None:
51+
with open(path, mode="w") as f:
52+
f.write(object_to_json(obj))
53+
54+
55+
def load_from_disk():
56+
global paths, stats
57+
try:
58+
if os.path.isfile(path_file):
59+
with open(path_file) as f:
60+
paths = jsonpickle.decode(f.read())
61+
if os.path.isfile(stats_file):
62+
with open(stats_file) as f:
63+
stats = jsonpickle.decode(f.read())
64+
except Exception as e:
65+
print("Unable to load config files ", e)
66+
67+
68+
def save_paths():
69+
os.makedirs(cleaner_dir, exist_ok=True)
70+
object_to_json_file(paths, path_file)
71+
72+
73+
def save_stats():
74+
os.makedirs(cleaner_dir, exist_ok=True)
75+
object_to_json_file(stats, stats_file)
76+
77+
78+
def update_stats(path: str, data: float):
79+
if path not in stats["individual"]:
80+
stats["individual"][path] = round(data, ndigits=2)
81+
else:
82+
stats["individual"][path] += round(data, ndigits=2)
83+
84+
85+
def start_application():
86+
load_from_disk()
87+
eel.start("templates/clean.htm", size=(1280, 720), jinja_templates="templates")
88+
89+
90+
91+
def get_random_id():
92+
id = ""
93+
while id == "" or id in paths:
94+
id = "".join(random.choices(string.ascii_uppercase, k=5))
95+
return id
96+
97+
98+
@eel.expose
99+
def change_path(id: str, path: str):
100+
if path == None:
101+
eel.triggerAlert(path + " is not a directory!", "Error", "red")
102+
return False
103+
if id not in paths:
104+
eel.triggerAlert("This entry doesn't exist!", "Error", "red")
105+
return False
106+
elif os.path.isdir(path):
107+
try:
108+
size = size_to_string(get_dir_size(path))
109+
paths[id].path = path
110+
eel.setSize(id, size)
111+
save_paths()
112+
return True
113+
except:
114+
eel.triggerAlert("Access denied!", "Error", "red")
115+
return False
116+
else:
117+
eel.triggerAlert(path + " is not a directory!", "Error", "red")
118+
return True
119+
120+
121+
@eel.expose
122+
def add_path(path: str):
123+
if path == None:
124+
eel.triggerAlert(path + " is not a directory!", "Error", "red")
125+
return
126+
if os.path.isdir(path):
127+
try:
128+
size = size_to_string(get_dir_size(path))
129+
id = get_random_id()
130+
paths[id] = DirectoryListItem(id, paths, path)
131+
eel.addPath(id, path, paths[id].recursive, size)
132+
save_paths()
133+
except Exception as e:
134+
eel.triggerAlert(str(e), "Error", "red")
135+
else:
136+
eel.triggerAlert(path + " is not a directory!", "Error", "red")
137+
138+
139+
@eel.expose
140+
def delete_path(id: str):
141+
if id in paths:
142+
del paths[id]
143+
save_paths()
144+
else:
145+
eel.triggerAlert("This entry doesn't exist!", "Error", "red")
146+
147+
148+
@eel.expose
149+
def set_recursive(id: str, recursive:bool):
150+
if id not in paths:
151+
eel.triggerAlert("This entry doesn't exist!", "Error", "red")
152+
else:
153+
paths[id].recursive = recursive
154+
save_paths()
155+
156+
157+
@eel.expose
158+
def get_paths():
159+
for k, p in paths.items():
160+
eel.addPath(p.id, p.path, p.recursive, size_to_string(get_dir_size(p.path)))
161+
162+
163+
@eel.expose
164+
def get_stats():
165+
return [size_to_string(stats["total"]), list(stats["individual"].keys()), list(stats["individual"].values())]
166+
167+
168+
"""
169+
Returns size of deleted file
170+
"""
171+
def delete_file(path:str) -> int:
172+
try:
173+
size = Path(path).stat().st_size
174+
os.remove(path)
175+
return size
176+
except Exception as e:
177+
eel.logText("Unable to delete " + path + " - " + str(e), "red")
178+
return 0
179+
180+
"""
181+
Returns the total size of all deleted files
182+
"""
183+
def clean_dir(path:str, recursive:bool) -> int:
184+
deleted_size = 0
185+
eel.logText("Processing " + path, "white")
186+
for entry in os.scandir(path):
187+
if entry.is_file():
188+
deleted_size += delete_file(entry.path)
189+
elif recursive and entry.is_dir():
190+
deleted_size += clean_dir(entry.path, recursive=recursive)
191+
try:
192+
os.rmdir(entry.path)
193+
except Exception as e:
194+
eel.logText("Unable to delete directory " + entry.path + " - " + str(e), "orange")
195+
return deleted_size
196+
197+
198+
def _run_cleaner():
199+
eel.disableInput()
200+
eel.logText("Starting cleaning process...", "blue")
201+
total_size = 0
202+
for id, path_obj in paths.items():
203+
size = clean_dir(path_obj.path, path_obj.recursive)
204+
size_str = size_to_string(size)
205+
total_size += size
206+
update_stats(path_obj.path, int(size)/(1024*1024)) # Converting to MB
207+
eel.logText("Deleted " + size_str + " from " + path_obj.path)
208+
eel.setSize(id, size_to_string(get_dir_size(path_obj.path)))
209+
eel.logText("Successfully deleted a total of " + size_to_string(total_size), "green")
210+
eel.logText("Done")
211+
eel.enableInput()
212+
eel.success(size_to_string(total_size))
213+
stats["total"] += total_size
214+
save_stats()
215+
216+
217+
def _open_repo_in_browser():
218+
webbrowser.open("https://github.com/serious-scribbler/BatchCleaner/", new=1)
219+
220+
221+
@eel.expose
222+
def clean_now():
223+
eel.spawn(_run_cleaner)
224+
225+
226+
@eel.expose
227+
def open_repo():
228+
eel.spawn(_open_repo_in_browser)
229+
230+
231+
def is_admin():
232+
try:
233+
return ctypes.windll.shell32.IsUserAnAdmin()
234+
except:
235+
return False
236+
237+
238+
if __name__ == "__main__":
239+
if platform.system() == "Windows":
240+
if "python" not in sys.executable:
241+
import pyi_splash
242+
pyi_splash.close()
243+
if not is_admin():
244+
print(sys.executable)
245+
args = " ".join(sys.argv if "python" in sys.executable else sys.argv[1:])
246+
ret = ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, args, os.path.abspath("."), 1)
247+
if ret < 32:
248+
print("Unable to run with elevated priviledges: " + str(ctypes.WinError(ret)))
249+
print("Launching without elevated priviledges.")
250+
start_application()
251+
252+
else:
253+
start_application()
254+
else:
255+
start_application()
256+

app/model/DirectoryListItem.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
3+
class DirectoryListItem():
4+
5+
def __init__(self, id: str, parent: dict, path: str, recursive:bool = False):
6+
self.parent = parent
7+
self.path = path
8+
self.recursive = recursive
9+
self.id = id
10+
11+
def delete(self):
12+
self.parent

app/model/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .DirectoryListItem import DirectoryListItem

logo/favicon.ico

21.9 KB
Binary file not shown.

logo/glow.png

23 KB
Loading

logo/glow_favicon.png

49 KB
Loading

logo/splash.afdesign

25.7 KB
Binary file not shown.

logo/splash.png

19.5 KB
Loading

logo/white.afdesign

80.8 KB
Binary file not shown.

logo/white.png

16.5 KB
Loading

0 commit comments

Comments
 (0)