diff --git a/cps/config_sql.py b/cps/config_sql.py index c73a6cf07f..8f0ab08573 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -84,8 +84,8 @@ class _Settings(_Base): config_authors_max = Column(Integer, default=0) config_read_column = Column(Integer, default=0) config_title_regex = Column(String, - default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine' - r'|Einen|Dem|Des|Einem|Eines|Le|La|Les|L\'|Un|Une)\s+') + default=r"^(A|The|An|Der|Die|Das|Den|Ein|Eine" + r"|Einen|Dem|Des|Einem|Eines|Le|La|Les|L'|Un|Une)(\s+|(?<='))") config_theme = Column(Integer, default=0) config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL) diff --git a/cps/kobo.py b/cps/kobo.py index 688c11f0df..aa043503b9 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -140,7 +140,6 @@ def convert_to_kobo_timestamp_string(timestamp): @kobo.route("/v1/library/sync") @requires_kobo_auth -# @limiter.limit("3/minute", key_func=get_remote_address) def HandleSyncRequest(): if not current_user.role_download(): log.info("Users need download permissions for syncing library to Kobo reader") @@ -336,7 +335,6 @@ def generate_sync_response(sync_token, sync_results, set_cont=False): @kobo.route("/v1/library//metadata") @requires_kobo_auth @download_required -# @limiter.limit("3/minute", key_func=get_remote_address) def HandleMetadataRequest(book_uuid): if not current_app.wsgi_app.is_proxied: log.debug('Kobo: Received unproxied request, changed request port to external server port') @@ -508,7 +506,6 @@ def get_metadata(book): @csrf.exempt @kobo.route("/v1/library/tags", methods=["POST", "DELETE"]) @requires_kobo_auth -# @limiter.limit("3/minute", key_func=get_remote_address) # Creates a Shelf with the given items, and returns the shelf's uuid. def HandleTagCreate(): # catch delete requests, otherwise they are handled in the book delete handler @@ -544,7 +541,6 @@ def HandleTagCreate(): @csrf.exempt @kobo.route("/v1/library/tags/", methods=["DELETE", "PUT"]) @requires_kobo_auth -# @limiter.limit("3/minute", key_func=get_remote_address) def HandleTagUpdate(tag_id): shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.uuid == tag_id, ub.Shelf.user_id == current_user.id).one_or_none() @@ -599,7 +595,6 @@ def add_items_to_shelf(items, shelf): @csrf.exempt @kobo.route("/v1/library/tags//items", methods=["POST"]) @requires_kobo_auth -# @limiter.limit("3/minute", key_func=get_remote_address) def HandleTagAddItem(tag_id): items = None try: @@ -630,7 +625,6 @@ def HandleTagAddItem(tag_id): @csrf.exempt @kobo.route("/v1/library/tags//items/delete", methods=["POST"]) @requires_kobo_auth -# @limiter.limit("3/minute", key_func=get_remote_address) def HandleTagRemoveItem(tag_id): items = None try: @@ -764,7 +758,6 @@ def create_kobo_tag(shelf): @csrf.exempt @kobo.route("/v1/library//state", methods=["GET", "PUT"]) @requires_kobo_auth -# @limiter.limit("3/minute", key_func=get_remote_address) def HandleStateRequest(book_uuid): book = calibre_db.get_book_by_uuid(book_uuid) if not book or not book.data: @@ -894,14 +887,21 @@ def get_statistics_response(statistics): return resp +def _clean_progress(value): + """Return progress as int if it's a whole number, preserving Kobo device expectations.""" + if value is not None and value == int(value): + return int(value) + return value + + def get_current_bookmark_response(current_bookmark): resp = { "LastModified": convert_to_kobo_timestamp_string(current_bookmark.last_modified), } - if current_bookmark.progress_percent: - resp["ProgressPercent"] = current_bookmark.progress_percent - if current_bookmark.content_source_progress_percent: - resp["ContentSourceProgressPercent"] = current_bookmark.content_source_progress_percent + if current_bookmark.progress_percent is not None: + resp["ProgressPercent"] = _clean_progress(current_bookmark.progress_percent) + if current_bookmark.content_source_progress_percent is not None: + resp["ContentSourceProgressPercent"] = _clean_progress(current_bookmark.content_source_progress_percent) if current_bookmark.location_value: resp["Location"] = { "Value": current_bookmark.location_value, @@ -914,7 +914,6 @@ def get_current_bookmark_response(current_bookmark): @kobo.route("/////image.jpg", defaults={'Quality': ""}) @kobo.route("//////image.jpg") @requires_kobo_auth -# @limiter.limit("3/minute", key_func=get_remote_address) def HandleCoverImageRequest(book_uuid, width, height, Quality, isGreyscale): try: if int(height) > 1000: @@ -951,7 +950,6 @@ def TopLevelEndpoint(): @csrf.exempt @kobo.route("/v1/library/", methods=["DELETE"]) @requires_kobo_auth -# @limiter.limit("3/minute", key_func=get_remote_address) def HandleBookDeletionRequest(book_uuid): log.info("Kobo book delete request received for book %s" % book_uuid) book = calibre_db.get_book_by_uuid(book_uuid) @@ -1062,7 +1060,6 @@ def make_calibre_web_auth_response(): @kobo.route("/v1/auth/refresh", methods=["POST"]) @kobo.route("/v1/auth/device", methods=["POST"]) @requires_kobo_auth -# @limiter.limit("3/minute", key_func=get_remote_address) def HandleAuthRequest(): log.error(limiter.current_limit) log.error(limiter.current_limit) @@ -1077,7 +1074,6 @@ def HandleAuthRequest(): @kobo.route("/v1/initialization") @requires_kobo_auth -# @limiter.limit("3/minute", key_func=get_remote_address) def HandleInitRequest(): log.info('Init') @@ -1121,6 +1117,8 @@ def HandleInitRequest(): width="{width}", height="{height}", isGreyscale='false')) + kobo_resources["library_sync"] = calibre_web_url + url_for("kobo.HandleSyncRequest", + auth_token=kobo_auth.get_auth_token()) else: kobo_resources["image_host"] = url_for("web.index", _external=True).strip("/") kobo_resources["image_url_quality_template"] = unquote(url_for("kobo.HandleCoverImageRequest", @@ -1138,6 +1136,9 @@ def HandleInitRequest(): height="{height}", isGreyscale='false', _external=True)) + kobo_resources["library_sync"] = url_for("kobo.HandleSyncRequest", + auth_token=kobo_auth.get_auth_token(), + _external=True) response = make_response(jsonify({"Resources": kobo_resources})) response.headers["x-kobo-apitoken"] = "e30=" @@ -1148,7 +1149,6 @@ def HandleInitRequest(): @kobo.route("/download//") @requires_kobo_auth @download_required -# @limiter.limit("3/minute", key_func=get_remote_address) def download_book(book_id, book_format): return get_download_link(book_id, book_format, "kobo") diff --git a/cps/main.py b/cps/main.py index b0f56b6d47..cc05bb50a6 100644 --- a/cps/main.py +++ b/cps/main.py @@ -24,7 +24,7 @@ def request_username(): - return request.authorization.username + return request.authorization.username if request.authorization else "" def main(): diff --git a/cps/static/css/style.css b/cps/static/css/style.css index 51a70d5f4b..9d5a8f6a26 100644 --- a/cps/static/css/style.css +++ b/cps/static/css/style.css @@ -1,3 +1,7 @@ +#add-to-shelves.dropdown-menu, #remove-from-shelves.dropdown-menu { + max-height: 300px; + overflow-y: auto; +} .tooltip.bottom .tooltip-inner { font-size: 13px; diff --git a/cps/static/js/get_meta.js b/cps/static/js/get_meta.js index df4119dca2..ce1fbfd203 100644 --- a/cps/static/js/get_meta.js +++ b/cps/static/js/get_meta.js @@ -103,11 +103,11 @@ $(function () { }); } else { - $("#meta-info").html("

" + msg.no_result + "!

" + $("#meta-info")[0].innerHTML) + $("#meta-info").html("

" + msg.no_result + "

" + $("#meta-info")[0].innerHTML) } }, error: function error() { - $("#meta-info").html("

" + msg.search_error + "!

" + $("#meta-info")[0].innerHTML); + $("#meta-info").html("

" + msg.search_error + "

" + $("#meta-info")[0].innerHTML); }, }); } diff --git a/cps/templates/detail.html b/cps/templates/detail.html index b273ef9eec..69ec460f7a 100644 --- a/cps/templates/detail.html +++ b/cps/templates/detail.html @@ -294,7 +294,7 @@

{{ _('Description:') }}

{% if current_user.is_authenticated %} {% if current_user.shelf.all() or g.shelves_access %}