From 5aa37c68a2635cb9d0d0e32535860855621c4621 Mon Sep 17 00:00:00 2001 From: ElQuimm <50202052+ElQuimm@users.noreply.github.com> Date: Mon, 19 Apr 2021 10:11:26 +0200 Subject: [PATCH 1/3] Updated version of italian.po Thank you. --- cps/translations/it/LC_MESSAGES/messages.po | 57 ++++++--------------- 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/cps/translations/it/LC_MESSAGES/messages.po b/cps/translations/it/LC_MESSAGES/messages.po index ba186301..85af2150 100644 --- a/cps/translations/it/LC_MESSAGES/messages.po +++ b/cps/translations/it/LC_MESSAGES/messages.po @@ -61,12 +61,10 @@ msgid "UI Configuration" msgstr "Configurazione dell'interfaccia utente" #: cps/admin.py:238 cps/templates/admin.html:46 -#, fuzzy msgid "Edit Users" msgstr "Modifica gli utenti" #: cps/admin.py:278 -#, fuzzy msgid "all" msgstr "tutti" @@ -81,11 +79,11 @@ msgstr "tutte le lingue presenti" #: cps/admin.py:353 cps/admin.py:1275 msgid "Guest Name can't be changed" -msgstr "Il nome dell'utente Guest non può essere modificato" +msgstr "Il nome dell'utente Guest (ospite) non può essere modificato" #: cps/admin.py:362 msgid "Guest can't have this role" -msgstr "" +msgstr "L'utente Guest (ospite) non può avere questo ruolo" #: cps/admin.py:371 cps/admin.py:1240 msgid "No admin user remaining, can't remove admin role" @@ -93,11 +91,11 @@ msgstr "Non rimarrebbe nessun utente amministratore, non posso rimuovere il ruol #: cps/admin.py:376 msgid "Guest can't have this view" -msgstr "" +msgstr " L'utente Guest (ospite) non può avere questa schermata" #: cps/admin.py:391 msgid "Guest's Locale is determined automatically and can't be set" -msgstr "" +msgstr "Le impostazioni locali dell'utente Guest (ospite) sono determinate automaticamente e non possono essere configurate" #: cps/admin.py:447 cps/admin.py:1132 msgid "Calibre-Web configuration updated" @@ -120,7 +118,6 @@ msgid "Are you sure you want to delete this shelf?" msgstr "Vuoi veramente eliminare questo scaffale?" #: cps/admin.py:466 -#, fuzzy msgid "Are you sure you want to change locales of selected user(s)?" msgstr "Sei sicuro di voler modificare le impostazioni locali dell'/degli utente/i selezionato/i?" @@ -237,7 +234,6 @@ msgid "User '%(user)s' created" msgstr "L'utente '%(user)s' è stato creato" #: cps/admin.py:1209 -#, fuzzy msgid "Found an existing account for this e-mail address or name." msgstr "Trovato un account esistente con questo e-mail o nome di utente" @@ -248,7 +244,7 @@ msgstr "L'utente '%(nick)s' è stato eliminato" #: cps/admin.py:1223 cps/admin.py:1224 msgid "Can't delete Guest User" -msgstr "" +msgstr "Non posso eliminare l'utente Guest (ospite)" #: cps/admin.py:1227 msgid "No admin user remaining, can't delete user" @@ -593,9 +589,8 @@ msgid "Book path %(path)s not found on Google Drive" msgstr "Non ho trovato la cartella %(path)s del libro su Google Drive" #: cps/helper.py:511 -#, fuzzy msgid "Found an existing account for this e-mail address" -msgstr "Ho trovato un account creato in precedenza con questa e-mail." +msgstr "Ho trovato un account creato in precedenza con questo indirizzo e-mail." #: cps/helper.py:519 msgid "This username is already taken" @@ -721,7 +716,7 @@ msgstr "GitHub, errore Oauth: per favore riprova più tardi." #: cps/oauth_bb.py:336 msgid "GitHub Oauth error: {}" -msgstr "" +msgstr "GitHub, errore Oauth: {}" #: cps/oauth_bb.py:357 msgid "Google Oauth error, please retry later." @@ -729,7 +724,7 @@ msgstr "Google, errore Oauth: per favore riprova più tardi." #: cps/oauth_bb.py:360 msgid "Google Oauth error: {}" -msgstr "" +msgstr "Google, errore Oauth: {}" #: cps/opds.py:110 cps/opds.py:199 cps/opds.py:276 cps/opds.py:328 #: cps/templates/grid.html:14 cps/templates/list.html:14 @@ -1257,11 +1252,11 @@ msgstr "Utente" #: cps/templates/admin.html:14 cps/templates/register.html:13 #: cps/templates/user_edit.html:14 cps/templates/user_table.html:106 msgid "E-mail Address" -msgstr "E-mail" +msgstr "Indirizzo e-mail" #: cps/templates/admin.html:15 cps/templates/user_edit.html:27 msgid "Send to Kindle E-mail Address" -msgstr "Invia all'email di Kindle" +msgstr "Invia all'indirizzo e-mail di Kindle" #: cps/templates/admin.html:17 cps/templates/layout.html:77 #: cps/templates/user_table.html:114 @@ -1806,7 +1801,7 @@ msgstr "Autenticazione Google Drive" #: cps/templates/config_edit.html:51 msgid "Please hit save to continue with setup" -msgstr "Per favore premi invio per proseguire con la configurazione" +msgstr "Per favore premi invio per proseguire la configurazione" #: cps/templates/config_edit.html:54 msgid "Please finish Google Drive setup after login" @@ -2588,9 +2583,8 @@ msgid "Select" msgstr "Seleziona" #: cps/templates/modal_dialogs.html:134 -#, fuzzy msgid "Ok" -msgstr "Libro" +msgstr "Ok" #: cps/templates/osd.xml:5 msgid "Calibre-Web eBook Catalog" @@ -2765,9 +2759,8 @@ msgid "Exclude Series" msgstr "Escludi serie" #: cps/templates/search_form.html:88 -#, fuzzy msgid "Exclude Shelves" -msgstr "Escludi serie" +msgstr "Escludi scaffali" #: cps/templates/search_form.html:108 msgid "Exclude Languages" @@ -2923,44 +2916,37 @@ msgstr "Aggiungi valori personali permessi/negati nelle colonne" #: cps/templates/user_edit.html:135 cps/templates/user_table.html:137 msgid "Delete User" -msgstr "Elimina questo utente" +msgstr "Elimina utente" #: cps/templates/user_edit.html:146 msgid "Generate Kobo Auth URL" msgstr "Genera un URL di autenticazione per Kobo" #: cps/templates/user_table.html:76 -#, fuzzy msgid "Select..." -msgstr "Seleziona" +msgstr "Seleziona..." #: cps/templates/user_table.html:102 -#, fuzzy msgid "Edit User" msgstr "Modifica utente" #: cps/templates/user_table.html:105 -#, fuzzy msgid "Enter Username" msgstr "Digita il nome utente" #: cps/templates/user_table.html:106 -#, fuzzy msgid "Enter E-mail Address" msgstr "Digita l'indirizzo e-mail" #: cps/templates/user_table.html:107 -#, fuzzy msgid "Enter Kindle E-mail Address" msgstr "Digita l'email di Kindle" #: cps/templates/user_table.html:107 -#, fuzzy msgid "Kindle E-mail" msgstr "E-mail di Kindle" #: cps/templates/user_table.html:108 -#, fuzzy msgid "Locale" msgstr "Locale" @@ -2969,7 +2955,6 @@ msgid "Visible Book Languages" msgstr "Lingue dei libri visualizzabili" #: cps/templates/user_table.html:110 -#, fuzzy msgid "Edit Denied Tags" msgstr "Modifica le categorie negate" @@ -2978,7 +2963,6 @@ msgid "Denied Tags" msgstr "Categorie negate" #: cps/templates/user_table.html:111 -#, fuzzy msgid "Edit Allowed Tags" msgstr "Modifica le categorie permesse" @@ -2987,27 +2971,22 @@ msgid "Allowed Tags" msgstr "Categorie permesse" #: cps/templates/user_table.html:112 -#, fuzzy msgid "Edit Allowed Column Values" msgstr "Modifica i valori delle colonne permesse" #: cps/templates/user_table.html:112 -#, fuzzy msgid "Allowed Column Values" msgstr "Valori delle colonne permesse" #: cps/templates/user_table.html:113 -#, fuzzy msgid "Edit Denied Column Values" msgstr "Modifica i valori delle colonne negate" #: cps/templates/user_table.html:113 -#, fuzzy msgid "Denied Columns Values" msgstr "Valori delle colonne negate" #: cps/templates/user_table.html:115 -#, fuzzy msgid "Change Password" msgstr "Modifica la password" @@ -3016,12 +2995,10 @@ msgid "View" msgstr "Visualizza" #: cps/templates/user_table.html:121 -#, fuzzy msgid "Edit Public Shelfs" -msgstr "Modifica scaffali pubblici" +msgstr "Modifica gli scaffali pubblici" #: cps/templates/user_table.html:124 -#, fuzzy msgid "Show read/unread selection" -msgstr "Mostra l'opzione per la selezione delle serie" +msgstr "Mostra l'opzione per la selezione dello stato letto/non letto" From 4e3a5ca33bf499a41a2f413e7751ed5d0308f0fd Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Sun, 25 Apr 2021 11:20:21 +0200 Subject: [PATCH 2/3] Added additional debug messages, catch additional errors in updater --- cps/admin.py | 33 ++++++++++++++++++++------- cps/helper.py | 3 ++- cps/logger.py | 4 ++-- cps/shelf.py | 29 +++++++++++++++++------- cps/updater.py | 60 ++++++++++++++++++++++++++++++++------------------ 5 files changed, 88 insertions(+), 41 deletions(-) diff --git a/cps/admin.py b/cps/admin.py index ba82d2c2..55ab41ad 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -318,11 +318,14 @@ def delete_user(): message = _delete_user(user) count += 1 except Exception as ex: + log.error(ex) errors.append({'type': "danger", 'message': str(ex)}) if count == 1: + log.info("User {} deleted".format(user_ids)) success = [{'type': "success", 'message': message}] elif count > 1: + 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') @@ -472,6 +475,7 @@ def update_view_configuration(): config.save() flash(_(u"Calibre-Web configuration updated"), category="success") + log.debug("Calibre-Web configuration updated") before_request() return view_configuration() @@ -900,6 +904,7 @@ def pathchooser(): def basic_configuration(): logout_user() if request.method == "POST": + log.debug("Basic Configuration send") return _configuration_update_helper(configured=filepicker) return _configuration_result(configured=filepicker) @@ -1155,7 +1160,8 @@ def _configuration_update_helper(configured): return _configuration_result(unrar_status, gdrive_error, configured) except (OperationalError, InvalidRequestError): ub.session.rollback() - _configuration_result(_(u"Settings DB is not Writeable"), gdrive_error, configured) + log.error("Settings DB is not Writeable") + _configuration_result(_("Settings DB is not Writeable"), gdrive_error, configured) try: metadata_db = os.path.join(config.config_calibre_dir, "metadata.db") @@ -1187,6 +1193,7 @@ def _configuration_result(error_flash=None, gdrive_error=None, configured=True): if gdrive_error is None: gdrive_error = gdriveutils.get_error_text() if gdrive_error: + log.error(gdrive_error) gdrive_error = _(gdrive_error) else: # if config.config_use_google_drive and\ @@ -1196,6 +1203,7 @@ def _configuration_result(error_flash=None, gdrive_error=None, configured=True): show_back_button = current_user.is_authenticated show_login_button = config.db_configured and not current_user.is_authenticated if error_flash: + log.error(error_flash) config.load() flash(error_flash, category="error") show_login_button = False @@ -1248,13 +1256,16 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support): ub.session.add(content) ub.session.commit() flash(_(u"User '%(user)s' created", user=content.name), category="success") + log.debug("User {} created".format(content.name)) return redirect(url_for('admin.admin')) except IntegrityError: ub.session.rollback() - flash(_(u"Found an existing account for this e-mail address or name."), category="error") + log.error("Found an existing account for {} or {}".format(content.name, content.email)) + flash(_("Found an existing account for this e-mail address or name."), category="error") except OperationalError: ub.session.rollback() - flash(_(u"Settings DB is not Writeable"), category="error") + log.error("Settings DB is not Writeable") + flash(_("Settings DB is not Writeable"), category="error") def _delete_user(content): if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, @@ -1277,12 +1288,14 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): try: flash(_delete_user(content), category="success") except Exception as ex: + log.error(ex) flash(str(ex), category="error") return redirect(url_for('admin.admin')) else: if not ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, ub.User.id != content.id).count() and 'admin_role' not in to_save: - flash(_(u"No admin user remaining, can't remove admin role", nick=content.name), category="error") + log.warning("No admin user remaining, can't remove admin role from {}".format(content.name)) + flash(_("No admin user remaining, can't remove admin role"), category="error") return redirect(url_for('admin.admin')) if to_save.get("password"): content.password = generate_password_hash(to_save["password"]) @@ -1322,6 +1335,7 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): if to_save.get("kindle_mail") != content.kindle_mail: content.kindle_mail = valid_email(to_save["kindle_mail"]) if to_save["kindle_mail"] else "" except Exception as ex: + log.error(ex) flash(str(ex), category="error") return render_title_template("user_edit.html", translations=translations, @@ -1336,12 +1350,14 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): try: ub.session_commit() flash(_(u"User '%(nick)s' updated", nick=content.name), category="success") - except IntegrityError: + except IntegrityError as ex: ub.session.rollback() - flash(_(u"An unknown error occured."), category="error") + log.error("An unknown error occurred while changing user: {}".format(str(ex))) + flash(_(u"An unknown error occurred."), category="error") except OperationalError: ub.session.rollback() - flash(_(u"Settings DB is not Writeable"), category="error") + log.error("Settings DB is not Writeable") + flash(_("Settings DB is not Writeable"), category="error") return "" @@ -1406,7 +1422,8 @@ def update_mailsettings(): config.save() except (OperationalError, InvalidRequestError): ub.session.rollback() - flash(_(u"Settings DB is not Writeable"), category="error") + log.error("Settings DB is not Writeable") + flash(_("Settings DB is not Writeable"), category="error") return edit_mailsettings() if to_save.get("test"): diff --git a/cps/helper.py b/cps/helper.py index 29163685..8495687c 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -693,6 +693,7 @@ 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, data.name + "." + book_format))) return response ################################## @@ -732,7 +733,6 @@ def json_serial(obj): 'seconds': obj.seconds, 'microseconds': obj.microseconds, } - # return obj.isoformat() raise TypeError("Type %s not serializable" % type(obj)) @@ -830,6 +830,7 @@ def get_download_link(book_id, book_format, client): if book: data1 = calibre_db.get_book_format(book.id, book_format.upper()) else: + log.error("Book id {} not found for downloading".format(book_id)) abort(404) if data1: # collect downloaded books only for registered user and not for anonymous user diff --git a/cps/logger.py b/cps/logger.py index cd38e3d3..e2747f53 100644 --- a/cps/logger.py +++ b/cps/logger.py @@ -153,11 +153,11 @@ def setup(log_file, log_level=None): file_handler.baseFilename = log_file else: try: - file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2, encoding='utf-8') + file_handler = RotatingFileHandler(log_file, maxBytes=100000, backupCount=2, encoding='utf-8') except IOError: if log_file == DEFAULT_LOG_FILE: raise - file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=50000, backupCount=2, encoding='utf-8') + file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=100000, backupCount=2, encoding='utf-8') log_file = "" file_handler.setFormatter(FORMATTER) diff --git a/cps/shelf.py b/cps/shelf.py index 68e8331b..a58e6d5c 100644 --- a/cps/shelf.py +++ b/cps/shelf.py @@ -99,12 +99,14 @@ def add_to_shelf(shelf_id, book_id): ub.session.commit() except (OperationalError, InvalidRequestError): ub.session.rollback() + log.error("Settings DB is not Writeable") flash(_(u"Settings DB is not Writeable"), category="error") if "HTTP_REFERER" in request.environ: return redirect(request.environ["HTTP_REFERER"]) else: return redirect(url_for('web.index')) if not xhr: + log.debug("Book has been added to shelf: {}".format(shelf.name)) flash(_(u"Book has been added to shelf: %(sname)s", sname=shelf.name), category="success") if "HTTP_REFERER" in request.environ: return redirect(request.environ["HTTP_REFERER"]) @@ -123,6 +125,7 @@ def search_to_shelf(shelf_id): return redirect(url_for('web.index')) if not check_shelf_edit_permissions(shelf): + log.warning("You are not allowed to add a book to the the shelf: {}".format(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('web.index')) @@ -140,7 +143,7 @@ def search_to_shelf(shelf_id): books_for_shelf = ub.searched_ids[current_user.id] if not books_for_shelf: - log.error("Books are already part of %s", shelf.name) + log.error("Books are already part of {}".format(shelf.name)) flash(_(u"Books are already part of the shelf: %(name)s", name=shelf.name), category="error") return redirect(url_for('web.index')) @@ -156,8 +159,10 @@ def search_to_shelf(shelf_id): flash(_(u"Books have been added to shelf: %(sname)s", sname=shelf.name), category="success") except (OperationalError, InvalidRequestError): ub.session.rollback() - flash(_(u"Settings DB is not Writeable"), category="error") + log.error("Settings DB is not Writeable") + flash(_("Settings DB is not Writeable"), category="error") else: + log.error("Could not add books to shelf: {}".format(shelf.name)) flash(_(u"Could not add books to shelf: %(sname)s", sname=shelf.name), category="error") return redirect(url_for('web.index')) @@ -168,7 +173,7 @@ def remove_from_shelf(shelf_id, book_id): xhr = request.headers.get('X-Requested-With') == 'XMLHttpRequest' shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() if shelf is None: - log.error("Invalid shelf specified: %s", shelf_id) + log.error("Invalid shelf specified: {}".format(shelf_id)) if not xhr: return redirect(url_for('web.index')) return "Invalid shelf specified", 400 @@ -197,7 +202,8 @@ def remove_from_shelf(shelf_id, book_id): ub.session.commit() except (OperationalError, InvalidRequestError): ub.session.rollback() - flash(_(u"Settings DB is not Writeable"), category="error") + log.error("Settings DB is not Writeable") + flash(_("Settings DB is not Writeable"), category="error") if "HTTP_REFERER" in request.environ: return redirect(request.environ["HTTP_REFERER"]) else: @@ -211,6 +217,7 @@ def remove_from_shelf(shelf_id, book_id): return "", 204 else: if not xhr: + log.warning("You are not allowed to remove a book from shelf: {}".format(shelf.name)) 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('web.index')) @@ -258,7 +265,8 @@ def create_edit_shelf(shelf, title, page, shelf_id=False): except (OperationalError, InvalidRequestError) as ex: ub.session.rollback() log.debug_or_exception(ex) - flash(_(u"Settings DB is not Writeable"), category="error") + log.error("Settings DB is not Writeable") + flash(_("Settings DB is not Writeable"), category="error") except Exception as ex: ub.session.rollback() log.debug_or_exception(ex) @@ -278,6 +286,7 @@ def check_shelf_is_unique(shelf, to_save, shelf_id=False): .first() is None if not is_shelf_name_unique: + log.error("A public shelf with the name '{}' already exists.".format(to_save["title"])) flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]), category="error") else: @@ -288,6 +297,7 @@ def check_shelf_is_unique(shelf, to_save, shelf_id=False): .first() is None if not is_shelf_name_unique: + log.error("A private shelf with the name '{}' already exists.".format(to_save["title"])) flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]), category="error") return is_shelf_name_unique @@ -311,7 +321,8 @@ def delete_shelf(shelf_id): delete_shelf_helper(cur_shelf) except InvalidRequestError: ub.session.rollback() - flash(_(u"Settings DB is not Writeable"), category="error") + log.error("Settings DB is not Writeable") + flash(_("Settings DB is not Writeable"), category="error") return redirect(url_for('web.index')) @@ -345,7 +356,8 @@ def order_shelf(shelf_id): ub.session.commit() except (OperationalError, InvalidRequestError): ub.session.rollback() - flash(_(u"Settings DB is not Writeable"), category="error") + log.error("Settings DB is not Writeable") + flash(_("Settings DB is not Writeable"), category="error") shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() result = list() @@ -415,7 +427,8 @@ def render_show_shelf(shelf_type, shelf_id, page_no, sort_param): ub.session.commit() except (OperationalError, InvalidRequestError): ub.session.rollback() - flash(_(u"Settings DB is not Writeable"), category="error") + log.error("Settings DB is not Writeable") + flash(_("Settings DB is not Writeable"), category="error") return render_title_template(page, entries=result, diff --git a/cps/updater.py b/cps/updater.py index 87aa842b..fb5219b6 100644 --- a/cps/updater.py +++ b/cps/updater.py @@ -103,20 +103,21 @@ class Updater(threading.Thread): time.sleep(2) return True except requests.exceptions.HTTPError as ex: - log.info(u'HTTP Error %s', ex) + log.error(u'HTTP Error %s', ex) self.status = 8 except requests.exceptions.ConnectionError: - log.info(u'Connection error') + log.error(u'Connection error') self.status = 9 except requests.exceptions.Timeout: - log.info(u'Timeout while establishing connection') + log.error(u'Timeout while establishing connection') self.status = 10 except (requests.exceptions.RequestException, zipfile.BadZipFile): self.status = 11 - log.info(u'General error') - except (IOError, OSError): + log.error(u'General error') + except (IOError, OSError) as ex: self.status = 12 - log.info(u'Update File Could Not be Saved in Temp Dir') + log.error(u'Possible Reason for error: update file could not be saved in temp dir') + log.debug_or_exception(ex) self.pause() return False @@ -182,39 +183,50 @@ class Updater(threading.Thread): @classmethod def moveallfiles(cls, root_src_dir, root_dst_dir): - change_permissions = True new_permissions = os.stat(root_dst_dir) - if sys.platform == "win32" or sys.platform == "darwin": - change_permissions = False - else: - log.debug('Update on OS-System : %s', sys.platform) + log.debug('Performing Update on OS-System: %s', sys.platform) + change_permissions = (sys.platform == "win32" or sys.platform == "darwin") 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) - log.debug('Create-Dir: %s', dst_dir) + try: + os.makedirs(dst_dir) + log.debug('Create directory: {}', dst_dir) + except OSError as e: + log.error('Failed creating folder: {} with error {}'.format(dst_dir, e)) 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) + try: + os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid) + except OSError as e: + old_permissions = os.stat(dst_dir) + log.error('Failed changing permissions of %s. Before: %s:%s After %s:%s error: %s', + dst_dir, old_permissions.st_uid, old_permissions.st_gid, + new_permissions.st_uid, new_permissions.st_gid, e) for file_ in files: src_file = os.path.join(src_dir, file_) dst_file = os.path.join(dst_dir, file_) if os.path.exists(dst_file): if change_permissions: permission = os.stat(dst_file) - log.debug('Remove file before copy: %s', dst_file) - os.remove(dst_file) + try: + os.remove(dst_file) + log.debug('Remove file before copy: %s', dst_file) + except OSError as e: + log.error('Failed removing file: {} with error {}'.format(dst_file, e)) else: if change_permissions: permission = new_permissions - shutil.move(src_file, dst_dir) - log.debug('Move File %s to %s', src_file, dst_dir) + try: + shutil.move(src_file, dst_dir) + log.debug('Move File %s to %s', src_file, dst_dir) + except OSError as ex: + log.error('Failed moving file from {} to {} with error {}'.format(src_file, dst_dir, ex)) if change_permissions: try: os.chown(dst_file, permission.st_uid, permission.st_gid) except OSError as e: old_permissions = os.stat(dst_file) - log.debug('Fail change permissions of %s. Before: %s:%s After %s:%s error: %s', + log.error('Failed changing permissions of %s. Before: %s:%s After %s:%s error: %s', dst_file, old_permissions.st_uid, old_permissions.st_gid, permission.st_uid, permission.st_gid, e) return @@ -266,9 +278,8 @@ class Updater(threading.Thread): shutil.rmtree(item_path, ignore_errors=True) else: try: - log.debug("Delete file %s", item_path) - # log_from_thread("Delete file " + item_path) os.remove(item_path) + log.debug("Delete file %s", item_path) except OSError: log.debug("Could not remove: %s", item_path) shutil.rmtree(source, ignore_errors=True) @@ -283,11 +294,13 @@ class Updater(threading.Thread): @classmethod def _nightly_version_info(cls): if is_sha1(constants.NIGHTLY_VERSION[0]) and len(constants.NIGHTLY_VERSION[1]) > 0: + log.debug("Nightly version: {}, {}".format(constants.NIGHTLY_VERSION[0], constants.NIGHTLY_VERSION[1])) return {'version': constants.NIGHTLY_VERSION[0], 'datetime': constants.NIGHTLY_VERSION[1]} return False @classmethod def _stable_version_info(cls): + log.debug("Stable version: {}".format(constants.STABLE_VERSION)) return constants.STABLE_VERSION # Current version @staticmethod @@ -381,6 +394,7 @@ class Updater(threading.Thread): # if 'committer' in update_data and 'message' in update_data: try: + log.debug("A new update is available.") status['success'] = True status['message'] = _( u'A new update is available. Click on the button below to update to the latest version.') @@ -401,6 +415,7 @@ class Updater(threading.Thread): except (IndexError, KeyError): status['success'] = False status['message'] = _(u'Could not fetch update information') + log.error("Could not fetch update information") return json.dumps(status) return '' @@ -468,6 +483,7 @@ class Updater(threading.Thread): # we are already on newest version, no update available if 'tag_name' not in commit[0]: status['message'] = _(u'Unexpected data while reading update information') + log.error("Unexpected data while reading update information") return json.dumps(status) if commit[0]['tag_name'] == version: status.update({ From 144c2b5fc78113a7f0087415afa8252585d703e1 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Mon, 26 Apr 2021 19:03:01 +0200 Subject: [PATCH 3/3] Renaming ipadress - ip_address Bugfix user table Result testrun Updated cn translation --- cps/cli.py | 24 +- cps/config_sql.py | 2 +- cps/gdriveutils.py | 98 +- cps/opds.py | 4 +- cps/static/js/table.js | 12 +- .../zh_Hans_CN/LC_MESSAGES/messages.po | 16 +- cps/ub.py | 5 +- cps/web.py | 12 +- test/Calibre-Web TestSummary_Linux.html | 917 ++++++++++-------- 9 files changed, 620 insertions(+), 470 deletions(-) diff --git a/cps/cli.py b/cps/cli.py index 07b719d2..d7dc596b 100644 --- a/cps/cli.py +++ b/cps/cli.py @@ -71,7 +71,7 @@ if args.c: if os.path.isfile(args.c): certfilepath = args.c else: - print("Certfilepath is invalid. Exiting...") + print("Certfile path is invalid. Exiting...") sys.exit(1) if args.c == "": @@ -81,7 +81,7 @@ if args.k: if os.path.isfile(args.k): keyfilepath = args.k else: - print("Keyfilepath is invalid. Exiting...") + print("Keyfile path is invalid. Exiting...") sys.exit(1) if (args.k and not args.c) or (not args.k and args.c): @@ -91,29 +91,29 @@ if (args.k and not args.c) or (not args.k and args.c): if args.k == "": keyfilepath = "" -# handle and check ipadress argument -ipadress = args.i or None -if ipadress: +# handle and check ip address argument +ip_address = args.i or None +if ip_address: try: # try to parse the given ip address with socket if hasattr(socket, 'inet_pton'): - if ':' in ipadress: - socket.inet_pton(socket.AF_INET6, ipadress) + if ':' in ip_address: + socket.inet_pton(socket.AF_INET6, ip_address) else: - socket.inet_pton(socket.AF_INET, ipadress) + socket.inet_pton(socket.AF_INET, ip_address) else: # on windows python < 3.4, inet_pton is not available # inet_atom only handles IPv4 addresses - socket.inet_aton(ipadress) + socket.inet_aton(ip_address) except socket.error as err: - print(ipadress, ':', err) + print(ip_address, ':', err) sys.exit(1) # handle and check user password argument user_credentials = args.s or None if user_credentials and ":" not in user_credentials: - print("No valid username:password format") + print("No valid 'username:password' format") sys.exit(3) -# Handles enableing of filepicker +# Handles enabling of filepicker filepicker = args.f or None diff --git a/cps/config_sql.py b/cps/config_sql.py index f7419ec9..ef90aee4 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -192,7 +192,7 @@ class _ConfigSQL(object): @staticmethod def get_config_ipaddress(): - return cli.ipadress or "" + return cli.ip_address or "" def _has_role(self, role_flag): return constants.has_flag(self.config_default_role, role_flag) diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py index 4c262661..13f83bd5 100644 --- a/cps/gdriveutils.py +++ b/cps/gdriveutils.py @@ -257,7 +257,12 @@ def getEbooksFolderId(drive=None): log.error('Error gDrive, root ID not found') gDriveId.path = '/' session.merge(gDriveId) - session.commit() + try: + session.commit() + except OperationalError as ex: + log.error("gdrive.db DB is not Writeable") + log.debug('Database error: %s', ex) + session.rollback() return gDriveId.gdrive_id @@ -272,37 +277,42 @@ def getFile(pathId, fileName, drive): def getFolderId(path, drive): # drive = getDrive(drive) - currentFolderId = getEbooksFolderId(drive) - sqlCheckPath = path if path[-1] == '/' else path + '/' - storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first() + try: + currentFolderId = getEbooksFolderId(drive) + sqlCheckPath = path if path[-1] == '/' else path + '/' + storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first() - if not storedPathName: - dbChange = False - s = path.split('/') - for i, x in enumerate(s): - if len(x) > 0: - currentPath = "/".join(s[:i+1]) - if currentPath[-1] != '/': - currentPath = currentPath + '/' - storedPathName = session.query(GdriveId).filter(GdriveId.path == currentPath).first() - if storedPathName: - currentFolderId = storedPathName.gdrive_id - else: - currentFolder = getFolderInFolder(currentFolderId, x, drive) - if currentFolder: - gDriveId = GdriveId() - gDriveId.gdrive_id = currentFolder['id'] - gDriveId.path = currentPath - session.merge(gDriveId) - dbChange = True - currentFolderId = currentFolder['id'] + if not storedPathName: + dbChange = False + s = path.split('/') + for i, x in enumerate(s): + if len(x) > 0: + currentPath = "/".join(s[:i+1]) + if currentPath[-1] != '/': + currentPath = currentPath + '/' + storedPathName = session.query(GdriveId).filter(GdriveId.path == currentPath).first() + if storedPathName: + currentFolderId = storedPathName.gdrive_id else: - currentFolderId = None - break - if dbChange: - session.commit() - else: - currentFolderId = storedPathName.gdrive_id + currentFolder = getFolderInFolder(currentFolderId, x, drive) + if currentFolder: + gDriveId = GdriveId() + gDriveId.gdrive_id = currentFolder['id'] + gDriveId.path = currentPath + session.merge(gDriveId) + dbChange = True + currentFolderId = currentFolder['id'] + else: + currentFolderId = None + break + if dbChange: + session.commit() + else: + currentFolderId = storedPathName.gdrive_id + except OperationalError as ex: + log.error("gdrive.db DB is not Writeable") + log.debug('Database error: %s', ex) + session.rollback() return currentFolderId @@ -346,7 +356,7 @@ def moveGdriveFolderRemote(origin_file, target_folder): addParents=gFileTargetDir['id'], removeParents=previous_parents, fields='id, parents').execute() - # if previous_parents has no childs anymore, delete original fileparent + # if previous_parents has no children anymore, delete original fileparent if len(children['items']) == 1: deleteDatabaseEntry(previous_parents) drive.auth.service.files().delete(fileId=previous_parents).execute() @@ -507,9 +517,10 @@ def deleteDatabaseOnChange(): try: session.query(GdriveId).delete() session.commit() - except (OperationalError, InvalidRequestError): + except (OperationalError, InvalidRequestError) as ex: session.rollback() - log.info(u"GDrive DB is not Writeable") + log.debug('Database error: %s', ex) + log.error(u"GDrive DB is not Writeable") def updateGdriveCalibreFromLocal(): @@ -524,13 +535,23 @@ def updateDatabaseOnEdit(ID,newPath): storedPathName = session.query(GdriveId).filter(GdriveId.gdrive_id == ID).first() if storedPathName: storedPathName.path = sqlCheckPath - session.commit() + try: + session.commit() + except OperationalError as ex: + log.error("gdrive.db DB is not Writeable") + log.debug('Database error: %s', ex) + session.rollback() # Deletes the hashes in database of deleted book def deleteDatabaseEntry(ID): session.query(GdriveId).filter(GdriveId.gdrive_id == ID).delete() - session.commit() + try: + session.commit() + except OperationalError as ex: + log.error("gdrive.db DB is not Writeable") + log.debug('Database error: %s', ex) + session.rollback() # Gets cover file from gdrive @@ -547,7 +568,12 @@ def get_cover_via_gdrive(cover_path): permissionAdded = PermissionAdded() permissionAdded.gdrive_id = df['id'] session.add(permissionAdded) - session.commit() + try: + session.commit() + except OperationalError as ex: + log.error("gdrive.db DB is not Writeable") + log.debug('Database error: %s', ex) + session.rollback() return df.metadata.get('webContentLink') else: return None diff --git a/cps/opds.py b/cps/opds.py index 85a978a7..e444302a 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -546,8 +546,8 @@ def check_auth(username, password): if bool(user and check_password_hash(str(user.password), password)): return True else: - ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) - log.warning('OPDS Login failed for user "%s" IP-address: %s', username.decode('utf-8'), ipAdress) + ip_Address = request.headers.get('X-Forwarded-For', request.remote_addr) + log.warning('OPDS Login failed for user "%s" IP-address: %s', username.decode('utf-8'), ip_Address) return False diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 2ccee233..117f7c53 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -682,7 +682,7 @@ function move_header_elements() { handleListServerResponse(data); }, error: function (data) { - handleListServerResponse({type:"danger", message:data.responseText}) + handleListServerResponse([{type:"danger", message:data.responseText}]) }, }); } @@ -721,7 +721,7 @@ function move_header_elements() { handleListServerResponse(data); }, error: function (data) { - handleListServerResponse({type:"danger", message:data.responseText}) + handleListServerResponse([{type:"danger", message:data.responseText}]) }, }); } @@ -750,7 +750,7 @@ function checkboxChange(checkbox, userId, field, field_index) { url: window.location.pathname + "/../../ajax/editlistusers/" + field, data: {"pk": userId, "field_index": field_index, "value": checkbox.checked}, error: function(data) { - handleListServerResponse({type:"danger", message:data.responseText}) + handleListServerResponse([{type:"danger", message:data.responseText}]) }, success: handleListServerResponse }); @@ -765,7 +765,7 @@ function selectHeader(element, field) { url: window.location.pathname + "/../../ajax/editlistusers/" + field, data: {"pk": result, "value": element.value}, error: function (data) { - handleListServerResponse({type:"danger", message:data.responseText}) + handleListServerResponse([{type:"danger", message:data.responseText}]) }, success: handleListServerResponse, }); @@ -783,7 +783,7 @@ function checkboxHeader(CheckboxState, field, field_index) { url: window.location.pathname + "/../../ajax/editlistusers/" + field, data: {"pk": result, "field_index": field_index, "value": CheckboxState}, error: function (data) { - handleListServerResponse({type:"danger", message:data.responseText}, true) + handleListServerResponse([{type:"danger", message:data.responseText}]) }, success: function (data) { handleListServerResponse (data, true) @@ -812,7 +812,7 @@ function deleteUser(a,id){ handleListServerResponse(data); }, error: function (data) { - handleListServerResponse({type:"danger", message:data.responseText}) + handleListServerResponse([{type:"danger", message:data.responseText}]) }, }); } diff --git a/cps/translations/zh_Hans_CN/LC_MESSAGES/messages.po b/cps/translations/zh_Hans_CN/LC_MESSAGES/messages.po index e436e774..0279ad2b 100644 --- a/cps/translations/zh_Hans_CN/LC_MESSAGES/messages.po +++ b/cps/translations/zh_Hans_CN/LC_MESSAGES/messages.po @@ -82,7 +82,7 @@ msgstr "显示全部" #: cps/admin.py:353 cps/admin.py:1275 msgid "Guest Name can't be changed" -msgstr "" +msgstr "访客名称无法更改" #: cps/admin.py:362 msgid "Guest can't have this role" @@ -1488,32 +1488,32 @@ msgstr "在书库" #: cps/templates/author.html:26 cps/templates/index.html:68 #: cps/templates/search.html:29 cps/templates/shelf.html:16 msgid "Sort according to book date, newest first" -msgstr "" +msgstr "按图书日期排序,最新优先" #: cps/templates/author.html:27 cps/templates/index.html:69 #: cps/templates/search.html:30 cps/templates/shelf.html:17 msgid "Sort according to book date, oldest first" -msgstr "" +msgstr "按图书日期排序,最旧优先" #: cps/templates/author.html:28 cps/templates/index.html:70 #: cps/templates/search.html:31 cps/templates/shelf.html:18 msgid "Sort title in alphabetical order" -msgstr "" +msgstr "按标题按字母顺序排序" #: cps/templates/author.html:29 cps/templates/index.html:71 #: cps/templates/search.html:32 cps/templates/shelf.html:19 msgid "Sort title in reverse alphabetical order" -msgstr "" +msgstr "按标题逆字母顺序排序" #: cps/templates/author.html:30 cps/templates/index.html:74 #: cps/templates/search.html:35 cps/templates/shelf.html:22 msgid "Sort according to publishing date, newest first" -msgstr "" +msgstr "按出版日期排序,最新优先" #: cps/templates/author.html:31 cps/templates/index.html:75 #: cps/templates/search.html:36 cps/templates/shelf.html:23 msgid "Sort according to publishing date, oldest first" -msgstr "" +msgstr "按出版日期排序,最旧优先" #: cps/templates/author.html:57 cps/templates/author.html:117 #: cps/templates/discover.html:30 cps/templates/index.html:29 @@ -2044,7 +2044,7 @@ msgstr "" #: cps/templates/config_edit.html:344 msgid "Autodetect" -msgstr "" +msgstr "自动检测" #: cps/templates/config_edit.html:345 msgid "Custom Filter" diff --git a/cps/ub.py b/cps/ub.py index a85f7404..d8d65bd0 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -713,9 +713,12 @@ def init_db(app_db_path): create_anonymous_user(session) if cli.user_credentials: - username, password = cli.user_credentials.split(':') + username, password = cli.user_credentials.split(':', 1) user = session.query(User).filter(func.lower(User.name) == username.lower()).first() if user: + if not password: + print("Empty password is not allowed") + sys.exit(4) user.password = generate_password_hash(password) if session_commit() == "": print("Password for user '{}' changed".format(username)) diff --git a/cps/web.py b/cps/web.py index 9b56babf..4203a812 100644 --- a/cps/web.py +++ b/cps/web.py @@ -1487,23 +1487,23 @@ def login(): log.info(error) flash(_(u"Could not login: %(message)s", message=error), category="error") else: - ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) - log.warning('LDAP Login failed for user "%s" IP-address: %s', form['username'], ipAdress) + ip_Address = request.headers.get('X-Forwarded-For', request.remote_addr) + log.warning('LDAP Login failed for user "%s" IP-address: %s', form['username'], ip_Address) flash(_(u"Wrong Username or Password"), category="error") else: - ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr) + ip_Address = request.headers.get('X-Forwarded-For', request.remote_addr) if 'forgot' in form and form['forgot'] == 'forgot': if user != None and user.name != "Guest": ret, __ = reset_password(user.id) if ret == 1: flash(_(u"New Password was send to your email address"), category="info") - log.info('Password reset for user "%s" IP-address: %s', form['username'], ipAdress) + log.info('Password reset for user "%s" IP-address: %s', form['username'], ip_Address) else: log.error(u"An unknown error occurred. Please try again later") flash(_(u"An unknown error occurred. Please try again later."), category="error") else: flash(_(u"Please enter valid username to reset password"), category="error") - log.warning('Username missing for password reset IP-address: %s', ipAdress) + log.warning('Username missing for password reset IP-address: %s', ip_Address) else: if user and check_password_hash(str(user.password), form['password']) and user.name != "Guest": login_user(user, remember=bool(form.get('remember_me'))) @@ -1512,7 +1512,7 @@ def login(): config.config_is_initial = False return redirect_back(url_for("web.index")) else: - log.warning('Login failed for user "%s" IP-address: %s', form['username'], ipAdress) + log.warning('Login failed for user "%s" IP-address: %s', form['username'], ip_Address) flash(_(u"Wrong Username or Password"), category="error") next_url = request.args.get('next', default=url_for("web.index"), type=str) diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 0376c51f..5f2e97ad 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2021-04-21 07:09:48

+

Start Time: 2021-04-26 08:28:54

-

Stop Time: 2021-04-21 09:55:49

+

Stop Time: 2021-04-26 11:26:09

-

Duration: 2h 16 min

+

Duration: 2h 24 min

@@ -234,13 +234,13 @@ - + TestCli 8 - 7 + 8 + 0 0 0 - 1 Detail @@ -268,7 +268,7 @@ -
TestCli - test_cli_SSL_files
+
TestCli - test_change_password
PASS @@ -277,7 +277,7 @@ -
TestCli - test_cli_different_folder
+
TestCli - test_cli_SSL_files
PASS @@ -286,35 +286,18 @@ -
TestCli - test_cli_different_settings_database
+
TestCli - test_cli_different_folder
PASS - + -
TestCli - test_cli_gdrive_location
- - -
- SKIP -
- - - +
TestCli - test_cli_different_settings_database
+ PASS @@ -338,6 +321,39 @@ + + TestCliGdrivedb + 2 + 2 + 0 + 0 + 0 + + Detail + + + + + + + +
TestCliGdrivedb - test_cli_gdrive_location
+ + PASS + + + + + + +
TestCliGdrivedb - test_gdrive_db_nonwrite
+ + PASS + + + + + TestCoverEditBooks 1 @@ -346,13 +362,13 @@ 0 0 - Detail + Detail - +
TestCoverEditBooks - test_upload_jpg
@@ -370,13 +386,13 @@ 0 0 - Detail + Detail - +
TestDeleteDatabase - test_delete_books_in_database
@@ -394,13 +410,13 @@ 0 0 - Detail + Detail - +
TestEbookConvertCalibre - test_convert_deactivate
@@ -409,7 +425,7 @@ - +
TestEbookConvertCalibre - test_convert_email
@@ -418,7 +434,7 @@ - +
TestEbookConvertCalibre - test_convert_failed_and_email
@@ -427,7 +443,7 @@ - +
TestEbookConvertCalibre - test_convert_only
@@ -436,7 +452,7 @@ - +
TestEbookConvertCalibre - test_convert_parameter
@@ -445,7 +461,7 @@ - +
TestEbookConvertCalibre - test_convert_wrong_excecutable
@@ -454,7 +470,7 @@ - +
TestEbookConvertCalibre - test_email_failed
@@ -463,7 +479,7 @@ - +
TestEbookConvertCalibre - test_email_only
@@ -472,7 +488,7 @@ - +
TestEbookConvertCalibre - test_kindle_send_not_configured
@@ -481,7 +497,7 @@ - +
TestEbookConvertCalibre - test_ssl_smtp_setup_error
@@ -490,7 +506,7 @@ - +
TestEbookConvertCalibre - test_starttls_smtp_setup_error
@@ -508,13 +524,13 @@ 0 0 - Detail + Detail - +
TestEbookConvertCalibreGDrive - test_convert_email
@@ -523,7 +539,7 @@ - +
TestEbookConvertCalibreGDrive - test_convert_failed_and_email
@@ -532,7 +548,7 @@ - +
TestEbookConvertCalibreGDrive - test_convert_only
@@ -541,7 +557,7 @@ - +
TestEbookConvertCalibreGDrive - test_convert_parameter
@@ -550,7 +566,7 @@ - +
TestEbookConvertCalibreGDrive - test_email_failed
@@ -559,7 +575,7 @@ - +
TestEbookConvertCalibreGDrive - test_email_only
@@ -577,13 +593,13 @@ 0 0 - Detail + Detail - +
TestEbookConvertKepubify - test_convert_deactivate
@@ -592,7 +608,7 @@ - +
TestEbookConvertKepubify - test_convert_only
@@ -601,7 +617,7 @@ - +
TestEbookConvertKepubify - test_convert_wrong_excecutable
@@ -619,13 +635,13 @@ 0 0 - Detail + Detail - +
TestEbookConvertGDriveKepubify - test_convert_deactivate
@@ -634,7 +650,7 @@ - +
TestEbookConvertGDriveKepubify - test_convert_only
@@ -643,7 +659,7 @@ - +
TestEbookConvertGDriveKepubify - test_convert_wrong_excecutable
@@ -661,13 +677,13 @@ 0 1 - Detail + Detail - +
TestEditAdditionalBooks - test_change_upload_formats
@@ -676,7 +692,7 @@ - +
TestEditAdditionalBooks - test_delete_book
@@ -685,7 +701,7 @@ - +
TestEditAdditionalBooks - test_delete_role
@@ -694,7 +710,7 @@ - +
TestEditAdditionalBooks - test_edit_book_identifier
@@ -703,7 +719,7 @@ - +
TestEditAdditionalBooks - test_edit_book_identifier_capital
@@ -712,7 +728,7 @@ - +
TestEditAdditionalBooks - test_edit_book_identifier_standard
@@ -721,7 +737,7 @@ - +
TestEditAdditionalBooks - test_edit_special_book_identifier
@@ -730,7 +746,7 @@ - +
TestEditAdditionalBooks - test_title_sort
@@ -739,7 +755,7 @@ - +
TestEditAdditionalBooks - test_upload_edit_role
@@ -748,7 +764,7 @@ - +
TestEditAdditionalBooks - test_upload_metadata_cbr
@@ -757,7 +773,7 @@ - +
TestEditAdditionalBooks - test_upload_metadata_cbt
@@ -766,19 +782,19 @@ - +
TestEditAdditionalBooks - test_writeonly_calibre_database
- SKIP + SKIP
-