diff --git a/cps/admin.py b/cps/admin.py index 93c1a3a9..045a9523 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -33,7 +33,7 @@ from functools import wraps from urllib.parse import urlparse from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response -from flask import Markup +from markupsafe import Markup from flask_login import login_required, current_user, logout_user from flask_babel import gettext as _ from flask_babel import get_locale, format_time, format_datetime, format_timedelta diff --git a/cps/db.py b/cps/db.py index f0295fe5..ceb692ec 100644 --- a/cps/db.py +++ b/cps/db.py @@ -663,7 +663,7 @@ class CalibreDB: cls.session_factory = scoped_session(sessionmaker(autocommit=False, autoflush=True, - bind=cls.engine)) + bind=cls.engine, future=True)) for inst in cls.instances: inst.init_session() diff --git a/cps/editbooks.py b/cps/editbooks.py index f52f08aa..b8f6363f 100755 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -25,16 +25,15 @@ from datetime import datetime import json from shutil import copyfile from uuid import uuid4 -from markupsafe import escape # dependency of flask +from markupsafe import escape, Markup # dependency of flask from functools import wraps -import re try: from lxml.html.clean import clean_html, Cleaner except ImportError: clean_html = None -from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response +from flask import Blueprint, request, flash, redirect, url_for, abort, Response from flask_babel import gettext as _ from flask_babel import lazy_gettext as N_ from flask_babel import get_locale diff --git a/cps/kobo.py b/cps/kobo.py index 582cafc3..ee394509 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -166,12 +166,6 @@ def HandleSyncRequest(): only_kobo_shelves = current_user.kobo_only_shelves_sync if only_kobo_shelves: - #if sqlalchemy_version2: - # changed_entries = select(db.Books, - # ub.ArchivedBook.last_modified, - # ub.BookShelf.date_added, - # ub.ArchivedBook.is_archived) - #else: changed_entries = calibre_db.session.query(db.Books, ub.ArchivedBook.last_modified, ub.BookShelf.date_added, @@ -192,9 +186,6 @@ def HandleSyncRequest(): .filter(ub.Shelf.kobo_sync) .distinct()) else: - #if sqlalchemy_version2: - # changed_entries = select(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived) - #else: changed_entries = calibre_db.session.query(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived) @@ -209,9 +200,6 @@ def HandleSyncRequest(): .order_by(db.Books.id)) reading_states_in_new_entitlements = [] - #if sqlalchemy_version2: - # books = calibre_db.session.execute(changed_entries.limit(SYNC_ITEM_LIMIT)) - #else: books = changed_entries.limit(SYNC_ITEM_LIMIT) log.debug("Books to Sync: {}".format(len(books.all()))) for book in books: @@ -255,13 +243,6 @@ def HandleSyncRequest(): new_books_last_created = max(ts_created, new_books_last_created) kobo_sync_status.add_synced_books(book.Books.id) - '''if sqlalchemy_version2: - max_change = calibre_db.session.execute(changed_entries - .filter(ub.ArchivedBook.is_archived) - .filter(ub.ArchivedBook.user_id == current_user.id) - .order_by(func.datetime(ub.ArchivedBook.last_modified).desc()))\ - .columns(db.Books).first() - else:''' max_change = changed_entries.filter(ub.ArchivedBook.is_archived)\ .filter(ub.ArchivedBook.user_id == current_user.id) \ .order_by(func.datetime(ub.ArchivedBook.last_modified).desc()).first() @@ -271,10 +252,6 @@ def HandleSyncRequest(): new_archived_last_modified = max(new_archived_last_modified, max_change) # no. of books returned - '''if sqlalchemy_version2: - entries = calibre_db.session.execute(changed_entries).all() - book_count = len(entries) - else:''' book_count = changed_entries.count() # last entry: cont_sync = bool(book_count) @@ -523,7 +500,7 @@ def get_metadata(book): @requires_kobo_auth # Creates a Shelf with the given items, and returns the shelf's uuid. def HandleTagCreate(): - # catch delete requests, otherwise the are handled in the book delete handler + # catch delete requests, otherwise they are handled in the book delete handler if request.method == "DELETE": abort(405) name, items = None, None @@ -717,14 +694,6 @@ def sync_shelves(sync_token, sync_results, only_kobo_shelves=False): }) extra_filters.append(ub.Shelf.kobo_sync) - '''if sqlalchemy_version2: - shelflist = ub.session.execute(select(ub.Shelf).outerjoin(ub.BookShelf).filter( - or_(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified, - func.datetime(ub.BookShelf.date_added) > sync_token.tags_last_modified), - ub.Shelf.user_id == current_user.id, - *extra_filters - ).distinct().order_by(func.datetime(ub.Shelf.last_modified).asc())).columns(ub.Shelf) - else:''' shelflist = ub.session.query(ub.Shelf).outerjoin(ub.BookShelf).filter( or_(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified, func.datetime(ub.BookShelf.date_added) > sync_token.tags_last_modified), diff --git a/cps/server.py b/cps/server.py index ed3b7716..ed5913bb 100644 --- a/cps/server.py +++ b/cps/server.py @@ -288,4 +288,7 @@ class WebServer(object): if _GEVENT: self.wsgiserver.close() else: - self.wsgiserver.add_callback_from_signal(self.wsgiserver.stop) + if restart: + self.wsgiserver.call_later(1.0, self.wsgiserver.stop) + else: + self.wsgiserver.add_callback_from_signal(self.wsgiserver.stop) diff --git a/cps/services/SyncToken.py b/cps/services/SyncToken.py index c44841c1..bf31a7bc 100644 --- a/cps/services/SyncToken.py +++ b/cps/services/SyncToken.py @@ -19,10 +19,8 @@ import sys from base64 import b64decode, b64encode -from jsonschema import validate, exceptions, __version__ -from datetime import datetime, timezone - -from urllib.parse import unquote +from jsonschema import validate, exceptions +from datetime import datetime from flask import json from .. import logger diff --git a/cps/static/js/kthoom.js b/cps/static/js/kthoom.js index d955dfb1..33e4b2fc 100644 --- a/cps/static/js/kthoom.js +++ b/cps/static/js/kthoom.js @@ -71,7 +71,8 @@ var settings = { fitMode: kthoom.Key.B, theme: "light", direction: 0, // 0 = Left to Right, 1 = Right to Left - scrollbar: 1, // 0 = Hide Scrollbar, 1 = Show Scrollbar + nextPage: 0, // 0 = Reset to Top, 1 = Remember Position + scrollbar: 1, // 0 = Hide Scrollbar, 1 = Show Scrollbar pageDisplay: 0 // 0 = Single Page, 1 = Long Strip }; @@ -131,8 +132,8 @@ var createURLFromArray = function(array, mimeType) { } if ((typeof URL !== "function" && typeof URL !== "object") || - typeof URL.createObjectURL !== "function") { - throw "Browser support for Object URLs is missing"; + typeof URL.createObjectURL !== "function") { + throw "Browser support for Object URLs is missing"; } return URL.createObjectURL(blob); @@ -177,12 +178,36 @@ kthoom.ImageFile = function(file) { } }; +function updateDirectionButtons(){ + $("#right").show(); + $("#left").show(); + if (currentImage == 0 ) { + if (settings.direction === 0) { + $("#right").show(); + $("#left").hide(); + } else { + $("#left").show(); + $("#right").hide(); + } + } + if ((currentImage + 1) >= Math.max(totalImages, imageFiles.length)) { + if (settings.direction === 0) { + $("#left").show(); + $("#right").hide(); + } else { + $("#right").show(); + $("#left").hide(); + } + } +} function initProgressClick() { $("#progress").click(function(e) { var offset = $(this).offset(); var x = e.pageX - offset.left; var rate = settings.direction === 0 ? x / $(this).width() : 1 - x / $(this).width(); currentImage = Math.max(1, Math.ceil(rate * totalImages)) - 1; + updateDirectionButtons(); + setBookmark(); updatePage(); }); } @@ -222,6 +247,7 @@ function loadFromArrayBuffer(ab) { // display first page if we haven't yet if (imageFiles.length === currentImage + 1) { + updateDirectionButtons(); updatePage(); } } else { @@ -241,7 +267,7 @@ function scrollTocToActive() { // Mark the current page in the TOC $("#tocView a[data-page]") - // Remove the currently active thumbnail + // Remove the currently active thumbnail .removeClass("active") // Find the new one .filter("[data-page=" + (currentImage + 1) + "]") @@ -409,6 +435,7 @@ function showLeftPage() { } else { showNextPage(); } + setBookmark(); } function showRightPage() { @@ -417,6 +444,7 @@ function showRightPage() { } else { showPrevPage(); } + setBookmark(); } function showPrevPage() { @@ -427,6 +455,7 @@ function showPrevPage() { } else { updatePage(); } + updateDirectionButtons(); } function showNextPage() { @@ -437,6 +466,7 @@ function showNextPage() { } else { updatePage(); } + updateDirectionButtons(); } function scrollCurrentImageIntoView() { @@ -621,11 +651,21 @@ function drawCanvas() { $("#mainContent").append(canvasElement); } +function updateArrows() { + if ($('input[name="direction"]:checked').val() === "0") { + $("#prev_page_key").html("←"); + $("#next_page_key").html("→"); + } else { + $("#prev_page_key").html("→"); + $("#next_page_key").html("←"); + } +}; + function init(filename) { var request = new XMLHttpRequest(); request.open("GET", filename); request.responseType = "arraybuffer"; - request.addEventListener("load", function() { + request.addEventListener("load", function () { if (request.status >= 200 && request.status < 300) { loadFromArrayBuffer(request.response); } else { @@ -641,18 +681,18 @@ function init(filename) { $(document).keydown(keyHandler); - $(window).resize(function() { + $(window).resize(function () { updateScale(); }); // Open TOC menu - $("#slider").click(function() { + $("#slider").click(function () { $("#sidebar").toggleClass("open"); $("#main").toggleClass("closed"); $(this).toggleClass("icon-menu icon-right"); // We need this in a timeout because if we call it during the CSS transition, IE11 shakes the page ¯\_(ツ)_/¯ - setTimeout(function() { + setTimeout(function () { // Focus on the TOC or the main content area, depending on which is open $("#main:not(.closed) #mainContent, #sidebar.open #tocView").focus(); scrollTocToActive(); @@ -660,12 +700,12 @@ function init(filename) { }); // Open Settings modal - $("#setting").click(function() { + $("#setting").click(function () { $("#settings-modal").toggleClass("md-show"); }); // On Settings input change - $("#settings input").on("change", function() { + $("#settings input").on("change", function () { // Get either the checked boolean or the assigned value var value = this.type === "checkbox" ? this.checked : this.value; @@ -674,39 +714,40 @@ function init(filename) { settings[this.name] = value; - if(["hflip", "vflip", "rotateTimes"].includes(this.name)) { + if (["hflip", "vflip", "rotateTimes"].includes(this.name)) { reloadImages(); - } else if(this.name === "direction") { + } else if (this.name === "direction") { + updateDirectionButtons(); return updateProgress(); } - + updatePage(); updateScale(); }); // Close modal - $(".closer, .overlay").click(function() { + $(".closer, .overlay").click(function () { $(".md-show").removeClass("md-show"); - $("#mainContent").focus(); // focus back on the main container so you use up/down keys without having to click on it + $("#mainContent").focus(); // focus back on the main container so you use up/down keys without having to click on it }); // TOC thumbnail pagination - $("#thumbnails").on("click", "a", function() { + $("#thumbnails").on("click", "a", function () { currentImage = $(this).data("page") - 1; updatePage(); }); // Fullscreen mode if (typeof screenfull !== "undefined") { - $("#fullscreen").click(function() { + $("#fullscreen").click(function () { screenfull.toggle($("#container")[0]); - // Focus on main container so you can use up/down keys immediately after fullscreen - $("#mainContent").focus(); + // Focus on main container so you can use up/down keys immediately after fullscreen + $("#mainContent").focus(); }); if (screenfull.raw) { var $button = $("#fullscreen"); - document.addEventListener(screenfull.raw.fullscreenchange, function() { + document.addEventListener(screenfull.raw.fullscreenchange, function () { screenfull.isFullscreen ? $button.addClass("icon-resize-small").removeClass("icon-resize-full") : $button.addClass("icon-resize-full").removeClass("icon-resize-small"); @@ -717,16 +758,16 @@ function init(filename) { // Focus the scrollable area so that keyboard scrolling work as expected $("#mainContent").focus(); - $("#mainContent").swipe( { - swipeRight:function() { + $("#mainContent").swipe({ + swipeRight: function () { showLeftPage(); }, - swipeLeft:function() { + swipeLeft: function () { showRightPage(); }, }); - $(".mainImage").click(function(evt) { - // Firefox does not support offsetX/Y so we have to manually calculate + $(".mainImage").click(function (evt) { + // Firefox does not support offsetX/Y, so we have to manually calculate // where the user clicked in the image. var mainContentWidth = $("#mainContent").width(); var mainContentHeight = $("#mainContent").height(); @@ -762,30 +803,38 @@ function init(filename) { }); // Scrolling up/down will update current image if a new image is into view (for Long Strip Display) - $("#mainContent").scroll(function(){ + $("#mainContent").scroll(function (){ var scroll = $("#mainContent").scrollTop(); - if(settings.pageDisplay === 0) { + var viewLength = 0; + $(".mainImage").each(function(){ + viewLength += $(this).height(); + }); + if (settings.pageDisplay === 0) { // Don't trigger the scroll for Single Page - } else if(scroll > prevScrollPosition) { + } else if (scroll > prevScrollPosition) { //Scroll Down - if(currentImage + 1 < imageFiles.length) { - if(currentImageOffset(currentImage + 1) <= 1) { - currentImage++; + if (currentImage + 1 < imageFiles.length) { + if (currentImageOffset(currentImage + 1) <= 1) { + currentImage = Math.floor((imageFiles.length) / (viewLength-viewLength/(imageFiles.length)) * scroll, 0); + if ( currentImage >= imageFiles.length) { + currentImage = imageFiles.length - 1; + } + console.log(currentImage); scrollTocToActive(); updateProgress(); } } } else { //Scroll Up - if(currentImage - 1 > -1 ) { - if(currentImageOffset(currentImage - 1) >= 0) { - currentImage--; + if (currentImage - 1 > -1) { + if (currentImageOffset(currentImage - 1) >= 0) { + currentImage = Math.floor((imageFiles.length) / (viewLength-viewLength/(imageFiles.length)) * scroll, 0); + console.log(currentImage); scrollTocToActive(); updateProgress(); } } } - // Update scroll position prevScrollPosition = scroll; }); @@ -794,3 +843,31 @@ function init(filename) { function currentImageOffset(imageIndex) { return $(".mainImage").eq(imageIndex).offset().top - $("#mainContent").position().top } + +function setBookmark() { + // get csrf_token + let csrf_token = $("input[name='csrf_token']").val(); + //This sends a bookmark update to calibreweb. + $.ajax(calibre.bookmarkUrl, { + method: "post", + data: { + csrf_token: csrf_token, + bookmark: currentImage + } + }).fail(function (xhr, status, error) { + console.error(error); + }); +} + +$(function() { + $('input[name="direction"]').change(function () { + updateArrows(); + }); + + $('#left').click(function () { + showLeftPage(); + }); + $('#right').click(function () { + showRightPage(); + }); +}); diff --git a/cps/static/js/main.js b/cps/static/js/main.js index 8d7354ef..34d3bc96 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -333,7 +333,6 @@ $(function() { } else { $("#parent").addClass('hidden') } - // console.log(data); data.files.forEach(function(entry) { if(entry.type === "dir") { var type = ""; diff --git a/cps/templates/readcbr.html b/cps/templates/readcbr.html index a4ac3722..39786ec2 100644 --- a/cps/templates/readcbr.html +++ b/cps/templates/readcbr.html @@ -1,5 +1,6 @@ +
@@ -20,23 +21,6 @@ -{{_('Keyboard Shortcuts')}} | |
---|---|
← | {{_('Previous Page')}} |
→ | {{_('Next Page')}} |
S | {{_('Single Page Display')}} |
R | {{_('Rotate Right')}} |
L | {{_('Rotate Left')}} |
F | {{_('Flip Image')}} |
{{_('Settings')}} | -|
---|---|
{{_('Theme')}}: | -
-
+ |
{{_('Settings')}} | +|
---|---|
{{_('Theme')}}: | +
+ |
-
{{_('Rotate')}}: | -
-
-
-
-
-
-
- |
-
{{_('Flip')}}: | -
-
-
-
-
- |
-
{{_('Direction')}}: | -
-
+
+ |
+
{{_('Rotate')}}: | +
+
+
+
+
+
+
+ |
+
{{_('Flip')}}: | +
+
+
+
+
+ |
+
{{_('Direction')}}: | +
+ |
{{_('Next Page')}}: | +
+
+
+
+
+ |
+
{{_('Scrollbar')}}: |
-
- |
-
Start Time: 2023-08-23 21:16:31
+Start Time: 2023-10-11 19:32:23
Stop Time: 2023-08-24 03:51:45
+Stop Time: 2023-10-12 01:29:49
Duration: 5h 34 min
+Duration: 4h 56 min
Traceback (most recent call last): + File "/home/ozzie/Development/calibre-web-test/test/test_backup_metadata.py", line 49, in test_backup_all + self.assertEqual(1, len(res)) +AssertionError: 1 != 0+
Traceback (most recent call last): - File "/home/ozzie/Development/calibre-web-test/test/test_edit_additional_books.py", line 225, in test_upload_metadata_cb7 - self.check_element_on_page((By.ID, 'edit_cancel')).click() -AttributeError: 'bool' object has no attribute 'click'-
Traceback (most recent call last): - File "/home/ozzie/Development/calibre-web-test/test/test_edit_books.py", line 1159, in test_upload_book_cb7 - self.check_element_on_page((By.ID, 'edit_cancel')).click() -AttributeError: 'bool' object has no attribute 'click'-
Traceback (most recent call last): - File "/home/ozzie/Development/calibre-web-test/test/test_edit_books.py", line 866, in test_upload_cover_hdd - self.delete_book(details['id']) -NameError: name 'details' is not defined-
Traceback (most recent call last): - File "/home/ozzie/Development/calibre-web-test/test/test_edit_books_metadata.py", line 209, in test_load_metadata - self.assertEqual(old_results, results) -AssertionError: Lists differ: [] != [{'cover_element': <selenium.webdriver.rem[10121 chars]4/'}] - -Second list contains 20 additional elements. -First extra element 0: -{'cover_element': <selenium.webdriver.remote.webelement.WebElement (session="34034d2d-f804-47c1-b9ad-fcf09f75f812", element="6dfe81e2-4752-4f1f-bd33-9388d0d529c1")>, 'cover': 'https://books.google.com/books/content?id=Ub8TAQAAIAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api&fife=w800-h900', 'source': 'https://books.google.com/', 'author': 'Martin Vogt', 'publisher': '', 'title': 'Der Buchtitel in der römischen Poesie', 'title_link': 'https://books.google.com/books?id=Ub8TAQAAIAAJ'} - -Diff is 10795 characters long. Set self.maxDiff to None to see it.+ File "/home/ozzie/Development/calibre-web-test/test/test_edit_books_metadata.py", line 84, in test_load_metadata + elif 'https://amazon.com/' == results[20]['source']: +IndexError: list index out of range
Traceback (most recent call last): + File "/home/ozzie/Development/calibre-web-test/test/test_thumbnails.py", line 311, in test_sideloaded_book + self.assertAlmostEqual(diff(BytesIO(list_cover), BytesIO(old_list_cover), delete_diff_file=True), 0.0, +AssertionError: 0.004399004046062869 != 0.0 within 0.0001 delta (0.004399004046062869 difference)+