From 190ee09e600940858160ac8689e7cbae97f3f755 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Tue, 26 Nov 2024 06:13:10 +0100 Subject: [PATCH 1/4] update Tests Per request db connection --- cps/__init__.py | 6 +- cps/admin.py | 5 +- cps/config_sql.py | 10 +- cps/db.py | 110 +- cps/tasks/convert.py | 264 +- cps/tasks/database.py | 10 +- cps/tasks/metadata_backup.py | 84 +- cps/tasks/thumbnail.py | 116 +- test/Calibre-Web TestSummary_Linux.html | 4994 +++++++++++++++++++---- 9 files changed, 4528 insertions(+), 1071 deletions(-) diff --git a/cps/__init__.py b/cps/__init__.py index 31614c2a..24267b1f 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -102,7 +102,7 @@ if wtf_present: else: csrf = None -calibre_db = db.CalibreDB() +calibre_db = db.CalibreDB(app) web_server = WebServer() @@ -146,9 +146,7 @@ def create_app(): lm.anonymous_user = ub.Anonymous lm.session_protection = 'strong' if config.config_session == 1 else "basic" - db.CalibreDB.update_config(config) - db.CalibreDB.setup_db(config.config_calibre_dir, cli_param.settings_path) - calibre_db.init_db() + db.CalibreDB.update_config(config, config.config_calibre_dir, cli_param.settings_path) updater_thread.init_updater(config, web_server) # Perform dry run of updater and exit afterward diff --git a/cps/admin.py b/cps/admin.py index 6076e86d..b56b5b5c 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -144,7 +144,6 @@ def shutdown(): show_text = {} if task in (0, 1): # valid commandos received # close all database connections - calibre_db.dispose() ub.dispose() if task == 0: @@ -1767,10 +1766,10 @@ def _db_configuration_update_helper(): config.config_allowed_column_value = "" config.config_read_column = 0 _config_string(to_save, "config_calibre_dir") - calibre_db.update_config(config) + calibre_db.update_config(config, config.config_calibre_dir, ub.app_DB_path) if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK): flash(_("DB is not Writeable"), category="warning") - calibre_db.update_config(config) + calibre_db.update_config(config, config.config_calibre_dir, ub.app_DB_path) config.save() return _db_configuration_result(None, gdrive_error) diff --git a/cps/config_sql.py b/cps/config_sql.py index 3607f0af..7e7d033d 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -405,11 +405,13 @@ class ConfigSQL(object): return self.config_calibre_split_dir if self.config_calibre_split_dir else self.config_calibre_dir def store_calibre_uuid(self, calibre_db, Library_table): + from . import app try: - calibre_uuid = calibre_db.session.query(Library_table).one_or_none() - if self.config_calibre_uuid != calibre_uuid.uuid: - self.config_calibre_uuid = calibre_uuid.uuid - self.save() + with app.app_context(): + calibre_uuid = calibre_db.session.query(Library_table).one_or_none() + if self.config_calibre_uuid != calibre_uuid.uuid: + self.config_calibre_uuid = calibre_uuid.uuid + self.save() except AttributeError: pass diff --git a/cps/db.py b/cps/db.py index c0a3fc97..47ea6a5c 100644 --- a/cps/db.py +++ b/cps/db.py @@ -23,7 +23,7 @@ import json from datetime import datetime, timezone from urllib.parse import quote import unidecode -from weakref import WeakSet +# from weakref import WeakSet from uuid import uuid4 from sqlite3 import OperationalError as sqliteOperationalError @@ -45,7 +45,7 @@ from sqlalchemy.ext.associationproxy import association_proxy from .cw_login import current_user from flask_babel import gettext as _ from flask_babel import get_locale -from flask import flash +from flask import flash, g, Flask from . import logger, ub, isoLanguages from .pagination import Pagination @@ -528,34 +528,25 @@ class AlchemyEncoder(json.JSONEncoder): class CalibreDB: - _init = False - engine = None config = None - session_factory = None - # This is a WeakSet so that references here don't keep other CalibreDB - # instances alive once they reach the end of their respective scopes - instances = WeakSet() + config_calibre_dir = None + app_db_path = None - def __init__(self, expire_on_commit=True, init=False): + def __init__(self, _app: Flask=None): # , expire_on_commit=True, init=False): """ Initialize a new CalibreDB session """ - self.session = None - if init: - self.init_db(expire_on_commit) + self.Session = None + #if init: + # self.init_db(expire_on_commit) + if _app is not None: + self.init_app(_app) - def init_db(self, expire_on_commit=True): - if self._init: - self.init_session(expire_on_commit) - - self.instances.add(self) - - def init_session(self, expire_on_commit=True): - self.session = self.session_factory() - self.session.expire_on_commit = expire_on_commit - self.create_functions(self.config) + def init_app(self, _app): + _app.teardown_appcontext(self.teardown) @classmethod def setup_db_cc_classes(cls, cc): + global cc_classes cc_ids = [] books_custom_column_links = {} for row in cc: @@ -623,8 +614,6 @@ class CalibreDB: secondary=books_custom_column_links[cc_id[0]], backref='books')) - return cc_classes - @classmethod def check_valid_db(cls, config_calibre_dir, app_db_path, config_calibre_uuid): if not config_calibre_dir: @@ -644,7 +633,6 @@ class CalibreDB: local_session = scoped_session(sessionmaker()) local_session.configure(bind=connection) database_uuid = local_session().query(Library_Id).one_or_none() - # local_session.dispose() check_engine.connect() db_change = config_calibre_uuid != database_uuid.uuid @@ -652,13 +640,30 @@ class CalibreDB: return False, False return True, db_change + def teardown(self, exception): + ctx = g.get("lib_sql") + if ctx: + ctx.close() + + @property + def session(self): + # connect or get active connection + if not g.get("lib_sql"): + g.lib_sql = self.connect() + return g.lib_sql + @classmethod - def update_config(cls, config): + def update_config(cls, config, config_calibre_dir, app_db_path): cls.config = config + cls.config_calibre_dir = config_calibre_dir + cls.app_db_path = app_db_path + + + def connect(self): + return self.setup_db(self.config_calibre_dir, self.app_db_path) @classmethod def setup_db(cls, config_calibre_dir, app_db_path): - cls.dispose() if not config_calibre_dir: cls.config.invalidate() @@ -670,17 +675,17 @@ class CalibreDB: return None try: - cls.engine = create_engine('sqlite://', + engine = create_engine('sqlite://', echo=False, isolation_level="SERIALIZABLE", connect_args={'check_same_thread': False}, poolclass=StaticPool) - with cls.engine.begin() as connection: + with engine.begin() as connection: connection.execute(text('PRAGMA cache_size = 10000;')) connection.execute(text("attach database '{}' as calibre;".format(dbpath))) connection.execute(text("attach database '{}' as app_settings;".format(app_db_path))) - conn = cls.engine.connect() + conn = engine.connect() # conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302 except Exception as ex: cls.config.invalidate(ex) @@ -696,13 +701,10 @@ class CalibreDB: log.error_or_exception(e) return None - cls.session_factory = scoped_session(sessionmaker(autocommit=False, - autoflush=True, - bind=cls.engine, future=True)) - for inst in cls.instances: - inst.init_session() + return scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine, future=True)) - cls._init = True def get_book(self, book_id): return self.session.query(Books).filter(Books.id == book_id).first() @@ -1066,41 +1068,9 @@ class CalibreDB: except sqliteOperationalError: pass - @classmethod - def dispose(cls): - # global session - - for inst in cls.instances: - old_session = inst.session - inst.session = None - if old_session: - try: - old_session.close() - except Exception: - pass - if old_session.bind: - try: - old_session.bind.dispose() - except Exception: - pass - - for attr in list(Books.__dict__.keys()): - if attr.startswith("custom_column_"): - setattr(Books, attr, None) - - for db_class in cc_classes.values(): - Base.metadata.remove(db_class.__table__) - cc_classes.clear() - - for table in reversed(Base.metadata.sorted_tables): - name = table.key - if name.startswith("custom_column_") or name.startswith("books_custom_column_"): - if table is not None: - Base.metadata.remove(table) - def reconnect_db(self, config, app_db_path): - self.dispose() - self.engine.dispose() + # self.dispose() + # self.engine.dispose() self.setup_db(config.config_calibre_dir, app_db_path) self.update_config(config) diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index 08eb63ce..9b39c6c2 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -28,7 +28,7 @@ from sqlalchemy.exc import SQLAlchemyError from flask_babel import lazy_gettext as N_ from cps.services.worker import CalibreTask -from cps import db +from cps import db, app from cps import logger, config from cps.subproc_wrapper import process_open from flask_babel import gettext as _ @@ -62,157 +62,159 @@ class TaskConvert(CalibreTask): def run(self, worker_thread): self.worker_thread = worker_thread if config.config_use_google_drive: - worker_db = db.CalibreDB(expire_on_commit=False, init=True) - cur_book = worker_db.get_book(self.book_id) - self.title = cur_book.title - data = worker_db.get_book_format(self.book_id, self.settings['old_book_format']) - df = gdriveutils.getFileFromEbooksFolder(cur_book.path, - data.name + "." + self.settings['old_book_format'].lower()) - df_cover = gdriveutils.getFileFromEbooksFolder(cur_book.path, "cover.jpg") - if df: - datafile_cover = None - datafile = os.path.join(config.get_book_path(), - cur_book.path, - data.name + "." + self.settings['old_book_format'].lower()) - if df_cover: - datafile_cover = os.path.join(config.get_book_path(), - cur_book.path, "cover.jpg") - if not os.path.exists(os.path.join(config.get_book_path(), cur_book.path)): - os.makedirs(os.path.join(config.get_book_path(), cur_book.path)) - df.GetContentFile(datafile) - if df_cover: - df_cover.GetContentFile(datafile_cover) - worker_db.session.close() - else: - # ToDo Include cover in error handling - error_message = _("%(format)s not found on Google Drive: %(fn)s", - format=self.settings['old_book_format'], - fn=data.name + "." + self.settings['old_book_format'].lower()) - worker_db.session.close() + with app.app_context(): + worker_db = db.CalibreDB(app) + cur_book = worker_db.get_book(self.book_id) + self.title = cur_book.title + data = worker_db.get_book_format(self.book_id, self.settings['old_book_format']) + df = gdriveutils.getFileFromEbooksFolder(cur_book.path, + data.name + "." + self.settings['old_book_format'].lower()) + df_cover = gdriveutils.getFileFromEbooksFolder(cur_book.path, "cover.jpg") + if df: + datafile_cover = None + datafile = os.path.join(config.get_book_path(), + cur_book.path, + data.name + "." + self.settings['old_book_format'].lower()) + if df_cover: + datafile_cover = os.path.join(config.get_book_path(), + cur_book.path, "cover.jpg") + if not os.path.exists(os.path.join(config.get_book_path(), cur_book.path)): + os.makedirs(os.path.join(config.get_book_path(), cur_book.path)) + df.GetContentFile(datafile) + if df_cover: + df_cover.GetContentFile(datafile_cover) + # worker_db.session.close() + else: + # ToDo Include cover in error handling + error_message = _("%(format)s not found on Google Drive: %(fn)s", + format=self.settings['old_book_format'], + fn=data.name + "." + self.settings['old_book_format'].lower()) + # worker_db.session.close() return self._handleError(error_message) - filename = self._convert_ebook_format() - if config.config_use_google_drive: - os.remove(self.file_path + '.' + self.settings['old_book_format'].lower()) - if df_cover: - os.remove(os.path.join(config.config_calibre_dir, cur_book.path, "cover.jpg")) - - if filename: + filename = self._convert_ebook_format() if config.config_use_google_drive: - # Upload files to gdrive - gdriveutils.updateGdriveCalibreFromLocal() - self._handleSuccess() - if self.ereader_mail: - # if we're sending to E-Reader after converting, create a one-off task and run it immediately - # todo: figure out how to incorporate this into the progress - try: - EmailText = N_(u"%(book)s send to E-Reader", book=escape(self.title)) - for email in self.ereader_mail.split(','): - email = strip_whitespaces(email) - worker_thread.add(self.user, TaskEmail(self.settings['subject'], - self.results["path"], - filename, - self.settings, - email, - EmailText, - self.settings['body'], - id=self.book_id, - internal=True) - ) - except Exception as ex: - return self._handleError(str(ex)) + os.remove(self.file_path + '.' + self.settings['old_book_format'].lower()) + if df_cover: + os.remove(os.path.join(config.config_calibre_dir, cur_book.path, "cover.jpg")) + + if filename: + if config.config_use_google_drive: + # Upload files to gdrive + gdriveutils.updateGdriveCalibreFromLocal() + self._handleSuccess() + if self.ereader_mail: + # if we're sending to E-Reader after converting, create a one-off task and run it immediately + # todo: figure out how to incorporate this into the progress + try: + EmailText = N_(u"%(book)s send to E-Reader", book=escape(self.title)) + for email in self.ereader_mail.split(','): + email = strip_whitespaces(email) + worker_thread.add(self.user, TaskEmail(self.settings['subject'], + self.results["path"], + filename, + self.settings, + email, + EmailText, + self.settings['body'], + id=self.book_id, + internal=True) + ) + except Exception as ex: + return self._handleError(str(ex)) def _convert_ebook_format(self): error_message = None - local_db = db.CalibreDB(expire_on_commit=False, init=True) - file_path = self.file_path - book_id = self.book_id - format_old_ext = '.' + self.settings['old_book_format'].lower() - format_new_ext = '.' + self.settings['new_book_format'].lower() + with app.app_context(): + local_db = db.CalibreDB(app) + file_path = self.file_path + book_id = self.book_id + format_old_ext = '.' + self.settings['old_book_format'].lower() + format_new_ext = '.' + self.settings['new_book_format'].lower() - # check to see if destination format already exists - or if book is in database - # if it does - mark the conversion task as complete and return a success - # this will allow to send to E-Reader workflow to continue to work - if os.path.isfile(file_path + format_new_ext) or\ - local_db.get_book_format(self.book_id, self.settings['new_book_format']): - log.info("Book id %d already converted to %s", book_id, format_new_ext) - cur_book = local_db.get_book(book_id) - self.title = cur_book.title - self.results['path'] = cur_book.path - self.results['title'] = self.title - new_format = local_db.session.query(db.Data).filter(db.Data.book == book_id)\ - .filter(db.Data.format == self.settings['new_book_format'].upper()).one_or_none() - if not new_format: - new_format = db.Data(name=os.path.basename(file_path), - book_format=self.settings['new_book_format'].upper(), - book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext)) - try: - local_db.session.merge(new_format) - local_db.session.commit() - except SQLAlchemyError as e: - local_db.session.rollback() - log.error("Database error: %s", e) - local_db.session.close() - self._handleError(N_("Oops! Database Error: %(error)s.", error=e)) - return - self._handleSuccess() - local_db.session.close() - return os.path.basename(file_path + format_new_ext) - else: - log.info("Book id %d - target format of %s does not exist. Moving forward with convert.", - book_id, - format_new_ext) - - if config.config_kepubifypath and format_old_ext == '.epub' and format_new_ext == '.kepub': - check, error_message = self._convert_kepubify(file_path, - format_old_ext, - format_new_ext) - else: - # check if calibre converter-executable is existing - if not os.path.exists(config.config_converterpath): - self._handleError(N_("Calibre ebook-convert %(tool)s not found", tool=config.config_converterpath)) - return - has_cover = local_db.get_book(book_id).has_cover - check, error_message = self._convert_calibre(file_path, format_old_ext, format_new_ext, has_cover) - - if check == 0: - cur_book = local_db.get_book(book_id) - if os.path.isfile(file_path + format_new_ext): - new_format = local_db.session.query(db.Data).filter(db.Data.book == book_id) \ + # check to see if destination format already exists - or if book is in database + # if it does - mark the conversion task as complete and return a success + # this will allow to send to E-Reader workflow to continue to work + if os.path.isfile(file_path + format_new_ext) or\ + local_db.get_book_format(self.book_id, self.settings['new_book_format']): + log.info("Book id %d already converted to %s", book_id, format_new_ext) + cur_book = local_db.get_book(book_id) + self.title = cur_book.title + self.results['path'] = cur_book.path + self.results['title'] = self.title + new_format = local_db.session.query(db.Data).filter(db.Data.book == book_id)\ .filter(db.Data.format == self.settings['new_book_format'].upper()).one_or_none() if not new_format: - new_format = db.Data(name=cur_book.data[0].name, + new_format = db.Data(name=os.path.basename(file_path), book_format=self.settings['new_book_format'].upper(), book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext)) try: local_db.session.merge(new_format) local_db.session.commit() - if self.settings['new_book_format'].upper() in ['KEPUB', 'EPUB', 'EPUB3']: - ub_session = init_db_thread() - remove_synced_book(book_id, True, ub_session) - ub_session.close() except SQLAlchemyError as e: local_db.session.rollback() log.error("Database error: %s", e) local_db.session.close() - self._handleError(error_message) + self._handleError(N_("Oops! Database Error: %(error)s.", error=e)) return - self.results['path'] = cur_book.path - self.title = cur_book.title - self.results['title'] = self.title - if not config.config_use_google_drive: self._handleSuccess() - return os.path.basename(file_path + format_new_ext) + local_db.session.close() + return os.path.basename(file_path + format_new_ext) else: - error_message = N_('%(format)s format not found on disk', format=format_new_ext.upper()) - local_db.session.close() - log.info("ebook converter failed with error while converting book") - if not error_message: - error_message = N_('Ebook converter failed with unknown error') - else: - log.error(error_message) - self._handleError(error_message) - return + log.info("Book id %d - target format of %s does not exist. Moving forward with convert.", + book_id, + format_new_ext) + + if config.config_kepubifypath and format_old_ext == '.epub' and format_new_ext == '.kepub': + check, error_message = self._convert_kepubify(file_path, + format_old_ext, + format_new_ext) + else: + # check if calibre converter-executable is existing + if not os.path.exists(config.config_converterpath): + self._handleError(N_("Calibre ebook-convert %(tool)s not found", tool=config.config_converterpath)) + return + has_cover = local_db.get_book(book_id).has_cover + check, error_message = self._convert_calibre(file_path, format_old_ext, format_new_ext, has_cover) + + if check == 0: + cur_book = local_db.get_book(book_id) + if os.path.isfile(file_path + format_new_ext): + new_format = local_db.session.query(db.Data).filter(db.Data.book == book_id) \ + .filter(db.Data.format == self.settings['new_book_format'].upper()).one_or_none() + if not new_format: + new_format = db.Data(name=cur_book.data[0].name, + book_format=self.settings['new_book_format'].upper(), + book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext)) + try: + local_db.session.merge(new_format) + local_db.session.commit() + if self.settings['new_book_format'].upper() in ['KEPUB', 'EPUB', 'EPUB3']: + ub_session = init_db_thread() + remove_synced_book(book_id, True, ub_session) + ub_session.close() + except SQLAlchemyError as e: + local_db.session.rollback() + log.error("Database error: %s", e) + local_db.session.close() + self._handleError(error_message) + return + self.results['path'] = cur_book.path + self.title = cur_book.title + self.results['title'] = self.title + if not config.config_use_google_drive: + self._handleSuccess() + return os.path.basename(file_path + format_new_ext) + else: + error_message = N_('%(format)s format not found on disk', format=format_new_ext.upper()) + local_db.session.close() + log.info("ebook converter failed with error while converting book") + if not error_message: + error_message = N_('Ebook converter failed with unknown error') + else: + log.error(error_message) + self._handleError(error_message) + return def _convert_kepubify(self, file_path, format_old_ext, format_new_ext): if config.config_embed_metadata and config.config_binariesdir: diff --git a/cps/tasks/database.py b/cps/tasks/database.py index c9c30d43..e5f630c6 100644 --- a/cps/tasks/database.py +++ b/cps/tasks/database.py @@ -18,7 +18,7 @@ from flask_babel import lazy_gettext as N_ -from cps import config, logger, db, ub +from cps import config, logger, db, ub, app from cps.services.worker import CalibreTask @@ -26,11 +26,13 @@ class TaskReconnectDatabase(CalibreTask): def __init__(self, task_message=N_('Reconnecting Calibre database')): super(TaskReconnectDatabase, self).__init__(task_message) self.log = logger.create() - self.calibre_db = db.CalibreDB(expire_on_commit=False, init=True) + # self.calibre_db = db.CalibreDB(expire_on_commit=False, init=True) def run(self, worker_thread): - self.calibre_db.reconnect_db(config, ub.app_DB_path) - self.calibre_db.session.close() + with app.app_context(): + calibre_db = db.CalibreDB(app) + calibre_db.reconnect_db(config, ub.app_DB_path) + # self.calibre_db.session.close() self._handleSuccess() @property diff --git a/cps/tasks/metadata_backup.py b/cps/tasks/metadata_backup.py index 5e0bb96a..dc5b8404 100644 --- a/cps/tasks/metadata_backup.py +++ b/cps/tasks/metadata_backup.py @@ -19,7 +19,7 @@ import os from lxml import etree -from cps import config, db, gdriveutils, logger +from cps import config, db, gdriveutils, logger, app from cps.services.worker import CalibreTask from flask_babel import lazy_gettext as N_ @@ -34,7 +34,7 @@ class TaskBackupMetadata(CalibreTask): task_message=N_('Backing up Metadata')): super(TaskBackupMetadata, self).__init__(task_message) self.log = logger.create() - self.calibre_db = db.CalibreDB(expire_on_commit=False, init=True) + # self.calibre_db = db.CalibreDB(expire_on_commit=False, init=True) self.export_language = export_language self.translated_title = translated_title self.set_dirty = set_dirty @@ -46,47 +46,51 @@ class TaskBackupMetadata(CalibreTask): self.backup_metadata() def set_all_books_dirty(self): - try: - books = self.calibre_db.session.query(db.Books).all() - for book in books: - self.calibre_db.set_metadata_dirty(book.id) - self.calibre_db.session.commit() - self._handleSuccess() - except Exception as ex: - self.log.debug('Error adding book for backup: ' + str(ex)) - self._handleError('Error adding book for backup: ' + str(ex)) - self.calibre_db.session.rollback() - self.calibre_db.session.close() + with app.app_context(): + calibre_db = db.CalibreDB(app) + try: + books = calibre_db.session.query(db.Books).all() + for book in books: + calibre_db.set_metadata_dirty(book.id) + calibre_db.session.commit() + self._handleSuccess() + except Exception as ex: + self.log.debug('Error adding book for backup: ' + str(ex)) + self._handleError('Error adding book for backup: ' + str(ex)) + calibre_db.session.rollback() + # self.calibre_db.session.close() def backup_metadata(self): - try: - metadata_backup = self.calibre_db.session.query(db.Metadata_Dirtied).all() - custom_columns = (self.calibre_db.session.query(db.CustomColumns) - .filter(db.CustomColumns.mark_for_delete == 0) - .filter(db.CustomColumns.datatype.notin_(db.cc_exceptions)) - .order_by(db.CustomColumns.label).all()) - count = len(metadata_backup) - i = 0 - for backup in metadata_backup: - book = self.calibre_db.session.query(db.Books).filter(db.Books.id == backup.book).one_or_none() - self.calibre_db.session.query(db.Metadata_Dirtied).filter( - db.Metadata_Dirtied.book == backup.book).delete() - self.calibre_db.session.commit() - if book: - self.open_metadata(book, custom_columns) - else: - self.log.error("Book {} not found in database".format(backup.book)) - i += 1 - self.progress = (1.0 / count) * i - self._handleSuccess() - self.calibre_db.session.close() + with app.app_context(): + try: + calibre_db = db.CalibreDB(app) + metadata_backup = calibre_db.session.query(db.Metadata_Dirtied).all() + custom_columns = (calibre_db.session.query(db.CustomColumns) + .filter(db.CustomColumns.mark_for_delete == 0) + .filter(db.CustomColumns.datatype.notin_(db.cc_exceptions)) + .order_by(db.CustomColumns.label).all()) + count = len(metadata_backup) + i = 0 + for backup in metadata_backup: + book = calibre_db.session.query(db.Books).filter(db.Books.id == backup.book).one_or_none() + calibre_db.session.query(db.Metadata_Dirtied).filter( + db.Metadata_Dirtied.book == backup.book).delete() + calibre_db.session.commit() + if book: + self.open_metadata(book, custom_columns) + else: + self.log.error("Book {} not found in database".format(backup.book)) + i += 1 + self.progress = (1.0 / count) * i + self._handleSuccess() + # self.calibre_db.session.close() - except Exception as ex: - b = "NaN" if not hasattr(book, 'id') else book.id - self.log.debug('Error creating metadata backup for book {}: '.format(b) + str(ex)) - self._handleError('Error creating metadata backup: ' + str(ex)) - self.calibre_db.session.rollback() - self.calibre_db.session.close() + except Exception as ex: + b = "NaN" if not hasattr(book, 'id') else book.id + self.log.debug('Error creating metadata backup for book {}: '.format(b) + str(ex)) + self._handleError('Error creating metadata backup: ' + str(ex)) + calibre_db.session.rollback() + # self.calibre_db.session.close() def open_metadata(self, book, custom_columns): # package = self.create_new_metadata_backup(book, custom_columns) diff --git a/cps/tasks/thumbnail.py b/cps/tasks/thumbnail.py index a7658c02..358f4ad1 100644 --- a/cps/tasks/thumbnail.py +++ b/cps/tasks/thumbnail.py @@ -23,10 +23,11 @@ from io import BytesIO from datetime import datetime, timezone from .. import constants -from cps import config, db, fs, gdriveutils, logger, ub +from cps import config, db, fs, gdriveutils, logger, ub, app from cps.services.worker import CalibreTask, STAT_CANCELLED, STAT_ENDED from sqlalchemy import func, text, or_ from flask_babel import lazy_gettext as N_ + try: from wand.image import Image use_IM = True @@ -113,9 +114,10 @@ class TaskGenerateCoverThumbnails(CalibreTask): @staticmethod def get_books_with_covers(book_id=-1): filter_exp = (db.Books.id == book_id) if book_id != -1 else True - calibre_db = db.CalibreDB(expire_on_commit=False, init=True) - books_cover = calibre_db.session.query(db.Books).filter(db.Books.has_cover == 1).filter(filter_exp).all() - calibre_db.session.close() + with app.app_context(): + calibre_db = db.CalibreDB(app) #, expire_on_commit=False, init=True) + books_cover = calibre_db.session.query(db.Books).filter(db.Books.has_cover == 1).filter(filter_exp).all() + # calibre_db.session.close() return books_cover def get_book_cover_thumbnails(self, book_id): @@ -246,7 +248,7 @@ class TaskGenerateSeriesThumbnails(CalibreTask): super(TaskGenerateSeriesThumbnails, self).__init__(task_message) self.log = logger.create() self.app_db_session = ub.get_new_session_instance() - self.calibre_db = db.CalibreDB(expire_on_commit=False, init=True) + # self.calibre_db = db.CalibreDB(expire_on_commit=False, init=True) self.cache = fs.FileSystem() self.resolutions = [ constants.COVER_THUMBNAIL_SMALL, @@ -254,58 +256,60 @@ class TaskGenerateSeriesThumbnails(CalibreTask): ] def run(self, worker_thread): - if self.calibre_db.session and use_IM and self.stat != STAT_CANCELLED and self.stat != STAT_ENDED: - self.message = 'Scanning Series' - all_series = self.get_series_with_four_plus_books() - count = len(all_series) + with app.app_context(): + calibre_db = db.CalibreDB(app) + if calibre_db.session and use_IM and self.stat != STAT_CANCELLED and self.stat != STAT_ENDED: + self.message = 'Scanning Series' + all_series = self.get_series_with_four_plus_books(calibre_db) + count = len(all_series) - total_generated = 0 - for i, series in enumerate(all_series): - generated = 0 - series_thumbnails = self.get_series_thumbnails(series.id) - series_books = self.get_series_books(series.id) + total_generated = 0 + for i, series in enumerate(all_series): + generated = 0 + series_thumbnails = self.get_series_thumbnails(series.id) + series_books = self.get_series_books(series.id, calibre_db) - # Generate new thumbnails for missing covers - resolutions = list(map(lambda t: t.resolution, series_thumbnails)) - missing_resolutions = list(set(self.resolutions).difference(resolutions)) - for resolution in missing_resolutions: - generated += 1 - self.create_series_thumbnail(series, series_books, resolution) - - # Replace outdated or missing thumbnails - for thumbnail in series_thumbnails: - if any(book.last_modified > thumbnail.generated_at for book in series_books): + # Generate new thumbnails for missing covers + resolutions = list(map(lambda t: t.resolution, series_thumbnails)) + missing_resolutions = list(set(self.resolutions).difference(resolutions)) + for resolution in missing_resolutions: generated += 1 - self.update_series_thumbnail(series_books, thumbnail) + self.create_series_thumbnail(series, series_books, resolution) - elif not self.cache.get_cache_file_exists(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS): - generated += 1 - self.update_series_thumbnail(series_books, thumbnail) + # Replace outdated or missing thumbnails + for thumbnail in series_thumbnails: + if any(book.last_modified > thumbnail.generated_at for book in series_books): + generated += 1 + self.update_series_thumbnail(series_books, thumbnail) - # Increment the progress - self.progress = (1.0 / count) * i + elif not self.cache.get_cache_file_exists(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS): + generated += 1 + self.update_series_thumbnail(series_books, thumbnail) - if generated > 0: - total_generated += generated - self.message = N_('Generated {0} series thumbnails').format(total_generated) + # Increment the progress + self.progress = (1.0 / count) * i - # Check if job has been cancelled or ended - if self.stat == STAT_CANCELLED: - self.log.info(f'GenerateSeriesThumbnails task has been cancelled.') - return + if generated > 0: + total_generated += generated + self.message = N_('Generated {0} series thumbnails').format(total_generated) - if self.stat == STAT_ENDED: - self.log.info(f'GenerateSeriesThumbnails task has been ended.') - return + # Check if job has been cancelled or ended + if self.stat == STAT_CANCELLED: + self.log.info(f'GenerateSeriesThumbnails task has been cancelled.') + return - if total_generated == 0: - self.self_cleanup = True + if self.stat == STAT_ENDED: + self.log.info(f'GenerateSeriesThumbnails task has been ended.') + return - self._handleSuccess() - self.app_db_session.remove() + if total_generated == 0: + self.self_cleanup = True - def get_series_with_four_plus_books(self): - return self.calibre_db.session \ + self._handleSuccess() + self.app_db_session.remove() + + def get_series_with_four_plus_books(self, calibre_db): + return calibre_db.session \ .query(db.Series) \ .join(db.books_series_link) \ .join(db.Books) \ @@ -314,8 +318,8 @@ class TaskGenerateSeriesThumbnails(CalibreTask): .having(func.count('book_series_link') > 3) \ .all() - def get_series_books(self, series_id): - return self.calibre_db.session \ + def get_series_books(self, series_id, calibre_db): + return calibre_db.session \ .query(db.Books) \ .join(db.books_series_link) \ .join(db.Series) \ @@ -461,13 +465,15 @@ class TaskClearCoverThumbnailCache(CalibreTask): def run(self, worker_thread): if self.app_db_session: - if self.book_id == 0: # delete superfluous thumbnails - calibre_db = db.CalibreDB(expire_on_commit=False, init=True) - thumbnails = (calibre_db.session.query(ub.Thumbnail) - .join(db.Books, ub.Thumbnail.entity_id == db.Books.id, isouter=True) - .filter(db.Books.id==None) - .all()) - calibre_db.session.close() + # delete superfluous thumbnails + if self.book_id == 0: + with app.app_context(): + calibre_db = db.CalibreDB(app) + thumbnails = (calibre_db.session.query(ub.Thumbnail) + .join(db.Books, ub.Thumbnail.entity_id == db.Books.id, isouter=True) + .filter(db.Books.id==None) + .all()) + # calibre_db.session.close() elif self.book_id > 0: # make sure single book is selected thumbnails = self.get_thumbnails_for_book(self.book_id) if self.book_id < 0: diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 8443cf4a..234bfb96 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2024-11-20 18:23:06

+

Start Time: 2024-11-25 20:45:11

-

Stop Time: 2024-11-21 01:38:12

+

Stop Time: 2024-11-26 02:45:09

-

Duration: 6h 7 min

+

Duration: 4h 59 min

@@ -234,12 +234,12 @@ - + TestBackupMetadata 21 - 21 - 0 - 0 + 16 + 4 + 1 0 Detail @@ -248,20 +248,67 @@ - +
TestBackupMetadata - test_backup_all
- PASS + +
+ FAIL +
+ + + + - +
TestBackupMetadata - test_backup_change_book_author
- PASS + +
+ FAIL +
+ + + + @@ -275,11 +322,31 @@ - +
TestBackupMetadata - test_backup_change_book_identifier
- PASS + +
+ FAIL +
+ + + + @@ -302,11 +369,31 @@ - +
TestBackupMetadata - test_backup_change_book_publishing_date
- PASS + +
+ ERROR +
+ + + + @@ -338,11 +425,32 @@ - +
TestBackupMetadata - test_backup_change_book_title
- PASS + +
+ FAIL +
+ + + + @@ -438,13 +546,13 @@ - + TestBackupMetadataGdrive 1 + 0 + 0 1 0 - 0 - 0 Detail @@ -452,31 +560,307 @@ - +
TestBackupMetadataGdrive - test_backup_gdrive
- PASS + +
+ ERROR +
+ + + + - - TestCli - 13 - 13 + + _ErrorHolder + 3 0 0 + 3 0 - Detail + Detail - + + +
tearDownClass (test_backup_metadata_gdrive)
+ + +
+ ERROR +
+ + + + + + + + + + +
tearDownClass (test_edit_additional_books)
+ + +
+ ERROR +
+ + + + + + + + + + +
tearDownClass (test_readonly_db)
+ + +
+ ERROR +
+ + + + + + + + + + + TestCli + 13 + 8 + 3 + 2 + 0 + + Detail + + + + + +
TestCli - test_already_started
@@ -485,7 +869,7 @@ - +
TestCli - test_bind_to_single_interface
@@ -494,7 +878,7 @@ - +
TestCli - test_change_password
@@ -503,7 +887,7 @@ - +
TestCli - test_cli_SSL_files
@@ -512,7 +896,7 @@ - +
TestCli - test_cli_different_folder
@@ -521,7 +905,7 @@ - +
TestCli - test_cli_different_settings_database
@@ -530,7 +914,7 @@ - +
TestCli - test_dryrun_update
@@ -539,16 +923,36 @@ - +
TestCli - test_enable_reconnect
- PASS + +
+ FAIL +
+ + + + - +
TestCli - test_environ_port_setting
@@ -557,89 +961,325 @@ - +
TestCli - test_logfile
- PASS + +
+ ERROR +
+ + + + - +
TestCli - test_no_database
- PASS + +
+ FAIL +
+ + + + - +
TestCli - test_settingsdb_not_writeable
- PASS + +
+ FAIL +
+ + + + - +
TestCli - test_writeonly_static_files
- PASS + +
+ ERROR +
+ + + + - + TestCliGdrivedb 4 - 4 - 0 0 + 3 + 1 0 - Detail + Detail - +
TestCliGdrivedb - test_cli_gdrive_folder
- PASS + +
+ ERROR +
+ + + + - +
TestCliGdrivedb - test_cli_gdrive_location
- PASS + +
+ FAIL +
+ + + + - +
TestCliGdrivedb - test_gdrive_db_nonwrite
- PASS + +
+ FAIL +
+ + + + - +
TestCliGdrivedb - test_no_database
- PASS + +
+ FAIL +
+ + + + @@ -653,13 +1293,13 @@ 0 0 - Detail + Detail - +
TestCoverEditBooks - test_invalid_jpg_hdd
@@ -668,7 +1308,7 @@ - +
TestCoverEditBooks - test_upload_jpg
@@ -686,13 +1326,13 @@ 0 0 - Detail + Detail - +
TestDeleteDatabase - test_delete_books_in_database
@@ -702,30 +1342,50 @@ - + TestEbookConvertCalibre 15 - 15 - 0 - 0 + 6 + 2 + 7 0 - Detail + Detail - +
TestEbookConvertCalibre - test_calibre_log
- PASS + +
+ ERROR +
+ + + + - +
TestEbookConvertCalibre - test_convert_deactivate
@@ -734,70 +1394,214 @@ - +
TestEbookConvertCalibre - test_convert_email
- PASS + +
+ ERROR +
+ + + + - +
TestEbookConvertCalibre - test_convert_failed_and_email
- PASS + +
+ ERROR +
+ + + + - +
TestEbookConvertCalibre - test_convert_only
- PASS + +
+ FAIL +
+ + + + - +
TestEbookConvertCalibre - test_convert_options
- PASS + +
+ ERROR +
+ + + + - +
TestEbookConvertCalibre - test_convert_parameter
- PASS + +
+ FAIL +
+ + + + - +
TestEbookConvertCalibre - test_convert_wrong_excecutable
- PASS + +
+ ERROR +
+ + + + - +
TestEbookConvertCalibre - test_convert_xss
- PASS + +
+ ERROR +
+ + + + - +
TestEbookConvertCalibre - test_email_failed
@@ -806,7 +1610,7 @@ - +
TestEbookConvertCalibre - test_email_only
@@ -815,7 +1619,7 @@ - +
TestEbookConvertCalibre - test_kindle_send_not_configured
@@ -824,7 +1628,7 @@ - +
TestEbookConvertCalibre - test_ssl_smtp_setup_error
@@ -833,7 +1637,7 @@ - +
TestEbookConvertCalibre - test_starttls_smtp_setup_error
@@ -842,67 +1646,173 @@ - +
TestEbookConvertCalibre - test_user_convert_xss
- PASS + +
+ ERROR +
+ + + + - + TestEbookConvertCalibreGDrive 7 - 7 - 0 - 0 + 3 + 3 + 1 0 - Detail + Detail - +
TestEbookConvertCalibreGDrive - test_convert_email
- PASS + +
+ ERROR +
+ + + + - +
TestEbookConvertCalibreGDrive - test_convert_failed_and_email
- PASS + +
+ FAIL +
+ + + + - +
TestEbookConvertCalibreGDrive - test_convert_only
- PASS + +
+ FAIL +
+ + + + - +
TestEbookConvertCalibreGDrive - test_convert_parameter
- PASS + +
+ FAIL +
+ + + + - +
TestEbookConvertCalibreGDrive - test_email_failed
@@ -911,7 +1821,7 @@ - +
TestEbookConvertCalibreGDrive - test_email_only
@@ -920,7 +1830,7 @@ - +
TestEbookConvertCalibreGDrive - test_thumbnail_cache
@@ -930,21 +1840,21 @@ - + TestEbookConvertKepubify 4 - 4 - 0 + 2 0 + 2 0 - Detail + Detail - +
TestEbookConvertKepubify - test_convert_deactivate
@@ -953,16 +1863,36 @@ - +
TestEbookConvertKepubify - test_convert_only
- PASS + +
+ ERROR +
+ + + + - +
TestEbookConvertKepubify - test_convert_wrong_excecutable
@@ -971,31 +1901,51 @@ - +
TestEbookConvertKepubify - test_kobo_kepub_formats
- PASS + +
+ ERROR +
+ + + + - + TestEbookConvertGDriveKepubify 3 - 3 - 0 + 2 + 1 0 0 - Detail + Detail - +
TestEbookConvertGDriveKepubify - test_convert_deactivate
@@ -1004,16 +1954,38 @@ - +
TestEbookConvertGDriveKepubify - test_convert_only
- PASS + +
+ FAIL +
+ + + + - +
TestEbookConvertGDriveKepubify - test_convert_wrong_excecutable
@@ -1023,21 +1995,21 @@ - + TestEditAdditionalBooks 20 - 18 - 0 + 15 0 + 3 2 - Detail + Detail - +
TestEditAdditionalBooks - test_cbz_comicinfo
@@ -1046,7 +2018,7 @@ - +
TestEditAdditionalBooks - test_change_upload_formats
@@ -1055,7 +2027,7 @@ - +
TestEditAdditionalBooks - test_delete_book
@@ -1064,7 +2036,7 @@ - +
TestEditAdditionalBooks - test_delete_role
@@ -1073,7 +2045,7 @@ - +
TestEditAdditionalBooks - test_details_popup
@@ -1082,7 +2054,7 @@ - +
TestEditAdditionalBooks - test_edit_book_identifier
@@ -1091,7 +2063,7 @@ - +
TestEditAdditionalBooks - test_edit_book_identifier_capital
@@ -1100,7 +2072,7 @@ - +
TestEditAdditionalBooks - test_edit_book_identifier_standard
@@ -1109,7 +2081,7 @@ - +
TestEditAdditionalBooks - test_edit_special_book_identifier
@@ -1118,7 +2090,7 @@ - +
TestEditAdditionalBooks - test_title_sort
@@ -1127,7 +2099,7 @@ - +
TestEditAdditionalBooks - test_upload_cbz_coverformats
@@ -1136,7 +2108,7 @@ - +
TestEditAdditionalBooks - test_upload_edit_role
@@ -1145,7 +2117,7 @@ - +
TestEditAdditionalBooks - test_upload_metadata_cb7
@@ -1154,7 +2126,7 @@ - +
TestEditAdditionalBooks - test_upload_metadata_cbr
@@ -1163,7 +2135,7 @@ - +
TestEditAdditionalBooks - test_upload_metadata_cbt
@@ -1172,19 +2144,19 @@ - +
TestEditAdditionalBooks - test_writeonly_calibre_database
- SKIP + SKIP
- @@ -4620,12 +5878,12 @@ Traceback (most recent call last): conn = self._new_conn() File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connection.py", line 186, in _new_conn raise NewConnectionError( -urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPConnection object at 0x7031c946f5e0>: Failed to establish a new connection: [Errno 111] Connection refused +urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPConnection object at 0x7f1405f26710>: Failed to establish a new connection: [Errno 111] Connection refused During handling of the above exception, another exception occurred: Traceback (most recent call last): - File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 935, in test_watch_metadata + File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 937, in test_watch_metadata button = self.check_element_on_page((By.ID, "enable_gdrive_watch")) File "/home/ozzie/Development/calibre-web-test/test/helper_ui.py", line 744, in check_element_on_page el = WebDriverWait(cls.driver, timeout).until(EC.presence_of_element_located(element)) @@ -4657,7 +5915,7 @@ Traceback (most recent call last): retries = retries.increment( File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/util/retry.py", line 592, in increment raise MaxRetryError(_pool, url, error or ResponseError(cause)) -urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', port=55101): Max retries exceeded with url: /session/c12963c5-b8e2-4d16-acb3-8dad08addce3/element (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7031c946f5e0>: Failed to establish a new connection: [Errno 111] Connection refused')) +urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', port=44323): Max retries exceeded with url: /session/675eac0b-8327-4776-a9d7-69d4b4655190/element (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f1405f26710>: Failed to establish a new connection: [Errno 111] Connection refused'))
@@ -4812,12 +6070,12 @@ urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', p - + TestEmbedMetadata 6 - 2 - 2 - 2 + 6 + 0 + 0 0 Detail @@ -4826,62 +6084,20 @@ urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', p - +
TestEmbedMetadata - test_convert_file_embed_metadata
- -
- ERROR -
- - - - + PASS - +
TestEmbedMetadata - test_convert_kepub_embed_metadata
- -
- ERROR -
- - - - + PASS @@ -4895,60 +6111,20 @@ FileNotFoundError: [Errno 2] No such file or directory: '/home/ozzie/Develop - +
TestEmbedMetadata - test_download_kepub_embed_metadata
- -
- FAIL -
- - - - + PASS - +
TestEmbedMetadata - test_download_permissions_missing_file
- -
- FAIL -
- - - - + PASS @@ -4966,9 +6142,9 @@ AssertionError: 20746 != 5954 TestEmbedMetadataGdrive 4 - 0 - 0 - 4 + 1 + 1 + 2 0 Detail @@ -4994,71 +6170,13 @@ AssertionError: 20746 != 5954
Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connection.py", line 174, in _new_conn
-    conn = connection.create_connection(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/util/connection.py", line 95, in create_connection
-    raise err
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/util/connection.py", line 85, in create_connection
-    sock.connect(sa)
-ConnectionRefusedError: [Errno 111] Connection refused
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 703, in urlopen
-    httplib_response = self._make_request(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 398, in _make_request
-    conn.request(method, url, **httplib_request_kw)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connection.py", line 239, in request
-    super(HTTPConnection, self).request(method, url, body=body, headers=headers)
-  File "/usr/lib/python3.10/http/client.py", line 1283, in request
-    self._send_request(method, url, body, headers, encode_chunked)
-  File "/usr/lib/python3.10/http/client.py", line 1329, in _send_request
-    self.endheaders(body, encode_chunked=encode_chunked)
-  File "/usr/lib/python3.10/http/client.py", line 1278, in endheaders
-    self._send_output(message_body, encode_chunked=encode_chunked)
-  File "/usr/lib/python3.10/http/client.py", line 1038, in _send_output
-    self.send(msg)
-  File "/usr/lib/python3.10/http/client.py", line 976, in send
-    self.connect()
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connection.py", line 205, in connect
-    conn = self._new_conn()
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connection.py", line 186, in _new_conn
-    raise NewConnectionError(
-urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPConnection object at 0x7031c915aec0>: Failed to establish a new connection: [Errno 111] Connection refused
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/test/test_embed_metadata_gdrive.py", line 161, in test_convert_file_embed_metadata
-    vals = self.get_convert_book(12)
-  File "/home/ozzie/Development/calibre-web-test/test/helper_ui.py", line 2058, in get_convert_book
-    cls.driver.get(root_url + "/admin/book/"+str(id))
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 449, in get
-    self.execute(Command.GET, {"url": url})
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 438, in execute
-    response = self.command_executor.execute(driver_command, params)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/remote_connection.py", line 290, in execute
-    return self._request(command_info[0], url, body=data)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/remote_connection.py", line 311, in _request
-    response = self._conn.request(method, url, body=body, headers=headers)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/request.py", line 78, in request
-    return self.request_encode_body(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/request.py", line 170, in request_encode_body
-    return self.urlopen(method, url, **extra_kw)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/poolmanager.py", line 376, in urlopen
-    response = conn.urlopen(method, u.request_uri, **kw)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
-    return self.urlopen(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
-    return self.urlopen(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
-    return self.urlopen(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 787, in urlopen
-    retries = retries.increment(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/util/retry.py", line 592, in increment
-    raise MaxRetryError(_pool, url, error or ResponseError(cause))
-urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', port=46827): Max retries exceeded with url: /session/888cc4bf-a51a-48ce-af19-e5699d37f1d8/url (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7031c915aec0>: Failed to establish a new connection: [Errno 111] Connection refused'))
+ File "/home/ozzie/Development/calibre-web-test/test/test_embed_metadata_gdrive.py", line 183, in test_convert_file_embed_metadata + with zipfile.ZipFile(fs.open(epub_path, "rb")) as thezip: + File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/fs/base.py", line 1228, in open + bin_file = self.openbin(path, mode=bin_mode, buffering=buffering) + File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/fs/googledrivefs/googledrivefs.py", line 438, in openbin + raise ResourceNotFound(path) +fs.errors.ResourceNotFound: resource '/test/Lulu de Marco/book10 (12)/book10 - Lulu de Marco.epub' not found
@@ -5085,71 +6203,13 @@ urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', p
Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connection.py", line 174, in _new_conn
-    conn = connection.create_connection(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/util/connection.py", line 95, in create_connection
-    raise err
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/util/connection.py", line 85, in create_connection
-    sock.connect(sa)
-ConnectionRefusedError: [Errno 111] Connection refused
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 703, in urlopen
-    httplib_response = self._make_request(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 398, in _make_request
-    conn.request(method, url, **httplib_request_kw)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connection.py", line 239, in request
-    super(HTTPConnection, self).request(method, url, body=body, headers=headers)
-  File "/usr/lib/python3.10/http/client.py", line 1283, in request
-    self._send_request(method, url, body, headers, encode_chunked)
-  File "/usr/lib/python3.10/http/client.py", line 1329, in _send_request
-    self.endheaders(body, encode_chunked=encode_chunked)
-  File "/usr/lib/python3.10/http/client.py", line 1278, in endheaders
-    self._send_output(message_body, encode_chunked=encode_chunked)
-  File "/usr/lib/python3.10/http/client.py", line 1038, in _send_output
-    self.send(msg)
-  File "/usr/lib/python3.10/http/client.py", line 976, in send
-    self.connect()
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connection.py", line 205, in connect
-    conn = self._new_conn()
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connection.py", line 186, in _new_conn
-    raise NewConnectionError(
-urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPConnection object at 0x7031c9159cc0>: Failed to establish a new connection: [Errno 111] Connection refused
-
-During handling of the above exception, another exception occurred:
-
-Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/test/test_embed_metadata_gdrive.py", line 193, in test_convert_kepub_embed_metadata
-    vals = self.get_convert_book(9)
-  File "/home/ozzie/Development/calibre-web-test/test/helper_ui.py", line 2058, in get_convert_book
-    cls.driver.get(root_url + "/admin/book/"+str(id))
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 449, in get
-    self.execute(Command.GET, {"url": url})
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 438, in execute
-    response = self.command_executor.execute(driver_command, params)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/remote_connection.py", line 290, in execute
-    return self._request(command_info[0], url, body=data)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/remote_connection.py", line 311, in _request
-    response = self._conn.request(method, url, body=body, headers=headers)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/request.py", line 78, in request
-    return self.request_encode_body(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/request.py", line 170, in request_encode_body
-    return self.urlopen(method, url, **extra_kw)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/poolmanager.py", line 376, in urlopen
-    response = conn.urlopen(method, u.request_uri, **kw)
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
-    return self.urlopen(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
-    return self.urlopen(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 815, in urlopen
-    return self.urlopen(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 787, in urlopen
-    retries = retries.increment(
-  File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/urllib3/util/retry.py", line 592, in increment
-    raise MaxRetryError(_pool, url, error or ResponseError(cause))
-urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', port=46827): Max retries exceeded with url: /session/888cc4bf-a51a-48ce-af19-e5699d37f1d8/url (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7031c9159cc0>: Failed to establish a new connection: [Errno 111] Connection refused'))
+ File "/home/ozzie/Development/calibre-web-test/test/test_embed_metadata_gdrive.py", line 214, in test_convert_kepub_embed_metadata + with zipfile.ZipFile(fs.open(epub_path, "rb")) as thezip: + File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/fs/base.py", line 1228, in open + bin_file = self.openbin(path, mode=bin_mode, buffering=buffering) + File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/fs/googledrivefs/googledrivefs.py", line 438, in openbin + raise ResourceNotFound(path) +fs.errors.ResourceNotFound: resource '/test/Sigurd Lindgren/book6 (9)/book6 - Sigurd Lindgren.kepub' not found
@@ -5159,191 +6219,35 @@ urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', p - +
TestEmbedMetadataGdrive - test_download_check_metadata
- -
- ERROR -
- - - - + PASS - +
TestEmbedMetadataGdrive - test_download_kepub_embed_metadata
- ERROR + FAIL
- From d45ed1c921975922e3786791c04c0a67af1f4577 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 30 Nov 2024 09:45:46 +0100 Subject: [PATCH 3/4] Fixes for parallel upload (coverfiles get individual temporary names) Updated teststatus --- cps/audio.py | 18 +- cps/comic.py | 12 +- cps/cover.py | 5 +- cps/uploader.py | 4 +- test/Calibre-Web TestSummary_Linux.html | 5036 +++-------------------- 5 files changed, 627 insertions(+), 4448 deletions(-) diff --git a/cps/audio.py b/cps/audio.py index b17bc7bd..749b2bdc 100644 --- a/cps/audio.py +++ b/cps/audio.py @@ -16,8 +16,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os - import mutagen import base64 from . import cover, logger @@ -51,13 +49,12 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na if not pubdate: pubdate = str(audio_file.tags.get('TDOR').text[0]) if "TDOR" in audio_file.tags else None if cover_data and not no_cover_processing: - tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg') cover_info = cover_data[0] for dat in cover_data: if dat.type == mutagen.id3.PictureType.COVER_FRONT: cover_info = dat break - cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:]) + tmp_cover_name = cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:]) elif original_file_extension in [".ogg", ".flac", ".opus", ".ogv"]: title = audio_file.tags.get('TITLE')[0] if "TITLE" in audio_file else None author = audio_file.tags.get('ARTIST')[0] if "ARTIST" in audio_file else None @@ -70,17 +67,15 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na cover_data = audio_file.tags.get('METADATA_BLOCK_PICTURE') if not no_cover_processing: if cover_data: - tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg') cover_info = mutagen.flac.Picture(base64.b64decode(cover_data[0])) - cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:]) + tmp_cover_name = cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:]) if hasattr(audio_file, "pictures"): cover_info = audio_file.pictures[0] for dat in audio_file.pictures: if dat.type == mutagen.id3.PictureType.COVER_FRONT: cover_info = dat break - tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg') - cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:]) + tmp_cover_name = cover.cover_processing(tmp_file_path, cover_info.data, "." + cover_info.mime[-3:]) elif original_file_extension in [".aac"]: title = audio_file.tags.get('Title').value if "Title" in audio_file else None author = audio_file.tags.get('Artist').value if "Artist" in audio_file else None @@ -92,7 +87,7 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na pubdate = audio_file.tags.get('Year').value if "Year" in audio_file else None cover_data = audio_file.tags['Cover Art (Front)'] if cover_data and not no_cover_processing: - tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg') + tmp_cover_name = tmp_file_path + '.jpg' with open(tmp_cover_name, "wb") as cover_file: cover_file.write(cover_data.value.split(b"\x00",1)[1]) elif original_file_extension in [".asf"]: @@ -106,7 +101,7 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na pubdate = audio_file.tags.get('Year')[0].value if "Year" in audio_file else None cover_data = audio_file.tags.get('WM/Picture', None) if cover_data and not no_cover_processing: - tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg') + tmp_cover_name = tmp_file_path + '.jpg' with open(tmp_cover_name, "wb") as cover_file: cover_file.write(cover_data[0].value) elif original_file_extension in [".mp4", ".m4a", ".m4b"]: @@ -120,7 +115,6 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na pubdate = audio_file.tags.get('©day')[0] if "©day" in audio_file.tags else None cover_data = audio_file.tags.get('covr', None) if cover_data and not no_cover_processing: - tmp_cover_name = os.path.join(os.path.dirname(tmp_file_path), 'cover.jpg') cover_type = None for c in cover_data: if c.imageformat == mutagen.mp4.AtomDataType.JPEG: @@ -132,7 +126,7 @@ def get_audio_file_info(tmp_file_path, original_file_extension, original_file_na cover_bin = c break if cover_type: - cover.cover_processing(tmp_file_path, cover_bin, cover_type) + tmp_cover_name = cover.cover_processing(tmp_file_path, cover_bin, cover_type) else: logger.error("Unknown covertype in file {} ".format(original_file_name)) diff --git a/cps/comic.py b/cps/comic.py index a4a4f136..271b1b45 100644 --- a/cps/comic.py +++ b/cps/comic.py @@ -90,7 +90,7 @@ def _extract_cover_from_archive(original_file_extension, tmp_file_name, rar_exec if len(ext) > 1: extension = ext[1].lower() if extension in cover.COVER_EXTENSIONS: - cover_data = cf.read([name]) + cover_data = cf.read(name) break except Exception as ex: log.error('Rarfile failed with error: {}'.format(ex)) @@ -109,13 +109,13 @@ def _extract_cover_from_archive(original_file_extension, tmp_file_name, rar_exec return cover_data, extension -def _extract_cover(tmp_file_name, original_file_extension, rar_executable): +def _extract_cover(tmp_file_path, original_file_extension, rar_executable): cover_data = extension = None if use_comic_meta: try: - archive = ComicArchive(tmp_file_name, rar_exe_path=rar_executable) + archive = ComicArchive(tmp_file_path, rar_exe_path=rar_executable) except TypeError: - archive = ComicArchive(tmp_file_name) + archive = ComicArchive(tmp_file_path) name_list = archive.getPageNameList if hasattr(archive, "getPageNameList") else archive.get_page_name_list for index, name in enumerate(name_list()): ext = os.path.splitext(name) @@ -126,8 +126,8 @@ def _extract_cover(tmp_file_name, original_file_extension, rar_executable): cover_data = get_page(index) break else: - cover_data, extension = _extract_cover_from_archive(original_file_extension, tmp_file_name, rar_executable) - return cover.cover_processing(tmp_file_name, cover_data, extension) + cover_data, extension = _extract_cover_from_archive(original_file_extension, tmp_file_path, rar_executable) + return cover.cover_processing(tmp_file_path, cover_data, extension) def get_comic_info(tmp_file_path, original_file_name, original_file_extension, rar_executable, no_cover_processing): diff --git a/cps/cover.py b/cps/cover.py index 5dd29534..89fa4770 100644 --- a/cps/cover.py +++ b/cps/cover.py @@ -29,8 +29,9 @@ NO_JPEG_EXTENSIONS = ['.png', '.webp', '.bmp'] COVER_EXTENSIONS = ['.png', '.webp', '.bmp', '.jpg', '.jpeg'] -def cover_processing(tmp_file_name, img, extension): - tmp_cover_name = os.path.join(os.path.dirname(tmp_file_name), 'cover.jpg') +def cover_processing(tmp_file_path, img, extension): + # tmp_cover_name = os.path.join(os.path.dirname(tmp_file_name), 'cover.jpg') + tmp_cover_name = tmp_file_path + '.jpg' if extension in NO_JPEG_EXTENSIONS: if use_IM: with Image(blob=img) as imgc: diff --git a/cps/uploader.py b/cps/uploader.py index 79016e49..b991049b 100644 --- a/cps/uploader.py +++ b/cps/uploader.py @@ -237,7 +237,7 @@ def pdf_preview(tmp_file_path, tmp_dir): if use_generic_pdf_cover: return None try: - cover_file_name = os.path.join(os.path.dirname(tmp_file_path), "cover.jpg") + cover_file_name = tmp_file_path + ".jpg" with Image() as img: img.options["pdf:use-cropbox"] = "true" img.read(filename=tmp_file_path + '[0]', resolution=150) @@ -245,7 +245,7 @@ def pdf_preview(tmp_file_path, tmp_dir): if img.alpha_channel: img.alpha_channel = 'remove' img.background_color = Color('white') - img.save(filename=os.path.join(tmp_dir, cover_file_name)) + img.save(filename=cover_file_name) return cover_file_name except PolicyError as ex: log.warning('Pdf extraction forbidden by Imagemagick policy: %s', ex) diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index ec9f87ce..039f2e7e 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2024-11-27 21:25:16

+

Start Time: 2024-11-29 20:17:45

-

Stop Time: 2024-11-28 03:32:55

+

Stop Time: 2024-11-30 03:36:53

-

Duration: 5h 7 min

+

Duration: 6h 12 min

@@ -462,11 +462,11 @@ - + TestCli 13 - 11 - 2 + 13 + 0 0 0 @@ -566,31 +566,11 @@ - +
TestCli - test_no_database
- -
- FAIL -
- - - - + PASS @@ -604,42 +584,22 @@ AssertionError: False is not true - +
TestCli - test_writeonly_static_files
- -
- FAIL -
- - - - + PASS - + TestCliGdrivedb 4 - 3 + 4 + 0 0 - 1 0 Detail @@ -675,33 +635,11 @@ AssertionError: False is not true - +
TestCliGdrivedb - test_no_database
- -
- ERROR -
- - - - + PASS @@ -914,1276 +852,79 @@ AttributeError: 'bool' object has no attribute 'click' - + TestEbookConvertCalibreGDrive - 14 + 7 + 7 + 0 0 - 1 - 13 0 - Detail + Detail - +
TestEbookConvertCalibreGDrive - test_convert_email
- -
- ERROR -
- - - - + PASS - - -
TestEbookConvertCalibreGDrive - test_convert_email
- - -
- ERROR -
- - - - - - - - - +
TestEbookConvertCalibreGDrive - test_convert_failed_and_email
- -
- ERROR -
- - - - + PASS - - -
TestEbookConvertCalibreGDrive - test_convert_failed_and_email
- - -
- ERROR -
- - - - - - - - - +
TestEbookConvertCalibreGDrive - test_convert_only
- -
- ERROR -
- - - - + PASS - - -
TestEbookConvertCalibreGDrive - test_convert_only
- - -
- ERROR -
- - - - - - - - - +
TestEbookConvertCalibreGDrive - test_convert_parameter
- -
- ERROR -
- - - - + PASS - - -
TestEbookConvertCalibreGDrive - test_convert_parameter
- - -
- ERROR -
- - - - - - - - - +
TestEbookConvertCalibreGDrive - test_email_failed
- -
- ERROR -
- - - - + PASS - - -
TestEbookConvertCalibreGDrive - test_email_failed
- - -
- ERROR -
- - - - - - - - - +
TestEbookConvertCalibreGDrive - test_email_only
- -
- ERROR -
- - - - + PASS - - -
TestEbookConvertCalibreGDrive - test_email_only
- - -
- ERROR -
- - - - - - - - - +
TestEbookConvertCalibreGDrive - test_thumbnail_cache
- -
- FAIL -
- - - - - - - - - - -
TestEbookConvertCalibreGDrive - test_thumbnail_cache
- - -
- ERROR -
- - - - + PASS @@ -2240,606 +981,57 @@ urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', p - + TestEbookConvertGDriveKepubify - 6 + 3 + 3 0 0 - 6 0 - Detail + Detail - +
TestEbookConvertGDriveKepubify - test_convert_deactivate
- -
- ERROR -
- - - - + PASS - - -
TestEbookConvertGDriveKepubify - test_convert_deactivate
- - -
- ERROR -
- - - - - - - - - +
TestEbookConvertGDriveKepubify - test_convert_only
- -
- ERROR -
- - - - + PASS - - -
TestEbookConvertGDriveKepubify - test_convert_only
- - -
- ERROR -
- - - - - - - - - +
TestEbookConvertGDriveKepubify - test_convert_wrong_excecutable
- -
- ERROR -
- - - - - - - - - - -
TestEbookConvertGDriveKepubify - test_convert_wrong_excecutable
- - -
- ERROR -
- - - - + PASS - + TestEditAdditionalBooks - 20 - 15 + 18 + 17 0 - 3 - 2 + 0 + 1 - Detail + Detail @@ -2980,62 +1172,7 @@ urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='localhost', p - - -
TestEditAdditionalBooks - test_writeonly_calibre_database
- - -
- SKIP -
- - - - - - - - - - -
TestEditAdditionalBooks - test_writeonly_path
- - -
- ERROR -
- - - - - - - - - +
TestEditAdditionalBooks - test_xss_author_edit
@@ -3044,248 +1181,20 @@ AttributeError: 'bool' object has no attribute 'click' - +
TestEditAdditionalBooks - test_xss_comment_edit
- -
- ERROR -
- - - - + PASS - +
TestEditAdditionalBooks - test_xss_custom_comment_edit
- -
- ERROR -
- - - - - - - - - - - _ErrorHolder - 3 - 0 - 0 - 3 - 0 - - Detail - - - - - - - -
tearDownClass (test_edit_additional_books)
- - -
- ERROR -
- - - - - - - - - - -
tearDownClass (test_edit_books_author_gdrive)
- - -
- ERROR -
- - - - - - - - - - -
tearDownClass (test_readonly_db)
- - -
- ERROR -
- - - - + PASS @@ -3299,13 +1208,13 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 0 1 - Detail + Detail - +
TestEditBooks - test_download_book
@@ -3314,7 +1223,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_author
@@ -3323,7 +1232,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_category
@@ -3332,7 +1241,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_comments
@@ -3341,7 +1250,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_custom_bool
@@ -3350,7 +1259,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_custom_categories
@@ -3359,7 +1268,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_custom_comment
@@ -3368,7 +1277,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_custom_date
@@ -3377,7 +1286,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_custom_float
@@ -3386,7 +1295,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_custom_int
@@ -3395,7 +1304,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_custom_rating
@@ -3404,7 +1313,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_custom_single_select
@@ -3413,7 +1322,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_custom_text
@@ -3422,7 +1331,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_language
@@ -3431,7 +1340,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_publisher
@@ -3440,7 +1349,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_publishing_date
@@ -3449,7 +1358,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_rating
@@ -3458,7 +1367,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_series
@@ -3467,7 +1376,7 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_edit_title
@@ -3476,19 +1385,19 @@ dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16 - +
TestEditBooks - test_rename_upper_lowercase
- SKIP + SKIP
-