def test_repo_add_success(app, qtbot, mocker, borg_json_output): LONG_PASSWORD = '******' # Add new repo window main = app.main_window add_repo_window = AddRepoWindow(main) qtbot.addWidget(add_repo_window) test_repo_url = f'vorta-test-repo.{uuid.uuid4()}.com:repo' # Random repo URL to avoid macOS keychain qtbot.keyClicks(add_repo_window.repoURL, test_repo_url) qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD) stdout, stderr = borg_json_output('info') popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result) qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton) with qtbot.waitSignal(add_repo_window.thread.result, timeout=3000) as blocker: pass main.repoTab.process_new_repo(blocker.args[0]) qtbot.waitUntil(lambda: EventLogModel.select().count() == 2) assert EventLogModel.select().count() == 2 assert RepoModel.get(id=2).url == test_repo_url from vorta.utils import keyring assert keyring.get_password("vorta-repo", RepoModel.get(id=2).url) == LONG_PASSWORD
def init_logs(self): self.logTableWidget.setAlternatingRowColors(True) header = self.logTableWidget.horizontalHeader() header.setVisible(True) [ header.setSectionResizeMode(i, QHeaderView.ResizeToContents) for i in range(5) ] header.setSectionResizeMode(3, QHeaderView.Stretch) self.logTableWidget.setSelectionBehavior(QTableView.SelectRows) self.logTableWidget.setEditTriggers(QTableView.NoEditTriggers) event_logs = [ s for s in EventLogModel.select().order_by( EventLogModel.start_time.desc()) ] for row, log_line in enumerate(event_logs): self.logTableWidget.insertRow(row) formatted_time = log_line.start_time.strftime('%Y-%m-%d %H:%M') self.logTableWidget.setItem(row, 0, QTableWidgetItem(formatted_time)) self.logTableWidget.setItem(row, 1, QTableWidgetItem(log_line.category)) self.logTableWidget.setItem(row, 2, QTableWidgetItem(log_line.subcommand)) self.logTableWidget.setItem(row, 3, QTableWidgetItem(log_line.repo_url)) self.logTableWidget.setItem( row, 4, QTableWidgetItem(str(log_line.returncode))) self.logTableWidget.setRowCount(len(event_logs)) self._draw_next_scheduled_backup()
def test_repo_add_success(qapp, qtbot, mocker, borg_json_output): # Add new repo window main = qapp.main_window main.repoTab.repoSelector.setCurrentIndex(1) add_repo_window = main.repoTab._window test_repo_url = f'vorta-test-repo.{uuid.uuid4()}.com:repo' # Random repo URL to avoid macOS keychain qtbot.keyClicks(add_repo_window.repoURL, test_repo_url) qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD) qtbot.keyClicks(add_repo_window.confirmLineEdit, LONG_PASSWORD) stdout, stderr = borg_json_output('info') popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result) qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton) with qtbot.waitSignal(add_repo_window.thread.result, timeout=3000) as _: pass assert EventLogModel.select().count() == 2 assert RepoModel.get(id=2).url == test_repo_url keyring = VortaKeyring.get_keyring() assert keyring.get_password("vorta-repo", RepoModel.get(id=2).url) == LONG_PASSWORD assert main.repoTab.repoSelector.currentText() == test_repo_url
def populate_logs(self): event_logs = [ s for s in EventLogModel.select().order_by( EventLogModel.start_time.desc()) ] sorting = self.logTableWidget.isSortingEnabled() self.logTableWidget.setSortingEnabled( False) # disable sorting while modifying the table. self.logTableWidget.setRowCount( len(event_logs )) # go ahead and set table length and then update the rows for row, log_line in enumerate(event_logs): formatted_time = log_line.start_time.strftime('%Y-%m-%d %H:%M') self.logTableWidget.setItem(row, LogTableColumn.Time, QTableWidgetItem(formatted_time)) self.logTableWidget.setItem(row, LogTableColumn.Category, QTableWidgetItem(log_line.category)) self.logTableWidget.setItem(row, LogTableColumn.Subcommand, QTableWidgetItem(log_line.subcommand)) self.logTableWidget.setItem(row, LogTableColumn.Repository, QTableWidgetItem(log_line.repo_url)) self.logTableWidget.setItem( row, LogTableColumn.ReturnCode, QTableWidgetItem(str(log_line.returncode))) self.logTableWidget.setSortingEnabled( sorting) # restore sorting now that modifications are done
def test_create(qapp, borg_json_output, mocker, qtbot): main = qapp.main_window stdout, stderr = borg_json_output('create') popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result) qtbot.mouseClick(main.createStartBtn, QtCore.Qt.LeftButton) qtbot.waitUntil(lambda: main.progressText.text().startswith('Backup finished.'), timeout=3000) qtbot.waitUntil(lambda: main.createStartBtn.isEnabled(), timeout=3000) assert EventLogModel.select().count() == 1 assert ArchiveModel.select().count() == 3 assert RepoModel.get(id=1).unique_size == 15520474 assert main.createStartBtn.isEnabled() assert main.archiveTab.archiveTable.rowCount() == 3 assert main.scheduleTab.logTableWidget.rowCount() == 1
def post_backup_tasks(self, profile_id): """ Pruning and checking after successful backup. """ profile = BackupProfileModel.get(id=profile_id) logger.info('Doing post-backup jobs for %s', profile.name) if profile.prune_on: msg = BorgPruneThread.prepare(profile) if msg['ok']: prune_thread = BorgPruneThread(msg['cmd'], msg) prune_thread.start() prune_thread.wait() # Refresh archives msg = BorgListRepoThread.prepare(profile) if msg['ok']: list_thread = BorgListRepoThread(msg['cmd'], msg) list_thread.start() list_thread.wait() validation_cutoff = date.today() - timedelta(days=7 * profile.validation_weeks) recent_validations = EventLogModel.select().where( ( EventLogModel.subcommand == 'check' ) & ( EventLogModel.start_time > validation_cutoff ) & ( EventLogModel.repo_url == profile.repo.url ) ).count() if profile.validation_on and recent_validations == 0: msg = BorgCheckThread.prepare(profile) if msg['ok']: check_thread = BorgCheckThread(msg['cmd'], msg) check_thread.start() check_thread.wait() logger.info('Finished background task for profile %s', profile.name)
def run(self): self.started_event() mutex.lock() log_entry = EventLogModel(category='borg-run', subcommand=self.cmd[1], profile=self.params.get('profile_name', None) ) log_entry.save() logger.info('Running command %s', ' '.join(self.cmd)) p = Popen(self.cmd, stdout=PIPE, stderr=PIPE, bufsize=1, universal_newlines=True, env=self.env, cwd=self.cwd, start_new_session=True) self.process = p # Prevent blocking of stdout/err. Via https://stackoverflow.com/a/7730201/3983708 os.set_blocking(p.stdout.fileno(), False) os.set_blocking(p.stderr.fileno(), False) def read_async(fd): try: return fd.read() except (IOError, TypeError): return '' stdout = [] while True: # Wait for new output select.select([p.stdout, p.stderr], [], [], 0.1) stdout.append(read_async(p.stdout)) stderr = read_async(p.stderr) if stderr: for line in stderr.split('\n'): try: parsed = json.loads(line) if parsed['type'] == 'log_message': self.app.backup_log_event.emit(f'{parsed["levelname"]}: {parsed["message"]}') level_int = getattr(logging, parsed["levelname"]) logger.log(level_int, parsed["message"]) elif parsed['type'] == 'file_status': self.app.backup_log_event.emit(f'{parsed["path"]} ({parsed["status"]})') elif parsed['type'] == 'archive_progress': msg = ( f"{self.category_label['files']}: {parsed['nfiles']}, " f"{self.category_label['original']}: {pretty_bytes(parsed['original_size'])}, " f"{self.category_label['deduplicated']}: {pretty_bytes(parsed['deduplicated_size'])}, " f"{self.category_label['compressed']}: {pretty_bytes(parsed['compressed_size'])}" ) self.app.backup_progress_event.emit(msg) except json.decoder.JSONDecodeError: msg = line.strip() if msg: # Log only if there is something to log. self.app.backup_log_event.emit(msg) logger.warning(msg) if p.poll() is not None: time.sleep(0.1) stdout.append(read_async(p.stdout)) break result = { 'params': self.params, 'returncode': self.process.returncode, 'cmd': self.cmd, } stdout = ''.join(stdout) try: result['data'] = json.loads(stdout) except ValueError: result['data'] = stdout log_entry.returncode = p.returncode log_entry.repo_url = self.params.get('repo_url', None) log_entry.save() self.process_result(result) self.finished_event(result) mutex.unlock()