diff --git a/cps/admin.py b/cps/admin.py index 5286cf8e..424a12b4 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -132,6 +132,7 @@ def admin(): allUser = ub.session.query(ub.User).all() email_settings = config.get_mail_settings() return render_title_template("admin.html", allUser=allUser, email=email_settings, config=config, commit=commit, + feature_support=feature_support, title=_(u"Admin page"), page="admin") @@ -637,6 +638,7 @@ def _configuration_update_helper(): _config_checkbox_int(to_save, "config_public_reg") _config_checkbox_int(to_save, "config_register_email") reboot_required |= _config_checkbox_int(to_save, "config_kobo_sync") + _config_int(to_save, "config_external_port") _config_checkbox_int(to_save, "config_kobo_proxy") _config_string(to_save, "config_upload_formats") diff --git a/cps/config_sql.py b/cps/config_sql.py index 3885bb01..3573abe7 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -57,6 +57,7 @@ class _Settings(_Base): config_calibre_dir = Column(String) config_port = Column(Integer, default=constants.DEFAULT_PORT) + config_external_port = Column(Integer, default=constants.DEFAULT_PORT) config_certfile = Column(String) config_keyfile = Column(String) diff --git a/cps/editbooks.py b/cps/editbooks.py index 103f683c..83b4afd1 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -377,7 +377,8 @@ def edit_book_publisher(to_save, book): if to_save["publisher"]: publisher = to_save["publisher"].rstrip().strip() if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name): - changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session, 'publisher') + changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session, + 'publisher') elif len(book.publishers): changed |= modify_database_object([], book.publishers, db.Publishers, calibre_db.session, 'publisher') return changed diff --git a/cps/epub.py b/cps/epub.py index f863db61..bdba0607 100644 --- a/cps/epub.py +++ b/cps/epub.py @@ -22,6 +22,7 @@ import zipfile from lxml import etree from . import isoLanguages +from .helper import split_authors from .constants import BookMeta @@ -64,9 +65,9 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): tmp = p.xpath('dc:%s/text()' % s, namespaces=ns) if len(tmp) > 0: if s == 'creator': - epub_metadata[s] = ' & '.join(p.xpath('dc:%s/text()' % s, namespaces=ns)) + epub_metadata[s] = ' & '.join(split_authors(p.xpath('dc:%s/text()' % s, namespaces=ns))) elif s == 'subject': - epub_metadata[s] = ', '.join(p.xpath('dc:%s/text()' % s, namespaces=ns)) + epub_metadata[s] = ', '.join(p.xpath('dc:%s/text()' % s, namespaces=ns)) else: epub_metadata[s] = p.xpath('dc:%s/text()' % s, namespaces=ns)[0] else: diff --git a/cps/helper.py b/cps/helper.py index 3530d864..e3a0e5b1 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -21,7 +21,6 @@ from __future__ import division, print_function, unicode_literals import sys import os import io -import json import mimetypes import re import shutil @@ -36,7 +35,7 @@ from babel.units import format_unit from flask import send_from_directory, make_response, redirect, abort from flask_babel import gettext as _ from flask_login import current_user -from sqlalchemy.sql.expression import true, false, and_, or_, text, func +from sqlalchemy.sql.expression import true, false, and_, text, func from werkzeug.datastructures import Headers from werkzeug.security import generate_password_hash from . import calibre_db @@ -60,10 +59,9 @@ try: except ImportError: use_PIL = False -from . import logger, config, get_locale, db, ub, isoLanguages +from . import logger, config, get_locale, db, ub from . import gdriveutils as gd from .constants import STATIC_DIR as _STATIC_DIR -from .pagination import Pagination from .subproc_wrapper import process_wait from .services.worker import WorkerThread, STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS from .tasks.email import TaskEmail @@ -240,22 +238,22 @@ def get_valid_filename(value, replace_whitespace=True): value = value[:-1]+u'_' value = value.replace("/", "_").replace(":", "_").strip('\0') if use_unidecode: - value = (unidecode.unidecode(value)).strip() + value = (unidecode.unidecode(value)) else: value = value.replace(u'§', u'SS') value = value.replace(u'ß', u'ss') value = unicodedata.normalize('NFKD', value) re_slugify = re.compile(r'[\W\s-]', re.UNICODE) if isinstance(value, str): # Python3 str, Python2 unicode - value = re_slugify.sub('', value).strip() + value = re_slugify.sub('', value) else: - value = unicode(re_slugify.sub('', value).strip()) + value = unicode(re_slugify.sub('', value)) if replace_whitespace: # *+:\"/<>? are replaced by _ - value = re.sub(r'[\*\+:\\\"/<>\?]+', u'_', value, flags=re.U) + value = re.sub(r'[*+:\\\"/<>?]+', u'_', value, flags=re.U) # pipe has to be replaced with comma - value = re.sub(r'[\|]+', u',', value, flags=re.U) - value = value[:128] + value = re.sub(r'[|]+', u',', value, flags=re.U) + value = value[:128].strip() if not value: raise ValueError("Filename cannot be empty") if sys.version_info.major == 3: @@ -264,6 +262,22 @@ def get_valid_filename(value, replace_whitespace=True): return value.decode('utf-8') +def split_authors(values): + authors_list = [] + for value in values: + authors = re.split('[&;]', value) + for author in authors: + commas = author.count(',') + if commas == 1: + author_split = author.split(',') + authors_list.append(author_split[1].strip() + ' ' + author_split[0].strip()) + elif commas > 1: + authors_list.extend([x.strip() for x in author.split(',')]) + else: + authors_list.append(author.strip()) + return authors_list + + def get_sorted_author(value): try: if ',' not in value: @@ -271,7 +285,10 @@ def get_sorted_author(value): combined = "(" + ")|(".join(regexes) + ")" value = value.split(" ") if re.match(combined, value[-1].upper()): - value2 = value[-2] + ", " + " ".join(value[:-2]) + " " + value[-1] + if len(value) > 1: + value2 = value[-2] + ", " + " ".join(value[:-2]) + " " + value[-1] + else: + value2 = value[0] elif len(value) == 1: value2 = value[0] else: @@ -280,7 +297,10 @@ def get_sorted_author(value): value2 = value except Exception as ex: log.error("Sorting author %s failed: %s", value, ex) - value2 = value + if isinstance(list, value2): + value2 = value[0] + else: + value2 = value return value2 @@ -304,8 +324,8 @@ def delete_book_file(book, calibrepath, book_format=None): log.warning("Deleting book {} failed, path {} has subfolders: {}".format(book.id, book.path, folders)) return True, _("Deleting bookfolder for book %(id)s failed, path has subfolders: %(path)s", - id=book.id, - path=book.path) + id=book.id, + path=book.path) shutil.rmtree(path) except (IOError, OSError) as e: log.error("Deleting book %s failed: %s", book.id, e) @@ -320,8 +340,8 @@ def delete_book_file(book, calibrepath, book_format=None): else: log.error("Deleting book %s failed, book path not valid: %s", book.id, book.path) return True, _("Deleting book %(id)s, book path not valid: %(path)s", - id=book.id, - path=book.path) + id=book.id, + path=book.path) def update_dir_structure_file(book_id, calibrepath, first_author): @@ -367,6 +387,7 @@ def update_dir_structure_file(book_id, calibrepath, first_author): src=path, dest=new_author_path, error=str(ex)) # Rename all files from old names to new names if authordir != new_authordir or titledir != new_titledir: + new_name = "" try: new_name = get_valid_filename(localbook.title) + ' - ' + get_valid_filename(new_authordir) path_name = os.path.join(calibrepath, new_authordir, os.path.basename(path)) @@ -475,14 +496,14 @@ def generate_random_password(): return "".join(s[c % len(s)] for c in os.urandom(passlen)) -def uniq(input): - output = [] - for x in input: - if x not in output: - output.append(x) - return output +def uniq(inpt): + output = [] + for x in inpt: + if x not in output: + output.append(x) + return output -################################## External interface +# ################################# External interface ################################# def update_dir_stucture(book_id, calibrepath, first_author=None): @@ -559,7 +580,6 @@ def save_cover_from_url(url, book_path): return False, _("Cover Format Error") - def save_cover_from_filestorage(filepath, saved_filename, img): if hasattr(img, '_content'): f = open(os.path.join(filepath, saved_filename), "wb") @@ -618,7 +638,6 @@ def save_cover(img, book_path): return save_cover_from_filestorage(os.path.join(config.config_calibre_dir, book_path), "cover.jpg", img) - def do_download_file(book, book_format, client, data, headers): if config.config_use_google_drive: startTime = time.time() @@ -771,6 +790,7 @@ def get_cc_columns(filter_config_custom_read=False): return cc + def get_download_link(book_id, book_format, client): book_format = book_format.split(".")[0] book = calibre_db.get_filtered_book(book_id) diff --git a/cps/kobo.py b/cps/kobo.py index 70c4b45a..a6dfc3f6 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -129,7 +129,7 @@ def HandleSyncRequest(): sync_token = SyncToken.SyncToken.from_headers(request.headers) log.info("Kobo library sync request received.") if not current_app.wsgi_app.is_proxied: - log.debug('Kobo: Received unproxied request, changed request port to server port') + log.debug('Kobo: Received unproxied request, changed request port to external server port') # TODO: Limit the number of books return per sync call, and rely on the sync-continuatation header # instead so that the device triggers another sync. @@ -252,7 +252,7 @@ def generate_sync_response(sync_token, sync_results): @download_required def HandleMetadataRequest(book_uuid): if not current_app.wsgi_app.is_proxied: - log.debug('Kobo: Received unproxied request, changed request port to server port') + log.debug('Kobo: Received unproxied request, changed request port to external server port') log.info("Kobo library metadata request received for book %s" % book_uuid) book = calibre_db.get_book_by_uuid(book_uuid) if not book or not book.data: @@ -269,10 +269,11 @@ def get_download_url_for_book(book, book_format): host = "".join(request.host.split(':')[:-1]) else: host = request.host + return "{url_scheme}://{url_base}:{url_port}/download/{book_id}/{book_format}".format( url_scheme=request.scheme, url_base=host, - url_port=config.config_port, + url_port=config.config_external_port, book_id=book.id, book_format=book_format.lower() ) @@ -924,7 +925,7 @@ def HandleInitRequest(): kobo_resources = NATIVE_KOBO_RESOURCES() if not current_app.wsgi_app.is_proxied: - log.debug('Kobo: Received unproxied request, changed request port to server port') + log.debug('Kobo: Received unproxied request, changed request port to external server port') if ':' in request.host and not request.host.endswith(']'): host = "".join(request.host.split(':')[:-1]) else: @@ -932,8 +933,9 @@ def HandleInitRequest(): calibre_web_url = "{url_scheme}://{url_base}:{url_port}".format( url_scheme=request.scheme, url_base=host, - url_port=config.config_port + url_port=config.config_external_port ) + log.debug('Kobo: Received unproxied request, changed request url to %s', calibre_web_url) kobo_resources["image_host"] = calibre_web_url kobo_resources["image_url_quality_template"] = unquote(calibre_web_url + url_for("kobo.HandleCoverImageRequest", @@ -942,16 +944,14 @@ def HandleInitRequest(): width="{width}", height="{height}", Quality='{Quality}', - isGreyscale='isGreyscale' - )) + isGreyscale='isGreyscale')) kobo_resources["image_url_template"] = unquote(calibre_web_url + url_for("kobo.HandleCoverImageRequest", auth_token=kobo_auth.get_auth_token(), book_uuid="{ImageId}", width="{width}", height="{height}", - isGreyscale='false' - )) + isGreyscale='false')) else: kobo_resources["image_host"] = url_for("web.index", _external=True).strip("/") kobo_resources["image_url_quality_template"] = unquote(url_for("kobo.HandleCoverImageRequest", @@ -970,7 +970,6 @@ def HandleInitRequest(): isGreyscale='false', _external=True)) - response = make_response(jsonify({"Resources": kobo_resources})) response.headers["x-kobo-apitoken"] = "e30=" diff --git a/cps/templates/admin.html b/cps/templates/admin.html index a21fae48..d8cf88db 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -88,6 +88,12 @@
Start Time: 2020-08-14 19:46:42
+Start Time: 2020-08-23 18:29:00
Stop Time: 2020-08-14 21:02:08
+Stop Time: 2020-08-23 19:44:09
Duration: 1h 243 min
+Duration: 1h 212 min