From 69392c5d4f133207a7fd1ff9fced62c2778ce2dd Mon Sep 17 00:00:00 2001 From: BasioMeusPuga Date: Thu, 30 Nov 2017 01:20:30 +0530 Subject: [PATCH] Table editing, Traverse and associate with tags all paths entered --- __main__.py | 3 +- database.py | 9 ++-- models.py | 15 ++++++ resources/raw/settings.ui | 22 ++++----- resources/settingswindow.py | 16 +++--- settings.py | 7 +-- settingsdialog.py | 99 ++++++++++++++++++++++--------------- sorter.py | 5 +- threaded.py | 40 ++++++++++++--- 9 files changed, 138 insertions(+), 78 deletions(-) diff --git a/__main__.py b/__main__.py index e881f87..e116172 100755 --- a/__main__.py +++ b/__main__.py @@ -230,6 +230,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): # Use maptosource() here to get the view_model # indices selected in the listView # Implement this for the tableview + # The same process can be used to mirror selection selected_books = self.listView.selectedIndexes() if selected_books: @@ -603,8 +604,6 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): self.format_contentView() def show_settings(self): - # TODO - # The hiding of the settings dialog should uncheck the settings show action if not self.settings_dialog.isVisible(): self.settings_dialog.show() else: diff --git a/database.py b/database.py index 9907a26..aff1616 100644 --- a/database.py +++ b/database.py @@ -38,11 +38,10 @@ class DatabaseFunctions: name = i[1] tags = i[2] - # TODO - # Get insert or replace working - - sql_command = ("INSERT OR REPLACE INTO directories (Path,Name,Tags) VALUES (?, ?, ?)") - self.database.execute(sql_command, [path, name, tags]) + sql_command = ( + "INSERT OR REPLACE INTO directories (ID, Path, Name, Tags)\ + VALUES ((SELECT ID FROM directories WHERE Path = ?), ?, ?, ?)") + self.database.execute(sql_command, [path, path, name, tags]) self.database.commit() self.close_database() diff --git a/models.py b/models.py index b263f27..535a204 100644 --- a/models.py +++ b/models.py @@ -91,6 +91,21 @@ class MostExcellentTableModel(QtCore.QAbstractTableModel): # These are standard select but don't edit values return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable + def setData(self, index, value, role=QtCore.Qt.EditRole): + # We don't need to connect this to dataChanged since the underlying + # table model (not the proxy model) is the one that's being updated + + # Database tags for files should not be updated each time + # a new folder gets added or deleted from the directory + # This will be done @ runtime + # Individually set file tags will be preserved + # Duplicate file tags will be removed + + row = index.row() + col = index.column() + self.display_data[row][col] = value + return True + class TableProxyModel(QtCore.QSortFilterProxyModel): def __init__(self, parent=None): diff --git a/resources/raw/settings.ui b/resources/raw/settings.ui index cf4b6f6..198f5fa 100644 --- a/resources/raw/settings.ui +++ b/resources/raw/settings.ui @@ -82,13 +82,6 @@ - - - - Refresh - - - @@ -135,16 +128,23 @@ - + - About + OK - + - OK + Cancel + + + + + + + About diff --git a/resources/settingswindow.py b/resources/settingswindow.py index 886295f..de34349 100644 --- a/resources/settingswindow.py +++ b/resources/settingswindow.py @@ -45,9 +45,6 @@ class Ui_Dialog(object): self.removeButton = QtWidgets.QPushButton(self.groupBox_2) self.removeButton.setObjectName("removeButton") self.horizontalLayout.addWidget(self.removeButton) - self.refreshButton = QtWidgets.QPushButton(self.groupBox_2) - self.refreshButton.setObjectName("refreshButton") - self.horizontalLayout.addWidget(self.refreshButton) self.gridLayout_2.addLayout(self.horizontalLayout, 1, 0, 1, 1) self.verticalLayout_2.addWidget(self.groupBox_2) self.groupBox = QtWidgets.QGroupBox(Dialog) @@ -71,12 +68,15 @@ class Ui_Dialog(object): self.gridLayout.addLayout(self.horizontalLayout_3, 0, 0, 1, 1) self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setObjectName("horizontalLayout_2") - self.aboutButton = QtWidgets.QPushButton(self.groupBox) - self.aboutButton.setObjectName("aboutButton") - self.horizontalLayout_2.addWidget(self.aboutButton) self.okButton = QtWidgets.QPushButton(self.groupBox) self.okButton.setObjectName("okButton") self.horizontalLayout_2.addWidget(self.okButton) + self.cancelButton = QtWidgets.QPushButton(self.groupBox) + self.cancelButton.setObjectName("cancelButton") + self.horizontalLayout_2.addWidget(self.cancelButton) + self.aboutButton = QtWidgets.QPushButton(self.groupBox) + self.aboutButton.setObjectName("aboutButton") + self.horizontalLayout_2.addWidget(self.aboutButton) self.gridLayout.addLayout(self.horizontalLayout_2, 1, 0, 1, 1) self.verticalLayout_2.addWidget(self.groupBox) self.gridLayout_3.addLayout(self.verticalLayout_2, 0, 0, 1, 1) @@ -91,12 +91,12 @@ class Ui_Dialog(object): self.tableFilterEdit.setPlaceholderText(_translate("Dialog", "Search for Paths, Names, Tags...")) self.addButton.setText(_translate("Dialog", "Add")) self.removeButton.setText(_translate("Dialog", "Remove")) - self.refreshButton.setText(_translate("Dialog", "Refresh")) self.groupBox.setTitle(_translate("Dialog", "Startup")) self.checkBox.setText(_translate("Dialog", "Auto add files")) self.fileRemember.setText(_translate("Dialog", "Remember open files")) self.checkBox_2.setText(_translate("Dialog", "Show Library")) self.checkBox_3.setText(_translate("Dialog", "Cover Shadows")) - self.aboutButton.setText(_translate("Dialog", "About")) self.okButton.setText(_translate("Dialog", "OK")) + self.cancelButton.setText(_translate("Dialog", "Cancel")) + self.aboutButton.setText(_translate("Dialog", "About")) diff --git a/settings.py b/settings.py index 28fad8b..4bda41c 100644 --- a/settings.py +++ b/settings.py @@ -124,6 +124,7 @@ class Settings: self.settings.endGroup() self.settings.beginGroup('settingsWindow') - self.settings.setValue('windowSize', self.parent_window.settings_dialog.window_size) - self.settings.setValue('windowPosition', self.parent_window.settings_dialog.window_position) - self.settings.setValue('tableHeaders', self.parent_window.settings_dialog.table_headers) + these_settings = self.parent_window.settings_dialog_settings + self.settings.setValue('windowSize', these_settings['size']) + self.settings.setValue('windowPosition', these_settings['position']) + self.settings.setValue('tableHeaders', these_settings['headers']) diff --git a/settingsdialog.py b/settingsdialog.py index b0b5e60..c5dec87 100644 --- a/settingsdialog.py +++ b/settingsdialog.py @@ -12,28 +12,33 @@ from threaded import BackGroundBookSearch, BackGroundBookAddition class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog): + # TODO + # Deletion from table + # Cancel behavior + # Update database on table model update + def __init__(self, parent_window): super(SettingsUI, self).__init__() self.setupUi(self) - # These are just for declarative purposes - self.window_size = None - self.window_position = None - self.table_headers = [] - self.last_open_directory = None self.parent_window = parent_window self.database_path = self.parent_window.database_path + self.resize(self.parent_window.settings_dialog_settings['size']) self.move(self.parent_window.settings_dialog_settings['position']) self.table_model = None + self.old_table_model = None self.table_proxy_model = None + self.paths = None self.thread = None self.tableFilterEdit.textChanged.connect(self.update_table_proxy_model) self.addButton.clicked.connect(self.add_directories) + self.cancelButton.clicked.connect(self.cancel_pressed) + self.okButton.clicked.connect(self.ok_pressed) self.generate_table() header_sizes = self.parent_window.settings_dialog_settings['headers'] @@ -45,27 +50,26 @@ class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog): QtWidgets.QHeaderView.Interactive) self.tableView.horizontalHeader().setHighlightSections(False) self.tableView.horizontalHeader().setStretchLastSection(True) - # self.tableView.horizontalHeader().setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch) - # self.database_data = collections.OrderedDict() - # self.database_modification = False - def generate_table(self): # Fetch all directories in the database - paths = database.DatabaseFunctions( + self.paths = database.DatabaseFunctions( self.database_path).fetch_data( ('Path', 'Name', 'Tags'), 'directories', {'Path': ''}, 'LIKE') - if not paths: + if not self.paths: print('Database returned no paths for settings...') + else: + # Convert to a list because tuples, well, they're tuples + self.paths = [list(i) for i in self.paths] table_header = ['Path', 'Name', 'Tags'] self.table_model = MostExcellentTableModel( - table_header, paths, None) + table_header, self.paths, None) self.create_table_proxy_model() @@ -86,52 +90,67 @@ class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog): self.tableFilterEdit.text()) def add_directories(self): - add_directory = QtWidgets.QFileDialog.getExistingDirectory( - self, 'Select Directory', self.last_open_directory, - QtWidgets.QFileDialog.ShowDirsOnly) - - data_pair = [[add_directory, None, None]] - database.DatabaseFunctions(self.database_path).set_library_paths(data_pair) - - self.generate_table() - # Directories will be added recursively - # Sub directory addition is allowed in that files will not + # Sub directory addition is not allowed + # In case it is to be allowed eventually, files will not # be duplicated. However, any additional tags will get # added to file tags - # Database tags for files should not be updated each time - # a new folder gets added or deleted from the directory - # This will be done @ runtime - # Individually set file tags will be preserved - # Duplicate file tags will be removed + add_directory = QtWidgets.QFileDialog.getExistingDirectory( + self, 'Select Directory', self.last_open_directory, + QtWidgets.QFileDialog.ShowDirsOnly) + add_directory = os.path.realpath(add_directory) - # Whatever code you write to recurse through directories will - # have to go into the threaded module + # TODO + # Account for a parent folder getting added after a subfolder + # Currently this does the inverse only + for i in self.paths: + already_present = os.path.realpath(i[0]) + if already_present == add_directory or already_present in add_directory: + QtWidgets.QMessageBox.critical( + self, + 'Error', + 'Duplicate or sub folder: ' + already_present + ' ', + QtWidgets.QMessageBox.Ok) + return + + # Set default name for the directory + directory_name = os.path.basename(add_directory).title() + data_pair = [[add_directory, directory_name, None]] + database.DatabaseFunctions(self.database_path).set_library_paths(data_pair) + self.generate_table() + + def ok_pressed(self): # Traverse directories looking for files - - def parse_all(self, directories): - add_directory = None - self.thread = BackGroundBookSearch(self, add_directory) + self.thread = BackGroundBookSearch(self, self.table_model.display_data) self.thread.finished.connect(self.do_something) self.thread.start() def do_something(self): print('Book search completed') - def closeEvent(self, event): - self.no_more_settings() - event.accept() + def cancel_pressed(self): + self.hide() + + # TODO + # Implement cancel by restoring the table model to an older version + # def showEvent(self, event): + # event.accept() def hideEvent(self, event): self.no_more_settings() event.accept() def no_more_settings(self): + self.table_model = self.old_table_model self.parent_window.libraryToolBar.settingsButton.setChecked(False) - self.window_size = self.size() - self.window_position = self.pos() - self.table_headers = [] + self.resizeEvent() + + def resizeEvent(self, event=None): + self.parent_window.settings_dialog_settings['size'] = self.size() + self.parent_window.settings_dialog_settings['position'] = self.pos() + table_headers = [] for i in range(2): - self.table_headers.append(self.tableView.horizontalHeader().sectionSize(i)) + table_headers.append(self.tableView.horizontalHeader().sectionSize(i)) + self.parent_window.settings_dialog_settings['headers'] = table_headers diff --git a/sorter.py b/sorter.py index 3e5c512..c3ccdc4 100644 --- a/sorter.py +++ b/sorter.py @@ -2,7 +2,6 @@ # TODO # See if you want to include a hash of the book's name and author -# Overwrite book if deleted and then re-added import io import os @@ -65,6 +64,10 @@ class BookSorter: self.progress_emitter.connect_to_progressbar() def database_hashes(self): + # TODO + # Overwrite book if deleted and then re-added + # Also fetch the path of the file here + all_hashes = database.DatabaseFunctions( self.database_path).fetch_data( ('Hash',), diff --git a/threaded.py b/threaded.py index d01d69a..334eedc 100644 --- a/threaded.py +++ b/threaded.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import os +from multiprocessing.dummy import Pool from PyQt5 import QtCore import sorter @@ -42,16 +43,39 @@ class BackGroundBookAddition(QtCore.QThread): class BackGroundBookSearch(QtCore.QThread): - def __init__(self, parent_window, root_directory, parent=None): + def __init__(self, parent_window, data_list, parent=None): super(BackGroundBookSearch, self).__init__(parent) self.parent_window = parent_window - self.root_directory = root_directory - self.valid_files = [] + self.data_list = data_list + self.valid_files = [] # A tuple should get added to this containing the + # file path and the folder name / tags def run(self): - for directory, subdir, files in os.walk(self.root_directory): - for filename in files: - if os.path.splitext(filename)[1][1:] in sorter.available_parsers: - self.valid_files.append(os.path.join(directory, filename)) - print(self.valid_files) + def traverse_directory(incoming_data): + root_directory = incoming_data[0] + folder_name = incoming_data[1] + folder_tags = incoming_data[2] + + for directory, subdir, files in os.walk(root_directory): + for filename in files: + if os.path.splitext(filename)[1][1:] in sorter.available_parsers: + self.valid_files.append( + (os.path.join(directory, filename), folder_name, folder_tags)) + + def initiate_threads(): + _pool = Pool(5) + _pool.map(traverse_directory, self.data_list) + _pool.close() + _pool.join() + + initiate_threads() + + # TODO + # Change existing sorter module functionality to handle + # preset tags + # Change database to accomodate User Tags, Folder Name, Folder Tags + + # self.valid_files will now be added to the database + # and models will be rebuilt accordingly + # Coming soon to a commit near you