From 91a21ababe755b4346bae0e55bebb0124b7a2e9a Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 4 Dec 2021 11:16:33 +0100 Subject: [PATCH 1/5] Allow download of archived books --- cps/helper.py | 2 +- cps/kobo.py | 19 +++++++++---------- cps/services/SyncToken.py | 5 ++--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/cps/helper.py b/cps/helper.py index d966b331..2c2c3cad 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -823,7 +823,7 @@ def get_cc_columns(filter_config_custom_read=False): def get_download_link(book_id, book_format, client): book_format = book_format.split(".")[0] - book = calibre_db.get_filtered_book(book_id) + book = calibre_db.get_filtered_book(book_id, allow_show_archived=True) if book: data1 = calibre_db.get_book_format(book.id, book_format.upper()) else: diff --git a/cps/kobo.py b/cps/kobo.py index 3d2af7f4..653321cb 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -355,7 +355,9 @@ def HandleMetadataRequest(book_uuid): return redirect_or_proxy_request() metadata = get_metadata(book) - return jsonify([metadata]) + response = make_response(json.dumps([metadata])) + response.headers["Content-Type"] = "application/json; charset=utf-8" + return response def get_download_url_for_book(book, book_format): @@ -413,15 +415,12 @@ def get_description(book): def get_author(book): if not book.authors: return {"Contributors": None} - if len(book.authors) > 1: - author_list = [] - autor_roles = [] - for author in book.authors: - autor_roles.append({"Name":author.name}) #.encode('unicode-escape').decode('latin-1') - author_list.append(author.name) - return {"ContributorRoles": autor_roles, "Contributors":author_list} - return {"ContributorRoles": [{"Name":book.authors[0].name}], - "Contributors": book.authors[0].name} + author_list = [] + autor_roles = [] + for author in book.authors: + autor_roles.append({"Name":author.name}) #.encode('unicode-escape').decode('latin-1') + author_list.append(author.name) + return {"ContributorRoles": autor_roles, "Contributors":author_list} def get_publisher(book): diff --git a/cps/services/SyncToken.py b/cps/services/SyncToken.py index 692aaa24..85ed5032 100644 --- a/cps/services/SyncToken.py +++ b/cps/services/SyncToken.py @@ -182,10 +182,9 @@ class SyncToken: return b64encode_json(token) def __str__(self): - return "{},{},{},{},{},{}".format(self.raw_kobo_store_token, - self.books_last_created, + return "{},{},{},{},{},{}".format(self.books_last_created, self.books_last_modified, self.archive_last_modified, self.reading_state_last_modified, - self.tags_last_modified) + self.tags_last_modified, self.raw_kobo_store_token) #self.books_last_id) From bd01e840cac13749d527c6f7f5e577f352d1451a Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 4 Dec 2021 11:50:25 +0100 Subject: [PATCH 2/5] Delete books in shelfs, downloaded books, kobo sync status, etc on database change (fixes #620) --- cps/admin.py | 11 ++++++++++- cps/db.py | 3 --- cps/static/js/main.js | 6 +----- cps/web.py | 3 --- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index f104aa29..2c32431f 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -1186,11 +1186,20 @@ def _db_configuration_update_helper(): if not calibre_db.setup_db(to_save['config_calibre_dir'], ub.app_DB_path): return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdrive_error) + # if db changed -> delete shelfs, delete download books, delete read books, kobo sync... + ub.session.query(ub.Downloads).delete() + ub.session.query(ub.ArchivedBook).delete() + ub.session.query(ub.ArchivedBook).delete() + ub.session.query(ub.ReadBook).delete() + ub.session.query(ub.BookShelf).delete() + ub.session.query(ub.Bookmark).delete() + ub.session.query(ub.KoboReadingState).delete() + ub.session.query(ub.KoboStatistics).delete() + ub.session.query(ub.KoboSyncedBooks).delete() _config_string(to_save, "config_calibre_dir") calibre_db.update_config(config) if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK): flash(_(u"DB is not Writeable"), category="warning") - # warning = {'type': "warning", 'message': _(u"DB is not Writeable")} config.save() return _db_configuration_result(None, gdrive_error) diff --git a/cps/db.py b/cps/db.py index 70da9108..4b0a7ac7 100644 --- a/cps/db.py +++ b/cps/db.py @@ -550,11 +550,8 @@ class CalibreDB(): @classmethod def setup_db(cls, config_calibre_dir, app_db_path): - # cls.config = config cls.dispose() - # toDo: if db changed -> delete shelfs, delete download books, delete read boks, kobo sync?? - if not config_calibre_dir: cls.config.invalidate() return False diff --git a/cps/static/js/main.js b/cps/static/js/main.js index 7a59b172..cf6fbe0d 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -284,11 +284,7 @@ $(function() { } function fillFileTable(path, type, folder, filt) { - if (window.location.pathname.endsWith("/basicconfig")) { - var request_path = "/../basicconfig/pathchooser/"; - } else { - var request_path = "/../../ajax/pathchooser/"; - } + var request_path = "/../../ajax/pathchooser/"; $.ajax({ dataType: "json", data: { diff --git a/cps/web.py b/cps/web.py index 2cb92a09..13c56f11 100644 --- a/cps/web.py +++ b/cps/web.py @@ -1525,9 +1525,6 @@ def register(): @web.route('/login', methods=['GET', 'POST']) def login(): - #if not config.db_configured: - # log.debug(u"Redirect to initial configuration") - # return redirect(url_for('admin.basic_configuration')) if current_user is not None and current_user.is_authenticated: return redirect(url_for('web.index')) if config.config_login_type == constants.LOGIN_LDAP and not services.ldap: From eb2e816bfdb86b4ff1c352b59463b7fa33203b5b Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 4 Dec 2021 14:58:28 +0100 Subject: [PATCH 3/5] Switch encoding in kobo metadata to ensure utf-8 characters to show up properly (finally) --- cps/kobo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/kobo.py b/cps/kobo.py index 653321cb..e8ea65a4 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -355,7 +355,7 @@ def HandleMetadataRequest(book_uuid): return redirect_or_proxy_request() metadata = get_metadata(book) - response = make_response(json.dumps([metadata])) + response = make_response(json.dumps([metadata], ensure_ascii=False)) response.headers["Content-Type"] = "application/json; charset=utf-8" return response From 3bf173d95885897702bc130d594d42f0e7409300 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sat, 4 Dec 2021 15:44:41 +0100 Subject: [PATCH 4/5] Added response for kobo-benefits route and kobo-gettest route --- cps/kobo.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cps/kobo.py b/cps/kobo.py index e8ea65a4..482eb13a 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -986,6 +986,25 @@ def HandleUserRequest(dummy=None): return redirect_or_proxy_request() +@csrf.exempt +@kobo.route("/v1/user/loyalty/benefits", methods=["GET"]) +def handle_benefits(): + if config.config_kobo_proxy: + return redirect_or_proxy_request() + else: + return make_response(jsonify({"Benefits": {}})) + + +@csrf.exempt +@kobo.route("/v1/analytics/gettests", methods=["GET", "POST"]) +def handle_getests(): + if config.config_kobo_proxy: + return redirect_or_proxy_request() + else: + testkey = request.headers.get("X-Kobo-userkey","") + return make_response(jsonify({"Result": "Success", "TestKey":testkey, "Tests": {}})) + + @csrf.exempt @kobo.route("/v1/products//prices", methods=["GET", "POST"]) @kobo.route("/v1/products//recommendations", methods=["GET", "POST"]) @@ -1001,6 +1020,7 @@ def HandleUserRequest(dummy=None): @kobo.route("/v1/products/deals", methods=["GET", "POST"]) @kobo.route("/v1/products", methods=["GET", "POST"]) @kobo.route("/v1/affiliate", methods=["GET", "POST"]) +@kobo.route("/v1/deals", methods=["GET", "POST"]) def HandleProductsRequest(dummy=None): log.debug("Unimplemented Products Request received: %s", request.base_url) return redirect_or_proxy_request() From fd5ab0ef533891e3bfbf2fc4bd961d0259e95651 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 5 Dec 2021 18:01:56 +0100 Subject: [PATCH 5/5] Bugfix handle archive bit --- cps/kobo_sync_status.py | 18 +++++++++--------- cps/static/js/table.js | 6 +++--- cps/web.py | 17 ++++------------- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/cps/kobo_sync_status.py b/cps/kobo_sync_status.py index b88cb6ac..eee47d89 100644 --- a/cps/kobo_sync_status.py +++ b/cps/kobo_sync_status.py @@ -20,7 +20,7 @@ from flask_login import current_user from . import ub import datetime -from sqlalchemy.sql.expression import or_ +from sqlalchemy.sql.expression import or_, and_ # Add the current book id to kobo_synced_books table for current user, if entry is already present, # do nothing (safety precaution) @@ -42,18 +42,18 @@ def remove_synced_book(book_id): ub.session_commit() -def add_archived_books(book_id): - archived_book = (ub.session.query(ub.ArchivedBook) - .filter(ub.ArchivedBook.book_id == book_id) - .filter(ub.ArchivedBook.user_id == current_user.id) - .first()) +def change_archived_books(book_id, state=None, message=None): + archived_book = ub.session.query(ub.ArchivedBook).filter(and_(ub.ArchivedBook.user_id == int(current_user.id), + ub.ArchivedBook.book_id == book_id)).first() if not archived_book: archived_book = ub.ArchivedBook(user_id=current_user.id, book_id=book_id) - archived_book.is_archived = True + + archived_book.is_archived = state if state else not archived_book.is_archived archived_book.last_modified = datetime.datetime.utcnow() ub.session.merge(archived_book) - ub.session_commit() + ub.session_commit(message) + return archived_book.is_archived # select all books which are synced by the current user and do not belong to a synced shelf and them to archive @@ -65,7 +65,7 @@ def update_on_sync_shelfs(user_id): .filter(or_(ub.Shelf.kobo_sync == 0, ub.Shelf.kobo_sync == None)) .filter(ub.KoboSyncedBooks.user_id == user_id).all()) for b in books_to_archive: - add_archived_books(b.book_id) + change_archived_books(b.book_id, True) ub.session.query(ub.KoboSyncedBooks) \ .filter(ub.KoboSyncedBooks.book_id == b.book_id) \ .filter(ub.KoboSyncedBooks.user_id == user_id).delete() diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 0600f0b3..e98f6a8b 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -631,14 +631,14 @@ function singleUserFormatter(value, row) { } function checkboxFormatter(value, row){ - if(value & this.column) + if (value & this.column) return ''; else return ''; } function singlecheckboxFormatter(value, row){ - if(value) + if (value) return ''; else return ''; @@ -793,7 +793,7 @@ function handleListServerResponse (data) { function checkboxChange(checkbox, userId, field, field_index) { $.ajax({ method: "post", - url: window.location.pathname + "/../../ajax/editlistusers/" + field, + url: getPath() + "/ajax/editlistusers/" + field, data: {"pk": userId, "field_index": field_index, "value": checkbox.checked}, error: function(data) { handleListServerResponse([{type:"danger", message:data.responseText}]) diff --git a/cps/web.py b/cps/web.py index 13c56f11..f203783b 100644 --- a/cps/web.py +++ b/cps/web.py @@ -56,6 +56,7 @@ from .redirect import redirect_back from .usermanagement import login_required_if_no_ano from .kobo_sync_status import remove_synced_book from .render_template import render_title_template +from .kobo_sync_status import change_archived_books feature_support = { 'ldap': bool(services.ldap), @@ -190,24 +191,15 @@ def toggle_read(book_id): return "Custom Column No.{} is not existing in calibre database".format(config.config_read_column), 400 except (OperationalError, InvalidRequestError) as e: calibre_db.session.rollback() - log.error(u"Read status could not set: %e", e) + log.error(u"Read status could not set: {}".format(e)) return "Read status could not set: {}".format(e), 400 return "" @web.route("/ajax/togglearchived/", methods=['POST']) @login_required def toggle_archived(book_id): - archived_book = ub.session.query(ub.ArchivedBook).filter(and_(ub.ArchivedBook.user_id == int(current_user.id), - ub.ArchivedBook.book_id == book_id)).first() - if archived_book: - archived_book.is_archived = not archived_book.is_archived - archived_book.last_modified = datetime.utcnow() - else: - archived_book = ub.ArchivedBook(user_id=current_user.id, book_id=book_id) - archived_book.is_archived = True - ub.session.merge(archived_book) - ub.session_commit("Book {} archivebit toggled".format(book_id)) - if archived_book.is_archived: + is_archived = change_archived_books(book_id, message="Book {} archivebit toggled".format(book_id)) + if is_archived: remove_synced_book(book_id) return "" @@ -801,7 +793,6 @@ def list_books(): if sort == "state": state = json.loads(request.args.get("state", "[]")) - # order = [db.Books.timestamp.asc()] if order == "asc" else [db.Books.timestamp.desc()] # ToDo wrong: sort ticked elif sort == "tags": order = [db.Tags.name.asc()] if order == "asc" else [db.Tags.name.desc()] join = db.books_tags_link,db.Books.id == db.books_tags_link.c.book, db.Tags