def restore_button_clicked(self): class WaitingThread(QThread): completed = pyqtSignal() def __init__(self, wthread): super(WaitingThread, self).__init__() self.wthread = wthread def __del__(self): self.wait() def run(self): self.wthread.wait() self.completed.emit() if self.backup_searching: if (self.compressing_timer is not None and self.compressing_timer.isActive()): self.compressing_timer.stop() self.backup_searching = False self.finish_backup_saves() main_window = self.get_main_window() status_bar = main_window.statusBar() status_bar.showMessage(_('Restore backup cancelled')) self.restore_button.setText(_('Restore backup')) elif self.backup_compressing: if self.compress_thread is not None: self.backup_current_button.setEnabled(False) self.compress_thread.quit() def completed(): self.finish_backup_saves() delete_path(self.backup_path) self.compress_thread = None waiting_thread = WaitingThread(self.compress_thread) waiting_thread.completed.connect(completed) self.waiting_thread = waiting_thread waiting_thread.start() else: self.finish_backup_saves() delete_path(self.backup_path) self.compress_thread = None self.backup_compressing = False main_window = self.get_main_window() status_bar = main_window.statusBar() status_bar.showMessage(_('Restore backup cancelled')) self.restore_button.setText(_('Restore backup')) elif self.extracting_backup: if self.extracting_thread is not None: self.restore_button.setEnabled(False) self.extracting_thread.quit() def completed(): save_dir = os.path.join(self.game_dir, 'save') delete_path(save_dir) if self.temp_save_dir is not None: retry_rename(self.temp_save_dir, save_dir) self.temp_save_dir = None self.finish_restore_backup() self.extracting_thread = None waiting_thread = WaitingThread(self.extracting_thread) waiting_thread.completed.connect(completed) self.waiting_thread = waiting_thread waiting_thread.start() else: self.finish_restore_backup() self.extracting_thread = None self.extracting_backup = False main_window = self.get_main_window() status_bar = main_window.statusBar() status_bar.showMessage(_('Restore backup cancelled')) else: selection_model = self.backups_table.selectionModel() if selection_model is None or not selection_model.hasSelection(): return selected = selection_model.currentIndex() table_item = self.backups_table.item(selected.row(), 0) selected_info = self.backups[table_item] if not os.path.isfile(selected_info['path']): return backup_previous = not config_true(get_config_value( 'do_not_backup_previous', 'False')) if backup_previous: ''' If restoring the before_last_restore, we rename it to make sure we make a proper backup first. ''' model = selection_model.model() backup_name = model.data(model.index(selected.row(), 0)) before_last_restore_name = _('before_last_restore') if backup_name.lower() == before_last_restore_name.lower(): backup_dir = os.path.join(self.game_dir, 'save_backups') name_lower = backup_name.lower() name_key = alphanum_key(name_lower) max_counter = 1 for entry in scandir(backup_dir): filename, ext = os.path.splitext(entry.name) if ext.lower() == '.zip': filename_lower = filename.lower() filename_key = alphanum_key(filename_lower) counter = filename_key[-1:][0] if len(filename_key) > 1 and isinstance(counter, int): filename_key = filename_key[:-1] if name_key == filename_key: max_counter = max(max_counter, counter) new_backup_name = (before_last_restore_name + str(max_counter + 1)) new_backup_path = os.path.join(backup_dir, new_backup_name + '.zip') if not retry_rename(selected_info['path'], new_backup_path): return selected_info['path'] = new_backup_path def next_step(): self.restore_backup() self.after_backup = next_step self.backup_saves(before_last_restore_name, True) self.restore_button.setEnabled(True) self.restore_button.setText(_('Cancel restore backup')) else: self.restore_backup()
def backup_saves(self, name, single=False): main_window = self.get_main_window() status_bar = main_window.statusBar() if self.game_dir is None: status_bar.showMessage(_('Game directory not found')) if self.after_backup is not None: self.after_backup() self.after_backup = None return save_dir = os.path.join(self.game_dir, 'save') if not os.path.isdir(save_dir): status_bar.showMessage(_('Save directory not found')) if self.after_backup is not None: self.after_backup() self.after_backup = None return self.save_dir = save_dir backup_dir = os.path.join(self.game_dir, 'save_backups') if not os.path.isdir(backup_dir): if os.path.isfile(backup_dir): os.remove(backup_dir) os.makedirs(backup_dir) if single: backup_filename = name + '.zip' self.backup_path = os.path.join(backup_dir, backup_filename) if os.path.isfile(self.backup_path): if not delete_path(self.backup_path): status_bar.showMessage(_('Could not delete previous ' 'backup archive')) return else: ''' Finding a backup filename which does not already exists or is the next backup name based on an incremental counter placed at the end of the filename without the extension. ''' name_lower = name.lower() name_key = alphanum_key(name_lower) if len(name_key) > 1 and isinstance(name_key[-1:][0], int): name_key = name_key[:-1] duplicate_name = False duplicate_basename = False max_counter = 0 for entry in scandir(backup_dir): filename, ext = os.path.splitext(entry.name) if entry.is_file() and ext.lower() == '.zip': filename_lower = filename.lower() if filename_lower == name_lower: duplicate_name = True else: filename_key = alphanum_key(filename_lower) counter = filename_key[-1:][0] if len(filename_key) > 1 and isinstance(counter, int): filename_key = filename_key[:-1] if name_key == filename_key: duplicate_basename = True max_counter = max(max_counter, counter) if duplicate_basename: name_key = alphanum_key(name) if len(name_key) > 1 and isinstance(name_key[-1:][0], int): name_key = name_key[:-1] name_key.append(max_counter + 1) backup_filename = ''.join(map(lambda x: str(x), name_key)) elif duplicate_name: backup_filename = name + '2' else: backup_filename = name backup_filename = backup_filename + '.zip' self.backup_path = os.path.join(backup_dir, backup_filename) self.backup_file = None status_bar.clearMessage() status_bar.busy += 1 compressing_label = QLabel() status_bar.addWidget(compressing_label, 100) self.compressing_label = compressing_label self.compressing_progress_bar = None self.compressing_speed_label = None self.compressing_size_label = None timer = QTimer(self) self.compressing_timer = timer self.backup_searching = True self.backup_compressing = False self.backup_files = deque() self.backup_file_sizes = {} self.backup_scan = None self.next_backup_scans = deque() self.total_backup_size = 0 self.total_files = 0 self.disable_tab() self.get_main_tab().disable_tab() self.get_soundpacks_tab().disable_tab() self.get_settings_tab().disable_tab() self.get_mods_tab().disable_tab() self.get_backups_tab().disable_tab() if self.manual_backup: self.backup_current_button.setText(_('Cancel backup')) self.backup_current_button.setEnabled(True) compressing_label.setText(_('Searching for save files')) def timeout(): if self.backup_scan is None: self.backup_scan = scandir(self.save_dir) else: try: entry = next(self.backup_scan) if entry.is_file(): self.compressing_label.setText( _('Found {filename} in {path}').format( filename=entry.name, path=os.path.dirname(entry.path))) self.backup_files.append(entry.path) self.total_backup_size += entry.stat().st_size self.backup_file_sizes[entry.path ] = entry.stat().st_size self.total_files += 1 elif entry.is_dir(): self.next_backup_scans.append(entry.path) except StopIteration: try: self.backup_scan = scandir( self.next_backup_scans.popleft()) except IndexError: self.backup_searching = False self.backup_compressing = True self.compressing_label.setText( _('Compressing save files')) compressing_speed_label = QLabel() compressing_speed_label.setText(_('{bytes_sec}/s' ).format(bytes_sec=sizeof_fmt(0))) status_bar.addWidget(compressing_speed_label) self.compressing_speed_label = ( compressing_speed_label) compressing_size_label = QLabel() compressing_size_label.setText( '{bytes_read}/{total_bytes}' .format(bytes_read=sizeof_fmt(0), total_bytes=sizeof_fmt(self.total_backup_size)) ) status_bar.addWidget(compressing_size_label) self.compressing_size_label = ( compressing_size_label) progress_bar = QProgressBar() progress_bar.setRange(0, self.total_backup_size) progress_bar.setValue(0) status_bar.addWidget(progress_bar) self.compressing_progress_bar = progress_bar self.comp_size = 0 self.comp_files = 0 self.last_comp_bytes = 0 self.last_comp = datetime.utcnow() self.next_backup_file = None if self.compressing_timer is not None: self.compressing_timer.stop() self.compressing_timer = None self.backup_saves_step2() timer.timeout.connect(timeout) timer.start(0)
def timeout(): try: entry = next(self.backups_scan) filename, ext = os.path.splitext(entry.name) if ext.lower() == '.zip': uncompressed_size = 0 character_count = 0 worlds_set = set() try: with zipfile.ZipFile(entry.path) as zfile: for info in zfile.infolist(): if not info.filename.startswith('save/'): return uncompressed_size += info.file_size path_items = info.filename.split('/') if len(path_items) == 3: save_file = path_items[-1] if save_file.endswith('.sav'): character_count += 1 if save_file in cons.WORLD_FILES: worlds_set.add(path_items[1]) except zipfile.BadZipFile: pass # We found a valid backup compressed_size = entry.stat().st_size modified_date = datetime.fromtimestamp( entry.stat().st_mtime) formated_date = format_datetime(modified_date, format='short', locale=self.app_locale) arrow_date = arrow.get(entry.stat().st_mtime) human_delta = arrow_date.humanize(arrow.utcnow(), locale=self.app_locale) row_index = self.backups_table.rowCount() self.backups_table.insertRow(row_index) flags = (Qt.ItemIsSelectable | Qt.ItemIsEnabled) if uncompressed_size == 0: compression_ratio = 0 else: compression_ratio = 1.0 - (compressed_size / uncompressed_size) rounded_ratio = round(compression_ratio, 4) ratio_percent = format_percent(rounded_ratio, format='#.##%', locale=self.app_locale) if self.previous_selection is not None: if entry.path == self.previous_selection: self.previous_selection_index = row_index fields = ( (filename, alphanum_key(filename)), (human_delta, modified_date), (str(len(worlds_set)), len(worlds_set)), (str(character_count), character_count), (sizeof_fmt(uncompressed_size), uncompressed_size), (sizeof_fmt(compressed_size), compressed_size), (ratio_percent, compression_ratio), (formated_date, modified_date) ) for index, value in enumerate(fields): item = SortEnabledTableWidgetItem(value[0], value[1]) item.setFlags(flags) self.backups_table.setItem(row_index, index, item) if index == 0: self.backups[item] = { 'path': entry.path, 'actual_size': uncompressed_size } except StopIteration: self.update_backups_timer.stop() if self.previous_selection_index is not None: selection_model = self.backups_table.selectionModel() model = selection_model.model() first_index = model.index(self.previous_selection_index, 0) last_index = model.index(self.previous_selection_index, self.backups_table.columnCount() - 1) row_selection = QItemSelection(first_index, last_index) selection_model.select(row_selection, QItemSelectionModel.Select) selection_model.setCurrentIndex(first_index, QItemSelectionModel.Select) self.backups_table.sortItems(1, Qt.DescendingOrder) self.backups_table.horizontalHeader().setSortIndicatorShown( True) if self.after_update_backups is not None: self.after_update_backups() self.after_update_backups = None