def delete_backup(self): current_item = self.backups_tree_widget.currentItem() if current_item is None: return backup_name = current_item.whatsThis(0) backup = Backups.load(backup_name) password, ret = QInputDialog.getText(self, "Password", "Enter your password:"******"Incorrect password", "Incorrect password") return reply = QMessageBox.question( self, "Confirm delete?", f"Are you sure you want to delete {backup_name}?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply is QMessageBox.Yes: Backups.delete(backup_name) self.populate() self.app.scheduler.reload()
def command_done(self, res): self.setEnabled(True) ret, message = res if not ret: QMessageBox.warning(self.window, "Invalid details", message) return if self.just_save: Backups.save(self.backup) super().accept()
def command_done(self, ret): self.setEnabled(True) if not ret: QMessageBox.warning( self.window, "Invalid details", f"Backup {self.backup.name} has not been initialized") return Backups.save(self.backup) self.window.app.scheduler.reload() super().accept()
def update_logs(self, force=False): current_item = self.backups_tree_widget.currentItem() if current_item is None: return backup_name = current_item.whatsThis(0) backup = Backups.load(backup_name) if not force and (self.log_file_thread.backup is not None and backup_name == self.log_file_thread.backup.name): return self.log_text_edit.clear() self.old_logs_thread = OldLogsThread(backup_name) self.old_logs_thread.started.connect( lambda: self.log_text_edit.setPlainText("Loading logs...")) self.old_logs_thread.result.connect( lambda x: self.got_old_logs(x, backup)) self.old_logs_thread.start() self.log_file_thread.set_backup(backup) self.set_sensitive_actions_status(False) self.backups_tree_widget.setEnabled(False) self.log_text_edit.hide() self.go_action.setEnabled(False) self.stop_action.setEnabled(False) self.only_errors_radio_button.setEnabled(False) self.show_everything_radio_button.setEnabled(False)
def run_all(self, location=None): if location is None: backups = Backups.load_all() else: backups = [ name for name, val in Backups.load_all().items() if val.location == location ] if len(backups) is 0: QMessageBox.information(self, "No backups", "No backups to run") return backups_str = ',\n'.join(backups) reply = QMessageBox.question( self, "Run all?", f"You are about to run {len(backups)} backups: \n\n{backups_str}\n\nContinue?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply is QMessageBox.No: return for backup_name in backups: self.go_backup(backup_name=backup_name)
def populate(self, backup_name=None): self.backups_tree_widget.clear() self.original_item_texts = {} plans = Backups.load_all() if len(plans) is 0: self.hide_regular_widgets() self.welcome_widget.show() return self.show_regular_widgets() self.welcome_widget.hide() selected_item = None for plan in plans.values(): schedule = "Manual" daily = plan.backup_daily_time if daily is not None and plan.backup_days is not None: days_order = { "mon": 0, "tue": 1, "wed": 2, "thu": 3, "fri": 4, "sat": 5, "sun": 6 } days_map = { "mon": "M", "tue": "T", "wed": "W", "thu": "Th", "fri": "F", "sat": "S", "sun": "Su" } time_str = datetime.datetime.strptime( f"{daily.hour()}:{daily.minute()}", "%H:%M").strftime("%I:%M %p") schedule = f"{time_str} | {','.join(days_map[d] for d in sorted(plan.backup_days.split(','), key=lambda x: days_order[x]))}" if plan.every_hour is not None and plan.every_min is not None: schedule = f"Every {plan.every_hour} hour(s) ({plan.every_min} mins)" item = QTreeWidgetItem( [plan.name, plan.location, "Idle", schedule]) item.setWhatsThis(0, plan.name) if backup_name is None: if selected_item is None: selected_item = item else: if plan.name == backup_name: selected_item = item self.backups_tree_widget.addTopLevelItem(item) self.log_file_thread.reset = True self.backups_tree_widget.setCurrentItem(selected_item) self.update_logs(force=True)
def go_backup(self, status=False, backup_name=None): if self.thread is not None: self.app.notify( f"Scheduled backup {backup_name} skipped because a restore is running." ) return if backup_name is None: current_item = self.backups_tree_widget.currentItem() if current_item is None: return backup_name = current_item.whatsThis(0) else: current_item = self.get_item_with_name(backup_name) backup = Backups.load(backup_name) if backup_name in self.threads: self.app.notify( f"Scheduled backup {backup_name} skipped because it's already running." ) return self.app.notify(f"{backup_name} backup started") if backup.paths is None or len(backup.paths) is 0: QMessageBox.warning(self, "Error", "This backup has no folders selected") return current_item.setText(2, "Running") self.status_bar.clearMessage() thread = BackupThread(backup, current_item, self.debug_mode_action.isChecked()) self.threads[backup_name] = thread thread.updated.connect(lambda item, message: item.setText(2, message)) thread.backup_finished.connect(self.backup_finished) thread.stop_initiated.connect( lambda item: item.setText(2, "Stopping backup")) thread.stop_finished.connect(self.backup_stopped) thread.error.connect(self.thread_error) thread.files_skipped.connect(self.files_skipped) thread.start() self.update_logs(force=True) self.set_sensitive_actions_status(False) self.go_action.setEnabled(False)
def delete_older_filename(filename, interval, profile="default", config=CONFIG_FILE, destination=None, **kwargs): """Delete backups matching the given filename older than the given interval string. :type filename: str :param filename: File/directory name. :type interval: str :param interval: Interval string like 1M, 1W, 1M3W4h2s... (s => seconds, m => minutes, h => hours, D => days, W => weeks, M => months, Y => Years). :type destination: str :param destination: glacier|s3|swift :type conf: dict :keyword conf: Override/set AWS configuration. :rtype: list :return: A list containing the deleted keys (S3) or archives (Glacier). """ storage_backend, destination, conf = _get_store_backend(config, destination, profile) session_id = str(uuid.uuid4()) events.before_delete_older_than(session_id) interval_seconds = _interval_string_to_seconds(interval) backup_date_filter = int(datetime.utcnow().strftime("%s")) - interval_seconds deleted = [] for backup in Backups.search_older_than(filename, backup_date_filter, destination=destination, profile=profile, config=config): real_key = backup.stored_filename log.info("Deleting {0}".format(real_key)) storage_backend.delete(real_key) backup.set_deleted() deleted.append(backup) events.on_delete_older_than(session_id, deleted) return deleted
def edit_backup(self): current_item = self.backups_tree_widget.currentItem() if current_item is None: return backup_name = current_item.whatsThis(0) if backup_name in self.threads: return backup = Backups.load(backup_name) password, ret = QInputDialog.getText(self, "Password", "Enter your password:"******"Incorrect password", "Incorrect password") return self.backup_settings(BackupSettings(backup, self, True))
def reload(self): self.remove_all_jobs() backups = Backups.load_all() for backup in backups.values(): trigger = None if backup.backup_daily_time is not None: trigger = CronTrigger(hour=backup.backup_daily_time.hour(), minute=backup.backup_daily_time.minute(), day_of_week=backup.backup_days) if backup.every_hour is not None and backup.every_min is not None: trigger = CronTrigger(hour=f'*/{backup.every_hour}', minute=backup.every_min) if self.get_job(backup.name) is not None and trigger is not None: self.reschedule_job(backup.name, trigger=trigger) elif trigger is not None: self.add_job(func=self.app.start_backup.emit, args=(False, backup.name), trigger=trigger, id=backup.name, misfire_grace_time=180) elif self.get_job(backup.name) is not None and trigger is None: self.remove_job(backup.name)
def view_backup(self): current_item = self.backups_tree_widget.currentItem() if current_item is None: return backup_name = current_item.whatsThis(0) backup = Backups.load(backup_name) password, ret = QInputDialog.getText(self, "Password", "Enter your password:"******"Incorrect password", "Incorrect password") return dialog = RestoreDialog(self, backup) dialog.setParent(self, Qt.Dialog) if dialog.exec_(): self.thread = RestoreThread(backup, dialog.snapshot_id, dialog.restore_dir, dialog.paths, self.debug_mode_action.isChecked()) self.thread.updated.connect( lambda x: self.status_bar.showMessage(x)) self.thread.restore_finished.connect(self.restore_finished) self.thread.error.connect(self.restore_thread_error) self.set_sensitive_actions_status(False) self.set_control_actions_status(False) if backup.name in self.threads and self.threads[ backup.name] is not None: self.app.notify( f"Restore skipped because this backup is running.") return self.thread.start() self.app.notify("Restore started") self.update_logs(force=True)
def validate_plan_name(name): if len(name) is 0: return False, "Backup name must not be empty" if name in Backups.load_all(): return False, "Backup name is already in use" return True, None