Manually added books no longer removed on library refresh

Overhaul database module
This commit is contained in:
BasioMeusPuga
2018-03-21 15:04:28 +05:30
parent bb8de60efe
commit a1dba753e8
7 changed files with 133 additions and 74 deletions

3
TODO
View File

@@ -73,8 +73,7 @@ TODO
✓ Define every widget in code
Bugs:
Slider position change might be acting up
Deleted directories should be removed from the database
There are still slashes in the code
Deselecting all directories in the settings dialog also filters out manually added books
Secondary:
Annotations

View File

@@ -302,7 +302,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
file_list = [QtCore.QFileInfo(i).absoluteFilePath() for i in my_args]
books = sorter.BookSorter(
file_list,
'addition',
('addition', 'manual'),
self.database_path,
self.settings['auto_tags'],
self.temp_dir.path())
@@ -472,7 +472,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
self.sorterProgress.setVisible(True)
self.statusMessage.setText(self._translate('Main_UI', 'Adding books...'))
self.thread = BackGroundBookAddition(
opened_files[0], self.database_path, False, self)
opened_files[0], self.database_path, 'manual', self)
self.thread.finished.connect(self.move_on)
self.thread.start()
@@ -756,7 +756,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
contents = sorter.BookSorter(
file_paths,
'reading',
('reading', None),
self.database_path,
True,
self.temp_dir.path()).initiate_threads()
@@ -1132,11 +1132,13 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
checked = [i for i in directory_list if i[3] == QtCore.Qt.Checked]
filter_list = list(map(generate_name, checked))
filter_list.sort()
filter_list.append(self._translate('Main_UI', 'Manually Added'))
filter_actions = [QtWidgets.QAction(i, self.libraryFilterMenu) for i in filter_list]
filter_list.append(self._translate('Main_UI', 'Manually Added'))
filter_actions = [QtWidgets.QAction(i, self.libraryFilterMenu) for i in filter_list]
filter_all = QtWidgets.QAction('All', self.libraryFilterMenu)
filter_actions.append(filter_all)
for i in filter_actions:
i.setCheckable(True)
i.setChecked(True)
@@ -1166,6 +1168,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow):
else:
self.libraryFilterMenu.actions()[-1].setChecked(True)
# print(self.active_library_filters)
self.lib_ref.update_proxymodels()
def toggle_distraction_free(self):

View File

@@ -25,29 +25,70 @@ from PyQt5 import QtCore
class DatabaseInit:
def __init__(self, location_prefix):
os.makedirs(location_prefix, exist_ok=True)
database_path = os.path.join(location_prefix, 'Lector.db')
self.database_path = os.path.join(location_prefix, 'Lector.db')
if not os.path.exists(database_path):
self.database = sqlite3.connect(database_path)
self.books_table_columns = {
'id': 'INTEGER PRIMARY KEY',
'Title': 'TEXT',
'Author': 'TEXT',
'Year': 'INTEGER',
'DateAdded': 'BLOB',
'Path': 'TEXT',
'Position': 'BLOB',
'ISBN': 'TEXT',
'Tags': 'TEXT',
'Hash': 'TEXT',
'LastAccessed': 'BLOB',
'Bookmarks': 'BLOB',
'CoverImage': 'BLOB',
'Addition': 'TEXT'}
self.directories_table_columns = {
'id': 'INTEGER PRIMARY KEY',
'Path': 'TEXT',
'Name': 'TEXT',
'Tags': 'TEXT',
'CheckState': 'INTEGER'}
if os.path.exists(self.database_path):
self.check_database()
else:
self.create_database()
def create_database(self):
# TODO
# Add separate columns for:
# addition mode
self.database.execute(
"CREATE TABLE books \
(id INTEGER PRIMARY KEY, Title TEXT, Author TEXT, Year INTEGER, DateAdded BLOB, \
Path TEXT, Position BLOB, ISBN TEXT, Tags TEXT, Hash TEXT, LastAccessed BLOB,\
Bookmarks BLOB, CoverImage BLOB)")
self.database = sqlite3.connect(self.database_path)
column_string = ', '.join(
[i[0] + ' ' + i[1] for i in self.books_table_columns.items()])
self.database.execute(f"CREATE TABLE books ({column_string})")
# CheckState is the standard QtCore.Qt.Checked / Unchecked
self.database.execute(
"CREATE TABLE directories (id INTEGER PRIMARY KEY, Path TEXT, \
Name TEXT, Tags TEXT, CheckState INTEGER)")
column_string = ', '.join(
[i[0] + ' ' + i[1] for i in self.directories_table_columns.items()])
self.database.execute(f"CREATE TABLE directories ({column_string})")
self.database.commit()
self.database.close()
def check_database(self):
self.database = sqlite3.connect(self.database_path)
database_return = self.database.execute("PRAGMA table_info(books)").fetchall()
database_columns = [i[1] for i in database_return]
# This allows for addition of a column without having to reform the database
commit_required = False
for i in self.books_table_columns.items():
if i[0] not in database_columns:
commit_required = True
print(f'Database: Adding column {i[0]}')
sql_command = f"ALTER TABLE books ADD COLUMN {i[0]} {i[1]}"
self.database.execute(sql_command)
if commit_required:
self.database.commit()
self.database.close()
class DatabaseFunctions:
def __init__(self, location_prefix):
@@ -55,10 +96,6 @@ class DatabaseFunctions:
self.database = sqlite3.connect(database_path)
def set_library_paths(self, data_iterable):
# TODO
# INSERT OR REPLACE is not working
# So this is the old fashion kitchen sink approach
self.database.execute("DELETE FROM directories")
for i in data_iterable:
@@ -67,10 +104,13 @@ class DatabaseFunctions:
tags = i[2]
is_checked = i[3]
if not os.path.exists(path):
continue # Remove invalid paths from the database
sql_command = (
"INSERT OR REPLACE INTO directories (ID, Path, Name, Tags, CheckState)\
VALUES ((SELECT ID FROM directories WHERE Path = ?), ?, ?, ?, ?)")
self.database.execute(sql_command, [path, path, name, tags, is_checked])
"INSERT INTO directories (Path, Name, Tags, CheckState)\
VALUES (?, ?, ?, ?)")
self.database.execute(sql_command, [path, name, tags, is_checked])
self.database.commit()
self.database.close()
@@ -95,6 +135,7 @@ class DatabaseFunctions:
path = i[1]['path']
cover = i[1]['cover_image']
isbn = i[1]['isbn']
addition_mode = i[1]['addition_mode']
tags = i[1]['tags']
if tags:
# Is a list. Needs to be a string
@@ -105,8 +146,9 @@ class DatabaseFunctions:
sql_command_add = (
"INSERT OR REPLACE INTO \
books (Title, Author, Year, DateAdded, Path, ISBN, Tags, Hash, CoverImage) \
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
books (Title, Author, Year, DateAdded, Path, \
ISBN, Tags, Hash, CoverImage, Addition) \
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
cover_insert = None
if cover:
@@ -115,7 +157,8 @@ class DatabaseFunctions:
self.database.execute(
sql_command_add,
[title, author, year, current_datetime_bin,
path, isbn, tags, book_hash, cover_insert])
path, isbn, tags, book_hash, cover_insert,
addition_mode])
self.database.commit()
self.database.close()
@@ -208,9 +251,10 @@ class DatabaseFunctions:
# target_data is an iterable
if column_name == '*':
self.database.execute('DELETE FROM books')
self.database.execute(
"DELETE FROM books WHERE NOT Addition = 'manual'")
else:
sql_command = f'DELETE FROM books WHERE {column_name} = ?'
sql_command = f"DELETE FROM books WHERE {column_name} = ?"
for i in target_data:
self.database.execute(sql_command, (i,))

View File

@@ -41,7 +41,8 @@ class Library:
books = database.DatabaseFunctions(
self.parent.database_path).fetch_data(
('Title', 'Author', 'Year', 'DateAdded', 'Path',
'Position', 'ISBN', 'Tags', 'Hash', 'LastAccessed'),
'Position', 'ISBN', 'Tags', 'Hash', 'LastAccessed',
'Addition'),
'books',
{'Title': ''},
'LIKE')
@@ -64,7 +65,7 @@ class Library:
books.append([
i[1]['title'], i[1]['author'], i[1]['year'], current_qdatetime,
i[1]['path'], None, i[1]['isbn'], _tags, i[0], None])
i[1]['path'], None, i[1]['isbn'], _tags, i[0], None, i[1]['addition_mode']])
else:
return
@@ -76,6 +77,8 @@ class Library:
author = i[1]
year = i[2]
path = i[4]
addition_mode = i[10]
last_accessed = i[9]
if last_accessed and not isinstance(last_accessed, QtCore.QDateTime):
last_accessed = pickle.loads(last_accessed)
@@ -121,6 +124,7 @@ class Library:
'tags': tags,
'hash': i[8],
'last_accessed': last_accessed,
'addition_mode': addition_mode,
'file_exists': file_exists}
author_string = self._translate('Library', 'Author')
@@ -240,11 +244,20 @@ class Library:
{'Path': ''},
'LIKE')
if not db_library_directories: # Empty database / table
return
if db_library_directories: # Empty database / table
library_directories = {
i[0]: (i[1], i[2]) for i in db_library_directories}
library_directories = {
i[0]: (i[1], i[2]) for i in db_library_directories}
else:
db_library_directories = database.DatabaseFunctions(
self.parent.database_path).fetch_data(
('Path',),
'books', # This checks the directories table NOT the book one
{'Path': ''},
'LIKE')
library_directories = {
i[0]: (None, None) for i in db_library_directories}
def get_tags(all_metadata):
path = os.path.dirname(all_metadata['path'])
@@ -264,6 +277,8 @@ class Library:
return directory_name, directory_tags
# A file is assigned a 'manually added' tag in case it isn't
# in any designated library directory
added_string = self._translate('Library', 'manually added')
return added_string.lower(), None
@@ -281,23 +296,22 @@ class Library:
# All files in unselected directories will have to be removed
# from both of the models
# They will also have to be deleted from the library
valid_paths = set(valid_paths)
# Get all paths
all_paths = set()
for i in range(self.view_model.rowCount()):
item = self.view_model.item(i, 0)
item_metadata = item.data(QtCore.Qt.UserRole + 3)
book_path = item_metadata['path']
all_paths.add(book_path)
invalid_paths = all_paths - valid_paths
# valid_paths = set(valid_paths)
invalid_paths = []
deletable_persistent_indexes = []
for i in range(self.view_model.rowCount()):
item = self.view_model.item(i)
path = item.data(QtCore.Qt.UserRole + 3)['path']
if path in invalid_paths:
item_metadata = item.data(QtCore.Qt.UserRole + 3)
book_path = item_metadata['path']
addition_mode = item_metadata['addition_mode']
if (book_path not in valid_paths and
(addition_mode != 'manual' or addition_mode is None)):
invalid_paths.append(book_path)
deletable_persistent_indexes.append(
QtCore.QPersistentModelIndex(item.index()))

View File

@@ -174,9 +174,6 @@ class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog):
self.treeView.hideColumn(i)
def start_library_scan(self):
# TODO
# return in case the treeView is not edited
self.hide()
data_pairs = []
@@ -195,15 +192,13 @@ class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog):
except AttributeError:
pass
self.parent.lib_ref.view_model.clear()
self.parent.lib_ref.table_rows = []
# TODO
# Change this to no longer include files added manually
database.DatabaseFunctions(
self.database_path).delete_from_database('*', '*')
self.parent.lib_ref.generate_model('build')
self.parent.lib_ref.generate_proxymodels()
self.parent.generate_library_filter_menu()
return
# Update the main window library filter menu
@@ -237,7 +232,7 @@ class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog):
# We now create a new thread to put those files into the database
self.thread = BackGroundBookAddition(
self.thread.valid_files, self.database_path, True, self.parent)
self.thread.valid_files, self.database_path, 'automatic', self.parent)
self.thread.finished.connect(self.parent.move_on)
self.thread.start()

View File

@@ -87,7 +87,8 @@ class BookSorter:
self.file_list = [i for i in file_list if os.path.exists(i)]
self.statistics = [0, (len(file_list))]
self.hashes_and_paths = {}
self.mode = mode
self.work_mode = mode[0]
self.addition_mode = mode[1]
self.database_path = database_path
self.auto_tags = auto_tags
self.temp_dir = temp_dir
@@ -98,7 +99,7 @@ class BookSorter:
self.queue = Manager().Queue()
self.processed_books = []
if self.mode == 'addition':
if self.work_mode == 'addition':
progress_object_generator()
def database_hashes(self):
@@ -153,7 +154,7 @@ class BookSorter:
# Do not allow addition in case the file
# is already in the database and it remains at its original path
if self.mode == 'addition' and file_md5 in self.hashes_and_paths:
if self.work_mode == 'addition' and file_md5 in self.hashes_and_paths:
if (self.hashes_and_paths[file_md5] == filename
or os.path.exists(self.hashes_and_paths[file_md5])):
@@ -181,7 +182,7 @@ class BookSorter:
'path': filename}
# Different modes require different values
if self.mode == 'addition':
if self.work_mode == 'addition':
# Reduce the size of the incoming image
# if one is found
title = book_ref.get_title()
@@ -205,8 +206,9 @@ class BookSorter:
cover_image = None
this_book[file_md5]['cover_image'] = cover_image
this_book[file_md5]['addition_mode'] = self.addition_mode
if self.mode == 'reading':
if self.work_mode == 'reading':
all_content = book_ref.get_contents()
# get_contents() returns a tuple. Index 1 is a collection of

View File

@@ -44,29 +44,31 @@ class BackGroundTabUpdate(QtCore.QThread):
class BackGroundBookAddition(QtCore.QThread):
def __init__(self, file_list, database_path, prune_required, parent=None):
def __init__(self, file_list, database_path, addition_mode, parent=None):
super(BackGroundBookAddition, self).__init__(parent)
self.file_list = file_list
self.parent = parent
self.database_path = database_path
self.prune_required = prune_required
self.addition_mode = addition_mode
self.prune_required = True
if self.addition_mode == 'manual':
self.prune_required = False
def run(self):
books = sorter.BookSorter(
self.file_list,
'addition',
('addition', self.addition_mode),
self.database_path,
self.parent.settings['auto_tags'],
self.parent.temp_dir.path())
parsed_books = books.initiate_threads()
if not parsed_books:
return
self.parent.lib_ref.generate_model('addition', parsed_books, False)
database.DatabaseFunctions(self.database_path).add_to_database(parsed_books)
if self.prune_required:
self.parent.lib_ref.prune_models(self.file_list)
database.DatabaseFunctions(self.database_path).add_to_database(parsed_books)
class BackGroundBookDeletion(QtCore.QThread):