diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce2bd780..c6006ad1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,6 +41,6 @@ Open a new GitHub pull request with the patch. Ensure the PR description clearly In case your code enhances features of Calibre-Web: Create your pull request for the development branch if your enhancement consists of more than some lines of code in a local section of Calibre-Webs code. This makes it easier to test it and check all implication before it's made public. -Please check if your code runs on Python 2.7 (still necessary in 2020) and mainly on python 3. If possible and the feature is related to operating system functions, try to check it on Windows and Linux. -Calibre-Web is automatically tested on Linux in combination with python 3.7. The code for testing is in a [separate repo](https://github.com/OzzieIsaacs/calibre-web-test) on Github. It uses unit tests and performs real system tests with selenium; it would be great if you could consider also writing some tests. +Please check if your code runs with python 3, python 2 is no longer supported. If possible and the feature is related to operating system functions, try to check it on Windows and Linux. +Calibre-Web is automatically tested on Linux in combination with python 3.8. The code for testing is in a [separate repo](https://github.com/OzzieIsaacs/calibre-web-test) on Github. It uses unit tests and performs real system tests with selenium; it would be great if you could consider also writing some tests. A static code analysis is done by Codacy, but it's partly broken and doesn't run automatically. You could check your code with ESLint before contributing, a configuration file can be found in the projects root folder. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..2f36fac8 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security issues to ozzie.fernandez.isaacs@googlemail.com diff --git a/cps/__init__.py b/cps/__init__.py index 26127076..70c53b85 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -102,8 +102,9 @@ def create_app(): log.info('Starting Calibre Web...') if sys.version_info < (3, 0): - log.info('Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2 please consider upgrading to Python3') - print('Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2 please consider upgrading to Python3') + log.info('*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***') + print('*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***') + sys.exit(5) Principal(app) lm.init_app(app) app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session)) diff --git a/cps/admin.py b/cps/admin.py index 0e5949b2..604ef819 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -99,10 +99,11 @@ def admin_required(f): @admi.before_app_request def before_request(): - if not ub.check_user_session(current_user.id, flask_session.get('_id')): + # make remember me function work + if current_user.is_authenticated: + confirm_login() + if not ub.check_user_session(current_user.id, flask_session.get('_id')) and 'opds' not in request.path: logout_user() - # if current_user.is_authenticated: - # confirm_login() g.constants = constants g.user = current_user g.allow_registration = config.config_public_reg @@ -1375,11 +1376,11 @@ def _delete_user(content): if content.name != "Guest": # Delete all books in shelfs belonging to user, all shelfs of user, downloadstat of user, read status # and user itself - ub.session.query(ub.ReadBook).filter(ub.User.id == ub.ReadBook.user_id).delete() - ub.session.query(ub.Downloads).filter(ub.User.id == ub.Downloads.user_id).delete() - for us in ub.session.query(ub.Shelf).filter(ub.User.id == ub.Shelf.user_id): + ub.session.query(ub.ReadBook).filter(content.id == ub.ReadBook.user_id).delete() + ub.session.query(ub.Downloads).filter(content.id == ub.Downloads.user_id).delete() + for us in ub.session.query(ub.Shelf).filter(content.id == ub.Shelf.user_id): ub.session.query(ub.BookShelf).filter(us.id == ub.BookShelf.shelf).delete() - ub.session.query(ub.Shelf).filter(ub.User.id == ub.Shelf.user_id).delete() + ub.session.query(ub.Shelf).filter(content.id == ub.Shelf.user_id).delete() ub.session.query(ub.User).filter(ub.User.id == content.id).delete() ub.session_commit() log.info(u"User {} deleted".format(content.name)) diff --git a/cps/constants.py b/cps/constants.py index f8ee3721..a0daa515 100644 --- a/cps/constants.py +++ b/cps/constants.py @@ -20,6 +20,9 @@ from __future__ import division, print_function, unicode_literals import sys import os from collections import namedtuple +from sqlalchemy import __version__ as sql_version + +sqlalchemy_version2 = ([int(x) for x in sql_version.split('.')] >= [2,0,0]) # if installed via pip this variable is set to true (empty file with name .HOMEDIR present) HOME_CONFIG = os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.HOMEDIR')) diff --git a/cps/converter.py b/cps/converter.py index f37168c7..6b0f22e4 100644 --- a/cps/converter.py +++ b/cps/converter.py @@ -39,7 +39,9 @@ def _get_command_version(path, pattern, argument=None): if argument: command.append(argument) try: - return process_wait(command, pattern=pattern).string + match = process_wait(command, pattern=pattern) + if isinstance(match, re.Match): + return match.string except Exception as ex: log.warning("%s: %s", path, ex) return _EXECUTION_ERROR diff --git a/cps/db.py b/cps/db.py index 3dd446bc..ed164afc 100644 --- a/cps/db.py +++ b/cps/db.py @@ -690,6 +690,8 @@ class CalibreDB(): randm = false() off = int(int(pagesize) * (page - 1)) query = self.session.query(database) + if len(join) == 6: + query = query.outerjoin(join[0], join[1]).outerjoin(join[2]).outerjoin(join[3], join[4]).outerjoin(join[5]) if len(join) == 3: query = query.outerjoin(join[0], join[1]).outerjoin(join[2]) elif len(join) == 2: @@ -755,6 +757,8 @@ class CalibreDB(): for authorterm in authorterms: q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%"))) query = self.session.query(Books) + if len(join) == 6: + query = query.outerjoin(join[0], join[1]).outerjoin(join[2]).outerjoin(join[3], join[4]).outerjoin(join[5]) if len(join) == 3: query = query.outerjoin(join[0], join[1]).outerjoin(join[2]) elif len(join) == 2: diff --git a/cps/debug_info.py b/cps/debug_info.py index cfa549f9..dd5e858e 100644 --- a/cps/debug_info.py +++ b/cps/debug_info.py @@ -22,10 +22,6 @@ import glob import zipfile import json from io import BytesIO -try: - from StringIO import StringIO -except ImportError: - from io import StringIO import os @@ -38,9 +34,9 @@ log = logger.create() def assemble_logfiles(file_name): log_list = sorted(glob.glob(file_name + '*'), reverse=True) - wfd = StringIO() + wfd = BytesIO() for f in log_list: - with open(f, 'r') as fd: + with open(f, 'rb') as fd: shutil.copyfileobj(fd, wfd) wfd.seek(0) if int(__version__.split('.')[0]) < 2: diff --git a/cps/jinjia.py b/cps/jinjia.py index de34cc86..70a6090e 100644 --- a/cps/jinjia.py +++ b/cps/jinjia.py @@ -113,10 +113,8 @@ def yesno(value, yes, no): @jinjia.app_template_filter('formatfloat') def formatfloat(value, decimals=1): - formatedstring = '%d' % value - if (value % 1) != 0: - formatedstring = ('%s.%d' % (formatedstring, (value % 1) * 10**decimals)).rstrip('0') - return formatedstring + value = 0 if not value else value + return ('{0:.' + str(decimals) + 'f}').format(value).rstrip('0').rstrip('.') @jinjia.app_template_filter('formatseriesindex') diff --git a/cps/kobo.py b/cps/kobo.py index a6c4236f..6952a692 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -44,11 +44,11 @@ from werkzeug.datastructures import Headers from sqlalchemy import func from sqlalchemy.sql.expression import and_, or_ from sqlalchemy.exc import StatementError -from sqlalchemy import __version__ as sql_version from sqlalchemy.sql import select import requests from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub +from .constants import sqlalchemy_version2 from .helper import get_download_link from .services import SyncToken as SyncToken from .web import download_required @@ -66,7 +66,6 @@ kobo_auth.register_url_value_preprocessor(kobo) log = logger.create() -sql2 = ([int(x) for x in sql_version.split('.')] >= [2,0,0]) def get_store_url_for_current_request(): # Programmatically modify the current url to point to the official Kobo store @@ -139,6 +138,7 @@ def convert_to_kobo_timestamp_string(timestamp): def HandleSyncRequest(): sync_token = SyncToken.SyncToken.from_headers(request.headers) log.info("Kobo library sync request received.") + log.debug("SyncToken: {}".format(sync_token)) if not current_app.wsgi_app.is_proxied: log.debug('Kobo: Received unproxied request, changed request port to external server port') @@ -158,7 +158,7 @@ def HandleSyncRequest(): only_kobo_shelves = current_user.kobo_only_shelves_sync if only_kobo_shelves: - if sql2: + if sqlalchemy_version2: changed_entries = select(db.Books, ub.ArchivedBook.last_modified, ub.BookShelf.date_added, @@ -182,7 +182,7 @@ def HandleSyncRequest(): .distinct() ) else: - if sql2: + 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, @@ -201,7 +201,7 @@ def HandleSyncRequest(): changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id) reading_states_in_new_entitlements = [] - if sql2: + if sqlalchemy_version2: books = calibre_db.session.execute(changed_entries.limit(SYNC_ITEM_LIMIT)) else: books = changed_entries.limit(SYNC_ITEM_LIMIT) @@ -245,7 +245,7 @@ def HandleSyncRequest(): new_books_last_created = max(ts_created, new_books_last_created) - if sql2: + if sqlalchemy_version2: max_change = calibre_db.session.execute(changed_entries .filter(ub.ArchivedBook.is_archived) .order_by(func.datetime(ub.ArchivedBook.last_modified).desc()))\ @@ -259,7 +259,7 @@ def HandleSyncRequest(): new_archived_last_modified = max(new_archived_last_modified, max_change) # no. of books returned - if sql2: + if sqlalchemy_version2: entries = calibre_db.session.execute(changed_entries).all() book_count = len(entries) else: @@ -330,6 +330,7 @@ def generate_sync_response(sync_token, sync_results, set_cont=False): extra_headers["x-kobo-sync"] = "continue" sync_token.to_headers(extra_headers) + log.debug("Kobo Sync Content: {}".format(sync_results)) response = make_response(jsonify(sync_results), extra_headers) return response @@ -695,7 +696,7 @@ def sync_shelves(sync_token, sync_results, only_kobo_shelves=False): }) extra_filters.append(ub.Shelf.kobo_sync) - if sql2: + 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), diff --git a/cps/services/SyncToken.py b/cps/services/SyncToken.py index b54d8d95..cc67542c 100644 --- a/cps/services/SyncToken.py +++ b/cps/services/SyncToken.py @@ -183,3 +183,12 @@ class SyncToken: }, } return b64encode_json(token) + + def __str__(self): + return "{},{},{},{},{},{},{}".format(self.raw_kobo_store_token, + self.books_last_created, + self.books_last_modified, + self.archive_last_modified, + self.reading_state_last_modified, + self.tags_last_modified, + self.books_last_id) diff --git a/cps/shelf.py b/cps/shelf.py index 431eeff8..d232e850 100644 --- a/cps/shelf.py +++ b/cps/shelf.py @@ -72,10 +72,9 @@ def add_to_shelf(shelf_id, book_id): if not check_shelf_edit_permissions(shelf): if not xhr: - flash(_(u"Sorry you are not allowed to add a book to the the shelf: %(shelfname)s", shelfname=shelf.name), - category="error") + flash(_(u"Sorry you are not allowed to add a book to that shelf"), category="error") return redirect(url_for('web.index')) - return "Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name, 403 + return "Sorry you are not allowed to add a book to the that shelf", 403 book_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id, ub.BookShelf.book_id == book_id).first() @@ -228,18 +227,21 @@ def remove_from_shelf(shelf_id, book_id): @login_required def create_shelf(): shelf = ub.Shelf() - return create_edit_shelf(shelf, title=_(u"Create a Shelf"), page="shelfcreate") + return create_edit_shelf(shelf, page_title=_(u"Create a Shelf"), page="shelfcreate") @shelf.route("/shelf/edit/", methods=["GET", "POST"]) @login_required def edit_shelf(shelf_id): shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() - return create_edit_shelf(shelf, title=_(u"Edit a shelf"), page="shelfedit", shelf_id=shelf_id) + if not check_shelf_edit_permissions(shelf): + flash(_(u"Sorry you are not allowed to edit this shelf"), category="error") + return redirect(url_for('web.index')) + return create_edit_shelf(shelf, page_title=_(u"Edit a shelf"), page="shelfedit", shelf_id=shelf_id) # if shelf ID is set, we are editing a shelf -def create_edit_shelf(shelf, title, page, shelf_id=False): +def create_edit_shelf(shelf, page_title, page, shelf_id=False): sync_only_selected_shelves = current_user.kobo_only_shelves_sync # calibre_db.session.query(ub.Shelf).filter(ub.Shelf.user_id == current_user.id).filter(ub.Shelf.kobo_sync).count() if request.method == "POST": @@ -247,20 +249,20 @@ def create_edit_shelf(shelf, title, page, shelf_id=False): shelf.is_public = 1 if to_save.get("is_public") else 0 if config.config_kobo_sync: shelf.kobo_sync = True if to_save.get("kobo_sync") else False - - if check_shelf_is_unique(shelf, to_save, shelf_id): - shelf.name = to_save["title"] + shelf_title = to_save.get("title", "") + if check_shelf_is_unique(shelf, shelf_title, shelf_id): + shelf.name = shelf_title if not shelf_id: shelf.user_id = int(current_user.id) ub.session.add(shelf) shelf_action = "created" - flash_text = _(u"Shelf %(title)s created", title=to_save["title"]) + flash_text = _(u"Shelf %(title)s created", title=shelf_title) else: shelf_action = "changed" - flash_text = _(u"Shelf %(title)s changed", title=to_save["title"]) + flash_text = _(u"Shelf %(title)s changed", title=shelf_title) try: ub.session.commit() - log.info(u"Shelf {} {}".format(to_save["title"], shelf_action)) + log.info(u"Shelf {} {}".format(shelf_title, shelf_action)) flash(flash_text, category="success") return redirect(url_for('shelf.show_shelf', shelf_id=shelf.id)) except (OperationalError, InvalidRequestError) as ex: @@ -274,37 +276,37 @@ def create_edit_shelf(shelf, title, page, shelf_id=False): flash(_(u"There was an error"), category="error") return render_title_template('shelf_edit.html', shelf=shelf, - title=title, + title=page_title, page=page, kobo_sync_enabled=config.config_kobo_sync, sync_only_selected_shelves=sync_only_selected_shelves) -def check_shelf_is_unique(shelf, to_save, shelf_id=False): +def check_shelf_is_unique(shelf, title, shelf_id=False): if shelf_id: ident = ub.Shelf.id != shelf_id else: ident = true() if shelf.is_public == 1: is_shelf_name_unique = ub.session.query(ub.Shelf) \ - .filter((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 1)) \ + .filter((ub.Shelf.name == title) & (ub.Shelf.is_public == 1)) \ .filter(ident) \ .first() is None if not is_shelf_name_unique: - log.error("A public shelf with the name '{}' already exists.".format(to_save["title"])) - flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]), + log.error("A public shelf with the name '{}' already exists.".format(title)) + flash(_(u"A public shelf with the name '%(title)s' already exists.", title=title), category="error") else: is_shelf_name_unique = ub.session.query(ub.Shelf) \ - .filter((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 0) & + .filter((ub.Shelf.name == title) & (ub.Shelf.is_public == 0) & (ub.Shelf.user_id == int(current_user.id))) \ .filter(ident) \ .first() is None if not is_shelf_name_unique: - log.error("A private shelf with the name '{}' already exists.".format(to_save["title"])) - flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]), + log.error("A private shelf with the name '{}' already exists.".format(title)) + flash(_(u"A private shelf with the name '%(title)s' already exists.", title=title), category="error") return is_shelf_name_unique @@ -378,7 +380,9 @@ def order_shelf(shelf_id): def change_shelf_order(shelf_id, order): - result = calibre_db.session.query(db.Books).join(ub.BookShelf, ub.BookShelf.book_id == db.Books.id) \ + result = calibre_db.session.query(db.Books).outerjoin(db.books_series_link, + db.Books.id == db.books_series_link.c.book)\ + .outerjoin(db.Series).join(ub.BookShelf, ub.BookShelf.book_id == db.Books.id) \ .filter(ub.BookShelf.shelf == shelf_id).order_by(*order).all() for index, entry in enumerate(result): book = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \ @@ -408,9 +412,11 @@ def render_show_shelf(shelf_type, shelf_id, page_no, sort_param): if sort_param == 'old': change_shelf_order(shelf_id, [db.Books.timestamp]) if sort_param == 'authaz': - change_shelf_order(shelf_id, [db.Books.author_sort.asc()]) + change_shelf_order(shelf_id, [db.Books.author_sort.asc(), db.Series.name, db.Books.series_index]) if sort_param == 'authza': - change_shelf_order(shelf_id, [db.Books.author_sort.desc()]) + change_shelf_order(shelf_id, [db.Books.author_sort.desc(), + db.Series.name.desc(), + db.Books.series_index.desc()]) page = "shelf.html" pagesize = 0 else: diff --git a/cps/static/css/caliBlur.css b/cps/static/css/caliBlur.css index f479f07f..3b226acb 100644 --- a/cps/static/css/caliBlur.css +++ b/cps/static/css/caliBlur.css @@ -3291,7 +3291,6 @@ div.btn-group[role=group][aria-label="Download, send to Kindle, reading"] .dropd transform-origin: center top; border: 0; left: 0 !important; - max-height: 80%; overflow-y: auto; } diff --git a/cps/static/js/caliBlur.js b/cps/static/js/caliBlur.js index 1a2814fc..ce230730 100644 --- a/cps/static/js/caliBlur.js +++ b/cps/static/js/caliBlur.js @@ -413,7 +413,11 @@ if($("body.advsearch").length > 0) { }); $('#add-to-shelf').height("40px"); function search_dropdownToggle() { - topPos = $("#add-to-shelf").offset().top-20; + if( $("#add-to-shelf").length) { + topPos = $("#add-to-shelf").offset().top - 20; + } else { + topPos = 0 + } if ($('div[aria-label="Add to shelves"]').length > 0) { position = $('div[aria-label="Add to shelves"]').offset().left diff --git a/cps/static/js/main.js b/cps/static/js/main.js index 2f7dc172..69788c2e 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -609,7 +609,10 @@ $(function() { if (xhr.status < 400) { $("#spinning_success").hide(); clearInterval(rebootInterval); - handle_response(data.result); + if (data.result) { + handle_response(data.result); + data.result = ""; + } } }, }); diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 15653aa9..a8a35909 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -662,33 +662,34 @@ function move_header_elements() { } }); $(".multi_selector").selectpicker(); - - if (! $._data($(".multi_head").get(0), "events") ) { - // Functions have to be here, otherwise the callbacks are not fired if visible columns are changed - $(".multi_head").on("click", function () { - var val = $(this).data("set"); - var field = $(this).data("name"); - var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); - var values = $("#" + field).val(); - confirmDialog( - "restrictions", - "GeneralChangeModal", - 0, - function () { - $.ajax({ - method: "post", - url: window.location.pathname + "/../../ajax/editlistusers/" + field, - data: {"pk": result, "value": values, "action": val}, - success: function (data) { - handleListServerResponse(data); - }, - error: function (data) { - handleListServerResponse([{type: "danger", message: data.responseText}]) - }, - }); - } - ); - }); + if ($(".multi_head").length) { + if (!$._data($(".multi_head").get(0), "events")) { + // Functions have to be here, otherwise the callbacks are not fired if visible columns are changed + $(".multi_head").on("click", function () { + var val = $(this).data("set"); + var field = $(this).data("name"); + var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); + var values = $("#" + field).val(); + confirmDialog( + "restrictions", + "GeneralChangeModal", + 0, + function () { + $.ajax({ + method: "post", + url: window.location.pathname + "/../../ajax/editlistusers/" + field, + data: {"pk": result, "value": values, "action": val}, + success: function (data) { + handleListServerResponse(data); + }, + error: function (data) { + handleListServerResponse([{type: "danger", message: data.responseText}]) + }, + }); + } + ); + }); + } } $("#user_delete_selection").click(function () { @@ -700,38 +701,41 @@ function move_header_elements() { $("#select_default_language").on("change", function () { selectHeader(this, "default_language"); }); - - if (! $._data($(".check_head").get(0), "events") ) { - $(".check_head").on("change", function () { - var val = $(this).data("set"); - var name = $(this).data("name"); - var data = $(this).data("val"); - checkboxHeader(val, name, data); - }); + if ($(".check_head").length) { + if (!$._data($(".check_head").get(0), "events")) { + $(".check_head").on("change", function () { + var val = $(this).data("set"); + var name = $(this).data("name"); + var data = $(this).data("val"); + checkboxHeader(val, name, data); + }); + } } - if (! $._data($(".button_head").get(0), "events") ) { - $(".button_head").on("click", function () { - var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); - confirmDialog( - "btndeluser", - "GeneralDeleteModal", - 0, - function () { - $.ajax({ - method: "post", - url: window.location.pathname + "/../../ajax/deleteuser", - data: {"userid": result}, - success: function (data) { - selections = selections.filter((el) => !result.includes(el)); - handleListServerResponse(data); - }, - error: function (data) { - handleListServerResponse([{type: "danger", message: data.responseText}]) - }, - }); - } - ); - }); + if ($(".button_head").length) { + if (!$._data($(".button_head").get(0), "events")) { + $(".button_head").on("click", function () { + var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id); + confirmDialog( + "btndeluser", + "GeneralDeleteModal", + 0, + function () { + $.ajax({ + method: "post", + url: window.location.pathname + "/../../ajax/deleteuser", + data: {"userid": result}, + success: function (data) { + selections = selections.filter((el) => !result.includes(el)); + handleListServerResponse(data); + }, + error: function (data) { + handleListServerResponse([{type: "danger", message: data.responseText}]) + }, + }); + } + ); + }); + } } } diff --git a/cps/subproc_wrapper.py b/cps/subproc_wrapper.py index 27375686..3cc4a070 100644 --- a/cps/subproc_wrapper.py +++ b/cps/subproc_wrapper.py @@ -52,10 +52,11 @@ def process_wait(command, serr=subprocess.PIPE, pattern=""): p.wait() for line in p.stdout.readlines(): if isinstance(line, bytes): - line = line.decode('utf-8') + line = line.decode('utf-8', errors="ignore") match = re.search(pattern, line, re.IGNORECASE) if match and ret_val == "": ret_val = match + break p.stdout.close() p.stderr.close() return ret_val diff --git a/cps/templates/author.html b/cps/templates/author.html index 4e32db80..b011bae8 100644 --- a/cps/templates/author.html +++ b/cps/templates/author.html @@ -5,7 +5,7 @@ {% if author is not none %}
{%if author.image_url is not none %} - {{author.name|safe}} + {{author.name|safe}} {% endif %} {%if author.about is not none %} @@ -37,14 +37,14 @@
-

{{entry.title|shortentitle}}

+

{{entry.title|shortentitle}}

{% for author in entry.authors %} @@ -104,11 +104,11 @@

-

{{entry.title|shortentitle}}

+

{{entry.title|shortentitle}}

{% for author in entry.authors %} {% if loop.index > g.config_authors_max and g.config_authors_max != 0 %} diff --git a/cps/templates/book_edit.html b/cps/templates/book_edit.html index 4f583a44..9be7558c 100644 --- a/cps/templates/book_edit.html +++ b/cps/templates/book_edit.html @@ -3,7 +3,7 @@ {% if book %}

- {{ book.title }} + {{ book.title }}
{% if g.user.role_delete_books() %}
diff --git a/cps/templates/config_db.html b/cps/templates/config_db.html index 0d1d1bce..e0e1bfd1 100644 --- a/cps/templates/config_db.html +++ b/cps/templates/config_db.html @@ -20,7 +20,7 @@
- {% if not gdriveError %} + {% if not gdriveError and config.config_use_google_drive %} {% if show_authenticate_google_drive and config.config_use_google_drive %}
{{_('Authenticate Google Drive')}} diff --git a/cps/templates/detail.html b/cps/templates/detail.html index e5a67cde..76a5a87d 100644 --- a/cps/templates/detail.html +++ b/cps/templates/detail.html @@ -4,7 +4,7 @@
- {{ entry.title }} + {{ entry.title }}
@@ -122,7 +122,7 @@ {% endif %} {% if entry.series|length > 0 %} -

{{_('Book')}} {{entry.series_index}} {{_('of')}} {{entry.series[0].name}}

+

{{_('Book')}} {{entry.series_index|formatfloat(2)}} {{_('of')}} {{entry.series[0].name}}

{% endif %} {% if entry.languages.__len__() > 0 %} diff --git a/cps/templates/discover.html b/cps/templates/discover.html index d57994b4..f6d8207d 100644 --- a/cps/templates/discover.html +++ b/cps/templates/discover.html @@ -9,7 +9,7 @@ {% if entry.has_cover is defined %} - {{ entry.title }} + {{ entry.title }} {% if entry.id in read_book_ids %}{% endif %} @@ -17,7 +17,7 @@
-

{{entry.title|shortentitle}}

+

{{entry.title|shortentitle}}

{% for author in entry.authors %} diff --git a/cps/templates/grid.html b/cps/templates/grid.html index 1e79c43d..f8beffc5 100644 --- a/cps/templates/grid.html +++ b/cps/templates/grid.html @@ -29,14 +29,14 @@

diff --git a/cps/templates/index.html b/cps/templates/index.html index d300fc65..b11500e4 100644 --- a/cps/templates/index.html +++ b/cps/templates/index.html @@ -9,14 +9,14 @@
-

{{entry.title|shortentitle}}

+

{{entry.title|shortentitle}}

{% for author in entry.authors %} @@ -86,14 +86,14 @@

-

{{entry.title|shortentitle}}

+

{{entry.title|shortentitle}}

{% for author in entry.authors %} diff --git a/cps/templates/read.html b/cps/templates/read.html index b38f783c..3d2566e0 100644 --- a/cps/templates/read.html +++ b/cps/templates/read.html @@ -3,7 +3,7 @@ - ePub Reader + {{_('epub Reader')}} | {{title}} diff --git a/cps/templates/readcbr.html b/cps/templates/readcbr.html index bad5f1f6..411e3fdd 100644 --- a/cps/templates/readcbr.html +++ b/cps/templates/readcbr.html @@ -1,10 +1,10 @@ - Comic Reader + {{_('Comic Reader')}} | {{title}} diff --git a/cps/templates/readdjvu.html b/cps/templates/readdjvu.html index c192ffcb..9771b7c9 100644 --- a/cps/templates/readdjvu.html +++ b/cps/templates/readdjvu.html @@ -7,7 +7,7 @@ -Djvu HTML5 browser demo +{{_('DJVU Reader')}} | {{title}} diff --git a/cps/templates/readpdf.html b/cps/templates/readpdf.html index 7af417ea..586625cc 100644 --- a/cps/templates/readpdf.html +++ b/cps/templates/readpdf.html @@ -26,7 +26,7 @@ See https://github.com/adobe-type-tools/cmap-resources - {{_('PDF reader')}} + {{_('PDF Reader')}} | {{title}} diff --git a/cps/templates/readtxt.html b/cps/templates/readtxt.html index da862fb2..ea294948 100644 --- a/cps/templates/readtxt.html +++ b/cps/templates/readtxt.html @@ -3,7 +3,7 @@ - {{_('Basic txt Reader')}} + {{_('txt Reader')}} | {{title}} diff --git a/cps/templates/search.html b/cps/templates/search.html index 81ab2d99..b63819be 100644 --- a/cps/templates/search.html +++ b/cps/templates/search.html @@ -44,7 +44,7 @@ {% if entry.has_cover is defined %} - {{ entry.title }} + {{ entry.title }} {% if entry.id in read_book_ids %}{% endif %} @@ -52,7 +52,7 @@

-

{{entry.title|shortentitle}}

+

{{entry.title|shortentitle}}

{% for author in entry.authors %} diff --git a/cps/templates/shelf.html b/cps/templates/shelf.html index 1ad79dbd..7ee96f7d 100644 --- a/cps/templates/shelf.html +++ b/cps/templates/shelf.html @@ -31,14 +31,14 @@

-

{{entry.title|shortentitle}}

+

{{entry.title|shortentitle}}

{% for author in entry.authors %} diff --git a/cps/templates/shelf_order.html b/cps/templates/shelf_order.html index 1e49f29a..fc53a69a 100644 --- a/cps/templates/shelf_order.html +++ b/cps/templates/shelf_order.html @@ -9,9 +9,9 @@

diff --git a/cps/templates/shelfdown.html b/cps/templates/shelfdown.html index 77251e02..1d781310 100644 --- a/cps/templates/shelfdown.html +++ b/cps/templates/shelfdown.html @@ -35,7 +35,7 @@
-

{{entry.title|shortentitle}}

+

{{entry.title|shortentitle}}

{% for author in entry.authors %} {{author.name.replace('|',',')}} diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html index cc83a1b5..b4429faf 100644 --- a/cps/templates/user_edit.html +++ b/cps/templates/user_edit.html @@ -67,15 +67,14 @@

{% endif %}
- {% for element in sidebar %} - {% if element['config_show'] %} -
- - -
- {% endif %} - {% endfor %} - + {% for element in sidebar %} + {% if element['config_show'] %} +
+ + +
+ {% endif %} + {% endfor %}
@@ -131,32 +130,33 @@
{% endif %}
-
-
{{_('Save')}}
- {% if not profile %} -
{{_('Cancel')}}
- {% endif %} - {% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %} -
{{_('Delete User')}}
- {% endif %} +
+
{{_('Save')}}
+ {% if not profile %} +
{{_('Cancel')}}
+ {% endif %} + {% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %} +
{{_('Delete User')}}
+ {% endif %} +
-