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

-## 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