From e7c2a85fd49516cc822a1e17da96c9a2e29be829 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 22 Dec 2024 13:11:59 +0100 Subject: [PATCH 01/16] Fix for uploading azw files (#3263) --- cps/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cps/__init__.py b/cps/__init__.py index eb94fcfba..526d431f8 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -58,8 +58,8 @@ mimetypes.add_type('application/epub+zip', '.kepub') mimetypes.add_type('application/fb2+zip', '.fb2') mimetypes.add_type('application/x-mobipocket-ebook', '.mobi') mimetypes.add_type('application/octet-stream', '.prc') -mimetypes.add_type('application/vnd.amazon.ebook', '.azw') -mimetypes.add_type('application/x-mobi8-ebook', '.azw3') +mimetypes.add_type('application/x-mobipocket-ebook', '.azw') +mimetypes.add_type('application/x-mobipocket-ebook', '.azw3') mimetypes.add_type('application/x-cbr', '.cbr') mimetypes.add_type('application/x-cbz', '.cbz') mimetypes.add_type('application/x-tar', '.cbt') From f5048ee1a844ffd9c72af0f08de78628413352e9 Mon Sep 17 00:00:00 2001 From: James McNee Date: Sat, 4 Jan 2025 11:35:32 +0000 Subject: [PATCH 02/16] Fix UI bug when book conversion enabled on mobile On mobile, when book conversion is enabled and using the cabliBlur theme, the upload menu appears behind the description text area making it difficult (though not impossible with some careful guesswork around where the buttons should be). This commit fixes the styling of this section so that it no longer appears behind the description text area. --- cps/static/css/caliBlur.css | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cps/static/css/caliBlur.css b/cps/static/css/caliBlur.css index dc42396a6..c5e96bf43 100644 --- a/cps/static/css/caliBlur.css +++ b/cps/static/css/caliBlur.css @@ -7153,12 +7153,11 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div. } body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3, body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3 { - max-width: 130px; - width: 130px; - height: 180px; - margin: 0; + position: relative; + max-width: unset; + width: 100%; + height: unset; padding: 15px; - position: absolute } body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9, body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 { @@ -7167,10 +7166,6 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div. width: 100% } - body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 > .form-group:nth-child(1), body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 > .form-group:nth-child(2), body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 > .form-group:nth-child(1), body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > form > div.col-sm-9 > .form-group:nth-child(2) { - padding-left: 120px - } - #deleteButton, body.editbook > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3 > div.text-center > #delete, body.upload > div.container-fluid > div.row-fluid > div.col-sm-10 > div.col-sm-3 > div.text-center > #delete { top: 48px; height: 42px From e4f1ce9c5aff2a047a33d120f6971b664525c828 Mon Sep 17 00:00:00 2001 From: Wouter Koch Date: Sun, 5 Jan 2025 01:49:46 +0100 Subject: [PATCH 03/16] Transform color space to srgb instead of rgb to prevent dark images --- cps/cover.py | 2 +- cps/helper.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cps/cover.py b/cps/cover.py index 89fa4770f..6c42d73e8 100644 --- a/cps/cover.py +++ b/cps/cover.py @@ -36,7 +36,7 @@ def cover_processing(tmp_file_path, img, extension): if use_IM: with Image(blob=img) as imgc: imgc.format = 'jpeg' - imgc.transform_colorspace('rgb') + imgc.transform_colorspace('srgb') imgc.save(filename=tmp_cover_name) return tmp_cover_name else: diff --git a/cps/helper.py b/cps/helper.py index 51eedb006..9a5f0104b 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -905,7 +905,7 @@ def save_cover(img, book_path): else: imgc = Image(blob=io.BytesIO(img.content)) imgc.format = 'jpeg' - imgc.transform_colorspace("rgb") + imgc.transform_colorspace("srgb") img = imgc except (BlobError, MissingDelegateError): log.error("Invalid cover file content") From 4512ab4524d297f72d4b6b0d737a16f5bfa92e92 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 6 Jan 2025 14:54:09 +0100 Subject: [PATCH 04/16] Renamed pdf reader javascript files (#3277) --- cps/static/js/libs/{pdf.mjs => pdf.js} | 4 ++-- cps/static/js/libs/{pdf.worker.mjs => pdf.worker.js} | 0 cps/static/js/libs/{viewer.mjs => viewer.js} | 2 +- cps/templates/readpdf.html | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) rename cps/static/js/libs/{pdf.mjs => pdf.js} (99%) rename cps/static/js/libs/{pdf.worker.mjs => pdf.worker.js} (100%) rename cps/static/js/libs/{viewer.mjs => viewer.js} (99%) diff --git a/cps/static/js/libs/pdf.mjs b/cps/static/js/libs/pdf.js similarity index 99% rename from cps/static/js/libs/pdf.mjs rename to cps/static/js/libs/pdf.js index 52a97c211..13face720 100644 --- a/cps/static/js/libs/pdf.mjs +++ b/cps/static/js/libs/pdf.js @@ -16667,7 +16667,7 @@ const PDFWorkerUtil = { { if (isNodeJS) { PDFWorkerUtil.isWorkerDisabled = true; - GlobalWorkerOptions.workerSrc ||= "./pdf.worker.mjs"; + GlobalWorkerOptions.workerSrc ||= "./pdf.worker.js"; } PDFWorkerUtil.isSameOrigin = function (baseUrl, otherUrl) { let base; @@ -24745,4 +24745,4 @@ var __webpack_exports__shadow = __webpack_exports__.shadow; var __webpack_exports__version = __webpack_exports__.version; export { __webpack_exports__AbortException as AbortException, __webpack_exports__AnnotationEditorLayer as AnnotationEditorLayer, __webpack_exports__AnnotationEditorParamsType as AnnotationEditorParamsType, __webpack_exports__AnnotationEditorType as AnnotationEditorType, __webpack_exports__AnnotationEditorUIManager as AnnotationEditorUIManager, __webpack_exports__AnnotationLayer as AnnotationLayer, __webpack_exports__AnnotationMode as AnnotationMode, __webpack_exports__CMapCompressionType as CMapCompressionType, __webpack_exports__ColorPicker as ColorPicker, __webpack_exports__DOMSVGFactory as DOMSVGFactory, __webpack_exports__DrawLayer as DrawLayer, __webpack_exports__FeatureTest as FeatureTest, __webpack_exports__GlobalWorkerOptions as GlobalWorkerOptions, __webpack_exports__ImageKind as ImageKind, __webpack_exports__InvalidPDFException as InvalidPDFException, __webpack_exports__MissingPDFException as MissingPDFException, __webpack_exports__OPS as OPS, __webpack_exports__PDFDataRangeTransport as PDFDataRangeTransport, __webpack_exports__PDFDateString as PDFDateString, __webpack_exports__PDFWorker as PDFWorker, __webpack_exports__PasswordResponses as PasswordResponses, __webpack_exports__PermissionFlag as PermissionFlag, __webpack_exports__PixelsPerInch as PixelsPerInch, __webpack_exports__RenderingCancelledException as RenderingCancelledException, __webpack_exports__TextLayer as TextLayer, __webpack_exports__UnexpectedResponseException as UnexpectedResponseException, __webpack_exports__Util as Util, __webpack_exports__VerbosityLevel as VerbosityLevel, __webpack_exports__XfaLayer as XfaLayer, __webpack_exports__build as build, __webpack_exports__createValidAbsoluteUrl as createValidAbsoluteUrl, __webpack_exports__fetchData as fetchData, __webpack_exports__getDocument as getDocument, __webpack_exports__getFilenameFromUrl as getFilenameFromUrl, __webpack_exports__getPdfFilenameFromUrl as getPdfFilenameFromUrl, __webpack_exports__getXfaPageViewport as getXfaPageViewport, __webpack_exports__isDataScheme as isDataScheme, __webpack_exports__isPdfFile as isPdfFile, __webpack_exports__noContextMenu as noContextMenu, __webpack_exports__normalizeUnicode as normalizeUnicode, __webpack_exports__setLayerDimensions as setLayerDimensions, __webpack_exports__shadow as shadow, __webpack_exports__version as version }; -//# sourceMappingURL=pdf.mjs.map \ No newline at end of file +//# sourceMappingURL=pdf.mjs.map diff --git a/cps/static/js/libs/pdf.worker.mjs b/cps/static/js/libs/pdf.worker.js similarity index 100% rename from cps/static/js/libs/pdf.worker.mjs rename to cps/static/js/libs/pdf.worker.js diff --git a/cps/static/js/libs/viewer.mjs b/cps/static/js/libs/viewer.js similarity index 99% rename from cps/static/js/libs/viewer.mjs rename to cps/static/js/libs/viewer.js index ba57b0791..9f797c3ba 100644 --- a/cps/static/js/libs/viewer.mjs +++ b/cps/static/js/libs/viewer.js @@ -828,7 +828,7 @@ const defaultOptions = { kind: OptionKind.WORKER }, workerSrc: { - value: "../build/pdf.worker.mjs", + value: "../build/pdf.worker.js", kind: OptionKind.WORKER } }; diff --git a/cps/templates/readpdf.html b/cps/templates/readpdf.html index c747d02e5..6d9c3f076 100644 --- a/cps/templates/readpdf.html +++ b/cps/templates/readpdf.html @@ -35,7 +35,7 @@ See https://github.com/adobe-type-tools/cmap-resources - + - + From 4ccc2c0aec1ee876c4a42d5ffa4385fae5f1e3a9 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 6 Jan 2025 17:36:17 +0100 Subject: [PATCH 05/16] Handle kindle browser download filename can only contain ASCII characters (#3266) --- cps/helper.py | 13 ++++++++----- cps/web.py | 7 ++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/cps/helper.py b/cps/helper.py index 9a5f0104b..072008341 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -237,7 +237,7 @@ def send_mail(book_id, book_format, convert, ereader_mail, calibrepath, user_id) return _("The requested file could not be read. Maybe wrong permissions?") -def get_valid_filename(value, replace_whitespace=True, chars=128): +def get_valid_filename(value, replace_whitespace=True, chars=128, force_unidecode=False): """ Returns the given string converted to a string that can be used for a clean filename. Limits num characters to 128 max. @@ -245,7 +245,7 @@ def get_valid_filename(value, replace_whitespace=True, chars=128): if value[-1:] == '.': value = value[:-1]+'_' value = value.replace("/", "_").replace(":", "_").strip('\0') - if config.config_unicode_filename: + if config.config_unicode_filename or force_unidecode: value = (unidecode.unidecode(value)) if replace_whitespace: # *+:\"/<>? are replaced by _ @@ -1105,11 +1105,14 @@ def get_download_link(book_id, book_format, client): file_name = book.title if len(book.authors) > 0: file_name = file_name + ' - ' + book.authors[0].name - file_name = get_valid_filename(file_name, replace_whitespace=False) + if client != "kindle": + file_name = get_valid_filename(file_name, replace_whitespace=False, force_unidecode=True) + else: + file_name = quote(get_valid_filename(file_name, replace_whitespace=False)) headers = Headers() headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream") - headers["Content-Disposition"] = "attachment; filename=%s.%s; filename*=UTF-8''%s.%s" % ( - quote(file_name), book_format, quote(file_name), book_format) + headers["Content-Disposition"] = ('attachment; filename="{}.{}"; filename*=UTF-8\'\'{}.{}').format( + file_name, book_format, file_name, book_format) return do_download_file(book, book_format, client, data1, headers) else: log.error("Book id {} not found for downloading".format(book_id)) diff --git a/cps/web.py b/cps/web.py index 12e8454d4..3fa620a89 100644 --- a/cps/web.py +++ b/cps/web.py @@ -1242,7 +1242,12 @@ def serve_book(book_id, book_format, anyname): @login_required_if_no_ano @download_required def download_link(book_id, book_format, anyname): - client = "kobo" if "Kobo" in request.headers.get('User-Agent') else "" + if "kindle" in request.headers.get('User-Agent').lower(): + client = "kindle" + elif "Kobo" in request.headers.get('User-Agent').lower(): + client = "kobo" + else: + client = "" return get_download_link(book_id, book_format, client) From efc7ce31d232d8d094ce390ece5c50c56d466009 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 6 Jan 2025 17:38:25 +0100 Subject: [PATCH 06/16] Fix kindle download handling --- cps/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/helper.py b/cps/helper.py index 072008341..4d4fdeb48 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -1105,7 +1105,7 @@ def get_download_link(book_id, book_format, client): file_name = book.title if len(book.authors) > 0: file_name = file_name + ' - ' + book.authors[0].name - if client != "kindle": + if client == "kindle": file_name = get_valid_filename(file_name, replace_whitespace=False, force_unidecode=True) else: file_name = quote(get_valid_filename(file_name, replace_whitespace=False)) From 90bccb86ef3ea43c683eb46356c71c627db10fe0 Mon Sep 17 00:00:00 2001 From: Robbinsch Date: Sat, 25 Jan 2025 17:52:01 +0100 Subject: [PATCH 07/16] fix: right html in config_db --- cps/templates/config_db.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/templates/config_db.html b/cps/templates/config_db.html index fc0606c96..d1602cf85 100644 --- a/cps/templates/config_db.html +++ b/cps/templates/config_db.html @@ -62,7 +62,7 @@ {% endif %} {% endif %}
-
{{_('Save')}}
+ {{_('Cancel')}}
From 1ee49791f456668709f4cb078011d47df21f58a8 Mon Sep 17 00:00:00 2001 From: Juan Orti Alcaine Date: Sat, 8 Feb 2025 17:29:02 +0100 Subject: [PATCH 08/16] Remove duplicated 'charset' in Content-Type header for Javascript files Javascript files are served over HTTP with an incorrect Content-Type header because the 'charset' option is duplicated: $ curl -I http://127.0.0.1:8083/static/js/table.js HTTP/1.1 200 OK Content-Disposition: inline; filename=table.js Content-Type: text/javascript; charset=UTF-8; charset=utf-8 Content-Length: 35967 Last-Modified: Sat, 16 Nov 2024 06:21:28 GMT Cache-Control: no-cache ETag: "1731738088.0-35967-471731799" Date: Sat, 08 Feb 2025 16:30:42 GMT Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'; font-src 'self' data:; img-src 'self' data:; object-src 'none'; X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Strict-Transport-Security: max-age=31536000 This header is not RFC 1521 compliant and it's breaking some reverse proxy setups that perform compression. See this bug for reference: https://github.com/crocodilestick/Calibre-Web-Automated/issues/217 --- cps/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/__init__.py b/cps/__init__.py index 526d431f8..5d870bf6e 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -77,7 +77,7 @@ mimetypes.add_type('audio/ogg', '.ogg') mimetypes.add_type('application/ogg', '.oga') mimetypes.add_type('text/css', '.css') mimetypes.add_type('application/x-ms-reader', '.lit') -mimetypes.add_type('text/javascript; charset=UTF-8', '.js') +mimetypes.add_type('text/javascript', '.js') mimetypes.add_type('text/rtf', '.rtf') log = logger.create() From 3d9594cfaae9980355514796965eed72ac432579 Mon Sep 17 00:00:00 2001 From: antar37 Date: Thu, 20 Mar 2025 12:43:56 -0600 Subject: [PATCH 09/16] Update mail.py using make_msgid to create a well-formed message-ID. The lack of brackets causes gmail to reject the message-ID --- cps/tasks/mail.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index f332e267b..49cd913fe 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -25,7 +25,7 @@ import mimetypes from io import StringIO from email.message import EmailMessage -from email.utils import formatdate, parseaddr +from email.utils import formatdate, parseaddr, make_msgid from email.generator import Generator from flask_babel import lazy_gettext as N_ @@ -142,7 +142,7 @@ class TaskEmail(CalibreTask): message['To'] = self.recipient message['Subject'] = self.subject message['Date'] = formatdate(localtime=True) - message['Message-Id'] = "{}@{}".format(uuid.uuid4(), self.get_msgid_domain()) + message['Message-ID'] = make_msgid(domain=self.get_msgid_domain()) message.set_content(self.text.encode('UTF-8'), "text", "plain") if self.attachment: data = self._get_attachment(self.filepath, self.attachment) From 46f111cd6ee5ccaf280224d3e1c7e2a45534ba46 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 23 Mar 2025 07:56:14 +0100 Subject: [PATCH 10/16] Better error handling during proxying kobo requests (fix for #3334) --- cps/kobo.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/cps/kobo.py b/cps/kobo.py index 34bf5be4f..182bf21d8 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -106,24 +106,29 @@ def make_request_to_kobo_store(sync_token=None): return store_response -def redirect_or_proxy_request(): +def redirect_or_proxy_request(auth=False): if config.config_kobo_proxy: - if request.method == "GET": - return redirect(get_store_url_for_current_request(), 307) - else: - # The Kobo device turns other request types into GET requests on redirects, - # so we instead proxy to the Kobo store ourselves. - store_response = make_request_to_kobo_store() + try: + if request.method == "GET": + alfa = redirect(get_store_url_for_current_request(), 307) + return alfa + else: + # The Kobo device turns other request types into GET requests on redirects, + # so we instead proxy to the Kobo store ourselves. + store_response = make_request_to_kobo_store() - response_headers = store_response.headers - for header_key in CONNECTION_SPECIFIC_HEADERS: - response_headers.pop(header_key, default=None) + response_headers = store_response.headers + for header_key in CONNECTION_SPECIFIC_HEADERS: + response_headers.pop(header_key, default=None) - return make_response( - store_response.content, store_response.status_code, response_headers.items() - ) - else: - return make_response(jsonify({})) + return make_response( + store_response.content, store_response.status_code, response_headers.items() + ) + except Exception as e: + log.error("Failed to receive or parse response from Kobo's endpoint: {}".format(e)) + if auth: + return make_calibre_web_auth_response() + return make_response(jsonify({})) def convert_to_kobo_timestamp_string(timestamp): @@ -1042,7 +1047,7 @@ def HandleAuthRequest(): log.debug('Kobo Auth request') if config.config_kobo_proxy: try: - return redirect_or_proxy_request() + return redirect_or_proxy_request(auth=True) except Exception: log.error("Failed to receive or parse response from Kobo's auth endpoint. Falling back to un-proxied mode.") return make_calibre_web_auth_response() From 80bcfb12de6a84ecdf216d1cf92924170d1fcc7f Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 23 Mar 2025 08:41:18 +0100 Subject: [PATCH 11/16] Add UniDecodeError to catched errors during kobo sync check layout --- cps/epub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/epub.py b/cps/epub.py index 626e68eea..3947cee65 100644 --- a/cps/epub.py +++ b/cps/epub.py @@ -56,7 +56,7 @@ def get_epub_layout(book, book_data): p = tree.xpath('/pkg:package/pkg:metadata', namespaces=default_ns)[0] layout = p.xpath('pkg:meta[@property="rendition:layout"]/text()', namespaces=default_ns) - except (etree.XMLSyntaxError, KeyError, IndexError, OSError) as e: + except (etree.XMLSyntaxError, KeyError, IndexError, OSError, UnicodeDecodeError) as e: log.error("Could not parse epub metadata of book {} during kobo sync: {}".format(book.id, e)) layout = [] From c289cb5d4ba30d86a4d460f72d429ed0183e6565 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 23 Mar 2025 08:44:55 +0100 Subject: [PATCH 12/16] Fix sync fails with error when "Synchronize Notebooks" enabled --- cps/kobo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/kobo.py b/cps/kobo.py index 182bf21d8..da6320df9 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -960,7 +960,7 @@ def HandleBookDeletionRequest(book_uuid): # TODO: Implement the following routes @csrf.exempt -@kobo.route("/v1/library/", methods=["DELETE", "GET"]) +@kobo.route("/v1/library/", methods=["DELETE", "GET", "POST"]) def HandleUnimplementedRequest(dummy=None): log.debug("Unimplemented Library Request received: %s (request is forwarded to kobo if configured)", request.base_url) From e485f10e9e2fe2e0b7a3f7f88fb9a7be5378af2c Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 23 Mar 2025 09:18:48 +0100 Subject: [PATCH 13/16] List locale alphabetically --- cps/admin.py | 4 ++-- cps/cw_babel.py | 2 +- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 71403e6c3..5451080a1 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -426,7 +426,7 @@ def table_get_locale(): current_locale = get_locale() for loc in locale: ret.append({'value': str(loc), 'text': loc.get_language_name(current_locale)}) - return json.dumps(ret) + return json.dumps(sorted(ret, key=lambda x: x['text'])) @admi.route("/ajax/getdefaultlanguage") @@ -438,7 +438,7 @@ def table_get_default_lang(): ret.append({'value': 'all', 'text': _('Show All')}) for lang in languages: ret.append({'value': lang.lang_code, 'text': lang.name}) - return json.dumps(ret) + return json.dumps(sorted(ret, key=lambda x: x['text'])) @admi.route("/ajax/editlistusers/", methods=['POST']) diff --git a/cps/cw_babel.py b/cps/cw_babel.py index cf043d528..878984f64 100644 --- a/cps/cw_babel.py +++ b/cps/cw_babel.py @@ -34,7 +34,7 @@ def get_user_locale_language(user_language): def get_available_locale(): - return [Locale('en')] + babel.list_translations() + return sorted(babel.list_translations(), key=lambda x: x.display_name.lower()) def get_available_translations(): diff --git a/requirements.txt b/requirements.txt index 2c73c88f1..3f4efbc67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ APScheduler>=3.6.3,<3.11.0 Babel>=1.3,<3.0 -Flask-Babel>=0.11.1,<4.1.0 +Flask-Babel>=3.0.0,<4.1.0 Flask-Principal>=0.3.2,<0.5.1 Flask>=1.0.2,<3.1.0 iso-639>=0.4.5,<0.5.0;python_version<'3.12' From 6dd7a023f30a3613eb9b209d1c3eaf82f36ff304 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 23 Mar 2025 13:55:47 +0100 Subject: [PATCH 14/16] Improved error handling for invalid recipient address --- cps/tasks/mail.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index 49cd913fe..1fbcd9d20 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -171,6 +171,10 @@ class TaskEmail(CalibreTask): except MemoryError as e: log.error_or_exception(e, stacklevel=3) self._handleError('MemoryError sending e-mail: {}'.format(str(e))) + except (smtplib.SMTPRecipientsRefused) as e: + log.error_or_exception(e, stacklevel=3) + self._handleError('Smtplib Error sending e-mail: {}'.format( + (list(e.args[0].values())[0][1]).decode('utf-8)').replace("\n", '. '))) except (smtplib.SMTPException, smtplib.SMTPAuthenticationError) as e: log.error_or_exception(e, stacklevel=3) if hasattr(e, "smtp_error"): From 646937272a0849aa72cc2c1b95cc8445e460c0f8 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 23 Mar 2025 15:12:06 +0100 Subject: [PATCH 15/16] Updates dependencies Update logger ->filename is logged instead of responsible logger --- cps/logger.py | 16 +++++----------- cps/tasks/mail.py | 16 ++++++++-------- cps/web.py | 6 +++++- optional-requirements.txt | 14 +++++++------- pyproject.toml | 3 +-- requirements.txt | 16 ++++++++-------- 6 files changed, 34 insertions(+), 37 deletions(-) diff --git a/cps/logger.py b/cps/logger.py index 9aa076d40..7ce2dbc24 100644 --- a/cps/logger.py +++ b/cps/logger.py @@ -29,7 +29,7 @@ from .constants import CONFIG_DIR as _CONFIG_DIR ACCESS_FORMATTER_GEVENT = Formatter("%(message)s") ACCESS_FORMATTER_TORNADO = Formatter("[%(asctime)s] %(message)s") -FORMATTER = Formatter("[%(asctime)s] %(levelname)5s {%(name)s:%(lineno)d} %(message)s") +FORMATTER = Formatter("[%(asctime)s] %(levelname)5s {%(filename)s:%(lineno)d} %(message)s") DEFAULT_LOG_LEVEL = logging.INFO DEFAULT_LOG_FILE = os.path.join(_CONFIG_DIR, "calibre-web.log") DEFAULT_ACCESS_LOG = os.path.join(_CONFIG_DIR, "access.log") @@ -42,18 +42,12 @@ logging.addLevelName(logging.CRITICAL, "CRIT") class _Logger(logging.Logger): - def error_or_exception(self, message, stacklevel=2, *args, **kwargs): + def error_or_exception(self, message, stacklevel=1, *args, **kwargs): is_debug = self.getEffectiveLevel() <= logging.DEBUG - if sys.version_info > (3, 7): - if is_debug: - self.exception(message, stacklevel=stacklevel, *args, **kwargs) - else: - self.error(message, stacklevel=stacklevel, *args, **kwargs) + if not is_debug: + self.exception(message, stacklevel=stacklevel, *args, **kwargs) else: - if is_debug: - self.exception(message, stack_info=True, *args, **kwargs) - else: - self.error(message, *args, **kwargs) + self.error(message, stacklevel=stacklevel, *args, **kwargs) def debug_no_auth(self, message, *args, **kwargs): message = message.strip("\r\n") diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index 1fbcd9d20..7206b732c 100644 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -35,7 +35,7 @@ from cps.embed_helper import do_calibre_export from cps import logger, config from cps import gdriveutils from cps.string_helper import strip_whitespaces -import uuid + log = logger.create() @@ -56,7 +56,7 @@ class EmailBase: def send(self, strg): """Send `strg' to the server.""" - log.debug_no_auth('send: {}'.format(strg[:300])) + log.debug_no_auth('send: {}'.format(strg[:300]), stacklevel=2) if hasattr(self, 'sock') and self.sock: try: if self.transferSize: @@ -169,14 +169,14 @@ class TaskEmail(CalibreTask): else: self.send_gmail_email(msg) except MemoryError as e: - log.error_or_exception(e, stacklevel=3) + log.error_or_exception(e, stacklevel=2) self._handleError('MemoryError sending e-mail: {}'.format(str(e))) except (smtplib.SMTPRecipientsRefused) as e: - log.error_or_exception(e, stacklevel=3) + log.error_or_exception(e, stacklevel=2) self._handleError('Smtplib Error sending e-mail: {}'.format( (list(e.args[0].values())[0][1]).decode('utf-8)').replace("\n", '. '))) except (smtplib.SMTPException, smtplib.SMTPAuthenticationError) as e: - log.error_or_exception(e, stacklevel=3) + log.error_or_exception(e, stacklevel=2) if hasattr(e, "smtp_error"): text = e.smtp_error.decode('utf-8').replace("\n", '. ') elif hasattr(e, "message"): @@ -187,10 +187,10 @@ class TaskEmail(CalibreTask): text = '' self._handleError('Smtplib Error sending e-mail: {}'.format(text)) except (socket.error) as e: - log.error_or_exception(e, stacklevel=3) + log.error_or_exception(e, stacklevel=2) self._handleError('Socket Error sending e-mail: {}'.format(e.strerror)) except Exception as ex: - log.error_or_exception(ex, stacklevel=3) + log.error_or_exception(ex, stacklevel=2) self._handleError('Error sending e-mail: {}'.format(ex)) def send_standard_email(self, msg): @@ -273,7 +273,7 @@ class TaskEmail(CalibreTask): if config.config_binariesdir and config.config_embed_metadata: os.remove(datafile) except IOError as e: - log.error_or_exception(e, stacklevel=3) + log.error_or_exception(e, stacklevel=2) log.error('The requested file could not be read. Maybe wrong permissions?') return None return data diff --git a/cps/web.py b/cps/web.py index 3fa620a89..0d9a6cf20 100644 --- a/cps/web.py +++ b/cps/web.py @@ -60,7 +60,7 @@ from .services.worker import WorkerThread from .tasks_status import render_task_status from .usermanagement import user_login_required from .string_helper import strip_whitespaces - +import traceback feature_support = { 'ldap': bool(services.ldap), @@ -808,6 +808,10 @@ def render_archived_books(page, sort_param): @web.route('/page/') @login_required_if_no_ano def index(page): + try: + x = 7/0 + except Exception: + log.error("test", stacklevel=1) sort_param = (request.args.get('sort') or 'stored').lower() return render_books_list("newest", sort_param, 1, page) diff --git a/optional-requirements.txt b/optional-requirements.txt index 1726847f5..027389d76 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -1,11 +1,11 @@ # GDrive Integration google-api-python-client>=1.7.11,<2.200.0 -gevent>20.6.0,<24.3.0 +gevent>20.6.0,<24.12.0 greenlet>=0.4.17,<3.2.0 httplib2>=0.9.2,<0.23.0 oauth2client>=4.0.0,<4.1.4 uritemplate>=3.0.0,<4.2.0 -pyasn1-modules>=0.0.8,<0.5.0 +pyasn1-modules>=0.0.8,<0.7.0 pyasn1>=0.1.9,<0.7.0 PyDrive2>=1.3.1,<1.22.0 PyYAML>=3.12,<6.1 @@ -17,23 +17,23 @@ google-api-python-client>=1.7.11,<2.200.0 # goodreads goodreads>=0.3.2,<0.4.0 -python-Levenshtein>=0.12.0,<0.27.0 +python-Levenshtein>=0.12.0,<0.28.0 # ldap login python-ldap>=3.0.0,<3.5.0 Flask-SimpleLDAP>=1.4.0,<2.1.0 # oauth -Flask-Dance>=2.0.0,<7.1.0 +Flask-Dance>=2.0.0,<7.2.0 SQLAlchemy-Utils>=0.33.5,<0.42.0 # metadata extraction rarfile>=3.2,<5.0 scholarly>=1.2.0,<1.8 -markdown2>=2.0.0,<2.5.0 -html2text>=2020.1.16,<2024.2.26 +markdown2>=2.0.0,<2.6.0 +html2text>=2020.1.16,<2025.2.26 python-dateutil>=2.1,<2.10.0 -beautifulsoup4>=4.0.1,<4.13.0 +beautifulsoup4>=4.0.1,<4.14.0 faust-cchardet>=2.1.18,<2.1.20 py7zr>=0.15.0,<0.21.0 mutagen>=1.40.0,<1.50.0 diff --git a/pyproject.toml b/pyproject.toml index 49f2d9153..4b86b0fa6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,12 +12,11 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: GNU Affero General Public License v3", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Operating System :: OS Independent", ] keywords = [ diff --git a/requirements.txt b/requirements.txt index 3f4efbc67..ad47f3635 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,26 +1,26 @@ -APScheduler>=3.6.3,<3.11.0 +APScheduler>=3.6.3,<3.12.0 Babel>=1.3,<3.0 Flask-Babel>=3.0.0,<4.1.0 Flask-Principal>=0.3.2,<0.5.1 -Flask>=1.0.2,<3.1.0 +Flask>=1.0.2,<3.2.0 iso-639>=0.4.5,<0.5.0;python_version<'3.12' pycountry>=20.0.0,<25.0.0;python_version>='3.12' -PyPDF>=3.15.6,<5.1.0 +PyPDF>=3.15.6,<5.5.0 pytz>=2016.10 requests>=2.32.0,<2.33.0 SQLAlchemy>=1.3.0,<2.1.0 -tornado>=6.3,<6.5 +tornado>=6.4.2,<6.6 Wand>=0.4.4,<0.7.0 unidecode>=0.04.19,<1.4.0 -lxml>=4.9.1,<5.3.0 +lxml>=4.9.1,<5.4.0 flask-wtf>=0.14.2,<1.3.0 chardet>=3.0.0,<5.3.0 netifaces-plus>=0.12.0,<0.13.0 urllib3>=1.22,<3.0 -Flask-Limiter>=2.3.0,<3.9.0 -regex>=2022.3.2,<2024.6.25 +Flask-Limiter>=2.3.0,<3.13.0 +regex>=2022.3.2,<2025.3.20 bleach>=6.0.0,<6.2.0 python-magic>=0.4.27,<0.5.0 python-magic-bin>=0.4.0,<0.5.0;sys_platform=='win32' flask-httpAuth>=4.4.0,<5.0.0 -cryptography>=30.0.0,<44.0.0 +cryptography>=43.0.4,<45.0.0 From 86e6161b891c5b52891a692cd7043e69635a7407 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 23 Mar 2025 15:29:03 +0100 Subject: [PATCH 16/16] Remove testing code --- cps/web.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cps/web.py b/cps/web.py index 0d9a6cf20..b36882579 100644 --- a/cps/web.py +++ b/cps/web.py @@ -808,10 +808,6 @@ def render_archived_books(page, sort_param): @web.route('/page/') @login_required_if_no_ano def index(page): - try: - x = 7/0 - except Exception: - log.error("test", stacklevel=1) sort_param = (request.args.get('sort') or 'stored').lower() return render_books_list("newest", sort_param, 1, page)