Skip to content

Commit 16baeca

Browse files
committed
Added temp-cleaner.py script
1 parent cb8c38d commit 16baeca

File tree

1 file changed

+314
-0
lines changed

1 file changed

+314
-0
lines changed

Temp-Cleaner/temp-cleaner.py

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
#!/usr/bin/env python
2+
3+
import os
4+
import sys
5+
import logging
6+
import argparse
7+
from datetime import datetime
8+
import platform
9+
10+
11+
class ProgressBar:
12+
def __init__(self, total, *, prefix='Progress:', suffix='Completed', width=50, fill='=', empty=' '):
13+
self.total = total
14+
self.prefix = prefix
15+
self.suffix = suffix
16+
self.width = width
17+
self.fill = fill
18+
self.empty = empty
19+
self.current = 0
20+
21+
def calculate_progress(self):
22+
try:
23+
progress = min(float(self.current) / self.total, 1.0)
24+
except ZeroDivisionError:
25+
progress = 1.0
26+
27+
filled = int(self.width * progress)
28+
bar = f'{self.fill * filled}{self.empty * (self.width - filled)}'
29+
percent = int(progress * 100)
30+
output_str = f'\r{self.prefix} [{bar}] {percent}% ({self.current}/{self.total}) {self.suffix}'
31+
32+
if self.current >= self.total:
33+
output_str += '\n'
34+
return output_str
35+
36+
def print(self):
37+
sys.stdout.write(self.calculate_progress())
38+
sys.stdout.flush()
39+
40+
def update(self, count=1):
41+
self.current = min(self.current + count, self.total)
42+
self.print()
43+
44+
def finish(self):
45+
self.current = self.total
46+
self.print()
47+
48+
49+
class Logger:
50+
__levels = {
51+
'DEBUG': logging.DEBUG,
52+
'INFO': logging.INFO,
53+
'WARNING': logging.WARNING,
54+
'ERROR': logging.ERROR,
55+
'CRITICAL': logging.CRITICAL
56+
}
57+
__default_log_filename = f"temp_cleaner_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
58+
__default_log_format = "%(asctime)s [%(levelname)s]: %(message)s"
59+
60+
def __init__(self):
61+
self.logger = logging.getLogger("TempCleaner")
62+
self.logger.setLevel(logging.INFO)
63+
self.handlers = []
64+
self.logger.propagate = False # Prevent duplicate logging
65+
66+
def add_handler(self, handler):
67+
handler.setFormatter(logging.Formatter(self.__default_log_format))
68+
self.handlers.append(handler)
69+
self.logger.addHandler(handler)
70+
71+
def clear_handlers(self):
72+
for handler in self.handlers:
73+
handler.flush()
74+
self.logger.removeHandler(handler)
75+
handler.close()
76+
self.handlers.clear()
77+
78+
def __del__(self):
79+
self.clear_handlers()
80+
81+
def __file_handler_logic(self, log_filename):
82+
log_file = log_filename if log_filename else self.__default_log_filename
83+
try:
84+
file_handler = logging.FileHandler(log_file)
85+
self.add_handler(file_handler)
86+
except (PermissionError, OSError) as e:
87+
self.logger.error(
88+
f"Failed to create log file '{log_file}': {e}")
89+
# Attempt to create in current directory as fallback
90+
if log_filename:
91+
fallback_file = os.path.basename(log_file)
92+
try:
93+
file_handler = logging.FileHandler(fallback_file)
94+
self.add_handler(file_handler)
95+
self.logger.warning(
96+
f"Using fallback log file: {fallback_file}")
97+
except Exception as e:
98+
self.logger.error(
99+
f"Failed to create fallback log file: {e}")
100+
101+
def configure(self, log_level='INFO', save_log=False, log_filename=None, silent=False):
102+
# Clean up existing handlers
103+
self.clear_handlers()
104+
105+
try:
106+
# Set log level
107+
level = self.__levels.get(log_level.upper(), logging.INFO)
108+
self.logger.setLevel(level)
109+
110+
# Silent mode: use NullHandler
111+
if silent:
112+
self.add_handler(logging.NullHandler())
113+
return self
114+
115+
# Add console handler by default unless silent
116+
self.add_handler(logging.StreamHandler())
117+
118+
# Handle file logging
119+
if save_log or log_filename:
120+
self.__file_handler_logic(log_filename)
121+
122+
except Exception as e:
123+
self.logger.error(f"Failed to configure logger: {e}")
124+
125+
finally:
126+
# Ensure we have at least a NullHandler
127+
if not self.handlers:
128+
self.add_handler(logging.NullHandler())
129+
130+
return self
131+
132+
def get_logger(self):
133+
return self.logger
134+
135+
136+
class ArgsParser:
137+
DEFAULT_CONFIG = {
138+
'--log-level': {
139+
'type': str,
140+
'default': 'INFO',
141+
'help': 'Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)'
142+
},
143+
'--save-log': {
144+
'action': 'store_true',
145+
'help': 'Save log to a file'
146+
},
147+
'--log-filename': {
148+
'type': str,
149+
'help': 'Specify log filename'
150+
},
151+
'--silent': {
152+
'action': 'store_true',
153+
'help': 'Show only the progress without logging details'
154+
}
155+
}
156+
157+
def __init__(self):
158+
description="Temp Files Cleaner that cleans temporary files from system temp directories."
159+
self.parser = argparse.ArgumentParser(description=description)
160+
for arg, params in self.DEFAULT_CONFIG.items():
161+
self.parser.add_argument(arg, **params)
162+
163+
def parse(self):
164+
return self.parser.parse_args()
165+
166+
167+
def does_path_exist(path):
168+
return path is not None and os.path.exists(path)
169+
170+
def get_temp_directories():
171+
temp_dirs = [
172+
os.environ.get('TEMP'),
173+
os.environ.get('TMP')
174+
]
175+
176+
match platform.system():
177+
case 'Windows': # Windows
178+
temp_dirs.extend([
179+
os.path.join('C:', 'Windows', 'Temp'),
180+
os.path.join('C:', 'Windows', 'Prefetch'),
181+
os.path.join('C:', 'Users', os.getlogin(), 'AppData', 'Local', 'Temp')
182+
])
183+
case 'Darwin': # macOS
184+
temp_dirs.extend([
185+
os.path.join(os.path.expanduser('~'), 'Library', 'Caches'),
186+
'/private/var/folders',
187+
'/private/var/tmp',
188+
'/private/tmp'
189+
])
190+
case 'Linux': # Any Linux Distro
191+
temp_dirs.extend([
192+
'/tmp',
193+
'/var/tmp',
194+
os.path.join(os.path.expanduser('~'), '.cache')
195+
])
196+
197+
return list(filter(does_path_exist, temp_dirs))
198+
199+
200+
def delete_directory_contents(path, logger=None, progress_bar=None):
201+
deleted, skipped = 0, 0
202+
203+
if logger is None:
204+
logger = logging.getLogger(__name__)
205+
206+
if not does_path_exist(path):
207+
logger.warning(f"Directory not found: {path}")
208+
return deleted, skipped
209+
210+
if os.path.isdir(path):
211+
try:
212+
# Safely get directory contents
213+
items = os.listdir(path)
214+
except PermissionError as e:
215+
logger.warning(f"Permission denied accessing directory: {path} | Reason: {e}")
216+
skipped += 1
217+
if progress_bar:
218+
progress_bar.update()
219+
return deleted, skipped
220+
except OSError as e:
221+
logger.error(f"OS error accessing directory: {path} | Reason: {e}")
222+
skipped += 1
223+
if progress_bar:
224+
progress_bar.update()
225+
return deleted, skipped
226+
227+
# Process directory contents
228+
for item in items:
229+
try:
230+
item_path = os.path.join(path, item)
231+
recursive_deleted, recursive_skipped = delete_directory_contents(
232+
item_path,
233+
logger,
234+
progress_bar
235+
)
236+
deleted += recursive_deleted
237+
skipped += recursive_skipped
238+
except OSError as e:
239+
logger.error(f"Error processing path: {item_path} | Reason: {e}")
240+
skipped += 1
241+
if progress_bar:
242+
progress_bar.update()
243+
else:
244+
try:
245+
os.remove(path)
246+
logger.info(f"Deleted file: {path}")
247+
deleted += 1
248+
except PermissionError as e:
249+
logger.warning(f"File in use: {path} | Reason: {e}")
250+
skipped += 1
251+
except OSError as e:
252+
logger.error(f"OS error deleting file: {path} | Reason: {e}")
253+
skipped += 1
254+
255+
if progress_bar:
256+
progress_bar.update()
257+
258+
return deleted, skipped
259+
260+
261+
def total_file_counter(directory):
262+
total_files = 0
263+
try:
264+
for root, dirs, files in os.walk(directory):
265+
total_files += len(files) + len(dirs)
266+
return total_files
267+
except (FileNotFoundError, PermissionError, OSError):
268+
return 0
269+
270+
271+
def execute_temp_cleaning(directory_list, cmd_args, logger_instance, progress_bar):
272+
logger = logger_instance.configure(**vars(cmd_args)).get_logger()
273+
274+
if progress_bar:
275+
progress_bar.print()
276+
277+
logger.info("---- Starting safe cleaning of temp directories ----")
278+
279+
for temp_dir in directory_list:
280+
logger.info(f"Cleaning directory: {temp_dir}")
281+
deleted, skipped = delete_directory_contents(
282+
temp_dir,
283+
logger,
284+
progress_bar
285+
)
286+
logger.info(
287+
f"Summary for {temp_dir}: Deleted: {deleted}, Skipped: {skipped}")
288+
289+
if progress_bar:
290+
progress_bar.finish()
291+
292+
logger.info("---- Cleaning completed. Check log for details. ----")
293+
294+
295+
def main():
296+
args_parser = ArgsParser()
297+
logger_instance = Logger()
298+
299+
directory_list = get_temp_directories()
300+
cmd_args = args_parser.parse()
301+
302+
total_temp_files_count = sum(map(total_file_counter, directory_list))
303+
progress_bar = ProgressBar(total_temp_files_count)
304+
305+
execute_temp_cleaning(
306+
directory_list,
307+
cmd_args,
308+
logger_instance,
309+
progress_bar
310+
)
311+
312+
313+
if __name__ == "__main__":
314+
main()

0 commit comments

Comments
 (0)