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..072abcc0 100644
--- a/cps/static/js/kthoom.js
+++ b/cps/static/js/kthoom.js
@@ -177,12 +177,31 @@ kthoom.ImageFile = function(file) {
}
};
+function updateDirectionButtons(){
+ $("#right").show();
+ $("#left").show();
+ if (currentImage == 0 ) {
+ if (settings.direction === 0) {
+ $("#left").hide();
+ } else {
+ $("#right").hide();
+ }
+ }
+ if ((currentImage + 1) >= Math.max(totalImages, imageFiles.length)) {
+ if (settings.direction === 0) {
+ $("#right").hide();
+ } else {
+ $("#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();
updatePage();
});
}
@@ -222,6 +241,11 @@ function loadFromArrayBuffer(ab) {
// display first page if we haven't yet
if (imageFiles.length === currentImage + 1) {
+ if (settings.direction === 0) {
+ $("#right").show();
+ } else {
+ $("#left").show();
+ }
updatePage();
}
} else {
@@ -427,6 +451,7 @@ function showPrevPage() {
} else {
updatePage();
}
+ updateDirectionButtons();
}
function showNextPage() {
@@ -437,6 +462,7 @@ function showNextPage() {
} else {
updatePage();
}
+ updateDirectionButtons();
}
function scrollCurrentImageIntoView() {
@@ -677,6 +703,7 @@ function init(filename) {
if(["hflip", "vflip", "rotateTimes"].includes(this.name)) {
reloadImages();
} else if(this.name === "direction") {
+ updateDirectionButtons();
return updateProgress();
}
@@ -726,7 +753,7 @@ function init(filename) {
},
});
$(".mainImage").click(function(evt) {
- // Firefox does not support offsetX/Y so we have to manually calculate
+ // 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();
diff --git a/cps/templates/readcbr.html b/cps/templates/readcbr.html
index a4ac3722..8b42cb79 100644
--- a/cps/templates/readcbr.html
+++ b/cps/templates/readcbr.html
@@ -77,8 +77,8 @@
- ‹
- ›
+ ‹
+ ›
diff --git a/cps/tornado_wsgi.py b/cps/tornado_wsgi.py
index af93219c..9be3645d 100644
--- a/cps/tornado_wsgi.py
+++ b/cps/tornado_wsgi.py
@@ -16,15 +16,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
from tornado.wsgi import WSGIContainer
import tornado
from tornado import escape
from tornado import httputil
+from tornado.ioloop import IOLoop
from typing import List, Tuple, Optional, Callable, Any, Dict, Text
-from types import TracebackType
+from types import TracebackType, FunctionType
import typing
if typing.TYPE_CHECKING:
@@ -34,61 +34,67 @@ if typing.TYPE_CHECKING:
class MyWSGIContainer(WSGIContainer):
def __call__(self, request: httputil.HTTPServerRequest) -> None:
- data = {} # type: Dict[str, Any]
- response = [] # type: List[bytes]
+ if tornado.version_info > (6, 2, 0, 0):
+ data = {} # type: Dict[str, Any]
+ response = [] # type: List[bytes]
- def start_response(
- status: str,
- headers: List[Tuple[str, str]],
- exc_info: Optional[
- Tuple[
- "Optional[Type[BaseException]]",
- Optional[BaseException],
- Optional[TracebackType],
- ]
- ] = None,
- ) -> Callable[[bytes], Any]:
- data["status"] = status
- data["headers"] = headers
- return response.append
+ def start_response(
+ status: str,
+ headers: List[Tuple[str, str]],
+ exc_info: Optional[
+ Tuple[
+ "Optional[Type[BaseException]]",
+ Optional[BaseException],
+ Optional[TracebackType],
+ ]
+ ] = None,
+ ) -> Callable[[bytes], Any]:
+ data["status"] = status
+ data["headers"] = headers
+ return response.append
- app_response = self.wsgi_application(
- MyWSGIContainer.environ(request), start_response
- )
- try:
- response.extend(app_response)
- body = b"".join(response)
- finally:
- if hasattr(app_response, "close"):
- app_response.close() # type: ignore
- if not data:
- raise Exception("WSGI app did not call start_response")
+ app_response = self.wsgi_application(
+ MyWSGIContainer.environ(self, request), start_response
+ )
+ try:
+ response.extend(app_response)
+ body = b"".join(response)
+ finally:
+ if hasattr(app_response, "close"):
+ app_response.close() # type: ignore
+ if not data:
+ raise Exception("WSGI app did not call start_response")
- status_code_str, reason = data["status"].split(" ", 1)
- status_code = int(status_code_str)
- headers = data["headers"] # type: List[Tuple[str, str]]
- header_set = set(k.lower() for (k, v) in headers)
- body = escape.utf8(body)
- if status_code != 304:
- if "content-length" not in header_set:
- headers.append(("Content-Length", str(len(body))))
- if "content-type" not in header_set:
- headers.append(("Content-Type", "text/html; charset=UTF-8"))
- if "server" not in header_set:
- headers.append(("Server", "TornadoServer/%s" % tornado.version))
+ status_code_str, reason = data["status"].split(" ", 1)
+ status_code = int(status_code_str)
+ headers = data["headers"] # type: List[Tuple[str, str]]
+ header_set = set(k.lower() for (k, v) in headers)
+ body = escape.utf8(body)
+ if status_code != 304:
+ if "content-length" not in header_set:
+ headers.append(("Content-Length", str(len(body))))
+ if "content-type" not in header_set:
+ headers.append(("Content-Type", "text/html; charset=UTF-8"))
+ if "server" not in header_set:
+ headers.append(("Server", "TornadoServer/%s" % tornado.version))
- start_line = httputil.ResponseStartLine("HTTP/1.1", status_code, reason)
- header_obj = httputil.HTTPHeaders()
- for key, value in headers:
- header_obj.add(key, value)
- assert request.connection is not None
- request.connection.write_headers(start_line, header_obj, chunk=body)
- request.connection.finish()
- self._log(status_code, request)
+ start_line = httputil.ResponseStartLine("HTTP/1.1", status_code, reason)
+ header_obj = httputil.HTTPHeaders()
+ for key, value in headers:
+ header_obj.add(key, value)
+ assert request.connection is not None
+ request.connection.write_headers(start_line, header_obj, chunk=body)
+ request.connection.finish()
+ self._log(status_code, request)
+ else:
+ IOLoop.current().spawn_callback(self.handle_request, request)
- @staticmethod
- def environ(request: httputil.HTTPServerRequest) -> Dict[Text, Any]:
- environ = WSGIContainer.environ(request)
+
+ def environ(self, request: httputil.HTTPServerRequest) -> Dict[Text, Any]:
+ if isinstance(WSGIContainer.environ, FunctionType):
+ environ = WSGIContainer.environ(self, request)
+ else:
+ environ = WSGIContainer.environ(request)
environ['RAW_URI'] = request.path
return environ
diff --git a/optional-requirements.txt b/optional-requirements.txt
index ff02d04f..45f842eb 100644
--- a/optional-requirements.txt
+++ b/optional-requirements.txt
@@ -1,31 +1,31 @@
# GDrive Integration
-google-api-python-client>=1.7.11,<2.90.0
-gevent>20.6.0,<23.0.0
+google-api-python-client>=1.7.11,<2.98.0
+gevent>20.6.0,<24.0.0
greenlet>=0.4.17,<2.1.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.4.0
pyasn1>=0.1.9,<0.6.0
-PyDrive2>=1.3.1,<1.16.0
-PyYAML>=3.12
+PyDrive2>=1.3.1,<1.18.0
+PyYAML>=3.12,<6.1
rsa>=3.4.2,<4.10.0
# Gmail
-google-auth-oauthlib>=0.4.3,<0.9.0
-google-api-python-client>=1.7.11,<2.90.0
+google-auth-oauthlib>=0.4.3,<1.1.0
+google-api-python-client>=1.7.11,<2.98.0
# goodreads
goodreads>=0.3.2,<0.4.0
-python-Levenshtein>=0.12.0,<0.21.0
+python-Levenshtein>=0.12.0,<0.22.0
# ldap login
python-ldap>=3.0.0,<3.5.0
Flask-SimpleLDAP>=1.4.0,<1.5.0
# oauth
-Flask-Dance>=2.0.0,<6.3.0
-SQLAlchemy-Utils>=0.33.5,<0.40.0
+Flask-Dance>=2.0.0,<7.1.0
+SQLAlchemy-Utils>=0.33.5,<0.42.0
# metadata extraction
rarfile>=3.2
@@ -33,12 +33,12 @@ scholarly>=1.2.0,<1.8
markdown2>=2.0.0,<2.5.0
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
+beautifulsoup4>=4.0.1,<4.13.0
+faust-cchardet>=2.1.18,<2.1.20
# Comics
natsort>=2.2.0,<8.4.0
comicapi>=2.2.0,<3.3.0
# Kobo integration
-jsonschema>=3.2.0,<4.18.0
+jsonschema>=3.2.0,<4.20.0
diff --git a/requirements.txt b/requirements.txt
index fef4bbf2..b12424cc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,19 +1,19 @@
APScheduler>=3.6.3,<3.11.0
Babel>=1.3,<3.0
-Flask-Babel>=0.11.1,<3.1.0
+Flask-Babel>=0.11.1,<3.2.0
Flask-Login>=0.3.2,<0.6.3
Flask-Principal>=0.3.2,<0.5.1
Flask>=1.0.2,<2.4.0
iso-639>=0.4.5,<0.5.0
-PyPDF>=3.0.0,<3.8.0
+PyPDF>=3.0.0,<3.16.0
pytz>=2016.10
-requests>=2.11.1,<2.29.0
+requests>=2.28.0,<2.32.0
SQLAlchemy>=1.3.0,<2.0.0
-tornado>=4.1,<6.3
+tornado>=6.3,<6.4
Wand>=0.4.4,<0.7.0
unidecode>=0.04.19,<1.4.0
lxml>=3.8.0,<5.0.0
flask-wtf>=0.14.2,<1.2.0
chardet>=3.0.0,<4.1.0
advocate>=1.0.0,<1.1.0
-Flask-Limiter>=2.3.0,<3.4.0
+Flask-Limiter>=2.3.0,<3.5.0