def __init__(self, master=None, **args): super().__init__(master) # Save our entity and its display self.entity = args['entity'] self.display = sanitize_string(get_display_name(self.entity)) # Get a cached client and initialize a backuper instance with it self.client = get_cached_client() self.backuper = Backuper(self.client, self.entity) self.backuper.on_metadata_change = self.on_metadata_change # Set up the frame itself self.master.title('Backup with {}'.format(self.display)) self.pack(padx=16, pady=16) self.create_widgets() # Download the profile picture in a different thread Thread(target=self.dl_propic).start()
def main(): cnf = configparser.ConfigParser() cnf.read("./example.config.ini") gencnf = cnf["General"] setup_logging(gencnf["Log"]) dbnames = [x for x in cnf.sections() if x.startswith("db-")] for db in dbnames: lines_start = 0 message = "" uploader_name = cnf[db].get("Uploader") from pprint import pprint # pprint(dict(cnf[db])) uploader = globals().get(uploader_name) backup_destination_folder = cnf[db].get( "BackupDestenationFolder", '/var/lib/postgresql/data/backup') mail_recipients = cnf["Mail"].get("Recipients", None) if mail_recipients: with open(gencnf["Log"]) as logfile: lines_start = sum(1 for line in logfile) uploader_args = { 'dest': cnf[db].get('uploaderdestination'), 'host': cnf[db].get('uploaderhost'), 'port': cnf[db].get('uploaderport'), 'user': cnf[db].get('uploaderuser'), 'identity': cnf[db].get('uploaderkey'), 'mount_point': cnf[db].get('uploadermountpoint') } pprint(uploader_args) worker( Backuper(db.replace('db-', ''), backup_destination_folder, cnf[db]["DBHost"], cnf[db]["DBPort"], cnf[db]["DBUser"], cnf[db]["DBPassword"]), cnf, uploader(**uploader_args)) if not mail_recipients: return with open(gencnf["Log"]) as log_file: lines = [l for l in log_file] message += lines[lines_start:] send_mail(mail_recipients, message)
def __init__(self, master=None, **args): super().__init__(master) # Get a cached client to load the dialogs later self.client = get_cached_client() # Set up the frame itself self.master.title('Select a conversation') self.pack() self.create_widgets() # Load previous backups entities self.entities = list(Backuper.enumerate_backups_entities()) self.update_conversation_list() # Load dialogs after the window has loaded (arbitrary 100ms) self.after(ms=100, func=self.on_load)
class BackupWindow(Frame): def __init__(self, master=None, **args): super().__init__(master) # Save our entity and its display self.entity = args['entity'] self.display = sanitize_string(get_display_name(self.entity)) # Get a cached client and initialize a backuper instance with it self.client = get_cached_client() self.backuper = Backuper(self.client, self.entity) self.backuper.on_metadata_change = self.on_metadata_change # Set up the frame itself self.master.title('Backup with {}'.format(self.display)) self.pack(padx=16, pady=16) self.create_widgets() # Download the profile picture in a different thread Thread(target=self.dl_propic).start() def dl_propic(self): self.backuper.update_total_messages() self.entity_card.update_profile_photo(self.backuper.backup_propic()) # Fire the on_metadata to update some visual fields (such as current backup progress) self.on_metadata_change() #region Widgets setup def create_widgets(self): # Title label self.title = Label(self, text='Backup generation for {}'.format(self.display), font='-weight bold -size 18', padding=(16, 0, 16, 16)) self.title.grid(row=0, columnspan=2) # -- Left column self.left_column = Frame(self, padding=(16, 0)) self.left_column.grid(row=1, column=0, sticky=NE) # Resume/pause backup download self.resume_pause = ToggleButton(self.left_column, text='Resume', image=load_png('resume'), checked_text='Pause', checked_image=load_png('pause'), on_toggle=self.resume_pause_backup) self.resume_pause.grid(row=0, sticky=NE) # Save (download) media self.save_media = ToggleButton(self.left_column, text='Save media', image=load_png('download'), checked_text='Cancel', checked_image=load_png('cancel'), on_toggle=self.prompt_save_media) self.save_media_dialog_shown = False self.save_media.grid(row=1, sticky=N) # Export backup self.export = Button(self.left_column, text='Export', image=load_png('export'), compound=LEFT, command=self.do_export) self.export.grid(row=2, sticky=NE) # Delete saved backup self.delete = Button(self.left_column, text='Delete', image=load_png('delete'), compound=LEFT, command=self.delete_backup) self.delete.grid(row=3, sticky=NE) # Margin label self.margin = Label(self.left_column) self.margin.grid(row=4, sticky=NE) # Go back self.back = Button(self.left_column, text='Back', image=load_png('back'), compound=LEFT, command=self.go_back) self.back.grid(row=5, sticky=NE) # -- Right column self.right_column = Frame(self) self.right_column.grid(row=1, column=1, sticky=NSEW) # Let this column (0) expand and contract with the window self.right_column.columnconfigure(0, weight=1) # Entity card showing stats self.entity_card = EntityCard(self.right_column, entity=self.entity, padding=16) self.entity_card.grid(row=0, sticky=EW) # Right bottom column self.bottom_column = Frame(self.right_column, padding=(0, 16, 0, 0)) self.bottom_column.grid(row=1, sticky=EW) # Let this column (0) also expand and contract with the window self.bottom_column.columnconfigure(0, weight=1) # Estimated time left self.etl = Label(self.bottom_column, text='Estimated time left: {}' .format(self.backuper.metadata.get('etl', '???'))) self.etl.grid(row=0, sticky=W) # Download progress bar self.progress = Progressbar(self.bottom_column) self.progress.grid(row=1, sticky=EW) # Downloaded messages/total messages self.text_progress = Label(self.bottom_column, text='???/??? messages saved') self.text_progress.grid(row=2, sticky=E) # Keep a tuple with all the buttons for easy access self.buttons = (self.resume_pause, self.save_media, self.export, self.delete, self.back) #endregion #region Button actions def resume_pause_backup(self): """Resumes or pauses the backup, depending on resume_pause current state""" if self.resume_pause.is_checked: # The button is now checked (paused → resumed) self.toggle_buttons(False, self.resume_pause) self.backuper.start_backup() else: # The button is now unchecked (resumed → paused) self.backuper.stop_backup() self.toggle_buttons(True, self.resume_pause) def prompt_save_media(self): """Prompts the save media dialog, or cancels the current media download""" if self.save_media_dialog_shown: return if self.save_media.is_checked: self.toggle_buttons(False, self.save_media) self.save_media_dialog_shown = True result = SelectMediaDialog.show_dialog(self, size_calculator=self.backuper.calculate_download_size) self.save_media_dialog_shown = False # Start the backup or restore the buttons depending on action if result: # Set a callback on the resulting dictionary result['progress_callback'] = lambda c, t, etl: \ self.update_labels(c, t, 'media downloaded', etl=etl, value_representation=size_to_str) self.backuper.start_media_backup(**result) else: self.save_media.toggle(checked=False) else: self.backuper.stop_backup() self.toggle_buttons(True, self.save_media) def delete_backup(self): """Asks the user whether to delete the current backup and goes back to the previous window""" do_delete = askquestion('Please read carefully', 'Deleting the backup will completely remove the backup directory ' 'with the selected dialog, INCLUDING ANY FILE YOU PLACED IN IT. ' 'Please make sure you did NOT put any personal file under:\n{}.\n\n' 'Also note that this will NOT delete any message from Telegram. ' 'Do you wish to continue?'.format(abspath(self.backuper.backup_dir)), icon='warning') if do_delete == 'yes': self.backuper.delete_backup() showinfo('Backup deleted', 'Your backup with {} has been completely removed. ' 'You will now go back to the dialogs window.'.format(self.display)) self.go_back() def do_export(self): """Runs the export process""" exporter = Exporter(self.backuper.backup_dir, self.display) self.toggle_buttons(enabled=False) exporter.export(callback=self.on_export_callback) def go_back(self): """Goes back to the previous (select dialog) window""" self.master.destroy() # Import the window here to avoid cyclic dependencies from gui.windows import SelectDialogWindow start_app(SelectDialogWindow) #endregion #region Events def on_metadata_change(self): """Occurs when the backuper's metadata changes""" self.update_labels(current=self.backuper.metadata['saved_msgs'], total=self.backuper.metadata['total_msgs'], progress_type='messages saved', etl=self.backuper.metadata['etl']) # Do we have all the messages? have_all = self.backuper.metadata['saved_msgs'] == self.backuper.metadata['total_msgs'] # If the backup finished (we have all the messages), toggle the pause button # The backup must also be running so we can stop it if have_all and self.backuper.backup_running: self.resume_pause.toggle(False) def on_export_callback(self, progress): """Occurs when the exporters progress changes""" self.update_labels(current=progress['exported'], total=progress['total'], progress_type='messages exported', etl=progress['etl']) if progress['exported'] == progress['total']: self.toggle_buttons(enabled=True) def update_labels(self, current, total, progress_type, etl, value_representation=str): """Updates the labels and progress given current/total and estimated time left. Progress type should be "messages saved", "messages exported", etc. value_representation should be a function taking a float value and returning a string""" self.text_progress.config(text='{}/{} {}{}'.format(value_representation(current), value_representation(total), progress_type, ' (completed)' if (current == total) else '')) self.progress.config(maximum=total, value=current) # Strip extra 0's from the end of the string (we don't want "1.00000", for example) etl = str(etl).rstrip('0') # However maybe we stripped 0 seconds and now we have "0:00:", so fix that too if etl[-1] == ':': etl += '00' self.etl.config(text='Estimated time left: {}'.format(etl)) #endregion #region Utilities def toggle_buttons(self, enabled, do_not_toggle=None): """Toggles all the buttons to be either enabled or disabled, except do_not_toggle""" state = NORMAL if enabled else DISABLED for b in self.buttons: if b != do_not_toggle: b.config(state=state)