From 1b072fc3558f715775850e1199ed176a5b7a206d Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 5 Sep 2020 21:59:44 +0200 Subject: [PATCH 01/64] add function to get path info async --- src/vorta/utils.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index d13f67ee3..875b6f800 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -17,6 +17,7 @@ from paramiko.ed25519key import Ed25519Key from paramiko.rsakey import RSAKey from PyQt5 import QtCore +from PyQt5.QtCore import QFileInfo, QDir, QThread, pyqtSignal from PyQt5.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon from vorta.borg._compatibility import BorgCompatibility @@ -34,6 +35,53 @@ borg_compat = BorgCompatibility() +class FilePathInfoAsync(QThread): + signal = pyqtSignal(str,int,int) + def __init__(self,path): + self.path = path + QThread.__init__(self) + self.exiting = False + + def run(self): + logger.info("running thread to get path=%s...",self.path) + self.filecount = 0 + self.size, self.filecount=get_path_datasize(self.path) + self.sleep(5) + self.signal.emit(self.path,self.size,self.filecount) + + +def get_directory_size(dirPath): + dataSize = 0 + qDir=QDir(dirPath) + qDir.setFilter(QDir.AllEntries | QDir.Hidden | QDir.System | QDir.NoDotAndDotDot) + filescount=qDir.count() + logger.info("file in dir=%u",filescount) + + + for fileInfo in qDir.entryInfoList(): + if fileInfo.isDir(): + logger.info("foldername %s",fileInfo.absoluteFilePath()) + dataSize, ftmp = get_directory_size(fileInfo.absoluteFilePath()) + filescount += ftmp + else: + logger.info("filename %s size=%u",fileInfo.absoluteFilePath(),fileInfo.size()) + dataSize += fileInfo.size() + + return dataSize, filescount + +def get_path_datasize(path): + fileInfo = QFileInfo(path) + folderSize=0 + + if fileInfo.isDir(): + folderSize, filescount = get_directory_size(fileInfo.absoluteFilePath()) + logger.info("path (folder) %s %u elements size now=%u (%s)",fileInfo.absoluteFilePath(),filescount,folderSize,pretty_bytes(folderSize)) + else: + logger.info("path (file) %s size=%u",fileInfo.path(),fileInfo.size()) + folderSize = fileInfo.size() + filescount = 1 + + return folderSize, filescount def nested_dict(): """ From 13fd1a26b41855cee63b916ed8ea2349f89ef9f1 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 5 Sep 2020 22:00:43 +0200 Subject: [PATCH 02/64] change sourcelist to QTableWidget --- src/vorta/assets/UI/sourcetab.ui | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/vorta/assets/UI/sourcetab.ui b/src/vorta/assets/UI/sourcetab.ui index e17d13cd0..271677ca3 100644 --- a/src/vorta/assets/UI/sourcetab.ui +++ b/src/vorta/assets/UI/sourcetab.ui @@ -24,7 +24,10 @@ - + + + true + 0 @@ -37,12 +40,33 @@ 0 + + QAbstractItemView::NoEditTriggers + QAbstractItemView::ExtendedSelection - - true + + QAbstractItemView::SelectRows + + false + + + false + + + false + + + false + + + true + + + true + From 9ec1b20ede4d84188cb0c0aff78474fd8f8a7d2f Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 5 Sep 2020 22:07:44 +0200 Subject: [PATCH 03/64] update for new source control --- src/vorta/views/source_tab.py | 46 +++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 305931995..e8396ce98 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -1,7 +1,7 @@ from PyQt5 import uic from ..models import SourceFileModel, BackupProfileMixin -from ..utils import get_asset, choose_file_dialog -from PyQt5.QtWidgets import QApplication, QMessageBox +from ..utils import get_asset, choose_file_dialog, pretty_bytes, FilePathInfoAsync +from PyQt5.QtWidgets import QApplication, QMessageBox, QTableWidgetItem, QHeaderView import os uifile = get_asset('UI/sourcetab.ui') @@ -9,9 +9,26 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin): + updateThreads = [] + def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) + + headerTxt=["Path","Type","Size","Folder/\nFiles Count"] + + self.sourceFilesWidget.setColumnCount(len(headerTxt)) + header = self.sourceFilesWidget.horizontalHeader() + + header.setVisible(True) + header.setSortIndicatorShown(1) + + header.setSectionResizeMode(0, QHeaderView.Stretch) + header.setSectionResizeMode(1, QHeaderView.ResizeToContents) + header.setSectionResizeMode(2, QHeaderView.ResizeToContents) + header.setSectionResizeMode(3, QHeaderView.ResizeToContents) + + self.sourceFilesWidget.setHorizontalHeaderLabels(headerTxt) self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) self.sourceAddFile.clicked.connect(lambda: self.source_add(want_folder=False)) @@ -20,17 +37,36 @@ def __init__(self, parent=None): self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) self.populate_from_profile() + + def set_path_info(self,path,datasize,filecount): + items = self.sourceFilesWidget.findItems(path,QtCore.Qt.MatchExactly) + for item in items: + self.sourceFilesWidget.item(item.row(),2).setText(pretty_bytes(datasize)) + self.sourceFilesWidget.item(item.row(),3).setText(format(filecount)) + + def add_source_to_table(self,path): + indexRow = self.sourceFilesWidget.rowCount() + self.sourceFilesWidget.insertRow(indexRow) + itemPath = QTableWidgetItem(path) + self.sourceFilesWidget.setItem(indexRow,0,itemPath) + self.sourceFilesWidget.setItem(indexRow,1,QTableWidgetItem("DIR")) + self.sourceFilesWidget.setItem(indexRow,2,QTableWidgetItem("load...")) + self.sourceFilesWidget.setItem(indexRow,3,QTableWidgetItem("load...")) + getDir = FilePathInfoAsync(path) + getDir.signal.connect(self.set_path_info) + self.updateThreads.append(getDir) + getDir.start() def populate_from_profile(self): profile = self.profile() self.excludePatternsField.textChanged.disconnect() self.excludeIfPresentField.textChanged.disconnect() - self.sourceFilesWidget.clear() + self.sourceFilesWidget.clearContents() self.excludePatternsField.clear() self.excludeIfPresentField.clear() for source in SourceFileModel.select().where(SourceFileModel.profile == profile): - self.sourceFilesWidget.addItem(source.dir) + self.add_source_to_table(source.dir) self.excludePatternsField.appendPlainText(profile.exclude_patterns) self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) @@ -43,7 +79,7 @@ def receive(): for dir in dirs: new_source, created = SourceFileModel.get_or_create(dir=dir, profile=self.profile()) if created: - self.sourceFilesWidget.addItem(dir) + self.add_source_to_table(dir) new_source.save() msg = self.tr("Choose directory to back up") if want_folder else self.tr("Choose file(s) to back up") From 768278008ec3e8b86187edf5f2401f646bfadfe7 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 5 Sep 2020 22:14:10 +0200 Subject: [PATCH 04/64] Update source_tab.py --- src/vorta/views/source_tab.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index e8396ce98..eb596c04f 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -1,6 +1,7 @@ from PyQt5 import uic from ..models import SourceFileModel, BackupProfileMixin from ..utils import get_asset, choose_file_dialog, pretty_bytes, FilePathInfoAsync +from PyQt5 import QtCore from PyQt5.QtWidgets import QApplication, QMessageBox, QTableWidgetItem, QHeaderView import os From dc5001ef0d9cfd7d6033033ad0aa713a574336e3 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 6 Sep 2020 20:13:30 +0000 Subject: [PATCH 05/64] add more attributes for paths in source list Not working yet, requires a new DB version I guess. Would appreciate any help in this regard --- src/vorta/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vorta/models.py b/src/vorta/models.py index 0d04b4845..762a5b464 100644 --- a/src/vorta/models.py +++ b/src/vorta/models.py @@ -106,6 +106,9 @@ class Meta: class SourceFileModel(pw.Model): """A folder to be backed up, related to a Backup Configuration.""" dir = pw.CharField() + dirSize = pw.IntegerField() + dirFilesCount = pw.IntegerField() + dirType = pw.BooleanField() profile = pw.ForeignKeyField(BackupProfileModel, default=1) added_at = pw.DateTimeField(default=datetime.utcnow) From 6974a8cb8ee3e95931edc649a8092efb5da7a876 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 6 Sep 2020 20:30:37 +0000 Subject: [PATCH 06/64] add missing code for DB update --- src/vorta/models.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/vorta/models.py b/src/vorta/models.py index 762a5b464..5f7d02a09 100644 --- a/src/vorta/models.py +++ b/src/vorta/models.py @@ -16,7 +16,7 @@ from vorta.i18n import trans_late from vorta.utils import slugify -SCHEMA_VERSION = 15 +SCHEMA_VERSION = 16 db = pw.Proxy() @@ -106,9 +106,9 @@ class Meta: class SourceFileModel(pw.Model): """A folder to be backed up, related to a Backup Configuration.""" dir = pw.CharField() - dirSize = pw.IntegerField() - dirFilesCount = pw.IntegerField() - dirType = pw.BooleanField() + dir_size = pw.IntegerField() + dir_files_count = pw.IntegerField() + dir_type_isdir = pw.BooleanField() profile = pw.ForeignKeyField(BackupProfileModel, default=1) added_at = pw.DateTimeField(default=datetime.utcnow) @@ -365,6 +365,17 @@ def init_db(con=None): 'dont_run_on_metered_networks', pw.BooleanField(default=True)) ) + if current_schema.version < 16: + _apply_schema_update( + current_schema, 16, + migrator.add_column(SourceFileModel._meta.table_name, + 'dir_size', pw.IntegerField(default = 0)), + migrator.add_column(SourceFileModel._meta.table_name, + 'dir_files_count', pw.IntegerField(default = 0)), + migrator.add_column(SourceFileModel._meta.table_name, + 'dir_type_isdir', pw.BooleanField(default = False)) + ) + # Create missing settings and update labels. Leave setting values untouched. for setting in get_misc_settings(): s, created = SettingsModel.get_or_create(key=setting['key'], defaults=setting) From 9e5e62601c27428f2b767507caac70c59b63d201 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Mon, 7 Sep 2020 14:24:31 +0000 Subject: [PATCH 07/64] updated source entry data --- src/vorta/models.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vorta/models.py b/src/vorta/models.py index 5f7d02a09..3444abbcb 100644 --- a/src/vorta/models.py +++ b/src/vorta/models.py @@ -106,9 +106,9 @@ class Meta: class SourceFileModel(pw.Model): """A folder to be backed up, related to a Backup Configuration.""" dir = pw.CharField() - dir_size = pw.IntegerField() - dir_files_count = pw.IntegerField() - dir_type_isdir = pw.BooleanField() + dir_size = pw.BigIntegerField() + dir_files_count = pw.BigIntegerField() + path_isdir = pw.BooleanField() profile = pw.ForeignKeyField(BackupProfileModel, default=1) added_at = pw.DateTimeField(default=datetime.utcnow) @@ -369,11 +369,11 @@ def init_db(con=None): _apply_schema_update( current_schema, 16, migrator.add_column(SourceFileModel._meta.table_name, - 'dir_size', pw.IntegerField(default = 0)), + 'dir_size', pw.BigIntegerField(default = -1)), migrator.add_column(SourceFileModel._meta.table_name, - 'dir_files_count', pw.IntegerField(default = 0)), + 'dir_files_count', pw.BigIntegerField(default = -1)), migrator.add_column(SourceFileModel._meta.table_name, - 'dir_type_isdir', pw.BooleanField(default = False)) + 'path_isdir', pw.BooleanField(default = False)) ) # Create missing settings and update labels. Leave setting values untouched. From 1e28b9109281f67bdf685e968601d97f6c0f3324 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Mon, 7 Sep 2020 14:25:35 +0000 Subject: [PATCH 08/64] updated function for getting a folders size Use native unix commands --- src/vorta/utils.py | 58 ++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 875b6f800..c2ee8fe42 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -7,6 +7,7 @@ import re import sys import unicodedata +import subprocess from collections import defaultdict from datetime import datetime as dt from functools import reduce @@ -44,44 +45,41 @@ def __init__(self,path): def run(self): logger.info("running thread to get path=%s...",self.path) - self.filecount = 0 - self.size, self.filecount=get_path_datasize(self.path) + self.files_count = 0 + self.size, self.files_count = get_path_datasize(self.path) self.sleep(5) - self.signal.emit(self.path,self.size,self.filecount) - + self.signal.emit(self.path,self.size,self.files_count) def get_directory_size(dirPath): - dataSize = 0 - qDir=QDir(dirPath) - qDir.setFilter(QDir.AllEntries | QDir.Hidden | QDir.System | QDir.NoDotAndDotDot) - filescount=qDir.count() - logger.info("file in dir=%u",filescount) - - - for fileInfo in qDir.entryInfoList(): - if fileInfo.isDir(): - logger.info("foldername %s",fileInfo.absoluteFilePath()) - dataSize, ftmp = get_directory_size(fileInfo.absoluteFilePath()) - filescount += ftmp - else: - logger.info("filename %s size=%u",fileInfo.absoluteFilePath(),fileInfo.size()) - dataSize += fileInfo.size() - - return dataSize, filescount + res = subprocess.run('find . | wc -l', cwd=dirPath, shell=True, check=False, + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + + files_count = int(res.stdout.split(b'\t')[0]) + + if files_count > 1: # files count on empty directory is still 1 because of '.', ignore it + res = subprocess.run('du -sb .', cwd=dirPath, shell=True, check=False, + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + data_size = int(res.stdout.split(b'\t')[0]) + else: + data_size = 0 + files_count = 0 + + return data_size, files_count + def get_path_datasize(path): - fileInfo = QFileInfo(path) - folderSize=0 + file_info = QFileInfo(path) + data_size = 0 - if fileInfo.isDir(): - folderSize, filescount = get_directory_size(fileInfo.absoluteFilePath()) - logger.info("path (folder) %s %u elements size now=%u (%s)",fileInfo.absoluteFilePath(),filescount,folderSize,pretty_bytes(folderSize)) + if file_info.isDir(): + data_size, files_count = get_directory_size(file_info.absoluteFilePath()) + logger.info("path (folder) %s %u elements size now=%u (%s)",file_info.absoluteFilePath(),files_count,data_size,pretty_bytes(data_size)) else: - logger.info("path (file) %s size=%u",fileInfo.path(),fileInfo.size()) - folderSize = fileInfo.size() - filescount = 1 + logger.info("path (file) %s size=%u",file_info.path(),file_info.size()) + data_size = file_info.size() + files_count = 1 - return folderSize, filescount + return data_size, files_count def nested_dict(): """ From fef831fe5c04d7f9e6adc4610088cefd5d321849 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Mon, 7 Sep 2020 14:27:09 +0000 Subject: [PATCH 09/64] Improved add/remove functionality It should now work to add items to the source list, and deleting them aswell. Size & files count is calculated once when added, and stored in database then --- src/vorta/views/source_tab.py | 63 +++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index eb596c04f..655eb3706 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -2,6 +2,7 @@ from ..models import SourceFileModel, BackupProfileMixin from ..utils import get_asset, choose_file_dialog, pretty_bytes, FilePathInfoAsync from PyQt5 import QtCore +from PyQt5.QtCore import QFileInfo from PyQt5.QtWidgets import QApplication, QMessageBox, QTableWidgetItem, QHeaderView import os @@ -16,7 +17,7 @@ def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) - headerTxt=["Path","Type","Size","Folder/\nFiles Count"] + headerTxt=["Path","Type","Size","Elements Count"] self.sourceFilesWidget.setColumnCount(len(headerTxt)) header = self.sourceFilesWidget.horizontalHeader() @@ -39,24 +40,40 @@ def __init__(self, parent=None): self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) self.populate_from_profile() - def set_path_info(self,path,datasize,filecount): + def set_path_info(self,path,data_size,files_count): items = self.sourceFilesWidget.findItems(path,QtCore.Qt.MatchExactly) for item in items: - self.sourceFilesWidget.item(item.row(),2).setText(pretty_bytes(datasize)) - self.sourceFilesWidget.item(item.row(),3).setText(format(filecount)) - - def add_source_to_table(self,path): + self.sourceFilesWidget.item(item.row(),2).setText(pretty_bytes(data_size)) + self.sourceFilesWidget.item(item.row(),3).setText(format(files_count)) + db_item = SourceFileModel.get(dir=path) + db_item.dir_size = data_size + db_item.dir_files_count = files_count + db_item.save() + + + def add_source_to_table(self,source,update_data): indexRow = self.sourceFilesWidget.rowCount() self.sourceFilesWidget.insertRow(indexRow) - itemPath = QTableWidgetItem(path) + itemPath = QTableWidgetItem(source.dir) self.sourceFilesWidget.setItem(indexRow,0,itemPath) - self.sourceFilesWidget.setItem(indexRow,1,QTableWidgetItem("DIR")) - self.sourceFilesWidget.setItem(indexRow,2,QTableWidgetItem("load...")) - self.sourceFilesWidget.setItem(indexRow,3,QTableWidgetItem("load...")) - getDir = FilePathInfoAsync(path) - getDir.signal.connect(self.set_path_info) - self.updateThreads.append(getDir) - getDir.start() + if source.path_isdir == True: + self.sourceFilesWidget.setItem(indexRow,1,QTableWidgetItem("")) + else: + self.sourceFilesWidget.setItem(indexRow,1,QTableWidgetItem("")) + if update_data == True: + self.sourceFilesWidget.setItem(indexRow,2,QTableWidgetItem("load...")) + self.sourceFilesWidget.setItem(indexRow,3,QTableWidgetItem("load...")) + getDir = FilePathInfoAsync(source.dir) + getDir.signal.connect(self.set_path_info) + self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? + getDir.start() + else: # Use cached data from DB + if source.dir_size < 0: + self.sourceFilesWidget.setItem(indexRow,2,QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(indexRow,3,QTableWidgetItem("N/A")) + else: + self.sourceFilesWidget.setItem(indexRow,2,QTableWidgetItem(pretty_bytes(source.dir_size))) + self.sourceFilesWidget.setItem(indexRow,3,QTableWidgetItem(format(source.dir_files_count))) def populate_from_profile(self): profile = self.profile() @@ -67,7 +84,7 @@ def populate_from_profile(self): self.excludeIfPresentField.clear() for source in SourceFileModel.select().where(SourceFileModel.profile == profile): - self.add_source_to_table(source.dir) + self.add_source_to_table(source,False) self.excludePatternsField.appendPlainText(profile.exclude_patterns) self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) @@ -78,9 +95,13 @@ def source_add(self, want_folder): def receive(): dirs = dialog.selectedFiles() for dir in dirs: - new_source, created = SourceFileModel.get_or_create(dir=dir, profile=self.profile()) + new_source, created = SourceFileModel.get_or_create(dir=dir, + dir_size=-1, + dir_files_count=-1, + path_isdir=QFileInfo(dir).isDir(), + profile=self.profile()) if created: - self.add_source_to_table(dir) + self.add_source_to_table(new_source,True) new_source.save() msg = self.tr("Choose directory to back up") if want_folder else self.tr("Choose file(s) to back up") @@ -88,14 +109,14 @@ def receive(): dialog.open(receive) def source_remove(self): - indexes = self.sourceFilesWidget.selectionModel().selectedIndexes() + indexes = self.sourceFilesWidget.selectionModel().selectedRows() # sort indexes, starting with lowest indexes.sort() - # remove each selected entry, starting with highest index (otherways, higher indexes become invalid) + # remove each selected row, starting with highest index (otherways, higher indexes become invalid) for index in reversed(indexes): - item = self.sourceFilesWidget.takeItem(index.row()) - db_item = SourceFileModel.get(dir=item.text()) + db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(),0).text()) db_item.delete_instance() + self.sourceFilesWidget.removeRow(index.row()) def save_exclude_patterns(self): profile = self.profile() From c4a9a6d79e3bbcbc426dfa572b344950d28ee592 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Thu, 10 Sep 2020 19:04:37 +0000 Subject: [PATCH 10/64] update properties --- src/vorta/assets/UI/sourcetab.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vorta/assets/UI/sourcetab.ui b/src/vorta/assets/UI/sourcetab.ui index 271677ca3..87b38d0b1 100644 --- a/src/vorta/assets/UI/sourcetab.ui +++ b/src/vorta/assets/UI/sourcetab.ui @@ -62,10 +62,10 @@ false - true + false - true + false From 8d5923b09e0285b14d41ce3a114960b50b6fe09a Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Thu, 10 Sep 2020 19:25:00 +0000 Subject: [PATCH 11/64] Add new option in misc ui Option is to enable/disable calculating source folder size when added to the list --- src/vorta/models.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vorta/models.py b/src/vorta/models.py index 3444abbcb..d78298ebb 100644 --- a/src/vorta/models.py +++ b/src/vorta/models.py @@ -218,6 +218,11 @@ def get_misc_settings(): 'label': trans_late('settings', 'Open main window on startup') }, + { + 'key': 'get_srcpath_datasize', 'value': True, 'type': 'checkbox', + 'label': trans_late('settings', + 'Get Size of Source path when added') + }, { 'key': 'previous_profile_id', 'str_value': '1', 'type': 'internal', 'label': 'Previously selected profile' From acf444e60a7d59398442af91fd9ae86e8b2c5845 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Thu, 10 Sep 2020 19:26:11 +0000 Subject: [PATCH 12/64] Check if new added source's size should be updated --- src/vorta/views/source_tab.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 655eb3706..20b85c8fb 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -1,5 +1,5 @@ from PyQt5 import uic -from ..models import SourceFileModel, BackupProfileMixin +from ..models import SourceFileModel, BackupProfileMixin, SettingsModel from ..utils import get_asset, choose_file_dialog, pretty_bytes, FilePathInfoAsync from PyQt5 import QtCore from PyQt5.QtCore import QFileInfo @@ -101,7 +101,7 @@ def receive(): path_isdir=QFileInfo(dir).isDir(), profile=self.profile()) if created: - self.add_source_to_table(new_source,True) + self.add_source_to_table(new_source,SettingsModel.get(key="get_srcpath_datasize").value) new_source.save() msg = self.tr("Choose directory to back up") if want_folder else self.tr("Choose file(s) to back up") From 8a810971431f7fbf43caa84541b0d23d46082f63 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Thu, 10 Sep 2020 19:40:25 +0000 Subject: [PATCH 13/64] variable name cleanup --- src/vorta/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 228f33ed8..6dd0e134e 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -47,14 +47,14 @@ def run(self): self.sleep(5) self.signal.emit(self.path,self.size,self.files_count) -def get_directory_size(dirPath): - res = subprocess.run('find . | wc -l', cwd=dirPath, shell=True, check=False, +def get_directory_size(path): + res = subprocess.run('find . | wc -l', cwd=path, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) files_count = int(res.stdout.split(b'\t')[0]) if files_count > 1: # files count on empty directory is still 1 because of '.', ignore it - res = subprocess.run('du -sb .', cwd=dirPath, shell=True, check=False, + res = subprocess.run('du -sb .', cwd=path, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) data_size = int(res.stdout.split(b'\t')[0]) else: From 34f23c97c9ae7d0fa382b90fdfbc1e56dd18a54c Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 26 Sep 2020 20:10:41 +0000 Subject: [PATCH 14/64] Add "Update sources" button --- src/vorta/assets/UI/sourcetab.ui | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vorta/assets/UI/sourcetab.ui b/src/vorta/assets/UI/sourcetab.ui index 87b38d0b1..2b8d88603 100644 --- a/src/vorta/assets/UI/sourcetab.ui +++ b/src/vorta/assets/UI/sourcetab.ui @@ -95,13 +95,20 @@ - + Remove + + + + Update Sources + + + From 00d91f72ddeb8b308786794ff192409f4a579428 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 26 Sep 2020 20:11:45 +0000 Subject: [PATCH 15/64] remove debug stuff --- src/vorta/utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 6dd0e134e..99885eee8 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -41,10 +41,8 @@ def __init__(self,path): self.exiting = False def run(self): - logger.info("running thread to get path=%s...",self.path) self.files_count = 0 self.size, self.files_count = get_path_datasize(self.path) - self.sleep(5) self.signal.emit(self.path,self.size,self.files_count) def get_directory_size(path): @@ -70,9 +68,9 @@ def get_path_datasize(path): if file_info.isDir(): data_size, files_count = get_directory_size(file_info.absoluteFilePath()) - logger.info("path (folder) %s %u elements size now=%u (%s)",file_info.absoluteFilePath(),files_count,data_size,pretty_bytes(data_size)) + # logger.info("path (folder) %s %u elements size now=%u (%s)",file_info.absoluteFilePath(),files_count,data_size,pretty_bytes(data_size)) else: - logger.info("path (file) %s size=%u",file_info.path(),file_info.size()) + # logger.info("path (file) %s size=%u",file_info.path(),file_info.size()) data_size = file_info.size() files_count = 1 From ae6eba8af248fad723383909efc619062b1e20af Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 26 Sep 2020 20:17:09 +0000 Subject: [PATCH 16/64] Add stuff to update source infos --- src/vorta/views/source_tab.py | 47 ++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 20b85c8fb..2dcbe83a7 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -35,6 +35,7 @@ def __init__(self, parent=None): self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) self.sourceAddFile.clicked.connect(lambda: self.source_add(want_folder=False)) self.sourceRemove.clicked.connect(self.source_remove) + self.sourcesUpdate.clicked.connect(self.sources_update) self.paste.clicked.connect(self.paste_text) self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) @@ -49,31 +50,39 @@ def set_path_info(self,path,data_size,files_count): db_item.dir_size = data_size db_item.dir_files_count = files_count db_item.save() - + # Remove thread from list when it's done + for thrd in self.updateThreads: + if thrd.objectName() == path: + self.updateThreads.remove(thrd) + + def update_path_info(self,index_row): + path = self.sourceFilesWidget.item(index_row,0).text() + self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem("load...")) + self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem("load...")) + getDir = FilePathInfoAsync(path) + getDir.signal.connect(self.set_path_info) + getDir.setObjectName(path) + self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? + getDir.start() def add_source_to_table(self,source,update_data): - indexRow = self.sourceFilesWidget.rowCount() - self.sourceFilesWidget.insertRow(indexRow) - itemPath = QTableWidgetItem(source.dir) - self.sourceFilesWidget.setItem(indexRow,0,itemPath) + index_row = self.sourceFilesWidget.rowCount() + self.sourceFilesWidget.insertRow(index_row) + item_path = QTableWidgetItem(source.dir) + self.sourceFilesWidget.setItem(index_row,0,item_path) if source.path_isdir == True: - self.sourceFilesWidget.setItem(indexRow,1,QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("")) else: - self.sourceFilesWidget.setItem(indexRow,1,QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("")) if update_data == True: - self.sourceFilesWidget.setItem(indexRow,2,QTableWidgetItem("load...")) - self.sourceFilesWidget.setItem(indexRow,3,QTableWidgetItem("load...")) - getDir = FilePathInfoAsync(source.dir) - getDir.signal.connect(self.set_path_info) - self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? - getDir.start() + self.update_path_info(index_row) else: # Use cached data from DB if source.dir_size < 0: - self.sourceFilesWidget.setItem(indexRow,2,QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(indexRow,3,QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem("N/A")) else: - self.sourceFilesWidget.setItem(indexRow,2,QTableWidgetItem(pretty_bytes(source.dir_size))) - self.sourceFilesWidget.setItem(indexRow,3,QTableWidgetItem(format(source.dir_files_count))) + self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem(pretty_bytes(source.dir_size))) + self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem(format(source.dir_files_count))) def populate_from_profile(self): profile = self.profile() @@ -91,6 +100,10 @@ def populate_from_profile(self): self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) + def sources_update(self): + for row in range(0, self.sourceFilesWidget.rowCount()): + self.update_path_info(row) # Update data for each entry + def source_add(self, want_folder): def receive(): dirs = dialog.selectedFiles() From e54bd42ed4d0e78cb8e33d096aaafbb2e28efe5c Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 27 Sep 2020 08:45:55 +0000 Subject: [PATCH 17/64] Update src/vorta/assets/UI/sourcetab.ui Co-authored-by: Samuel Woon --- src/vorta/assets/UI/sourcetab.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/assets/UI/sourcetab.ui b/src/vorta/assets/UI/sourcetab.ui index 2b8d88603..8a9534505 100644 --- a/src/vorta/assets/UI/sourcetab.ui +++ b/src/vorta/assets/UI/sourcetab.ui @@ -105,7 +105,7 @@ - Update Sources + Recalculate sizes From 65916b1586c03ff9c4e44e9c0a480e3e51fcf87c Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 27 Sep 2020 08:46:27 +0000 Subject: [PATCH 18/64] Update src/vorta/views/source_tab.py Co-authored-by: Samuel Woon --- src/vorta/views/source_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 2dcbe83a7..a9b2b3240 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -71,7 +71,7 @@ def add_source_to_table(self,source,update_data): item_path = QTableWidgetItem(source.dir) self.sourceFilesWidget.setItem(index_row,0,item_path) if source.path_isdir == True: - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("Folder")) else: self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("")) if update_data == True: From 384c2c700cd6ddb937bf993b62c60d0669f9829b Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 27 Sep 2020 08:46:36 +0000 Subject: [PATCH 19/64] Update src/vorta/views/source_tab.py Co-authored-by: Samuel Woon --- src/vorta/views/source_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index a9b2b3240..8455eac0d 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -73,7 +73,7 @@ def add_source_to_table(self,source,update_data): if source.path_isdir == True: self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("Folder")) else: - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("File")) if update_data == True: self.update_path_info(index_row) else: # Use cached data from DB From 7516a74192b7ed785f57a8d57338ea64a1fdf633 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 27 Sep 2020 08:49:34 +0000 Subject: [PATCH 20/64] Update src/vorta/assets/UI/sourcetab.ui Co-authored-by: Samuel Woon --- src/vorta/assets/UI/sourcetab.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/assets/UI/sourcetab.ui b/src/vorta/assets/UI/sourcetab.ui index 8a9534505..a467fe14a 100644 --- a/src/vorta/assets/UI/sourcetab.ui +++ b/src/vorta/assets/UI/sourcetab.ui @@ -95,7 +95,7 @@ - + Remove From b64a486086e9d16500646ee6c5c741072eaef82b Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 27 Sep 2020 17:51:33 +0000 Subject: [PATCH 21/64] run lint --- src/vorta/models.py | 11 +++-------- src/vorta/utils.py | 44 ++++++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/vorta/models.py b/src/vorta/models.py index d78298ebb..d0cebdccf 100644 --- a/src/vorta/models.py +++ b/src/vorta/models.py @@ -218,11 +218,6 @@ def get_misc_settings(): 'label': trans_late('settings', 'Open main window on startup') }, - { - 'key': 'get_srcpath_datasize', 'value': True, 'type': 'checkbox', - 'label': trans_late('settings', - 'Get Size of Source path when added') - }, { 'key': 'previous_profile_id', 'str_value': '1', 'type': 'internal', 'label': 'Previously selected profile' @@ -374,11 +369,11 @@ def init_db(con=None): _apply_schema_update( current_schema, 16, migrator.add_column(SourceFileModel._meta.table_name, - 'dir_size', pw.BigIntegerField(default = -1)), + 'dir_size', pw.BigIntegerField(default=-1)), migrator.add_column(SourceFileModel._meta.table_name, - 'dir_files_count', pw.BigIntegerField(default = -1)), + 'dir_files_count', pw.BigIntegerField(default=-1)), migrator.add_column(SourceFileModel._meta.table_name, - 'path_isdir', pw.BooleanField(default = False)) + 'path_isdir', pw.BooleanField(default=False)) ) # Create missing settings and update labels. Leave setting values untouched. diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 99885eee8..89c005046 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -31,28 +31,36 @@ keyring = VortaKeyring.get_keyring() logger.info('Using %s Keyring implementation.', keyring.__class__.__name__) +network_status_monitor = NetworkStatusMonitor.get_network_status_monitor() +logger.info('Using %s NetworkStatusMonitor implementation.', network_status_monitor.__class__.__name__) + borg_compat = BorgCompatibility() + class FilePathInfoAsync(QThread): - signal = pyqtSignal(str,int,int) - def __init__(self,path): + signal = pyqtSignal(str, int, int) + + def __init__(self, path): self.path = path QThread.__init__(self) self.exiting = False def run(self): + logger.info("running thread to get path=%s...", self.path) self.files_count = 0 self.size, self.files_count = get_path_datasize(self.path) - self.signal.emit(self.path,self.size,self.files_count) + self.sleep(5) + self.signal.emit(self.path, self.size, self.files_count) + -def get_directory_size(path): - res = subprocess.run('find . | wc -l', cwd=path, shell=True, check=False, +def get_directory_size(dirPath): + res = subprocess.run('find . | wc -l', cwd=dirPath, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) files_count = int(res.stdout.split(b'\t')[0]) - if files_count > 1: # files count on empty directory is still 1 because of '.', ignore it - res = subprocess.run('du -sb .', cwd=path, shell=True, check=False, + if files_count > 1: # files count on empty directory is still 1 because of '.', ignore it + res = subprocess.run('du -sb .', cwd=dirPath, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) data_size = int(res.stdout.split(b'\t')[0]) else: @@ -64,28 +72,19 @@ def get_directory_size(path): def get_path_datasize(path): file_info = QFileInfo(path) - data_size = 0 + data_size = 0 if file_info.isDir(): data_size, files_count = get_directory_size(file_info.absoluteFilePath()) - # logger.info("path (folder) %s %u elements size now=%u (%s)",file_info.absoluteFilePath(),files_count,data_size,pretty_bytes(data_size)) + logger.info("path (folder) %s %u elements size now=%u (%s)", + file_info.absoluteFilePath(), files_count, data_size, pretty_bytes(data_size)) else: - # logger.info("path (file) %s size=%u",file_info.path(),file_info.size()) + logger.info("path (file) %s size=%u", file_info.path(), file_info.size()) data_size = file_info.size() files_count = 1 return data_size, files_count -_network_status_monitor = None - - -def get_network_status_monitor(): - global _network_status_monitor - if _network_status_monitor is None: - _network_status_monitor = NetworkStatusMonitor.get_network_status_monitor() - logger.info('Using %s NetworkStatusMonitor implementation.', _network_status_monitor.__class__.__name__) - return _network_status_monitor - def nested_dict(): """ @@ -148,7 +147,7 @@ def get_private_keys(): def pretty_bytes(size): """from https://stackoverflow.com/questions/12523586/ python-format-size-application-converting-b-to-kb-mb-gb-tb/37423778""" - if type(size) != int: + if not isinstance(size, int): return '' power = 1000 # GiB is base 2**10, GB is base 10**3. n = 0 @@ -179,7 +178,7 @@ def get_sorted_wifis(profile): from vorta.models import WifiSettingModel - system_wifis = get_network_status_monitor().get_known_wifis() + system_wifis = network_status_monitor.get_known_wifis() if system_wifis is None: # Don't show any networks if we can't get the current list return [] @@ -295,3 +294,4 @@ def is_system_tray_available(): is_available = tray.isSystemTrayAvailable() return is_available + From f67203ee51d5a2b6f6f41f3ecd8b845229fbad07 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 27 Sep 2020 17:52:07 +0000 Subject: [PATCH 22/64] run lint --- src/vorta/views/source_tab.py | 86 +++++++++++++++-------------------- 1 file changed, 36 insertions(+), 50 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 8455eac0d..828f4f2cc 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -1,5 +1,5 @@ from PyQt5 import uic -from ..models import SourceFileModel, BackupProfileMixin, SettingsModel +from ..models import SourceFileModel, BackupProfileMixin from ..utils import get_asset, choose_file_dialog, pretty_bytes, FilePathInfoAsync from PyQt5 import QtCore from PyQt5.QtCore import QFileInfo @@ -12,19 +12,19 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin): updateThreads = [] - + def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) - - headerTxt=["Path","Type","Size","Elements Count"] + + headerTxt = ["Path", "Type", "Size", "Elements Count"] self.sourceFilesWidget.setColumnCount(len(headerTxt)) header = self.sourceFilesWidget.horizontalHeader() header.setVisible(True) header.setSortIndicatorShown(1) - + header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.setSectionResizeMode(2, QHeaderView.ResizeToContents) @@ -35,54 +35,44 @@ def __init__(self, parent=None): self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) self.sourceAddFile.clicked.connect(lambda: self.source_add(want_folder=False)) self.sourceRemove.clicked.connect(self.source_remove) - self.sourcesUpdate.clicked.connect(self.sources_update) self.paste.clicked.connect(self.paste_text) self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) self.populate_from_profile() - - def set_path_info(self,path,data_size,files_count): - items = self.sourceFilesWidget.findItems(path,QtCore.Qt.MatchExactly) + + def set_path_info(self, path, data_size, files_count): + items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchExactly) for item in items: - self.sourceFilesWidget.item(item.row(),2).setText(pretty_bytes(data_size)) - self.sourceFilesWidget.item(item.row(),3).setText(format(files_count)) + self.sourceFilesWidget.item(item.row(), 2).setText(pretty_bytes(data_size)) + self.sourceFilesWidget.item(item.row(), 3).setText(format(files_count)) db_item = SourceFileModel.get(dir=path) db_item.dir_size = data_size db_item.dir_files_count = files_count db_item.save() - # Remove thread from list when it's done - for thrd in self.updateThreads: - if thrd.objectName() == path: - self.updateThreads.remove(thrd) - - def update_path_info(self,index_row): - path = self.sourceFilesWidget.item(index_row,0).text() - self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem("load...")) - self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem("load...")) - getDir = FilePathInfoAsync(path) - getDir.signal.connect(self.set_path_info) - getDir.setObjectName(path) - self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? - getDir.start() - - def add_source_to_table(self,source,update_data): - index_row = self.sourceFilesWidget.rowCount() - self.sourceFilesWidget.insertRow(index_row) - item_path = QTableWidgetItem(source.dir) - self.sourceFilesWidget.setItem(index_row,0,item_path) + + def add_source_to_table(self, source, update_data): + indexRow = self.sourceFilesWidget.rowCount() + self.sourceFilesWidget.insertRow(indexRow) + itemPath = QTableWidgetItem(source.dir) + self.sourceFilesWidget.setItem(indexRow, 0, itemPath) if source.path_isdir == True: - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("Folder")) + self.sourceFilesWidget.setItem(indexRow, 1, QTableWidgetItem("")) else: - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("File")) + self.sourceFilesWidget.setItem(indexRow, 1, QTableWidgetItem("")) if update_data == True: - self.update_path_info(index_row) - else: # Use cached data from DB + self.sourceFilesWidget.setItem(indexRow, 2, QTableWidgetItem("load...")) + self.sourceFilesWidget.setItem(indexRow, 3, QTableWidgetItem("load...")) + getDir = FilePathInfoAsync(source.dir) + getDir.signal.connect(self.set_path_info) + self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? + getDir.start() + else: # Use cached data from DB if source.dir_size < 0: - self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(indexRow, 2, QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(indexRow, 3, QTableWidgetItem("N/A")) else: - self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem(pretty_bytes(source.dir_size))) - self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem(format(source.dir_files_count))) + self.sourceFilesWidget.setItem(indexRow, 2, QTableWidgetItem(pretty_bytes(source.dir_size))) + self.sourceFilesWidget.setItem(indexRow, 3, QTableWidgetItem(format(source.dir_files_count))) def populate_from_profile(self): profile = self.profile() @@ -93,28 +83,24 @@ def populate_from_profile(self): self.excludeIfPresentField.clear() for source in SourceFileModel.select().where(SourceFileModel.profile == profile): - self.add_source_to_table(source,False) + self.add_source_to_table(source, False) self.excludePatternsField.appendPlainText(profile.exclude_patterns) self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) - def sources_update(self): - for row in range(0, self.sourceFilesWidget.rowCount()): - self.update_path_info(row) # Update data for each entry - def source_add(self, want_folder): def receive(): dirs = dialog.selectedFiles() for dir in dirs: - new_source, created = SourceFileModel.get_or_create(dir=dir, - dir_size=-1, - dir_files_count=-1, - path_isdir=QFileInfo(dir).isDir(), + new_source, created = SourceFileModel.get_or_create(dir=dir, + dir_size=-1, + dir_files_count=-1, + path_isdir=QFileInfo(dir).isDir(), profile=self.profile()) if created: - self.add_source_to_table(new_source,SettingsModel.get(key="get_srcpath_datasize").value) + self.add_source_to_table(new_source, True) new_source.save() msg = self.tr("Choose directory to back up") if want_folder else self.tr("Choose file(s) to back up") @@ -127,7 +113,7 @@ def source_remove(self): indexes.sort() # remove each selected row, starting with highest index (otherways, higher indexes become invalid) for index in reversed(indexes): - db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(),0).text()) + db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(), 0).text()) db_item.delete_instance() self.sourceFilesWidget.removeRow(index.row()) From b84f7a95c13294510f42c6edd75e2128a3cd363b Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 29 Sep 2020 19:00:42 +0000 Subject: [PATCH 23/64] re-add network status monitor --- src/vorta/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 431cad544..c9168ed9e 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -28,6 +28,10 @@ QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) # enable highdpi scaling QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) # use highdpi icons +keyring = VortaKeyring.get_keyring() +logger.info('Using %s Keyring implementation.', keyring.__class__.__name__) +network_status_monitor = NetworkStatusMonitor.get_network_status_monitor() +logger.info('Using %s NetworkStatusMonitor implementation.', network_status_monitor.__class__.__name__) borg_compat = BorgCompatibility() From 567cbbea0c12b3fadbf503e611ed6a78da711c66 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 29 Sep 2020 19:04:51 +0000 Subject: [PATCH 24/64] Add spacer --- src/vorta/assets/UI/sourcetab.ui | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/vorta/assets/UI/sourcetab.ui b/src/vorta/assets/UI/sourcetab.ui index a467fe14a..484694b95 100644 --- a/src/vorta/assets/UI/sourcetab.ui +++ b/src/vorta/assets/UI/sourcetab.ui @@ -109,6 +109,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + From 1e7b2a09e7c7f5b3e9354056649d055538582634 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 29 Sep 2020 21:06:54 +0200 Subject: [PATCH 25/64] re-add network status monitor --- src/vorta/utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index c9168ed9e..aa3125899 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -68,6 +68,13 @@ def get_directory_size(dirPath): return data_size, files_count +def get_network_status_monitor(): + global _network_status_monitor + if _network_status_monitor is None: + _network_status_monitor = NetworkStatusMonitor.get_network_status_monitor() + logger.info('Using %s NetworkStatusMonitor implementation.', _network_status_monitor.__class__.__name__) + return _network_status_monitor + def get_path_datasize(path): file_info = QFileInfo(path) From 04ec022160bfa3206bc0d538b72c814ef44fbfa9 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 29 Sep 2020 21:09:28 +0200 Subject: [PATCH 26/64] remove keyring stuff --- src/vorta/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index aa3125899..b30de5a96 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -28,8 +28,6 @@ QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) # enable highdpi scaling QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) # use highdpi icons -keyring = VortaKeyring.get_keyring() -logger.info('Using %s Keyring implementation.', keyring.__class__.__name__) network_status_monitor = NetworkStatusMonitor.get_network_status_monitor() logger.info('Using %s NetworkStatusMonitor implementation.', network_status_monitor.__class__.__name__) From 4023708fe039838e051fc0d7beff67428caea6f2 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 29 Sep 2020 20:04:00 +0000 Subject: [PATCH 27/64] fixes some previous bugs --- src/vorta/models.py | 5 +++++ src/vorta/utils.py | 15 +++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vorta/models.py b/src/vorta/models.py index d0cebdccf..62011f2a1 100644 --- a/src/vorta/models.py +++ b/src/vorta/models.py @@ -218,6 +218,11 @@ def get_misc_settings(): 'label': trans_late('settings', 'Open main window on startup') }, + { + 'key': 'get_srcpath_datasize', 'value': True, 'type': 'checkbox', + 'label': trans_late('settings', + 'Get Size of Source path when added') + }, { 'key': 'previous_profile_id', 'str_value': '1', 'type': 'internal', 'label': 'Previously selected profile' diff --git a/src/vorta/utils.py b/src/vorta/utils.py index b30de5a96..07c4f749f 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -32,10 +32,10 @@ logger.info('Using %s NetworkStatusMonitor implementation.', network_status_monitor.__class__.__name__) borg_compat = BorgCompatibility() - +_network_status_monitor = None class FilePathInfoAsync(QThread): - signal = pyqtSignal(str, int, int) + signal = pyqtSignal(str, str, str) def __init__(self, path): self.path = path @@ -43,11 +43,10 @@ def __init__(self, path): self.exiting = False def run(self): - logger.info("running thread to get path=%s...", self.path) + # logger.info("running thread to get path=%s...", self.path) self.files_count = 0 self.size, self.files_count = get_path_datasize(self.path) - self.sleep(5) - self.signal.emit(self.path, self.size, self.files_count) + self.signal.emit(self.path, str(self.size), str(self.files_count)) def get_directory_size(dirPath): @@ -80,10 +79,10 @@ def get_path_datasize(path): if file_info.isDir(): data_size, files_count = get_directory_size(file_info.absoluteFilePath()) - logger.info("path (folder) %s %u elements size now=%u (%s)", - file_info.absoluteFilePath(), files_count, data_size, pretty_bytes(data_size)) + # logger.info("path (folder) %s %u elements size now=%u (%s)", + # file_info.absoluteFilePath(), files_count, data_size, pretty_bytes(data_size)) else: - logger.info("path (file) %s size=%u", file_info.path(), file_info.size()) + # logger.info("path (file) %s size=%u", file_info.path(), file_info.size()) data_size = file_info.size() files_count = 1 From 55a125771db3562780550be79ca087130ed99836 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 29 Sep 2020 20:06:06 +0000 Subject: [PATCH 28/64] Fixed some bugs --- src/vorta/views/source_tab.py | 102 ++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 828f4f2cc..468c10d4a 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -1,5 +1,5 @@ from PyQt5 import uic -from ..models import SourceFileModel, BackupProfileMixin +from ..models import SourceFileModel, BackupProfileMixin, SettingsModel from ..utils import get_asset, choose_file_dialog, pretty_bytes, FilePathInfoAsync from PyQt5 import QtCore from PyQt5.QtCore import QFileInfo @@ -12,19 +12,19 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin): updateThreads = [] - + def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) - - headerTxt = ["Path", "Type", "Size", "Elements Count"] + + headerTxt=["Path", "Type", "Size", "Folders /\nFiles Count"] self.sourceFilesWidget.setColumnCount(len(headerTxt)) header = self.sourceFilesWidget.horizontalHeader() header.setVisible(True) header.setSortIndicatorShown(1) - + header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.setSectionResizeMode(2, QHeaderView.ResizeToContents) @@ -35,44 +35,68 @@ def __init__(self, parent=None): self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) self.sourceAddFile.clicked.connect(lambda: self.source_add(want_folder=False)) self.sourceRemove.clicked.connect(self.source_remove) + self.sourcesUpdate.clicked.connect(self.sources_update) self.paste.clicked.connect(self.paste_text) self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) self.populate_from_profile() + + def set_path_info(self,path,data_size,files_count): + items = self.sourceFilesWidget.findItems(path,QtCore.Qt.MatchExactly) + # Conversion int->str->int needed because QT limits int to 32-bit + data_size = int(data_size) + files_count = int(files_count) - def set_path_info(self, path, data_size, files_count): - items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchExactly) for item in items: - self.sourceFilesWidget.item(item.row(), 2).setText(pretty_bytes(data_size)) - self.sourceFilesWidget.item(item.row(), 3).setText(format(files_count)) db_item = SourceFileModel.get(dir=path) + if QFileInfo(path).isDir(): + self.sourceFilesWidget.setItem(item.row(),1,QTableWidgetItem("Folder")) + db_item.path_isdir = True + else: + self.sourceFilesWidget.setItem(item.row(),1,QTableWidgetItem("File")) + db_item.path_isdir = False + self.sourceFilesWidget.item(item.row(),2).setText(pretty_bytes(data_size)) + self.sourceFilesWidget.item(item.row(),3).setText(format(files_count)) + db_item.dir_size = data_size db_item.dir_files_count = files_count db_item.save() - - def add_source_to_table(self, source, update_data): - indexRow = self.sourceFilesWidget.rowCount() - self.sourceFilesWidget.insertRow(indexRow) - itemPath = QTableWidgetItem(source.dir) - self.sourceFilesWidget.setItem(indexRow, 0, itemPath) - if source.path_isdir == True: - self.sourceFilesWidget.setItem(indexRow, 1, QTableWidgetItem("")) - else: - self.sourceFilesWidget.setItem(indexRow, 1, QTableWidgetItem("")) + # Remove thread from list when it's done + for thrd in self.updateThreads: + if thrd.objectName() == path: + self.updateThreads.remove(thrd) + + def update_path_info(self,index_row): + path = self.sourceFilesWidget.item(index_row,0).text() + self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("load...")) + self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem("load...")) + self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem("load...")) + getDir = FilePathInfoAsync(path) + getDir.signal.connect(self.set_path_info) + getDir.setObjectName(path) + self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? + getDir.start() + + def add_source_to_table(self,source,update_data): + index_row = self.sourceFilesWidget.rowCount() + self.sourceFilesWidget.insertRow(index_row) + item_path = QTableWidgetItem(source.dir) + self.sourceFilesWidget.setItem(index_row,0,item_path) if update_data == True: - self.sourceFilesWidget.setItem(indexRow, 2, QTableWidgetItem("load...")) - self.sourceFilesWidget.setItem(indexRow, 3, QTableWidgetItem("load...")) - getDir = FilePathInfoAsync(source.dir) - getDir.signal.connect(self.set_path_info) - self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? - getDir.start() - else: # Use cached data from DB + self.update_path_info(index_row) + else: # Use cached data from DB if source.dir_size < 0: - self.sourceFilesWidget.setItem(indexRow, 2, QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(indexRow, 3, QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem("N/A")) else: - self.sourceFilesWidget.setItem(indexRow, 2, QTableWidgetItem(pretty_bytes(source.dir_size))) - self.sourceFilesWidget.setItem(indexRow, 3, QTableWidgetItem(format(source.dir_files_count))) + if source.path_isdir == True: + self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("Folder")) + else: + self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("File")) + + self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem(pretty_bytes(source.dir_size))) + self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem(format(source.dir_files_count))) def populate_from_profile(self): profile = self.profile() @@ -83,24 +107,28 @@ def populate_from_profile(self): self.excludeIfPresentField.clear() for source in SourceFileModel.select().where(SourceFileModel.profile == profile): - self.add_source_to_table(source, False) + self.add_source_to_table(source,False) self.excludePatternsField.appendPlainText(profile.exclude_patterns) self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) + def sources_update(self): + for row in range(0, self.sourceFilesWidget.rowCount()): + self.update_path_info(row) # Update data for each entry + def source_add(self, want_folder): def receive(): dirs = dialog.selectedFiles() for dir in dirs: - new_source, created = SourceFileModel.get_or_create(dir=dir, - dir_size=-1, - dir_files_count=-1, - path_isdir=QFileInfo(dir).isDir(), + new_source, created = SourceFileModel.get_or_create(dir=dir, + dir_size=-1, + dir_files_count=-1, + path_isdir=False, profile=self.profile()) if created: - self.add_source_to_table(new_source, True) + self.add_source_to_table(new_source,SettingsModel.get(key="get_srcpath_datasize").value) new_source.save() msg = self.tr("Choose directory to back up") if want_folder else self.tr("Choose file(s) to back up") @@ -113,7 +141,7 @@ def source_remove(self): indexes.sort() # remove each selected row, starting with highest index (otherways, higher indexes become invalid) for index in reversed(indexes): - db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(), 0).text()) + db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(),0).text()) db_item.delete_instance() self.sourceFilesWidget.removeRow(index.row()) From b71c1565082611e79754eed4faa3c51fef74f9bd Mon Sep 17 00:00:00 2001 From: Samuel Date: Thu, 1 Oct 2020 03:01:04 -0500 Subject: [PATCH 29/64] Lint, use QTableWidget --- src/vorta/__init__.py | 1 - src/vorta/assets/UI/sourcetab.ui | 37 ++++++++++----- src/vorta/models.py | 2 +- src/vorta/utils.py | 5 ++- src/vorta/views/source_tab.py | 77 +++++++++++++++----------------- 5 files changed, 67 insertions(+), 55 deletions(-) delete mode 100644 src/vorta/__init__.py diff --git a/src/vorta/__init__.py b/src/vorta/__init__.py deleted file mode 100644 index 8dee4bf82..000000000 --- a/src/vorta/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._version import __version__ diff --git a/src/vorta/assets/UI/sourcetab.ui b/src/vorta/assets/UI/sourcetab.ui index 484694b95..0759ff2c7 100644 --- a/src/vorta/assets/UI/sourcetab.ui +++ b/src/vorta/assets/UI/sourcetab.ui @@ -64,9 +64,26 @@ false - - false - + + + Path + + + + + Type + + + + + Size + + + + + File/Folder Count + + @@ -85,6 +102,13 @@ + + + + Remove + + + @@ -95,13 +119,6 @@ - - - - Remove - - - diff --git a/src/vorta/models.py b/src/vorta/models.py index 62011f2a1..9996158e6 100644 --- a/src/vorta/models.py +++ b/src/vorta/models.py @@ -221,7 +221,7 @@ def get_misc_settings(): { 'key': 'get_srcpath_datasize', 'value': True, 'type': 'checkbox', 'label': trans_late('settings', - 'Get Size of Source path when added') + 'Get size of file/folder when added') }, { 'key': 'previous_profile_id', 'str_value': '1', 'type': 'internal', diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 07c4f749f..49198f457 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -18,7 +18,7 @@ from paramiko.ed25519key import Ed25519Key from paramiko.rsakey import RSAKey from PyQt5 import QtCore -from PyQt5.QtCore import QFileInfo, QDir, QThread, pyqtSignal +from PyQt5.QtCore import QFileInfo, QThread, pyqtSignal from PyQt5.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon from vorta.borg._compatibility import BorgCompatibility @@ -34,6 +34,7 @@ borg_compat = BorgCompatibility() _network_status_monitor = None + class FilePathInfoAsync(QThread): signal = pyqtSignal(str, str, str) @@ -65,6 +66,7 @@ def get_directory_size(dirPath): return data_size, files_count + def get_network_status_monitor(): global _network_status_monitor if _network_status_monitor is None: @@ -297,4 +299,3 @@ def is_system_tray_available(): is_available = tray.isSystemTrayAvailable() return is_available - diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 468c10d4a..b1a11cfb8 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -12,26 +12,21 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin): updateThreads = [] - + def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) - - headerTxt=["Path", "Type", "Size", "Folders /\nFiles Count"] - self.sourceFilesWidget.setColumnCount(len(headerTxt)) header = self.sourceFilesWidget.horizontalHeader() header.setVisible(True) header.setSortIndicatorShown(1) - + header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.setSectionResizeMode(2, QHeaderView.ResizeToContents) header.setSectionResizeMode(3, QHeaderView.ResizeToContents) - self.sourceFilesWidget.setHorizontalHeaderLabels(headerTxt) - self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) self.sourceAddFile.clicked.connect(lambda: self.source_add(want_folder=False)) self.sourceRemove.clicked.connect(self.source_remove) @@ -40,9 +35,9 @@ def __init__(self, parent=None): self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) self.populate_from_profile() - - def set_path_info(self,path,data_size,files_count): - items = self.sourceFilesWidget.findItems(path,QtCore.Qt.MatchExactly) + + def set_path_info(self, path, data_size, files_count): + items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchExactly) # Conversion int->str->int needed because QT limits int to 32-bit data_size = int(data_size) files_count = int(files_count) @@ -50,13 +45,13 @@ def set_path_info(self,path,data_size,files_count): for item in items: db_item = SourceFileModel.get(dir=path) if QFileInfo(path).isDir(): - self.sourceFilesWidget.setItem(item.row(),1,QTableWidgetItem("Folder")) + self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("Folder")) db_item.path_isdir = True else: - self.sourceFilesWidget.setItem(item.row(),1,QTableWidgetItem("File")) + self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("File")) db_item.path_isdir = False - self.sourceFilesWidget.item(item.row(),2).setText(pretty_bytes(data_size)) - self.sourceFilesWidget.item(item.row(),3).setText(format(files_count)) + self.sourceFilesWidget.item(item.row(), 2).setText(pretty_bytes(data_size)) + self.sourceFilesWidget.item(item.row(), 3).setText(format(files_count)) db_item.dir_size = data_size db_item.dir_files_count = files_count @@ -66,48 +61,48 @@ def set_path_info(self,path,data_size,files_count): if thrd.objectName() == path: self.updateThreads.remove(thrd) - def update_path_info(self,index_row): - path = self.sourceFilesWidget.item(index_row,0).text() - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("load...")) - self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem("load...")) - self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem("load...")) + def update_path_info(self, index_row): + path = self.sourceFilesWidget.item(index_row, 0).text() + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Calculating...")) + self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("Calculating...")) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("Calculating...")) getDir = FilePathInfoAsync(path) getDir.signal.connect(self.set_path_info) getDir.setObjectName(path) - self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? + self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? getDir.start() - def add_source_to_table(self,source,update_data): + def add_source_to_table(self, source, update_data): index_row = self.sourceFilesWidget.rowCount() self.sourceFilesWidget.insertRow(index_row) item_path = QTableWidgetItem(source.dir) - self.sourceFilesWidget.setItem(index_row,0,item_path) - if update_data == True: + self.sourceFilesWidget.setItem(index_row, 0, item_path) + if update_data: self.update_path_info(index_row) - else: # Use cached data from DB + else: # Use cached data from DB if source.dir_size < 0: - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("N/A")) else: - if source.path_isdir == True: - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("Folder")) + if source.path_isdir: + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Folder")) else: - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("File")) + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("File")) - self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem(pretty_bytes(source.dir_size))) - self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem(format(source.dir_files_count))) + self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem(pretty_bytes(source.dir_size))) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem(format(source.dir_files_count))) def populate_from_profile(self): profile = self.profile() self.excludePatternsField.textChanged.disconnect() self.excludeIfPresentField.textChanged.disconnect() - self.sourceFilesWidget.clearContents() + self.sourceFilesWidget.setRowCount(0) # Clear rows self.excludePatternsField.clear() self.excludeIfPresentField.clear() for source in SourceFileModel.select().where(SourceFileModel.profile == profile): - self.add_source_to_table(source,False) + self.add_source_to_table(source, False) self.excludePatternsField.appendPlainText(profile.exclude_patterns) self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) @@ -116,19 +111,19 @@ def populate_from_profile(self): def sources_update(self): for row in range(0, self.sourceFilesWidget.rowCount()): - self.update_path_info(row) # Update data for each entry + self.update_path_info(row) # Update data for each entry def source_add(self, want_folder): def receive(): dirs = dialog.selectedFiles() for dir in dirs: - new_source, created = SourceFileModel.get_or_create(dir=dir, - dir_size=-1, - dir_files_count=-1, - path_isdir=False, + new_source, created = SourceFileModel.get_or_create(dir=dir, + dir_size=-1, + dir_files_count=-1, + path_isdir=False, profile=self.profile()) if created: - self.add_source_to_table(new_source,SettingsModel.get(key="get_srcpath_datasize").value) + self.add_source_to_table(new_source, SettingsModel.get(key="get_srcpath_datasize").value) new_source.save() msg = self.tr("Choose directory to back up") if want_folder else self.tr("Choose file(s) to back up") @@ -141,7 +136,7 @@ def source_remove(self): indexes.sort() # remove each selected row, starting with highest index (otherways, higher indexes become invalid) for index in reversed(indexes): - db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(),0).text()) + db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(), 0).text()) db_item.delete_instance() self.sourceFilesWidget.removeRow(index.row()) From dec0ae90355bf2360203301a536dc87de3d06008 Mon Sep 17 00:00:00 2001 From: Samuel Date: Thu, 1 Oct 2020 03:12:14 -0500 Subject: [PATCH 30/64] Fix tests --- tests/conftest.py | 2 +- tests/test_source.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f462a420a..bc54d7513 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,7 +37,7 @@ def init_db(qapp): test_archive1 = ArchiveModel(snapshot_id='99998', name='test-archive1', time=dt(2000, 1, 1, 0, 0), repo=1) test_archive1.save() - source_dir = SourceFileModel(dir='/tmp/another', repo=new_repo) + source_dir = SourceFileModel(dir='/tmp/another', repo=new_repo, dir_size=100, dir_files_count=18, path_isdir=True) source_dir.save() qapp.open_main_window_action() diff --git a/tests/test_source.py b/tests/test_source.py index b461dad09..0fb751a61 100644 --- a/tests/test_source.py +++ b/tests/test_source.py @@ -11,4 +11,4 @@ def test_add_folder(qapp, qtbot, tmpdir, monkeypatch, choose_file_dialog): tab = main.sourceTab tab.sourceAddFolder.click() - qtbot.waitUntil(lambda: tab.sourceFilesWidget.count() == 2, timeout=5000) + qtbot.waitUntil(lambda: tab.sourceFilesWidget.rowCount() == 2, timeout=5000) From cf4d35ecf28a3565ea9dff7af1be7be303126f4e Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 3 Oct 2020 20:13:59 +0000 Subject: [PATCH 31/64] applied patch --- src/vorta/assets/UI/source_tab.py | 169 ++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 src/vorta/assets/UI/source_tab.py diff --git a/src/vorta/assets/UI/source_tab.py b/src/vorta/assets/UI/source_tab.py new file mode 100644 index 000000000..b1a11cfb8 --- /dev/null +++ b/src/vorta/assets/UI/source_tab.py @@ -0,0 +1,169 @@ +from PyQt5 import uic +from ..models import SourceFileModel, BackupProfileMixin, SettingsModel +from ..utils import get_asset, choose_file_dialog, pretty_bytes, FilePathInfoAsync +from PyQt5 import QtCore +from PyQt5.QtCore import QFileInfo +from PyQt5.QtWidgets import QApplication, QMessageBox, QTableWidgetItem, QHeaderView +import os + +uifile = get_asset('UI/sourcetab.ui') +SourceUI, SourceBase = uic.loadUiType(uifile) + + +class SourceTab(SourceBase, SourceUI, BackupProfileMixin): + updateThreads = [] + + def __init__(self, parent=None): + super().__init__(parent) + self.setupUi(parent) + + header = self.sourceFilesWidget.horizontalHeader() + + header.setVisible(True) + header.setSortIndicatorShown(1) + + header.setSectionResizeMode(0, QHeaderView.Stretch) + header.setSectionResizeMode(1, QHeaderView.ResizeToContents) + header.setSectionResizeMode(2, QHeaderView.ResizeToContents) + header.setSectionResizeMode(3, QHeaderView.ResizeToContents) + + self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) + self.sourceAddFile.clicked.connect(lambda: self.source_add(want_folder=False)) + self.sourceRemove.clicked.connect(self.source_remove) + self.sourcesUpdate.clicked.connect(self.sources_update) + self.paste.clicked.connect(self.paste_text) + self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) + self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) + self.populate_from_profile() + + def set_path_info(self, path, data_size, files_count): + items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchExactly) + # Conversion int->str->int needed because QT limits int to 32-bit + data_size = int(data_size) + files_count = int(files_count) + + for item in items: + db_item = SourceFileModel.get(dir=path) + if QFileInfo(path).isDir(): + self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("Folder")) + db_item.path_isdir = True + else: + self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("File")) + db_item.path_isdir = False + self.sourceFilesWidget.item(item.row(), 2).setText(pretty_bytes(data_size)) + self.sourceFilesWidget.item(item.row(), 3).setText(format(files_count)) + + db_item.dir_size = data_size + db_item.dir_files_count = files_count + db_item.save() + # Remove thread from list when it's done + for thrd in self.updateThreads: + if thrd.objectName() == path: + self.updateThreads.remove(thrd) + + def update_path_info(self, index_row): + path = self.sourceFilesWidget.item(index_row, 0).text() + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Calculating...")) + self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("Calculating...")) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("Calculating...")) + getDir = FilePathInfoAsync(path) + getDir.signal.connect(self.set_path_info) + getDir.setObjectName(path) + self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? + getDir.start() + + def add_source_to_table(self, source, update_data): + index_row = self.sourceFilesWidget.rowCount() + self.sourceFilesWidget.insertRow(index_row) + item_path = QTableWidgetItem(source.dir) + self.sourceFilesWidget.setItem(index_row, 0, item_path) + if update_data: + self.update_path_info(index_row) + else: # Use cached data from DB + if source.dir_size < 0: + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("N/A")) + else: + if source.path_isdir: + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Folder")) + else: + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("File")) + + self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem(pretty_bytes(source.dir_size))) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem(format(source.dir_files_count))) + + def populate_from_profile(self): + profile = self.profile() + self.excludePatternsField.textChanged.disconnect() + self.excludeIfPresentField.textChanged.disconnect() + self.sourceFilesWidget.setRowCount(0) # Clear rows + self.excludePatternsField.clear() + self.excludeIfPresentField.clear() + + for source in SourceFileModel.select().where(SourceFileModel.profile == profile): + self.add_source_to_table(source, False) + + self.excludePatternsField.appendPlainText(profile.exclude_patterns) + self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) + self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) + self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) + + def sources_update(self): + for row in range(0, self.sourceFilesWidget.rowCount()): + self.update_path_info(row) # Update data for each entry + + def source_add(self, want_folder): + def receive(): + dirs = dialog.selectedFiles() + for dir in dirs: + new_source, created = SourceFileModel.get_or_create(dir=dir, + dir_size=-1, + dir_files_count=-1, + path_isdir=False, + profile=self.profile()) + if created: + self.add_source_to_table(new_source, SettingsModel.get(key="get_srcpath_datasize").value) + new_source.save() + + msg = self.tr("Choose directory to back up") if want_folder else self.tr("Choose file(s) to back up") + dialog = choose_file_dialog(self, msg, want_folder=want_folder) + dialog.open(receive) + + def source_remove(self): + indexes = self.sourceFilesWidget.selectionModel().selectedRows() + # sort indexes, starting with lowest + indexes.sort() + # remove each selected row, starting with highest index (otherways, higher indexes become invalid) + for index in reversed(indexes): + db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(), 0).text()) + db_item.delete_instance() + self.sourceFilesWidget.removeRow(index.row()) + + def save_exclude_patterns(self): + profile = self.profile() + profile.exclude_patterns = self.excludePatternsField.toPlainText() + profile.save() + + def save_exclude_if_present(self): + profile = self.profile() + profile.exclude_if_present = self.excludeIfPresentField.toPlainText() + profile.save() + + def paste_text(self): + sources = QApplication.clipboard().text().splitlines() + invalidSources = "" + for source in sources: + if len(source) > 0: # Ignore empty newlines + if not os.path.exists(source): + invalidSources = invalidSources + "\n" + source + else: + new_source, created = SourceFileModel.get_or_create(dir=source, profile=self.profile()) + if created: + self.sourceFilesWidget.addItem(source) + new_source.save() + + if len(invalidSources) != 0: # Check if any invalid paths + msg = QMessageBox() + msg.setText("Some of your sources are invalid:" + invalidSources) + msg.exec() From 2db7347f1e8a3bc9abc957f3a4d05c81399f8171 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 3 Oct 2020 20:14:30 +0000 Subject: [PATCH 32/64] applied patch --- src/vorta/models.py | 2 +- src/vorta/utils.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vorta/models.py b/src/vorta/models.py index 62011f2a1..9996158e6 100644 --- a/src/vorta/models.py +++ b/src/vorta/models.py @@ -221,7 +221,7 @@ def get_misc_settings(): { 'key': 'get_srcpath_datasize', 'value': True, 'type': 'checkbox', 'label': trans_late('settings', - 'Get Size of Source path when added') + 'Get size of file/folder when added') }, { 'key': 'previous_profile_id', 'str_value': '1', 'type': 'internal', diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 07c4f749f..49198f457 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -18,7 +18,7 @@ from paramiko.ed25519key import Ed25519Key from paramiko.rsakey import RSAKey from PyQt5 import QtCore -from PyQt5.QtCore import QFileInfo, QDir, QThread, pyqtSignal +from PyQt5.QtCore import QFileInfo, QThread, pyqtSignal from PyQt5.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon from vorta.borg._compatibility import BorgCompatibility @@ -34,6 +34,7 @@ borg_compat = BorgCompatibility() _network_status_monitor = None + class FilePathInfoAsync(QThread): signal = pyqtSignal(str, str, str) @@ -65,6 +66,7 @@ def get_directory_size(dirPath): return data_size, files_count + def get_network_status_monitor(): global _network_status_monitor if _network_status_monitor is None: @@ -297,4 +299,3 @@ def is_system_tray_available(): is_available = tray.isSystemTrayAvailable() return is_available - From a22455c9dc818376fd61561707bf48c6aa5674b6 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 3 Oct 2020 20:15:10 +0000 Subject: [PATCH 33/64] applied patch --- src/vorta/views/source_tab.py | 77 ++++++++++++++++------------------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 468c10d4a..b1a11cfb8 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -12,26 +12,21 @@ class SourceTab(SourceBase, SourceUI, BackupProfileMixin): updateThreads = [] - + def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) - - headerTxt=["Path", "Type", "Size", "Folders /\nFiles Count"] - self.sourceFilesWidget.setColumnCount(len(headerTxt)) header = self.sourceFilesWidget.horizontalHeader() header.setVisible(True) header.setSortIndicatorShown(1) - + header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.setSectionResizeMode(2, QHeaderView.ResizeToContents) header.setSectionResizeMode(3, QHeaderView.ResizeToContents) - self.sourceFilesWidget.setHorizontalHeaderLabels(headerTxt) - self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) self.sourceAddFile.clicked.connect(lambda: self.source_add(want_folder=False)) self.sourceRemove.clicked.connect(self.source_remove) @@ -40,9 +35,9 @@ def __init__(self, parent=None): self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) self.populate_from_profile() - - def set_path_info(self,path,data_size,files_count): - items = self.sourceFilesWidget.findItems(path,QtCore.Qt.MatchExactly) + + def set_path_info(self, path, data_size, files_count): + items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchExactly) # Conversion int->str->int needed because QT limits int to 32-bit data_size = int(data_size) files_count = int(files_count) @@ -50,13 +45,13 @@ def set_path_info(self,path,data_size,files_count): for item in items: db_item = SourceFileModel.get(dir=path) if QFileInfo(path).isDir(): - self.sourceFilesWidget.setItem(item.row(),1,QTableWidgetItem("Folder")) + self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("Folder")) db_item.path_isdir = True else: - self.sourceFilesWidget.setItem(item.row(),1,QTableWidgetItem("File")) + self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("File")) db_item.path_isdir = False - self.sourceFilesWidget.item(item.row(),2).setText(pretty_bytes(data_size)) - self.sourceFilesWidget.item(item.row(),3).setText(format(files_count)) + self.sourceFilesWidget.item(item.row(), 2).setText(pretty_bytes(data_size)) + self.sourceFilesWidget.item(item.row(), 3).setText(format(files_count)) db_item.dir_size = data_size db_item.dir_files_count = files_count @@ -66,48 +61,48 @@ def set_path_info(self,path,data_size,files_count): if thrd.objectName() == path: self.updateThreads.remove(thrd) - def update_path_info(self,index_row): - path = self.sourceFilesWidget.item(index_row,0).text() - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("load...")) - self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem("load...")) - self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem("load...")) + def update_path_info(self, index_row): + path = self.sourceFilesWidget.item(index_row, 0).text() + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Calculating...")) + self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("Calculating...")) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("Calculating...")) getDir = FilePathInfoAsync(path) getDir.signal.connect(self.set_path_info) getDir.setObjectName(path) - self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? + self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? getDir.start() - def add_source_to_table(self,source,update_data): + def add_source_to_table(self, source, update_data): index_row = self.sourceFilesWidget.rowCount() self.sourceFilesWidget.insertRow(index_row) item_path = QTableWidgetItem(source.dir) - self.sourceFilesWidget.setItem(index_row,0,item_path) - if update_data == True: + self.sourceFilesWidget.setItem(index_row, 0, item_path) + if update_data: self.update_path_info(index_row) - else: # Use cached data from DB + else: # Use cached data from DB if source.dir_size < 0: - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("N/A")) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("N/A")) else: - if source.path_isdir == True: - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("Folder")) + if source.path_isdir: + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Folder")) else: - self.sourceFilesWidget.setItem(index_row,1,QTableWidgetItem("File")) + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("File")) - self.sourceFilesWidget.setItem(index_row,2,QTableWidgetItem(pretty_bytes(source.dir_size))) - self.sourceFilesWidget.setItem(index_row,3,QTableWidgetItem(format(source.dir_files_count))) + self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem(pretty_bytes(source.dir_size))) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem(format(source.dir_files_count))) def populate_from_profile(self): profile = self.profile() self.excludePatternsField.textChanged.disconnect() self.excludeIfPresentField.textChanged.disconnect() - self.sourceFilesWidget.clearContents() + self.sourceFilesWidget.setRowCount(0) # Clear rows self.excludePatternsField.clear() self.excludeIfPresentField.clear() for source in SourceFileModel.select().where(SourceFileModel.profile == profile): - self.add_source_to_table(source,False) + self.add_source_to_table(source, False) self.excludePatternsField.appendPlainText(profile.exclude_patterns) self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) @@ -116,19 +111,19 @@ def populate_from_profile(self): def sources_update(self): for row in range(0, self.sourceFilesWidget.rowCount()): - self.update_path_info(row) # Update data for each entry + self.update_path_info(row) # Update data for each entry def source_add(self, want_folder): def receive(): dirs = dialog.selectedFiles() for dir in dirs: - new_source, created = SourceFileModel.get_or_create(dir=dir, - dir_size=-1, - dir_files_count=-1, - path_isdir=False, + new_source, created = SourceFileModel.get_or_create(dir=dir, + dir_size=-1, + dir_files_count=-1, + path_isdir=False, profile=self.profile()) if created: - self.add_source_to_table(new_source,SettingsModel.get(key="get_srcpath_datasize").value) + self.add_source_to_table(new_source, SettingsModel.get(key="get_srcpath_datasize").value) new_source.save() msg = self.tr("Choose directory to back up") if want_folder else self.tr("Choose file(s) to back up") @@ -141,7 +136,7 @@ def source_remove(self): indexes.sort() # remove each selected row, starting with highest index (otherways, higher indexes become invalid) for index in reversed(indexes): - db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(),0).text()) + db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(), 0).text()) db_item.delete_instance() self.sourceFilesWidget.removeRow(index.row()) From 6f6de6700b7fd7a851bef3535f77710d63493f1e Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 3 Oct 2020 20:59:30 +0000 Subject: [PATCH 34/64] Changed as requested Files count for file = empty, Text "N/A" removed for unknown entry data --- src/vorta/views/source_tab.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index b1a11cfb8..5f2793e0c 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -46,12 +46,14 @@ def set_path_info(self, path, data_size, files_count): db_item = SourceFileModel.get(dir=path) if QFileInfo(path).isDir(): self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("Folder")) + self.sourceFilesWidget.item(item.row(), 3).setText(format(files_count)) db_item.path_isdir = True else: self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("File")) + # No files count, if entry itself is a file + self.sourceFilesWidget.item(item.row(), 3).setText("") db_item.path_isdir = False self.sourceFilesWidget.item(item.row(), 2).setText(pretty_bytes(data_size)) - self.sourceFilesWidget.item(item.row(), 3).setText(format(files_count)) db_item.dir_size = data_size db_item.dir_files_count = files_count @@ -81,17 +83,19 @@ def add_source_to_table(self, source, update_data): self.update_path_info(index_row) else: # Use cached data from DB if source.dir_size < 0: - self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("N/A")) + # Size/count wasn't calculated yet + self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("")) else: if source.path_isdir: self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Folder")) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem(format(source.dir_files_count))) else: self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("File")) + self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("")) self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem(pretty_bytes(source.dir_size))) - self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem(format(source.dir_files_count))) def populate_from_profile(self): profile = self.profile() From 4464318df50c9070fe2fd7ba2e31b516852a95dd Mon Sep 17 00:00:00 2001 From: samuel-w Date: Sat, 3 Oct 2020 16:11:36 -0500 Subject: [PATCH 35/64] Remove extra file --- src/vorta/assets/UI/source_tab.py | 169 ------------------------------ 1 file changed, 169 deletions(-) delete mode 100644 src/vorta/assets/UI/source_tab.py diff --git a/src/vorta/assets/UI/source_tab.py b/src/vorta/assets/UI/source_tab.py deleted file mode 100644 index b1a11cfb8..000000000 --- a/src/vorta/assets/UI/source_tab.py +++ /dev/null @@ -1,169 +0,0 @@ -from PyQt5 import uic -from ..models import SourceFileModel, BackupProfileMixin, SettingsModel -from ..utils import get_asset, choose_file_dialog, pretty_bytes, FilePathInfoAsync -from PyQt5 import QtCore -from PyQt5.QtCore import QFileInfo -from PyQt5.QtWidgets import QApplication, QMessageBox, QTableWidgetItem, QHeaderView -import os - -uifile = get_asset('UI/sourcetab.ui') -SourceUI, SourceBase = uic.loadUiType(uifile) - - -class SourceTab(SourceBase, SourceUI, BackupProfileMixin): - updateThreads = [] - - def __init__(self, parent=None): - super().__init__(parent) - self.setupUi(parent) - - header = self.sourceFilesWidget.horizontalHeader() - - header.setVisible(True) - header.setSortIndicatorShown(1) - - header.setSectionResizeMode(0, QHeaderView.Stretch) - header.setSectionResizeMode(1, QHeaderView.ResizeToContents) - header.setSectionResizeMode(2, QHeaderView.ResizeToContents) - header.setSectionResizeMode(3, QHeaderView.ResizeToContents) - - self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) - self.sourceAddFile.clicked.connect(lambda: self.source_add(want_folder=False)) - self.sourceRemove.clicked.connect(self.source_remove) - self.sourcesUpdate.clicked.connect(self.sources_update) - self.paste.clicked.connect(self.paste_text) - self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) - self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) - self.populate_from_profile() - - def set_path_info(self, path, data_size, files_count): - items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchExactly) - # Conversion int->str->int needed because QT limits int to 32-bit - data_size = int(data_size) - files_count = int(files_count) - - for item in items: - db_item = SourceFileModel.get(dir=path) - if QFileInfo(path).isDir(): - self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("Folder")) - db_item.path_isdir = True - else: - self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("File")) - db_item.path_isdir = False - self.sourceFilesWidget.item(item.row(), 2).setText(pretty_bytes(data_size)) - self.sourceFilesWidget.item(item.row(), 3).setText(format(files_count)) - - db_item.dir_size = data_size - db_item.dir_files_count = files_count - db_item.save() - # Remove thread from list when it's done - for thrd in self.updateThreads: - if thrd.objectName() == path: - self.updateThreads.remove(thrd) - - def update_path_info(self, index_row): - path = self.sourceFilesWidget.item(index_row, 0).text() - self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Calculating...")) - self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("Calculating...")) - self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("Calculating...")) - getDir = FilePathInfoAsync(path) - getDir.signal.connect(self.set_path_info) - getDir.setObjectName(path) - self.updateThreads.append(getDir) # this is ugly, is there a better way to keep the thread object? - getDir.start() - - def add_source_to_table(self, source, update_data): - index_row = self.sourceFilesWidget.rowCount() - self.sourceFilesWidget.insertRow(index_row) - item_path = QTableWidgetItem(source.dir) - self.sourceFilesWidget.setItem(index_row, 0, item_path) - if update_data: - self.update_path_info(index_row) - else: # Use cached data from DB - if source.dir_size < 0: - self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("N/A")) - self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("N/A")) - else: - if source.path_isdir: - self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Folder")) - else: - self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("File")) - - self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem(pretty_bytes(source.dir_size))) - self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem(format(source.dir_files_count))) - - def populate_from_profile(self): - profile = self.profile() - self.excludePatternsField.textChanged.disconnect() - self.excludeIfPresentField.textChanged.disconnect() - self.sourceFilesWidget.setRowCount(0) # Clear rows - self.excludePatternsField.clear() - self.excludeIfPresentField.clear() - - for source in SourceFileModel.select().where(SourceFileModel.profile == profile): - self.add_source_to_table(source, False) - - self.excludePatternsField.appendPlainText(profile.exclude_patterns) - self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) - self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) - self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) - - def sources_update(self): - for row in range(0, self.sourceFilesWidget.rowCount()): - self.update_path_info(row) # Update data for each entry - - def source_add(self, want_folder): - def receive(): - dirs = dialog.selectedFiles() - for dir in dirs: - new_source, created = SourceFileModel.get_or_create(dir=dir, - dir_size=-1, - dir_files_count=-1, - path_isdir=False, - profile=self.profile()) - if created: - self.add_source_to_table(new_source, SettingsModel.get(key="get_srcpath_datasize").value) - new_source.save() - - msg = self.tr("Choose directory to back up") if want_folder else self.tr("Choose file(s) to back up") - dialog = choose_file_dialog(self, msg, want_folder=want_folder) - dialog.open(receive) - - def source_remove(self): - indexes = self.sourceFilesWidget.selectionModel().selectedRows() - # sort indexes, starting with lowest - indexes.sort() - # remove each selected row, starting with highest index (otherways, higher indexes become invalid) - for index in reversed(indexes): - db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(), 0).text()) - db_item.delete_instance() - self.sourceFilesWidget.removeRow(index.row()) - - def save_exclude_patterns(self): - profile = self.profile() - profile.exclude_patterns = self.excludePatternsField.toPlainText() - profile.save() - - def save_exclude_if_present(self): - profile = self.profile() - profile.exclude_if_present = self.excludeIfPresentField.toPlainText() - profile.save() - - def paste_text(self): - sources = QApplication.clipboard().text().splitlines() - invalidSources = "" - for source in sources: - if len(source) > 0: # Ignore empty newlines - if not os.path.exists(source): - invalidSources = invalidSources + "\n" + source - else: - new_source, created = SourceFileModel.get_or_create(dir=source, profile=self.profile()) - if created: - self.sourceFilesWidget.addItem(source) - new_source.save() - - if len(invalidSources) != 0: # Check if any invalid paths - msg = QMessageBox() - msg.setText("Some of your sources are invalid:" + invalidSources) - msg.exec() From 99c7d8ce6722993ffc61a68b568f045ac6d56631 Mon Sep 17 00:00:00 2001 From: samuel-w Date: Sun, 4 Oct 2020 02:52:27 -0500 Subject: [PATCH 36/64] Undelete version --- src/vorta/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/vorta/__init__.py diff --git a/src/vorta/__init__.py b/src/vorta/__init__.py new file mode 100644 index 000000000..8dee4bf82 --- /dev/null +++ b/src/vorta/__init__.py @@ -0,0 +1 @@ +from ._version import __version__ From 5f8c42126650d20dec37c3ef9abda26b33738b57 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Wed, 7 Oct 2020 20:11:05 +0000 Subject: [PATCH 37/64] enabled sorting via sort indicator Currently this is not working as intended when sorting on the "Size" column --- src/vorta/views/source_tab.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 5f2793e0c..cd8dba55a 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -26,6 +26,7 @@ def __init__(self, parent=None): header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.setSectionResizeMode(2, QHeaderView.ResizeToContents) header.setSectionResizeMode(3, QHeaderView.ResizeToContents) + header.sortIndicatorChanged.connect(self.sort_entries_by_column) self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) self.sourceAddFile.clicked.connect(lambda: self.source_add(want_folder=False)) @@ -36,6 +37,9 @@ def __init__(self, parent=None): self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) self.populate_from_profile() + def sort_entries_by_column(self, column_index, order): + self.sourceFilesWidget.model().sort(column_index, order) + def set_path_info(self, path, data_size, files_count): items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchExactly) # Conversion int->str->int needed because QT limits int to 32-bit @@ -108,6 +112,8 @@ def populate_from_profile(self): for source in SourceFileModel.select().where(SourceFileModel.profile == profile): self.add_source_to_table(source, False) + # Initially, sort entries by path name in ascending order + self.sourceFilesWidget.model().sort(0,QtCore.Qt.AscendingOrder) self.excludePatternsField.appendPlainText(profile.exclude_patterns) self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) From cc0cd26eff919083eb267b68a25a4614a63cd9a0 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Thu, 8 Oct 2020 17:33:00 +0000 Subject: [PATCH 38/64] some cleanups --- src/vorta/views/source_tab.py | 60 ++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index cd8dba55a..18f47320a 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -9,10 +9,16 @@ uifile = get_asset('UI/sourcetab.ui') SourceUI, SourceBase = uic.loadUiType(uifile) +class SourceColumn: + Path = 0 + Type = 1 + Size = 2 + FilesCount = 3 class SourceTab(SourceBase, SourceUI, BackupProfileMixin): updateThreads = [] + def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) @@ -22,10 +28,10 @@ def __init__(self, parent=None): header.setVisible(True) header.setSortIndicatorShown(1) - header.setSectionResizeMode(0, QHeaderView.Stretch) - header.setSectionResizeMode(1, QHeaderView.ResizeToContents) - header.setSectionResizeMode(2, QHeaderView.ResizeToContents) - header.setSectionResizeMode(3, QHeaderView.ResizeToContents) + header.setSectionResizeMode(SourceColumn.Path, QHeaderView.Stretch) + header.setSectionResizeMode(SourceColumn.Type, QHeaderView.ResizeToContents) + header.setSectionResizeMode(SourceColumn.Size, QHeaderView.ResizeToContents) + header.setSectionResizeMode(SourceColumn.FilesCount, QHeaderView.ResizeToContents) header.sortIndicatorChanged.connect(self.sort_entries_by_column) self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) @@ -38,7 +44,11 @@ def __init__(self, parent=None): self.populate_from_profile() def sort_entries_by_column(self, column_index, order): - self.sourceFilesWidget.model().sort(column_index, order) + if column_index == SourceColumn.Size: + # todo change this + self.sourceFilesWidget.model().sort(column_index, order) + else: + self.sourceFilesWidget.model().sort(column_index, order) def set_path_info(self, path, data_size, files_count): items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchExactly) @@ -49,15 +59,15 @@ def set_path_info(self, path, data_size, files_count): for item in items: db_item = SourceFileModel.get(dir=path) if QFileInfo(path).isDir(): - self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("Folder")) - self.sourceFilesWidget.item(item.row(), 3).setText(format(files_count)) + self.sourceFilesWidget.setItem(item.row(), SourceColumn.Type, QTableWidgetItem("Folder")) + self.sourceFilesWidget.item(item.row(), SourceColumn.FilesCount).setText(format(files_count)) db_item.path_isdir = True else: - self.sourceFilesWidget.setItem(item.row(), 1, QTableWidgetItem("File")) + self.sourceFilesWidget.setItem(item.row(), SourceColumn.Type, QTableWidgetItem("File")) # No files count, if entry itself is a file - self.sourceFilesWidget.item(item.row(), 3).setText("") + self.sourceFilesWidget.item(item.row(), SourceColumn.FilesCount).setText("") db_item.path_isdir = False - self.sourceFilesWidget.item(item.row(), 2).setText(pretty_bytes(data_size)) + self.sourceFilesWidget.item(item.row(), SourceColumn.Size).setText(pretty_bytes(data_size)) db_item.dir_size = data_size db_item.dir_files_count = files_count @@ -68,10 +78,10 @@ def set_path_info(self, path, data_size, files_count): self.updateThreads.remove(thrd) def update_path_info(self, index_row): - path = self.sourceFilesWidget.item(index_row, 0).text() - self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Calculating...")) - self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("Calculating...")) - self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("Calculating...")) + path = self.sourceFilesWidget.item(index_row, SourceColumn.Path).text() + self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("Calculating...")) + self.sourceFilesWidget.setItem(index_row, SourceColumn.Size, QTableWidgetItem("Calculating...")) + self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, QTableWidgetItem("Calculating...")) getDir = FilePathInfoAsync(path) getDir.signal.connect(self.set_path_info) getDir.setObjectName(path) @@ -82,24 +92,24 @@ def add_source_to_table(self, source, update_data): index_row = self.sourceFilesWidget.rowCount() self.sourceFilesWidget.insertRow(index_row) item_path = QTableWidgetItem(source.dir) - self.sourceFilesWidget.setItem(index_row, 0, item_path) + self.sourceFilesWidget.setItem(index_row, SourceColumn.Path, item_path) if update_data: self.update_path_info(index_row) else: # Use cached data from DB if source.dir_size < 0: # Size/count wasn't calculated yet - self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("")) - self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem("")) - self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row, SourceColumn.Size, QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, QTableWidgetItem("")) else: if source.path_isdir: - self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("Folder")) - self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem(format(source.dir_files_count))) + self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("Folder")) + self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, QTableWidgetItem(format(source.dir_files_count))) else: - self.sourceFilesWidget.setItem(index_row, 1, QTableWidgetItem("File")) - self.sourceFilesWidget.setItem(index_row, 3, QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("File")) + self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, QTableWidgetItem("")) - self.sourceFilesWidget.setItem(index_row, 2, QTableWidgetItem(pretty_bytes(source.dir_size))) + self.sourceFilesWidget.setItem(index_row, SourceColumn.Size, QTableWidgetItem(pretty_bytes(source.dir_size))) def populate_from_profile(self): profile = self.profile() @@ -113,7 +123,7 @@ def populate_from_profile(self): self.add_source_to_table(source, False) # Initially, sort entries by path name in ascending order - self.sourceFilesWidget.model().sort(0,QtCore.Qt.AscendingOrder) + self.sourceFilesWidget.model().sort(SourceColumn.Path, QtCore.Qt.AscendingOrder) self.excludePatternsField.appendPlainText(profile.exclude_patterns) self.excludeIfPresentField.appendPlainText(profile.exclude_if_present) self.excludePatternsField.textChanged.connect(self.save_exclude_patterns) @@ -146,7 +156,7 @@ def source_remove(self): indexes.sort() # remove each selected row, starting with highest index (otherways, higher indexes become invalid) for index in reversed(indexes): - db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(), 0).text()) + db_item = SourceFileModel.get(dir=self.sourceFilesWidget.item(index.row(), SourceColumn.Path).text()) db_item.delete_instance() self.sourceFilesWidget.removeRow(index.row()) From 2a3031bb1c0080db5c3d2e0e77d3e17f368e6f45 Mon Sep 17 00:00:00 2001 From: samuel-w Date: Thu, 8 Oct 2020 14:08:01 -0500 Subject: [PATCH 39/64] Sort by file size Sorting by file/folder count is broken --- src/vorta/utils.py | 12 ++++++++++++ src/vorta/views/source_tab.py | 25 +++++++++++++------------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 49198f457..452e0b01f 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -20,6 +20,7 @@ from PyQt5 import QtCore from PyQt5.QtCore import QFileInfo, QThread, pyqtSignal from PyQt5.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon +from decimal import Decimal from vorta.borg._compatibility import BorgCompatibility from vorta.log import logger @@ -149,6 +150,17 @@ def get_private_keys(): return available_private_keys +def sort_sizes(size_list): + """ Sorts sizes with extensions. Assumes that size is already in largest unit possible """ + final_list = [] + for suffix in [" B", " KB", " MB", " GB", " TB"]: + sub_list = [Decimal(size[:-len(suffix)]) + for size in size_list if size.endswith(suffix) and size[:-len(suffix)][-1].isnumeric()] + sub_list.sort() + final_list += [(str(size) + suffix) for size in sub_list] + return final_list + + def pretty_bytes(size): """from https://stackoverflow.com/questions/12523586/ python-format-size-application-converting-b-to-kb-mb-gb-tb/37423778""" diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 18f47320a..f2f518a25 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -1,6 +1,6 @@ from PyQt5 import uic from ..models import SourceFileModel, BackupProfileMixin, SettingsModel -from ..utils import get_asset, choose_file_dialog, pretty_bytes, FilePathInfoAsync +from ..utils import get_asset, choose_file_dialog, pretty_bytes, sort_sizes, FilePathInfoAsync from PyQt5 import QtCore from PyQt5.QtCore import QFileInfo from PyQt5.QtWidgets import QApplication, QMessageBox, QTableWidgetItem, QHeaderView @@ -9,16 +9,22 @@ uifile = get_asset('UI/sourcetab.ui') SourceUI, SourceBase = uic.loadUiType(uifile) + class SourceColumn: Path = 0 Type = 1 Size = 2 FilesCount = 3 + +class SizeItem(QTableWidgetItem): + def __lt__(self, other): + return sort_sizes([self.text(), other.text()]) != [self.text(), other.text()] + + class SourceTab(SourceBase, SourceUI, BackupProfileMixin): updateThreads = [] - def __init__(self, parent=None): super().__init__(parent) self.setupUi(parent) @@ -32,8 +38,8 @@ def __init__(self, parent=None): header.setSectionResizeMode(SourceColumn.Type, QHeaderView.ResizeToContents) header.setSectionResizeMode(SourceColumn.Size, QHeaderView.ResizeToContents) header.setSectionResizeMode(SourceColumn.FilesCount, QHeaderView.ResizeToContents) - header.sortIndicatorChanged.connect(self.sort_entries_by_column) + self.sourceFilesWidget.setSortingEnabled(True) self.sourceAddFolder.clicked.connect(lambda: self.source_add(want_folder=True)) self.sourceAddFile.clicked.connect(lambda: self.source_add(want_folder=False)) self.sourceRemove.clicked.connect(self.source_remove) @@ -43,13 +49,6 @@ def __init__(self, parent=None): self.excludeIfPresentField.textChanged.connect(self.save_exclude_if_present) self.populate_from_profile() - def sort_entries_by_column(self, column_index, order): - if column_index == SourceColumn.Size: - # todo change this - self.sourceFilesWidget.model().sort(column_index, order) - else: - self.sourceFilesWidget.model().sort(column_index, order) - def set_path_info(self, path, data_size, files_count): items = self.sourceFilesWidget.findItems(path, QtCore.Qt.MatchExactly) # Conversion int->str->int needed because QT limits int to 32-bit @@ -104,12 +103,14 @@ def add_source_to_table(self, source, update_data): else: if source.path_isdir: self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("Folder")) - self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, QTableWidgetItem(format(source.dir_files_count))) + self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, + QTableWidgetItem(format(source.dir_files_count))) else: self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("File")) self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, QTableWidgetItem("")) - self.sourceFilesWidget.setItem(index_row, SourceColumn.Size, QTableWidgetItem(pretty_bytes(source.dir_size))) + self.sourceFilesWidget.setItem(index_row, SourceColumn.Size, + SizeItem(pretty_bytes(source.dir_size))) def populate_from_profile(self): profile = self.profile() From dbaab6755512ace0d0a28e128a323b95805220b9 Mon Sep 17 00:00:00 2001 From: samuel-w Date: Thu, 8 Oct 2020 19:16:40 -0500 Subject: [PATCH 40/64] Fix --- src/vorta/utils.py | 3 +++ src/vorta/views/source_tab.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 452e0b01f..6a38c6740 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -158,6 +158,9 @@ def sort_sizes(size_list): for size in size_list if size.endswith(suffix) and size[:-len(suffix)][-1].isnumeric()] sub_list.sort() final_list += [(str(size) + suffix) for size in sub_list] + # Skip additional loops + if len(final_list) == len(size_list): + break return final_list diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index f2f518a25..c8e7c5ed9 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -19,7 +19,7 @@ class SourceColumn: class SizeItem(QTableWidgetItem): def __lt__(self, other): - return sort_sizes([self.text(), other.text()]) != [self.text(), other.text()] + return sort_sizes([self.text(), other.text()]) == [self.text(), other.text()] class SourceTab(SourceBase, SourceUI, BackupProfileMixin): From a0ca73700d35a68ea782a5b1eed1c725044efcdb Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 10 Oct 2020 20:37:46 +0000 Subject: [PATCH 41/64] Fixed sorting --- src/vorta/views/source_tab.py | 52 +++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index c8e7c5ed9..47a181b91 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -22,6 +22,20 @@ def __lt__(self, other): return sort_sizes([self.text(), other.text()]) == [self.text(), other.text()] +class FilesCount(QTableWidgetItem): + def __lt__(self, other): + # Verify that conversion is only performed on valid integers + # If one of the 2 elements is no number, put these elements at the end + # This is important if the text i "Calculating..." or "" + if self.text().isdigit() and other.text().isdigit(): + return int(self.text()) < int(other.text()) # Compare & return result + else: + if not self.text().isdigit(): + return 1 # Move one down if current item has no valid count + if not other.text().isdigit(): + return 0 + + class SourceTab(SourceBase, SourceUI, BackupProfileMixin): updateThreads = [] @@ -58,11 +72,11 @@ def set_path_info(self, path, data_size, files_count): for item in items: db_item = SourceFileModel.get(dir=path) if QFileInfo(path).isDir(): - self.sourceFilesWidget.setItem(item.row(), SourceColumn.Type, QTableWidgetItem("Folder")) + self.sourceFilesWidget.item(item.row(), SourceColumn.Type).setText("Folder") self.sourceFilesWidget.item(item.row(), SourceColumn.FilesCount).setText(format(files_count)) db_item.path_isdir = True else: - self.sourceFilesWidget.setItem(item.row(), SourceColumn.Type, QTableWidgetItem("File")) + self.sourceFilesWidget.item(item.row(), SourceColumn.Type).setText("File") # No files count, if entry itself is a file self.sourceFilesWidget.item(item.row(), SourceColumn.FilesCount).setText("") db_item.path_isdir = False @@ -78,9 +92,9 @@ def set_path_info(self, path, data_size, files_count): def update_path_info(self, index_row): path = self.sourceFilesWidget.item(index_row, SourceColumn.Path).text() - self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("Calculating...")) - self.sourceFilesWidget.setItem(index_row, SourceColumn.Size, QTableWidgetItem("Calculating...")) - self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, QTableWidgetItem("Calculating...")) + self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText("Calculating...") + self.sourceFilesWidget.item(index_row, SourceColumn.Size).setText("Calculating...") + self.sourceFilesWidget.item(index_row, SourceColumn.FilesCount).setText("Calculating...") getDir = FilePathInfoAsync(path) getDir.signal.connect(self.set_path_info) getDir.setObjectName(path) @@ -90,27 +104,23 @@ def update_path_info(self, index_row): def add_source_to_table(self, source, update_data): index_row = self.sourceFilesWidget.rowCount() self.sourceFilesWidget.insertRow(index_row) - item_path = QTableWidgetItem(source.dir) - self.sourceFilesWidget.setItem(index_row, SourceColumn.Path, item_path) + # Insert all items on current row + self.sourceFilesWidget.setItem(index_row, SourceColumn.Path, QTableWidgetItem(source.dir)) + self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("")) + self.sourceFilesWidget.setItem(index_row, SourceColumn.Size, SizeItem("")) + self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, FilesCount("")) + if update_data: self.update_path_info(index_row) else: # Use cached data from DB - if source.dir_size < 0: - # Size/count wasn't calculated yet - self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("")) - self.sourceFilesWidget.setItem(index_row, SourceColumn.Size, QTableWidgetItem("")) - self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, QTableWidgetItem("")) - else: + if source.dir_size > -1: + self.sourceFilesWidget.item(index_row, SourceColumn.Size).setText(pretty_bytes(source.dir_size)) + if source.path_isdir: - self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("Folder")) - self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, - QTableWidgetItem(format(source.dir_files_count))) + self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText("Folder") + self.sourceFilesWidget.item(index_row, SourceColumn.FilesCount).setText(format(source.dir_files_count)) else: - self.sourceFilesWidget.setItem(index_row, SourceColumn.Type, QTableWidgetItem("File")) - self.sourceFilesWidget.setItem(index_row, SourceColumn.FilesCount, QTableWidgetItem("")) - - self.sourceFilesWidget.setItem(index_row, SourceColumn.Size, - SizeItem(pretty_bytes(source.dir_size))) + self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText("File") def populate_from_profile(self): profile = self.profile() From d323eb156fb6ae39dc877e2a49afe0d5fc145e34 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sat, 10 Oct 2020 20:39:15 +0000 Subject: [PATCH 42/64] Changed command to get foldersize As requested for Mac --- src/vorta/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 6a38c6740..a66b13182 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -58,7 +58,7 @@ def get_directory_size(dirPath): files_count = int(res.stdout.split(b'\t')[0]) if files_count > 1: # files count on empty directory is still 1 because of '.', ignore it - res = subprocess.run('du -sb .', cwd=dirPath, shell=True, check=False, + res = subprocess.run('du -s -b .', cwd=dirPath, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) data_size = int(res.stdout.split(b'\t')[0]) else: From 84d3d7691430dfe73882efec3df6614747bb7a12 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 11 Oct 2020 09:01:20 +0000 Subject: [PATCH 43/64] GetFolderSize: Add different cmd for MacOS --- src/vorta/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index a66b13182..c0a4dc8de 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -58,8 +58,13 @@ def get_directory_size(dirPath): files_count = int(res.stdout.split(b'\t')[0]) if files_count > 1: # files count on empty directory is still 1 because of '.', ignore it - res = subprocess.run('du -s -b .', cwd=dirPath, shell=True, check=False, - stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + if sys.platform == 'darwin': + res = subprocess.run('du -s -B 1 -A .', cwd=dirPath, shell=True, check=False, + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + else: + res = subprocess.run('du -sb .', cwd=dirPath, shell=True, check=False, + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + data_size = int(res.stdout.split(b'\t')[0]) else: data_size = 0 From 955c45ddb78982349be57baa3d821f97522bdf3e Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 11 Oct 2020 09:02:00 +0000 Subject: [PATCH 44/64] fix lint --- src/vorta/views/source_tab.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 47a181b91..0dc813779 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -28,10 +28,10 @@ def __lt__(self, other): # If one of the 2 elements is no number, put these elements at the end # This is important if the text i "Calculating..." or "" if self.text().isdigit() and other.text().isdigit(): - return int(self.text()) < int(other.text()) # Compare & return result + return int(self.text()) < int(other.text()) # Compare & return result else: if not self.text().isdigit(): - return 1 # Move one down if current item has no valid count + return 1 # Move one down if current item has no valid count if not other.text().isdigit(): return 0 @@ -118,7 +118,8 @@ def add_source_to_table(self, source, update_data): if source.path_isdir: self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText("Folder") - self.sourceFilesWidget.item(index_row, SourceColumn.FilesCount).setText(format(source.dir_files_count)) + self.sourceFilesWidget.item(index_row, + SourceColumn.FilesCount).setText(format(source.dir_files_count)) else: self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText("File") From 53c916cd90ff3fe97ed9efd7b9db7285a424db43 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Sun, 11 Oct 2020 09:04:55 +0000 Subject: [PATCH 45/64] fix lint2 --- src/vorta/views/source_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 0dc813779..7b1ecdcb5 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -118,7 +118,7 @@ def add_source_to_table(self, source, update_data): if source.path_isdir: self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText("Folder") - self.sourceFilesWidget.item(index_row, + self.sourceFilesWidget.item(index_row, SourceColumn.FilesCount).setText(format(source.dir_files_count)) else: self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText("File") From 2fa600ebe258763d4d8261cfcd33d19e7568dc03 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Mon, 12 Oct 2020 17:09:55 +0000 Subject: [PATCH 46/64] Change for Mac OS Get folder size correctly on MacOS --- src/vorta/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index c0a4dc8de..54074f9af 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -61,11 +61,11 @@ def get_directory_size(dirPath): if sys.platform == 'darwin': res = subprocess.run('du -s -B 1 -A .', cwd=dirPath, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + data_size = int(res.stdout.split()) else: res = subprocess.run('du -sb .', cwd=dirPath, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) - - data_size = int(res.stdout.split(b'\t')[0]) + data_size = int(res.stdout.split(b'\t')[0]) else: data_size = 0 files_count = 0 From 78e5f19692559535ed948b5a667b805aad51a787 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Mon, 12 Oct 2020 17:11:59 +0000 Subject: [PATCH 47/64] Syntax corrected --- src/vorta/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 54074f9af..c6affa1a1 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -61,7 +61,7 @@ def get_directory_size(dirPath): if sys.platform == 'darwin': res = subprocess.run('du -s -B 1 -A .', cwd=dirPath, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) - data_size = int(res.stdout.split()) + data_size = int(res.stdout.split()[0]) else: res = subprocess.run('du -sb .', cwd=dirPath, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) From 9776708489c8b37efee5e7745fe4d7ab301a640d Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Mon, 12 Oct 2020 17:12:31 +0000 Subject: [PATCH 48/64] typo --- src/vorta/views/source_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 7b1ecdcb5..db6fa0d09 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -26,7 +26,7 @@ class FilesCount(QTableWidgetItem): def __lt__(self, other): # Verify that conversion is only performed on valid integers # If one of the 2 elements is no number, put these elements at the end - # This is important if the text i "Calculating..." or "" + # This is important if the text is "Calculating..." or "" if self.text().isdigit() and other.text().isdigit(): return int(self.text()) < int(other.text()) # Compare & return result else: From 4f6bbff9e7c150912f43d3e701c7fa2ebc7dd237 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 13 Oct 2020 18:25:51 +0000 Subject: [PATCH 49/64] Changed get_directory_size Implemented fallback for non-linux OS --- src/vorta/utils.py | 47 +++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index c6affa1a1..03c1bfb84 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -51,24 +51,41 @@ def run(self): self.signal.emit(self.path, str(self.size), str(self.files_count)) -def get_directory_size(dirPath): - res = subprocess.run('find . | wc -l', cwd=dirPath, shell=True, check=False, - stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) - - files_count = int(res.stdout.split(b'\t')[0]) - - if files_count > 1: # files count on empty directory is still 1 because of '.', ignore it - if sys.platform == 'darwin': - res = subprocess.run('du -s -B 1 -A .', cwd=dirPath, shell=True, check=False, - stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) - data_size = int(res.stdout.split()[0]) - else: - res = subprocess.run('du -sb .', cwd=dirPath, shell=True, check=False, +def get_directory_size(dir_path): + if sys.platform.startswith('linux'): + res = subprocess.run('find . | wc -l', cwd=dir_path, shell=True, check=False, + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + files_count = int(res.stdout.split(b'\t')[0]) + + if files_count > 1: # files count on empty directory is still 1 because of '.', ignore it + res = subprocess.run('du -sb .', cwd=dir_path, shell=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) data_size = int(res.stdout.split(b'\t')[0]) - else: + else: + data_size = 0 + files_count = 0 + else: # Other platform than linux, use fallback to determine files count and data sizes + ''' Get number of files only and total size in bytes from a path. + Based off https://stackoverflow.com/a/17936789 ''' data_size = 0 - files_count = 0 + seen = set() + + for curr_path, _, file_names in os.walk(dir_path): + for file_name in file_names: + file_path = os.path.join(curr_path, file_name) + + # Ignore symbolic links, since borg doesn't follow them + if os.path.islink(file_path): + continue + + stat = os.stat(file_path) + + # Visit each file once + if stat.st_ino not in seen: + seen.add(stat.st_ino) + data_size += stat.st_size + + files_count = len(seen) return data_size, files_count From 79f7c411eaaea6be11695303132609b6815b1236 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 13 Oct 2020 18:29:38 +0000 Subject: [PATCH 50/64] fix lint --- src/vorta/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 03c1bfb84..c56847675 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -65,7 +65,7 @@ def get_directory_size(dir_path): data_size = 0 files_count = 0 else: # Other platform than linux, use fallback to determine files count and data sizes - ''' Get number of files only and total size in bytes from a path. + ''' Get number of files only and total size in bytes from a path. Based off https://stackoverflow.com/a/17936789 ''' data_size = 0 seen = set() From 0e4614345bdc2b83875d0b1178e3eef2e490f7cb Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Wed, 14 Oct 2020 18:51:15 +0000 Subject: [PATCH 51/64] Update src/vorta/views/source_tab.py Co-authored-by: Samuel --- src/vorta/views/source_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index db6fa0d09..87c55aeac 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -72,7 +72,7 @@ def set_path_info(self, path, data_size, files_count): for item in items: db_item = SourceFileModel.get(dir=path) if QFileInfo(path).isDir(): - self.sourceFilesWidget.item(item.row(), SourceColumn.Type).setText("Folder") + self.sourceFilesWidget.item(item.row(), SourceColumn.Type).setText(self.tr("Folder")) self.sourceFilesWidget.item(item.row(), SourceColumn.FilesCount).setText(format(files_count)) db_item.path_isdir = True else: From 2fd8539b4c2cda3141fa1958b6c563df442112ba Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Wed, 14 Oct 2020 18:51:27 +0000 Subject: [PATCH 52/64] Update src/vorta/views/source_tab.py Co-authored-by: Samuel --- src/vorta/views/source_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 87c55aeac..40fce3178 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -76,7 +76,7 @@ def set_path_info(self, path, data_size, files_count): self.sourceFilesWidget.item(item.row(), SourceColumn.FilesCount).setText(format(files_count)) db_item.path_isdir = True else: - self.sourceFilesWidget.item(item.row(), SourceColumn.Type).setText("File") + self.sourceFilesWidget.item(item.row(), SourceColumn.Type).setText(self.tr("File")) # No files count, if entry itself is a file self.sourceFilesWidget.item(item.row(), SourceColumn.FilesCount).setText("") db_item.path_isdir = False From 3cda2edd35d276a4e8c678fd909b3646a09a5776 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Wed, 14 Oct 2020 18:51:37 +0000 Subject: [PATCH 53/64] Update src/vorta/views/source_tab.py Co-authored-by: Samuel --- src/vorta/views/source_tab.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 40fce3178..9dcb20d96 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -92,9 +92,9 @@ def set_path_info(self, path, data_size, files_count): def update_path_info(self, index_row): path = self.sourceFilesWidget.item(index_row, SourceColumn.Path).text() - self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText("Calculating...") - self.sourceFilesWidget.item(index_row, SourceColumn.Size).setText("Calculating...") - self.sourceFilesWidget.item(index_row, SourceColumn.FilesCount).setText("Calculating...") + self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText(self.tr("Calculating...")) + self.sourceFilesWidget.item(index_row, SourceColumn.Size).setText(self.tr("Calculating...")) + self.sourceFilesWidget.item(index_row, SourceColumn.FilesCount).setText(self.tr("Calculating...")) getDir = FilePathInfoAsync(path) getDir.signal.connect(self.set_path_info) getDir.setObjectName(path) From 2dad3fb96cd6dea97002b37ec4b332d3afc2bf21 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Wed, 14 Oct 2020 18:51:50 +0000 Subject: [PATCH 54/64] Update src/vorta/views/source_tab.py Co-authored-by: Samuel --- src/vorta/views/source_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index 9dcb20d96..e8383854c 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -117,7 +117,7 @@ def add_source_to_table(self, source, update_data): self.sourceFilesWidget.item(index_row, SourceColumn.Size).setText(pretty_bytes(source.dir_size)) if source.path_isdir: - self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText("Folder") + self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText(self.tr("Folder")) self.sourceFilesWidget.item(index_row, SourceColumn.FilesCount).setText(format(source.dir_files_count)) else: From 629faeb9c6d0ef8d64756190e203c6a11a5c3a96 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Wed, 14 Oct 2020 18:51:59 +0000 Subject: [PATCH 55/64] Update src/vorta/assets/UI/sourcetab.ui Co-authored-by: Samuel --- src/vorta/assets/UI/sourcetab.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/assets/UI/sourcetab.ui b/src/vorta/assets/UI/sourcetab.ui index 0759ff2c7..2e7d20838 100644 --- a/src/vorta/assets/UI/sourcetab.ui +++ b/src/vorta/assets/UI/sourcetab.ui @@ -81,7 +81,7 @@ - File/Folder Count + File Count From 6d9e8479dbabc489325a7dd852e676da8aaa8709 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Wed, 14 Oct 2020 18:52:11 +0000 Subject: [PATCH 56/64] Update src/vorta/views/source_tab.py Co-authored-by: Samuel --- src/vorta/views/source_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/source_tab.py b/src/vorta/views/source_tab.py index e8383854c..89096fc70 100644 --- a/src/vorta/views/source_tab.py +++ b/src/vorta/views/source_tab.py @@ -121,7 +121,7 @@ def add_source_to_table(self, source, update_data): self.sourceFilesWidget.item(index_row, SourceColumn.FilesCount).setText(format(source.dir_files_count)) else: - self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText("File") + self.sourceFilesWidget.item(index_row, SourceColumn.Type).setText(self.tr("File")) def populate_from_profile(self): profile = self.profile() From b0d00209fd6e0456f352a77ace697319b5653978 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Wed, 14 Oct 2020 18:59:10 +0000 Subject: [PATCH 57/64] Use python method to get folder size --- src/vorta/utils.py | 49 +++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index c56847675..e5aabad65 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -52,40 +52,27 @@ def run(self): def get_directory_size(dir_path): - if sys.platform.startswith('linux'): - res = subprocess.run('find . | wc -l', cwd=dir_path, shell=True, check=False, - stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) - files_count = int(res.stdout.split(b'\t')[0]) - - if files_count > 1: # files count on empty directory is still 1 because of '.', ignore it - res = subprocess.run('du -sb .', cwd=dir_path, shell=True, check=False, - stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) - data_size = int(res.stdout.split(b'\t')[0]) - else: - data_size = 0 - files_count = 0 - else: # Other platform than linux, use fallback to determine files count and data sizes - ''' Get number of files only and total size in bytes from a path. - Based off https://stackoverflow.com/a/17936789 ''' - data_size = 0 - seen = set() - - for curr_path, _, file_names in os.walk(dir_path): - for file_name in file_names: - file_path = os.path.join(curr_path, file_name) - - # Ignore symbolic links, since borg doesn't follow them - if os.path.islink(file_path): - continue + ''' Get number of files only and total size in bytes from a path. + Based off https://stackoverflow.com/a/17936789 ''' + data_size = 0 + seen = set() + + for curr_path, _, file_names in os.walk(dir_path): + for file_name in file_names: + file_path = os.path.join(curr_path, file_name) + + # Ignore symbolic links, since borg doesn't follow them + if os.path.islink(file_path): + continue - stat = os.stat(file_path) + stat = os.stat(file_path) - # Visit each file once - if stat.st_ino not in seen: - seen.add(stat.st_ino) - data_size += stat.st_size + # Visit each file once + if stat.st_ino not in seen: + seen.add(stat.st_ino) + data_size += stat.st_size - files_count = len(seen) + files_count = len(seen) return data_size, files_count From 8ed9ad96c20a3cd7e02bf4dc3c120344beea7cdd Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Wed, 14 Oct 2020 19:01:38 +0000 Subject: [PATCH 58/64] lint --- src/vorta/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index e5aabad65..9b759d618 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -7,7 +7,6 @@ import re import sys import unicodedata -import subprocess from collections import defaultdict from datetime import datetime as dt from functools import reduce From 1c88285c41afdb581abeabeea1b88365cc8d8650 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 20 Oct 2020 17:46:11 +0000 Subject: [PATCH 59/64] Update src/vorta/models.py Co-authored-by: Samuel --- src/vorta/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/models.py b/src/vorta/models.py index 9996158e6..ac6ef79ef 100644 --- a/src/vorta/models.py +++ b/src/vorta/models.py @@ -221,7 +221,7 @@ def get_misc_settings(): { 'key': 'get_srcpath_datasize', 'value': True, 'type': 'checkbox', 'label': trans_late('settings', - 'Get size of file/folder when added') + 'Get statistics of file/folder when added') }, { 'key': 'previous_profile_id', 'str_value': '1', 'type': 'internal', From 87d357aba63967f9cce79c51ac12f183168c69e9 Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 20 Oct 2020 17:46:25 +0000 Subject: [PATCH 60/64] Update src/vorta/utils.py Co-authored-by: Samuel --- src/vorta/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 9b759d618..bcef0c1c3 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -28,7 +28,6 @@ QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) # enable highdpi scaling QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) # use highdpi icons -network_status_monitor = NetworkStatusMonitor.get_network_status_monitor() logger.info('Using %s NetworkStatusMonitor implementation.', network_status_monitor.__class__.__name__) borg_compat = BorgCompatibility() From 1704e77a27fb5bdae1ef7d1b11dddbfbc3c13f0b Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Tue, 20 Oct 2020 17:46:37 +0000 Subject: [PATCH 61/64] Update src/vorta/utils.py Co-authored-by: Samuel --- src/vorta/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index bcef0c1c3..f202090cd 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -205,7 +205,7 @@ def get_sorted_wifis(profile): from vorta.models import WifiSettingModel - system_wifis = network_status_monitor.get_known_wifis() + system_wifis = get_network_status_monitor().get_known_wifis() if system_wifis is None: # Don't show any networks if we can't get the current list return [] From 1f59399967a66db2a3ed8d04e2653edb7ffd2f4a Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Wed, 21 Oct 2020 11:30:37 +0000 Subject: [PATCH 62/64] Update src/vorta/utils.py Co-authored-by: Samuel --- src/vorta/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index f202090cd..2fd236a31 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -28,8 +28,6 @@ QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) # enable highdpi scaling QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) # use highdpi icons -logger.info('Using %s NetworkStatusMonitor implementation.', network_status_monitor.__class__.__name__) - borg_compat = BorgCompatibility() _network_status_monitor = None From 1753170df424faaaa921df618941cdcadd4b4bad Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Mon, 26 Oct 2020 14:47:35 +0000 Subject: [PATCH 63/64] Update src/vorta/utils.py Co-authored-by: Samuel --- src/vorta/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index 2fd236a31..d35a0e0c1 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -19,7 +19,6 @@ from PyQt5 import QtCore from PyQt5.QtCore import QFileInfo, QThread, pyqtSignal from PyQt5.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon -from decimal import Decimal from vorta.borg._compatibility import BorgCompatibility from vorta.log import logger From 790d9381623ccdbad58f2dcaaba558de6655e02e Mon Sep 17 00:00:00 2001 From: XXXBold <30773753+XXXBold@users.noreply.github.com> Date: Mon, 26 Oct 2020 14:47:44 +0000 Subject: [PATCH 64/64] Update src/vorta/utils.py Co-authored-by: Samuel --- src/vorta/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/utils.py b/src/vorta/utils.py index d35a0e0c1..c3ae92d57 100644 --- a/src/vorta/utils.py +++ b/src/vorta/utils.py @@ -158,7 +158,7 @@ def sort_sizes(size_list): """ Sorts sizes with extensions. Assumes that size is already in largest unit possible """ final_list = [] for suffix in [" B", " KB", " MB", " GB", " TB"]: - sub_list = [Decimal(size[:-len(suffix)]) + sub_list = [float(size[:-len(suffix)]) for size in size_list if size.endswith(suffix) and size[:-len(suffix)][-1].isnumeric()] sub_list.sort() final_list += [(str(size) + suffix) for size in sub_list]