diff --git a/cps/__init__.py b/cps/__init__.py index 24267b1f..1b1fa191 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -55,13 +55,13 @@ mimetypes.init() mimetypes.add_type('application/xhtml+xml', '.xhtml') mimetypes.add_type('application/epub+zip', '.epub') mimetypes.add_type('application/epub+zip', '.kepub') -mimetypes.add_type('text/xml', '.fb2') +mimetypes.add_type('application/fb2+zip', '.fb2') mimetypes.add_type('application/octet-stream', '.mobi') mimetypes.add_type('application/octet-stream', '.prc') mimetypes.add_type('application/vnd.amazon.ebook', '.azw') mimetypes.add_type('application/x-mobi8-ebook', '.azw3') -mimetypes.add_type('application/x-rar', '.cbr') -mimetypes.add_type('application/zip', '.cbz') +mimetypes.add_type('application/x-cbr', '.cbr') +mimetypes.add_type('application/x-cbz', '.cbz') mimetypes.add_type('application/x-tar', '.cbt') mimetypes.add_type('application/x-7z-compressed', '.cb7') mimetypes.add_type('image/vnd.djv', '.djv') diff --git a/cps/admin.py b/cps/admin.py index 943abe92..71403e6c 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -32,7 +32,8 @@ from datetime import time as datetime_time from functools import wraps from urllib.parse import urlparse -from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, send_from_directory, g, Response +from flask import Blueprint, flash, redirect, url_for, abort, request, make_response, \ + send_from_directory, g, jsonify from markupsafe import Markup from .cw_login import current_user from flask_babel import gettext as _ @@ -378,10 +379,7 @@ def list_users(): user.default = get_user_locale_language(user.default_language) table_entries = {'totalNotFiltered': total_count, 'total': filtered_count, "rows": users} - js_list = json.dumps(table_entries, cls=db.AlchemyEncoder) - response = make_response(js_list) - response.headers["Content-Type"] = "application/json; charset=utf-8" - return response + return make_response(json.dumps(table_entries, cls=db.AlchemyEncoder)) @admi.route("/ajax/deleteuser", methods=['POST']) @@ -400,7 +398,7 @@ def delete_user(): success = list() if not users: log.error("User not found") - return Response(json.dumps({'type': "danger", 'message': _("User not found")}), mimetype='application/json') + return make_response(jsonify(type="danger", message=_("User not found"))) for user in users: try: message = _delete_user(user) @@ -416,7 +414,7 @@ def delete_user(): log.info("Users {} deleted".format(user_ids)) success = [{'type': "success", 'message': _("{} users deleted successfully").format(count)}] success.extend(errors) - return Response(json.dumps(success), mimetype='application/json') + return make_response(jsonify(success)) @admi.route("/ajax/getlocale") @@ -498,10 +496,10 @@ def edit_list_user(param): if not ub.session.query(ub.User). \ filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, ub.User.id != user.id).count(): - return Response( - json.dumps([{'type': "danger", + return make_response( + jsonify([{'type': "danger", 'message': _("No admin user remaining, can't remove admin role", - nick=user.name)}]), mimetype='application/json') + nick=user.name)}])) user.role &= ~value else: raise Exception(_("Value has to be true or false")) @@ -947,7 +945,7 @@ def do_full_kobo_sync(userid): count = ub.session.query(ub.KoboSyncedBooks).filter(userid == ub.KoboSyncedBooks.user_id).delete() message = _("{} sync entries deleted").format(count) ub.session_commit(message) - return Response(json.dumps([{"type": "success", "message": message}]), mimetype='application/json') + return make_response(jsonify(type="success", message=message)) def check_valid_read_column(column): @@ -1264,7 +1262,7 @@ def _configuration_ldap_helper(to_save): @admin_required def simulatedbchange(): db_change, db_valid = _db_simulate_change() - return Response(json.dumps({"change": db_change, "valid": db_valid}), mimetype='application/json') + return make_response(jsonify(change=db_change, valid=db_valid)) @admi.route("/admin/user/new", methods=["GET", "POST"]) @@ -1896,7 +1894,7 @@ def _configuration_result(error_flash=None, reboot=False): resp['result'] = [{'type': "success", 'message': _("Calibre-Web configuration updated")}] resp['reboot'] = reboot resp['config_upload'] = config.config_upload_formats - return Response(json.dumps(resp), mimetype='application/json') + return make_response(jsonify(resp)) def _db_configuration_result(error_flash=None, gdrive_error=None): diff --git a/cps/editbooks.py b/cps/editbooks.py index d5d92453..355a9bfd 100644 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -28,11 +28,11 @@ from shutil import copyfile from markupsafe import escape, Markup # dependency of flask from functools import wraps -from flask import Blueprint, request, flash, redirect, url_for, abort, Response +from flask import Blueprint, request, flash, redirect, url_for, abort, jsonify, make_response, Response from flask_babel import gettext as _ from flask_babel import lazy_gettext as N_ from flask_babel import get_locale -from .cw_login import current_user, login_required +from .cw_login import current_user from sqlalchemy.exc import OperationalError, IntegrityError, InterfaceError from sqlalchemy.orm.exc import StaleDataError from sqlalchemy.sql.expression import func @@ -76,7 +76,7 @@ def edit_required(f): @editbook.route("/ajax/delete/", methods=["POST"]) @user_login_required def delete_book_from_details(book_id): - return Response(delete_book_from_table(book_id, "", True), mimetype='application/json') + return delete_book_from_table(book_id, "", True) # , mimetype='application/json') @editbook.route("/delete/", defaults={'book_format': ""}, methods=["POST"]) @@ -158,16 +158,16 @@ def upload(): if len(request.files.getlist("btn-upload")) < 2: if current_user.role_edit() or current_user.role_admin(): resp = {"location": url_for('edit-book.show_edit_book', book_id=book_id)} - return Response(json.dumps(resp), mimetype='application/json') + return make_response(jsonify(resp)) else: resp = {"location": url_for('web.show_book', book_id=book_id)} - return Response(json.dumps(resp), mimetype='application/json') + return make_response(jsonify(resp)) except (OperationalError, IntegrityError, StaleDataError) as e: calibre_db.session.rollback() log.error_or_exception("Database error: {}".format(e)) flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e), category="error") - return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + return make_response(jsonify(location=url_for("web.index"))) abort(404) @@ -206,7 +206,7 @@ def table_get_custom_enum(c_id): ret.append({'value': "", 'text': ""}) for idx, en in enumerate(cc.get_display_dict()['enum_values']): ret.append({'value': en, 'text': en}) - return json.dumps(ret) + return make_response(jsonify(ret)) @editbook.route("/ajax/editbooks/", methods=['POST']) @@ -221,68 +221,54 @@ def edit_list_book(param): try: if param == 'series_index': edit_book_series_index(vals['value'], book) - ret = Response(json.dumps({'success': True, 'newValue': book.series_index}), mimetype='application/json') + ret = make_response(jsonify(success=True, newValue=book.series_index)) elif param == 'tags': edit_book_tags(vals['value'], book) - ret = Response(json.dumps({'success': True, 'newValue': ', '.join([tag.name for tag in book.tags])}), - mimetype='application/json') + ret = make_response(jsonify(success=True, newValue=', '.join([tag.name for tag in book.tags]))) elif param == 'series': edit_book_series(vals['value'], book) - ret = Response(json.dumps({'success': True, 'newValue': ', '.join([serie.name for serie in book.series])}), - mimetype='application/json') + ret = make_response(jsonify(success=True, newValue=', '.join([serie.name for serie in book.series]))) elif param == 'publishers': edit_book_publisher(vals['value'], book) - ret = Response(json.dumps({'success': True, - 'newValue': ', '.join([publisher.name for publisher in book.publishers])}), - mimetype='application/json') + ret = make_response(jsonify(success=True, + newValue=', '.join([publisher.name for publisher in book.publishers]))) elif param == 'languages': invalid = list() edit_book_languages(vals['value'], book, invalid=invalid) if invalid: - ret = Response(json.dumps({'success': False, - 'msg': 'Invalid languages in request: {}'.format(','.join(invalid))}), - mimetype='application/json') + ret = make_response(jsonify(success=False, + msg='Invalid languages in request: {}'.format(','.join(invalid)))) else: lang_names = list() for lang in book.languages: lang_names.append(isoLanguages.get_language_name(get_locale(), lang.lang_code)) - ret = Response(json.dumps({'success': True, 'newValue': ', '.join(lang_names)}), - mimetype='application/json') + ret = make_response(jsonify(success=True, newValue=', '.join(lang_names))) elif param == 'author_sort': book.author_sort = vals['value'] - ret = Response(json.dumps({'success': True, 'newValue': book.author_sort}), - mimetype='application/json') + ret = make_response(jsonify(success=True, newValue=book.author_sort)) elif param == 'title': sort_param = book.sort if handle_title_on_edit(book, vals.get('value', "")): rename_error = helper.update_dir_structure(book.id, config.get_book_path()) if not rename_error: - ret = Response(json.dumps({'success': True, 'newValue': book.title}), - mimetype='application/json') + ret = make_response(jsonify(success=True, newValue=book.title)) else: - ret = Response(json.dumps({'success': False, - 'msg': rename_error}), - mimetype='application/json') + ret = make_response(jsonify(success=False, msg=rename_error)) elif param == 'sort': book.sort = vals['value'] - ret = Response(json.dumps({'success': True, 'newValue': book.sort}), - mimetype='application/json') + ret = make_response(jsonify(success=True,newValue=book.sort)) elif param == 'comments': edit_book_comments(vals['value'], book) - ret = Response(json.dumps({'success': True, 'newValue': book.comments[0].text}), - mimetype='application/json') + ret = make_response(jsonify(success=True, newValue=book.comments[0].text)) elif param == 'authors': input_authors, __ = handle_author_on_edit(book, vals['value'], vals.get('checkA', None) == "true") rename_error = helper.update_dir_structure(book.id, config.get_book_path(), input_authors[0]) if not rename_error: - ret = Response(json.dumps({ - 'success': True, - 'newValue': ' & '.join([author.replace('|', ',') for author in input_authors])}), - mimetype='application/json') + ret = make_response(jsonify( + success=True, + newValue=' & '.join([author.replace('|', ',') for author in input_authors]))) else: - ret = Response(json.dumps({'success': False, - 'msg': rename_error}), - mimetype='application/json') + ret = make_response(jsonify(success=False, msg=rename_error)) elif param == 'is_archived': is_archived = change_archived_books(book.id, vals['value'] == "True", message="Book {} archive bit set to: {}".format(book.id, vals['value'])) @@ -301,8 +287,7 @@ def edit_list_book(param): if vals['value'] in ["True", "False"]: ret = "" else: - ret = Response(json.dumps({'success': True, 'newValue': vals['value']}), - mimetype='application/json') + ret = make_response(jsonify(success=True, newValue=vals['value'])) else: return _("Parameter not found"), 400 book.last_modified = datetime.now(timezone.utc) @@ -315,9 +300,8 @@ def edit_list_book(param): except (OperationalError, IntegrityError, StaleDataError) as e: calibre_db.session.rollback() log.error_or_exception("Database error: {}".format(e)) - ret = Response(json.dumps({'success': False, - 'msg': 'Database error: {}'.format(e.orig if hasattr(e, "orig") else e)}), - mimetype='application/json') + ret = make_response(jsonify(success=False, + msg='Database error: {}'.format(e.orig if hasattr(e, "orig") else e))) return ret @@ -328,13 +312,13 @@ def get_sorted_entry(field, bookid): book = calibre_db.get_filtered_book(bookid) if book: if field == 'title': - return json.dumps({'sort': book.sort}) + return make_response(jsonify(sort=book.sort)) elif field == 'authors': - return json.dumps({'author_sort': book.author_sort}) + return make_response(jsonify(author_sort=book.author_sort)) if field == 'sort': - return json.dumps({'sort': book.title}) + return make_response(jsonify(sort=book.title)) if field == 'author_sort': - return json.dumps({'authors': " & ".join([a.name for a in calibre_db.order_authors([book])])}) + return make_response(jsonify(authors=" & ".join([a.name for a in calibre_db.order_authors([book])]))) return "" @@ -350,7 +334,7 @@ def simulate_merge_list_book(): from_book = [] for book_id in vals: from_book.append(calibre_db.get_book(book_id).title) - return json.dumps({'to': to_book, 'from': from_book}) + return make_response(jsonify({'to': to_book, 'from': from_book})) return "" @@ -388,7 +372,7 @@ def merge_list_book(): element.uncompressed_size, to_name)) delete_book_from_table(from_book.id, "", True) - return json.dumps({'success': True}) + return make_response(jsonify(success=True)) return "" @@ -428,11 +412,11 @@ def table_xchange_author_title(): except (OperationalError, IntegrityError, StaleDataError) as e: calibre_db.session.rollback() log.error_or_exception("Database error: {}".format(e)) - return json.dumps({'success': False}) + return make_response(jsonify(success=False)) if config.config_use_google_drive: gdriveutils.updateGdriveCalibreFromLocal() - return json.dumps({'success': True}) + return make_response(jsonify(success=True)) return "" @@ -560,7 +544,7 @@ def do_edit_book(book_id, upload_formats=None): if upload_formats: resp = {"location": url_for('edit-book.show_edit_book', book_id=book_id)} - return Response(json.dumps(resp), mimetype='application/json') + return make_response(jsonify(resp)) if "detail_view" in to_save: return redirect(url_for('web.show_book', book_id=book.id)) @@ -769,17 +753,17 @@ def file_handling_on_upload(requested_file): if config.config_check_extensions and allowed_extensions != ['']: if not validate_mime_type(requested_file, allowed_extensions): flash(_("File type isn't allowed to be uploaded to this server"), category="error") - return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + return None, make_response(jsonify(location=url_for("web.index"))) if '.' in requested_file.filename: file_ext = requested_file.filename.rsplit('.', 1)[-1].lower() if file_ext not in allowed_extensions and '' not in allowed_extensions: flash( _("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext), category="error") - return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + return None, make_response(jsonify(location=url_for("web.index"))) else: flash(_('File to be uploaded must have an extension'), category="error") - return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + return None, make_response(jsonify(location=url_for("web.index"))) # extract metadata from file try: @@ -788,7 +772,7 @@ def file_handling_on_upload(requested_file): log.error("File %s could not saved to temp dir", requested_file.filename) flash(_("File %(filename)s could not saved to temp dir", filename=requested_file.filename), category="error") - return None, Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json') + return None, make_response(jsonify(location=url_for("web.index"))) return meta, None @@ -860,7 +844,7 @@ def delete_whole_book(book_id, book): def render_delete_book_result(book_format, json_response, warning, book_id, location=""): if book_format: if json_response: - return json.dumps([warning, {"location": url_for("edit-book.show_edit_book", book_id=book_id), + return jsonify([warning, {"location": url_for("edit-book.show_edit_book", book_id=book_id), "type": "success", "format": book_format, "message": _('Book Format Successfully Deleted')}]) @@ -869,7 +853,7 @@ def render_delete_book_result(book_format, json_response, warning, book_id, loca return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) else: if json_response: - return json.dumps([warning, {"location": get_redirect_location(location, "web.index"), + return jsonify([warning, {"location": get_redirect_location(location, "web.index"), "type": "success", "format": book_format, "message": _('Book Successfully Deleted')}]) @@ -887,7 +871,7 @@ def delete_book_from_table(book_id, book_format, json_response, location=""): result, error = helper.delete_book(book, config.get_book_path(), book_format=book_format.upper()) if not result: if json_response: - return json.dumps([{"location": url_for("edit-book.show_edit_book", book_id=book_id), + return jsonify([{"location": url_for("edit-book.show_edit_book", book_id=book_id), "type": "danger", "format": "", "message": error}]) @@ -914,7 +898,7 @@ def delete_book_from_table(book_id, book_format, json_response, location=""): log.error_or_exception(ex) calibre_db.session.rollback() if json_response: - return json.dumps([{"location": url_for("edit-book.show_edit_book", book_id=book_id), + return jsonify([{"location": url_for("edit-book.show_edit_book", book_id=book_id), "type": "danger", "format": "", "message": ex}]) @@ -928,7 +912,7 @@ def delete_book_from_table(book_id, book_format, json_response, location=""): return render_delete_book_result(book_format, json_response, warning, book_id, location) message = _("You are missing permissions to delete books") if json_response: - return json.dumps({"location": url_for("edit-book.show_edit_book", book_id=book_id), + return jsonify({"location": url_for("edit-book.show_edit_book", book_id=book_id), "type": "danger", "format": "", "message": message}) diff --git a/cps/file_helper.py b/cps/file_helper.py index 9d5406d3..361709f8 100644 --- a/cps/file_helper.py +++ b/cps/file_helper.py @@ -34,6 +34,15 @@ except ImportError as e: error = "Cannot import python-magic, checking uploaded file metadata will not work: {}".format(e) +def get_mimetype(ext): + # overwrite some mimetypes for proper file detection + mimes = {".fb2": "text/xml", + ".cbz": "application/zip", + ".cbr": "application/x-rar" + } + return mimes.get(ext, mimetypes.types_map[ext]) + + def get_temp_dir(): tmp_dir = os.path.join(gettempdir(), 'calibre_web') if not os.path.isdir(tmp_dir): @@ -54,7 +63,7 @@ def validate_mime_type(file_buffer, allowed_extensions): allowed_mimetypes = list() for x in allowed_extensions: try: - allowed_mimetypes.append(mimetypes.types_map["." + x]) + allowed_mimetypes.append(get_mimetype("." + x)) except KeyError: log.error("Unkown mimetype for Extension: {}".format(x)) tmp_mime_type = mime.from_buffer(file_buffer.read()) diff --git a/cps/helper.py b/cps/helper.py index d6f4dd7d..0706d40b 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -30,7 +30,7 @@ import requests import unidecode from uuid import uuid4 -from flask import send_from_directory, make_response, abort, url_for, Response +from flask import send_from_directory, make_response, abort, url_for, Response, request from flask_babel import gettext as _ from flask_babel import lazy_gettext as N_ from flask_babel import get_locale @@ -974,7 +974,8 @@ def do_download_file(book, book_format, client, data, headers): # ToDo Check headers parameter for element in headers: response.headers[element[0]] = element[1] - log.info('Downloading file: {}'.format(os.path.join(filename, book_name + "." + book_format))) + log.info('Downloading file: \'%s\' by %s - %s', format(os.path.join(filename, book_name + "." + book_format)), + current_user.name, request.headers.get('X-Forwarded-For', request.remote_addr)) return response diff --git a/cps/opds.py b/cps/opds.py index 485d241a..6dea544a 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -21,10 +21,10 @@ # along with this program. If not, see . import datetime -import json +# import json from urllib.parse import unquote_plus -from flask import Blueprint, request, render_template, make_response, abort, Response, g +from flask import Blueprint, request, render_template, make_response, abort, g, jsonify from flask_babel import get_locale from flask_babel import gettext as _ @@ -451,7 +451,7 @@ def get_database_stats(): stat['authors'] = calibre_db.session.query(db.Authors).count() stat['categories'] = calibre_db.session.query(db.Tags).count() stat['series'] = calibre_db.session.query(db.Series).count() - return Response(json.dumps(stat), mimetype="application/json") + return make_response(jsonify(stat)) @opds.route("/opds/thumb_240_240/") diff --git a/cps/search_metadata.py b/cps/search_metadata.py index 719331f0..ae3c9faf 100644 --- a/cps/search_metadata.py +++ b/cps/search_metadata.py @@ -23,7 +23,7 @@ import json import os import sys -from flask import Blueprint, Response, request, url_for +from flask import Blueprint, request, url_for, make_response, jsonify from .cw_login import current_user from flask_babel import get_locale from sqlalchemy.exc import InvalidRequestError, OperationalError @@ -89,7 +89,7 @@ def metadata_provider(): provider.append( {"name": c.__name__, "active": ac, "initial": ac, "id": c.__id__} ) - return Response(json.dumps(provider), mimetype="application/json") + return make_response(jsonify(provider)) @meta.route("/metadata/provider", methods=["POST"]) @@ -114,9 +114,7 @@ def metadata_change_active_provider(prov_name): provider = next((c for c in cl if c.__id__ == prov_name), None) if provider is not None: data = provider.search(new_state.get("query", "")) - return Response( - json.dumps([asdict(x) for x in data]), mimetype="application/json" - ) + return make_response(jsonify([asdict(x) for x in data])) return "" @@ -138,4 +136,4 @@ def metadata_search(): } for future in concurrent.futures.as_completed(meta): data.extend([asdict(x) for x in future.result() if x]) - return Response(json.dumps(data), mimetype="application/json") + return make_response(jsonify(data)) diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py index e0df3802..dc0af0c4 100644 --- a/cps/tasks/convert.py +++ b/cps/tasks/convert.py @@ -210,13 +210,13 @@ class TaskConvert(CalibreTask): else: error_message = N_('%(format)s format not found on disk', format=format_new_ext.upper()) local_db.session.close() - log.info("ebook converter failed with error while converting book") - if not error_message: - error_message = N_('Ebook converter failed with unknown error') - else: - log.error(error_message) - self._handleError(error_message) - return + log.info("ebook converter failed with error while converting book") + if not error_message: + error_message = N_('Ebook converter failed with unknown error') + else: + log.error(error_message) + self._handleError(error_message) + return def _convert_kepubify(self, file_path, format_old_ext, format_new_ext): if config.config_embed_metadata and config.config_binariesdir: diff --git a/cps/web.py b/cps/web.py index 36653a45..e1068c6f 100644 --- a/cps/web.py +++ b/cps/web.py @@ -25,8 +25,7 @@ import chardet # dependency of requests import copy from importlib.metadata import metadata -from flask import Blueprint, jsonify -from flask import request, redirect, send_from_directory, make_response, flash, abort, url_for, Response +from flask import Blueprint, jsonify, request, redirect, send_from_directory, make_response, flash, abort, url_for from flask import session as flask_session from flask_babel import gettext as _ from flask_babel import get_locale @@ -1198,13 +1197,14 @@ def serve_book(book_id, book_format, anyname): if not data: return "File not in Database" range_header = request.headers.get('Range', None) - + if not range_header: + log.info('Serving book: \'%s\' to %s - %s', data.name, current_user.name, + request.headers.get('X-Forwarded-For', request.remote_addr)) if config.config_use_google_drive: try: headers = Headers() headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream") - if not range_header: - log.info('Serving book: %s', data.name) + if not range_header: headers['Accept-Ranges'] = 'bytes' df = getFileFromEbooksFolder(book.path, data.name + "." + book_format) return do_gdrive_download(df, headers, (book_format.upper() == 'TXT')) @@ -1213,7 +1213,6 @@ def serve_book(book_id, book_format, anyname): return "File Not Found" else: if book_format.upper() == 'TXT': - log.info('Serving book: %s', data.name) try: rawdata = open(os.path.join(config.get_book_path(), book.path, data.name + "." + book_format), "rb").read() @@ -1234,7 +1233,6 @@ def serve_book(book_id, book_format, anyname): response = make_response( send_from_directory(os.path.join(config.get_book_path(), book.path), data.name + "." + book_format)) if not range_header: - log.info('Serving book: %s', data.name) response.headers['Accept-Ranges'] = 'bytes' return response @@ -1253,8 +1251,7 @@ def download_link(book_id, book_format, anyname): @download_required def send_to_ereader(book_id, book_format, convert): if not config.get_mail_server_configured(): - response = [{'type': "danger", 'message': _("Please configure the SMTP mail settings first...")}] - return Response(json.dumps(response), mimetype='application/json') + return make_response(jsonify(type="danger", message=_("Please configure the SMTP mail settings first..."))) elif current_user.kindle_mail: result = send_mail(book_id, book_format, convert, current_user.kindle_mail, config.get_book_path(), current_user.name) @@ -1266,7 +1263,7 @@ def send_to_ereader(book_id, book_format, convert): response = [{'type': "danger", 'message': _("Oops! There was an error sending book: %(res)s", res=result)}] else: response = [{'type': "danger", 'message': _("Oops! Please update your profile with a valid eReader Email.")}] - return Response(json.dumps(response), mimetype='application/json') + return make_response(jsonify(response)) # ################################### Login Logout ################################################################## diff --git a/optional-requirements.txt b/optional-requirements.txt index b9d98544..1726847f 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -1,13 +1,13 @@ # GDrive Integration google-api-python-client>=1.7.11,<2.200.0 gevent>20.6.0,<24.3.0 -greenlet>=0.4.17,<3.1.0 +greenlet>=0.4.17,<3.2.0 httplib2>=0.9.2,<0.23.0 oauth2client>=4.0.0,<4.1.4 uritemplate>=3.0.0,<4.2.0 pyasn1-modules>=0.0.8,<0.5.0 pyasn1>=0.1.9,<0.7.0 -PyDrive2>=1.3.1,<1.20.0 +PyDrive2>=1.3.1,<1.22.0 PyYAML>=3.12,<6.1 rsa>=3.4.2,<4.10.0 diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 039f2e7e..fc3b5acd 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2024-11-29 20:17:45

+

Start Time: 2024-12-06 17:23:58

-

Stop Time: 2024-11-30 03:36:53

+

Stop Time: 2024-12-07 00:45:19

-

Duration: 6h 12 min

+

Duration: 6h 11 min

@@ -1023,13 +1023,13 @@ - + TestEditAdditionalBooks 18 - 17 + 18 + 0 0 0 - 1 Detail @@ -1172,11 +1172,11 @@ - +
TestEditAdditionalBooks - test_xss_author_edit
- SKIP + PASS @@ -2074,11 +2074,11 @@ IndexError: list index out of range - + TestEditBooksOnGdrive 18 - 18 - 0 + 16 + 2 0 0 @@ -2205,11 +2205,31 @@ IndexError: list index out of range - +
TestEditBooksOnGdrive - test_edit_rating
- PASS + +
+ FAIL +
+ + + + @@ -2241,11 +2261,31 @@ IndexError: list index out of range - +
TestEditBooksOnGdrive - test_watch_metadata
- PASS + +
+ FAIL +
+ + + + @@ -5801,10 +5841,10 @@ IndexError: list index out of range Total 523 - 514 - 0 + 513 + 2 1 - 8 + 7   @@ -6348,7 +6388,7 @@ IndexError: list index out of range