Switch to Count-Based Retention and update UI

This commit is contained in:
2026-01-31 15:08:47 +08:00
parent 44bae4552d
commit cecb1809e6
2 changed files with 37 additions and 23 deletions

View File

@@ -219,8 +219,12 @@ class BackupStrategy(ABC):
def is_cancelled(self): def is_cancelled(self):
return self._cancel_flag return self._cancel_flag
def cleanup_old_backups(self, dest, retention_days, job_name=None): def cleanup_old_backups(self, dest, retention_limit, job_name=None):
if retention_days <= 0: """
Count-Based Retention: Keep 'retention_limit' newest files/archives.
Delete the rest.
"""
if retention_limit <= 0:
return return
# SAFETY FIX: Scope cleanup to the specific Job Name folder # SAFETY FIX: Scope cleanup to the specific Job Name folder
@@ -229,32 +233,47 @@ class BackupStrategy(ABC):
scan_root = os.path.join(dest, safe_job_name) scan_root = os.path.join(dest, safe_job_name)
else: else:
# If no job name provided, DO NOT scan the root dest to prevent deleting user files # If no job name provided, DO NOT scan the root dest to prevent deleting user files
# self.log("Warning: Nama pekerjaan tidak ada. Skip pembersihan demi keamanan.")
return return
if not os.path.exists(scan_root): if not os.path.exists(scan_root):
return return
self.log("Menjalankan pembersihan di {} (Retensi: {} hari)...".format(scan_root, retention_days)) self.log("Menjalankan pembersihan di {} (Batasan: {} file terbaru)...".format(scan_root, retention_limit))
now = time.time()
cutoff = now - (retention_days * 86400)
count = 0 all_backups = []
try: try:
# Recursive scan for YYYY/MM/DD structure # 1. Collect all files recursively
for root, dirs, files in os.walk(scan_root): for root, dirs, files in os.walk(scan_root):
for filename in files: for filename in files:
filepath = os.path.join(root, filename) filepath = os.path.join(root, filename)
try: try:
# Check modified time mtime = os.path.getmtime(filepath)
file_time = os.path.getmtime(filepath) all_backups.append((filepath, mtime))
if file_time < cutoff: except:
os.remove(filepath) pass
self.log("Menghapus backup lama: {}".format(filename))
count += 1
except Exception as e:
self.log("Gagal menghapus {}: {}".format(filename, e))
# 2. Sort by time DESCENDING (Newest first)
all_backups.sort(key=lambda x: x[1], reverse=True)
# 3. Check if we have excess
if len(all_backups) > retention_limit:
files_to_delete = all_backups[retention_limit:]
count = 0
for filepath, _ in files_to_delete:
try:
os.remove(filepath)
self.log("Menghapus backup lama (Pruning): {}".format(os.path.basename(filepath)))
count += 1
except Exception as e:
self.log("Gagal menghapus {}: {}".format(os.path.basename(filepath), e))
if count > 0:
self.log("Pembersihan selesai. Dihapus: {} file.".format(count))
else:
self.log("Jumlah file ({}) masih dibawah batas ({}). Tidak ada penghapusan.".format(len(all_backups), retention_limit))
# Optional: Remove empty folders after cleanup # Optional: Remove empty folders after cleanup
for root, dirs, files in os.walk(scan_root, topdown=False): for root, dirs, files in os.walk(scan_root, topdown=False):
for name in dirs: for name in dirs:
@@ -266,11 +285,6 @@ class BackupStrategy(ABC):
except Exception as e: except Exception as e:
self.log("Error saat pembersihan: {}".format(e)) self.log("Error saat pembersihan: {}".format(e))
if count > 0:
self.log("Pembersihan selesai. Menghapus {} file lama.".format(count))
else:
self.log("Pembersihan selesai. Tidak ada file lama ditemukan.")
def _get_dated_dest_dir(self, base_dest, job_name=None): def _get_dated_dest_dir(self, base_dest, job_name=None):
"""Creates and returns JobName/YYYY/MM/DD subfolder path inside base_dest.""" """Creates and returns JobName/YYYY/MM/DD subfolder path inside base_dest."""

View File

@@ -501,10 +501,10 @@ class BackupApp(QMainWindow):
sched_layout.addStretch() sched_layout.addStretch()
sched_layout.addWidget(QLabel("| Retensi:")) sched_layout.addWidget(QLabel("| Retensi:"))
self.retention_spin = QSpinBox()
self.retention_spin.setRange(0, 3650) self.retention_spin.setRange(0, 3650)
self.retention_spin.setSpecialValueText("Selamanya") self.retention_spin.setSpecialValueText("Selamanya")
self.retention_spin.setToolTip("Hapus backup lebih lama dari X hari. 0 = Simpan Selamanya.") self.retention_spin.setSuffix(" File")
self.retention_spin.setToolTip("Simpan X file backup terbaru. File yang lebih lama akan dihapus.")
self.create_spinbox_with_buttons(self.retention_spin, sched_layout) self.create_spinbox_with_buttons(self.retention_spin, sched_layout)
sched_group.setLayout(sched_layout) sched_group.setLayout(sched_layout)