From cecb1809e6038e05195bec2ec1dc36d87f6c9180 Mon Sep 17 00:00:00 2001 From: wwartana Date: Sat, 31 Jan 2026 15:08:47 +0800 Subject: [PATCH] Switch to Count-Based Retention and update UI --- backup_manager.py | 56 +++++++++++++++++++++++++++++------------------ main.py | 4 ++-- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/backup_manager.py b/backup_manager.py index 1c65706..fcd6d79 100644 --- a/backup_manager.py +++ b/backup_manager.py @@ -219,8 +219,12 @@ class BackupStrategy(ABC): def is_cancelled(self): return self._cancel_flag - def cleanup_old_backups(self, dest, retention_days, job_name=None): - if retention_days <= 0: + def cleanup_old_backups(self, dest, retention_limit, job_name=None): + """ + Count-Based Retention: Keep 'retention_limit' newest files/archives. + Delete the rest. + """ + if retention_limit <= 0: return # 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) else: # 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 if not os.path.exists(scan_root): return - self.log("Menjalankan pembersihan di {} (Retensi: {} hari)...".format(scan_root, retention_days)) - now = time.time() - cutoff = now - (retention_days * 86400) + self.log("Menjalankan pembersihan di {} (Batasan: {} file terbaru)...".format(scan_root, retention_limit)) - count = 0 + all_backups = [] + try: - # Recursive scan for YYYY/MM/DD structure + # 1. Collect all files recursively for root, dirs, files in os.walk(scan_root): for filename in files: filepath = os.path.join(root, filename) try: - # Check modified time - file_time = os.path.getmtime(filepath) - if file_time < cutoff: - os.remove(filepath) - self.log("Menghapus backup lama: {}".format(filename)) - count += 1 - except Exception as e: - self.log("Gagal menghapus {}: {}".format(filename, e)) + mtime = os.path.getmtime(filepath) + all_backups.append((filepath, mtime)) + except: + pass + # 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 for root, dirs, files in os.walk(scan_root, topdown=False): for name in dirs: @@ -266,11 +285,6 @@ class BackupStrategy(ABC): except Exception as 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): """Creates and returns JobName/YYYY/MM/DD subfolder path inside base_dest.""" diff --git a/main.py b/main.py index c28b3d2..3b94878 100644 --- a/main.py +++ b/main.py @@ -501,10 +501,10 @@ class BackupApp(QMainWindow): sched_layout.addStretch() sched_layout.addWidget(QLabel("| Retensi:")) - self.retention_spin = QSpinBox() self.retention_spin.setRange(0, 3650) 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) sched_group.setLayout(sched_layout)