Auto Temp Cleaner Service – Python Windows Background Cleaner | FuzzuTech
Demo :
Click Video πππ
π§ Features:
-
Auto-clean temp folders every 10 minutes
-
Logs each cleanup cycle
-
Safe dry-run testing
-
Runs as background Windows Service
π Source Code: temp_cleaner_service.py
Code :
"""
temp_cleaner_service.py
Windows Service & fallback daemon to auto-clean temp files.
Dependencies: psutil, pywin32 (Windows service)
Install: pip install psutil pywin32
Windows service usage:
python temp_cleaner_service.py install
python temp_cleaner_service.py start
python temp_cleaner_service.py stop
python temp_cleaner_service.py remove
Non-Windows usage (or for quick testing):
python temp_cleaner_service.py --run
BE CAREFUL: This script deletes files. Use dry_run=True to test first.
"""
import os
import sys
import time
import argparse
import logging
import tempfile
import shutil
from datetime import datetime, timedelta
import psutil
# Try importing win32service only when available (Windows)
IS_WINDOWS = sys.platform.startswith("win")
if IS_WINDOWS:
try:
import win32serviceutil
import win32service
import win32event
import servicemanager
except Exception:
# will run in fallback mode if not properly installed
IS_WINDOWS = False
# -------------------------
# CONFIGURATION - edit as needed
# -------------------------
DRY_RUN = True # True => only log what WOULD be deleted
AGE_DAYS = 7 # delete files older than AGE_DAYS
CYCLE_SECONDS = 60 * 10 # how often to scan (10 minutes)
LOG_FILE = os.path.join(os.path.dirname(__file__), "temp_cleaner.log")
# Add directories to scan (order matters) - default common temp folders
SCAN_DIRS = [
tempfile.gettempdir(), # system temp
os.path.join(os.environ.get("USERPROFILE", ""), "AppData", "Local", "Temp"), # user temp on Windows
r"C:\Windows\Temp", # Windows temp (may require admin)
]
# Exclude sub-paths (absolute or relative substrings)
EXCLUDE_PATTERNS = [
# e.g., r"C:\Windows\Temp\ImportantBackup"
]
# Minimum free disk percent threshold: if disk free% < this, do more aggressive cleanup
MIN_FREE_PERCENT = 10.0
# Files/Extensions to keep (case-insensitive)
KEEP_EXTENSIONS = {".sys", ".dll", ".drv"} # avoid deleting system files
KEEP_FILENAMES = {"pagefile.sys", "hiberfil.sys"} # exact names to keep
# Logging setup
logger = logging.getLogger("TempCleaner")
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler(LOG_FILE)
fh.setLevel(logging.DEBUG)
fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
fh.setFormatter(fmt)
logger.addHandler(fh)
# also console
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(fmt)
logger.addHandler(ch)
# -------------------------
# Helper functions
# -------------------------
def is_excluded(path):
for pat in EXCLUDE_PATTERNS:
if pat and pat in path:
return True
return False
def should_keep(path):
name = os.path.basename(path).lower()
if name in KEEP_FILENAMES:
return True
_, ext = os.path.splitext(name)
if ext and ext.lower() in KEEP_EXTENSIONS:
return True
return False
def age_in_days(path):
try:
mtime = os.path.getmtime(path)
return (time.time() - mtime) / (24 * 3600)
except Exception:
return 0
def safe_remove_file(path):
try:
if DRY_RUN:
logger.info(f"[DRY] Remove file: {path}")
return True
os.remove(path)
logger.info(f"Removed file: {path}")
return True
except PermissionError:
logger.warning(f"Permission denied removing file: {path}")
except FileNotFoundError:
logger.debug(f"File not found during remove (ignored): {path}")
except Exception as e:
logger.exception(f"Error removing file {path}: {e}")
return False
def safe_remove_dir(path):
try:
if DRY_RUN:
logger.info(f"[DRY] Remove dir: {path}")
return True
shutil.rmtree(path)
logger.info(f"Removed directory: {path}")
return True
except PermissionError:
logger.warning(f"Permission denied removing dir: {path}")
except FileNotFoundError:
logger.debug(f"Dir not found (ignored): {path}")
except Exception as e:
logger.exception(f"Error removing dir {path}: {e}")
return False
def disk_free_percent(path):
try:
du = shutil.disk_usage(path)
free_percent = du.free / du.total * 100.0
return free_percent
except Exception:
return 100.0
# -------------------------
# Core cleaning logic
# -------------------------
def scan_and_clean_once():
logger.info("Starting temp scan cycle")
now = time.time()
total_deleted_files = 0
total_deleted_dirs = 0
# detect running processes that may hold locks (optional use)
processes = {p.pid: p.name() for p in psutil.process_iter(attrs=['name'])}
# adjust age threshold based on disk free
aggressive = False
for d in SCAN_DIRS:
try:
free_pct = disk_free_percent(d)
if free_pct < MIN_FREE_PERCENT:
aggressive = True
logger.info(f"Low disk free on {d}: {free_pct:.1f}% -> aggressive cleanup")
except Exception:
continue
effective_age = AGE_DAYS if not aggressive else max(1, AGE_DAYS // 3)
for base in SCAN_DIRS:
if not base or not os.path.exists(base):
logger.debug(f"Scan dir does not exist: {base}")
continue
logger.info(f"Scanning directory: {base}")
for root, dirs, files in os.walk(base, topdown=False):
if is_excluded(root):
logger.debug(f"Excluded folder: {root}")
continue
# files first
for fname in files:
fpath = os.path.join(root, fname)
try:
if is_excluded(fpath) or should_keep(fpath):
logger.debug(f"Keeping (excluded/keeplist): {fpath}")
continue
age_days = age_in_days(fpath)
if age_days >= effective_age:
# attempt to check if file is in use by another process (Windows)
locked = False
try:
# psutil can check open files for processes - expensive, so minimal check
# skip explicit check in default mode for performance
pass
except Exception:
pass
if safe_remove_file(fpath):
total_deleted_files += 1
except Exception as e:
logger.exception(f"Error processing file {fpath}: {e}")
# then directories: remove empty dirs older than threshold
for dname in dirs:
dpath = os.path.join(root, dname)
try:
if is_excluded(dpath):
continue
# only remove if directory is empty
try:
if not os.listdir(dpath):
age_days = age_in_days(dpath)
if age_days >= effective_age:
if safe_remove_dir(dpath):
total_deleted_dirs += 1
except Exception:
# if cannot list, skip
continue
except Exception:
logger.exception(f"Error processing dir {dpath}")
logger.info(f"Scan complete. Files deleted: {total_deleted_files}, Dirs deleted: {total_deleted_dirs}")
return total_deleted_files, total_deleted_dirs
# -------------------------
# Windows Service implementation
# -------------------------
if IS_WINDOWS:
class TempCleanerService(win32serviceutil.ServiceFramework):
_svc_name_ = "TempCleanerService"
_svc_display_name_ = "Temp File Auto Cleaner (FuzzuTech)"
_svc_description_ = "Background service that periodically deletes old temp files."
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
self.stop_requested = False
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)
self.stop_requested = True
def SvcDoRun(self):
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, ''))
self.main()
def main(self):
while not self.stop_requested:
try:
scan_and_clean_once()
except Exception:
logger.exception("Exception in service loop")
# wait with ability to break early
rc = win32event.WaitForSingleObject(self.hWaitStop, CYCLE_SECONDS * 1000)
if rc == win32event.WAIT_OBJECT_0:
break
# -------------------------
# Fallback simple daemon (cross-platform)
# -------------------------
def run_daemon():
logger.info("Running in daemon/background mode (CTRL+C to stop)")
try:
while True:
scan_and_clean_once()
time.sleep(CYCLE_SECONDS)
except KeyboardInterrupt:
logger.info("Daemon stopped by user")
# -------------------------
# CLI / Entrypoint
# -------------------------
def parse_args():
p = argparse.ArgumentParser()
p.add_argument("--run", action="store_true", help="Run in foreground daemon mode (non-service)")
p.add_argument("--dry", action="store_true", help="Run with dry_run=True for testing")
return p.parse_args()
def main():
global DRY_RUN
args = parse_args()
if args.dry:
DRY_RUN = True
logger.info("Running in DRY RUN mode (no deletions will happen)")
if args.run or not IS_WINDOWS:
run_daemon()
return
# on Windows: allow service commands through win32serviceutil
if len(sys.argv) > 1:
# pass through to win32serviceutil
try:
win32serviceutil.HandleCommandLine(TempCleanerService)
except Exception as e:
logger.exception("Service command failed")
else:
# if no args: start the service (when launched by SCM)
win32serviceutil.HandleCommandLine(TempCleanerService)
if __name__ == "__main__":
main()
Comments
Post a Comment