From 3764c33a3a2fe7bbe5c9136c62b7b7f8a47267de Mon Sep 17 00:00:00 2001 From: zelazna Date: Tue, 1 Oct 2019 14:15:39 +0200 Subject: [PATCH 01/49] Add the posibility to change the username --- cps/admin.py | 19 +++++++++++++++++-- cps/templates/user_edit.html | 2 +- cps/ub.py | 1 + cps/web.py | 19 ++++++++++++++++++- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index ccb07d84..4f293058 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -35,7 +35,7 @@ from flask_login import login_required, current_user, logout_user from flask_babel import gettext as _ from sqlalchemy import and_ from sqlalchemy.exc import IntegrityError -from sqlalchemy.sql.expression import func +from sqlalchemy.sql.expression import func, exists from werkzeug.security import generate_password_hash from . import constants, logger, helper, services @@ -563,7 +563,6 @@ def edit_user(user_id): else: if "password" in to_save and to_save["password"]: content.password = generate_password_hash(to_save["password"]) - anonymous = content.is_anonymous content.role = constants.selected_roles(to_save) if anonymous: @@ -601,6 +600,22 @@ def edit_user(user_id): return render_title_template("user_edit.html", translations=translations, languages=languages, new_user=0, content=content, downloads=downloads, registered_oauth=oauth_check, title=_(u"Edit User %(nick)s", nick=content.nickname), page="edituser") + if "nickname" in to_save and to_save["nickname"] != content.nickname: + existing_nickname = ub.session.query(exists().where( + ub.User.nickname == to_save["nickname"])).scalar() + if not existing_nickname: + content.nickname = to_save["nickname"] + else: + flash(_(u"This username is already taken."), category="error") + return render_title_template("user_edit.html", + translations=translations, + languages=languages, + new_user=0, content=content, + downloads=downloads, + registered_oauth=oauth_check, + title=_(u"Edit User %(nick)s", + nick=content.nickname), + page="edituser") if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail: content.kindle_mail = to_save["kindle_mail"] diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html index e22a9415..99aaeeb3 100644 --- a/cps/templates/user_edit.html +++ b/cps/templates/user_edit.html @@ -3,7 +3,7 @@

{{title}}

- {% if g.user and g.user.role_admin() and new_user %} + {% if g.user or g.user.role_admin() or new_user %}
diff --git a/cps/ub.py b/cps/ub.py index b262e0eb..84ac3e2a 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -27,6 +27,7 @@ from flask import g from flask_babel import gettext as _ from flask_login import AnonymousUserMixin from werkzeug.local import LocalProxy + try: from flask_dance.consumer.backend.sqla import OAuthConsumerMixin oauth_support = True diff --git a/cps/web.py b/cps/web.py index a946573e..1ba3e1d3 100644 --- a/cps/web.py +++ b/cps/web.py @@ -38,7 +38,8 @@ from flask import render_template, request, redirect, send_from_directory, make_ from flask_babel import gettext as _ from flask_login import login_user, logout_user, login_required, current_user from sqlalchemy.exc import IntegrityError -from sqlalchemy.sql.expression import text, func, true, false, not_, and_ +from sqlalchemy.sql.expression import text, func, true, false, not_, and_, \ + exists from werkzeug.exceptions import default_exceptions from werkzeug.datastructures import Headers from werkzeug.security import generate_password_hash, check_password_hash @@ -1252,6 +1253,22 @@ def profile(): return render_title_template("user_edit.html", content=current_user, downloads=downloads, title=_(u"%(name)s's profile", name=current_user.nickname), page="me", registered_oauth=oauth_check, oauth_status=oauth_status) + if "nickname" in to_save and to_save["nickname"] != current_user.nickname: + existing_nickname = ub.session.query(exists().where( + ub.User.nickname == to_save["nickname"])).scalar() + if not existing_nickname: + current_user.nickname = to_save["nickname"] + else: + flash(_(u"This username is already taken."), category="error") + return render_title_template("user_edit.html", + translations=translations, + languages=languages, + new_user=0, content=current_user, + downloads=downloads, + registered_oauth=oauth_check, + title=_(u"Edit User %(nick)s", + nick=current_user.nickname), + page="edituser") current_user.email = to_save["email"] if "show_random" in to_save and to_save["show_random"] == "on": current_user.random_books = 1 From fadd085b57100e53ed3eba13c998912b1b260037 Mon Sep 17 00:00:00 2001 From: Jan Guzej Date: Sat, 26 Oct 2019 11:54:27 +0200 Subject: [PATCH 02/49] Checking and warrying on possible duplicity --- cps/editbooks.py | 6 ++++++ cps/helper.py | 10 ++++++++++ cps/templates/layout.html | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/cps/editbooks.py b/cps/editbooks.py index 2da9991f..f8431725 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -567,6 +567,12 @@ def upload(): filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir) saved_filename = os.path.join(filepath, title_dir + meta.extension.lower()) + if unicode(title) != u'Unknown' and unicode(authr) != u'Unknown': + entry = helper.check_exists_book(authr, title) + if entry: + book_html = flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ") + + Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning") + # check if file path exists, otherwise create it, copy file to calibre path and delete temp file if not os.path.exists(filepath): try: diff --git a/cps/helper.py b/cps/helper.py index b45300c0..81addeb8 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -780,7 +780,17 @@ def get_download_link(book_id, book_format): else: abort(404) +def check_exists_book(authr,title): + db.session.connection().connection.connection.create_function("lower", 1, lcase) + q = list() + authorterms = re.split(r'\s*&\s*', authr) + for authorterm in authorterms: + q.append(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + authorterm + "%"))) + return db.session.query(db.Books).filter( + and_(db.Books.authors.any(and_(*q)), + func.lower(db.Books.title).ilike("%" + title + "%") + )).first() ############### Database Helper functions diff --git a/cps/templates/layout.html b/cps/templates/layout.html index 72eba890..9ffb04f8 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -99,6 +99,11 @@
{{ message[1] }}
{%endif%} + {%if message[0] == "warning" %} +
+
{{ message[1] }}
+
+ {%endif%} {%if message[0] == "success" %}
{{ message[1] }}
From 94ae9937f0fdef4b6c253af177d30c8442c4e6f6 Mon Sep 17 00:00:00 2001 From: Jan Guzej Date: Sat, 26 Oct 2019 13:45:06 +0200 Subject: [PATCH 03/49] Add flash show book template --- cps/templates/book_exists_flash.html | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 cps/templates/book_exists_flash.html diff --git a/cps/templates/book_exists_flash.html b/cps/templates/book_exists_flash.html new file mode 100644 index 00000000..b0855120 --- /dev/null +++ b/cps/templates/book_exists_flash.html @@ -0,0 +1,3 @@ + + {{entry.title|shortentitle}} + \ No newline at end of file From d1afdb4aacdc614fd519fe20ced9a1741de484e7 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Thu, 31 Oct 2019 15:44:36 +0100 Subject: [PATCH 04/49] Fix #1074, #1071 --- cps/server.py | 3 +++ 1 file changed, 3 insertions(+) mode change 100644 => 100755 cps/server.py diff --git a/cps/server.py b/cps/server.py old mode 100644 new mode 100755 index e5fe78e4..43792ecd --- a/cps/server.py +++ b/cps/server.py @@ -146,6 +146,9 @@ class WebServer(object): self.unix_socket_file = None def _start_tornado(self): + if os.name == 'nt': + import asyncio + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) log.info('Starting Tornado server on %s', _readable_listen_address(self.listen_address, self.listen_port)) # Max Buffersize set to 200MB ) From f782dc1857b5e0874d2c84eaed352437758630df Mon Sep 17 00:00:00 2001 From: Ghighi Eftimie Date: Thu, 14 Nov 2019 12:08:19 +0200 Subject: [PATCH 05/49] fix for search title --- cps/web.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cps/web.py b/cps/web.py index 3edc42a5..98efa5a2 100644 --- a/cps/web.py +++ b/cps/web.py @@ -798,9 +798,9 @@ def search(): for element in entries: ids.append(element.id) searched_ids[current_user.id] = ids - return render_title_template('search.html', searchterm=term, entries=entries, page="search") + return render_title_template('search.html', searchterm=term, entries=entries, title=_(u"Search"), page="search") else: - return render_title_template('search.html', searchterm="", page="search") + return render_title_template('search.html', searchterm="", title=_(u"Search"), page="search") @web.route("/advanced_search", methods=['GET']) From 1f5edffccf58ff4f918efafb7faec1096506208d Mon Sep 17 00:00:00 2001 From: Jony <23194385+jony0008@users.noreply.github.com> Date: Sat, 16 Nov 2019 07:49:07 +0100 Subject: [PATCH 06/49] Fix typo and update translation --- cps/translations/sv/LC_MESSAGES/messages.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cps/translations/sv/LC_MESSAGES/messages.po b/cps/translations/sv/LC_MESSAGES/messages.po index c3f5f79b..d847219c 100644 --- a/cps/translations/sv/LC_MESSAGES/messages.po +++ b/cps/translations/sv/LC_MESSAGES/messages.po @@ -140,7 +140,7 @@ msgstr "Ett okänt fel uppstod. Försök igen senare." #: cps/admin.py:645 msgid "Logfile viewer" -msgstr "Visaren för lggfiler" +msgstr "Visaren för loggfil" #: cps/admin.py:680 msgid "Requesting update package" @@ -192,7 +192,7 @@ msgstr "Allmänt fel" #: cps/converter.py:31 msgid "not configured" -msgstr "" +msgstr "inte konfigurerad" #: cps/editbooks.py:214 cps/editbooks.py:393 msgid "Error opening eBook. File does not exist or file is not accessible" From 62ea8b8913ea31c881cf3c98c3c009d57ffc713b Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sat, 16 Nov 2019 10:09:34 +0100 Subject: [PATCH 07/49] Logging to stdout, proposal form #1078 --- cps/logger.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cps/logger.py b/cps/logger.py index 54be25e7..77a721d3 100644 --- a/cps/logger.py +++ b/cps/logger.py @@ -18,6 +18,7 @@ from __future__ import division, print_function, unicode_literals import os +import sys import inspect import logging from logging import Formatter, StreamHandler @@ -34,6 +35,7 @@ DEFAULT_LOG_LEVEL = logging.INFO DEFAULT_LOG_FILE = os.path.join(_CONFIG_DIR, "calibre-web.log") DEFAULT_ACCESS_LOG = os.path.join(_CONFIG_DIR, "access.log") LOG_TO_STDERR = '/dev/stderr' +LOG_TO_STDOUT = '/dev/stdout' logging.addLevelName(logging.WARNING, "WARN") logging.addLevelName(logging.CRITICAL, "CRIT") @@ -112,9 +114,13 @@ def setup(log_file, log_level=None): return logging.debug("logging to %s level %s", log_file, r.level) - if log_file == LOG_TO_STDERR: - file_handler = StreamHandler() - file_handler.baseFilename = LOG_TO_STDERR + if log_file == LOG_TO_STDERR or log_file == LOG_TO_STDOUT: + if log_file == LOG_TO_STDOUT: + file_handler = StreamHandler(sys.stdout) + file_handler.baseFilename = log_file + else: + file_handler = StreamHandler() + file_handler.baseFilename = log_file else: try: file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2) @@ -164,5 +170,5 @@ class StderrLogger(object): self.log.debug("Logging Error") -# default configuration, before application settngs are applied +# default configuration, before application settings are applied setup(LOG_TO_STDERR, logging.DEBUG if os.environ.get('FLASK_DEBUG') else DEFAULT_LOG_LEVEL) From 78f9ee86b1a60471185619712404705dc83a8527 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Tue, 26 Nov 2019 08:19:03 +0100 Subject: [PATCH 08/49] Fix pdf cover Fix massadding books Add feature inform of duplicate books --- cps/editbooks.py | 6 +++ cps/helper.py | 10 ++++ cps/templates/book_exists_flash.html | 3 ++ cps/templates/layout.html | 5 ++ cps/tess.py | 65 ++++++++++++++++++++++++ cps/uploader.py | 2 +- cps/worker.py | 75 ++++++++++++++++------------ 7 files changed, 134 insertions(+), 32 deletions(-) create mode 100644 cps/templates/book_exists_flash.html create mode 100644 cps/tess.py diff --git a/cps/editbooks.py b/cps/editbooks.py index 2da9991f..f8431725 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -567,6 +567,12 @@ def upload(): filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir) saved_filename = os.path.join(filepath, title_dir + meta.extension.lower()) + if unicode(title) != u'Unknown' and unicode(authr) != u'Unknown': + entry = helper.check_exists_book(authr, title) + if entry: + book_html = flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ") + + Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning") + # check if file path exists, otherwise create it, copy file to calibre path and delete temp file if not os.path.exists(filepath): try: diff --git a/cps/helper.py b/cps/helper.py index edb031bc..9771d57a 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -780,7 +780,17 @@ def get_download_link(book_id, book_format): else: abort(404) +def check_exists_book(authr,title): + db.session.connection().connection.connection.create_function("lower", 1, lcase) + q = list() + authorterms = re.split(r'\s*&\s*', authr) + for authorterm in authorterms: + q.append(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + authorterm + "%"))) + return db.session.query(db.Books).filter( + and_(db.Books.authors.any(and_(*q)), + func.lower(db.Books.title).ilike("%" + title + "%") + )).first() ############### Database Helper functions diff --git a/cps/templates/book_exists_flash.html b/cps/templates/book_exists_flash.html new file mode 100644 index 00000000..b0855120 --- /dev/null +++ b/cps/templates/book_exists_flash.html @@ -0,0 +1,3 @@ + + {{entry.title|shortentitle}} + \ No newline at end of file diff --git a/cps/templates/layout.html b/cps/templates/layout.html index 72eba890..9ffb04f8 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -99,6 +99,11 @@
{{ message[1] }}
{%endif%} + {%if message[0] == "warning" %} +
+
{{ message[1] }}
+
+ {%endif%} {%if message[0] == "success" %}
{{ message[1] }}
diff --git a/cps/tess.py b/cps/tess.py new file mode 100644 index 00000000..e0120e1c --- /dev/null +++ b/cps/tess.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) +# Copyright (C) 2018-2019 OzzieIsaacs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from subproc_wrapper import process_open, cmdlineCall +import os +import sys +import re +import time + +def main(): + quotes = [1, 2] + format_new_ext = '.mobi' + format_old_ext = '.epub' + file_path = '/home/matthias/Dokumente/bücher/Bettina Szramah/Die Giftmischerin TCP_IP (10)/Die Giftmischerin TCP_IP - Bettina, Szrama' + command = ['/opt/calibre/ebook-convert', (file_path + format_old_ext), + (file_path + format_new_ext)] + + #print(command) + #p1 = cmdlineCall(command[0],command[1:]) + #time.sleep(10) + #print(p1) + + p = process_open(command, quotes) + while p.poll() is None: + nextline = p.stdout.readline() + if os.name == 'nt' and sys.version_info < (3, 0): + nextline = nextline.decode('windows-1252') + elif os.name == 'posix' and sys.version_info < (3, 0): + nextline = nextline.decode('utf-8') + # log.debug(nextline.strip('\r\n')) + # parse progress string from calibre-converter + progress = re.search(r"(\d+)%\s.*", nextline) + if progress: + print('Progress:' + str(progress)) + # self.UIqueue[index]['progress'] = progress.group(1) + ' % + + # process returncode + check = p.returncode + calibre_traceback = p.stderr.readlines() + for ele in calibre_traceback: + if sys.version_info < (3, 0): + ele = ele.decode('utf-8') + print(ele.strip('\n')) + if not ele.startswith('Traceback') and not ele.startswith(' File'): + print( "Calibre failed with error: %s" % ele.strip('\n')) + print(str(check)) + +if __name__ == '__main__': + main() diff --git a/cps/uploader.py b/cps/uploader.py index 0ee0c1d9..79ac9417 100644 --- a/cps/uploader.py +++ b/cps/uploader.py @@ -65,7 +65,7 @@ except ImportError as e: use_fb2_meta = False try: - from PIL import PILImage + from PIL import Image as PILImage from PIL import __version__ as PILversion use_PIL = True except ImportError as e: diff --git a/cps/worker.py b/cps/worker.py index 50f35855..e5b4618b 100644 --- a/cps/worker.py +++ b/cps/worker.py @@ -193,21 +193,27 @@ class WorkerThread(threading.Thread): def run(self): main_thread = _get_main_thread() while main_thread.is_alive(): - self.doLock.acquire() - if self.current != self.last: - index = self.current - self.doLock.release() - if self.queue[index]['taskType'] == TASK_EMAIL: - self._send_raw_email() - if self.queue[index]['taskType'] == TASK_CONVERT: - self._convert_any_format() - if self.queue[index]['taskType'] == TASK_CONVERT_ANY: - self._convert_any_format() - # TASK_UPLOAD is handled implicitly + try: self.doLock.acquire() - self.current += 1 - self.doLock.release() - else: + if self.current != self.last: + index = self.current + self.doLock.release() + if self.queue[index]['taskType'] == TASK_EMAIL: + self._send_raw_email() + if self.queue[index]['taskType'] == TASK_CONVERT: + self._convert_any_format() + if self.queue[index]['taskType'] == TASK_CONVERT_ANY: + self._convert_any_format() + # TASK_UPLOAD is handled implicitly + self.doLock.acquire() + self.current += 1 + if self.current > self.last: + self.current = self.last + self.doLock.release() + else: + self.doLock.release() + except Exception as e: + log.exception(e) self.doLock.release() if main_thread.is_alive(): time.sleep(1) @@ -225,7 +231,8 @@ class WorkerThread(threading.Thread): self.queue.pop(index) self.UIqueue.pop(index) # if we are deleting entries before the current index, adjust the index - self.current -= 1 + if index <= self.current and index: + self.current -= 1 self.last = len(self.queue) def get_taskstatus(self): @@ -248,7 +255,7 @@ class WorkerThread(threading.Thread): self.doLock.release() self.UIqueue[index]['stat'] = STAT_STARTED self.queue[index]['starttime'] = datetime.now() - self.UIqueue[index]['formStarttime'] = self.queue[self.current]['starttime'] + self.UIqueue[index]['formStarttime'] = self.queue[index]['starttime'] curr_task = self.queue[index]['taskType'] filename = self._convert_ebook_format() if filename: @@ -390,8 +397,7 @@ class WorkerThread(threading.Thread): def add_convert(self, file_path, bookid, user_name, taskMessage, settings, kindle_mail=None): - addLock = threading.Lock() - addLock.acquire() + self.doLock.acquire() if self.last >= 20: self._delete_completed_tasks() # progress, runtime, and status = 0 @@ -405,13 +411,12 @@ class WorkerThread(threading.Thread): 'runtime': '0 s', 'stat': STAT_WAITING,'id': self.id, 'taskType': task } ) self.last=len(self.queue) - addLock.release() + self.doLock.release() def add_email(self, subject, filepath, attachment, settings, recipient, user_name, taskMessage, text): # if more than 20 entries in the list, clean the list - addLock = threading.Lock() - addLock.acquire() + self.doLock.acquire() if self.last >= 20: self._delete_completed_tasks() # progress, runtime, and status = 0 @@ -422,23 +427,31 @@ class WorkerThread(threading.Thread): self.UIqueue.append({'user': user_name, 'formStarttime': '', 'progress': " 0 %", 'taskMess': taskMessage, 'runtime': '0 s', 'stat': STAT_WAITING,'id': self.id, 'taskType': TASK_EMAIL }) self.last=len(self.queue) - addLock.release() + self.doLock.release() def add_upload(self, user_name, taskMessage): # if more than 20 entries in the list, clean the list - addLock = threading.Lock() - addLock.acquire() + self.doLock.acquire() + + if self.last >= 20: self._delete_completed_tasks() # progress=100%, runtime=0, and status finished + log.info("Last " + str(self.last)) + log.info("Current " + str(self.current)) + log.info("id" + str(self.id)) + for i in range(0, len(self.queue)): + message = '%s:%s' % (i, self.queue[i].items()) + log.info(message) + self.id += 1 - self.queue.append({'starttime': datetime.now(), 'taskType': TASK_UPLOAD}) - self.UIqueue.append({'user': user_name, 'formStarttime': '', 'progress': "100 %", 'taskMess': taskMessage, + starttime = datetime.now() + self.queue.append({'starttime': starttime, 'taskType': TASK_UPLOAD}) + self.UIqueue.append({'user': user_name, 'formStarttime': starttime, 'progress': "100 %", 'taskMess': taskMessage, 'runtime': '0 s', 'stat': STAT_FINISH_SUCCESS,'id': self.id, 'taskType': TASK_UPLOAD}) - self.UIqueue[self.current]['formStarttime'] = self.queue[self.current]['starttime'] + # self.UIqueue[self.current]['formStarttime'] = self.queue[self.current]['starttime'] self.last=len(self.queue) - addLock.release() - + self.doLock.release() def _send_raw_email(self): self.doLock.acquire() @@ -525,7 +538,7 @@ class WorkerThread(threading.Thread): self.doLock.release() self.UIqueue[index]['stat'] = STAT_FAIL self.UIqueue[index]['progress'] = "100 %" - self.UIqueue[index]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime'] + self.UIqueue[index]['formRuntime'] = datetime.now() - self.queue[index]['starttime'] self.UIqueue[index]['message'] = error_message def _handleSuccess(self): @@ -534,7 +547,7 @@ class WorkerThread(threading.Thread): self.doLock.release() self.UIqueue[index]['stat'] = STAT_FINISH_SUCCESS self.UIqueue[index]['progress'] = "100 %" - self.UIqueue[index]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime'] + self.UIqueue[index]['formRuntime'] = datetime.now() - self.queue[index]['starttime'] _worker = WorkerThread() From 8af178c19c20e5db06e4b8ee554ee2182e219b76 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Tue, 26 Nov 2019 10:46:06 +0100 Subject: [PATCH 09/49] Fix for gdrive not working #1081 --- cps/admin.py | 5 ++++- cps/gdriveutils.py | 2 ++ cps/worker.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 25218e51..1862dda8 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -272,6 +272,8 @@ def _configuration_update_helper(): gdrive_secrets = {} gdriveError = gdriveutils.get_error_text(gdrive_secrets) if "config_use_google_drive" in to_save and not config.config_use_google_drive and not gdriveError: + with open(gdriveutils.CLIENT_SECRETS, 'r') as settings: + gdrive_secrets = json.load(settings)['web'] if not gdrive_secrets: return _configuration_result('client_secrets.json is not configured for web application') gdriveutils.update_settings( @@ -413,7 +415,8 @@ def _configuration_result(error_flash=None, gdriveError=None): if gdriveError: gdriveError = _(gdriveError) else: - gdrivefolders = gdriveutils.listRootFolders() + if config.config_use_google_drive and not gdrive_authenticate: + gdrivefolders = gdriveutils.listRootFolders() show_back_button = current_user.is_authenticated show_login_button = config.db_configured and not current_user.is_authenticated diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py index 4ebbb0c7..d950f738 100644 --- a/cps/gdriveutils.py +++ b/cps/gdriveutils.py @@ -584,5 +584,7 @@ def get_error_text(client_secrets=None): filedata = json.load(settings) if 'web' not in filedata: return 'client_secrets.json is not configured for web application' + if 'redirect_uris' not in filedata['web']: + return 'Callback url (redirect url) is missing in client_secrets.json' if client_secrets: client_secrets.update(filedata['web']) diff --git a/cps/worker.py b/cps/worker.py index e5b4618b..5ccf2d8c 100644 --- a/cps/worker.py +++ b/cps/worker.py @@ -213,7 +213,7 @@ class WorkerThread(threading.Thread): else: self.doLock.release() except Exception as e: - log.exception(e) + log.exception(e) self.doLock.release() if main_thread.is_alive(): time.sleep(1) From f8a99c60d892ff5790f8bc141ea798fc3c9f27ab Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 1 Dec 2019 09:33:11 +0100 Subject: [PATCH 10/49] Fix for #1096 (exception on digest request) --- cps/opds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/opds.py b/cps/opds.py index e2930646..9198554b 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -47,7 +47,7 @@ def requires_basic_auth_if_no_ano(f): def decorated(*args, **kwargs): auth = request.authorization if config.config_anonbrowse != 1: - if not auth or not check_auth(auth.username, auth.password): + if not auth or auth.type != 'basic' or not check_auth(auth.username, auth.password): return authenticate() return f(*args, **kwargs) if config.config_login_type == constants.LOGIN_LDAP and services.ldap: From 54079b36ae9525d48514e9242a26633a9ca58498 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 1 Dec 2019 12:21:21 +0100 Subject: [PATCH 11/49] Fix #1095 (epub viewer not working if only viewer rule wa sapplied) --- cps/templates/read.html | 2 +- cps/web.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cps/templates/read.html b/cps/templates/read.html index 88eaba0d..b3caec49 100644 --- a/cps/templates/read.html +++ b/cps/templates/read.html @@ -86,7 +86,7 @@ filePath: "{{ url_for('static', filename='js/libs/') }}", cssPath: "{{ url_for('static', filename='css/') }}", bookmarkUrl: "{{ url_for('web.bookmark', book_id=bookid, book_format='EPUB') }}", - bookUrl: "{{ url_for('web.download_link', book_id=bookid, book_format='epub', anyname='file.epub') }}", + bookUrl: "{{ url_for('web.serve_book', book_id=bookid, book_format='epub', anyname='file.epub') }}", bookmark: "{{ bookmark.bookmark_key if bookmark != None }}", useBookmarks: "{{ g.user.is_authenticated | tojson }}" }; diff --git a/cps/web.py b/cps/web.py index 00db18b9..7aa921e4 100644 --- a/cps/web.py +++ b/cps/web.py @@ -992,10 +992,11 @@ def get_cover(book_id): return get_book_cover(book_id) -@web.route("/show//") +@web.route("/show//", defaults={'anyname': 'None'}) +@web.route("/show///") @login_required_if_no_ano @viewer_required -def serve_book(book_id, book_format): +def serve_book(book_id, book_format, anyname): book_format = book_format.split(".")[0] book = db.session.query(db.Books).filter(db.Books.id == book_id).first() data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == book_format.upper())\ @@ -1010,11 +1011,11 @@ def serve_book(book_id, book_format): return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format) -@web.route("/download//", defaults={'anyname': 'None'}) -@web.route("/download///") +# @web.route("/download//", defaults={'anyname': 'None'}) +@web.route("/download//") @login_required_if_no_ano @download_required -def download_link(book_id, book_format, anyname): +def download_link(book_id, book_format): return get_download_link(book_id, book_format) From fda0ab1e863cf2682eec12cf675e39dffc61cea7 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 1 Dec 2019 12:36:55 +0100 Subject: [PATCH 12/49] FIx for (#1092 listening to mp3 not working) --- cps/editbooks.py | 2 +- cps/templates/listenmp3.html | 94 ------------------------------------ 2 files changed, 1 insertion(+), 95 deletions(-) diff --git a/cps/editbooks.py b/cps/editbooks.py index f8431725..f0156f71 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -567,7 +567,7 @@ def upload(): filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir) saved_filename = os.path.join(filepath, title_dir + meta.extension.lower()) - if unicode(title) != u'Unknown' and unicode(authr) != u'Unknown': + if title != u'Unknown' and authr != u'Unknown': entry = helper.check_exists_book(authr, title) if entry: book_html = flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ") diff --git a/cps/templates/listenmp3.html b/cps/templates/listenmp3.html index 0abc289d..8113c759 100644 --- a/cps/templates/listenmp3.html +++ b/cps/templates/listenmp3.html @@ -17,11 +17,6 @@ - - - {% endblock %} {% block header %} + {% endblock %} diff --git a/cps/templates/search.html b/cps/templates/search.html index dfa42799..c44fbda7 100644 --- a/cps/templates/search.html +++ b/cps/templates/search.html @@ -1,4 +1,7 @@ {% extends "layout.html" %} +{% block header %} + +{% endblock %} {% block body %}
{% if entries|length < 1 %} From c61463447fcf208d71d2501fa9b03dc49d8a5c8e Mon Sep 17 00:00:00 2001 From: Christian Keil Date: Fri, 6 Dec 2019 15:00:01 +0100 Subject: [PATCH 16/49] Merge metadata of uploaded book versions. --- cps/editbooks.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/cps/editbooks.py b/cps/editbooks.py index f0156f71..492333a8 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -360,6 +360,9 @@ def upload_single_file(request, book, book_id): worker.add_upload(current_user.nickname, "" + uploadText + "") + return uploader.process( + saved_filename, *os.path.splitext(requested_file.filename)) + def upload_cover(request, book): if 'btn-upload-cover' in request.files: @@ -393,11 +396,12 @@ def edit_book(book_id): flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error") return redirect(url_for("web.index")) - upload_single_file(request, book, book_id) + meta = upload_single_file(request, book, book_id) if upload_cover(request, book) is True: book.has_cover = 1 try: to_save = request.form.to_dict() + merge_metadata(to_save, meta) # Update book edited_books_id = None #handle book title @@ -531,6 +535,20 @@ def edit_book(book_id): return redirect(url_for('web.show_book', book_id=book.id)) +def merge_metadata(to_save, meta): + if to_save['author_name'].lower() == _(u'unknown'): + to_save['author_name'] = '' + if to_save['book_title'].lower() == _(u'unknown'): + to_save['book_title'] = '' + for s_field, m_field in [ + ('tags', 'tags'), ('author_name', 'author'), ('series', 'series'), + ('series_index', 'series_id'), ('languages', 'languages'), + ('book_title', 'title')]: + to_save[s_field] = to_save[s_field] or getattr(meta, m_field, '') + to_save["description"] = to_save["description"] or Markup( + getattr(meta, 'description', '')).unescape() + + @editbook.route("/upload", methods=["GET", "POST"]) @login_required_if_no_ano @upload_required From e0faad1e591038ccebbd99a8df745f5217aebc13 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 8 Dec 2019 09:40:54 +0100 Subject: [PATCH 17/49] Handle no write permission to tmp folder (#1060) --- cps/editbooks.py | 22 ++++++++++++++++------ cps/uploader.py | 2 +- cps/worker.py | 10 +++------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/cps/editbooks.py b/cps/editbooks.py index f0156f71..acf20651 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -550,13 +550,19 @@ def upload(): flash( _("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext), category="error") - return redirect(url_for('web.index')) + return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') else: flash(_('File to be uploaded must have an extension'), category="error") - return redirect(url_for('web.index')) + return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') # extract metadata from file - meta = uploader.upload(requested_file) + try: + meta = uploader.upload(requested_file) + except (IOError, OSError): + log.error("File %s could not saved to temp dir", requested_file.filename) + flash(_(u"File %(filename)s could not saved to temp dir", + filename= requested_file.filename), category="error") + return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') title = meta.title authr = meta.author tags = meta.tags @@ -570,7 +576,8 @@ def upload(): if title != u'Unknown' and authr != u'Unknown': entry = helper.check_exists_book(authr, title) if entry: - book_html = flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ") + log.info("Uploaded book probably exists in library") + flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ") + Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning") # check if file path exists, otherwise create it, copy file to calibre path and delete temp file @@ -578,16 +585,19 @@ def upload(): try: os.makedirs(filepath) except OSError: + log.error("Failed to create path %s (Permission denied)", filepath) flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error") - return redirect(url_for('web.index')) + return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') try: copyfile(meta.file_path, saved_filename) except OSError: + log.error("Failed to store file %s (Permission denied)", saved_filename) flash(_(u"Failed to store file %(file)s (Permission denied).", file=saved_filename), category="error") - return redirect(url_for('web.index')) + return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') try: os.unlink(meta.file_path) except OSError: + log.error("Failed to delete file %(file)s (Permission denied)", meta.file_path) flash(_(u"Failed to delete file %(file)s (Permission denied).", file= meta.file_path), category="warning") diff --git a/cps/uploader.py b/cps/uploader.py index 79ac9417..b036b6dc 100644 --- a/cps/uploader.py +++ b/cps/uploader.py @@ -195,12 +195,12 @@ def upload(uploadfile): if not os.path.isdir(tmp_dir): os.mkdir(tmp_dir) - filename = uploadfile.filename filename_root, file_extension = os.path.splitext(filename) md5 = hashlib.md5() md5.update(filename.encode('utf-8')) tmp_file_path = os.path.join(tmp_dir, md5.hexdigest()) + log.debug("Temporary file: %s", tmp_file_path) uploadfile.save(tmp_file_path) meta = process(tmp_file_path, filename_root, file_extension) return meta diff --git a/cps/worker.py b/cps/worker.py index 5ccf2d8c..b508c437 100644 --- a/cps/worker.py +++ b/cps/worker.py @@ -437,19 +437,15 @@ class WorkerThread(threading.Thread): if self.last >= 20: self._delete_completed_tasks() # progress=100%, runtime=0, and status finished - log.info("Last " + str(self.last)) - log.info("Current " + str(self.current)) - log.info("id" + str(self.id)) - for i in range(0, len(self.queue)): - message = '%s:%s' % (i, self.queue[i].items()) - log.info(message) + log.debug("Last " + str(self.last)) + log.debug("Current " + str(self.current)) + log.debug("id" + str(self.id)) self.id += 1 starttime = datetime.now() self.queue.append({'starttime': starttime, 'taskType': TASK_UPLOAD}) self.UIqueue.append({'user': user_name, 'formStarttime': starttime, 'progress': "100 %", 'taskMess': taskMessage, 'runtime': '0 s', 'stat': STAT_FINISH_SUCCESS,'id': self.id, 'taskType': TASK_UPLOAD}) - # self.UIqueue[self.current]['formStarttime'] = self.queue[self.current]['starttime'] self.last=len(self.queue) self.doLock.release() From e308a74dc203c52da6ced110d1c8d2dd3e47214b Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Thu, 12 Dec 2019 20:08:16 +0100 Subject: [PATCH 18/49] Fix (#1103) Internal server error with Goodreads and no result for author name on Goodreads --- cps/templates/author.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/templates/author.html b/cps/templates/author.html index 27a16fbc..0d4369a3 100644 --- a/cps/templates/author.html +++ b/cps/templates/author.html @@ -89,7 +89,7 @@
-{% if other_books %} +{% if other_books and author is not none %}

{{_("More by")}} {{ author.name.replace('|',',')|safe }}

From b661c2fa920068a162c1a896f597e4f84a9a3cb0 Mon Sep 17 00:00:00 2001 From: Andrew Roberts Date: Thu, 12 Dec 2019 21:27:38 -0500 Subject: [PATCH 19/49] added config fields to settings table --- cps/config_sql.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cps/config_sql.py b/cps/config_sql.py index 809e97d8..f4af9d66 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -104,6 +104,9 @@ class _Settings(_Base): config_calibre = Column(String) config_rarfile_location = Column(String) + config_allow_reverse_proxy_header_login = Column(Boolean, default=False) + config_reverse_proxy_login_header_name = Column(String) + config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE) def __repr__(self): From af7dbbf1e4a7f68eb283f4953ddbfed3cb436e1f Mon Sep 17 00:00:00 2001 From: Andrew Roberts Date: Thu, 12 Dec 2019 21:27:40 -0500 Subject: [PATCH 20/49] added logic for reverse proxy login --- cps/web.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/cps/web.py b/cps/web.py index 7aa921e4..e88d423d 100644 --- a/cps/web.py +++ b/cps/web.py @@ -116,14 +116,35 @@ web = Blueprint('web', __name__) log = logger.create() # ################################### Login logic and rights management ############################################### +def _fetch_user_by_name(username): + return ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == username.lower()).first() @lm.user_loader def load_user(user_id): return ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() -@lm.header_loader -def load_user_from_header(header_val): +@lm.request_loader +def load_user_from_request(request): + auth_header = request.headers.get("Authorization") + if auth_header: + user = load_user_from_auth_header(auth_header) + if user: + return user + + if config.config_allow_reverse_proxy_header_login: + rp_header_name = config.config_reverse_proxy_login_header_name + if rp_header_name: + rp_header = request.headers.get(rp_header_name) + if rp_header_username: + user = _fetch_user_by_name(rp_header_username) + if user: + return user + + return + + +def load_user_from_auth_header(header_val): if header_val.startswith('Basic '): header_val = header_val.replace('Basic ', '', 1) basic_username = basic_password = '' @@ -133,7 +154,7 @@ def load_user_from_header(header_val): basic_password = header_val.split(':')[1] except TypeError: pass - user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == basic_username.lower()).first() + user = _fetch_user_by_name(basic_username) if user and check_password_hash(str(user.password), basic_password): return user return From 77b0954c7007693c083167f14609fbaafa107b89 Mon Sep 17 00:00:00 2001 From: Andrew Roberts Date: Thu, 12 Dec 2019 21:28:50 -0500 Subject: [PATCH 21/49] use a macro for the display of boolean settings --- cps/templates/admin.html | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 17b84f34..5324f80e 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -1,4 +1,7 @@ {% extends "layout.html" %} +{% macro display_bool_setting(setting_value) -%} + {% if setting_value %}{% else %}{% endif %} +{%- endmacro %} {% block body %}
@@ -23,11 +26,11 @@ {{user.email}} {{user.kindle_mail}} {{user.downloads.count()}} - {% if user.role_admin() %}{% else %}{% endif %} - {% if user.role_download() %}{% else %}{% endif %} - {% if user.role_viewer() %}{% else %}{% endif %} - {% if user.role_upload() %}{% else %}{% endif %} - {% if user.role_edit() %}{% else %}{% endif %} + {{ display_bool_setting(user.role_admin()) }} + {{ display_bool_setting(user.role_download()) }} + {{ display_bool_setting(user.role_viewer()) }} + {{ display_bool_setting(user.role_upload()) }} + {{ display_bool_setting(user.role_edit()) }} {% endif %} {% endfor %} @@ -83,19 +86,19 @@
{{_('Uploading')}}
-
{% if config.config_uploading %}{% else %}{% endif %}
+
{{ display_bool_setting(config.config_uploading) }}
{{_('Anonymous browsing')}}
-
{% if config.config_anonbrowse %}{% else %}{% endif %}
+
{{ display_bool_setting(config.config_anonbrowse) }}
{{_('Public registration')}}
-
{% if config.config_public_reg %}{% else %}{% endif %}
+
{{ display_bool_setting(config.config_public_reg) }}
{{_('Remote login')}}
-
{% if config.config_remote_login %}{% else %}{% endif %}
+
{{ display_bool_setting(config.config_remote_login) }}
From f0760c07d8119afb60a70026ee6e952ae167dafc Mon Sep 17 00:00:00 2001 From: Andrew Roberts Date: Thu, 12 Dec 2019 21:31:12 -0500 Subject: [PATCH 22/49] added admin display of reverse proxy settings --- cps/templates/admin.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 5324f80e..e698d014 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -100,6 +100,16 @@
{{_('Remote login')}}
{{ display_bool_setting(config.config_remote_login) }}
+
+
{{_('Reverse proxy login')}}
+
{{ display_bool_setting(config.config_allow_reverse_proxy_header_login) }}
+
+ {% if config.config_allow_reverse_proxy_header_login %} +
+
{{_('Reverse proxy header name')}}
+
{{ config.config_reverse_proxy_login_header_name }}
+
+ {% endif %}
From 9351ff032ff1b953dc027a758a902cc10232207e Mon Sep 17 00:00:00 2001 From: Andrew Roberts Date: Thu, 12 Dec 2019 21:31:17 -0500 Subject: [PATCH 23/49] whitespace --- cps/templates/admin.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/templates/admin.html b/cps/templates/admin.html index e698d014..a7770c59 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -1,6 +1,6 @@ {% extends "layout.html" %} {% macro display_bool_setting(setting_value) -%} - {% if setting_value %}{% else %}{% endif %} + {% if setting_value %}{% else %}{% endif %} {%- endmacro %} {% block body %}
From 39b6b100f9e70388994f74d5098ebc622d6f9be9 Mon Sep 17 00:00:00 2001 From: Andrew Roberts Date: Thu, 12 Dec 2019 21:31:19 -0500 Subject: [PATCH 24/49] whitespace --- cps/templates/config_edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index 85b9598e..b46d07f5 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -200,7 +200,7 @@
{% if feature_support['ldap'] %} -
+
From efcee0a7b7ecf33e6527a98b05b1acdd82c1d2f6 Mon Sep 17 00:00:00 2001 From: Andrew Roberts Date: Thu, 12 Dec 2019 21:31:21 -0500 Subject: [PATCH 25/49] added reverse proxy configuration form and handler --- cps/admin.py | 4 ++++ cps/templates/config_edit.html | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/cps/admin.py b/cps/admin.py index 1862dda8..6cb5bfdb 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -347,6 +347,10 @@ def _configuration_update_helper(): _config_int("config_updatechannel") + # Reverse proxy login configuration + _config_checkbox("config_allow_reverse_proxy_header_login") + _config_string("config_reverse_proxy_login_header_name") + # GitHub OAuth configuration if config.config_login_type == constants.LOGIN_OAUTH: active_oauths = 0 diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index b46d07f5..0d28b8ea 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -271,6 +271,16 @@
{% endif %} {% endif %} +
+ + +
+
+
+ + +
+
From 3dc372c5737b50e7bfc59bbf4cbd9241bbbdd1fe Mon Sep 17 00:00:00 2001 From: Andrew Roberts Date: Thu, 12 Dec 2019 21:38:45 -0500 Subject: [PATCH 26/49] fixed typo --- cps/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/web.py b/cps/web.py index e88d423d..c6a3e1c4 100644 --- a/cps/web.py +++ b/cps/web.py @@ -135,7 +135,7 @@ def load_user_from_request(request): if config.config_allow_reverse_proxy_header_login: rp_header_name = config.config_reverse_proxy_login_header_name if rp_header_name: - rp_header = request.headers.get(rp_header_name) + rp_header_username = request.headers.get(rp_header_name) if rp_header_username: user = _fetch_user_by_name(rp_header_username) if user: From 86fe970651e4ae15c818d7fc4e21e2cf9d762537 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sat, 14 Dec 2019 22:22:27 +0100 Subject: [PATCH 27/49] More fixes for googledrive --- cps/admin.py | 7 ++++++- cps/gdrive.py | 6 +++++- cps/gdriveutils.py | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 1862dda8..3c64b01b 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -170,6 +170,9 @@ def update_view_configuration(): _config_int("config_books_per_page") _config_int("config_authors_max") + if config.config_google_drive_watch_changes_response: + config.config_google_drive_watch_changes_response = json.dumps(config.config_google_drive_watch_changes_response) + config.config_default_role = constants.selected_roles(to_save) config.config_default_role &= ~constants.ROLE_ANONYMOUS @@ -258,6 +261,7 @@ def _configuration_update_helper(): db_change = False to_save = request.form.to_dict() + # _config_dict = lambda x: config.set_from_dictionary(to_save, x, lambda y: y['id']) _config_string = lambda x: config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y) _config_int = lambda x: config.set_from_dictionary(to_save, x, int) _config_checkbox = lambda x: config.set_from_dictionary(to_save, x, lambda y: y == "on", False) @@ -415,7 +419,8 @@ def _configuration_result(error_flash=None, gdriveError=None): if gdriveError: gdriveError = _(gdriveError) else: - if config.config_use_google_drive and not gdrive_authenticate: + # if config.config_use_google_drive and\ + if not gdrive_authenticate: gdrivefolders = gdriveutils.listRootFolders() show_back_button = current_user.is_authenticated diff --git a/cps/gdrive.py b/cps/gdrive.py index 263c829b..a95060b0 100644 --- a/cps/gdrive.py +++ b/cps/gdrive.py @@ -23,6 +23,7 @@ from __future__ import division, print_function, unicode_literals import os +import sys import hashlib import json import tempfile @@ -141,7 +142,10 @@ def on_received_watch_confirmation(): response = gdriveutils.getChangeById(gdriveutils.Gdrive.Instance().drive, j['id']) log.debug('%r', response) if response: - dbpath = os.path.join(config.config_calibre_dir, "metadata.db") + if sys.version_info < (3, 0): + dbpath = os.path.join(config.config_calibre_dir, "metadata.db") + else: + dbpath = os.path.join(config.config_calibre_dir, "metadata.db").encode() if not response['deleted'] and response['file']['title'] == 'metadata.db' and response['file']['md5Checksum'] != hashlib.md5(dbpath): tmpDir = tempfile.gettempdir() log.info('Database file updated') diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py index d950f738..f70747a6 100644 --- a/cps/gdriveutils.py +++ b/cps/gdriveutils.py @@ -47,6 +47,10 @@ CREDENTIALS = os.path.join(_CONFIG_DIR, 'gdrive_credentials') CLIENT_SECRETS = os.path.join(_CONFIG_DIR, 'client_secrets.json') log = logger.create() +if gdrive_support: + logger.get('googleapiclient.discovery_cache').setLevel(logger.logging.ERROR) + if not logger.is_debug_enabled(): + logger.get('googleapiclient.discovery').setLevel(logger.logging.ERROR) class Singleton: From 2215bf3d7f7feba7a904b57316f800bb92421846 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 15 Dec 2019 11:35:07 +0100 Subject: [PATCH 28/49] Implemented #1083 (Advanced search for extensions) --- cps/templates/search_form.html | 22 +++++++++++++++++++++- cps/web.py | 23 +++++++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/cps/templates/search_form.html b/cps/templates/search_form.html index 4cb20a02..6a64085d 100644 --- a/cps/templates/search_form.html +++ b/cps/templates/search_form.html @@ -31,7 +31,7 @@ -
+
{% for tag in tags %}
{% endif%} + +
+
+ {% for extension in extensions %} + + {% endfor %} +
+
+ +
+
+ {% for extension in extensions %} + + {% endfor %} +
+
diff --git a/cps/web.py b/cps/web.py index 7aa921e4..7823be39 100644 --- a/cps/web.py +++ b/cps/web.py @@ -431,6 +431,8 @@ def get_matching_tags(): title_input = request.args.get('book_title') include_tag_inputs = request.args.getlist('include_tag') exclude_tag_inputs = request.args.getlist('exclude_tag') + include_extension_inputs = request.args.getlist('include_extension') + exclude_extension_inputs = request.args.getlist('exclude_extension') q = q.filter(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + author_input + "%")), func.lower(db.Books.title).ilike("%" + title_input + "%")) if len(include_tag_inputs) > 0: @@ -439,6 +441,12 @@ def get_matching_tags(): if len(exclude_tag_inputs) > 0: for tag in exclude_tag_inputs: q = q.filter(not_(db.Books.tags.any(db.Tags.id == tag))) + '''if len(include_extension_inputs) > 0: + for tag in exclude_tag_inputs: + q = q.filter(not_(db.Books.tags.any(db.Tags.id == tag))) + if len(exclude_extension_inputs) > 0: + for tag in exclude_tag_inputs: + q = q.filter(not_(db.Books.tags.any(db.Tags.id == tag)))''' for book in q: for tag in book.tags: if tag.id not in tag_dict['tags']: @@ -818,6 +826,8 @@ def advanced_search(): exclude_series_inputs = request.args.getlist('exclude_serie') include_languages_inputs = request.args.getlist('include_language') exclude_languages_inputs = request.args.getlist('exclude_language') + include_extension_inputs = request.args.getlist('include_extension') + exclude_extension_inputs = request.args.getlist('exclude_extension') author_name = request.args.get("author_name") book_title = request.args.get("book_title") @@ -843,7 +853,8 @@ def advanced_search(): if include_tag_inputs or exclude_tag_inputs or include_series_inputs or exclude_series_inputs or \ include_languages_inputs or exclude_languages_inputs or author_name or book_title or \ - publisher or pub_start or pub_end or rating_low or rating_high or description or cc_present: + publisher or pub_start or pub_end or rating_low or rating_high or description or cc_present or \ + include_extension_inputs or exclude_extension_inputs: searchterm = [] searchterm.extend((author_name.replace('|', ','), book_title, publisher)) if pub_start: @@ -872,6 +883,8 @@ def advanced_search(): searchterm.extend([_(u"Rating <= %(rating)s", rating=rating_high)]) if rating_low: searchterm.extend([_(u"Rating >= %(rating)s", rating=rating_low)]) + searchterm.extend(ext for ext in include_extension_inputs) + searchterm.extend(ext for ext in exclude_extension_inputs) # handle custom columns for c in cc: if request.args.get('custom_column_' + str(c.id)): @@ -896,6 +909,10 @@ def advanced_search(): q = q.filter(db.Books.series.any(db.Series.id == serie)) for serie in exclude_series_inputs: q = q.filter(not_(db.Books.series.any(db.Series.id == serie))) + for extension in include_extension_inputs: + q = q.filter(db.Books.data.any(db.Data.format == extension)) + for extension in exclude_extension_inputs: + q = q.filter(not_(db.Books.data.any(db.Data.format == extension))) if current_user.filter_language() != "all": q = q.filter(db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())) else: @@ -936,11 +953,13 @@ def advanced_search(): # tags = db.session.query(db.Tags).order_by(db.Tags.name).all() tags = db.session.query(db.Tags).filter(tags_filters()).order_by(db.Tags.name).all() series = db.session.query(db.Series).order_by(db.Series.name).all() + extensions = db.session.query(db.Data) \ + .group_by(db.Data.format).order_by(db.Data.format).all() if current_user.filter_language() == u"all": languages = speaking_language() else: languages = None - return render_title_template('search_form.html', tags=tags, languages=languages, + return render_title_template('search_form.html', tags=tags, languages=languages, extensions=extensions, series=series, title=_(u"search"), cc=cc, page="advsearch") From c33623efeea9ec5be5afa8c81bceed5dea10e332 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 15 Dec 2019 13:32:34 +0100 Subject: [PATCH 29/49] Unified wording for recently added books, series, categories, etc in opds and web UI ( #1045) Added file formats and languages to opds feed --- cps/opds.py | 68 +++++++++++++++++++++++++++++++++++++++-- cps/templates/feed.xml | 4 ++- cps/templates/index.xml | 20 ++++++++++-- 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/cps/opds.py b/cps/opds.py index 9198554b..f5cc4673 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -31,11 +31,13 @@ from flask_login import current_user from sqlalchemy.sql.expression import func, text, or_, and_ from werkzeug.security import check_password_hash -from . import constants, logger, config, db, ub, services -from .helper import fill_indexpage, get_download_link, get_book_cover +from . import constants, logger, config, db, ub, services, get_locale, isoLanguages +from .helper import fill_indexpage, get_download_link, get_book_cover, speaking_language from .pagination import Pagination from .web import common_filters, get_search_results, render_read_books, download_required - +from flask_babel import gettext as _ +from babel import Locale as LC +from babel.core import UnknownLocaleError opds = Blueprint('opds', __name__) @@ -213,6 +215,66 @@ def feed_series(book_id): db.Books, db.Books.series.any(db.Series.id == book_id), [db.Books.series_index]) return render_xml_template('feed.xml', entries=entries, pagination=pagination) +@opds.route("/opds/formats") +@requires_basic_auth_if_no_ano +def feed_formatindex(): + off = request.args.get("offset") or 0 + entries = db.session.query(db.Data).join(db.Books).filter(common_filters()) \ + .group_by(db.Data.format).order_by(db.Data.format).all() + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, + len(entries)) + for entry in entries: + entry.name = entry.format + entry.id = entry.format + return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_format', pagination=pagination) + + +@opds.route("/opds/formats/") +@requires_basic_auth_if_no_ano +def feed_format(book_id): + off = request.args.get("offset") or 0 + entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), + db.Books, db.Books.data.any(db.Data.format == book_id.upper()), [db.Books.timestamp.desc()]) + return render_xml_template('feed.xml', entries=entries, pagination=pagination) + +@opds.route("/opds/language") +@opds.route("/opds/language/") +@requires_basic_auth_if_no_ano +def feed_languagesindex(): + off = request.args.get("offset") or 0 + if current_user.filter_language() == u"all": + languages = speaking_language() + else: + try: + cur_l = LC.parse(current_user.filter_language()) + except UnknownLocaleError: + cur_l = None + languages = db.session.query(db.Languages).filter( + db.Languages.lang_code == current_user.filter_language()).all() + if cur_l: + languages[0].name = cur_l.get_language_name(get_locale()) + else: + languages[0].name = _(isoLanguages.get(part3=languages[0].lang_code).name) + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, + len(languages)) + return render_xml_template('feed.xml', listelements=languages, folder='opds.feed_languages', pagination=pagination) + + +@opds.route("/opds/language/") +@requires_basic_auth_if_no_ano +def feed_languages(book_id): + off = request.args.get("offset") or 0 + entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), + db.Books, db.Books.languages.any(db.Languages.id == book_id), [db.Books.timestamp.desc()]) + '''for entry in entries: + for index in range(0, len(entry.languages)): + try: + entry.languages[index].language_name = LC.parse(entry.languages[index].lang_code).get_language_name( + get_locale()) + except UnknownLocaleError: + entry.languages[index].language_name = _( + isoLanguages.get(part3=entry.languages[index].lang_code).name)''' + return render_xml_template('feed.xml', entries=entries, pagination=pagination) @opds.route("/opds/shelfindex/", defaults={'public': 0}) @opds.route("/opds/shelfindex/") diff --git a/cps/templates/feed.xml b/cps/templates/feed.xml index 16548a91..37b7765e 100644 --- a/cps/templates/feed.xml +++ b/cps/templates/feed.xml @@ -53,7 +53,9 @@ {{entry.publishers[0].name}} {% endif %} - {{entry.language}} + {% for lang in entry.languages %} + {{lang.lang_code}} + {% endfor %} {% for tag in entry.tags %} {{_('Popular publications from this catalog based on Rating.')}} - {{_('New Books')}} + {{_('Recently added Books')}} {{url_for('opds.feed_new')}} {{ current_time }} @@ -72,19 +72,33 @@ {{_('Books ordered by publisher')}} - {{_('Category list')}} + {{_('Categories')}} {{url_for('opds.feed_categoryindex')}} {{ current_time }} {{_('Books ordered by category')}} - {{_('Series list')}} + {{_('Series')}} {{url_for('opds.feed_seriesindex')}} {{ current_time }} {{_('Books ordered by series')}} + + {{_('Languages')}} + + {{url_for('opds.feed_languagesindex')}} + {{ current_time }} + {{_('Books ordered by Languages')}} + + + {{_('File formats')}} + + {{url_for('opds.feed_formatindex')}} + {{ current_time }} + {{_('Books ordered by file formats')}} + {{_('Public Shelves')}} From b6d7207ec3a53b3c83b17f275bde3f90e1f3f4e3 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 15 Dec 2019 13:33:38 +0100 Subject: [PATCH 30/49] Added platform information for better debugging --- cps/about.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cps/about.py b/cps/about.py index 630243f4..aa1e866e 100644 --- a/cps/about.py +++ b/cps/about.py @@ -22,6 +22,7 @@ from __future__ import division, print_function, unicode_literals import sys +import platform import sqlite3 from collections import OrderedDict @@ -48,6 +49,7 @@ about = flask.Blueprint('about', __name__) _VERSIONS = OrderedDict( + Platform = ' '.join(platform.uname()), Python=sys.version, WebServer=server.VERSION, Flask=flask.__version__, @@ -65,6 +67,7 @@ _VERSIONS = OrderedDict( Unidecode = unidecode_version, Flask_SimpleLDAP = u'installed' if bool(services.ldap) else u'not installed', Goodreads = u'installed' if bool(services.goodreads_support) else u'not installed', + ) _VERSIONS.update(uploader.get_versions()) From eabc6e23be014ae921989ba31515ef28691af422 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 15 Dec 2019 17:08:17 +0100 Subject: [PATCH 31/49] Test Email now send to user's email address (#834) Added forgot/reset password routine (#1098, #1063) --- cps/admin.py | 24 +++++++++--------------- cps/helper.py | 14 ++++++++++++++ cps/templates/login.html | 3 +++ cps/web.py | 37 ++++++++++++++++++++++++++----------- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index 3c64b01b..57796080 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -36,11 +36,10 @@ from flask_babel import gettext as _ from sqlalchemy import and_ from sqlalchemy.exc import IntegrityError from sqlalchemy.sql.expression import func -from werkzeug.security import generate_password_hash from . import constants, logger, helper, services from . import db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils -from .helper import speaking_language, check_valid_domain, send_test_mail, generate_random_password, send_registration_mail +from .helper import speaking_language, check_valid_domain, send_test_mail, reset_password, generate_password_hash from .gdriveutils import is_gdrive_ready, gdrive_support from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano @@ -524,15 +523,15 @@ def update_mailsettings(): config.save() if to_save.get("test"): - if current_user.kindle_mail: - result = send_test_mail(current_user.kindle_mail, current_user.nickname) + if current_user.email: + result = send_test_mail(current_user.email, current_user.nickname) if result is None: - flash(_(u"Test e-mail successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail), + flash(_(u"Test e-mail successfully send to %(kindlemail)s", kindlemail=current_user.email), category="success") else: flash(_(u"There was an error sending the Test e-mail: %(res)s", res=result), category="error") else: - flash(_(u"Please configure your kindle e-mail address first..."), category="error") + flash(_(u"Please configure your e-mail address first..."), category="error") else: flash(_(u"E-mail server settings updated"), category="success") @@ -644,15 +643,10 @@ def reset_password(user_id): if not config.config_public_reg: abort(404) if current_user is not None and current_user.is_authenticated: - existing_user = ub.session.query(ub.User).filter(ub.User.id == user_id).first() - password = generate_random_password() - existing_user.password = generate_password_hash(password) - try: - ub.session.commit() - send_registration_mail(existing_user.email, existing_user.nickname, password, True) - flash(_(u"Password for user %(user)s reset", user=existing_user.nickname), category="success") - except Exception: - ub.session.rollback() + ret, message = reset_password(user_id) + if ret == 1: + flash(_(u"Password for user %(user)s reset", user=message), category="success") + else: flash(_(u"An unknown error occurred. Please try again later."), category="error") return redirect(url_for('admin.admin')) diff --git a/cps/helper.py b/cps/helper.py index 9771d57a..025fc4d1 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -41,6 +41,7 @@ from flask_babel import gettext as _ from flask_login import current_user from sqlalchemy.sql.expression import true, false, and_, or_, text, func from werkzeug.datastructures import Headers +from werkzeug.security import generate_password_hash try: from urllib.parse import quote @@ -407,6 +408,19 @@ def delete_book_gdrive(book, book_format): return error +def reset_password(user_id): + existing_user = ub.session.query(ub.User).filter(ub.User.id == user_id).first() + password = generate_random_password() + existing_user.password = generate_password_hash(password) + try: + ub.session.commit() + send_registration_mail(existing_user.email, existing_user.nickname, password, True) + return (1, existing_user.nickname) + except Exception: + ub.session.rollback() + return (0, None) + + def generate_random_password(): s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?" passlen = 8 diff --git a/cps/templates/login.html b/cps/templates/login.html index 4f01157b..4a259287 100644 --- a/cps/templates/login.html +++ b/cps/templates/login.html @@ -18,6 +18,9 @@
+ {% if config.config_login_type == 0 and mail%} + + {% endif %} {% if config.config_remote_login %} {{_('Log in with magic link')}} {% endif %} diff --git a/cps/web.py b/cps/web.py index 7823be39..572ac969 100644 --- a/cps/web.py +++ b/cps/web.py @@ -38,8 +38,7 @@ from flask import render_template, request, redirect, send_from_directory, make_ from flask_babel import gettext as _ from flask_login import login_user, logout_user, login_required, current_user from sqlalchemy.exc import IntegrityError -from sqlalchemy.sql.expression import text, func, true, false, not_, and_, \ - exists +from sqlalchemy.sql.expression import text, func, true, false, not_, and_, exists from werkzeug.exceptions import default_exceptions from werkzeug.datastructures import Headers from werkzeug.security import generate_password_hash, check_password_hash @@ -50,7 +49,7 @@ from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download from .helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \ order_authors, get_typeahead, render_task_status, json_serial, get_cc_columns, \ get_book_cover, get_download_link, send_mail, generate_random_password, send_registration_mail, \ - check_send_to_kindle, check_read_formats, lcase, tags_filters + check_send_to_kindle, check_read_formats, lcase, tags_filters, reset_password from .pagination import Pagination from .redirect import redirect_back @@ -1043,7 +1042,7 @@ def download_link(book_id, book_format): @download_required def send_to_kindle(book_id, book_format, convert): settings = config.get_mail_settings() - if settings.get("mail_server", "mail.example.com") == "mail.example.com": + if settings.get("mail_server", "mail.example.org") == "mail.example.org": flash(_(u"Please configure the SMTP mail settings first..."), category="error") elif current_user.kindle_mail: result = send_mail(book_id, book_format, convert, current_user.kindle_mail, config.config_calibre_dir, @@ -1140,17 +1139,33 @@ def login(): log.info('LDAP Login failed for user "%s" IP-adress: %s', form['username'], ipAdress) flash(_(u"Wrong Username or Password"), category="error") else: - if user and check_password_hash(str(user.password), form['password']) and user.nickname != "Guest": - login_user(user, remember=True) - flash(_(u"You are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") - return redirect_back(url_for("web.index")) ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) - log.info('Login failed for user "%s" IP-adress: %s', form['username'], ipAdress) - flash(_(u"Wrong Username or Password"), category="error") + if 'forgot' in form and form['forgot'] == 'forgot': + if user != None and user.nickname != "Guest": + ret, __ = reset_password(user.id) + if ret == 1: + flash(_(u"New Password was send to your email address"), category="info") + log.info('Password reset for user "%s" IP-adress: %s', form['username'], ipAdress) + else: + flash(_(u"An unknown error occurred. Please try again later."), category="error") + else: + flash(_(u"Please enter valid username to reset password"), category="error") + log.info('Username missing for password reset IP-adress: %s', ipAdress) + else: + if user and check_password_hash(str(user.password), form['password']) and user.nickname != "Guest": + login_user(user, remember=True) + flash(_(u"You are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") + return redirect_back(url_for("web.index")) + else: + log.info('Login failed for user "%s" IP-adress: %s', form['username'], ipAdress) + flash(_(u"Wrong Username or Password"), category="error") + settings = config.get_mail_settings() + mail_configured = bool(settings.get("mail_server", "mail.example.org") != "mail.example.org") next_url = url_for('web.index') - return render_title_template('login.html', title=_(u"login"), next_url=next_url, config=config, page="login") + return render_title_template('login.html', title=_(u"login"), next_url=next_url, config=config, + mail = mail_configured, page="login") @web.route('/logout') From 7098d08888a2f0e340737ba8f97202f81d3712cf Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 15 Dec 2019 18:44:02 +0100 Subject: [PATCH 32/49] Added option to convert AZW3 to mobi for sending to kindle --- cps/editbooks.py | 4 ++-- cps/helper.py | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cps/editbooks.py b/cps/editbooks.py index 51ed9e48..91134db6 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -729,7 +729,7 @@ def convert_bookformat(book_id): if (book_format_from is None) or (book_format_to is None): flash(_(u"Source or destination format for conversion missing"), category="error") - return redirect(request.environ["HTTP_REFERER"]) + return redirect(url_for('editbook.edit_book', book_id=book_id)) log.info('converting: book id: %s from: %s to: %s', book_id, book_format_from, book_format_to) rtn = helper.convert_book_format(book_id, config.config_calibre_dir, book_format_from.upper(), @@ -741,4 +741,4 @@ def convert_bookformat(book_id): category="success") else: flash(_(u"There was an error converting this book: %(res)s", res=rtn), category="error") - return redirect(request.environ["HTTP_REFERER"]) + return redirect(url_for('editbook.edit_book', book_id=book_id)) diff --git a/cps/helper.py b/cps/helper.py index 025fc4d1..2b92ef75 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -167,10 +167,10 @@ def check_send_to_kindle(entry): if 'EPUB' in formats and not 'MOBI' in formats: bookformats.append({'format': 'Mobi','convert':1, 'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Mobi')}) - '''if config.config_ebookconverter == 2: - if 'EPUB' in formats and not 'AZW3' in formats: - bookformats.append({'format': 'Azw3','convert':1, - 'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Azw3')})''' + if config.config_ebookconverter == 2: + if 'AZW3' in formats and not 'MOBI' in formats: + bookformats.append({'format': 'Mobi','convert':2, + 'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Azw3',format='Mobi')}) return bookformats else: log.error(u'Cannot find book entry %d', entry.id) @@ -197,9 +197,13 @@ def send_mail(book_id, book_format, convert, kindle_mail, calibrepath, user_id): """Send email with attachments""" book = db.session.query(db.Books).filter(db.Books.id == book_id).first() - if convert: + if convert == 1: # returns None if success, otherwise errormessage return convert_book_format(book_id, calibrepath, u'epub', book_format.lower(), user_id, kindle_mail) + if convert == 2: + # returns None if success, otherwise errormessage + return convert_book_format(book_id, calibrepath, u'azw3', book_format.lower(), user_id, kindle_mail) + for entry in iter(book.data): if entry.format.upper() == book_format.upper(): From b586a32843bf9bf5f4528a053d177fc0b967af8c Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 22 Dec 2019 15:24:22 +0100 Subject: [PATCH 33/49] Fix #1115 (comic reader not working under iOS, maybe invalid issue) Improvement for #925 (Next/Prev buttons are bigger) --- cps/static/css/kthoom.css | 33 ----------------------- cps/static/css/listen.css | 24 +---------------- cps/static/css/main.css | 22 +++++++-------- cps/static/js/archive/rarvm.js | 12 +++++++-- cps/static/js/reading/epub.js | 4 ++- cps/templates/readtxt.html | 49 +++++++--------------------------- 6 files changed, 33 insertions(+), 111 deletions(-) diff --git a/cps/static/css/kthoom.css b/cps/static/css/kthoom.css index 6dfb9967..d7668156 100644 --- a/cps/static/css/kthoom.css +++ b/cps/static/css/kthoom.css @@ -152,39 +152,6 @@ body { max-width: 70%; } -#left { - left: 40px; -} - -#right { - right: 40px; -} - -.arrow { - position: absolute; - top: 50%; - margin-top: -32px; - font-size: 64px; - color: #E2E2E2; - font-family: arial, sans-serif; - font-weight: bold; - cursor: pointer; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.arrow:hover { - color: #777; -} - -.arrow:active, -.arrow.active { - color: #000; -} - th, td { padding: 5px; } diff --git a/cps/static/css/listen.css b/cps/static/css/listen.css index b08cc33c..0e34d163 100644 --- a/cps/static/css/listen.css +++ b/cps/static/css/listen.css @@ -65,28 +65,6 @@ right: 40px; } - .arrow { - position: absolute; - top: 50%; - margin-top: -32px; - font-size: 64px; - color: #E2E2E2; - font-family: arial, sans-serif; - font-weight: bold; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - } - - .arrow:hover { - color: #777; - } - - .arrow:active { - color: #000; - } - xmp, pre, plaintext { @@ -111,4 +89,4 @@ -moz-column-gap: 20px; -webkit-column-gap: 20px; position: relative; - } \ No newline at end of file + } diff --git a/cps/static/css/main.css b/cps/static/css/main.css index 4afb28f7..60470f3b 100644 --- a/cps/static/css/main.css +++ b/cps/static/css/main.css @@ -15,16 +15,10 @@ body { } #main { - /* height: 500px; */ position: absolute; width: 100%; height: 100%; right: 0; - /* left: 40px; */ -/* -webkit-transform: translate(40px, 0); - -moz-transform: translate(40px, 0); */ - - /* border-radius: 5px 0px 0px 5px; */ border-radius: 5px; background: #fff; overflow: hidden; @@ -114,18 +108,20 @@ body { border: none; } -#prev { +#left,#prev { left: 40px; + padding-right:80px; } -#next { +#right,#next { right: 40px; + padding-left:80px; } .arrow { position: absolute; top: 50%; - margin-top: -32px; + margin-top: -192px; font-size: 64px; color: #E2E2E2; font-family: arial, sans-serif; @@ -136,6 +132,8 @@ body { -moz-user-select: none; -ms-user-select: none; user-select: none; + padding-top: 160px; + padding-bottom: 160px; } .arrow:hover { @@ -753,9 +751,9 @@ input:-ms-placeholder { } }*/ -@media only screen -and (min-device-width : 768px) -and (max-device-width : 1024px) +@media only screen +and (min-device-width : 768px) +and (max-device-width : 1024px) and (orientation : landscape) /*and (-webkit-min-device-pixel-ratio: 2)*/ { #viewer{ diff --git a/cps/static/js/archive/rarvm.js b/cps/static/js/archive/rarvm.js index 44e09330..8858fa05 100644 --- a/cps/static/js/archive/rarvm.js +++ b/cps/static/js/archive/rarvm.js @@ -9,10 +9,18 @@ /** * CRC Implementation. */ -/* global Uint8Array, Uint32Array, bitjs, DataView */ +/* global Uint8Array, Uint32Array, bitjs, DataView, mem */ /* exported MAXWINMASK, UnpackFilter */ -var CRCTab = new Array(256).fill(0); +function emptyArr(n, v) { + var arr = []; + for (var i = 0; i < n; i += 1) { + arr[i] = v; + } + return arr; +} + +var CRCTab = emptyArr(256, 0); function initCRC() { for (var i = 0; i < 256; ++i) { diff --git a/cps/static/js/reading/epub.js b/cps/static/js/reading/epub.js index 169c207f..8b7fb075 100644 --- a/cps/static/js/reading/epub.js +++ b/cps/static/js/reading/epub.js @@ -1,12 +1,14 @@ /* global $, calibre, EPUBJS, ePubReader */ +var reader; + (function() { "use strict"; EPUBJS.filePath = calibre.filePath; EPUBJS.cssPath = calibre.cssPath; - var reader = ePubReader(calibre.bookUrl, { + reader = ePubReader(calibre.bookUrl, { restore: true, bookmarks: calibre.bookmark ? [calibre.bookmark] : [] }); diff --git a/cps/templates/readtxt.html b/cps/templates/readtxt.html index 316878f9..46a16d8f 100644 --- a/cps/templates/readtxt.html +++ b/cps/templates/readtxt.html @@ -7,7 +7,7 @@ - + @@ -16,10 +16,10 @@