diff --git a/cps/admin.py b/cps/admin.py index 0b90327e..447e0227 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -1237,7 +1237,7 @@ def _db_configuration_update_helper(): config.store_calibre_uuid(calibre_db, db.LibraryId) # if db changed -> delete shelfs, delete download books, delete read books, kobo sync... if db_change: - log.info("Calibre Database changed, delete all Calibre-Web info related to old Database") + log.info("Calibre Database changed, all Calibre-Web info related to old Database gets deleted") ub.session.query(ub.Downloads).delete() ub.session.query(ub.ArchivedBook).delete() ub.session.query(ub.ReadBook).delete() diff --git a/cps/db.py b/cps/db.py index b910363b..f2fe9b78 100644 --- a/cps/db.py +++ b/cps/db.py @@ -620,8 +620,8 @@ class CalibreDB: bd = (self.session.query(Books, read_column.value, ub.ArchivedBook.is_archived).select_from(Books) .join(read_column, read_column.book == book_id, isouter=True)) - except (KeyError, AttributeError): - log.error("Custom Column No.%d is not existing in calibre database", read_column) + except (KeyError, AttributeError, IndexError): + log.error("Custom Column No.{} is not existing in calibre database".format(read_column)) # Skip linking read column and return None instead of read status bd = self.session.query(Books, None, ub.ArchivedBook.is_archived) return (bd.filter(Books.id == book_id) @@ -665,11 +665,11 @@ class CalibreDB: neg_content_cc_filter = false() if neg_cc_list == [''] else \ getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \ any(cc_classes[self.config.config_restricted_column].value.in_(neg_cc_list)) - except (KeyError, AttributeError): + except (KeyError, AttributeError, IndexError): pos_content_cc_filter = false() neg_content_cc_filter = true() - log.error(u"Custom Column No.%d is not existing in calibre database", - self.config.config_restricted_column) + log.error("Custom Column No.{} is not existing in calibre database".format( + self.config.config_restricted_column)) flash(_("Custom Column No.%(column)d is not existing in calibre database", column=self.config.config_restricted_column), category="error") @@ -727,8 +727,8 @@ class CalibreDB: query = (self.session.query(database, read_column.value, ub.ArchivedBook.is_archived) .select_from(Books) .outerjoin(read_column, read_column.book == Books.id)) - except (KeyError, AttributeError): - log.error("Custom Column No.%d is not existing in calibre database", read_column) + except (KeyError, AttributeError, IndexError): + log.error("Custom Column No.{} is not existing in calibre database".format(read_column)) # Skip linking read column and return None instead of read status query = self.session.query(database, None, ub.ArchivedBook.is_archived) query = query.outerjoin(ub.ArchivedBook, and_(Books.id == ub.ArchivedBook.book_id, @@ -839,8 +839,8 @@ class CalibreDB: read_column = cc_classes[config_read_column] query = (self.session.query(Books, ub.ArchivedBook.is_archived, read_column.value).select_from(Books) .outerjoin(read_column, read_column.book == Books.id)) - except (KeyError, AttributeError): - log.error("Custom Column No.%d is not existing in calibre database", config_read_column) + except (KeyError, AttributeError, IndexError): + log.error("Custom Column No.{} is not existing in calibre database".format(config_read_column)) # Skip linking read column query = self.session.query(Books, ub.ArchivedBook.is_archived, None) query = query.outerjoin(ub.ArchivedBook, and_(Books.id == ub.ArchivedBook.book_id, diff --git a/cps/editbooks.py b/cps/editbooks.py index 728164f9..c07e5d24 100755 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -289,13 +289,13 @@ def delete_whole_book(book_id, book): def render_delete_book_result(book_format, json_response, warning, book_id): if book_format: if json_response: - return json.dumps([warning, {"location": url_for("edit-book.edit_book", book_id=book_id), + return json.dumps([warning, {"location": url_for("edit-book.show_edit_book", book_id=book_id), "type": "success", "format": book_format, "message": _('Book Format Successfully Deleted')}]) else: flash(_('Book Format Successfully Deleted'), category="success") - return redirect(url_for('edit-book.edit_book', book_id=book_id)) + return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) else: if json_response: return json.dumps([warning, {"location": url_for('web.index'), @@ -316,16 +316,16 @@ def delete_book_from_table(book_id, book_format, json_response): result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper()) if not result: if json_response: - return json.dumps([{"location": url_for("edit-book.edit_book", book_id=book_id), + return json.dumps([{"location": url_for("edit-book.show_edit_book", book_id=book_id), "type": "danger", "format": "", "message": error}]) else: flash(error, category="error") - return redirect(url_for('edit-book.edit_book', book_id=book_id)) + return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) if error: if json_response: - warning = {"location": url_for("edit-book.edit_book", book_id=book_id), + warning = {"location": url_for("edit-book.show_edit_book", book_id=book_id), "type": "warning", "format": "", "message": error} @@ -343,13 +343,13 @@ def delete_book_from_table(book_id, book_format, json_response): log.error_or_exception(ex) calibre_db.session.rollback() if json_response: - return json.dumps([{"location": url_for("edit-book.edit_book", book_id=book_id), + return json.dumps([{"location": url_for("edit-book.show_edit_book", book_id=book_id), "type": "danger", "format": "", "message": ex}]) else: flash(str(ex), category="error") - return redirect(url_for('edit-book.edit_book', book_id=book_id)) + return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) else: # book not found @@ -357,13 +357,13 @@ def delete_book_from_table(book_id, book_format, json_response): return render_delete_book_result(book_format, json_response, warning, book_id) message = _("You are missing permissions to delete books") if json_response: - return json.dumps({"location": url_for("edit-book.edit_book", book_id=book_id), + return json.dumps({"location": url_for("edit-book.show_edit_book", book_id=book_id), "type": "danger", "format": "", "message": message}) else: flash(message, category="error") - return redirect(url_for('edit-book.edit_book', book_id=book_id)) + return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) def render_edit_book(book_id): @@ -413,18 +413,18 @@ def render_edit_book(book_id): def edit_book_ratings(to_save, book): changed = False - if to_save["rating"].strip(): + if to_save.get("rating","").strip(): old_rating = False if len(book.ratings) > 0: old_rating = book.ratings[0].rating - ratingx2 = int(float(to_save["rating"]) * 2) - if ratingx2 != old_rating: + rating_x2 = int(float(to_save.get("rating","")) * 2) + if rating_x2 != old_rating: changed = True - is_rating = calibre_db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first() + is_rating = calibre_db.session.query(db.Ratings).filter(db.Ratings.rating == rating_x2).first() if is_rating: book.ratings.append(is_rating) else: - new_rating = db.Ratings(rating=ratingx2) + new_rating = db.Ratings(rating=rating_x2) book.ratings.append(new_rating) if old_rating: book.ratings.remove(book.ratings[0]) @@ -622,24 +622,26 @@ def edit_cc_data(book_id, book, to_save, cc): 'custom') return changed - +# returns None if no file is uploaded +# returns False if an error occours, in all other cases the ebook metadata is returned def upload_single_file(file_request, book, book_id): # Check and handle Uploaded file - if 'btn-upload-format' in file_request.files: - requested_file = file_request.files['btn-upload-format'] + requested_file = file_request.files.get('btn-upload-format', None) + if requested_file: # check for empty request if requested_file.filename != '': if not current_user.role_upload(): - abort(403) + flash(_(u"User has no rights to upload additional file formats"), category="error") + return False if '.' in requested_file.filename: file_ext = requested_file.filename.rsplit('.', 1)[-1].lower() if file_ext not in constants.EXTENSIONS_UPLOAD and '' 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 redirect(url_for('web.show_book', book_id=book.id)) + return False else: flash(_('File to be uploaded must have an extension'), category="error") - return redirect(url_for('web.show_book', book_id=book.id)) + return False file_name = book.path.rsplit('/', 1)[-1] filepath = os.path.normpath(os.path.join(config.config_calibre_dir, book.path)) @@ -651,12 +653,12 @@ def upload_single_file(file_request, book, book_id): os.makedirs(filepath) except OSError: flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error") - return redirect(url_for('web.show_book', book_id=book.id)) + return False try: requested_file.save(saved_filename) except OSError: flash(_(u"Failed to store file %(file)s.", file=saved_filename), category="error") - return redirect(url_for('web.show_book', book_id=book.id)) + return False file_size = os.path.getsize(saved_filename) is_format = calibre_db.get_book_format(book_id, file_ext.upper()) @@ -674,7 +676,7 @@ def upload_single_file(file_request, book, book_id): calibre_db.session.rollback() log.error_or_exception("Database error: {}".format(e)) flash(_(u"Database error: %(error)s.", error=e.orig), category="error") - return redirect(url_for('web.show_book', book_id=book.id)) + return False # return redirect(url_for('web.show_book', book_id=book.id)) # Queue uploader info link = '{}'.format(url_for('web.show_book', book_id=book.id), escape(book.title)) @@ -684,15 +686,16 @@ def upload_single_file(file_request, book, book_id): return uploader.process( saved_filename, *os.path.splitext(requested_file.filename), rarExecutable=config.config_rarfile_location) - + return None def upload_cover(cover_request, book): - if 'btn-upload-cover' in cover_request.files: - requested_file = cover_request.files['btn-upload-cover'] + requested_file = cover_request.files.get('btn-upload-cover', None) + if requested_file: # check for empty request if requested_file.filename != '': if not current_user.role_upload(): - abort(403) + flash(_(u"User has no rights to upload cover"), category="error") + return False ret, message = helper.save_cover(requested_file, book.path) if ret is True: return True @@ -716,25 +719,6 @@ def handle_title_on_edit(book, book_title): def handle_author_on_edit(book, author_name, update_stored=True): # handle author(s) input_authors, renamed = prepare_authors(author_name) - '''input_authors = author_name.split('&') - input_authors = list(map(lambda it: it.strip().replace(',', '|'), input_authors)) - # Remove duplicates in authors list - input_authors = helper.uniq(input_authors) - # we have all author names now - if input_authors == ['']: - input_authors = [_(u'Unknown')] # prevent empty Author - - renamed = list() - for in_aut in input_authors: - renamed_author = calibre_db.session.query(db.Authors).filter(db.Authors.name == in_aut).first() - if renamed_author and in_aut != renamed_author.name: - renamed.append(renamed_author.name) - all_books = calibre_db.session.query(db.Books) \ - .filter(db.Books.authors.any(db.Authors.name == renamed_author.name)).all() - sorted_renamed_author = helper.get_sorted_author(renamed_author.name) - sorted_old_author = helper.get_sorted_author(in_aut) - for one_book in all_books: - one_book.author_sort = one_book.author_sort.replace(sorted_renamed_author, sorted_old_author)''' change = modify_database_object(input_authors, book.authors, db.Authors, calibre_db.session, 'author') @@ -754,12 +738,19 @@ def handle_author_on_edit(book, author_name, update_stored=True): change = True return input_authors, change, renamed +@EditBook.route("/admin/book/", methods=['GET']) +@login_required_if_no_ano +@edit_required +def show_edit_book(book_id): + return render_edit_book(book_id) -@EditBook.route("/admin/book/", methods=['GET', 'POST']) + +@EditBook.route("/admin/book/", methods=['POST']) @login_required_if_no_ano @edit_required def edit_book(book_id): modify_date = False + edit_error = False # create the function for sorting... try: @@ -768,109 +759,120 @@ def edit_book(book_id): log.error_or_exception(e) calibre_db.session.rollback() - # Show form - if request.method != 'POST': - return render_edit_book(book_id) - book = calibre_db.get_filtered_book(book_id, allow_show_archived=True) - # Book not found if not book: flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"), category="error") return redirect(url_for("web.index")) - meta = upload_single_file(request, book, book_id) - if upload_cover(request, book) is True: - book.has_cover = 1 - modify_date = True + to_save = request.form.to_dict() + try: - to_save = request.form.to_dict() - merge_metadata(to_save, meta) - # Update book + # Update folder of book on local disk edited_books_id = None - - # handle book title + title_author_error = None + # handle book title change title_change = handle_title_on_edit(book, to_save["book_title"]) - - input_authors, authorchange, renamed = handle_author_on_edit(book, to_save["author_name"]) - if authorchange or title_change: + # handle book author change + input_authors, author_change, renamed = handle_author_on_edit(book, to_save["author_name"]) + if author_change or title_change: edited_books_id = book.id modify_date = True + title_author_error = helper.update_dir_structure(edited_books_id, + config.config_calibre_dir, + input_authors[0], + renamed_author=renamed) + if title_author_error: + flash(title_author_error, category="error") + calibre_db.session.rollback() + book = calibre_db.get_filtered_book(book_id, allow_show_archived=True) + # handle upload other formats from local disk + meta = upload_single_file(request, book, book_id) + # only merge metadata if file was uploaded and no error occurred (meta equals not false or none) + if meta: + merge_metadata(to_save, meta) + # handle upload covers from local disk + cover_upload_success = upload_cover(request, book) + if cover_upload_success: + book.has_cover = 1 + modify_date = True + + # upload new covers or new file formats to google drive if config.config_use_google_drive: gdriveutils.updateGdriveCalibreFromLocal() - error = "" - if edited_books_id: - error = helper.update_dir_structure(edited_books_id, config.config_calibre_dir, input_authors[0], - renamed_author=renamed) + if to_save.get("cover_url", None): + if not current_user.role_upload(): + edit_error = True + flash(_(u"User has no rights to upload cover"), category="error") + if to_save["cover_url"].endswith('/static/generic_cover.jpg'): + book.has_cover = 0 + else: + result, error = helper.save_cover_from_url(to_save["cover_url"], book.path) + if result is True: + book.has_cover = 1 + modify_date = True + else: + flash(error, category="error") - if not error: - if "cover_url" in to_save: - if to_save["cover_url"]: - if not current_user.role_upload(): - calibre_db.session.rollback() - return "", 403 - if to_save["cover_url"].endswith('/static/generic_cover.jpg'): - book.has_cover = 0 - else: - result, error = helper.save_cover_from_url(to_save["cover_url"], book.path) - if result is True: - book.has_cover = 1 - modify_date = True - else: - flash(error, category="error") - - # Add default series_index to book - modify_date |= edit_book_series_index(to_save["series_index"], book) - # Handle book comments/description - modify_date |= edit_book_comments(Markup(to_save['description']).unescape(), book) - # Handle identifiers - input_identifiers = identifier_list(to_save, book) - modification, warning = modify_identifiers(input_identifiers, book.identifiers, calibre_db.session) - if warning: - flash(_("Identifiers are not Case Sensitive, Overwriting Old Identifier"), category="warning") - modify_date |= modification - # Handle book tags - modify_date |= edit_book_tags(to_save['tags'], book) - # Handle book series - modify_date |= edit_book_series(to_save["series"], book) - # handle book publisher - modify_date |= edit_book_publisher(to_save['publisher'], book) - # handle book languages + # Add default series_index to book + modify_date |= edit_book_series_index(to_save["series_index"], book) + # Handle book comments/description + modify_date |= edit_book_comments(Markup(to_save['description']).unescape(), book) + # Handle identifiers + input_identifiers = identifier_list(to_save, book) + modification, warning = modify_identifiers(input_identifiers, book.identifiers, calibre_db.session) + if warning: + flash(_("Identifiers are not Case Sensitive, Overwriting Old Identifier"), category="warning") + modify_date |= modification + # Handle book tags + modify_date |= edit_book_tags(to_save['tags'], book) + # Handle book series + modify_date |= edit_book_series(to_save["series"], book) + # handle book publisher + modify_date |= edit_book_publisher(to_save['publisher'], book) + # handle book languages + try: modify_date |= edit_book_languages(to_save['languages'], book) - # handle book ratings - modify_date |= edit_book_ratings(to_save, book) - # handle cc data - modify_date |= edit_all_cc_data(book_id, book, to_save) + except ValueError as e: + flash(str(e), category="error") + edit_error = True + # handle book ratings + modify_date |= edit_book_ratings(to_save, book) + # handle cc data + modify_date |= edit_all_cc_data(book_id, book, to_save) - if to_save["pubdate"]: - try: - book.pubdate = datetime.strptime(to_save["pubdate"], "%Y-%m-%d") - except ValueError: - book.pubdate = db.Books.DEFAULT_PUBDATE - else: + if to_save.get("pubdate", None): + try: + book.pubdate = datetime.strptime(to_save["pubdate"], "%Y-%m-%d") + except ValueError as e: book.pubdate = db.Books.DEFAULT_PUBDATE - - if modify_date: - book.last_modified = datetime.utcnow() - kobo_sync_status.remove_synced_book(edited_books_id, all=True) - - calibre_db.session.merge(book) - calibre_db.session.commit() - if config.config_use_google_drive: - gdriveutils.updateGdriveCalibreFromLocal() - if "detail_view" in to_save: - return redirect(url_for('web.show_book', book_id=book.id)) - else: - flash(_("Metadata successfully updated"), category="success") - return render_edit_book(book_id) + flash(str(e), category="error") + edit_error = True + else: + book.pubdate = db.Books.DEFAULT_PUBDATE + + if modify_date: + book.last_modified = datetime.utcnow() + kobo_sync_status.remove_synced_book(edited_books_id, all=True) + + calibre_db.session.merge(book) + calibre_db.session.commit() + if config.config_use_google_drive: + gdriveutils.updateGdriveCalibreFromLocal() + if meta is not False \ + and edit_error is not True \ + and title_author_error is not True \ + and cover_upload_success is not False: + flash(_("Metadata successfully updated"), category="success") + if "detail_view" in to_save: + return redirect(url_for('web.show_book', book_id=book.id)) else: - calibre_db.session.rollback() - flash(error, category="error") return render_edit_book(book_id) except ValueError as e: + log.error_or_exception("Error: {}".format(e)) calibre_db.session.rollback() flash(str(e), category="error") return redirect(url_for('web.show_book', book_id=book.id)) @@ -882,14 +884,14 @@ def edit_book(book_id): except Exception as ex: log.error_or_exception(ex) calibre_db.session.rollback() - flash(_("Error editing book, please check logfile for details"), category="error") + flash(_("Error editing book: {}".format(ex)), category="error") return redirect(url_for('web.show_book', book_id=book.id)) def merge_metadata(to_save, meta): - if to_save['author_name'] == _(u'Unknown'): + if to_save.get('author_name', "") == _(u'Unknown'): to_save['author_name'] = '' - if to_save['book_title'] == _(u'Unknown'): + if to_save.get('book_title', "") == _(u'Unknown'): to_save['book_title'] = '' for s_field, m_field in [ ('tags', 'tags'), ('author_name', 'author'), ('series', 'series'), @@ -1117,7 +1119,7 @@ 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.edit_book', book_id=book_id)} + resp = {"location": url_for('edit-book.show_edit_book', book_id=book_id)} return Response(json.dumps(resp), mimetype='application/json') else: resp = {"location": url_for('web.show_book', book_id=book_id)} @@ -1139,7 +1141,7 @@ def convert_bookformat(book_id): if (book_format_from is None) or (book_format_to is None): flash(_(u"Source or destination format for conversion missing"), category="error") - return redirect(url_for('edit-book.edit_book', book_id=book_id)) + return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) log.info('converting: book id: %s from: %s to: %s', book_id, book_format_from, book_format_to) rtn = helper.convert_book_format(book_id, config.config_calibre_dir, book_format_from.upper(), @@ -1151,7 +1153,7 @@ def convert_bookformat(book_id): category="success") else: flash(_(u"There was an error converting this book: %(res)s", res=rtn), category="error") - return redirect(url_for('edit-book.edit_book', book_id=book_id)) + return redirect(url_for('edit-book.show_edit_book', book_id=book_id)) @EditBook.route("/ajax/getcustomenum/") @@ -1211,10 +1213,15 @@ def edit_list_book(param): mimetype='application/json') elif param == 'title': sort_param = book.sort - handle_title_on_edit(book, vals.get('value', "")) - helper.update_dir_structure(book.id, config.config_calibre_dir) - ret = Response(json.dumps({'success': True, 'newValue': book.title}), - mimetype='application/json') + if handle_title_on_edit(book, vals.get('value', "")): + rename_error = helper.update_dir_structure(book.id, config.config_calibre_dir) + if not rename_error: + ret = Response(json.dumps({'success': True, 'newValue': book.title}), + mimetype='application/json') + else: + ret = Response(json.dumps({'success': False, + 'msg': rename_error}), + mimetype='application/json') elif param == 'sort': book.sort = vals['value'] ret = Response(json.dumps({'success': True, 'newValue': book.sort}), @@ -1225,11 +1232,17 @@ def edit_list_book(param): mimetype='application/json') elif param == 'authors': input_authors, __, renamed = handle_author_on_edit(book, vals['value'], vals.get('checkA', None) == "true") - helper.update_dir_structure(book.id, config.config_calibre_dir, input_authors[0], renamed_author=renamed) - ret = Response(json.dumps({ - 'success': True, - 'newValue': ' & '.join([author.replace('|', ',') for author in input_authors])}), - mimetype='application/json') + rename_error = helper.update_dir_structure(book.id, config.config_calibre_dir, input_authors[0], + renamed_author=renamed) + if not rename_error: + ret = Response(json.dumps({ + 'success': True, + 'newValue': ' & '.join([author.replace('|', ',') for author in input_authors])}), + mimetype='application/json') + else: + ret = Response(json.dumps({'success': False, + 'msg': rename_error}), + mimetype='application/json') 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'])) @@ -1356,8 +1369,8 @@ def table_xchange_author_title(): author_names.append(authr.name.replace('|', ',')) title_change = handle_title_on_edit(book, " ".join(author_names)) - input_authors, authorchange, renamed = handle_author_on_edit(book, authors) - if authorchange or title_change: + input_authors, author_change, renamed = handle_author_on_edit(book, authors) + if author_change or title_change: edited_books_id = book.id modify_date = True @@ -1365,8 +1378,9 @@ def table_xchange_author_title(): gdriveutils.updateGdriveCalibreFromLocal() if edited_books_id: - helper.update_dir_structure(edited_books_id, config.config_calibre_dir, input_authors[0], - renamed_author=renamed) + # toDo: Handle error + edit_error = helper.update_dir_structure(edited_books_id, config.config_calibre_dir, input_authors[0], + renamed_author=renamed) if modify_date: book.last_modified = datetime.utcnow() try: diff --git a/cps/helper.py b/cps/helper.py index 7dd3ca9f..3a2276e9 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -327,8 +327,9 @@ def edit_book_read_status(book_id, read_status=None): new_cc = cc_class(value=read_status or 1, book=book_id) calibre_db.session.add(new_cc) calibre_db.session.commit() - except (KeyError, AttributeError): - log.error(u"Custom Column No.%d is not existing in calibre database", config.config_read_column) + except (KeyError, AttributeError, IndexError): + log.error( + "Custom Column No.{} is not existing in calibre database".format(config.config_read_column)) return "Custom Column No.{} is not existing in calibre database".format(config.config_read_column) except (OperationalError, InvalidRequestError) as ex: calibre_db.session.rollback() @@ -435,7 +436,8 @@ def rename_all_authors(first_author, renamed_author, calibre_path="", localbook= new_author_path = os.path.join(calibre_path, new_author_rename_dir) shutil.move(os.path.normcase(old_author_path), os.path.normcase(new_author_path)) except OSError as ex: - log.error_or_exception("Rename author from: %s to %s: %s", old_author_path, new_author_path, ex) + log.error("Rename author from: %s to %s: %s", old_author_path, new_author_path, ex) + log.debug(ex, exc_info=True) return _("Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s", src=old_author_path, dest=new_author_path, error=str(ex)) else: @@ -446,31 +448,31 @@ def rename_all_authors(first_author, renamed_author, calibre_path="", localbook= # Moves files in file storage during author/title rename, or from temp dir to file storage def update_dir_structure_file(book_id, calibre_path, first_author, original_filepath, db_filename, renamed_author): # get book database entry from id, if original path overwrite source with original_filepath - localbook = calibre_db.get_book(book_id) + local_book = calibre_db.get_book(book_id) if original_filepath: path = original_filepath else: - path = os.path.join(calibre_path, localbook.path) + path = os.path.join(calibre_path, local_book.path) - # Create (current) authordir and titledir from database - authordir = localbook.path.split('/')[0] - titledir = localbook.path.split('/')[1] + # Create (current) author_dir and title_dir from database + author_dir = local_book.path.split('/')[0] + title_dir = local_book.path.split('/')[1] - # Create new_authordir from parameter or from database - # Create new titledir from database and add id - new_authordir = rename_all_authors(first_author, renamed_author, calibre_path, localbook) + # Create new_author_dir from parameter or from database + # Create new title_dir from database and add id + new_author_dir = rename_all_authors(first_author, renamed_author, calibre_path, local_book) if first_author: if first_author.lower() in [r.lower() for r in renamed_author]: - if os.path.isdir(os.path.join(calibre_path, new_authordir)): - path = os.path.join(calibre_path, new_authordir, titledir) + if os.path.isdir(os.path.join(calibre_path, new_author_dir)): + path = os.path.join(calibre_path, new_author_dir, title_dir) - new_titledir = get_valid_filename(localbook.title, chars=96) + " (" + str(book_id) + ")" + new_title_dir = get_valid_filename(local_book.title, chars=96) + " (" + str(book_id) + ")" - if titledir != new_titledir or authordir != new_authordir or original_filepath: + if title_dir != new_title_dir or author_dir != new_author_dir or original_filepath: error = move_files_on_change(calibre_path, - new_authordir, - new_titledir, - localbook, + new_author_dir, + new_title_dir, + local_book, db_filename, original_filepath, path) @@ -478,7 +480,7 @@ def update_dir_structure_file(book_id, calibre_path, first_author, original_file return error # Rename all files from old names to new names - return rename_files_on_change(first_author, renamed_author, localbook, original_filepath, path, calibre_path) + return rename_files_on_change(first_author, renamed_author, local_book, original_filepath, path, calibre_path) def upload_new_file_gdrive(book_id, first_author, renamed_author, title, title_dir, original_filepath, filename_ext): @@ -490,7 +492,7 @@ def upload_new_file_gdrive(book_id, first_author, renamed_author, title, title_d title_dir + " (" + str(book_id) + ")") book.path = gdrive_path.replace("\\", "/") gd.uploadFileToEbooksFolder(os.path.join(gdrive_path, file_name).replace("\\", "/"), original_filepath) - return rename_files_on_change(first_author, renamed_author, localbook=book, gdrive=True) + return rename_files_on_change(first_author, renamed_author, local_book=book, gdrive=True) def update_dir_structure_gdrive(book_id, first_author, renamed_author): @@ -549,7 +551,7 @@ def move_files_on_change(calibre_path, new_authordir, new_titledir, localbook, d # change location in database to new author/title path localbook.path = os.path.join(new_authordir, new_titledir).replace('\\', '/') except OSError as ex: - log.error_or_exception("Rename title from: %s to %s: %s", path, new_path, ex) + log.error_or_exception("Rename title from {} to {} failed with error: {}".format(path, new_path, ex)) return _("Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s", src=path, dest=new_path, error=str(ex)) return False @@ -557,8 +559,8 @@ def move_files_on_change(calibre_path, new_authordir, new_titledir, localbook, d def rename_files_on_change(first_author, renamed_author, - localbook, - orignal_filepath="", + local_book, + original_filepath="", path="", calibre_path="", gdrive=False): @@ -566,12 +568,12 @@ def rename_files_on_change(first_author, try: clean_author_database(renamed_author, calibre_path, gdrive=gdrive) if first_author and first_author not in renamed_author: - clean_author_database([first_author], calibre_path, localbook, gdrive) - if not gdrive and not renamed_author and not orignal_filepath and len(os.listdir(os.path.dirname(path))) == 0: + clean_author_database([first_author], calibre_path, local_book, gdrive) + if not gdrive and not renamed_author and not original_filepath and len(os.listdir(os.path.dirname(path))) == 0: shutil.rmtree(os.path.dirname(path)) except (OSError, FileNotFoundError) as ex: - log.error_or_exception("Error in rename file in path %s", ex) - return _("Error in rename file in path: %(error)s", error=str(ex)) + log.error_or_exception("Error in rename file in path {}".format(ex)) + return _("Error in rename file in path: {}".format(str(ex))) return False diff --git a/cps/render_template.py b/cps/render_template.py index 7cd341ea..09a32497 100644 --- a/cps/render_template.py +++ b/cps/render_template.py @@ -110,8 +110,8 @@ def get_readbooks_ids(): readBooks = calibre_db.session.query(db.cc_classes[config.config_read_column])\ .filter(db.cc_classes[config.config_read_column].value == True).all() return frozenset([x.book for x in readBooks]) - except (KeyError, AttributeError): - log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column) + except (KeyError, AttributeError, IndexError): + log.error("Custom Column No.{} is not existing in calibre database".format(config.config_read_column)) return [] # Returns the template for rendering and includes the instance name diff --git a/cps/templates/detail.html b/cps/templates/detail.html index 38005cb9..3ced5854 100644 --- a/cps/templates/detail.html +++ b/cps/templates/detail.html @@ -295,7 +295,7 @@ {% if g.user.role_edit() %} {% endif %} diff --git a/cps/ub.py b/cps/ub.py index a2be004c..4bb66ff6 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -67,6 +67,7 @@ logged_in = dict() def signal_store_user_session(object, user): store_user_session() + def store_user_session(): if flask_session.get('user_id', ""): flask_session['_user_id'] = flask_session.get('user_id', "") @@ -85,15 +86,16 @@ def store_user_session(): else: log.error("No user id in session") + def delete_user_session(user_id, session_key): try: log.debug("Deleted session_key: " + session_key) - session.query(User_Sessions).filter(User_Sessions.user_id==user_id, - User_Sessions.session_key==session_key).delete() + session.query(User_Sessions).filter(User_Sessions.user_id == user_id, + User_Sessions.session_key == session_key).delete() session.commit() - except (exc.OperationalError, exc.InvalidRequestError) as e: + except (exc.OperationalError, exc.InvalidRequestError) as ex: session.rollback() - log.exception(e) + log.exception(ex) def check_user_session(user_id, session_key): @@ -209,9 +211,9 @@ class UserBase: pass try: session.commit() - except (exc.OperationalError, exc.InvalidRequestError): + except (exc.OperationalError, exc.InvalidRequestError) as e: session.rollback() - # ToDo: Error message + log.error_or_exception(e) def __repr__(self): return '' % self.name diff --git a/cps/web.py b/cps/web.py index b1f27035..d21ba630 100644 --- a/cps/web.py +++ b/cps/web.py @@ -87,7 +87,7 @@ def add_security_headers(resp): csp += ''.join([' ' + host for host in config.config_trustedhosts.strip().split(',')]) csp += " 'unsafe-inline' 'unsafe-eval'; font-src 'self' data:; img-src 'self' data:" resp.headers['Content-Security-Policy'] = csp - if request.endpoint == "edit-book.edit_book" or config.config_use_google_drive: + if request.endpoint == "edit-book.show_edit_book" or config.config_use_google_drive: resp.headers['Content-Security-Policy'] += " *" elif request.endpoint == "web.read_book": resp.headers['Content-Security-Policy'] += " blob:;style-src-elem 'self' blob: 'unsafe-inline';" @@ -646,8 +646,8 @@ def render_read_books(page, are_read, as_xml=False, order=None): db.Books.id == db.books_series_link.c.book, db.Series, db.cc_classes[config.config_read_column]) - except (KeyError, AttributeError): - log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column) + except (KeyError, AttributeError, IndexError): + log.error("Custom Column No.{} is not existing in calibre database".format(config.config_read_column)) if not as_xml: flash(_("Custom Column No.%(column)d is not existing in calibre database", column=config.config_read_column), @@ -826,8 +826,9 @@ def list_books(): books = (calibre_db.session.query(db.Books, read_column.value, ub.ArchivedBook.is_archived) .select_from(db.Books) .outerjoin(read_column, read_column.book == db.Books.id)) - except (KeyError, AttributeError): - log.error("Custom Column No.%d is not existing in calibre database", read_column) + except (KeyError, AttributeError, IndexError): + log.error( + "Custom Column No.{} is not existing in calibre database".format(config.config_read_column)) # Skip linking read column and return None instead of read status books = calibre_db.session.query(db.Books, None, ub.ArchivedBook.is_archived) books = (books.outerjoin(ub.ArchivedBook, and_(db.Books.id == ub.ArchivedBook.book_id, @@ -1139,8 +1140,9 @@ def adv_search_read_status(q, read_status): else: q = q.join(db.cc_classes[config.config_read_column], isouter=True) \ .filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True) - except (KeyError, AttributeError): - log.error(u"Custom Column No.%d is not existing in calibre database", config.config_read_column) + except (KeyError, AttributeError, IndexError): + log.error( + "Custom Column No.{} is not existing in calibre database".format(config.config_read_column)) flash(_("Custom Column No.%(column)d is not existing in calibre database", column=config.config_read_column), category="error") @@ -1262,8 +1264,8 @@ def render_adv_search_results(term, offset=None, order=None, limit=None): query = (calibre_db.session.query(db.Books, ub.ArchivedBook.is_archived, read_column.value) .select_from(db.Books) .outerjoin(read_column, read_column.book == db.Books.id)) - except (KeyError, AttributeError): - log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column) + except (KeyError, AttributeError, IndexError): + log.error("Custom Column No.{} is not existing in calibre database".format(config.config_read_column)) # Skip linking read column query = calibre_db.session.query(db.Books, ub.ArchivedBook.is_archived, None) query = query.outerjoin(ub.ArchivedBook, and_(db.Books.id == ub.ArchivedBook.book_id, diff --git a/optional-requirements.txt b/optional-requirements.txt index aea1efb7..e54b0829 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -1,5 +1,5 @@ # GDrive Integration -google-api-python-client>=1.7.11,<2.41.0 +google-api-python-client>=1.7.11,<2.42.0 gevent>20.6.0,<22.0.0 greenlet>=0.4.17,<1.2.0 httplib2>=0.9.2,<0.21.0 @@ -13,7 +13,7 @@ rsa>=3.4.2,<4.9.0 # Gmail google-auth-oauthlib>=0.4.3,<0.6.0 -google-api-python-client>=1.7.11,<2.41.0 +google-api-python-client>=1.7.11,<2.42.0 # goodreads goodreads>=0.3.2,<0.4.0 diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index e3b7a3d7..7be8da35 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2022-03-14 21:13:33

+

Start Time: 2022-03-19 22:04:05

-

Stop Time: 2022-03-15 02:05:20

+

Stop Time: 2022-03-20 02:50:09

-

Duration: 4h 3 min

+

Duration: 3h 58 min

@@ -1589,8 +1589,8 @@ TestEditBooksOnGdrive 20 - 17 - 3 + 16 + 4 0 0 @@ -1735,11 +1735,31 @@ - +
TestEditBooksOnGdrive - test_edit_title
- PASS + +
+ FAIL +
+ + + + @@ -1761,7 +1781,7 @@
Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 853, in test_upload_book_epub
+  File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 855, in test_upload_book_epub
     self.assertEqual('8936', resp.headers['Content-Length'])
 AssertionError: '8936' != '1103'
 - 8936
@@ -1801,7 +1821,7 @@ AssertionError: '8936' != '1103'
                     
Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 766, in test_upload_cover_hdd
+  File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 768, in test_upload_cover_hdd
     self.assertGreater(diff('original.png', 'jpeg.png', delete_diff_file=True), 0.02)
 AssertionError: 0.0 not greater than 0.02
@@ -1830,9 +1850,9 @@ AssertionError: 0.0 not greater than 0.02
Traceback (most recent call last):
-  File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 935, in test_watch_metadata
+  File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 937, in test_watch_metadata
     self.assertNotIn('series', book)
-AssertionError: 'series' unexpectedly found in {'id': 5, 'reader': [], 'title': 'testbook', 'author': ['John Döe'], 'rating': 0, 'languages': ['English'], 'identifier': [], 'cover': '/cover/5?edit=2e081d1c-86d2-461f-a309-e51e1e378161', 'tag': [], 'publisher': ['Randomhäus'], 'pubdate': 'Jan 19, 2017', 'comment': 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.Aenean commodo ligula eget dolor.Aenean massa.Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.Nulla consequat massa quis enim.Donec pede justo, fringilla vel, aliquet nec, vulputate', 'add_shelf': [], 'del_shelf': [], 'edit_enable': True, 'kindle': None, 'kindlebtn': None, 'download': ['EPUB (6.7 kB)'], 'read': False, 'archived': False, 'series_all': 'Book 1 of test', 'series_index': '1', 'series': 'test', 'cust_columns': []}
+AssertionError: 'series' unexpectedly found in {'id': 5, 'reader': [], 'title': 'testbook', 'author': ['John Döe'], 'rating': 0, 'languages': ['English'], 'identifier': [], 'cover': '/cover/5?edit=34e51cc2-2413-4a23-8324-26d568a421ba', 'tag': [], 'publisher': ['Randomhäus'], 'pubdate': 'Jan 19, 2017', 'comment': 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.Aenean commodo ligula eget dolor.Aenean massa.Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.Nulla consequat massa quis enim.Donec pede justo, fringilla vel, aliquet nec, vulputate', 'add_shelf': [], 'del_shelf': [], 'edit_enable': True, 'kindle': None, 'kindlebtn': None, 'download': ['EPUB (6.7 kB)'], 'read': False, 'archived': False, 'series_all': 'Book 1 of test', 'series_index': '1', 'series': 'test', 'cust_columns': []}
@@ -2825,34 +2845,65 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 're - - TestMergeBooksList - 2 - 2 + + _ErrorHolder + 1 0 0 + 1 0 - Detail + Detail - + -
TestMergeBooksList - test_book_merge
+
setUpClass (test_merge_books_list)
- PASS - - - - - - -
TestMergeBooksList - test_delete_book
+ +
+ ERROR +
+ + + - PASS @@ -4600,10 +4651,10 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 're Total - 405 - 396 - 3 - 0 + 404 + 393 + 4 + 1 6   @@ -4776,7 +4827,7 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 're google-api-python-client - 2.40.0 + 2.41.0 TestCliGdrivedb @@ -4806,7 +4857,7 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 're google-api-python-client - 2.40.0 + 2.41.0 TestEbookConvertCalibreGDrive @@ -4836,7 +4887,7 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 're google-api-python-client - 2.40.0 + 2.41.0 TestEbookConvertGDriveKepubify @@ -4878,7 +4929,7 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 're google-api-python-client - 2.40.0 + 2.41.0 TestEditAuthorsGdrive @@ -4914,7 +4965,7 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 're google-api-python-client - 2.40.0 + 2.41.0 TestEditBooksOnGdrive @@ -4956,7 +5007,7 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 're google-api-python-client - 2.40.0 + 2.41.0 TestSetupGdrive @@ -5046,7 +5097,7 @@ AssertionError: 'series' unexpectedly found in {'id': 5, 're