diff --git a/README.md b/README.md index 6e08661..8dc635a 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,30 @@ Currently supports: * azw / azw3 / azw4 * cbr / cbz -Support for a bunch of other formats is coming. Please see the TODO for additional information. +## Donate to support development +Bitcoin: 17jaxj26vFJNqQ2hEVerbBV5fpTusfqFro ## Requirements +### Needed | Package | Version tested | | --- | --- | | Qt5 | 5.10.1 | | Python | 3.6 | | PyQt5 | 5.10.1 | +| python-lxml | 4.3.0 | | python-beautifulsoup4 | 4.6.0 | + +### Optional +| Package | Version tested | +| --- | --- | | poppler-qt5 | 0.61.1 | | python-poppler-qt5 | 0.24.2 | -poppler-qt5 and python-poppler-qt5 are optional. +## Support +When reporting issues: +* Include the log `~/.local/share/Lector/Lector.log` AND terminal output. +* If you're having trouble with a book while the rest of the application / other books work, please link to a copy of the book itself. +* If nothing is working, please make sure the requirements mentioned above are all installed, and are at least at the version mentioned. ## Installation ### Manual @@ -85,12 +96,6 @@ Please keep the translations short. There's only so much space for UI elements. ### Settings window ![alt tag](https://i.imgur.com/l6zJXaH.png) -## Reporting issues -When reporting issues: - -* If you're having trouble with a book while the rest of the application / other books work, please link to a copy of the book itself. -* If nothing is working, please make sure the requirements mentioned above are all installed, and are at least at the version mentioned. - ## Attributions * [KindleUnpack](https://github.com/kevinhendricks/KindleUnpack) * [rarfile](https://github.com/markokr/rarfile) diff --git a/lector/__main__.py b/lector/__main__.py index 36dae67..792bdd1 100755 --- a/lector/__main__.py +++ b/lector/__main__.py @@ -19,9 +19,12 @@ import os import gc import sys +import logging import hashlib import pathlib +from PyQt5 import QtWidgets, QtGui, QtCore + # This allows for the program to be launched from the # dir where it's been copied instead of needing to be # installed @@ -29,7 +32,20 @@ install_dir = os.path.realpath(__file__) install_dir = pathlib.Path(install_dir).parents[1] sys.path.append(str(install_dir)) -from PyQt5 import QtWidgets, QtGui, QtCore +# Initialize logging +# This is outside the UI declaration to allow sharing +# the object across modules without explicit redeclaration +logger_filename = os.path.join( + QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppDataLocation), + 'Lector', + 'Lector.log') +logging.basicConfig( + filename=logger_filename, + filemode='a', + format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', + datefmt='%H:%M:%S', + level=logging.ERROR) +logger = logging.getLogger('lector.main') from lector import database from lector import sorter @@ -240,7 +256,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): # Get list of available parsers self.available_parsers = '*.' + ' *.'.join(sorter.available_parsers) - print('Available parsers: ' + self.available_parsers) + logger.info('Available parsers: ' + self.available_parsers) # The Library tab gets no button self.tabWidget.tabBar().setTabButton( @@ -407,7 +423,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): if not file_paths: return - print('Attempting to open: ' + ', '.join(file_paths)) + logger.info( + 'Attempting to open: ' + ', '.join(file_paths)) contents = sorter.BookSorter( file_paths, @@ -420,13 +437,18 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): # Notification feedback in case all books return nothing if not contents: + logger.error('No parseable files found') return + successfully_opened = [] for i in contents: # New tabs are created here # Initial position adjustment is carried out by the tab itself file_data = contents[i] Tab(file_data, self) + successfully_opened.append(file_data['path']) + logger.info( + 'Successfully opened: ' + ', '.join(file_paths)) if self.settings['last_open_tab'] == 'library': self.tabWidget.setCurrentIndex(0) @@ -1041,7 +1063,8 @@ def main(): translations_out_string = '(Translations found)' if not translations_found: translations_out_string = '(No translations found)' - print(f'Locale: {QtCore.QLocale.system().name()}', translations_out_string) + log_string = f'Locale: {QtCore.QLocale.system().name()}' + translations_out_string + logger.info(log_string) form = MainUI() form.show() @@ -1050,4 +1073,5 @@ def main(): if __name__ == '__main__': + logger.info('Application start') main() diff --git a/lector/annotations.py b/lector/annotations.py index 05f8b04..d043b1b 100644 --- a/lector/annotations.py +++ b/lector/annotations.py @@ -14,10 +14,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import logging + from PyQt5 import QtWidgets, QtCore, QtGui from lector.resources import annotationswindow +logger = logging.getLogger(__name__) + class AnnotationsUI(QtWidgets.QDialog, annotationswindow.Ui_Dialog): def __init__(self, parent=None): diff --git a/lector/contentwidgets.py b/lector/contentwidgets.py index 30fb592..702f5e2 100644 --- a/lector/contentwidgets.py +++ b/lector/contentwidgets.py @@ -16,6 +16,7 @@ import os import zipfile +import logging import webbrowser try: @@ -29,6 +30,8 @@ from lector.rarfile import rarfile from lector.threaded import BackGroundCacheRefill from lector.annotations import AnnotationPlacement +logger = logging.getLogger(__name__) + class PliantQGraphicsView(QtWidgets.QGraphicsView): def __init__(self, filepath, main_window, parent=None): @@ -124,7 +127,7 @@ class PliantQGraphicsView(QtWidgets.QGraphicsView): return bigPixmap def generate_image_cache(current_page): - print('Building image cache') + logger.info('(Re)building image cache') current_page_index = all_pages.index(current_page) # Image caching for single and double page views @@ -491,7 +494,7 @@ class PliantQTextBrowser(QtWidgets.QTextBrowser): selected_index = self.parent.annotationListView.currentIndex() self.current_annotation = self.parent.annotationModel.data( selected_index, QtCore.Qt.UserRole) - print('Current annotation: ' + self.current_annotation['name']) + logger.info('Selected annotation: ' + + self.current_annotation['name']) def mouseReleaseEvent(self, event): # This takes care of annotation placement diff --git a/lector/database.py b/lector/database.py index d063283..4687b45 100644 --- a/lector/database.py +++ b/lector/database.py @@ -17,9 +17,12 @@ import os import pickle import sqlite3 +import logging from PyQt5 import QtCore +logger = logging.getLogger(__name__) + class DatabaseInit: def __init__(self, location_prefix): @@ -81,7 +84,8 @@ class DatabaseInit: 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]}"') + info_string = f'Database: Adding column "{i[0]}"' + logger.info(info_string) sql_command = f"ALTER TABLE books ADD COLUMN {i[0]} {i[1]}" self.database.execute(sql_command) @@ -208,7 +212,7 @@ class DatabaseFunctions: return None except (KeyError, sqlite3.OperationalError): - print('SQLite is in wretched rebellion @ data fetching handling') + logger.critical('SQLite is in wretched rebellion @ data fetching handling') def fetch_covers_only(self, hash_list): parameter_marks = ','.join(['?' for i in hash_list]) @@ -241,7 +245,7 @@ class DatabaseFunctions: self.database.execute( sql_command, update_data) except sqlite3.OperationalError: - print('SQLite is in wretched rebellion @ metadata handling') + logger.critical('SQLite is in wretched rebellion @ metadata handling') self.database.commit() self.database.close() diff --git a/lector/definitionsdialog.py b/lector/definitionsdialog.py index 11f7f29..9d6ebbc 100644 --- a/lector/definitionsdialog.py +++ b/lector/definitionsdialog.py @@ -15,14 +15,20 @@ # along with this program. If not, see . import json +import logging import urllib.request from PyQt5 import QtWidgets, QtCore, QtGui + +logger = logging.getLogger(__name__) + try: from PyQt5 import QtMultimedia multimedia_available = True except ImportError: - print('QtMultimedia not found. Sounds will not play.') + error_string = 'QtMultimedia not found. Sounds will not play.' + print(error_string) + logger.error(error_string) multimedia_available = False from lector.resources import definitions diff --git a/lector/delegates.py b/lector/delegates.py index 3a8db07..8414c61 100644 --- a/lector/delegates.py +++ b/lector/delegates.py @@ -14,9 +14,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import logging + from PyQt5 import QtWidgets, QtGui, QtCore + from lector.resources import pie_chart +logger = logging.getLogger(__name__) + class LibraryDelegate(QtWidgets.QStyledItemDelegate): def __init__(self, temp_dir, parent=None): diff --git a/lector/guifunctions.py b/lector/guifunctions.py index 1f89789..9f73beb 100644 --- a/lector/guifunctions.py +++ b/lector/guifunctions.py @@ -14,12 +14,16 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import logging + from PyQt5 import QtCore, QtGui, QtWidgets from lector import database from lector.settings import Settings from lector.resources import resources +logger = logging.getLogger(__name__) + class QImageFactory: def __init__(self, parent): diff --git a/lector/library.py b/lector/library.py index db597af..fbd8b26 100644 --- a/lector/library.py +++ b/lector/library.py @@ -16,6 +16,7 @@ import os import pickle +import logging import pathlib from PyQt5 import QtGui, QtCore @@ -23,6 +24,8 @@ from PyQt5 import QtGui, QtCore from lector import database from lector.models import TableProxyModel, ItemProxyModel +logger = logging.getLogger(__name__) + class Library: def __init__(self, parent): @@ -47,7 +50,7 @@ class Library: 'LIKE') if not books: - print('Database returned nothing') + logger.error('Database returned nothing') return elif mode == 'addition': @@ -320,7 +323,7 @@ class Library: addition_mode = item_metadata['addition_mode'] except KeyError: addition_mode = 'automatic' - print('Libary: Error setting addition mode for prune') + logger.error('Libary: Error setting addition mode for prune') if (book_path not in valid_paths and (addition_mode != 'manual' or addition_mode is None)): diff --git a/lector/metadatadialog.py b/lector/metadatadialog.py index c8b6b00..90fb8fc 100644 --- a/lector/metadatadialog.py +++ b/lector/metadatadialog.py @@ -14,12 +14,16 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import logging + from PyQt5 import QtWidgets, QtCore, QtGui from lector import database from lector.widgets import PliantQGraphicsScene from lector.resources import metadata +logger = logging.getLogger(__name__) + class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog): def __init__(self, parent): diff --git a/lector/models.py b/lector/models.py index 5a39c1f..536a4c9 100644 --- a/lector/models.py +++ b/lector/models.py @@ -14,11 +14,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import logging import pathlib from PyQt5 import QtCore, QtWidgets from lector.resources import pie_chart +logger = logging.getLogger(__name__) + class BookmarkProxyModel(QtCore.QSortFilterProxyModel): def __init__(self, parent=None): diff --git a/lector/parsers/comicbooks.py b/lector/parsers/comicbooks.py index feb1f33..6fb05df 100644 --- a/lector/parsers/comicbooks.py +++ b/lector/parsers/comicbooks.py @@ -19,10 +19,13 @@ import os import time +import logging import zipfile from lector.rarfile import rarfile +logger = logging.getLogger(__name__) + class ParseCOMIC: def __init__(self, filename, *args): diff --git a/lector/parsers/epub.py b/lector/parsers/epub.py index c9709d7..ec87a9c 100644 --- a/lector/parsers/epub.py +++ b/lector/parsers/epub.py @@ -16,9 +16,12 @@ import os import zipfile +import logging from lector.readers.read_epub import EPUB +logger = logging.getLogger(__name__) + class ParseEPUB: def __init__(self, filename, temp_dir, file_md5): diff --git a/lector/parsers/fb2.py b/lector/parsers/fb2.py index 68ab03a..32c54e1 100644 --- a/lector/parsers/fb2.py +++ b/lector/parsers/fb2.py @@ -15,9 +15,12 @@ # along with this program. If not, see . import os +import logging from lector.readers.read_fb2 import FB2 +logger = logging.getLogger(__name__) + class ParseFB2: def __init__(self, filename, temp_dir, file_md5): diff --git a/lector/parsers/mobi.py b/lector/parsers/mobi.py index 227bfcc..e0537a7 100644 --- a/lector/parsers/mobi.py +++ b/lector/parsers/mobi.py @@ -21,10 +21,13 @@ import os import sys import shutil import zipfile +import logging from lector.readers.read_epub import EPUB import lector.KindleUnpack.kindleunpack as KindleUnpack +logger = logging.getLogger(__name__) + class ParseMOBI: def __init__(self, filename, temp_dir, file_md5): diff --git a/lector/parsers/pdf.py b/lector/parsers/pdf.py index 607abc5..eb38067 100644 --- a/lector/parsers/pdf.py +++ b/lector/parsers/pdf.py @@ -20,12 +20,15 @@ import io import os +import logging from PyQt5 import QtCore from bs4 import BeautifulSoup import popplerqt5 +logger = logging.getLogger(__name__) + class ParsePDF: def __init__(self, filename, *args): diff --git a/lector/readers/read_epub.py b/lector/readers/read_epub.py index b759c44..ca5ad18 100644 --- a/lector/readers/read_epub.py +++ b/lector/readers/read_epub.py @@ -15,11 +15,14 @@ # along with this program. If not, see . import os +import logging import zipfile from urllib.parse import unquote from bs4 import BeautifulSoup +logger = logging.getLogger(__name__) + class EPUB: def __init__(self, filename): @@ -189,7 +192,7 @@ class EPUB: if biggest_image: self.book['cover'] = self.read_from_zip(biggest_image) else: - print('No cover found for: ' + self.filename) + logger.error('No cover found for: ' + self.filename) # Parse spine and arrange chapter paths acquired from the opf # according to the order IN THE SPINE diff --git a/lector/readers/read_fb2.py b/lector/readers/read_fb2.py index e8bffc3..e6f272e 100644 --- a/lector/readers/read_fb2.py +++ b/lector/readers/read_fb2.py @@ -17,9 +17,12 @@ import os import base64 import zipfile +import logging from bs4 import BeautifulSoup +logger = logging.getLogger(__name__) + class FB2: def __init__(self, filename): @@ -83,7 +86,9 @@ class FB2: for i in cover_image_data: if cover_image_name.endswith(i.get('id')): self.book['cover'] = base64.decodebytes(i.text.encode()) - except AttributeError: + except (AttributeError, TypeError): + # Catch TypeError in case no images exist in the book + logger.error('No cover found for: ' + self.filename) self.book['cover'] = None def parse_chapters(self, temp_dir): diff --git a/lector/resources/about.html b/lector/resources/about.html index fee1cf9..38d87ba 100644 --- a/lector/resources/about.html +++ b/lector/resources/about.html @@ -10,5 +10,6 @@

Author: BasioMeusPuga disgruntled.mob@gmail.com

Page: https://github.com/BasioMeusPuga/Lector

License: GPLv3 https://www.gnu.org/licenses/gpl-3.0.en.html

+

Donate (Bitcoin): 17jaxj26vFJNqQ2hEVerbBV5fpTusfqFro

 

diff --git a/lector/settings.py b/lector/settings.py index 3529bc7..3666637 100644 --- a/lector/settings.py +++ b/lector/settings.py @@ -17,10 +17,13 @@ # Keep in mind that all integer / boolean settings are returned as strings import os - +import logging from ast import literal_eval + from PyQt5 import QtCore, QtGui +logger = logging.getLogger(__name__) + class Settings: def __init__(self, parent): diff --git a/lector/settingsdialog.py b/lector/settingsdialog.py index 335d6f9..85e1d6c 100644 --- a/lector/settingsdialog.py +++ b/lector/settingsdialog.py @@ -19,6 +19,7 @@ import os import copy +import logging import pathlib from PyQt5 import QtWidgets, QtCore, QtGui @@ -30,6 +31,8 @@ from lector.threaded import BackGroundBookSearch, BackGroundBookAddition from lector.resources import settingswindow from lector.settings import Settings +logger = logging.getLogger(__name__) + class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog): def __init__(self, parent=None): @@ -189,7 +192,7 @@ class SettingsUI(QtWidgets.QDialog, settingswindow.Ui_Dialog): self.main_window.generate_library_filter_menu(paths) directory_data = {} if not paths: - print('Database: No paths for settings...') + logger.error('Database: No paths for settings...') else: # Convert to the dictionary format that is # to be fed into the QFileSystemModel diff --git a/lector/sorter.py b/lector/sorter.py index 09e572b..5cda05a 100644 --- a/lector/sorter.py +++ b/lector/sorter.py @@ -36,6 +36,7 @@ import os import sys import time import pickle +import logging import hashlib import threading @@ -53,6 +54,8 @@ from lector.parsers.mobi import ParseMOBI from lector.parsers.fb2 import ParseFB2 from lector.parsers.comicbooks import ParseCOMIC +logger = logging.getLogger(__name__) + sorter = { 'epub': ParseEPUB, 'mobi': ParseMOBI, @@ -70,7 +73,9 @@ try: from lector.parsers.pdf import ParsePDF sorter['pdf'] = ParsePDF except ImportError: - print('python-poppler-qt5 is not installed. Pdf files will not work.') + error_string = 'python-poppler-qt5 is not installed. Pdf files will not work.' + print(error_string) + logger.error(error_string) available_parsers = [i for i in sorter] progressbar = None # This is populated by __main__ @@ -194,7 +199,7 @@ class BookSorter: # None values are accounted for here is_valid = book_ref.read_book() if not is_valid: - print('Cannot parse: ' + filename) + logger.error('Cannot parse:' + filename) return if book_ref.book: @@ -311,7 +316,8 @@ class BookSorter: return_books[j] = i[j] del self.processed_books - print('Finished processing in', time.time() - start_time) + processing_time = str(time.time() - start_time) + logger.info('Finished processing in:' + processing_time) return return_books diff --git a/lector/threaded.py b/lector/threaded.py index 0b26e4b..f812898 100644 --- a/lector/threaded.py +++ b/lector/threaded.py @@ -16,6 +16,7 @@ import os import re +import logging import pathlib from multiprocessing.dummy import Pool @@ -24,6 +25,8 @@ from PyQt5 import QtCore, QtGui from lector import sorter from lector import database +logger = logging.getLogger(__name__) + class BackGroundTabUpdate(QtCore.QThread): def __init__(self, database_path, all_metadata, parent=None): diff --git a/lector/toolbars.py b/lector/toolbars.py index 376a88c..775ed47 100644 --- a/lector/toolbars.py +++ b/lector/toolbars.py @@ -14,8 +14,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import logging + from PyQt5 import QtWidgets, QtCore +logger = logging.getLogger(__name__) + class BookToolBar(QtWidgets.QToolBar): def __init__(self, parent=None): diff --git a/lector/widgets.py b/lector/widgets.py index 580ff20..5b240d3 100644 --- a/lector/widgets.py +++ b/lector/widgets.py @@ -20,6 +20,7 @@ import os import uuid +import logging from PyQt5 import QtWidgets, QtGui, QtCore @@ -28,6 +29,8 @@ from lector.sorter import resize_image from lector.threaded import BackGroundTextSearch from lector.contentwidgets import PliantQGraphicsView, PliantQTextBrowser +logger = logging.getLogger(__name__) + class Tab(QtWidgets.QWidget): def __init__(self, metadata, main_window, parent=None):