diff --git a/cps/admin.py b/cps/admin.py index 1b8824ca..d5be6839 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -21,6 +21,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import logging import os from flask import Blueprint, flash, redirect, url_for from flask import abort, request, make_response @@ -39,20 +40,26 @@ from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDat import helper from werkzeug.security import generate_password_hash from oauth_bb import oauth_check +try: + from urllib.parse import quote + from imp import reload +except ImportError: + from urllib import quote +feature_support = dict() try: from goodreads.client import GoodreadsClient - goodreads_support = True + feature_support['goodreads'] = True except ImportError: - goodreads_support = False + feature_support['goodreads'] = False try: import rarfile - rar_support = True + feature_support['rar'] = True except ImportError: - rar_support = False - + feature_support['rar'] = False +feature_support['gdrive'] = gdrive_support admi = Blueprint('admin', __name__) @@ -287,7 +294,7 @@ def configuration_helper(origin): db_change = False success = False filedata = None - if gdrive_support is False: + if not feature_support['gdrive']: gdriveError = _('Import of optional Google Drive requirements missing') else: if not os.path.isfile(os.path.join(config.get_main_dir, 'client_secrets.json')): @@ -327,7 +334,7 @@ def configuration_helper(origin): else: flash(_(u'client_secrets.json is not configured for web application'), category="error") return render_title_template("config_edit.html", content=config, origin=origin, - gdrive=gdrive_support, gdriveError=gdriveError, + gdriveError=gdriveError, goodreads=goodreads_support, title=_(u"Basic Configuration"), page="config") # always show google drive settings, but in case of error deny support @@ -353,7 +360,7 @@ def configuration_helper(origin): ub.session.commit() flash(_(u'Keyfile location is not valid, please enter correct path'), category="error") return render_title_template("config_edit.html", content=config, origin=origin, - gdrive=gdrive_support, gdriveError=gdriveError, + gdriveError=gdriveError, goodreads=goodreads_support, title=_(u"Basic Configuration"), page="config") if "config_certfile" in to_save: @@ -365,9 +372,8 @@ def configuration_helper(origin): ub.session.commit() flash(_(u'Certfile location is not valid, please enter correct path'), category="error") return render_title_template("config_edit.html", content=config, origin=origin, - gdrive=gdrive_support, gdriveError=gdriveError, - goodreads=goodreads_support, title=_(u"Basic Configuration"), - page="config") + gdriveError=gdriveError, feature_support=feature_support, + title=_(u"Basic Configuration"), page="config") content.config_uploading = 0 content.config_anonbrowse = 0 content.config_public_reg = 0 @@ -391,9 +397,8 @@ def configuration_helper(origin): ub.session.commit() flash(_(u'Please enter a LDAP provider and a DN'), category="error") return render_title_template("config_edit.html", content=config, origin=origin, - gdrive=gdrive_support, gdriveError=gdriveError, - goodreads=goodreads_support, title=_(u"Basic Configuration"), - page="config") + gdriveError=gdriveError, feature_support=feature_support, + title=_(u"Basic Configuration"), page="config") else: content.config_use_ldap = 1 content.config_ldap_provider_url = to_save["config_ldap_provider_url"] @@ -450,9 +455,8 @@ def configuration_helper(origin): ub.session.commit() flash(_(u'Logfile location is not valid, please enter correct path'), category="error") return render_title_template("config_edit.html", content=config, origin=origin, - gdrive=gdrive_support, gdriveError=gdriveError, - goodreads=goodreads_support, title=_(u"Basic Configuration"), - page="config") + gdriveError=gdriveError, feature_support=feature_support, + title=_(u"Basic Configuration"), page="config") else: content.config_logfile = to_save["config_logfile"] reboot_required = True @@ -465,8 +469,7 @@ def configuration_helper(origin): else: flash(check[1], category="error") return render_title_template("config_edit.html", content=config, origin=origin, - gdrive=gdrive_support, goodreads=goodreads_support, - rarfile_support=rar_support, title=_(u"Basic Configuration")) + feature_support=feature_support, title=_(u"Basic Configuration")) try: if content.config_use_google_drive and is_gdrive_ready() and not \ os.path.exists(os.path.join(content.config_calibre_dir, "metadata.db")): @@ -479,20 +482,18 @@ def configuration_helper(origin): flash(_(u"Calibre-Web configuration updated"), category="success") config.loadSettings() app.logger.setLevel(config.config_log_level) - logging.getLogger("book_formats").setLevel(config.config_log_level) + logging.getLogger("uploader").setLevel(config.config_log_level) except Exception as e: flash(e, category="error") return render_title_template("config_edit.html", content=config, origin=origin, - gdrive=gdrive_support, gdriveError=gdriveError, - goodreads=goodreads_support, rarfile_support=rar_support, + gdriveError=gdriveError, feature_support=feature_support, title=_(u"Basic Configuration"), page="config") if db_change: reload(db) if not db.setup_db(): flash(_(u'DB location is not valid, please enter correct path'), category="error") return render_title_template("config_edit.html", content=config, origin=origin, - gdrive=gdrive_support, gdriveError=gdriveError, - goodreads=goodreads_support, rarfile_support=rar_support, + gdriveError=gdriveError, feature_support=feature_support, title=_(u"Basic Configuration"), page="config") if reboot_required: # stop Server @@ -501,15 +502,14 @@ def configuration_helper(origin): app.logger.info('Reboot required, restarting') if origin: success = True - if is_gdrive_ready() and gdrive_support is True: # and config.config_use_google_drive == True: + if is_gdrive_ready() and feature_support['gdrive'] is True: # and config.config_use_google_drive == True: gdrivefolders = listRootFolders() else: gdrivefolders = list() return render_title_template("config_edit.html", origin=origin, success=success, content=config, show_authenticate_google_drive=not is_gdrive_ready(), - gdrive=gdrive_support, gdriveError=gdriveError, - gdrivefolders=gdrivefolders, rarfile_support=rar_support, - goodreads=goodreads_support, title=_(u"Basic Configuration"), page="config") + gdriveError=gdriveError, gdrivefolders=gdrivefolders, feature_support=feature_support, + title=_(u"Basic Configuration"), page="config") @admi.route("/admin/user/new", methods=["GET", "POST"]) @@ -569,20 +569,20 @@ def new_user(): if not to_save["nickname"] or not to_save["email"] or not to_save["password"]: flash(_(u"Please fill out all fields!"), category="error") return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, - title=_(u"Add new user")) + registered_oauth=oauth_check, title=_(u"Add new user")) content.password = generate_password_hash(to_save["password"]) content.nickname = to_save["nickname"] if config.config_public_reg and not check_valid_domain(to_save["email"]): flash(_(u"E-mail is not from valid domain"), category="error") return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, - title=_(u"Add new user")) + registered_oauth=oauth_check, title=_(u"Add new user")) else: content.email = to_save["email"] try: ub.session.add(content) ub.session.commit() flash(_(u"User '%(user)s' created", user=content.nickname), category="success") - return redirect(url_for('admin')) + return redirect(url_for('admin.admin')) except IntegrityError: ub.session.rollback() flash(_(u"Found an existing account for this e-mail address or nickname."), category="error") @@ -591,7 +591,8 @@ def new_user(): content.sidebar_view = config.config_default_show content.mature_content = bool(config.config_default_show & ub.MATURE_CONTENT) return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, - languages=languages, title=_(u"Add new user"), page="newuser", registered_oauth=oauth_check) + languages=languages, title=_(u"Add new user"), page="newuser", + registered_oauth=oauth_check) @admi.route("/admin/mailsettings", methods=["GET", "POST"]) @@ -649,7 +650,7 @@ def edit_user(user_id): ub.session.query(ub.User).filter(ub.User.id == content.id).delete() ub.session.commit() flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success") - return redirect(url_for('admin')) + return redirect(url_for('admin.admin')) else: if "password" in to_save and to_save["password"]: content.password = generate_password_hash(to_save["password"]) @@ -766,8 +767,8 @@ def edit_user(user_id): ub.session.rollback() flash(_(u"An unknown error occured."), category="error") return render_title_template("user_edit.html", translations=translations, languages=languages, new_user=0, - content=content, downloads=downloads, title=_(u"Edit User %(nick)s", - nick=content.nickname), page="edituser", registered_oauth=oauth_check) + content=content, downloads=downloads, registered_oauth=oauth_check, + title=_(u"Edit User %(nick)s", nick=content.nickname), page="edituser") @admi.route("/admin/resetpassword/") @@ -787,7 +788,7 @@ def reset_password(user_id): except Exception: ub.session.rollback() flash(_(u"An unknown error occurred. Please try again later."), category="error") - return redirect(url_for('admin')) + return redirect(url_for('admin.admin')) @admi.route("/get_update_status", methods=['GET']) diff --git a/cps/editbooks.py b/cps/editbooks.py index 2c20b8a9..c758bb18 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -42,7 +42,7 @@ from iso639 import languages as isoLanguages editbook = Blueprint('editbook', __name__) -EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'html', 'rtf', 'odt'} +EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt'} EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx', 'fb2', 'html', 'rtf', 'odt', 'mp3', 'm4a', 'm4b'} @@ -380,7 +380,7 @@ def upload_single_file(request, book, book_id): # Queue uploader info uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title) global_WorkerThread.add_upload(current_user.nickname, - "" + uploadText + "") + "" + uploadText + "") def upload_cover(request, book): if 'btn-upload-cover' in request.files: @@ -589,10 +589,10 @@ 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('index')) + return redirect(url_for('web.index')) else: flash(_('File to be uploaded must have an extension'), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) # extract metadata from file meta = uploader.upload(requested_file) @@ -612,12 +612,12 @@ def upload(): os.makedirs(filepath) except OSError: flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) try: copyfile(meta.file_path, saved_filename) except OSError: flash(_(u"Failed to store file %(file)s (Permission denied).", file=saved_filename), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) try: os.unlink(meta.file_path) except OSError: diff --git a/cps/gdrive.py b/cps/gdrive.py index 3cd9c1dc..025c2d65 100644 --- a/cps/gdrive.py +++ b/cps/gdrive.py @@ -70,7 +70,7 @@ def google_drive_callback(): f.write(credentials.to_json()) except ValueError as error: app.logger.error(error) - return redirect(url_for('configuration')) + return redirect(url_for('admin.configuration')) @gdrive.route("/gdrive/watch/subscribe") @@ -102,7 +102,7 @@ def watch_gdrive(): else: flash(reason['message'], category="error") - return redirect(url_for('configuration')) + return redirect(url_for('admin.configuration')) @gdrive.route("/gdrive/watch/revoke") @@ -121,7 +121,7 @@ def revoke_watch_gdrive(): ub.session.merge(settings) ub.session.commit() config.loadSettings() - return redirect(url_for('configuration')) + return redirect(url_for('admin.configuration')) @gdrive.route("/gdrive/watch/callback", methods=['GET', 'POST']) diff --git a/cps/helper.py b/cps/helper.py index ba323449..16530410 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -19,13 +19,13 @@ # along with this program. If not, see . -import db -from cps import config +from cps import config, global_WorkerThread, get_locale from flask import current_app as app from tempfile import gettempdir import sys import os import re +import db import unicodedata import worker import time @@ -40,7 +40,6 @@ try: import gdriveutils as gd except ImportError: pass -# import web import random from subproc_wrapper import process_open import ub @@ -244,7 +243,7 @@ def get_sorted_author(value): else: value2 = value except Exception: - web.app.logger.error("Sorting author " + str(value) + "failed") + app.logger.error("Sorting author " + str(value) + "failed") value2 = value return value2 @@ -261,13 +260,13 @@ def delete_book_file(book, calibrepath, book_format=None): else: if os.path.isdir(path): if len(next(os.walk(path))[1]): - web.app.logger.error( + app.logger.error( "Deleting book " + str(book.id) + " failed, path has subfolders: " + book.path) return False shutil.rmtree(path, ignore_errors=True) return True else: - web.app.logger.error("Deleting book " + str(book.id) + " failed, book path not valid: " + book.path) + app.logger.error("Deleting book " + str(book.id) + " failed, book path not valid: " + book.path) return False @@ -290,7 +289,7 @@ def update_dir_structure_file(book_id, calibrepath, first_author): if not os.path.exists(new_title_path): os.renames(path, new_title_path) else: - web.app.logger.info("Copying title: " + path + " into existing: " + new_title_path) + app.logger.info("Copying title: " + path + " into existing: " + new_title_path) for dir_name, subdir_list, file_list in os.walk(path): for file in file_list: os.renames(os.path.join(dir_name, file), @@ -298,8 +297,8 @@ def update_dir_structure_file(book_id, calibrepath, first_author): path = new_title_path localbook.path = localbook.path.split('/')[0] + '/' + new_titledir except OSError as ex: - web.app.logger.error("Rename title from: " + path + " to " + new_title_path + ": " + str(ex)) - web.app.logger.debug(ex, exc_info=True) + app.logger.error("Rename title from: " + path + " to " + new_title_path + ": " + str(ex)) + app.logger.debug(ex, exc_info=True) return _("Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s", src=path, dest=new_title_path, error=str(ex)) if authordir != new_authordir: @@ -308,8 +307,8 @@ def update_dir_structure_file(book_id, calibrepath, first_author): os.renames(path, new_author_path) localbook.path = new_authordir + '/' + localbook.path.split('/')[1] except OSError as ex: - web.app.logger.error("Rename author from: " + path + " to " + new_author_path + ": " + str(ex)) - web.app.logger.debug(ex, exc_info=True) + app.logger.error("Rename author from: " + path + " to " + new_author_path + ": " + str(ex)) + app.logger.debug(ex, exc_info=True) return _("Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s", src=path, dest=new_author_path, error=str(ex)) # Rename all files from old names to new names @@ -322,8 +321,8 @@ def update_dir_structure_file(book_id, calibrepath, first_author): os.path.join(path_name,new_name + '.' + file_format.format.lower())) file_format.name = new_name except OSError as ex: - web.app.logger.error("Rename file in path " + path + " to " + new_name + ": " + str(ex)) - web.app.logger.debug(ex, exc_info=True) + app.logger.error("Rename file in path " + path + " to " + new_name + ": " + str(ex)) + app.logger.debug(ex, exc_info=True) return _("Rename file in path '%(src)s' to '%(dest)s' failed with error: %(error)s", src=path, dest=new_name, error=str(ex)) return False @@ -418,17 +417,17 @@ def delete_book(book, calibrepath, book_format): def get_book_cover(cover_path): if config.config_use_google_drive: try: - if not web.is_gdrive_ready(): + if not gd.is_gdrive_ready(): return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg") path=gd.get_cover_via_gdrive(cover_path) if path: return redirect(path) else: - web.app.logger.error(cover_path + '/cover.jpg not found on Google Drive') + app.logger.error(cover_path + '/cover.jpg not found on Google Drive') return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg") except Exception as e: - web.app.logger.error("Error Message: " + e.message) - web.app.logger.exception(e) + app.logger.error("Error Message: " + e.message) + app.logger.exception(e) # traceback.print_exc() return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg") else: @@ -439,7 +438,7 @@ def get_book_cover(cover_path): def save_cover(url, book_path): img = requests.get(url) if img.headers.get('content-type') != 'image/jpeg': - web.app.logger.error("Cover is no jpg file, can't save") + app.logger.error("Cover is no jpg file, can't save") return False if config.config_use_google_drive: @@ -448,13 +447,13 @@ def save_cover(url, book_path): f.write(img.content) f.close() gd.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name)) - web.app.logger.info("Cover is saved on Google Drive") + app.logger.info("Cover is saved on Google Drive") return True f = open(os.path.join(config.config_calibre_dir, book_path, "cover.jpg"), "wb") f.write(img.content) f.close() - web.app.logger.info("Cover is saved") + app.logger.info("Cover is saved") return True @@ -462,7 +461,7 @@ def do_download_file(book, book_format, data, headers): if config.config_use_google_drive: startTime = time.time() df = gd.getFileFromEbooksFolder(book.path, data.name + "." + book_format) - web.app.logger.debug(time.time() - startTime) + app.logger.debug(time.time() - startTime) if df: return gd.do_gdrive_download(df, headers) else: @@ -471,7 +470,7 @@ def do_download_file(book, book_format, data, headers): filename = os.path.join(config.config_calibre_dir, book.path) if not os.path.isfile(os.path.join(filename, data.name + "." + book_format)): # ToDo: improve error handling - web.app.logger.error('File not found: %s' % os.path.join(filename, data.name + "." + book_format)) + app.logger.error('File not found: %s' % os.path.join(filename, data.name + "." + book_format)) response = make_response(send_from_directory(filename, data.name + "." + book_format)) response.headers = headers return response @@ -497,7 +496,7 @@ def check_unrar(unrarLocation): version = value.group(1) except OSError as e: error = True - web.app.logger.exception(e) + app.logger.exception(e) version =_(u'Error excecuting UnRar') else: version = _(u'Unrar binary file not found') @@ -522,7 +521,7 @@ def render_task_status(tasklist): if task['user'] == current_user.nickname or current_user.role_admin(): # task2 = copy.deepcopy(task) # = task if task['formStarttime']: - task['starttime'] = format_datetime(task['formStarttime'], format='short', locale=web.get_locale()) + task['starttime'] = format_datetime(task['formStarttime'], format='short', locale=get_locale()) # task2['formStarttime'] = "" else: if 'starttime' not in task: diff --git a/cps/oauth.py b/cps/oauth.py index 679e7f31..960a3810 100644 --- a/cps/oauth.py +++ b/cps/oauth.py @@ -2,133 +2,136 @@ # -*- coding: utf-8 -*- from flask import session -from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user -from sqlalchemy.orm.exc import NoResultFound - - -class OAuthBackend(SQLAlchemyBackend): - """ - Stores and retrieves OAuth tokens using a relational database through - the `SQLAlchemy`_ ORM. - - .. _SQLAlchemy: http://www.sqlalchemy.org/ - """ - def __init__(self, model, session, - user=None, user_id=None, user_required=None, anon_user=None, - cache=None): - super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache) - - def get(self, blueprint, user=None, user_id=None): - if blueprint.name + '_oauth_token' in session and session[blueprint.name + '_oauth_token'] != '': - return session[blueprint.name + '_oauth_token'] - # check cache - cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id) - token = self.cache.get(cache_key) - if token: +try: + from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user + from sqlalchemy.orm.exc import NoResultFound + + class OAuthBackend(SQLAlchemyBackend): + """ + Stores and retrieves OAuth tokens using a relational database through + the `SQLAlchemy`_ ORM. + + .. _SQLAlchemy: http://www.sqlalchemy.org/ + """ + def __init__(self, model, session, + user=None, user_id=None, user_required=None, anon_user=None, + cache=None): + super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache) + + def get(self, blueprint, user=None, user_id=None): + if blueprint.name + '_oauth_token' in session and session[blueprint.name + '_oauth_token'] != '': + return session[blueprint.name + '_oauth_token'] + # check cache + cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id) + token = self.cache.get(cache_key) + if token: + return token + + # if not cached, make database queries + query = ( + self.session.query(self.model) + .filter_by(provider=blueprint.name) + ) + uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) + u = first(_get_real_user(ref, self.anon_user) + for ref in (user, self.user, blueprint.config.get("user"))) + + use_provider_user_id = False + if blueprint.name + '_oauth_user_id' in session and session[blueprint.name + '_oauth_user_id'] != '': + query = query.filter_by(provider_user_id=session[blueprint.name + '_oauth_user_id']) + use_provider_user_id = True + + if self.user_required and not u and not uid and not use_provider_user_id: + #raise ValueError("Cannot get OAuth token without an associated user") + return None + # check for user ID + if hasattr(self.model, "user_id") and uid: + query = query.filter_by(user_id=uid) + # check for user (relationship property) + elif hasattr(self.model, "user") and u: + query = query.filter_by(user=u) + # if we have the property, but not value, filter by None + elif hasattr(self.model, "user_id"): + query = query.filter_by(user_id=None) + # run query + try: + token = query.one().token + except NoResultFound: + token = None + + # cache the result + self.cache.set(cache_key, token) + return token - # if not cached, make database queries - query = ( - self.session.query(self.model) - .filter_by(provider=blueprint.name) - ) - uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) - u = first(_get_real_user(ref, self.anon_user) - for ref in (user, self.user, blueprint.config.get("user"))) - - use_provider_user_id = False - if blueprint.name + '_oauth_user_id' in session and session[blueprint.name + '_oauth_user_id'] != '': - query = query.filter_by(provider_user_id=session[blueprint.name + '_oauth_user_id']) - use_provider_user_id = True - - if self.user_required and not u and not uid and not use_provider_user_id: - #raise ValueError("Cannot get OAuth token without an associated user") - return None - # check for user ID - if hasattr(self.model, "user_id") and uid: - query = query.filter_by(user_id=uid) - # check for user (relationship property) - elif hasattr(self.model, "user") and u: - query = query.filter_by(user=u) - # if we have the property, but not value, filter by None - elif hasattr(self.model, "user_id"): - query = query.filter_by(user_id=None) - # run query - try: - token = query.one().token - except NoResultFound: - token = None - - # cache the result - self.cache.set(cache_key, token) - - return token - - def set(self, blueprint, token, user=None, user_id=None): - uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) - u = first(_get_real_user(ref, self.anon_user) + def set(self, blueprint, token, user=None, user_id=None): + uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) + u = first(_get_real_user(ref, self.anon_user) + for ref in (user, self.user, blueprint.config.get("user"))) + + if self.user_required and not u and not uid: + raise ValueError("Cannot set OAuth token without an associated user") + + # if there was an existing model, delete it + existing_query = ( + self.session.query(self.model) + .filter_by(provider=blueprint.name) + ) + # check for user ID + has_user_id = hasattr(self.model, "user_id") + if has_user_id and uid: + existing_query = existing_query.filter_by(user_id=uid) + # check for user (relationship property) + has_user = hasattr(self.model, "user") + if has_user and u: + existing_query = existing_query.filter_by(user=u) + # queue up delete query -- won't be run until commit() + existing_query.delete() + # create a new model for this token + kwargs = { + "provider": blueprint.name, + "token": token, + } + if has_user_id and uid: + kwargs["user_id"] = uid + if has_user and u: + kwargs["user"] = u + self.session.add(self.model(**kwargs)) + # commit to delete and add simultaneously + self.session.commit() + # invalidate cache + self.cache.delete(self.make_cache_key( + blueprint=blueprint, user=user, user_id=user_id + )) + + def delete(self, blueprint, user=None, user_id=None): + query = ( + self.session.query(self.model) + .filter_by(provider=blueprint.name) + ) + uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) + u = first(_get_real_user(ref, self.anon_user) for ref in (user, self.user, blueprint.config.get("user"))) - if self.user_required and not u and not uid: - raise ValueError("Cannot set OAuth token without an associated user") - - # if there was an existing model, delete it - existing_query = ( - self.session.query(self.model) - .filter_by(provider=blueprint.name) - ) - # check for user ID - has_user_id = hasattr(self.model, "user_id") - if has_user_id and uid: - existing_query = existing_query.filter_by(user_id=uid) - # check for user (relationship property) - has_user = hasattr(self.model, "user") - if has_user and u: - existing_query = existing_query.filter_by(user=u) - # queue up delete query -- won't be run until commit() - existing_query.delete() - # create a new model for this token - kwargs = { - "provider": blueprint.name, - "token": token, - } - if has_user_id and uid: - kwargs["user_id"] = uid - if has_user and u: - kwargs["user"] = u - self.session.add(self.model(**kwargs)) - # commit to delete and add simultaneously - self.session.commit() - # invalidate cache - self.cache.delete(self.make_cache_key( - blueprint=blueprint, user=user, user_id=user_id - )) - - def delete(self, blueprint, user=None, user_id=None): - query = ( - self.session.query(self.model) - .filter_by(provider=blueprint.name) - ) - uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) - u = first(_get_real_user(ref, self.anon_user) - for ref in (user, self.user, blueprint.config.get("user"))) - - if self.user_required and not u and not uid: - raise ValueError("Cannot delete OAuth token without an associated user") - - # check for user ID - if hasattr(self.model, "user_id") and uid: - query = query.filter_by(user_id=uid) - # check for user (relationship property) - elif hasattr(self.model, "user") and u: - query = query.filter_by(user=u) - # if we have the property, but not value, filter by None - elif hasattr(self.model, "user_id"): - query = query.filter_by(user_id=None) - # run query - query.delete() - self.session.commit() - # invalidate cache - self.cache.delete(self.make_cache_key( - blueprint=blueprint, user=user, user_id=user_id, - )) + if self.user_required and not u and not uid: + raise ValueError("Cannot delete OAuth token without an associated user") + + # check for user ID + if hasattr(self.model, "user_id") and uid: + query = query.filter_by(user_id=uid) + # check for user (relationship property) + elif hasattr(self.model, "user") and u: + query = query.filter_by(user=u) + # if we have the property, but not value, filter by None + elif hasattr(self.model, "user_id"): + query = query.filter_by(user_id=None) + # run query + query.delete() + self.session.commit() + # invalidate cache + self.cache.delete(self.make_cache_key( + blueprint=blueprint, user=user, user_id=user_id, + )) + +except ImportError: + pass diff --git a/cps/oauth_bb.py b/cps/oauth_bb.py index 7cfe1d92..830292e0 100644 --- a/cps/oauth_bb.py +++ b/cps/oauth_bb.py @@ -20,11 +20,14 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see -from flask_dance.contrib.github import make_github_blueprint, github -from flask_dance.contrib.google import make_google_blueprint, google -from flask_dance.consumer import oauth_authorized, oauth_error +try: + from flask_dance.contrib.github import make_github_blueprint, github + from flask_dance.contrib.google import make_google_blueprint, google + from flask_dance.consumer import oauth_authorized, oauth_error + from oauth import OAuthBackend +except ImportError: + pass from sqlalchemy.orm.exc import NoResultFound -from oauth import OAuthBackend from flask import flash, session, redirect, url_for, request, make_response, abort import json from cps import config, app @@ -91,226 +94,226 @@ def logout_oauth_user(): if oauth + '_oauth_user_id' in session: session.pop(oauth + '_oauth_user_id') +if ub.oauth_support: + github_blueprint = make_github_blueprint( + client_id=config.config_github_oauth_client_id, + client_secret=config.config_github_oauth_client_secret, + redirect_to="github_login",) + + google_blueprint = make_google_blueprint( + client_id=config.config_google_oauth_client_id, + client_secret=config.config_google_oauth_client_secret, + redirect_to="google_login", + scope=[ + "https://www.googleapis.com/auth/plus.me", + "https://www.googleapis.com/auth/userinfo.email", + ] + ) -github_blueprint = make_github_blueprint( - client_id=config.config_github_oauth_client_id, - client_secret=config.config_github_oauth_client_secret, - redirect_to="github_login",) - -google_blueprint = make_google_blueprint( - client_id=config.config_google_oauth_client_id, - client_secret=config.config_google_oauth_client_secret, - redirect_to="google_login", - scope=[ - "https://www.googleapis.com/auth/plus.me", - "https://www.googleapis.com/auth/userinfo.email", - ] -) - -app.register_blueprint(google_blueprint, url_prefix="/login") -app.register_blueprint(github_blueprint, url_prefix='/login') + app.register_blueprint(google_blueprint, url_prefix="/login") + app.register_blueprint(github_blueprint, url_prefix='/login') -github_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True) -google_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True) + github_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True) + google_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True) -if config.config_use_github_oauth: - register_oauth_blueprint(github_blueprint, 'GitHub') -if config.config_use_google_oauth: - register_oauth_blueprint(google_blueprint, 'Google') + if config.config_use_github_oauth: + register_oauth_blueprint(github_blueprint, 'GitHub') + if config.config_use_google_oauth: + register_oauth_blueprint(google_blueprint, 'Google') -@oauth_authorized.connect_via(github_blueprint) -def github_logged_in(blueprint, token): - if not token: - flash(_("Failed to log in with GitHub."), category="error") - return False + @oauth_authorized.connect_via(github_blueprint) + def github_logged_in(blueprint, token): + if not token: + flash(_("Failed to log in with GitHub."), category="error") + return False - resp = blueprint.session.get("/user") - if not resp.ok: - flash(_("Failed to fetch user info from GitHub."), category="error") - return False + resp = blueprint.session.get("/user") + if not resp.ok: + flash(_("Failed to fetch user info from GitHub."), category="error") + return False - github_info = resp.json() - github_user_id = str(github_info["id"]) - return oauth_update_token(blueprint, token, github_user_id) + github_info = resp.json() + github_user_id = str(github_info["id"]) + return oauth_update_token(blueprint, token, github_user_id) -@oauth_authorized.connect_via(google_blueprint) -def google_logged_in(blueprint, token): - if not token: - flash(_("Failed to log in with Google."), category="error") - return False + @oauth_authorized.connect_via(google_blueprint) + def google_logged_in(blueprint, token): + if not token: + flash(_("Failed to log in with Google."), category="error") + return False - resp = blueprint.session.get("/oauth2/v2/userinfo") - if not resp.ok: - flash(_("Failed to fetch user info from Google."), category="error") - return False + resp = blueprint.session.get("/oauth2/v2/userinfo") + if not resp.ok: + flash(_("Failed to fetch user info from Google."), category="error") + return False - google_info = resp.json() - google_user_id = str(google_info["id"]) + google_info = resp.json() + google_user_id = str(google_info["id"]) - return oauth_update_token(blueprint, token, google_user_id) + return oauth_update_token(blueprint, token, google_user_id) -def oauth_update_token(blueprint, token, provider_user_id): - session[blueprint.name + "_oauth_user_id"] = provider_user_id - session[blueprint.name + "_oauth_token"] = token + def oauth_update_token(blueprint, token, provider_user_id): + session[blueprint.name + "_oauth_user_id"] = provider_user_id + session[blueprint.name + "_oauth_token"] = token - # Find this OAuth token in the database, or create it - query = ub.session.query(ub.OAuth).filter_by( - provider=blueprint.name, - provider_user_id=provider_user_id, - ) - try: - oauth = query.one() - # update token - oauth.token = token - except NoResultFound: - oauth = ub.OAuth( + # Find this OAuth token in the database, or create it + query = ub.session.query(ub.OAuth).filter_by( provider=blueprint.name, provider_user_id=provider_user_id, - token=token, ) - try: - ub.session.add(oauth) - ub.session.commit() - except Exception as e: - app.logger.exception(e) - ub.session.rollback() - - # Disable Flask-Dance's default behavior for saving the OAuth token - return False + try: + oauth = query.one() + # update token + oauth.token = token + except NoResultFound: + oauth = ub.OAuth( + provider=blueprint.name, + provider_user_id=provider_user_id, + token=token, + ) + try: + ub.session.add(oauth) + ub.session.commit() + except Exception as e: + app.logger.exception(e) + ub.session.rollback() + + # Disable Flask-Dance's default behavior for saving the OAuth token + return False -def bind_oauth_or_register(provider, provider_user_id, redirect_url): - query = ub.session.query(ub.OAuth).filter_by( - provider=provider, - provider_user_id=provider_user_id, - ) - try: - oauth = query.one() - # already bind with user, just login - if oauth.user: - login_user(oauth.user) - return redirect(url_for('index')) - else: - # bind to current user + def bind_oauth_or_register(provider, provider_user_id, redirect_url): + query = ub.session.query(ub.OAuth).filter_by( + provider=provider, + provider_user_id=provider_user_id, + ) + try: + oauth = query.one() + # already bind with user, just login + if oauth.user: + login_user(oauth.user) + return redirect(url_for('web.index')) + else: + # bind to current user + if current_user and current_user.is_authenticated: + oauth.user = current_user + try: + ub.session.add(oauth) + ub.session.commit() + except Exception as e: + app.logger.exception(e) + ub.session.rollback() + return redirect(url_for('web.register')) + except NoResultFound: + return redirect(url_for(redirect_url)) + + + def get_oauth_status(): + status = [] + query = ub.session.query(ub.OAuth).filter_by( + user_id=current_user.id, + ) + try: + oauths = query.all() + for oauth in oauths: + status.append(oauth.provider) + return status + except NoResultFound: + return None + + + def unlink_oauth(provider): + if request.host_url + 'me' != request.referrer: + pass + query = ub.session.query(ub.OAuth).filter_by( + provider=provider, + user_id=current_user.id, + ) + try: + oauth = query.one() if current_user and current_user.is_authenticated: oauth.user = current_user try: - ub.session.add(oauth) + ub.session.delete(oauth) ub.session.commit() + logout_oauth_user() + flash(_("Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success") except Exception as e: app.logger.exception(e) ub.session.rollback() - return redirect(url_for('web.register')) - except NoResultFound: - return redirect(url_for(redirect_url)) - - -def get_oauth_status(): - status = [] - query = ub.session.query(ub.OAuth).filter_by( - user_id=current_user.id, - ) - try: - oauths = query.all() - for oauth in oauths: - status.append(oauth.provider) - return status - except NoResultFound: - return None - - -def unlink_oauth(provider): - if request.host_url + 'me' != request.referrer: - pass - query = ub.session.query(ub.OAuth).filter_by( - provider=provider, - user_id=current_user.id, - ) - try: - oauth = query.one() - if current_user and current_user.is_authenticated: - oauth.user = current_user - try: - ub.session.delete(oauth) - ub.session.commit() - logout_oauth_user() - flash(_("Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success") - except Exception as e: - app.logger.exception(e) - ub.session.rollback() - flash(_("Unlink to %(oauth)s failed.", oauth=oauth_check[provider]), category="error") - except NoResultFound: - app.logger.warning("oauth %s for user %d not fount" % (provider, current_user.id)) - flash(_("Not linked to %(oauth)s.", oauth=oauth_check[provider]), category="error") - return redirect(url_for('profile')) - - -# notify on OAuth provider error -@oauth_error.connect_via(github_blueprint) -def github_error(blueprint, error, error_description=None, error_uri=None): - msg = ( - "OAuth error from {name}! " - "error={error} description={description} uri={uri}" - ).format( - name=blueprint.name, - error=error, - description=error_description, - uri=error_uri, - ) - flash(msg, category="error") - -''' -@oauth.route('/github') -@github_oauth_required -def github_login(): - if not github.authorized: - return redirect(url_for('github.login')) - account_info = github.get('/user') - if account_info.ok: - account_info_json = account_info.json() - return bind_oauth_or_register(github_blueprint.name, account_info_json['id'], 'github.login') - flash(_(u"GitHub Oauth error, please retry later."), category="error") - return redirect(url_for('login')) - - -@oauth.route('/unlink/github', methods=["GET"]) -@login_required -def github_login_unlink(): - return unlink_oauth(github_blueprint.name) - - -@oauth.route('/google') -@google_oauth_required -def google_login(): - if not google.authorized: - return redirect(url_for("google.login")) - resp = google.get("/oauth2/v2/userinfo") - if resp.ok: - account_info_json = resp.json() - return bind_oauth_or_register(google_blueprint.name, account_info_json['id'], 'google.login') - flash(_(u"Google Oauth error, please retry later."), category="error") - return redirect(url_for('login')) -''' - -@oauth_error.connect_via(google_blueprint) -def google_error(blueprint, error, error_description=None, error_uri=None): - msg = ( - "OAuth error from {name}! " - "error={error} description={description} uri={uri}" - ).format( - name=blueprint.name, - error=error, - description=error_description, - uri=error_uri, - ) - flash(msg, category="error") + flash(_("Unlink to %(oauth)s failed.", oauth=oauth_check[provider]), category="error") + except NoResultFound: + app.logger.warning("oauth %s for user %d not fount" % (provider, current_user.id)) + flash(_("Not linked to %(oauth)s.", oauth=oauth_check[provider]), category="error") + return redirect(url_for('profile')) + + + # notify on OAuth provider error + @oauth_error.connect_via(github_blueprint) + def github_error(blueprint, error, error_description=None, error_uri=None): + msg = ( + "OAuth error from {name}! " + "error={error} description={description} uri={uri}" + ).format( + name=blueprint.name, + error=error, + description=error_description, + uri=error_uri, + ) + flash(msg, category="error") + + ''' + @oauth.route('/github') + @github_oauth_required + def github_login(): + if not github.authorized: + return redirect(url_for('github.login')) + account_info = github.get('/user') + if account_info.ok: + account_info_json = account_info.json() + return bind_oauth_or_register(github_blueprint.name, account_info_json['id'], 'github.login') + flash(_(u"GitHub Oauth error, please retry later."), category="error") + return redirect(url_for('web.login')) + + + @oauth.route('/unlink/github', methods=["GET"]) + @login_required + def github_login_unlink(): + return unlink_oauth(github_blueprint.name) + + + @oauth.route('/google') + @google_oauth_required + def google_login(): + if not google.authorized: + return redirect(url_for("google.login")) + resp = google.get("/oauth2/v2/userinfo") + if resp.ok: + account_info_json = resp.json() + return bind_oauth_or_register(google_blueprint.name, account_info_json['id'], 'google.login') + flash(_(u"Google Oauth error, please retry later."), category="error") + return redirect(url_for('web.login')) + ''' + + @oauth_error.connect_via(google_blueprint) + def google_error(blueprint, error, error_description=None, error_uri=None): + msg = ( + "OAuth error from {name}! " + "error={error} description={description} uri={uri}" + ).format( + name=blueprint.name, + error=error, + description=error_description, + uri=error_uri, + ) + flash(msg, category="error") -''' -@oauth.route('/unlink/google', methods=["GET"]) -@login_required -def google_login_unlink(): - return unlink_oauth(google_blueprint.name)''' + ''' + @oauth.route('/unlink/google', methods=["GET"]) + @login_required + def google_login_unlink(): + return unlink_oauth(google_blueprint.name)''' diff --git a/cps/opds.py b/cps/opds.py index dd6ed984..419cdea2 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -38,7 +38,6 @@ from werkzeug.security import check_password_hash from werkzeug.datastructures import Headers try: from urllib.parse import quote - from imp import reload except ImportError: from urllib import quote @@ -315,7 +314,7 @@ def authenticate(): def render_xml_template(*args, **kwargs): #ToDo: return time in current timezone similar to %z currtime = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S+00:00") - xml = render_template(current_time=currtime, *args, **kwargs) + xml = render_template(current_time=currtime, instance=config.config_calibre_web_title, *args, **kwargs) response = make_response(xml) response.headers["Content-Type"] = "application/atom+xml; charset=utf-8" return response diff --git a/cps/shelf.py b/cps/shelf.py index 6300a0ce..34d8eb47 100644 --- a/cps/shelf.py +++ b/cps/shelf.py @@ -40,7 +40,7 @@ def add_to_shelf(shelf_id, book_id): app.logger.info("Invalid shelf specified") if not request.is_xhr: flash(_(u"Invalid shelf specified"), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) return "Invalid shelf specified", 400 if not shelf.is_public and not shelf.user_id == int(current_user.id): @@ -48,14 +48,14 @@ def add_to_shelf(shelf_id, book_id): if not request.is_xhr: flash(_(u"Sorry you are not allowed to add a book to the the shelf: %(shelfname)s", shelfname=shelf.name), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) return "Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name, 403 if shelf.is_public and not current_user.role_edit_shelfs(): app.logger.info("User is not allowed to edit public shelves") if not request.is_xhr: flash(_(u"You are not allowed to edit public shelves"), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) return "User is not allowed to edit public shelves", 403 book_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id, @@ -64,7 +64,7 @@ def add_to_shelf(shelf_id, book_id): app.logger.info("Book is already part of the shelf: %s" % shelf.name) if not request.is_xhr: flash(_(u"Book is already part of the shelf: %(shelfname)s", shelfname=shelf.name), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) return "Book is already part of the shelf: %s" % shelf.name, 400 maxOrder = ub.session.query(func.max(ub.BookShelf.order)).filter(ub.BookShelf.shelf == shelf_id).first() @@ -81,7 +81,7 @@ def add_to_shelf(shelf_id, book_id): if "HTTP_REFERER" in request.environ: return redirect(request.environ["HTTP_REFERER"]) else: - return redirect(url_for('index')) + return redirect(url_for('web.index')) return "", 204 @@ -92,17 +92,17 @@ def search_to_shelf(shelf_id): if shelf is None: app.logger.info("Invalid shelf specified") flash(_(u"Invalid shelf specified"), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) if not shelf.is_public and not shelf.user_id == int(current_user.id): app.logger.info("You are not allowed to add a book to the the shelf: %s" % shelf.name) flash(_(u"You are not allowed to add a book to the the shelf: %(name)s", name=shelf.name), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) if shelf.is_public and not current_user.role_edit_shelfs(): app.logger.info("User is not allowed to edit public shelves") flash(_(u"User is not allowed to edit public shelves"), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) if current_user.id in searched_ids and searched_ids[current_user.id]: books_for_shelf = list() @@ -120,7 +120,7 @@ def search_to_shelf(shelf_id): if not books_for_shelf: app.logger.info("Books are already part of the shelf: %s" % shelf.name) flash(_(u"Books are already part of the shelf: %(name)s", name=shelf.name), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) maxOrder = ub.session.query(func.max(ub.BookShelf.order)).filter(ub.BookShelf.shelf == shelf_id).first() if maxOrder[0] is None: @@ -146,7 +146,7 @@ def remove_from_shelf(shelf_id, book_id): if shelf is None: app.logger.info("Invalid shelf specified") if not request.is_xhr: - return redirect(url_for('index')) + return redirect(url_for('web.index')) return "Invalid shelf specified", 400 # if shelf is public and use is allowed to edit shelfs, or if shelf is private and user is owner @@ -165,7 +165,7 @@ def remove_from_shelf(shelf_id, book_id): if book_shelf is None: app.logger.info("Book already removed from shelf") if not request.is_xhr: - return redirect(url_for('index')) + return redirect(url_for('web.index')) return "Book already removed from shelf", 410 ub.session.delete(book_shelf) @@ -180,7 +180,7 @@ def remove_from_shelf(shelf_id, book_id): if not request.is_xhr: flash(_(u"Sorry you are not allowed to remove a book from this shelf: %(sname)s", sname=shelf.name), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) return "Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name, 403 diff --git a/cps/templates/book_edit.html b/cps/templates/book_edit.html index 0a3f4bb9..664a5b4f 100644 --- a/cps/templates/book_edit.html +++ b/cps/templates/book_edit.html @@ -19,7 +19,7 @@

{{_('Delete formats:')}}

{% for file in book.data %} {% endfor %}
@@ -28,7 +28,7 @@ {% if source_formats|length > 0 and conversion_formats|length > 0 %}

{{_('Convert book format:')}}

-
+
diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index a7d0bcb9..12f04e2d 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -159,7 +159,7 @@
- {% if goodreads %} + {% if feature_support['goodreads'] %}
@@ -176,6 +176,7 @@
{% endif %} + {% if feature_support['ldap'] %}
@@ -190,6 +191,8 @@
+ {% endif %} + {% if feature_support['oauth'] %}
@@ -220,6 +223,7 @@
+ {% endif %} diff --git a/cps/templates/detail.html b/cps/templates/detail.html index a459b89e..c6019294 100644 --- a/cps/templates/detail.html +++ b/cps/templates/detail.html @@ -57,9 +57,22 @@ {% endif %} {% endif %} + {% if reader_list %} +
+ + +
+ {% endif %} {% if reader_list %} {% if audioentries|length %} -
+ {% endif %} {% endif %} diff --git a/cps/templates/index.xml b/cps/templates/index.xml index 7ab305aa..f7e8d6f0 100644 --- a/cps/templates/index.xml +++ b/cps/templates/index.xml @@ -2,13 +2,12 @@ urn:uuid:2853dacf-ed79-42f5-8e8a-a7bb3d1ae6a2 {{ current_time }} - - + - {{instance}} {{instance}} @@ -16,88 +15,88 @@ {{_('Hot Books')}} - - {{url_for('feed_hot')}} + + {{url_for('opds.feed_hot')}} {{ current_time }} {{_('Popular publications from this catalog based on Downloads.')}} {{_('Best rated Books')}} - - {{url_for('feed_best_rated')}} + + {{url_for('opds.feed_best_rated')}} {{ current_time }} {{_('Popular publications from this catalog based on Rating.')}} {{_('New Books')}} - - {{url_for('feed_new')}} + + {{url_for('opds.feed_new')}} {{ current_time }} {{_('The latest Books')}} {{_('Random Books')}} - - {{url_for('feed_discover')}} + + {{url_for('opds.feed_discover')}} {{ current_time }} {{_('Show Random Books')}} {% if not current_user.is_anonymous %} {{_('Read Books')}} - - {{url_for('feed_read_books')}} + + {{url_for('opds.feed_read_books')}} {{ current_time }} {{_('Read Books')}} - {% endif %} {{_('Unread Books')}} - - {{url_for('feed_unread_books')}} + + {{url_for('opds.feed_unread_books')}} {{ current_time }} {{_('Unread Books')}} + {% endif %} {{_('Authors')}} - - {{url_for('feed_authorindex')}} + + {{url_for('opds.feed_authorindex')}} {{ current_time }} {{_('Books ordered by Author')}} {{_('Publishers')}} - - {{url_for('feed_publisherindex')}} + + {{url_for('opds.feed_publisherindex')}} {{ current_time }} {{_('Books ordered by publisher')}} {{_('Category list')}} - - {{url_for('feed_categoryindex')}} + + {{url_for('opds.feed_categoryindex')}} {{ current_time }} {{_('Books ordered by category')}} {{_('Series list')}} - - {{url_for('feed_seriesindex')}} + + {{url_for('opds.feed_seriesindex')}} {{ current_time }} {{_('Books ordered by series')}} {{_('Public Shelves')}} - - {{url_for('feed_shelfindex', public="public")}} + + {{url_for('opds.feed_shelfindex', public="public")}} {{ current_time }} {{_('Books organized in public shelfs, visible to everyone')}} {% if not current_user.is_anonymous %} {{_('Your Shelves')}} - - {{url_for('feed_shelfindex')}} + + {{url_for('opds.feed_shelfindex')}} {{ current_time }} {{_("User's own shelfs, only visible to the current user himself")}} diff --git a/cps/templates/json.txt b/cps/templates/json.txt index fa5239b1..c068b1b4 100644 --- a/cps/templates/json.txt +++ b/cps/templates/json.txt @@ -1,53 +1,53 @@ { - "pubdate": "{{entry.pubdate}}", - "title": "{{entry.title}}", + "pubdate": "{{entry.pubdate}}", + "title": "{{entry.title}}", "format_metadata": { - {% for format in entry.data %} + {% for format in entry.data %} "{{format.format}}": { - "mtime": "{{entry.last_modified}}", - "size": {{format.uncompressed_size}}, + "mtime": "{{entry.last_modified}}", + "size": {{format.uncompressed_size}}, "path": "" }{% if not loop.last %},{% endif %} {% endfor %} - }, + }, "formats": [ - {% for format in entry.data %} + {% for format in entry.data %} "{{format.format}}"{% if not loop.last %},{% endif %} - {% endfor %} - ], - "series": null, - "cover": "{{url_for('feed_get_cover', book_id=entry.id)}}", + {% endfor %} + ], + "series": null, + "cover": "{{url_for('opds.feed_get_cover', book_id=entry.id)}}", "languages": [ - {% for lang in entry.languages %} + {% for lang in entry.languages %} "{{lang.lang_code}}"{% if not loop.last %},{% endif %} {% endfor %} - ], + ], "comments": "{% if entry.comments|length > 0 %}{{entry.comments[0].text.replace('"', '\\"')|safe}}{% endif %}", "tags": [ {% for tag in entry.tags %} "{{tag.name}}"{% if not loop.last %},{% endif %} - {% endfor %} - ], - "application_id": {{entry.id}}, - "series_index": {% if entry.series|length > 0 %}"{{entry.series_index}}"{% else %}null{% endif %}, - "last_modified": "{{entry.last_modified}}", - "author_sort": "{{entry.author_sort}}", - "uuid": "{{entry.uuid}}", - "timestamp": "{{entry.timestamp}}", - "thumbnail": "{{url_for('feed_get_cover', book_id=entry.id)}}", + {% endfor %} + ], + "application_id": {{entry.id}}, + "series_index": {% if entry.series|length > 0 %}"{{entry.series_index}}"{% else %}null{% endif %}, + "last_modified": "{{entry.last_modified}}", + "author_sort": "{{entry.author_sort}}", + "uuid": "{{entry.uuid}}", + "timestamp": "{{entry.timestamp}}", + "thumbnail": "{{url_for('opds.feed_get_cover', book_id=entry.id)}}", "main_format": { - "{{entry.data[0].format|lower}}": "{{ url_for('get_opds_download_link', book_id=entry.id, book_format=entry.data[0].format|lower)}}" + "{{entry.data[0].format|lower}}": "{{ url_for('opds.get_opds_download_link', book_id=entry.id, book_format=entry.data[0].format|lower)}}" }, "rating":{% if entry.ratings.__len__() > 0 %} "{{entry.ratings[0].rating}}.0"{% else %}0.0{% endif %}, "authors": [ {% for author in entry.authors %} "{{author.name.replace('|',',')}}"{% if not loop.last %},{% endif %} - {% endfor %} - ], + {% endfor %} + ], "other_formats": { {% if entry.data.__len__() > 1 %} {% for format in entry.data[1:] %} - "{{format.format|lower}}": "{{ url_for('get_opds_download_link', book_id=entry.id, book_format=format.format|lower)}}"{% if not loop.last %},{% endif %} + "{{format.format|lower}}": "{{ url_for('opds.get_opds_download_link', book_id=entry.id, book_format=format.format|lower)}}"{% if not loop.last %},{% endif %} {% endfor %} {% endif %} }, "title_sort": "{{entry.sort}}" diff --git a/cps/templates/layout.html b/cps/templates/layout.html index ef83e842..897c6cb6 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -161,7 +161,7 @@ {% if g.user.is_authenticated or g.user.is_anonymous %} {% for shelf in g.public_shelfes %} -
  • {{shelf.name|shortentitle(40)}}
  • +
  • {{shelf.name|shortentitle(40)}}
  • {% endfor %} {% for shelf in g.user.shelf %} diff --git a/cps/templates/osd.xml b/cps/templates/osd.xml index b88e6823..bb741bb5 100644 --- a/cps/templates/osd.xml +++ b/cps/templates/osd.xml @@ -6,9 +6,9 @@ Janeczku https://github.com/janeczku/calibre-web + template="{{url_for('opds.search')}}?query={searchTerms}"/> + template="{{url_for('opds.feed_normal_search')}}?query={searchTerms}"/> open {{lang}} UTF-8 diff --git a/cps/ub.py b/cps/ub.py index 59f0b613..dbc42ac4 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -23,7 +23,6 @@ from sqlalchemy import exc from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import * from flask_login import AnonymousUserMixin -from flask_dance.consumer.backend.sqla import OAuthConsumerMixin import sys import os import logging @@ -34,8 +33,11 @@ from binascii import hexlify import cli try: + from flask_dance.consumer.backend.sqla import OAuthConsumerMixin import ldap + oauth_support = True except ImportError: + oauth_support = False pass engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False) @@ -207,11 +209,11 @@ class User(UserBase, Base): default_language = Column(String(3), default="all") mature_content = Column(Boolean, default=True) - -class OAuth(OAuthConsumerMixin, Base): - provider_user_id = Column(String(256)) - user_id = Column(Integer, ForeignKey(User.id)) - user = relationship(User) +if oauth_support: + class OAuth(OAuthConsumerMixin, Base): + provider_user_id = Column(String(256)) + user_id = Column(Integer, ForeignKey(User.id)) + user = relationship(User) # Class for anonymous user is derived from User base and completly overrides methods and properties for the @@ -834,7 +836,7 @@ def delete_download(book_id): session.commit() # Generate user Guest (translated text), as anoymous user, no rights -def create_anonymous_user(session): +def create_anonymous_user(): user = User() user.nickname = "Guest" user.email = 'no@email' diff --git a/cps/updater.py b/cps/updater.py index b01646a0..e5dd6bae 100644 --- a/cps/updater.py +++ b/cps/updater.py @@ -18,11 +18,10 @@ # along with this program. If not, see . -from cps import config, get_locale +from cps import config, get_locale, Server, app import threading import zipfile import requests -import logging import time from io import BytesIO import os @@ -35,7 +34,6 @@ import json from flask_babel import gettext as _ from babel.dates import format_datetime -import server def is_sha1(sha1): if len(sha1) != 40: @@ -69,39 +67,45 @@ class Updater(threading.Thread): def run(self): try: self.status = 1 - r = requests.get(self._get_request_path(), stream=True) + app.logger.debug(u'Download update file') + headers = {'Accept': 'application/vnd.github.v3+json'} + r = requests.get(self._get_request_path(), stream=True, headers=headers) r.raise_for_status() self.status = 2 + app.logger.debug(u'Opening zipfile') z = zipfile.ZipFile(BytesIO(r.content)) self.status = 3 + app.logger.debug(u'Extracting zipfile') tmp_dir = gettempdir() z.extractall(tmp_dir) foldername = os.path.join(tmp_dir, z.namelist()[0])[:-1] if not os.path.isdir(foldername): self.status = 11 - logging.getLogger('cps.web').info(u'Extracted contents of zipfile not found in temp folder') + app.logger.info(u'Extracted contents of zipfile not found in temp folder') return self.status = 4 + app.logger.debug(u'Replacing files') self.update_source(foldername, config.get_main_dir) self.status = 6 + app.logger.debug(u'Preparing restart of server') time.sleep(2) - server.Server.setRestartTyp(True) - server.Server.stopServer() + Server.setRestartTyp(True) + Server.stopServer() self.status = 7 time.sleep(2) except requests.exceptions.HTTPError as ex: - logging.getLogger('cps.web').info( u'HTTP Error' + ' ' + str(ex)) + app.logger.info( u'HTTP Error' + ' ' + str(ex)) self.status = 8 except requests.exceptions.ConnectionError: - logging.getLogger('cps.web').info(u'Connection error') + app.logger.info(u'Connection error') self.status = 9 except requests.exceptions.Timeout: - logging.getLogger('cps.web').info(u'Timeout while establishing connection') + app.logger.info(u'Timeout while establishing connection') self.status = 10 except requests.exceptions.RequestException: self.status = 11 - logging.getLogger('cps.web').info(u'General error') + app.logger.info(u'General error') def get_update_status(self): return self.status @@ -149,14 +153,14 @@ class Updater(threading.Thread): if sys.platform == "win32" or sys.platform == "darwin": change_permissions = False else: - logging.getLogger('cps.web').debug('Update on OS-System : ' + sys.platform) + app.logger.debug('Update on OS-System : ' + sys.platform) new_permissions = os.stat(root_dst_dir) # print new_permissions for src_dir, __, files in os.walk(root_src_dir): dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1) if not os.path.exists(dst_dir): os.makedirs(dst_dir) - logging.getLogger('cps.web').debug('Create-Dir: '+dst_dir) + app.logger.debug('Create-Dir: '+dst_dir) if change_permissions: # print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid)) os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid) @@ -166,20 +170,20 @@ class Updater(threading.Thread): if os.path.exists(dst_file): if change_permissions: permission = os.stat(dst_file) - logging.getLogger('cps.web').debug('Remove file before copy: '+dst_file) + app.logger.debug('Remove file before copy: '+dst_file) os.remove(dst_file) else: if change_permissions: permission = new_permissions shutil.move(src_file, dst_dir) - logging.getLogger('cps.web').debug('Move File '+src_file+' to '+dst_dir) + app.logger.debug('Move File '+src_file+' to '+dst_dir) if change_permissions: try: os.chown(dst_file, permission.st_uid, permission.st_gid) except (Exception) as e: # ex = sys.exc_info() old_permissions = os.stat(dst_file) - logging.getLogger('cps.web').debug('Fail change permissions of ' + str(dst_file) + '. Before: ' + app.logger.debug('Fail change permissions of ' + str(dst_file) + '. Before: ' + str(old_permissions.st_uid) + ':' + str(old_permissions.st_gid) + ' After: ' + str(permission.st_uid) + ':' + str(permission.st_gid) + ' error: '+str(e)) return @@ -215,15 +219,15 @@ class Updater(threading.Thread): for item in remove_items: item_path = os.path.join(destination, item[1:]) if os.path.isdir(item_path): - logging.getLogger('cps.web').debug("Delete dir " + item_path) + app.logger.debug("Delete dir " + item_path) shutil.rmtree(item_path, ignore_errors=True) else: try: - logging.getLogger('cps.web').debug("Delete file " + item_path) + app.logger.debug("Delete file " + item_path) # log_from_thread("Delete file " + item_path) os.remove(item_path) except Exception: - logging.getLogger('cps.web').debug("Could not remove:" + item_path) + app.logger.debug("Could not remove:" + item_path) shutil.rmtree(source, ignore_errors=True) def _nightly_version_info(self): @@ -263,7 +267,8 @@ class Updater(threading.Thread): status['update'] = True try: - r = requests.get(repository_url + '/git/commits/' + commit['object']['sha']) + headers = {'Accept': 'application/vnd.github.v3+json'} + r = requests.get(repository_url + '/git/commits/' + commit['object']['sha'], headers=headers) r.raise_for_status() update_data = r.json() except requests.exceptions.HTTPError as e: @@ -310,7 +315,8 @@ class Updater(threading.Thread): # check if we are more than one update behind if so, go up the tree if parent_commit['sha'] != status['current_commit_hash']: try: - r = requests.get(parent_commit['url']) + headers = {'Accept': 'application/vnd.github.v3+json'} + r = requests.get(parent_commit['url'], headers=headers) r.raise_for_status() parent_data = r.json() @@ -368,7 +374,8 @@ class Updater(threading.Thread): # check if we are more than one update behind if so, go up the tree if commit['sha'] != status['current_commit_hash']: try: - r = requests.get(parent_commit['url']) + headers = {'Accept': 'application/vnd.github.v3+json'} + r = requests.get(parent_commit['url'], headers=headers) r.raise_for_status() parent_data = r.json() @@ -492,7 +499,8 @@ class Updater(threading.Thread): else: status['current_commit_hash'] = version['version'] try: - r = requests.get(repository_url) + headers = {'Accept': 'application/vnd.github.v3+json'} + r = requests.get(repository_url, headers=headers) commit = r.json() r.raise_for_status() except requests.exceptions.HTTPError as e: diff --git a/cps/web.py b/cps/web.py index 91cc8ff0..ef87712f 100644 --- a/cps/web.py +++ b/cps/web.py @@ -47,18 +47,19 @@ from cps import lm, babel, ub, config, get_locale, language_table, app from pagination import Pagination from sqlalchemy.sql.expression import text -from oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status - -'''try: - oauth_support = True +feature_support = dict() +try: + from oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status + feature_support['oauth'] = True except ImportError: - oauth_support = False''' + feature_support['oauth'] = False + oauth_check = {} try: import ldap - ldap_support = True + feature_support['ldap'] = True except ImportError: - ldap_support = False + feature_support['ldap'] = False try: from googleapiclient.errors import HttpError @@ -67,15 +68,15 @@ except ImportError: try: from goodreads.client import GoodreadsClient - goodreads_support = True + feature_support['goodreads'] = True except ImportError: - goodreads_support = False + feature_support['goodreads'] = False try: import Levenshtein - levenshtein_support = True + feature_support['levenshtein'] = True except ImportError: - levenshtein_support = False + feature_support['levenshtein'] = False try: from functools import reduce, wraps @@ -84,9 +85,9 @@ except ImportError: try: import rarfile - rar_support=True + feature_support['rar'] = True except ImportError: - rar_support=False + feature_support['rar'] = False try: from natsort import natsorted as sort @@ -95,18 +96,17 @@ except ImportError: try: from urllib.parse import quote - from imp import reload except ImportError: from urllib import quote - from flask import Blueprint # Global variables EXTENSIONS_AUDIO = {'mp3', 'm4a', 'm4b'} -# EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] + (['rar','cbr'] if rar_support else [])) +'''EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] + + (['rar','cbr'] if feature_support['rar'] else []))''' # custom error page @@ -346,8 +346,8 @@ def before_request(): g.allow_upload = config.config_uploading g.current_theme = config.config_theme g.public_shelfes = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1).order_by(ub.Shelf.name).all() - if not config.db_configured and request.endpoint not in ('web.basic_configuration', 'login') and '/static/' not in request.path: - return redirect(url_for('web.basic_configuration')) + if not config.db_configured and request.endpoint not in ('admin.basic_configuration', 'login') and '/static/' not in request.path: + return redirect(url_for('admin.basic_configuration')) @web.route("/ajax/emailstat") @@ -373,7 +373,7 @@ def get_comic_book(book_id, book_format, page): if bookformat.format.lower() == book_format.lower(): cbr_file = os.path.join(config.config_calibre_dir, book.path, bookformat.name) + "." + book_format if book_format in ("cbr", "rar"): - if rar_support == True: + if feature_support['rar'] == True: rarfile.UNRAR_TOOL = config.config_rarfile_location try: rf = rarfile.RarFile(cbr_file) @@ -636,7 +636,7 @@ def author(book_id, page): author_info = None other_books = [] - if goodreads_support and config.config_use_goodreads: + if feature_support['goodreads'] and config.config_use_goodreads: try: gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret) author_info = gc.find_author(author_name=name) @@ -688,7 +688,7 @@ def get_unique_other_books(library_books, author_books): author_books) # Fuzzy match book titles - if levenshtein_support: + if feature_support['levenshtein']: library_titles = reduce(lambda acc, book: acc + [book.title], library_books, []) other_books = filter(lambda author_book: not filter( lambda library_book: @@ -1215,7 +1215,7 @@ def read_book(book_id, book_format): # copyfile(cbr_file, tmp_file) return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"), extension=fileext) - '''if rar_support == True: + '''if feature_support['rar']: extensionList = ["cbr","cbt","cbz"] else: extensionList = ["cbt","cbz"] @@ -1292,7 +1292,7 @@ def register(): try: ub.session.add(content) ub.session.commit() - if oauth_support: + if feature_support['oauth']: register_user_with_oauth(content) helper.send_registration_mail(to_save["email"], to_save["nickname"], password) except Exception: @@ -1310,7 +1310,7 @@ def register(): flash(_(u"This username or e-mail address is already in use."), category="error") return render_title_template('register.html', title=_(u"register"), page="register") - if oauth_support: + if feature_support['oauth']: register_user_with_oauth() return render_title_template('register.html', config=config, title=_(u"register"), page="register") @@ -1318,7 +1318,7 @@ def register(): @web.route('/login', methods=['GET', 'POST']) def login(): if not config.db_configured: - return redirect(url_for('web.basic_configuration')) + return redirect(url_for('admin.basic_configuration')) if current_user is not None and current_user.is_authenticated: return redirect(url_for('web.index')) if request.method == "POST": @@ -1358,7 +1358,7 @@ def login(): def logout(): if current_user is not None and current_user.is_authenticated: logout_user() - if oauth_support: + if feature_support['oauth']: logout_oauth_user() return redirect(url_for('web.login')) @@ -1370,7 +1370,7 @@ def remote_login(): ub.session.add(auth_token) ub.session.commit() - verify_url = url_for('verify_token', token=auth_token.auth_token, _external=true) + verify_url = url_for('web.verify_token', token=auth_token.auth_token, _external=true) return render_title_template('remote_login.html', title=_(u"login"), token=auth_token.auth_token, verify_url=verify_url, page="remotelogin") @@ -1385,7 +1385,7 @@ def verify_token(token): # Token not found if auth_token is None: flash(_(u"Token not found"), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) # Token expired if datetime.datetime.now() > auth_token.expiration: @@ -1393,7 +1393,7 @@ def verify_token(token): ub.session.commit() flash(_(u"Token has expired"), category="error") - return redirect(url_for('index')) + return redirect(url_for('web.index')) # Update token with user information auth_token.user_id = current_user.id @@ -1401,7 +1401,7 @@ def verify_token(token): ub.session.commit() flash(_(u"Success! Please return to your device"), category="success") - return redirect(url_for('index')) + return redirect(url_for('web.index')) @web.route('/ajax/verify_token', methods=['POST']) @@ -1472,7 +1472,10 @@ def profile(): downloads = list() languages = speaking_language() translations = babel.list_translations() + [LC('en')] - oauth_status = get_oauth_status() + if feature_support['oauth']: + oauth_status = get_oauth_status() + else: + oauth_status = None for book in content.downloads: downloadBook = db.session.query(db.Books).filter(db.Books.id == book.book_id).first() if downloadBook: @@ -1535,9 +1538,11 @@ def profile(): ub.session.rollback() flash(_(u"Found an existing account for this e-mail address."), category="error") return render_title_template("user_edit.html", content=content, downloads=downloads, - title=_(u"%(name)s's profile", name=current_user.nickname, registered_oauth=oauth_check, oauth_status=oauth_status)) + title=_(u"%(name)s's profile", name=current_user.nickname, + registered_oauth=oauth_check, oauth_status=oauth_status)) flash(_(u"Profile updated"), category="success") return render_title_template("user_edit.html", translations=translations, profile=1, languages=languages, - content=content, downloads=downloads, title=_(u"%(name)s's profile", - name=current_user.nickname), page="me", registered_oauth=oauth_check, oauth_status=oauth_status) + content=content, downloads=downloads, title=_(u"%(name)s's profile", + name=current_user.nickname), page="me", registered_oauth=oauth_check, + oauth_status=oauth_status) diff --git a/cps/worker.py b/cps/worker.py index c0c68833..77df162c 100644 --- a/cps/worker.py +++ b/cps/worker.py @@ -27,14 +27,12 @@ import socket import sys import os from email.generator import Generator -from cps import config, db # , app -# import web +from cps import config, db, app from flask_babel import gettext as _ import re -# import gdriveutils as gd +import gdriveutils as gd from subproc_wrapper import process_open - try: from StringIO import StringIO from email.MIMEBase import MIMEBase @@ -90,8 +88,8 @@ def get_attachment(bookpath, filename): data = file_.read() file_.close() except IOError as e: - # web.app.logger.exception(e) # traceback.print_exc() - # web.app.logger.error(u'The requested file could not be read. Maybe wrong permissions?') + app.logger.exception(e) # traceback.print_exc() + app.logger.error(u'The requested file could not be read. Maybe wrong permissions?') return None attachment = MIMEBase('application', 'octet-stream') @@ -116,8 +114,7 @@ class emailbase(): def send(self, strg): """Send `strg' to the server.""" - if self.debuglevel > 0: - print('send:', repr(strg[:300]), file=sys.stderr) + app.logger.debug('send:' + repr(strg[:300])) if hasattr(self, 'sock') and self.sock: try: if self.transferSize: @@ -141,6 +138,9 @@ class emailbase(): else: raise smtplib.SMTPServerDisconnected('please run connect() first') + def _print_debug(self, *args): + app.logger.debug(args) + def getTransferStatus(self): if self.transferSize: lock2 = threading.Lock() @@ -254,14 +254,14 @@ class WorkerThread(threading.Thread): # if it does - mark the conversion task as complete and return a success # this will allow send to kindle workflow to continue to work if os.path.isfile(file_path + format_new_ext): - # web.app.logger.info("Book id %d already converted to %s", bookid, format_new_ext) + app.logger.info("Book id %d already converted to %s", bookid, format_new_ext) cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first() self.queue[self.current]['path'] = file_path self.queue[self.current]['title'] = cur_book.title self._handleSuccess() return file_path + format_new_ext else: - web.app.logger.info("Book id %d - target format of %s does not exist. Moving forward with convert.", bookid, format_new_ext) + app.logger.info("Book id %d - target format of %s does not exist. Moving forward with convert.", bookid, format_new_ext) # check if converter-executable is existing if not os.path.exists(config.config_converterpath): @@ -274,22 +274,22 @@ class WorkerThread(threading.Thread): if format_old_ext == '.epub' and format_new_ext == '.mobi': if config.config_ebookconverter == 1: '''if os.name == 'nt': - command = web.ub.config.config_converterpath + u' "' + file_path + u'.epub"' + command = config.config_converterpath + u' "' + file_path + u'.epub"' if sys.version_info < (3, 0): command = command.encode(sys.getfilesystemencoding()) else:''' command = [config.config_converterpath, file_path + u'.epub'] - quotes = (1) + quotes = [1] if config.config_ebookconverter == 2: # Linux py2.7 encode as list without quotes no empty element for parameters # linux py3.x no encode and as list without quotes no empty element for parameters # windows py2.7 encode as string with quotes empty element for parameters is okay # windows py 3.x no encode and as string with quotes empty element for parameters is okay # separate handling for windows and linux - quotes = (1,2) + quotes = [1,2] '''if os.name == 'nt': - command = web.ub.config.config_converterpath + u' "' + file_path + format_old_ext + u'" "' + \ - file_path + format_new_ext + u'" ' + web.ub.config.config_calibre + command = config.config_converterpath + u' "' + file_path + format_old_ext + u'" "' + \ + file_path + format_new_ext + u'" ' + config.config_calibre if sys.version_info < (3, 0): command = command.encode(sys.getfilesystemencoding()) else:''' @@ -317,13 +317,13 @@ class WorkerThread(threading.Thread): if conv_error: error_message = _(u"Kindlegen failed with Error %(error)s. Message: %(message)s", error=conv_error.group(1), message=conv_error.group(2).strip()) - web.app.logger.debug("convert_kindlegen: " + nextline) + app.logger.debug("convert_kindlegen: " + nextline) else: while p.poll() is None: nextline = p.stdout.readline() if os.name == 'nt' and sys.version_info < (3, 0): nextline = nextline.decode('windows-1252') - web.app.logger.debug(nextline.strip('\r\n')) + app.logger.debug(nextline.strip('\r\n')) # parse progress string from calibre-converter progress = re.search("(\d+)%\s.*", nextline) if progress: @@ -353,7 +353,7 @@ class WorkerThread(threading.Thread): return file_path + format_new_ext else: error_message = format_new_ext.upper() + ' format not found on disk' - # web.app.logger.info("ebook converter failed with error while converting book") + app.logger.info("ebook converter failed with error while converting book") if not error_message: error_message = 'Ebook converter failed with unknown error' self._handleError(error_message) @@ -414,7 +414,6 @@ class WorkerThread(threading.Thread): def _send_raw_email(self): self.queue[self.current]['starttime'] = datetime.now() self.UIqueue[self.current]['formStarttime'] = self.queue[self.current]['starttime'] - # self.queue[self.current]['status'] = STAT_STARTED self.UIqueue[self.current]['stat'] = STAT_STARTED obj=self.queue[self.current] # create MIME message @@ -446,8 +445,11 @@ class WorkerThread(threading.Thread): # send email timeout = 600 # set timeout to 5mins - org_stderr = sys.stderr - sys.stderr = StderrLogger() + # redirect output to logfile on python2 pn python3 debugoutput is caught with overwritten + # _print_debug function + if sys.version_info < (3, 0): + org_smtpstderr = smtplib.stderr + smtplib.stderr = StderrLogger() if use_ssl == 2: self.asyncSMTP = email_SSL(obj['settings']["mail_server"], obj['settings']["mail_port"], timeout) @@ -455,7 +457,7 @@ class WorkerThread(threading.Thread): self.asyncSMTP = email(obj['settings']["mail_server"], obj['settings']["mail_port"], timeout) # link to logginglevel - if web.ub.config.config_log_level != logging.DEBUG: + if config.config_log_level != logging.DEBUG: self.asyncSMTP.set_debuglevel(0) else: self.asyncSMTP.set_debuglevel(1) @@ -466,7 +468,9 @@ class WorkerThread(threading.Thread): self.asyncSMTP.sendmail(obj['settings']["mail_from"], obj['recipent'], msg) self.asyncSMTP.quit() self._handleSuccess() - sys.stderr = org_stderr + + if sys.version_info < (3, 0): + smtplib.stderr = org_smtpstderr except (MemoryError) as e: self._handleError(u'Error sending email: ' + e.message) @@ -497,7 +501,7 @@ class WorkerThread(threading.Thread): return retVal def _handleError(self, error_message): - web.app.logger.error(error_message) + app.logger.error(error_message) # self.queue[self.current]['status'] = STAT_FAIL self.UIqueue[self.current]['stat'] = STAT_FAIL self.UIqueue[self.current]['progress'] = "100 %" @@ -519,13 +523,12 @@ class StderrLogger(object): buffer = '' def __init__(self): - self.logger = web.app.logger + self.logger = app.logger def write(self, message): try: if message == '\n': - self.logger.debug(self.buffer) - print(self.buffer) + self.logger.debug(self.buffer.replace("\n","\\n")) self.buffer = '' else: self.buffer += message diff --git a/optional-requirements.txt b/optional-requirements.txt index 399acec4..9b740f6c 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -11,12 +11,19 @@ PyDrive==1.3.1 PyYAML==3.12 rsa==3.4.2 six==1.10.0 + # goodreads goodreads>=0.3.2 python-Levenshtein>=0.12.0 + # ldap login python_ldap>=3.0.0 + # other lxml>=3.8.0 rarfile>=2.7 natsort>=2.2.0 + +# Oauth Login +flask-dance>=0.13.0 +sqlalchemy_utils>=0.33.5 diff --git a/requirements.txt b/requirements.txt index 2b13eb54..3fb23ea3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,3 @@ SQLAlchemy>=1.1.0 tornado>=4.1 Wand>=0.4.4 unidecode>=0.04.19 -flask-dance>=0.13.0 -sqlalchemy_utils>=0.33.5 diff --git a/test/Calibre-Web TestSummary.html b/test/Calibre-Web TestSummary.html index 97d89880..32ddf212 100644 --- a/test/Calibre-Web TestSummary.html +++ b/test/Calibre-Web TestSummary.html @@ -30,15 +30,15 @@
    -

    Start Time: 2019-01-27 07:20:26.105524

    +

    Start Time: 2019-02-10 12:10:31.096065

    -

    Stop Time: 2019-01-27 08:08:50.419347

    +

    Stop Time: 2019-02-10 12:53:18.410539

    -

    Duration: 0:48:24.313823

    +

    Duration: 0:42:47.314474

    @@ -577,97 +577,92 @@ - + test_email_ssl.test_SSL_Python27 4 - 4 - 0 + 1 0 + 3 0 Detail - +
    test_SSL_None_setup_error
    - PASS + ERROR
    -