Пример #1
0
    def prune_models(self, valid_paths):
        # To be executed when the library is updated by folder
        # 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

        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:
                deletable_persistent_indexes.append(
                    QtCore.QPersistentModelIndex(item.index()))

        if deletable_persistent_indexes:
            for i in deletable_persistent_indexes:
                self.view_model.removeRow(i.row())

        # Remove invalid paths from the database as well
        database.DatabaseFunctions(
            self.parent.database_path).delete_from_database(
                'Path', invalid_paths)
Пример #2
0
    def database_hashes(self):
        all_hashes_and_paths = database.DatabaseFunctions(
            self.database_path).fetch_data(('Hash', 'Path'), 'books',
                                           {'Hash': ''}, 'LIKE')

        if all_hashes_and_paths:
            # self.hashes = [i[0] for i in all_hashes]
            self.hashes_and_paths = {i[0]: i[1] for i in all_hashes_and_paths}
Пример #3
0
    def start_library_scan(self):
        # TODO
        # return in case the treeView is not edited

        self.hide()

        data_pairs = []
        for i in self.filesystem_model.tag_data.items():
            data_pairs.append(
                [i[0], i[1]['name'], i[1]['tags'], i[1]['check_state']])

        database.DatabaseFunctions(
            self.database_path).set_library_paths(data_pairs)

        if not data_pairs:
            try:
                if self.sender().objectName() == 'reloadLibrary':
                    self.show()
            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('*', '*')

            return

        # Update the main window library filter menu
        self.parent.generate_library_filter_menu(data_pairs)
        self.parent.set_library_filter()

        # Disallow rechecking until the first check completes
        self.okButton.setEnabled(False)
        self.parent.reloadLibrary.setEnabled(False)
        self.okButton.setToolTip('Library scan in progress...')

        # Traverse directories looking for files
        self.parent.statusMessage.setText('Checking library folders')
        self.thread = BackGroundBookSearch(data_pairs, self)
        self.thread.finished.connect(self.finished_iterating)
        self.thread.start()
Пример #4
0
    def cull_covers(self, event=None):
        blank_pixmap = QtGui.QPixmap()
        blank_pixmap.load(
            ':/images/blank.png')  # Keep this. Removing it causes the
        # listView to go blank on a resize

        all_indexes = set()
        for i in range(self.lib_ref.item_proxy_model.rowCount()):
            all_indexes.add(self.lib_ref.item_proxy_model.index(i, 0))

        y_range = list(range(0, self.listView.viewport().height(), 100))
        y_range.extend((-20, self.listView.viewport().height() + 20))
        x_range = range(0, self.listView.viewport().width(), 80)

        visible_indexes = set()
        for i in y_range:
            for j in x_range:
                this_index = self.listView.indexAt(QtCore.QPoint(j, i))
                visible_indexes.add(this_index)

        invisible_indexes = all_indexes - visible_indexes
        for i in invisible_indexes:
            model_index = self.lib_ref.item_proxy_model.mapToSource(i)
            this_item = self.lib_ref.view_model.item(model_index.row())

            if this_item:
                this_item.setIcon(QtGui.QIcon(blank_pixmap))
                this_item.setData(False, QtCore.Qt.UserRole + 8)

        hash_index_dict = {}
        hash_list = []
        for i in visible_indexes:
            model_index = self.lib_ref.item_proxy_model.mapToSource(i)

            book_hash = self.lib_ref.view_model.data(model_index,
                                                     QtCore.Qt.UserRole + 6)
            cover_displayed = self.lib_ref.view_model.data(
                model_index, QtCore.Qt.UserRole + 8)

            if book_hash and not cover_displayed:
                hash_list.append(book_hash)
                hash_index_dict[book_hash] = model_index

        all_covers = database.DatabaseFunctions(
            self.database_path).fetch_covers_only(hash_list)

        for i in all_covers:
            book_hash = i[0]
            cover = i[1]
            model_index = hash_index_dict[book_hash]

            book_item = self.lib_ref.view_model.item(model_index.row())
            self.cover_loader(book_item, cover)
Пример #5
0
    def database_entry_for_book(self, file_hash):
        database_return = database.DatabaseFunctions(
            self.database_path).fetch_data(('Position', 'Bookmarks'), 'books',
                                           {'Hash': file_hash}, 'EQUALS')[0]

        book_data = []
        for i in database_return:
            # All of these values are pickled and stored
            if i:
                book_data.append(pickle.loads(i))
            else:
                book_data.append(None)
        return book_data
Пример #6
0
    def generate_library_tags(self):
        db_library_directories = database.DatabaseFunctions(
            self.parent.database_path
        ).fetch_data(
            ('Path', 'Name', 'Tags'),
            'directories',  # This checks the directories table NOT the book one
            {'Path': ''},
            'LIKE')

        if not db_library_directories:  # Empty database / table
            return

        library_directories = {
            i[0]: (i[1], i[2])
            for i in db_library_directories
        }

        def get_tags(all_metadata):
            path = os.path.dirname(all_metadata['path'])
            path_ref = pathlib.Path(path)

            for i in library_directories:
                if i == path or pathlib.Path(i) in path_ref.parents:
                    directory_name = library_directories[i][0]
                    if directory_name:
                        directory_name = directory_name.lower()
                    else:
                        directory_name = path.rsplit('/')[-1].lower()

                    directory_tags = library_directories[i][1]
                    if directory_tags:
                        directory_tags = directory_tags.lower()

                    return directory_name, directory_tags

            return 'manually added', None

        # Generate tags for the QStandardItemModel
        for i in range(self.view_model.rowCount()):
            this_item = self.view_model.item(i, 0)
            all_metadata = this_item.data(QtCore.Qt.UserRole + 3)
            directory_name, directory_tags = get_tags(all_metadata)

            this_item.setData(directory_name, QtCore.Qt.UserRole + 10)
            this_item.setData(directory_tags, QtCore.Qt.UserRole + 11)
Пример #7
0
    def load_all_covers(self):
        all_covers_db = database.DatabaseFunctions(
            self.database_path).fetch_data((
                'Hash',
                'CoverImage',
            ), 'books', {'Hash': ''}, 'LIKE')

        all_covers = {i[0]: i[1] for i in all_covers_db}

        for i in range(self.lib_ref.view_model.rowCount()):
            this_item = self.lib_ref.view_model.item(i, 0)

            is_cover_already_displayed = this_item.data(QtCore.Qt.UserRole + 8)
            if is_cover_already_displayed:
                continue

            book_hash = this_item.data(QtCore.Qt.UserRole + 6)
            cover = all_covers[book_hash]
            self.cover_loader(this_item, cover)
Пример #8
0
    def open_books_at_startup(self):
        # Last open books and command line books aren't being opened together
        # so that command line books are processed last and therefore retain focus

        # Open last... open books.
        # Then set the value to None for the next run
        if self.settings['last_open_books']:
            files_to_open = {i: None for i in self.settings['last_open_books']}
            self.open_files(files_to_open)
        else:
            self.settings['last_open_tab'] = None

        # Open input files if specified
        cl_parser = QtCore.QCommandLineParser()
        cl_parser.process(QtWidgets.qApp)
        my_args = cl_parser.positionalArguments()
        if my_args:
            file_list = [
                QtCore.QFileInfo(i).absoluteFilePath() for i in my_args
            ]
            books = sorter.BookSorter(file_list, 'addition',
                                      self.database_path,
                                      self.settings['auto_tags'],
                                      self.temp_dir.path())

            parsed_books = books.initiate_threads()
            database.DatabaseFunctions(
                self.database_path).add_to_database(parsed_books)
            self.lib_ref.generate_model('addition', parsed_books, True)

            file_dict = {
                QtCore.QFileInfo(i).absoluteFilePath(): None
                for i in my_args
            }
            self.open_files(file_dict)

            self.move_on()
Пример #9
0
    def ok_pressed(self, event):
        book_item = self.parent.lib_ref.view_model.item(self.book_index.row())

        title = self.titleLine.text()
        author = self.authorLine.text()
        tags = self.tagsLine.text()

        try:
            year = int(self.yearLine.text())
        except ValueError:
            year = self.book_year

        tooltip_string = title + '\nAuthor: ' + author + '\nYear: ' + str(year)

        book_item.setData(title, QtCore.Qt.UserRole)
        book_item.setData(author, QtCore.Qt.UserRole + 1)
        book_item.setData(year, QtCore.Qt.UserRole + 2)
        book_item.setData(tags, QtCore.Qt.UserRole + 4)
        book_item.setToolTip(tooltip_string)

        book_hash = book_item.data(QtCore.Qt.UserRole + 6)
        database_dict = {
            'Title': title,
            'Author': author,
            'Year': year,
            'Tags': tags
        }

        if self.cover_for_database:
            database_dict['CoverImage'] = self.cover_for_database
            self.parent.cover_loader(book_item, self.cover_for_database)

        self.parent.lib_ref.update_proxymodels()
        self.hide()

        database.DatabaseFunctions(self.database_path).modify_metadata(
            database_dict, book_hash)
Пример #10
0
    def generate_model(self, mode, parsed_books=None, is_database_ready=True):
        if mode == 'build':
            self.view_model = QtGui.QStandardItemModel()
            self.view_model.setColumnCount(10)

            books = database.DatabaseFunctions(
                self.parent.database_path).fetch_data(
                    ('Title', 'Author', 'Year', 'DateAdded', 'Path',
                     'Position', 'ISBN', 'Tags', 'Hash', 'LastAccessed'),
                    'books', {'Title': ''}, 'LIKE')

            if not books:
                print('Database returned nothing')
                return

        elif mode == 'addition':
            # Assumes self.view_model already exists and may be extended
            # Because any additional books have already been added to the
            # database using background threads

            books = []
            current_qdatetime = QtCore.QDateTime().currentDateTime()
            for i in parsed_books.items():
                _tags = i[1]['tags']
                if _tags:
                    _tags = ', '.join([j for j in _tags if j])

                books.append([
                    i[1]['title'], i[1]['author'], i[1]['year'],
                    current_qdatetime, i[1]['path'], None, i[1]['isbn'], _tags,
                    i[0], None
                ])

        else:
            return

        for i in books:
            # The database query returns (or the extension data is)
            # an iterable with the following indices:
            title = i[0]
            author = i[1]
            year = i[2]
            path = i[4]
            last_accessed = i[9]

            tags = i[7]
            if isinstance(tags,
                          list):  # When files are added for the first time
                if tags:
                    tags = ', '.join(str(this_tag) for this_tag in tags)
                else:
                    tags = None

            try:
                date_added = pickle.loads(i[3])
            except TypeError:  # Because of datetime.datetime.now() above
                date_added = i[3]

            position_perc = None
            position = i[5]
            if position:
                position = pickle.loads(position)
                if position['is_read']:
                    position_perc = 100
                else:
                    try:
                        position_perc = (position['current_chapter'] * 100 /
                                         position['total_chapters'])
                    except KeyError:
                        position_perc = None

            file_exists = os.path.exists(path)

            all_metadata = {
                'title': title,
                'author': author,
                'year': year,
                'date_added': date_added,
                'path': path,
                'position': position,
                'isbn': i[6],
                'tags': tags,
                'hash': i[8],
                'last_accessed': last_accessed,
                'file_exists': file_exists
            }

            tooltip_string = title + '\nAuthor: ' + author + '\nYear: ' + str(
                year)

            # Additional data can be set using an incrementing
            # QtCore.Qt.UserRole
            # QtCore.Qt.DisplayRole is the same as item.setText()
            # The model is a single row and has no columns

            # No covers are set at this time
            # That is to be achieved by way of the culling function
            img_pixmap = QtGui.QPixmap()
            img_pixmap.load(':/images/blank.png')
            img_pixmap = img_pixmap.scaled(420, 600,
                                           QtCore.Qt.IgnoreAspectRatio)
            item = QtGui.QStandardItem()
            item.setToolTip(tooltip_string)

            # Just keep the following order. It's way too much trouble otherwise
            item.setData(title, QtCore.Qt.UserRole)
            item.setData(author, QtCore.Qt.UserRole + 1)
            item.setData(year, QtCore.Qt.UserRole + 2)
            item.setData(all_metadata, QtCore.Qt.UserRole + 3)
            item.setData(tags, QtCore.Qt.UserRole + 4)
            item.setData(file_exists, QtCore.Qt.UserRole + 5)
            item.setData(i[8], QtCore.Qt.UserRole + 6)  # File hash
            item.setData(position_perc, QtCore.Qt.UserRole + 7)
            item.setData(False, QtCore.Qt.UserRole +
                         8)  # Is the cover being displayed?
            item.setData(date_added, QtCore.Qt.UserRole + 9)
            item.setData(last_accessed, QtCore.Qt.UserRole + 12)
            item.setIcon(QtGui.QIcon(img_pixmap))

            self.view_model.appendRow(item)

        # The is_database_ready boolean is required when a new thread sends
        # books here for model generation.
        if not self.parent.settings['perform_culling'] and is_database_ready:
            self.parent.load_all_covers()
Пример #11
0
 def database_care(self):
     database.DatabaseFunctions(self.database_path).vacuum_database()
     QtWidgets.qApp.exit()
Пример #12
0
    def generate_library_context_menu(self, position):
        index = self.sender().indexAt(position)
        if not index.isValid():
            return

        # It's worth remembering that these are indexes of the view_model
        # and NOT of the proxy models
        selected_indexes = self.get_selection(self.sender())

        context_menu = QtWidgets.QMenu()

        openAction = context_menu.addAction(
            QtGui.QIcon.fromTheme('view-readermode'), 'Start reading')

        editAction = None
        if len(selected_indexes) == 1:
            editAction = context_menu.addAction(
                QtGui.QIcon.fromTheme('edit-rename'), 'Edit')

        deleteAction = context_menu.addAction(
            QtGui.QIcon.fromTheme('trash-empty'), 'Delete')
        readAction = context_menu.addAction(
            QtGui.QIcon.fromTheme('vcs-normal'), 'Mark read')
        unreadAction = context_menu.addAction(
            QtGui.QIcon.fromTheme('emblem-unavailable'), 'Mark unread')

        action = context_menu.exec_(self.sender().mapToGlobal(position))

        if action == openAction:
            books_to_open = {}
            for i in selected_indexes:
                metadata = self.lib_ref.view_model.data(
                    i, QtCore.Qt.UserRole + 3)
                books_to_open[metadata['path']] = metadata['hash']

            self.open_files(books_to_open)

        if action == editAction:
            edit_book = selected_indexes[0]
            metadata = self.lib_ref.view_model.data(edit_book,
                                                    QtCore.Qt.UserRole + 3)
            is_cover_loaded = self.lib_ref.view_model.data(
                edit_book, QtCore.Qt.UserRole + 8)

            # Loads a cover in case culling is enabled and the table view is visible
            if not is_cover_loaded:
                book_hash = self.lib_ref.view_model.data(
                    edit_book, QtCore.Qt.UserRole + 6)
                book_item = self.lib_ref.view_model.item(edit_book.row())
                book_cover = database.DatabaseFunctions(
                    self.database_path).fetch_covers_only([book_hash])[0][1]
                self.cover_loader(book_item, book_cover)

            cover = self.lib_ref.view_model.item(edit_book.row()).icon()
            title = metadata['title']
            author = metadata['author']
            year = str(metadata['year'])
            tags = metadata['tags']

            self.metadataDialog.load_book(cover, title, author, year, tags,
                                          edit_book)

            self.metadataDialog.show()

        if action == deleteAction:
            self.delete_books(selected_indexes)

        if action == readAction or action == unreadAction:
            for i in selected_indexes:
                metadata = self.lib_ref.view_model.data(
                    i, QtCore.Qt.UserRole + 3)
                book_hash = self.lib_ref.view_model.data(
                    i, QtCore.Qt.UserRole + 6)
                position = metadata['position']

                if position:
                    if action == readAction:
                        position['is_read'] = True
                        position['scroll_value'] = 1
                    elif action == unreadAction:
                        position['is_read'] = False
                        position['current_chapter'] = 1
                        position['scroll_value'] = 0
                else:
                    position = {}
                    if action == readAction:
                        position['is_read'] = True

                metadata['position'] = position

                position_perc = None
                last_accessed_time = None
                if action == readAction:
                    last_accessed_time = QtCore.QDateTime().currentDateTime()
                    position_perc = 100

                self.lib_ref.view_model.setData(i, metadata,
                                                QtCore.Qt.UserRole + 3)
                self.lib_ref.view_model.setData(i, position_perc,
                                                QtCore.Qt.UserRole + 7)
                self.lib_ref.view_model.setData(i, last_accessed_time,
                                                QtCore.Qt.UserRole + 12)
                self.lib_ref.update_proxymodels()

                database_dict = {
                    'Position': position,
                    'LastAccessed': last_accessed_time
                }

                database.DatabaseFunctions(self.database_path).modify_metadata(
                    database_dict, book_hash)
Пример #13
0
    def generate_tree(self):
        # Fetch all directories in the database
        paths = database.DatabaseFunctions(self.database_path).fetch_data(
            ('Path', 'Name', 'Tags', 'CheckState'), 'directories',
            {'Path': ''}, 'LIKE')

        self.parent.generate_library_filter_menu(paths)
        directory_data = {}
        if not paths:
            print('Database returned no paths for settings...')
        else:
            # Convert to the dictionary format that is
            # to be fed into the QFileSystemModel
            for i in paths:
                directory_data[i[0]] = {
                    'name': i[1],
                    'tags': i[2],
                    'check_state': i[3]
                }

        self.filesystem_model = MostExcellentFileSystemModel(directory_data)
        self.filesystem_model.setFilter(QtCore.QDir.NoDotAndDotDot
                                        | QtCore.QDir.Dirs)
        self.treeView.setModel(self.filesystem_model)

        # TODO
        # This here might break on them pestilent non unixy OSes
        # Check and see

        root_directory = QtCore.QDir().rootPath()
        self.treeView.setRootIndex(
            self.filesystem_model.setRootPath(root_directory))

        # Set the treeView and QFileSystemModel to its desired state
        selected_paths = [
            i for i in directory_data
            if directory_data[i]['check_state'] == QtCore.Qt.Checked
        ]
        expand_paths = set()
        for i in selected_paths:

            # Recursively grind down parent paths for expansion
            this_path = i
            while True:
                parent_path = os.path.dirname(this_path)
                if parent_path == this_path:
                    break
                expand_paths.add(parent_path)
                this_path = parent_path

        # Expand all the parent paths derived from the selected path
        if root_directory in expand_paths:
            expand_paths.remove(root_directory)

        for i in expand_paths:
            this_index = self.filesystem_model.index(i)
            self.treeView.expand(this_index)

        header_sizes = self.parent.settings['settings_dialog_headers']
        if header_sizes:
            for count, i in enumerate((0, 4)):
                self.treeView.setColumnWidth(i, int(header_sizes[count]))

        # TODO
        # Set a QSortFilterProxy model on top of the existing QFileSystem model
        # self.filesystem_proxy_model = FileSystemProxyModel()
        # self.filesystem_proxy_model.setSourceModel(self.filesystem_model)
        # self.treeView.setModel(self.filesystem_proxy_model)

        for i in range(1, 4):
            self.treeView.hideColumn(i)