Update readme
Begin logging Account for fb2 books without covers
This commit is contained in:
21
README.md
21
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)
|
||||
|
@@ -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()
|
||||
|
@@ -14,10 +14,14 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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):
|
||||
|
@@ -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
|
||||
|
@@ -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()
|
||||
|
@@ -15,14 +15,20 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
@@ -14,9 +14,14 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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):
|
||||
|
@@ -14,12 +14,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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):
|
||||
|
@@ -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)):
|
||||
|
@@ -14,12 +14,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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):
|
||||
|
@@ -14,11 +14,14 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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):
|
||||
|
@@ -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):
|
||||
|
@@ -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):
|
||||
|
@@ -15,9 +15,12 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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):
|
||||
|
@@ -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):
|
||||
|
@@ -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):
|
||||
|
@@ -15,11 +15,14 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
@@ -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):
|
||||
|
@@ -10,5 +10,6 @@
|
||||
<p>Author: BasioMeusPuga <a href="mailto:disgruntled.mob@gmail.com">disgruntled.mob@gmail.com</a></p>
|
||||
<p>Page: <a href="https://github.com/BasioMeusPuga/Lector">https://github.com/BasioMeusPuga/Lector</a></p>
|
||||
<p>License: GPLv3 <a href="https://www.gnu.org/licenses/gpl-3.0.en.html">https://www.gnu.org/licenses/gpl-3.0.en.html</a></p>
|
||||
<p>Donate (Bitcoin): 17jaxj26vFJNqQ2hEVerbBV5fpTusfqFro</p>
|
||||
<p> </p></body>
|
||||
</html>
|
||||
|
@@ -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):
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
||||
|
@@ -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):
|
||||
|
@@ -14,8 +14,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BookToolBar(QtWidgets.QToolBar):
|
||||
def __init__(self, parent=None):
|
||||
|
@@ -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):
|
||||
|
Reference in New Issue
Block a user