Compare commits

...

3 Commits

4 changed files with 53 additions and 50 deletions

View File

@@ -100,9 +100,11 @@ exe = EXE(
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
icon='icon.ico',
)

View File

@@ -219,58 +219,60 @@ 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, target_folder, retention_limit):
"""
Count-Based Retention (Scoped):
Keep 'retention_limit' newest items (files/folders) in 'target_folder'.
Do NOT scan recursively. Do NOT look at other date folders.
"""
if retention_limit <= 0:
return
# SAFETY FIX: Scope cleanup to the specific Job Name folder
if job_name:
safe_job_name = "".join(c for c in job_name if c.isalnum() or c in (' ', '.', '_', '-')).strip()
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.")
if not os.path.exists(target_folder):
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: {} item terbaru)...".format(os.path.basename(target_folder), retention_limit))
count = 0
all_backups = []
try:
# Recursive scan for YYYY/MM/DD structure
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))
# 1. Collect all items in this SPECIFIC folder only (Non-recursive)
# Python 3.4 compatible (os.scandir came in 3.5)
for entry in os.listdir(target_folder):
try:
full_path = os.path.join(target_folder, entry)
# Get modified time
mtime = os.path.getmtime(full_path)
all_backups.append((full_path, mtime))
except:
pass
# Optional: Remove empty folders after cleanup
for root, dirs, files in os.walk(scan_root, topdown=False):
for name in dirs:
d_path = os.path.join(root, name)
# 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:
items_to_delete = all_backups[retention_limit:]
count = 0
for path, _ in items_to_delete:
try:
if not os.listdir(d_path): # Check if empty
os.rmdir(d_path)
except: pass
if os.path.isdir(path):
shutil.rmtree(path)
else:
os.remove(path)
self.log("Pruning item lama: {}".format(os.path.basename(path)))
count += 1
except Exception as e:
self.log("Gagal menghapus {}: {}".format(os.path.basename(path), e))
if count > 0:
self.log("Pembersihan selesai. Dihapus: {} item.".format(count))
else:
self.log("Jumlah item ({}) aman (Batas: {}).".format(len(all_backups), retention_limit))
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."""
@@ -385,11 +387,10 @@ class FileBackup(BackupStrategy):
dst_file = os.path.join(current_dest_dir, file)
shutil.copy2(src_file, dst_file)
# Retention
# Cleanup
retention_days = config.get('retention_days', 0)
if retention_days > 0:
job_name = config.get('name')
self.cleanup_old_backups(dest, retention_days, job_name)
self.cleanup_old_backups(target_dest, retention_days)
self.progress(100)
@@ -552,7 +553,7 @@ class MySQLBackup(BackupStrategy):
self.log("Backup MySQL berhasil diselesaikan.")
self.progress(100)
self.cleanup_old_backups(dest, config.get('retention_days', 0))
self.cleanup_old_backups(target_dest, config.get('retention_days', 0))
except Exception as e:
self.log("Gagal melakukan backup: {}".format(e))
@@ -713,7 +714,7 @@ class PostgresBackup(BackupStrategy):
self.log("Backup PostgreSQL berhasil diselesaikan.")
self.progress(100)
self.cleanup_old_backups(dest, config.get('retention_days', 0))
self.cleanup_old_backups(target_dest, config.get('retention_days', 0))
except Exception as e:
self.log("Gagal melakukan backup: {}".format(e))

Binary file not shown.

View File

@@ -8,7 +8,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QProgressBar, QFileDialog, QMessageBox, QGroupBox,
QSystemTrayIcon, QMenu, QSpinBox, QTabWidget,
QListWidget, QSplitter, QListWidgetItem, QInputDialog, QDialog,
QSizePolicy, QAction, QStyle)
QSizePolicy, QAction, QStyle, QTimeEdit)
from PyQt5.QtCore import Qt, QThread, pyqtSignal as Signal, QTimer, QTime
from PyQt5.QtGui import QIcon
@@ -490,7 +490,6 @@ class BackupApp(QMainWindow):
pd_layout = QHBoxLayout(page_daily)
pd_layout.setContentsMargins(0,0,0,0)
pd_layout.addWidget(QLabel("Pukul:"))
from PyQt5.QtWidgets import QTimeEdit
self.job_time_edit = QTimeEdit()
self.job_time_edit.setDisplayFormat("HH:mm")
self.create_spinbox_with_buttons(self.job_time_edit, pd_layout)
@@ -504,7 +503,8 @@ class BackupApp(QMainWindow):
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)