From 5906a4b0c2a0de25c04652dfc3b857e4465c3df0 Mon Sep 17 00:00:00 2001 From: Manu Date: Sun, 28 Oct 2018 01:24:34 +0800 Subject: [PATCH] Refactor into modular widgets. --- vorta/UI/addrepo.ui | 149 ---------------- vorta/UI/initrepo.ui | 66 ------- vorta/UI/mainwindow.ui | 382 ++-------------------------------------- vorta/UI/repoadd.ui | 143 +++++++++++++++ vorta/UI/repotab.ui | 233 ++++++++++++++++++++++++ vorta/UI/snapshottab.ui | 122 +++++++++++++ vorta/UI/sourcetab.ui | 53 ++++++ vorta/config.py | 2 +- vorta/main.py | 228 ++---------------------- vorta/repo_add.py | 4 +- vorta/repo_tab.py | 136 ++++++++++++++ vorta/snapshots_tab.py | 75 ++++++++ vorta/source_tab.py | 36 ++++ vorta/ssh_keys.py | 28 --- vorta/utils.py | 33 +++- 15 files changed, 861 insertions(+), 829 deletions(-) delete mode 100644 vorta/UI/addrepo.ui delete mode 100644 vorta/UI/initrepo.ui create mode 100644 vorta/UI/repoadd.ui create mode 100644 vorta/UI/repotab.ui create mode 100644 vorta/UI/snapshottab.ui create mode 100644 vorta/UI/sourcetab.ui create mode 100644 vorta/repo_tab.py create mode 100644 vorta/snapshots_tab.py create mode 100644 vorta/source_tab.py delete mode 100644 vorta/ssh_keys.py diff --git a/vorta/UI/addrepo.ui b/vorta/UI/addrepo.ui deleted file mode 100644 index 1681cefb..00000000 --- a/vorta/UI/addrepo.ui +++ /dev/null @@ -1,149 +0,0 @@ - - - AddRepository - - - - 0 - 0 - 600 - 250 - - - - Dialog - - - true - - - - - 0 - 20 - 591 - 271 - - - - - QFormLayout::ExpandingFieldsGrow - - - 20 - - - 20 - - - 20 - - - 20 - - - - - Repository URL: - - - - - - - csvis8xq@csvis8xq.repo.borgbase.com:repo - - - - - - - Encryption: - - - - - - - - 0 - 0 - - - - - Select Encryption Mode - - - - - - - - - - Add - - - - - - - Cancel - - - - - - - - - SSH Key: - - - - - - - - 0 - 0 - - - - - Automatically choose SSH Key (default) - - - - - - - - - - - - - - - Password - - - - - - - true - - - QLineEdit::Password - - - - - - - - - diff --git a/vorta/UI/initrepo.ui b/vorta/UI/initrepo.ui deleted file mode 100644 index 764e8ee5..00000000 --- a/vorta/UI/initrepo.ui +++ /dev/null @@ -1,66 +0,0 @@ - - - Form - - - - 0 - 0 - 600 - 300 - - - - Form - - - - - 0 - 0 - 601 - 301 - - - - - 10 - - - 10 - - - 10 - - - 10 - - - - - - Courier - 12 - - - - true - - - Logging output - - - - - - - Close - - - - - - - - - diff --git a/vorta/UI/mainwindow.ui b/vorta/UI/mainwindow.ui index c1d18aa2..d0adf23e 100644 --- a/vorta/UI/mainwindow.ui +++ b/vorta/UI/mainwindow.ui @@ -54,390 +54,28 @@ - 0 + 2 - + + + + 0 + 0 + + Repository - - - - 10 - 10 - 351 - 16 - - - - Remote repository. The place to store your backups. - - - - - - 0 - 240 - 771 - 171 - - - - - QFormLayout::ExpandingFieldsGrow - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - 10 - - - 25 - - - - - Encryption: - - - - - - - - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - - - Original Size: - - - - - - - - - - - - - - Deduplicated Size: - - - - - - - Compressed Size: - - - - - - - - - - - - - - - - - - - - - - - 0 - 30 - 769 - 197 - - - - - QFormLayout::AllNonFixedFieldsGrow - - - 10 - - - 5 - - - 25 - - - - - Repository: - - - - - - - - 1 - 0 - - - - - Select Backup Destination - - - - - - - - <html><head/><body><p>Need hosting for your Borg repository? <a href="https://www.borgbackup.org/support/commercial.html"><span style=" text-decoration: underline; color:#0000ff;">View providers</span></a>.</p></body></html> - - - true - - - - - - - SSH Key: - - - - - - - - Automatically choose SSH Key (default) - - - - - Create New Key - - - - - - - - Compression: - - - - - - - - - - To access repository securely. - - - - - - + Sources - - - - 10 - 40 - 721 - 371 - - - - - - - - - - - - Add Folder - - - - - - - Remove Folder - - - - - - - - - - - 20 - 10 - 361 - 16 - - - - Choose the folders to back up. - - - + Snapshots - - - - -10 - 0 - 791 - 401 - - - - - 10 - - - 10 - - - 10 - - - 10 - - - - - false - - - false - - - true - - - false - - - - Name - - - - - Date - - - - - - - - true - - - <html><head/><body><p>To mount snapshots, first install &quot;FUSE for macOS&quot; from <a href="https://osxfuse.github.io/"><span style=" text-decoration: underline; color:#0000ff;">here</span></a>.</p></body></html> - - - - - - - - - - 0 - 0 - - - - Mount - - - - - - - false - - - - 0 - 0 - - - - Delete - - - - - - - false - - - - 0 - 0 - - - - Refresh - - - - - - - diff --git a/vorta/UI/repoadd.ui b/vorta/UI/repoadd.ui new file mode 100644 index 00000000..647816c4 --- /dev/null +++ b/vorta/UI/repoadd.ui @@ -0,0 +1,143 @@ + + + AddRepository + + + + 0 + 0 + 600 + 250 + + + + Dialog + + + true + + + + + + QFormLayout::ExpandingFieldsGrow + + + 20 + + + 20 + + + 20 + + + 20 + + + + + Repository URL: + + + + + + + csvis8xq@csvis8xq.repo.borgbase.com:repo + + + + + + + Encryption: + + + + + + + + 0 + 0 + + + + + Select Encryption Mode + + + + + + + + + + Add + + + + + + + Cancel + + + + + + + + + SSH Key: + + + + + + + + 0 + 0 + + + + + Automatically choose SSH Key (default) + + + + + + + + + + + + + + + Password + + + + + + + true + + + QLineEdit::Password + + + + + + + + + + diff --git a/vorta/UI/repotab.ui b/vorta/UI/repotab.ui new file mode 100644 index 00000000..f2dc19a8 --- /dev/null +++ b/vorta/UI/repotab.ui @@ -0,0 +1,233 @@ + + + Form + + + true + + + + 0 + 0 + 831 + 461 + + + + + 0 + 0 + + + + Form + + + + + + + 0 + 0 + + + + Remote repository. The place to store your backups. + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + 10 + + + 5 + + + 25 + + + + + Repository: + + + + + + + + 1 + 0 + + + + + Select Backup Destination + + + + + + + + <html><head/><body><p>Need hosting for your Borg repository? <a href="https://www.borgbackup.org/support/commercial.html"><span style=" text-decoration: underline; color:#0000ff;">View providers</span></a>.</p></body></html> + + + true + + + + + + + SSH Key: + + + + + + + + Automatically choose SSH Key (default) + + + + + Create New Key + + + + + + + + Compression: + + + + + + + + + + To access repository securely. + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + 10 + + + 25 + + + + + Encryption: + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + Original Size: + + + + + + + + + + + + + + Deduplicated Size: + + + + + + + Compressed Size: + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vorta/UI/snapshottab.ui b/vorta/UI/snapshottab.ui new file mode 100644 index 00000000..d44f3d88 --- /dev/null +++ b/vorta/UI/snapshottab.ui @@ -0,0 +1,122 @@ + + + Form + + + + 0 + 0 + 799 + 514 + + + + Form + + + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + false + + + false + + + true + + + false + + + + Name + + + + + Date + + + + + + + + true + + + <html><head/><body><p>To mount snapshots, first install &quot;FUSE for macOS&quot; from <a href="https://osxfuse.github.io/"><span style=" text-decoration: underline; color:#0000ff;">here</span></a>.</p></body></html> + + + + + + + + + + 0 + 0 + + + + Mount + + + + + + + false + + + + 0 + 0 + + + + Delete + + + + + + + false + + + + 0 + 0 + + + + Refresh + + + + + + + + + + + + diff --git a/vorta/UI/sourcetab.ui b/vorta/UI/sourcetab.ui new file mode 100644 index 00000000..be68b37d --- /dev/null +++ b/vorta/UI/sourcetab.ui @@ -0,0 +1,53 @@ + + + Form + + + + 0 + 0 + 791 + 497 + + + + Form + + + + + + Choose the folders to back up. + + + + + + + + + + + + + + Add Folder + + + + + + + Remove Folder + + + + + + + + + + + + diff --git a/vorta/config.py b/vorta/config.py index 1b072096..6db8b9b3 100644 --- a/vorta/config.py +++ b/vorta/config.py @@ -9,5 +9,5 @@ SETTINGS_DIR = appdirs.user_data_dir(APP_NAME, APP_AUTHOR) if not os.path.exists(SETTINGS_DIR): os.makedirs(SETTINGS_DIR) -def reset_app(): +def remove_config(): shutil.rmtree(SETTINGS_DIR) diff --git a/vorta/main.py b/vorta/main.py index 9b0b9d0d..bcb53181 100644 --- a/vorta/main.py +++ b/vorta/main.py @@ -2,16 +2,15 @@ import os import platform from datetime import datetime as dt from dateutil import parser -from PyQt5.QtWidgets import QApplication, QFileDialog, QTableWidgetItem, QTableView, QTableWidget -from PyQt5 import uic, QtCore, QtGui, QtWidgets +from PyQt5.QtWidgets import QApplication +from PyQt5 import uic -from .repo_add import AddRepoWindow, ExistingRepoWindow -from .ssh_add import SSHAddWindow -from .config import APP_NAME, reset_app -from .models import RepoModel, SourceDirModel, SnapshotModel, BackupProfileModel -from .ssh_keys import get_private_keys +from .config import APP_NAME, remove_config +from .models import SnapshotModel, BackupProfileModel, SourceDirModel from .borg_runner import BorgThread -from .utils import prettyByes +from .repo_tab import RepoTab +from .source_tab import SourceTab +from .snapshots_tab import SnapshotTab uifile = os.path.join(os.path.dirname(__file__), 'UI/mainwindow.ui') @@ -24,11 +23,12 @@ class MainWindow(MainWindowBase, MainWindowUI): self.setupUi(self) self.setWindowTitle(APP_NAME) self.profile = BackupProfileModel.get(id=1) - self.init_repo() - self.init_source() - self.init_snapshots() - self.init_compression() - self.init_ssh() + + self.repoTab = RepoTab(self.repoTabSlot) + self.repoTab.repo_changed.connect(lambda: print('adf')) + + self.sourceTab = SourceTab(self.sourceTabSlot) + self.snapshotTab = SnapshotTab(self.snapshotTabSlot) self.createStartBtn.clicked.connect(self.create_action) self.actionResetApp.triggered.connect(self.menu_reset) @@ -41,7 +41,7 @@ class MainWindow(MainWindowBase, MainWindowUI): self.createProgressText.repaint() def create_action(self): - n_backup_folders = self.sourceDirectoriesWidget.count() + n_backup_folders = SourceDirModel.select().count() if n_backup_folders == 0: self.set_status('Add some folders to back up first.') return @@ -53,8 +53,8 @@ class MainWindow(MainWindowBase, MainWindowUI): cmd = ['borg', 'create', '--list', '--info', '--log-json', '--json', '-C', self.profile.compression, f'{repo.url}::{platform.node()}-{dt.now().isoformat()}' ] - for i in range(n_backup_folders): - cmd.append(self.sourceDirectoriesWidget.item(i).text()) + for f in SourceDirModel.select(): + cmd.append(f.dir) thread = BorgThread(self, cmd, {}) thread.updated.connect(self.create_update_log) @@ -84,200 +84,8 @@ class MainWindow(MainWindowBase, MainWindowUI): repo.unique_size = stats['unique_size'] repo.total_unique_chunks = stats['total_unique_chunks'] repo.save() - self.init_snapshots() - - def init_source(self): - self.sourceAdd.clicked.connect(self.source_add) - self.sourceRemove.clicked.connect(self.source_remove) - for source in SourceDirModel.select(): - self.sourceDirectoriesWidget.addItem(source.dir) - - def init_ssh(self): - keys = get_private_keys() - for key in keys: - self.sshComboBox.addItem(f'{key["filename"]} ({key["format"]}:{key["fingerprint"]})', key['filename']) - self.sshComboBox.currentIndexChanged.connect(self.ssh_select_action) - - def ssh_select_action(self, index): - if index == 1: - ssh_add_window = SSHAddWindow() - ssh_add_window.setParent(self, QtCore.Qt.Sheet) - ssh_add_window.show() - if ssh_add_window.exec_(): - self.init_ssh() - else: - self.profile.ssh_key = self.sshComboBox.itemData(index) - self.profile.save() - print('set ssh key to', self.profile.ssh_key) - - def init_snapshots(self): - if self.profile.repo: - snapshots = [s for s in self.profile.repo.snapshots.select()] - - for row, snapshot in enumerate(snapshots): - self.snapshotTable.insertRow(row) - self.snapshotTable.setItem(row, 0, QTableWidgetItem(snapshot.name)) - formatted_time = snapshot.time.strftime('%Y-%m-%d %H:%M') - self.snapshotTable.setItem(row, 1, QTableWidgetItem(formatted_time)) - - self.sizeCompressed.setText(prettyByes(self.profile.repo.unique_csize)) - self.sizeDeduplicated.setText(prettyByes(self.profile.repo.unique_size)) - self.sizeOriginal.setText(prettyByes(self.profile.repo.total_size)) - self.repoEncryption.setText(str(self.profile.repo.encryption)) - self.snapshotTable.setRowCount(len(snapshots)) - - header = self.snapshotTable.horizontalHeader() - header.setVisible(True) - header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) - header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) - - self.snapshotTable.setSelectionBehavior(QTableView.SelectRows) - self.snapshotTable.setEditTriggers(QTableView.NoEditTriggers) - - self.snapshotMountButton.clicked.connect(self.snapshot_mount) - self.snapshotDeleteButton.clicked.connect(self.snapshot_mount) - self.snapshotRefreshButton.clicked.connect(self.snapshot_mount) - - def snapshot_mount(self): - cmd = ['borg', 'mount', '--log-json'] - row_selected = self.snapshotTable.selectionModel().selectedRows() - if row_selected: - snapshot_cell = self.snapshotTable.item(row_selected[0].row(), 0) - if snapshot_cell: - snapshot_name = snapshot_cell.text() - cmd.append(f'{self.profile.repo.url}::{snapshot_name}') - else: - cmd.append(f'{self.profile.repo.url}') - else: - cmd.append(f'{self.profile.repo.url}') - - options = QFileDialog.Options() - options |= QFileDialog.ShowDirsOnly - options |= QFileDialog.DontUseNativeDialog - mountPoint = QFileDialog.getExistingDirectory( - self, "Choose Mount Point", "", options=options) - if mountPoint: - cmd.append(mountPoint) - - self.set_status('Mounting snapshot into folder', 0) - thread = BorgThread(self, cmd, {}) - thread.updated.connect(self.mount_update_log) - thread.result.connect(self.mount_get_result) - thread.start() - - def mount_update_log(self, text): - self.mountErrors.setText(text) - - def mount_get_result(self, result): - self.set_status(progress_max=100) - if result['returncode'] == 0: - self.set_status('Mounted successfully.') - - def init_compression(self): - self.repoCompression.addItem('LZ4 (default)', 'lz4') - self.repoCompression.addItem('Zstandard (medium)', 'zstd') - self.repoCompression.addItem('LZMA (high)', 'lzma,6') - self.repoCompression.setCurrentIndex(self.repoCompression.findData(self.profile.compression)) - self.repoCompression.currentIndexChanged.connect(self.compression_select_action) - - def compression_select_action(self, index): - self.profile.compression = self.repoCompression.currentData() - self.profile.save() - - def init_repo(self): - self.repoSelector.model().item(0).setEnabled(False) - self.repoSelector.addItem('Initialize New Repository', 'init') - self.repoSelector.addItem('Add Existing Repository', 'existing') - for repo in RepoModel.select(): - self.repoSelector.addItem(repo.url, repo.id) - - if self.profile.repo: - self.repoSelector.setCurrentIndex(self.repoSelector.findData(self.profile.repo.id)) - self.repoSelector.currentIndexChanged.connect(self.repo_select_action) - - def repo_select_action(self, index): - if index <= 2: - if index == 1: - repo_add_window = AddRepoWindow() - else: - repo_add_window = ExistingRepoWindow() - - repo_add_window.setParent(self, QtCore.Qt.Sheet) - repo_add_window.show() - if repo_add_window.exec_(): - params = repo_add_window.get_values() - - if index == 1: - cmd = ["borg", "init", "--log-json", f"--encryption={params['encryption']}", params['repo_url']] - else: - cmd = ["borg", "list", "--json", params['repo_url']] - - self.set_status('Connecting to repo...', 0) - thread = BorgThread(self, cmd, params) - thread.updated.connect(self.repo_add_update_log) - thread.result.connect(self.repo_add_result) - thread.start() - else: - self.profile.repo = self.repoSelector.currentData() - self.profile.save() - self.init_snapshots() - - def repo_add_update_log(self, text): - self.set_status(text) - - def repo_add_result(self, result): - if result['returncode'] == 0: - self.set_status('Successfully connected to repo.', 100) - new_repo, _ = RepoModel.get_or_create( - url=result['params']['repo_url'], - defaults={ - 'password': result['params']['password'], - # 'encryption': result['params'].get('encryption', '') - } - ) - if 'cache' in result['data']: - stats = result['data']['cache']['stats'] - new_repo.total_size = stats['total_size'] - new_repo.unique_csize = stats['unique_csize'] - new_repo.unique_size = stats['unique_size'] - new_repo.total_unique_chunks = stats['total_unique_chunks'] - new_repo.encryption = result['data']['encryption']['mode'] - new_repo.save() - self.profile.repo = new_repo.id - self.profile.save() - - if 'archives' in result['data'].keys(): - for snapshot in result['data']['archives']: - new_snapshot, _ = SnapshotModel.get_or_create( - snapshot_id=snapshot['id'], - defaults={ - 'repo': new_repo.id, - 'name': snapshot['name'], - 'time': parser.parse(snapshot['time']) - } - ) - new_snapshot.save() - self.repoSelector.addItem(new_repo.url, new_repo.id) - self.repoSelector.setCurrentIndex(self.repoSelector.count()-1) - self.init_snapshots() - - def source_add(self): - options = QFileDialog.Options() - options |= QFileDialog.ShowDirsOnly - options |= QFileDialog.DontUseNativeDialog - fileName = QFileDialog.getExistingDirectory( - self, "Choose Backup Directory", "", options=options) - if fileName: - self.sourceDirectoriesWidget.addItem(fileName) - new_source = SourceDirModel(dir=fileName) - new_source.save() - - def source_remove(self): - item = self.sourceDirectoriesWidget.takeItem(self.sourceDirectoriesWidget.currentRow()) - db_item = SourceDirModel.get(dir=item.text()) - db_item.delete_instance() - item = None + self.snapshotTab.populate() def menu_reset(self): - reset_app() + remove_config() QApplication.instance().quit() diff --git a/vorta/repo_add.py b/vorta/repo_add.py index 87023603..5145d598 100644 --- a/vorta/repo_add.py +++ b/vorta/repo_add.py @@ -1,8 +1,8 @@ import os from PyQt5 import uic -from .ssh_keys import get_private_keys +from .utils import get_private_keys -uifile = os.path.join(os.path.dirname(__file__), 'UI/addrepo.ui') +uifile = os.path.join(os.path.dirname(__file__), 'UI/repoadd.ui') AddRepoUI, AddRepoBase = uic.loadUiType(uifile) diff --git a/vorta/repo_tab.py b/vorta/repo_tab.py new file mode 100644 index 00000000..f3d94f89 --- /dev/null +++ b/vorta/repo_tab.py @@ -0,0 +1,136 @@ +import os +from dateutil import parser +from PyQt5 import uic, QtCore + +from .models import RepoModel, SourceDirModel, SnapshotModel, BackupProfileModel +from .repo_add import AddRepoWindow, ExistingRepoWindow +from .borg_runner import BorgThread +from .utils import prettyBytes, get_private_keys +from .ssh_add import SSHAddWindow + +uifile = os.path.join(os.path.dirname(__file__), 'UI/repotab.ui') +RepoUI, RepoBase = uic.loadUiType(uifile) + + +class RepoTab(RepoBase, RepoUI): + repo_changed = QtCore.pyqtSignal(int) + + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(parent) + self.profile = self.window().profile + + self.repoSelector.model().item(0).setEnabled(False) + self.repoSelector.addItem('Initialize New Repository', 'init') + self.repoSelector.addItem('Add Existing Repository', 'existing') + for repo in RepoModel.select(): + self.repoSelector.addItem(repo.url, repo.id) + + if self.profile.repo: + self.repoSelector.setCurrentIndex(self.repoSelector.findData(self.profile.repo.id)) + self.repoSelector.currentIndexChanged.connect(self.repo_select_action) + + self.repoCompression.addItem('LZ4 (default)', 'lz4') + self.repoCompression.addItem('Zstandard (medium)', 'zstd') + self.repoCompression.addItem('LZMA (high)', 'lzma,6') + self.repoCompression.setCurrentIndex(self.repoCompression.findData(self.profile.compression)) + self.repoCompression.currentIndexChanged.connect(self.compression_select_action) + + self.init_ssh() + self.init_repo_stats() + + def init_repo_stats(self): + self.sizeCompressed.setText(prettyBytes(self.profile.repo.unique_csize)) + self.sizeDeduplicated.setText(prettyBytes(self.profile.repo.unique_size)) + self.sizeOriginal.setText(prettyBytes(self.profile.repo.total_size)) + self.repoEncryption.setText(str(self.profile.repo.encryption)) + self.repo_changed.emit(self.profile.repo.id) + + def init_ssh(self): + keys = get_private_keys() + for key in keys: + self.sshComboBox.addItem(f'{key["filename"]} ({key["format"]}:{key["fingerprint"]})', key['filename']) + self.sshComboBox.currentIndexChanged.connect(self.ssh_select_action) + + def ssh_select_action(self, index): + if index == 1: + ssh_add_window = SSHAddWindow() + ssh_add_window.setParent(self, QtCore.Qt.Sheet) + ssh_add_window.show() + if ssh_add_window.exec_(): + self.init_ssh() + else: + self.profile.ssh_key = self.sshComboBox.itemData(index) + self.profile.save() + print('set ssh key to', self.profile.ssh_key) + + + def compression_select_action(self, index): + self.profile.compression = self.repoCompression.currentData() + self.profile.save() + + def repo_select_action(self, index): + if index <= 2: + if index == 1: + repo_add_window = AddRepoWindow() + else: + repo_add_window = ExistingRepoWindow() + + repo_add_window.setParent(self, QtCore.Qt.Sheet) + repo_add_window.show() + if repo_add_window.exec_(): + params = repo_add_window.get_values() + + if index == 1: + cmd = ["borg", "init", "--log-json", f"--encryption={params['encryption']}", params['repo_url']] + else: + cmd = ["borg", "list", "--json", params['repo_url']] + + self.set_status('Connecting to repo...', 0) + thread = BorgThread(self, cmd, params) + thread.updated.connect(self.repo_add_update_log) + thread.result.connect(self.repo_add_result) + thread.start() + else: + self.profile.repo = self.repoSelector.currentData() + self.profile.save() + self.init_repo_stats() + + def repo_add_update_log(self, text): + self.set_status(text) + + def repo_add_result(self, result): + if result['returncode'] == 0: + self.set_status('Successfully connected to repo.', 100) + new_repo, _ = RepoModel.get_or_create( + url=result['params']['repo_url'], + defaults={ + 'password': result['params']['password'], + # 'encryption': result['params'].get('encryption', '') + } + ) + if 'cache' in result['data']: + stats = result['data']['cache']['stats'] + new_repo.total_size = stats['total_size'] + new_repo.unique_csize = stats['unique_csize'] + new_repo.unique_size = stats['unique_size'] + new_repo.total_unique_chunks = stats['total_unique_chunks'] + new_repo.encryption = result['data']['encryption']['mode'] + new_repo.save() + self.profile.repo = new_repo.id + self.profile.save() + + if 'archives' in result['data'].keys(): + for snapshot in result['data']['archives']: + new_snapshot, _ = SnapshotModel.get_or_create( + snapshot_id=snapshot['id'], + defaults={ + 'repo': new_repo.id, + 'name': snapshot['name'], + 'time': parser.parse(snapshot['time']) + } + ) + new_snapshot.save() + self.repoSelector.addItem(new_repo.url, new_repo.id) + self.repoSelector.setCurrentIndex(self.repoSelector.count()-1) + self.init_snapshots() diff --git a/vorta/snapshots_tab.py b/vorta/snapshots_tab.py new file mode 100644 index 00000000..82686d1d --- /dev/null +++ b/vorta/snapshots_tab.py @@ -0,0 +1,75 @@ +import os +from PyQt5 import uic, QtCore +from PyQt5.QtWidgets import QFileDialog, QTableWidgetItem, QTableView, QHeaderView + +from .borg_runner import BorgThread + +uifile = os.path.join(os.path.dirname(__file__), 'UI/snapshottab.ui') +SnapshotUI, SnapshotBase = uic.loadUiType(uifile) + + +class SnapshotTab(SnapshotBase, SnapshotUI): + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(parent) + self.profile = self.window().profile + + header = self.snapshotTable.horizontalHeader() + header.setVisible(True) + header.setSectionResizeMode(0, QHeaderView.Stretch) + header.setSectionResizeMode(1, QHeaderView.ResizeToContents) + + self.snapshotTable.setSelectionBehavior(QTableView.SelectRows) + self.snapshotTable.setEditTriggers(QTableView.NoEditTriggers) + + self.snapshotMountButton.clicked.connect(self.snapshot_mount) + self.snapshotDeleteButton.clicked.connect(self.snapshot_mount) + self.snapshotRefreshButton.clicked.connect(self.snapshot_mount) + + self.populate() + + def populate(self): + if self.profile.repo: + snapshots = [s for s in self.profile.repo.snapshots.select()] + + for row, snapshot in enumerate(snapshots): + self.snapshotTable.insertRow(row) + self.snapshotTable.setItem(row, 0, QTableWidgetItem(snapshot.name)) + formatted_time = snapshot.time.strftime('%Y-%m-%d %H:%M') + self.snapshotTable.setItem(row, 1, QTableWidgetItem(formatted_time)) + self.snapshotTable.setRowCount(len(snapshots)) + + def snapshot_mount(self): + cmd = ['borg', 'mount', '--log-json'] + row_selected = self.snapshotTable.selectionModel().selectedRows() + if row_selected: + snapshot_cell = self.snapshotTable.item(row_selected[0].row(), 0) + if snapshot_cell: + snapshot_name = snapshot_cell.text() + cmd.append(f'{self.profile.repo.url}::{snapshot_name}') + else: + cmd.append(f'{self.profile.repo.url}') + else: + cmd.append(f'{self.profile.repo.url}') + + options = QFileDialog.Options() + options |= QFileDialog.ShowDirsOnly + options |= QFileDialog.DontUseNativeDialog + mountPoint = QFileDialog.getExistingDirectory( + self, "Choose Mount Point", "", options=options) + if mountPoint: + cmd.append(mountPoint) + + self.set_status('Mounting snapshot into folder', 0) + thread = BorgThread(self, cmd, {}) + thread.updated.connect(self.mount_update_log) + thread.result.connect(self.mount_get_result) + thread.start() + + def mount_update_log(self, text): + self.mountErrors.setText(text) + + def mount_get_result(self, result): + self.set_status(progress_max=100) + if result['returncode'] == 0: + self.set_status('Mounted successfully.') diff --git a/vorta/source_tab.py b/vorta/source_tab.py new file mode 100644 index 00000000..a5e562be --- /dev/null +++ b/vorta/source_tab.py @@ -0,0 +1,36 @@ +import os +from PyQt5 import uic, QtCore +from PyQt5.QtWidgets import QFileDialog +from .models import SourceDirModel + +uifile = os.path.join(os.path.dirname(__file__), 'UI/sourcetab.ui') +SourceUI, SourceBase = uic.loadUiType(uifile) + + +class SourceTab(SourceBase, SourceUI): + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(parent) + self.profile = self.window().profile + + self.sourceAdd.clicked.connect(self.source_add) + self.sourceRemove.clicked.connect(self.source_remove) + for source in SourceDirModel.select(): + self.sourceDirectoriesWidget.addItem(source.dir) + + def source_add(self): + options = QFileDialog.Options() + options |= QFileDialog.ShowDirsOnly + options |= QFileDialog.DontUseNativeDialog + fileName = QFileDialog.getExistingDirectory( + self, "Choose Backup Directory", "", options=options) + if fileName: + self.sourceDirectoriesWidget.addItem(fileName) + new_source = SourceDirModel(dir=fileName) + new_source.save() + + def source_remove(self): + item = self.sourceDirectoriesWidget.takeItem(self.sourceDirectoriesWidget.currentRow()) + db_item = SourceDirModel.get(dir=item.text()) + db_item.delete_instance() + item = None diff --git a/vorta/ssh_keys.py b/vorta/ssh_keys.py deleted file mode 100644 index 1315aee5..00000000 --- a/vorta/ssh_keys.py +++ /dev/null @@ -1,28 +0,0 @@ -import os -from paramiko.rsakey import RSAKey -from paramiko.ecdsakey import ECDSAKey -from paramiko.ed25519key import Ed25519Key -from paramiko import SSHException - - -def get_private_keys(): - key_formats = [RSAKey, ECDSAKey, Ed25519Key] - - ssh_folder = os.path.expanduser('~/.ssh') - - available_private_keys = [] - for key in os.listdir(ssh_folder): - for key_format in key_formats: - try: - parsed_key = key_format.from_private_key_file(os.path.join(ssh_folder, key)) - key_details = { - 'filename': key, - 'format': parsed_key.get_name(), - 'bits': parsed_key.get_bits(), - 'fingerprint': parsed_key.get_fingerprint().hex() - } - available_private_keys.append(key_details) - except (SSHException, UnicodeDecodeError, IsADirectoryError): - continue - - return available_private_keys diff --git a/vorta/utils.py b/vorta/utils.py index bd5d6011..d1d4cf30 100644 --- a/vorta/utils.py +++ b/vorta/utils.py @@ -1,5 +1,36 @@ +import os +from paramiko.rsakey import RSAKey +from paramiko.ecdsakey import ECDSAKey +from paramiko.ed25519key import Ed25519Key +from paramiko import SSHException -def prettyByes(size): + +def get_private_keys(): + """Find SSH keys in standard folder.""" + key_formats = [RSAKey, ECDSAKey, Ed25519Key] + + ssh_folder = os.path.expanduser('~/.ssh') + + available_private_keys = [] + if os.path.isdir(ssh_folder): + for key in os.listdir(ssh_folder): + for key_format in key_formats: + try: + parsed_key = key_format.from_private_key_file(os.path.join(ssh_folder, key)) + key_details = { + 'filename': key, + 'format': parsed_key.get_name(), + 'bits': parsed_key.get_bits(), + 'fingerprint': parsed_key.get_fingerprint().hex() + } + available_private_keys.append(key_details) + except (SSHException, UnicodeDecodeError, IsADirectoryError): + continue + + return available_private_keys + + +def prettyBytes(size): """from https://stackoverflow.com/questions/12523586/ python-format-size-application-converting-b-to-kb-mb-gb-tb/37423778""" if type(size) != int: