diff --git a/cps/__init__.py b/cps/__init__.py index eee38fdd..7dd4e11f 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -36,10 +36,6 @@ from flask_principal import Principal from . import config_sql, logger, cache_buster, cli, ub, db from .reverseproxy import ReverseProxied from .server import WebServer -try: - from werkzeug.middleware.proxy_fix import ProxyFix -except ImportError: - from werkzeug.contrib.fixers import ProxyFix mimetypes.init() mimetypes.add_type('application/xhtml+xml', '.xhtml') @@ -80,10 +76,7 @@ log = logger.create() from . import services def create_app(): - try: - app.wsgi_app = ReverseProxied(ProxyFix(app.wsgi_app, x_for=1, x_host=1)) - except (ValueError, TypeError): - app.wsgi_app = ReverseProxied(ProxyFix(app.wsgi_app)) + app.wsgi_app = ReverseProxied(app.wsgi_app) # For python2 convert path to unicode if sys.version_info < (3, 0): app.static_folder = app.static_folder.decode('utf-8') @@ -95,7 +88,7 @@ def create_app(): log.info('Starting Calibre Web...') Principal(app) lm.init_app(app) - app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT') + app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session)) web_server.init_app(app, config) db.setup_db(config) diff --git a/cps/config_sql.py b/cps/config_sql.py index 41c4f144..603d38d5 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -22,7 +22,7 @@ import os import json import sys -from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean +from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean, BLOB from sqlalchemy.ext.declarative import declarative_base from . import constants, cli, logger, ub @@ -31,6 +31,15 @@ from . import constants, cli, logger, ub log = logger.create() _Base = declarative_base() +class _Flask_Settings(_Base): + __tablename__ = 'flask_settings' + + id = Column(Integer, primary_key=True) + flask_session_key = Column(BLOB, default="") + + def __init__(self, key): + self.flask_session_key = key + # Baseclass for representing settings in app.db with email server settings and Calibre database settings # (application settings) @@ -304,7 +313,7 @@ def _migrate_table(session, orm_class): log.debug("%s: %s", column_name, err.args[0]) if column.default is not None: if sys.version_info < (3, 0): - if isinstance(column.default.arg,unicode): + if isinstance(column.default.arg, unicode): column.default.arg = column.default.arg.encode('utf-8') if column.default is None: column_default = "" @@ -340,6 +349,7 @@ def _migrate_database(session): # make sure the table is created, if it does not exist _Base.metadata.create_all(session.bind) _migrate_table(session, _Settings) + _migrate_table(session, _Flask_Settings) def load_configuration(session): @@ -357,3 +367,11 @@ def load_configuration(session): update({"denied_tags": conf.config_mature_content_tags}, synchronize_session=False) session.commit() return conf + +def get_flask_session_key(session): + flask_settings = session.query(_Flask_Settings).one_or_none() + if flask_settings == None: + flask_settings = _Flask_Settings(os.urandom(32)) + session.add(flask_settings) + session.commit() + return flask_settings.flask_session_key diff --git a/cps/constants.py b/cps/constants.py index fffc4feb..73072d83 100644 --- a/cps/constants.py +++ b/cps/constants.py @@ -127,7 +127,7 @@ def selected_roles(dictionary): BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, ' 'series_id, languages') -STABLE_VERSION = {'version': '0.6.7 Beta'} +STABLE_VERSION = {'version': '0.6.8 Beta'} NIGHTLY_VERSION = {} NIGHTLY_VERSION[0] = '$Format:%H$' diff --git a/cps/editbooks.py b/cps/editbooks.py index 3ca4e793..13e26f5b 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -30,6 +30,7 @@ from uuid import uuid4 from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response from flask_babel import gettext as _ from flask_login import current_user, login_required +from sqlalchemy.exc import OperationalError from . import constants, logger, isoLanguages, gdriveutils, uploader, helper from . import config, get_locale, db, ub, worker @@ -444,10 +445,16 @@ def upload_single_file(request, book, book_id): if is_format: log.warning('Book format %s already existing', file_ext.upper()) else: - db_format = db.Data(book_id, file_ext.upper(), file_size, file_name) - db.session.add(db_format) - db.session.commit() - db.update_title_sort(config) + try: + db_format = db.Data(book_id, file_ext.upper(), file_size, file_name) + db.session.add(db_format) + db.session.commit() + db.update_title_sort(config) + except OperationalError as e: + db.session.rollback() + log.error('Database error: %s', e) + flash(_(u"Database error: %(error)s.", error=e), category="error") + return redirect(url_for('web.show_book', book_id=book.id)) # Queue uploader info uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title) @@ -455,7 +462,8 @@ def upload_single_file(request, book, book_id): "" + uploadText + "") return uploader.process( - saved_filename, *os.path.splitext(requested_file.filename)) + saved_filename, *os.path.splitext(requested_file.filename), + rarExcecutable=config.config_rarfile_location) def upload_cover(request, book): @@ -653,181 +661,188 @@ def upload(): abort(404) if request.method == 'POST' and 'btn-upload' in request.files: for requested_file in request.files.getlist("btn-upload"): - # create the function for sorting... - db.update_title_sort(config) - db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4())) - - # check if file extension is correct - if '.' in requested_file.filename: - file_ext = requested_file.filename.rsplit('.', 1)[-1].lower() - if file_ext not in constants.EXTENSIONS_UPLOAD: - flash( - _("File extension '%(ext)s' is not allowed to be uploaded to this server", - ext=file_ext), category="error") - 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 Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') - - # extract metadata from file try: - meta = uploader.upload(requested_file, config.config_rarfile_location) - 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 - series = meta.series - series_index = meta.series_id - - if title != _(u'Unknown') and authr != _(u'Unknown'): - entry = helper.check_exists_book(authr, title) - if entry: - 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") - - # handle authors - is_author = db.session.query(db.Authors).filter(db.Authors.name == authr).first() - if is_author: - db_author = is_author - authr= is_author.name - else: - db_author = db.Authors(authr, helper.get_sorted_author(authr), "") - db.session.add(db_author) - - title_dir = helper.get_valid_filename(title) - author_dir = helper.get_valid_filename(authr) - filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir) - saved_filename = os.path.join(filepath, title_dir + meta.extension.lower()) + # create the function for sorting... + db.update_title_sort(config) + db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4())) + + # check if file extension is correct + if '.' in requested_file.filename: + file_ext = requested_file.filename.rsplit('.', 1)[-1].lower() + if file_ext not in constants.EXTENSIONS_UPLOAD: + flash( + _("File extension '%(ext)s' is not allowed to be uploaded to this server", + ext=file_ext), category="error") + 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 Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') - # check if file path exists, otherwise create it, copy file to calibre path and delete temp file - if not os.path.exists(filepath): + # extract metadata from file 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") + meta = uploader.upload(requested_file, config.config_rarfile_location) + 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') - 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 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") - - if meta.cover is None: - has_cover = 0 - copyfile(os.path.join(constants.STATIC_DIR, 'generic_cover.jpg'), - os.path.join(filepath, "cover.jpg")) - else: - has_cover = 1 - - # handle series - db_series = None - is_series = db.session.query(db.Series).filter(db.Series.name == series).first() - if is_series: - db_series = is_series - elif series != '': - db_series = db.Series(series, "") - db.session.add(db_series) - - # add language actually one value in list - input_language = meta.languages - db_language = None - if input_language != "": - input_language = isoLanguages.get(name=input_language).part3 - hasLanguage = db.session.query(db.Languages).filter(db.Languages.lang_code == input_language).first() - if hasLanguage: - db_language = hasLanguage + title = meta.title + authr = meta.author + tags = meta.tags + series = meta.series + series_index = meta.series_id + + if title != _(u'Unknown') and authr != _(u'Unknown'): + entry = helper.check_exists_book(authr, title) + if entry: + 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") + + # handle authors + is_author = db.session.query(db.Authors).filter(db.Authors.name == authr).first() + if is_author: + db_author = is_author + authr= is_author.name else: - db_language = db.Languages(input_language) - db.session.add(db_language) - - # If the language of the file is excluded from the users view, it's not imported, to allow the user to view - # the book it's language is set to the filter language - if db_language != current_user.filter_language() and current_user.filter_language() != "all": - db_language = db.session.query(db.Languages).\ - filter(db.Languages.lang_code == current_user.filter_language()).first() - - # combine path and normalize path from windows systems - path = os.path.join(author_dir, title_dir).replace('\\', '/') - # Calibre adds books with utc as timezone - db_book = db.Books(title, "", db_author.sort, datetime.utcnow(), datetime(101, 1, 1), - series_index, datetime.utcnow(), path, has_cover, db_author, [], db_language) - db_book.authors.append(db_author) - if db_series: - db_book.series.append(db_series) - if db_language is not None: - db_book.languages.append(db_language) - file_size = os.path.getsize(saved_filename) - db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, title_dir) - - # handle tags - input_tags = tags.split(',') - input_tags = list(map(lambda it: it.strip(), input_tags)) - if input_tags[0] !="": - modify_database_object(input_tags, db_book.tags, db.Tags, db.session, 'tags') - - # flush content, get db_book.id available - db_book.data.append(db_data) - db.session.add(db_book) - db.session.flush() - - # add comment - book_id = db_book.id - upload_comment = Markup(meta.description).unescape() - if upload_comment != "": - db.session.add(db.Comments(upload_comment, book_id)) - - # save data to database, reread data - db.session.commit() - db.update_title_sort(config) - # Reread book. It's important not to filter the result, as it could have language which hide it from - # current users view (tags are not stored/extracted from metadata and could also be limited) - book = db.session.query(db.Books).filter(db.Books.id == book_id).first() - # upload book to gdrive if nesseccary and add "(bookid)" to folder name - if config.config_use_google_drive: - gdriveutils.updateGdriveCalibreFromLocal() - error = helper.update_dir_stucture(book.id, config.config_calibre_dir) - - # move cover to final directory, including book id - if has_cover: - move(meta.cover, os.path.join(filepath+ ' ({})'.format(book_id), "cover.jpg")) - db.session.commit() - if config.config_use_google_drive: - gdriveutils.updateGdriveCalibreFromLocal() - if error: - flash(error, category="error") - uploadText=_(u"File %(file)s uploaded", file=book.title) - worker.add_upload(current_user.nickname, - "" + uploadText + "") + db_author = db.Authors(authr, helper.get_sorted_author(authr), "") + db.session.add(db_author) + + title_dir = helper.get_valid_filename(title) + author_dir = helper.get_valid_filename(authr) + filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir) + saved_filename = os.path.join(filepath, title_dir + meta.extension.lower()) + + # check if file path exists, otherwise create it, copy file to calibre path and delete temp file + if not os.path.exists(filepath): + 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 Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + try: + copyfile(meta.file_path, saved_filename) + os.unlink(meta.file_path) + except OSError as e: + log.error("Failed to move file %s: %s", saved_filename, e) + flash(_(u"Failed to Move File %(file)s: %(error)s", file=saved_filename, error=e), category="error") + return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') - # create data for displaying display Full language name instead of iso639.part3language - if db_language is not None: - book.languages[0].language_name = _(meta.languages) - author_names = [] - for author in db_book.authors: - author_names.append(author.name) - if len(request.files.getlist("btn-upload")) < 2: - if current_user.role_edit() or current_user.role_admin(): - resp = {"location": url_for('editbook.edit_book', book_id=db_book.id)} - return Response(json.dumps(resp), mimetype='application/json') + if meta.cover is None: + has_cover = 0 + copyfile(os.path.join(constants.STATIC_DIR, 'generic_cover.jpg'), + os.path.join(filepath, "cover.jpg")) else: - resp = {"location": url_for('web.show_book', book_id=db_book.id)} - return Response(json.dumps(resp), mimetype='application/json') + has_cover = 1 + + # handle series + db_series = None + is_series = db.session.query(db.Series).filter(db.Series.name == series).first() + if is_series: + db_series = is_series + elif series != '': + db_series = db.Series(series, "") + db.session.add(db_series) + + # add language actually one value in list + input_language = meta.languages + db_language = None + if input_language != "": + input_language = isoLanguages.get(name=input_language).part3 + hasLanguage = db.session.query(db.Languages).filter(db.Languages.lang_code == input_language).first() + if hasLanguage: + db_language = hasLanguage + else: + db_language = db.Languages(input_language) + db.session.add(db_language) + + # If the language of the file is excluded from the users view, it's not imported, to allow the user to view + # the book it's language is set to the filter language + if db_language != current_user.filter_language() and current_user.filter_language() != "all": + db_language = db.session.query(db.Languages).\ + filter(db.Languages.lang_code == current_user.filter_language()).first() + + # combine path and normalize path from windows systems + path = os.path.join(author_dir, title_dir).replace('\\', '/') + # Calibre adds books with utc as timezone + db_book = db.Books(title, "", db_author.sort, datetime.utcnow(), datetime(101, 1, 1), + series_index, datetime.utcnow(), path, has_cover, db_author, [], db_language) + db_book.authors.append(db_author) + if db_series: + db_book.series.append(db_series) + if db_language is not None: + db_book.languages.append(db_language) + file_size = os.path.getsize(saved_filename) + db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, title_dir) + + # handle tags + input_tags = tags.split(',') + input_tags = list(map(lambda it: it.strip(), input_tags)) + if input_tags[0] !="": + modify_database_object(input_tags, db_book.tags, db.Tags, db.session, 'tags') + + # flush content, get db_book.id available + db_book.data.append(db_data) + db.session.add(db_book) + db.session.flush() + + # add comment + book_id = db_book.id + upload_comment = Markup(meta.description).unescape() + if upload_comment != "": + db.session.add(db.Comments(upload_comment, book_id)) + + # save data to database, reread data + db.session.commit() + db.update_title_sort(config) + # Reread book. It's important not to filter the result, as it could have language which hide it from + # current users view (tags are not stored/extracted from metadata and could also be limited) + book = db.session.query(db.Books).filter(db.Books.id == book_id).first() + # upload book to gdrive if nesseccary and add "(bookid)" to folder name + if config.config_use_google_drive: + gdriveutils.updateGdriveCalibreFromLocal() + error = helper.update_dir_stucture(book.id, config.config_calibre_dir) + + # move cover to final directory, including book id + if has_cover: + try: + new_coverpath = os.path.join(filepath+ ' ({})'.format(book_id), "cover.jpg") + copyfile(meta.cover, new_coverpath) + os.unlink(meta.cover) + except OSError as e: + log.error("Failed to move cover file %s: %s", new_coverpath, e) + flash(_(u"Failed to Move Cover File %(file)s: %(error)s", file=new_coverpath, + error=e), + category="error") + db.session.commit() + if config.config_use_google_drive: + gdriveutils.updateGdriveCalibreFromLocal() + if error: + flash(error, category="error") + uploadText=_(u"File %(file)s uploaded", file=book.title) + worker.add_upload(current_user.nickname, + "" + uploadText + "") + + # create data for displaying display Full language name instead of iso639.part3language + if db_language is not None: + book.languages[0].language_name = _(meta.languages) + author_names = [] + for author in db_book.authors: + author_names.append(author.name) + if len(request.files.getlist("btn-upload")) < 2: + if current_user.role_edit() or current_user.role_admin(): + resp = {"location": url_for('editbook.edit_book', book_id=db_book.id)} + return Response(json.dumps(resp), mimetype='application/json') + else: + resp = {"location": url_for('web.show_book', book_id=db_book.id)} + return Response(json.dumps(resp), mimetype='application/json') + except OperationalError as e: + db.session.rollback() + log.error("Database error: %s", e) + flash(_(u"Database error: %(error)s.", error=e), category="error") return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') - @editbook.route("/admin/book/convert/", methods=['POST']) @login_required_if_no_ano @edit_required diff --git a/cps/helper.py b/cps/helper.py index 95abc69c..fcabf2f0 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -291,6 +291,7 @@ def delete_book_file(book, calibrepath, book_format=None): for file in os.listdir(path): if file.upper().endswith("."+book_format): os.remove(os.path.join(path, file)) + return True, None else: if os.path.isdir(path): if len(next(os.walk(path))[1]): diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index 44360f9c..647ac762 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -90,15 +90,15 @@ +
-
+
- @@ -349,23 +349,23 @@
+
-
+
-
{% if feature_support['rar'] %} +
- diff --git a/cps/templates/config_view_edit.html b/cps/templates/config_view_edit.html index 12820dda..2123eeff 100644 --- a/cps/templates/config_view_edit.html +++ b/cps/templates/config_view_edit.html @@ -6,8 +6,8 @@ {% block body %}

{{title}}

-
-
+ +

diff --git a/cps/templates/email_edit.html b/cps/templates/email_edit.html index ec2f587a..7a198c14 100644 --- a/cps/templates/email_edit.html +++ b/cps/templates/email_edit.html @@ -35,12 +35,12 @@

+
- - - - + + +
diff --git a/cps/translations/it/LC_MESSAGES/messages.mo b/cps/translations/it/LC_MESSAGES/messages.mo index 5e5afe85..bbe29221 100644 Binary files a/cps/translations/it/LC_MESSAGES/messages.mo and b/cps/translations/it/LC_MESSAGES/messages.mo differ diff --git a/cps/translations/it/LC_MESSAGES/messages.po b/cps/translations/it/LC_MESSAGES/messages.po index 7ca6e798..cdb92ec5 100644 --- a/cps/translations/it/LC_MESSAGES/messages.po +++ b/cps/translations/it/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Calibre-Web\n" "Report-Msgid-Bugs-To: https://github.com/janeczku/Calibre-Web\n" -"POT-Creation-Date: 2020-05-01 17:15+0200\n" +"POT-Creation-Date: 2020-05-04 20:19+0200\n" "PO-Revision-Date: 2017-04-04 15:09+0200\n" "Last-Translator: ElQuimm \n" "Language: it\n" @@ -172,7 +172,7 @@ msgstr "Configurazione del server e-mail aggiornata" #: cps/admin.py:821 msgid "User not found" -msgstr "" +msgstr "Utente non trovato" #: cps/admin.py:842 #, python-format @@ -185,7 +185,7 @@ msgstr "Non rimarrebbe nessun utente amministratore, non posso eliminare l'utent #: cps/admin.py:851 msgid "No admin user remaining, can't remove admin role" -msgstr "" +msgstr "Non rimarrebbe nessun utente amministratore, non posso eliminare il ruolo di amministratore" #: cps/admin.py:887 cps/web.py:1515 msgid "Found an existing account for this e-mail address." @@ -285,11 +285,11 @@ msgstr "non configurato" #: cps/editbooks.py:239 msgid "Book Format Successfully Deleted" -msgstr "" +msgstr "Il formato del libro è stato eliminato con successo" #: cps/editbooks.py:242 msgid "Book Successfully Deleted" -msgstr "" +msgstr "Il libro é stato eliminato con successo" #: cps/editbooks.py:253 cps/editbooks.py:489 msgid "Error opening eBook. File does not exist or file is not accessible" @@ -321,12 +321,12 @@ msgstr "Impossibile creare la cartella %(path)s (autorizzazione negata)." #: cps/editbooks.py:434 #, python-format msgid "Failed to store file %(file)s." -msgstr "Il salvataggio del file %(file)s è fallito." +msgstr "Il salvataggio del file %(file)s non è riuscito." #: cps/editbooks.py:451 #, python-format msgid "File format %(ext)s added to %(book)s" -msgstr "Ho aggiunto l'estensione %(ext)s al libro %(book)s" +msgstr "Ho aggiunto il formato %(ext)s al libro %(book)s" #: cps/editbooks.py:606 msgid "Metadata successfully updated" @@ -362,7 +362,7 @@ msgstr "Il file %(file)s è stato caricato" #: cps/editbooks.py:833 msgid "Source or destination format for conversion missing" -msgstr "Il formato sorgente o quello di destinazione, necessari alla conversione, mancano" +msgstr "Mancano o il formato sorgente o quello di destinazione, necessari alla conversione" #: cps/editbooks.py:841 #, python-format @@ -446,17 +446,17 @@ msgstr "Il file richiesto non può essere letto. I permessi sono corretti?" #: cps/helper.py:299 #, python-format msgid "Deleting book %(id)s failed, path has subfolders: %(path)s" -msgstr "" +msgstr "L'eliminazione del libro %(id)s non è riuscita, poiché il percorso ha delle sottocartelle: %(path)s" #: cps/helper.py:309 #, python-format msgid "Deleting book %(id)s failed: %(message)s" -msgstr "" +msgstr "L'eliminazione del libro %(id)s non è riuscita: %(message)s" #: cps/helper.py:319 #, python-format msgid "Deleting book %(id)s failed, book path not valid: %(path)s" -msgstr "" +msgstr "L'eliminazione del libro %(id)s non è riuscita, poiché il percorso non è valido: %(path)s" #: cps/helper.py:354 #, python-format @@ -489,7 +489,7 @@ msgstr "Errore nel creare la cartella per la copertina" #: cps/helper.py:555 msgid "Cover-file is not a valid image file, or could not be stored" -msgstr "" +msgstr "Il file della copertina non è in un formato immagine valido o non può essere salvato" #: cps/helper.py:566 msgid "Only jpg/jpeg/png/webp files are supported as coverfile" @@ -501,11 +501,11 @@ msgstr "Solamente i file nei formati jpg/jpeg sono supportati per le copertine" #: cps/helper.py:622 msgid "Unrar binary file not found" -msgstr "" +msgstr "Non ho trovato il file binario di UnRar" #: cps/helper.py:635 msgid "Error excecuting UnRar" -msgstr "" +msgstr "Errore nell'eseguire UnRar" #: cps/helper.py:691 msgid "Waiting" @@ -558,19 +558,19 @@ msgstr "Registra con %(provider)s" #: cps/oauth_bb.py:154 msgid "Failed to log in with GitHub." -msgstr "Accesso con GitHub non riuscito." +msgstr "Accesso con GitHub non è riuscito." #: cps/oauth_bb.py:159 msgid "Failed to fetch user info from GitHub." -msgstr "Fallito il recupero delle informazioni dell'utente da GitHub." +msgstr "Il recupero delle informazioni dell'utente da GitHub non è riuscito." #: cps/oauth_bb.py:170 msgid "Failed to log in with Google." -msgstr "Fallito l'accesso con Google." +msgstr "L'accesso con Google non è riuscito." #: cps/oauth_bb.py:175 msgid "Failed to fetch user info from Google." -msgstr "Fallito il recupero delle informazioni dell'utente da Google." +msgstr "Il recupero delle informazioni dell'utente da Google non è riuscito." #: cps/oauth_bb.py:225 cps/web.py:1291 cps/web.py:1431 #, python-format @@ -584,7 +584,7 @@ msgstr "Collegamento a %(oauth)s avvenuto con successo" #: cps/oauth_bb.py:241 msgid "Login failed, No User Linked With OAuth Account" -msgstr "Accesso fallito, non c'è un utente collegato all'account OAuth" +msgstr "Accesso non riuscito, non c'è un utente collegato all'account OAuth" #: cps/oauth_bb.py:283 #, python-format @@ -594,7 +594,7 @@ msgstr "Scollegamento da %(oauth)s avvenuto con successo" #: cps/oauth_bb.py:287 #, python-format msgid "Unlink to %(oauth)s Failed" -msgstr "Scollegamento da %(oauth)s fallito" +msgstr "Scollegamento da %(oauth)s non riuscito" #: cps/oauth_bb.py:290 #, python-format @@ -818,11 +818,11 @@ msgstr "Mostra la selezione del formato dei file" #: cps/ub.py:107 cps/web.py:1150 msgid "Archived Books" -msgstr "" +msgstr "Libri archiviati" #: cps/ub.py:109 msgid "Show archived books" -msgstr "" +msgstr "Mostra l'opzione per la selezione dei libri archiviati" #: cps/updater.py:294 cps/updater.py:305 cps/updater.py:406 cps/updater.py:420 msgid "Unexpected data while reading update information" @@ -1360,23 +1360,23 @@ msgstr "Descrizione" #: cps/templates/book_edit.html:66 msgid "Identifiers" -msgstr "" +msgstr "Identificatori" #: cps/templates/book_edit.html:70 cps/templates/book_edit.html:308 msgid "Identifier Type" -msgstr "" +msgstr "Tipo di identificatore" #: cps/templates/book_edit.html:71 cps/templates/book_edit.html:309 msgid "Identifier Value" -msgstr "" +msgstr "Valore dell'identificatore" #: cps/templates/book_edit.html:72 cps/templates/book_edit.html:310 msgid "Remove" -msgstr "" +msgstr "Rimuovi" #: cps/templates/book_edit.html:76 msgid "Add Identifier" -msgstr "" +msgstr "Aggiungi un identificatore" #: cps/templates/book_edit.html:80 cps/templates/search_form.html:33 msgid "Tags" @@ -1453,11 +1453,11 @@ msgstr "e dal disco rigido" #: cps/templates/book_edit.html:209 msgid "Important Kobo Note: deleted books will remain on any paired Kobo device." -msgstr "" +msgstr "Oservazione importante riguardo Kobo: i libri eliminati, rimarranno in ogni lettore Kobo accoppiato." #: cps/templates/book_edit.html:210 msgid "Books must first be archived and the device synced before a book can safely be deleted." -msgstr "" +msgstr "Prima di poter elimnare in sicurezza un libro, prima occorre che il libro venga archiviato e che l'apparecchio venga sincronizzato." #: cps/templates/book_edit.html:232 msgid "Keyword" @@ -1775,7 +1775,7 @@ msgstr "Percorso del convertitore" #: cps/templates/config_edit.html:349 msgid "Location of Unrar binary" -msgstr "Percorso di UnRar" +msgstr "Percorso del file binario di UnRar" #: cps/templates/config_edit.html:368 cps/templates/layout.html:84 #: cps/templates/login.html:4 cps/templates/login.html:20 @@ -1912,15 +1912,15 @@ msgstr "da leggere" #: cps/templates/detail.html:208 msgid "Restore from archive" -msgstr "" +msgstr "Ripristina dall'archivio" #: cps/templates/detail.html:208 msgid "Add to archive" -msgstr "" +msgstr "Aggiungi all'archivio" #: cps/templates/detail.html:209 msgid "Archived" -msgstr "" +msgstr "Archiviato" #: cps/templates/detail.html:219 msgid "Description:" @@ -2252,7 +2252,7 @@ msgstr "Scuro" #: cps/templates/readcbr.html:121 msgid "Scale" -msgstr "Adatta" +msgstr "Scala" #: cps/templates/readcbr.html:124 msgid "Best" @@ -2396,7 +2396,7 @@ msgstr "Cambia ordine" #: cps/templates/shelf.html:67 msgid "Are you sure you want to delete this shelf?" -msgstr "Vuoi davvero eliminare lo scaffale?" +msgstr "Vuoi davvero eliminare questo scaffale?" #: cps/templates/shelf.html:70 msgid "Shelf will be deleted for all users" diff --git a/cps/web.py b/cps/web.py index 7960df2f..d3d2111b 100644 --- a/cps/web.py +++ b/cps/web.py @@ -37,9 +37,9 @@ from flask import Blueprint from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for from flask_babel import gettext as _ from flask_login import login_user, logout_user, login_required, current_user -from sqlalchemy.exc import IntegrityError, InvalidRequestError +from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError from sqlalchemy.sql.expression import text, func, true, false, not_, and_, or_ -from werkzeug.exceptions import default_exceptions +from werkzeug.exceptions import default_exceptions, InternalServerError try: from werkzeug.exceptions import FailedDependency except ImportError: @@ -119,9 +119,16 @@ for ex in default_exceptions: if feature_support['ldap']: # Only way of catching the LDAPException upon logging in with LDAP server down @app.errorhandler(services.ldap.LDAPException) - def handle_exception(e): - log.debug('LDAP server not accessible while trying to login to opds feed') - return error_http(FailedDependency()) + def handle_LDAP_exception(e): + log.debug('LDAP server not accssible while trying to login to opds feed %s', e) + return error_http(e) + +# @app.errorhandler(InvalidRequestError) +#@app.errorhandler(OperationalError) +#def handle_db_exception(e): +# db.session.rollback() +# log.error('Database request error: %s',e) +# return internal_error(InternalServerError(e)) web = Blueprint('web', __name__) @@ -435,6 +442,10 @@ def toggle_read(book_id): db.session.commit() except KeyError: log.error(u"Custom Column No.%d is not exisiting in calibre database", config.config_read_column) + except OperationalError as e: + db.session.rollback() + log.error(u"Read status could not set: %e", e) + return "" @web.route("/ajax/togglearchived/", methods=['POST']) diff --git a/test/Calibre-Web TestSummary.html b/test/Calibre-Web TestSummary.html old mode 100644 new mode 100755 index ea31b506..4f560221 --- a/test/Calibre-Web TestSummary.html +++ b/test/Calibre-Web TestSummary.html @@ -36,17 +36,17 @@
-

Start Time: 2020-05-01 13:35:57

+

Start Time: 2020-05-05 19:02:03

-

Stop Time: 2020-05-01 14:32:26

+

Stop Time: 2020-05-05 19:58:37

-

Duration: 47:49 min

+

Duration: 47:42 min

@@ -1829,8 +1829,8 @@ AssertionError: False is not true : logfile config value is not empty after rese test_updater.test_updater 7 - 6 - 0 + 5 + 1 0 1 @@ -1867,11 +1867,33 @@ AssertionError: False is not true : logfile config value is not empty after rese - +
test_check_update_stable_versions
- PASS + +
+ FAIL +
+ + + + @@ -1924,8 +1946,8 @@ AssertionError: False is not true : logfile config value is not empty after rese test_user_template.test_user_template 19 - 18 - 1 + 19 + 0 0 0 @@ -2088,31 +2110,11 @@ AssertionError: False is not true : logfile config value is not empty after rese - +
test_series_user_template
- -
- FAIL -
- - - - + PASS @@ -2574,7 +2576,7 @@ AssertionError: False is not true SQLAlchemy-Utils - 0.36.4 + 0.36.5 test_OAuth_login