From 77e95c5e41ff74d12ac644932c50f227a00c6737 Mon Sep 17 00:00:00 2001 From: wwartana Date: Fri, 30 Jan 2026 09:55:35 +0800 Subject: [PATCH] feat: simplify backup filename timestamps to hourly granularity by removing minutes and seconds. --- backup_manager.py | 31 ++++++++++++++------- main.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/backup_manager.py b/backup_manager.py index c0f9de3..fe6fd9a 100644 --- a/backup_manager.py +++ b/backup_manager.py @@ -263,10 +263,17 @@ class BackupStrategy(ABC): else: self.log("Pembersihan selesai. Tidak ada file lama ditemukan.") - def _get_dated_dest_dir(self, base_dest): - """Creates and returns YYYY/MM/DD subfolder path inside base_dest.""" + def _get_dated_dest_dir(self, base_dest, job_name=None): + """Creates and returns JobName/YYYY/MM/DD subfolder path inside base_dest.""" + + # Add Job Name folder if provided + if job_name: + # Sanitize job name for folder validity + safe_job_name = "".join(c for c in job_name if c.isalnum() or c in (' ', '.', '_', '-')).strip() + base_dest = os.path.join(base_dest, safe_job_name) + now = datetime.datetime.now() - # Create structure: base/YYYY/MM/DD + # Create structure: base/JobName/YYYY/MM/DD dated_path = os.path.join(base_dest, now.strftime("%Y"), now.strftime("%m"), now.strftime("%d")) if not os.path.exists(dated_path): os.makedirs(dated_path, exist_ok=True) @@ -286,8 +293,12 @@ class FileBackup(BackupStrategy): if not os.path.exists(source): raise FileNotFoundError("Sumber tidak ditemukan: {}".format(source)) - # Use dated directory - target_dest = self._get_dated_dest_dir(dest) + if not os.path.exists(source): + raise FileNotFoundError("Sumber tidak ditemukan: {}".format(source)) + + # Use dated directory with Job Name + job_name = config.get('name') + target_dest = self._get_dated_dest_dir(dest, job_name) self.log("Memulai Backup File: {} -> {}".format(source, target_dest)) @@ -414,8 +425,9 @@ class MySQLBackup(BackupStrategy): - # Use dated directory - target_dest = self._get_dated_dest_dir(dest) + # Use dated directory with Job Name + job_name = config.get('name') + target_dest = self._get_dated_dest_dir(dest, job_name) # Log tool location for debugging self.log("Menggunakan mysqldump: {}".format(mysqldump_path)) @@ -551,8 +563,9 @@ class PostgresBackup(BackupStrategy): dest = config.get('dest') all_databases = config.get('all_databases', False) - # Use dated directory - target_dest = self._get_dated_dest_dir(dest) + # Use dated directory with Job Name + job_name = config.get('name') + target_dest = self._get_dated_dest_dir(dest, job_name) # Detect Server Version self.log("Mendeteksi versi server Postgres di {}:{}...".format(host, port)) diff --git a/main.py b/main.py index 023edcb..d01a2bc 100644 --- a/main.py +++ b/main.py @@ -739,6 +739,9 @@ class BackupApp(QMainWindow): def new_job(self): job = ConfigManager.create_new_job() + # Add timestamp prefix as requested + # "kalau buat daftar pekerjaan baru, tambahkan prefix tanggal dan jam" + job['name'] = "{} {}".format(time.strftime("%Y-%m-%d %H:%M"), job['name']) self.jobs.append(job) self.refresh_job_list() @@ -1384,6 +1387,30 @@ class BackupApp(QMainWindow): # Disable if already activated if not self.trial_mode: self.activate_action.setEnabled(False) + # Menu Bar + menubar = self.menuBar() + + # File Menu + file_menu = menubar.addMenu('File') + exit_action = QAction('Keluar', self) + exit_action.triggered.connect(self.close) + file_menu.addAction(exit_action) + + # Settings Menu (Pengaturan) + settings_menu = menubar.addMenu('Pengaturan') + + # Auto Start Action + self.auto_start_action = QAction('Auto Start saat Windows', self) + self.auto_start_action.setCheckable(True) + self.auto_start_action.setChecked(self.check_auto_start_status()) + self.auto_start_action.triggered.connect(self.toggle_auto_start) + settings_menu.addAction(self.auto_start_action) + + # Help Menu + help_menu = menubar.addMenu('Bantuan') + about_action = QAction('Tentang', self) + about_action.triggered.connect(self.show_about) + help_menu.addAction(about_action) quit_action = QAction("Keluar", self) quit_action.triggered.connect(self.quit_app) @@ -1412,6 +1439,44 @@ class BackupApp(QMainWindow): except: pass event.accept() + def check_auto_start_status(self): + """Check if registry key exists for auto start""" + try: + import winreg + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", 0, winreg.KEY_READ) + winreg.QueryValueEx(key, "ProBackupXP") + winreg.CloseKey(key) + return True + except: + return False + + def toggle_auto_start(self, checked): + """Add or remove registry key for auto start""" + import winreg + key_path = r"Software\Microsoft\Windows\CurrentVersion\Run" + app_name = "ProBackupXP" + + try: + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_ALL_ACCESS) + if checked: + # Add to registry + # Use sys.executable for the running exe path + exe_path = '"{}"'.format(os.path.abspath(sys.executable)) + winreg.SetValueEx(key, app_name, 0, winreg.REG_SZ, exe_path) + self.log("Auto Start diaktifkan.") + else: + # Remove from registry + try: + winreg.DeleteValue(key, app_name) + self.log("Auto Start dinonaktifkan.") + except: + pass # Key might not exist + winreg.CloseKey(key) + except Exception as e: + QMessageBox.critical(self, "Error", "Gagal mengubah pengaturan registry:\n{}".format(e)) + # Revert checkbox state if failed + self.auto_start_action.setChecked(not checked) + def show_window(self): self.show() self.raise_() @@ -1492,6 +1557,9 @@ if __name__ == "__main__": ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) except: pass + + +if __name__ == "__main__": app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) # For tray icon