diff --git a/cps/__init__.py b/cps/__init__.py index 8b0b86ad..f4f8dbf2 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -64,7 +64,8 @@ mimetypes.add_type('application/x-mobi8-ebook', '.azw3') mimetypes.add_type('application/x-cbr', '.cbr') mimetypes.add_type('application/x-cbz', '.cbz') mimetypes.add_type('application/x-cbt', '.cbt') -mimetypes.add_type('image/vnd.djvu', '.djvu') +mimetypes.add_type('application/x-cb7', '.cb7') +mimetypes.add_type('image/vnd.djv', '.djv') mimetypes.add_type('application/mpeg', '.mpeg') mimetypes.add_type('application/mpeg', '.mp3') mimetypes.add_type('application/mp4', '.m4a') diff --git a/cps/comic.py b/cps/comic.py index 13774756..4242bb2f 100644 --- a/cps/comic.py +++ b/cps/comic.py @@ -52,6 +52,12 @@ except (ImportError, LookupError) as e: except (ImportError, SyntaxError) as e: log.debug('Cannot import rarfile, extracting cover files from rar files will not work: %s', e) use_rarfile = False + try: + import py7zr + use_7zip = True + except (ImportError, SyntaxError) as e: + log.debug('Cannot import py7zr, extracting cover files from CB7 files will not work: %s', e) + use_7zip = False use_comic_meta = False @@ -84,10 +90,22 @@ 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.debug('Rarfile failed with error: {}'.format(ex)) + log.error('Rarfile failed with error: {}'.format(ex)) + elif original_file_extension.upper() == '.CB7' and use_7zip: + cf = py7zr.SevenZipFile(tmp_file_name) + for name in cf.getnames(): + ext = os.path.splitext(name) + if len(ext) > 1: + extension = ext[1].lower() + if extension in cover.COVER_EXTENSIONS: + try: + cover_data = cf.read(name)[name].read() + except (py7zr.Bad7zFile, OSError) as ex: + log.error('7Zip file failed with error: {}'.format(ex)) + break return cover_data, extension diff --git a/cps/constants.py b/cps/constants.py index 069630b6..c7d3a6ce 100644 --- a/cps/constants.py +++ b/cps/constants.py @@ -147,7 +147,7 @@ EXTENSIONS_CONVERT_FROM = ['pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'txt', 'htmlz', 'rtf', 'odt', 'cbz', 'cbr'] EXTENSIONS_CONVERT_TO = ['pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt'] -EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'kepub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'djv', +EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'kepub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'cb7', 'djvu', 'djv', 'prc', 'doc', 'docx', 'fb2', 'html', 'rtf', 'lit', 'odt', 'mp3', 'mp4', 'ogg', 'opus', 'wav', 'flac', 'm4a', 'm4b'} diff --git a/cps/editbooks.py b/cps/editbooks.py index 5a15740c..f52f08aa 100755 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -1215,7 +1215,7 @@ def upload_single_file(file_request, book, book_id): return uploader.process( saved_filename, *os.path.splitext(requested_file.filename), - rarExecutable=config.config_rarfile_location) + rar_executable=config.config_rarfile_location) return None diff --git a/cps/epub.py b/cps/epub.py index c22bad7b..50adba59 100644 --- a/cps/epub.py +++ b/cps/epub.py @@ -21,10 +21,11 @@ import zipfile from lxml import etree from . import isoLanguages, cover -from . import config +from . import config, logger from .helper import split_authors from .constants import BookMeta +log = logger.create() def _extract_cover(zip_file, cover_file, cover_path, tmp_file_name): if cover_file is None: @@ -49,15 +50,20 @@ def get_epub_layout(book, book_data): } file_path = os.path.normpath(os.path.join(config.config_calibre_dir, book.path, book_data.name + "." + book_data.format.lower())) - epubZip = zipfile.ZipFile(file_path) - txt = epubZip.read('META-INF/container.xml') - tree = etree.fromstring(txt) - cfname = tree.xpath('n:rootfiles/n:rootfile/@full-path', namespaces=ns)[0] - cf = epubZip.read(cfname) - tree = etree.fromstring(cf) - p = tree.xpath('/pkg:package/pkg:metadata', namespaces=ns)[0] + try: + epubZip = zipfile.ZipFile(file_path) + txt = epubZip.read('META-INF/container.xml') + tree = etree.fromstring(txt) + cfname = tree.xpath('n:rootfiles/n:rootfile/@full-path', namespaces=ns)[0] + cf = epubZip.read(cfname) - layout = p.xpath('pkg:meta[@property="rendition:layout"]/text()', namespaces=ns) + tree = etree.fromstring(cf) + p = tree.xpath('/pkg:package/pkg:metadata', namespaces=ns)[0] + + layout = p.xpath('pkg:meta[@property="rendition:layout"]/text()', namespaces=ns) + except (etree.XMLSyntaxError, KeyError, IndexError) as e: + log.error("Could not parse epub metadata of book {} during kobo sync: {}".format(book.id, e)) + layout = [] if len(layout) == 0: return None diff --git a/cps/helper.py b/cps/helper.py index 92bcb2ad..0c526d01 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -732,28 +732,27 @@ def delete_book(book, calibrepath, book_format): return delete_book_file(book, calibrepath, book_format) -def get_cover_on_failure(use_generic_cover): - if use_generic_cover: - try: - return send_from_directory(_STATIC_DIR, "generic_cover.jpg") - except PermissionError: - log.error("No permission to access generic_cover.jpg file.") - abort(403) - abort(404) +def get_cover_on_failure(): + try: + return send_from_directory(_STATIC_DIR, "generic_cover.jpg") + except PermissionError: + log.error("No permission to access generic_cover.jpg file.") + abort(403) def get_book_cover(book_id, resolution=None): book = calibre_db.get_filtered_book(book_id, allow_show_archived=True) - return get_book_cover_internal(book, use_generic_cover_on_failure=True, resolution=resolution) + return get_book_cover_internal(book, resolution=resolution) -# Called only by kobo sync -> cover not found should be answered with 404 and not with default cover def get_book_cover_with_uuid(book_uuid, resolution=None): book = calibre_db.get_book_by_uuid(book_uuid) - return get_book_cover_internal(book, use_generic_cover_on_failure=False, resolution=resolution) + if not book: + return # allows kobo.HandleCoverImageRequest to proxy request + return get_book_cover_internal(book, resolution=resolution) -def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=None): +def get_book_cover_internal(book, resolution=None): if book and book.has_cover: # Send the book cover thumbnail if it exists in cache @@ -769,16 +768,16 @@ def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=None) if config.config_use_google_drive: try: if not gd.is_gdrive_ready(): - return get_cover_on_failure(use_generic_cover_on_failure) + return get_cover_on_failure() path = gd.get_cover_via_gdrive(book.path) if path: return redirect(path) else: log.error('{}/cover.jpg not found on Google Drive'.format(book.path)) - return get_cover_on_failure(use_generic_cover_on_failure) + return get_cover_on_failure() except Exception as ex: log.error_or_exception(ex) - return get_cover_on_failure(use_generic_cover_on_failure) + return get_cover_on_failure() # Send the book cover from the Calibre directory else: @@ -786,9 +785,9 @@ def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=None) if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")): return send_from_directory(cover_file_path, "cover.jpg") else: - return get_cover_on_failure(use_generic_cover_on_failure) + return get_cover_on_failure() else: - return get_cover_on_failure(use_generic_cover_on_failure) + return get_cover_on_failure() def get_book_cover_thumbnail(book, resolution): @@ -811,7 +810,7 @@ def get_series_thumbnail_on_failure(series_id, resolution): .filter(db.Books.has_cover == 1) \ .first() - return get_book_cover_internal(book, use_generic_cover_on_failure=True, resolution=resolution) + return get_book_cover_internal(book, resolution=resolution) def get_series_cover_thumbnail(series_id, resolution=None): diff --git a/cps/kobo.py b/cps/kobo.py index a8cdf25c..47cc4bda 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -930,20 +930,26 @@ def get_current_bookmark_response(current_bookmark): @kobo.route("//////image.jpg") @requires_kobo_auth def HandleCoverImageRequest(book_uuid, width, height, Quality, isGreyscale): - book_cover = helper.get_book_cover_with_uuid(book_uuid, resolution=COVER_THUMBNAIL_SMALL) - if not book_cover: - if config.config_kobo_proxy: - log.debug("Cover for unknown book: %s proxied to kobo" % book_uuid) - return redirect(KOBO_IMAGEHOST_URL + - "/{book_uuid}/{width}/{height}/false/image.jpg".format(book_uuid=book_uuid, - width=width, - height=height), 307) - else: - log.debug("Cover for unknown book: %s requested" % book_uuid) - # additional proxy request make no sense, -> direct return - return make_response(jsonify({})) - log.debug("Cover request received for book %s" % book_uuid) - return book_cover + try: + resolution = None if int(height) > 1000 else COVER_THUMBNAIL_SMALL + except ValueError: + log.error("Requested height %s of book %s is invalid" % (book_uuid, height)) + resolution = COVER_THUMBNAIL_SMALL + book_cover = helper.get_book_cover_with_uuid(book_uuid, resolution=resolution) + if book_cover: + log.debug("Serving local cover image of book %s" % book_uuid) + return book_cover + + if not config.config_kobo_proxy: + log.debug("Returning 404 for cover image of unknown book %s" % book_uuid) + # additional proxy request make no sense, -> direct return + return abort(404) + + log.debug("Redirecting request for cover image of unknown book %s to Kobo" % book_uuid) + return redirect(KOBO_IMAGEHOST_URL + + "/{book_uuid}/{width}/{height}/false/image.jpg".format(book_uuid=book_uuid, + width=width, + height=height), 307) @kobo.route("") @@ -1041,7 +1047,7 @@ def make_calibre_web_auth_response(): "RefreshToken": RefreshToken, "TokenType": "Bearer", "TrackingId": str(uuid.uuid4()), - "UserKey": content['UserKey'], + "UserKey": content.get('UserKey',""), } ) ) diff --git a/cps/metadata_provider/amazon.py b/cps/metadata_provider/amazon.py index a83747e6..30291a3f 100644 --- a/cps/metadata_provider/amazon.py +++ b/cps/metadata_provider/amazon.py @@ -98,7 +98,7 @@ class Amazon(Metadata): try: match.authors = [next( filter(lambda i: i != " " and i != "\n" and not i.startswith("{"), - x.findAll(text=True))).strip() + x.findAll(string=True))).strip() for x in soup2.findAll("span", attrs={"class": "author"})] except (AttributeError, TypeError, StopIteration): match.authors = "" diff --git a/cps/metadata_provider/lubimyczytac.py b/cps/metadata_provider/lubimyczytac.py index e4abe9db..4644cad9 100644 --- a/cps/metadata_provider/lubimyczytac.py +++ b/cps/metadata_provider/lubimyczytac.py @@ -102,7 +102,7 @@ class LubimyCzytac(Metadata): PUBLISH_DATE = "//dt[contains(@title,'Data pierwszego wydania" FIRST_PUBLISH_DATE = f"{DETAILS}{PUBLISH_DATE} oryginalnego')]{SIBLINGS}[1]/text()" FIRST_PUBLISH_DATE_PL = f"{DETAILS}{PUBLISH_DATE} polskiego')]{SIBLINGS}[1]/text()" - TAGS = "//nav[@aria-label='breadcrumb']//a[contains(@href,'/ksiazki/k/')]/text()" + TAGS = "//nav[@aria-label='breadcrumbs']//a[contains(@href,'/ksiazki/k/')]/span/text()" RATING = "//meta[@property='books:rating:value']/@content" COVER = "//meta[@property='og:image']/@content" diff --git a/cps/opds.py b/cps/opds.py index 074a9b73..4067712f 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -21,9 +21,10 @@ # along with this program. If not, see . import datetime +import json from urllib.parse import unquote_plus -from flask import Blueprint, request, render_template, make_response, abort +from flask import Blueprint, request, render_template, make_response, abort, Response from flask_login import current_user from flask_babel import get_locale from flask_babel import gettext as _ @@ -412,6 +413,17 @@ def get_metadata_calibre_companion(uuid, library): return "" +@opds.route("/opds/stats") +@requires_basic_auth_if_no_ano +def get_database_stats(): + stat = dict() + stat['books'] = calibre_db.session.query(db.Books).count() + stat['authors'] = calibre_db.session.query(db.Authors).count() + stat['categories'] = calibre_db.session.query(db.Tags).count() + stat['series'] = calibre_db.session.query(db.Series).count() + return Response(json.dumps(stat), mimetype="application/json") + + @opds.route("/opds/thumb_240_240/") @opds.route("/opds/cover_240_240/") @opds.route("/opds/cover_90_90/") diff --git a/cps/static/css/caliBlur.css b/cps/static/css/caliBlur.css index dbbea88e..cf743761 100644 --- a/cps/static/css/caliBlur.css +++ b/cps/static/css/caliBlur.css @@ -7279,6 +7279,11 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div. float: right } + body.blur #main-nav + #scnd-nav .create-shelf, body.blur #main-nav + .col-sm-2 #scnd-nav .create-shelf { + float: none; + margin: 5px 0 10px -10px; + } + #main-nav + #scnd-nav .nav-head.hidden-xs { display: list-item !important; width: 225px diff --git a/cps/static/js/details.js b/cps/static/js/details.js index 24b98437..b8b8b21e 100644 --- a/cps/static/js/details.js +++ b/cps/static/js/details.js @@ -40,6 +40,7 @@ $(".sendbtn-form").click(function() { $.ajax({ method: 'post', url: $(this).data('href'), + data: {csrf_token: $("input[name='csrf_token']").val()}, success: function (data) { handleResponse(data) } diff --git a/cps/templates/detail.html b/cps/templates/detail.html index 7a1b3376..30430663 100755 --- a/cps/templates/detail.html +++ b/cps/templates/detail.html @@ -43,30 +43,30 @@ {% endif %} {% endif %} - {% endif %} - {% if current_user.kindle_mail and entry.email_share_list %} - - {% if entry.email_share_list.__len__() == 1 %} -
- -
- {% else %} -
- - -
+ {% if current_user.kindle_mail and entry.email_share_list %} + + {% if entry.email_share_list.__len__() == 1 %} +
+ +
+ {% else %} +
+ + +
+ {% endif %} {% endif %} {% endif %} {% if entry.reader_list and current_user.role_viewer() %} diff --git a/cps/templates/grid.html b/cps/templates/grid.html index 1905d52d..3fa6958f 100644 --- a/cps/templates/grid.html +++ b/cps/templates/grid.html @@ -28,7 +28,7 @@
- {{ image.series(entry[0].series[0], alt=entry[0].series[0].name|shortentitle) }} + {{ image.book_cover(entry[0])}} {{entry.count}} diff --git a/cps/uploader.py b/cps/uploader.py index 42b776aa..23dfc4a6 100644 --- a/cps/uploader.py +++ b/cps/uploader.py @@ -79,7 +79,7 @@ def process(tmp_file_path, original_file_name, original_file_extension, rar_exec meta = epub.get_epub_info(tmp_file_path, original_file_name, original_file_extension) elif ".FB2" == extension_upper and use_fb2_meta is True: meta = fb2.get_fb2_info(tmp_file_path, original_file_extension) - elif extension_upper in ['.CBZ', '.CBT', '.CBR']: + elif extension_upper in ['.CBZ', '.CBT', '.CBR', ".CB7"]: meta = comic.get_comic_info(tmp_file_path, original_file_name, original_file_extension, @@ -88,7 +88,7 @@ def process(tmp_file_path, original_file_name, original_file_extension, rar_exec log.warning('cannot parse metadata, using default: %s', ex) if not meta.title.strip(): - meta = original_file_name + meta = meta._replace(title=original_file_name) if not meta.author.strip() or meta.author.lower() == 'unknown': meta = meta._replace(author=_('Unknown')) return meta diff --git a/cps/web.py b/cps/web.py index 51ff32b3..9f94d1b4 100755 --- a/cps/web.py +++ b/cps/web.py @@ -1002,13 +1002,21 @@ def series_list(): if no_series_count: entries.append([db.Category(_("None"), "-1"), no_series_count]) entries = sorted(entries, key=lambda x: x[0].name.lower(), reverse=not order_no) - return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=char_list, - title=_("Series"), page="serieslist", data="series", order=order_no) + return render_title_template('list.html', + entries=entries, + folder='web.books_list', + charlist=char_list, + title=_("Series"), + page="serieslist", + data="series", order=order_no) else: - entries = calibre_db.session.query(db.Books, func.count('books_series_link').label('count'), - func.max(db.Books.series_index), db.Books.id) \ - .join(db.books_series_link).join(db.Series).filter(calibre_db.common_filters()) \ - .group_by(text('books_series_link.series')).order_by(order).all() + entries = (calibre_db.session.query(db.Books, func.count('books_series_link').label('count'), + func.max(db.Books.series_index), db.Books.id) + .join(db.books_series_link).join(db.Series).filter(calibre_db.common_filters()) + .group_by(text('books_series_link.series')) + .having(func.max(db.Books.series_index)) + .order_by(order) + .all()) return render_title_template('grid.html', entries=entries, folder='web.books_list', charlist=char_list, title=_("Series"), page="serieslist", data="series", bodyClass="grid-view", order=order_no) diff --git a/optional-requirements.txt b/optional-requirements.txt index ff02d04f..d34d09aa 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -35,6 +35,7 @@ html2text>=2020.1.16,<2022.1.1 python-dateutil>=2.1,<2.9.0 beautifulsoup4>=4.0.1,<4.12.0 faust-cchardet>=2.1.18 +py7zr>=0.15.0,<0.21.0 # Comics natsort>=2.2.0,<8.4.0 diff --git a/setup.cfg b/setup.cfg index b445eb5e..1f617648 100644 --- a/setup.cfg +++ b/setup.cfg @@ -92,6 +92,7 @@ metadata = python-dateutil>=2.1,<2.9.0 beautifulsoup4>=4.0.1,<4.12.0 faust-cchardet>=2.1.18 + py7zr>=0.15.0,<0.21.0 comics = natsort>=2.2.0,<8.4.0 comicapi>=2.2.0,<3.3.0 diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 309c9a25..66d4df88 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2023-07-26 21:47:14

+

Start Time: 2023-08-23 21:16:31

-

Stop Time: 2023-07-27 04:10:01

+

Stop Time: 2023-08-24 03:51:45

-

Duration: 5h 21 min

+

Duration: 5h 34 min

@@ -234,12 +234,12 @@ - + TestBackupMetadata 22 - 20 - 1 - 1 + 22 + 0 + 0 0 Detail @@ -293,32 +293,11 @@ - +
TestBackupMetadata - test_backup_change_book_publisher
- -
- FAIL -
- - - - + PASS @@ -395,33 +374,11 @@ AssertionError: '' != 'Lo,执|1u' - +
TestBackupMetadata - test_backup_change_custom_categories
- -
- ERROR -
- - - - + PASS @@ -1015,11 +972,11 @@ TypeError: 'NoneType' object is not iterable - + TestEbookConvertGDriveKepubify 3 - 2 - 1 + 3 + 0 0 0 @@ -1038,33 +995,11 @@ TypeError: 'NoneType' object is not iterable - +
TestEbookConvertGDriveKepubify - test_convert_only
- -
- FAIL -
- - - - + PASS @@ -1079,15 +1014,15 @@ AssertionError: 'Started' != 'Finished' - + TestEditAdditionalBooks + 20 17 - 16 - 0 0 1 + 2 - Detail + Detail @@ -1201,7 +1136,36 @@ AssertionError: 'Started' != 'Finished' - + + +
TestEditAdditionalBooks - test_upload_metadata_cb7
+ + +
+ ERROR +
+ + + + + + + + +
TestEditAdditionalBooks - test_upload_metadata_cbr
@@ -1210,7 +1174,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditAdditionalBooks - test_upload_metadata_cbt
@@ -1219,7 +1183,42 @@ AssertionError: 'Started' != 'Finished' - + + +
TestEditAdditionalBooks - test_writeonly_calibre_database
+ + +
+ SKIP +
+ + + + + + + + + + +
TestEditAdditionalBooks - test_writeonly_path
+ + PASS + + + + +
TestEditAdditionalBooks - test_xss_author_edit
@@ -1228,7 +1227,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditAdditionalBooks - test_xss_comment_edit
@@ -1237,7 +1236,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditAdditionalBooks - test_xss_custom_comment_edit
@@ -1247,15 +1246,15 @@ AssertionError: 'Started' != 'Finished' - + TestEditBooks - 37 - 35 - 0 + 38 + 34 0 2 + 2 - Detail + Detail @@ -1538,7 +1537,36 @@ AssertionError: 'Started' != 'Finished' - + + +
TestEditBooks - test_upload_book_cb7
+ + +
+ ERROR +
+ + + + + + + + +
TestEditBooks - test_upload_book_cbr
@@ -1547,7 +1575,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_cbt
@@ -1556,7 +1584,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_cbz
@@ -1565,7 +1593,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_epub
@@ -1574,7 +1602,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_fb2
@@ -1583,7 +1611,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_lit
@@ -1592,7 +1620,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_mobi
@@ -1601,7 +1629,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_book_pdf
@@ -1610,7 +1638,7 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_cbz_coverformats
@@ -1619,11 +1647,31 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooks - test_upload_cover_hdd
- PASS + +
+ ERROR +
+ + + + @@ -1944,11 +1992,11 @@ AssertionError: 'Started' != 'Finished' - + TestLoadMetadata 1 - 1 0 + 1 0 0 @@ -1958,21 +2006,47 @@ AssertionError: 'Started' != 'Finished' - +
TestLoadMetadata - test_load_metadata
- PASS + +
+ FAIL +
+ + + + - + TestEditBooksOnGdrive 18 - 17 - 1 + 18 + 0 0 0 @@ -2135,31 +2209,11 @@ AssertionError: 'Started' != 'Finished' - +
TestEditBooksOnGdrive - test_watch_metadata
- -
- FAIL -
- - - - + PASS @@ -3606,11 +3660,11 @@ AssertionError: False is not true - + TestReader 6 - 5 - 1 + 6 + 0 0 0 @@ -3656,37 +3710,11 @@ AssertionError: False is not true - +
TestReader - test_sound_listener
- -
- FAIL -
- - - - + PASS @@ -4054,11 +4082,11 @@ AssertionError: '0:03' != '0:02' - + TestThumbnails 8 - 6 - 1 + 7 + 0 0 1 @@ -4095,31 +4123,11 @@ AssertionError: '0:03' != '0:02' - +
TestThumbnails - test_cover_change_on_upload_new_cover
- -
- FAIL -
- - - - + PASS @@ -5229,11 +5237,11 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 Total - 457 - 443 - 5 + 461 + 448 1 - 8 + 3 + 9   @@ -5261,13 +5269,13 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 Platform - Linux 6.2.0-25-generic #25~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC Wed Jun 28 09:55:23 UTC 2 x86_64 x86_64 + Linux 6.2.0-26-generic #26~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Jul 13 16:27:29 UTC 2 x86_64 x86_64 Basic Python - 3.10.6 + 3.10.12 Basic @@ -5279,7 +5287,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 APScheduler - 3.10.1 + 3.10.4 Basic @@ -5297,7 +5305,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 Flask - 2.3.2 + 2.3.3 Basic @@ -5405,13 +5413,13 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 Werkzeug - 2.3.6 + 2.3.7 Basic google-api-python-client - 2.95.0 + 2.97.0 TestBackupMetadataGdrive @@ -5429,7 +5437,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestBackupMetadataGdrive @@ -5441,7 +5449,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 google-api-python-client - 2.95.0 + 2.97.0 TestCliGdrivedb @@ -5459,7 +5467,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestCliGdrivedb @@ -5471,7 +5479,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 google-api-python-client - 2.95.0 + 2.97.0 TestEbookConvertCalibreGDrive @@ -5489,7 +5497,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestEbookConvertCalibreGDrive @@ -5501,7 +5509,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 google-api-python-client - 2.95.0 + 2.97.0 TestEbookConvertGDriveKepubify @@ -5519,7 +5527,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestEbookConvertGDriveKepubify @@ -5535,15 +5543,27 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 TestEditAdditionalBooks + + py7zr + 0.20.6 + TestEditAdditionalBooks + + rarfile 4.0 TestEditAdditionalBooks + + py7zr + 0.20.6 + TestEditBooks + + google-api-python-client - 2.95.0 + 2.97.0 TestEditAuthorsGdrive @@ -5561,7 +5581,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestEditAuthorsGdrive @@ -5579,7 +5599,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 google-api-python-client - 2.95.0 + 2.97.0 TestEditBooksOnGdrive @@ -5597,7 +5617,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestEditBooksOnGdrive @@ -5621,7 +5641,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 google-api-python-client - 2.95.0 + 2.97.0 TestSetupGdrive @@ -5639,7 +5659,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 PyDrive2 - 1.16.1 + 1.17.0 TestSetupGdrive @@ -5663,13 +5683,13 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 jsonschema - 4.18.4 + 4.19.0 TestKoboSync jsonschema - 4.18.4 + 4.19.0 TestKoboSyncBig @@ -5681,7 +5701,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03 jsonschema - 4.18.4 + 4.19.0 TestLdapLogin @@ -5711,7 +5731,7 @@ AssertionError: 0.0288805190529425 not greater than or equal to 0.03