mirror of
https://github.com/janeczku/calibre-web
synced 2024-12-26 01:50:31 +00:00
Merge branch 'master' into cover_thumbnail
# Conflicts: # cps/editbooks.py # test/Calibre-Web TestSummary_Linux.html
This commit is contained in:
commit
ec53570118
@ -36,6 +36,6 @@ To receive fixes for security vulnerabilities it is required to always upgrade t
|
|||||||
| V 0.6.17 | The SSRF Protection can no longer be bypassed via 0.0.0.0 and it's ipv6 equivalent. Thanks to @r0hanSH ||
|
| V 0.6.17 | The SSRF Protection can no longer be bypassed via 0.0.0.0 and it's ipv6 equivalent. Thanks to @r0hanSH ||
|
||||||
|
|
||||||
|
|
||||||
## Staement regarding Log4j (CVE-2021-44228 and related)
|
## Statement regarding Log4j (CVE-2021-44228 and related)
|
||||||
|
|
||||||
Calibre-web is not affected by bugs related to Log4j. Calibre-Web is a python program, therefore not using Java, and not using the Java logging feature log4j.
|
Calibre-web is not affected by bugs related to Log4j. Calibre-Web is a python program, therefore not using Java, and not using the Java logging feature log4j.
|
||||||
|
@ -161,7 +161,7 @@ def shutdown():
|
|||||||
# needed for docker applications, as changes on metadata.db from host are not visible to application
|
# needed for docker applications, as changes on metadata.db from host are not visible to application
|
||||||
@admi.route("/reconnect", methods=['GET'])
|
@admi.route("/reconnect", methods=['GET'])
|
||||||
def reconnect():
|
def reconnect():
|
||||||
if cli.args.r:
|
if cli.reconnect_enable:
|
||||||
calibre_db.reconnect_db(config, ub.app_DB_path)
|
calibre_db.reconnect_db(config, ub.app_DB_path)
|
||||||
return json.dumps({})
|
return json.dumps({})
|
||||||
else:
|
else:
|
||||||
@ -1239,7 +1239,7 @@ def _db_configuration_update_helper():
|
|||||||
config.store_calibre_uuid(calibre_db, db.LibraryId)
|
config.store_calibre_uuid(calibre_db, db.LibraryId)
|
||||||
# if db changed -> delete shelfs, delete download books, delete read books, kobo sync...
|
# if db changed -> delete shelfs, delete download books, delete read books, kobo sync...
|
||||||
if db_change:
|
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.Downloads).delete()
|
||||||
ub.session.query(ub.ArchivedBook).delete()
|
ub.session.query(ub.ArchivedBook).delete()
|
||||||
ub.session.query(ub.ReadBook).delete()
|
ub.session.query(ub.ReadBook).delete()
|
||||||
|
@ -84,10 +84,14 @@ if args.k == "":
|
|||||||
|
|
||||||
# dry run updater
|
# dry run updater
|
||||||
dry_run = args.d or None
|
dry_run = args.d or None
|
||||||
|
# enable reconnect endpoint for docker database reconnect
|
||||||
|
reconnect_enable = args.r or os.environ.get("CALIBRE_RECONNECT", None)
|
||||||
# load covers from localhost
|
# load covers from localhost
|
||||||
allow_localhost = args.l or None
|
allow_localhost = args.l or os.environ.get("CALIBRE_LOCALHOST", None)
|
||||||
# handle and check ip address argument
|
# handle and check ip address argument
|
||||||
ip_address = args.i or None
|
ip_address = args.i or None
|
||||||
|
|
||||||
|
|
||||||
if ip_address:
|
if ip_address:
|
||||||
try:
|
try:
|
||||||
# try to parse the given ip address with socket
|
# try to parse the given ip address with socket
|
||||||
|
18
cps/db.py
18
cps/db.py
@ -620,8 +620,8 @@ class CalibreDB:
|
|||||||
bd = (self.session.query(Books, read_column.value, ub.ArchivedBook.is_archived).select_from(Books)
|
bd = (self.session.query(Books, read_column.value, ub.ArchivedBook.is_archived).select_from(Books)
|
||||||
.join(read_column, read_column.book == book_id,
|
.join(read_column, read_column.book == book_id,
|
||||||
isouter=True))
|
isouter=True))
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError, IndexError):
|
||||||
log.error("Custom Column No.%d is not existing in calibre database", read_column)
|
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
|
# Skip linking read column and return None instead of read status
|
||||||
bd = self.session.query(Books, None, ub.ArchivedBook.is_archived)
|
bd = self.session.query(Books, None, ub.ArchivedBook.is_archived)
|
||||||
return (bd.filter(Books.id == book_id)
|
return (bd.filter(Books.id == book_id)
|
||||||
@ -665,11 +665,11 @@ class CalibreDB:
|
|||||||
neg_content_cc_filter = false() if neg_cc_list == [''] else \
|
neg_content_cc_filter = false() if neg_cc_list == [''] else \
|
||||||
getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \
|
getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \
|
||||||
any(cc_classes[self.config.config_restricted_column].value.in_(neg_cc_list))
|
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()
|
pos_content_cc_filter = false()
|
||||||
neg_content_cc_filter = true()
|
neg_content_cc_filter = true()
|
||||||
log.error(u"Custom Column No.%d is not existing in calibre database",
|
log.error("Custom Column No.{} is not existing in calibre database".format(
|
||||||
self.config.config_restricted_column)
|
self.config.config_restricted_column))
|
||||||
flash(_("Custom Column No.%(column)d is not existing in calibre database",
|
flash(_("Custom Column No.%(column)d is not existing in calibre database",
|
||||||
column=self.config.config_restricted_column),
|
column=self.config.config_restricted_column),
|
||||||
category="error")
|
category="error")
|
||||||
@ -728,8 +728,8 @@ class CalibreDB:
|
|||||||
query = (self.session.query(database, read_column.value, ub.ArchivedBook.is_archived)
|
query = (self.session.query(database, read_column.value, ub.ArchivedBook.is_archived)
|
||||||
.select_from(Books)
|
.select_from(Books)
|
||||||
.outerjoin(read_column, read_column.book == Books.id))
|
.outerjoin(read_column, read_column.book == Books.id))
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError, IndexError):
|
||||||
log.error("Custom Column No.%d is not existing in calibre database", read_column)
|
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
|
# Skip linking read column and return None instead of read status
|
||||||
query = self.session.query(database, None, ub.ArchivedBook.is_archived)
|
query = self.session.query(database, None, ub.ArchivedBook.is_archived)
|
||||||
query = query.outerjoin(ub.ArchivedBook, and_(Books.id == ub.ArchivedBook.book_id,
|
query = query.outerjoin(ub.ArchivedBook, and_(Books.id == ub.ArchivedBook.book_id,
|
||||||
@ -840,8 +840,8 @@ class CalibreDB:
|
|||||||
read_column = cc_classes[config_read_column]
|
read_column = cc_classes[config_read_column]
|
||||||
query = (self.session.query(Books, ub.ArchivedBook.is_archived, read_column.value).select_from(Books)
|
query = (self.session.query(Books, ub.ArchivedBook.is_archived, read_column.value).select_from(Books)
|
||||||
.outerjoin(read_column, read_column.book == Books.id))
|
.outerjoin(read_column, read_column.book == Books.id))
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError, IndexError):
|
||||||
log.error("Custom Column No.%d is not existing in calibre database", config_read_column)
|
log.error("Custom Column No.{} is not existing in calibre database".format(config_read_column))
|
||||||
# Skip linking read column
|
# Skip linking read column
|
||||||
query = self.session.query(Books, ub.ArchivedBook.is_archived, None)
|
query = self.session.query(Books, ub.ArchivedBook.is_archived, None)
|
||||||
query = query.outerjoin(ub.ArchivedBook, and_(Books.id == ub.ArchivedBook.book_id,
|
query = query.outerjoin(ub.ArchivedBook, and_(Books.id == ub.ArchivedBook.book_id,
|
||||||
|
307
cps/editbooks.py
307
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):
|
def render_delete_book_result(book_format, json_response, warning, book_id):
|
||||||
if book_format:
|
if book_format:
|
||||||
if json_response:
|
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",
|
"type": "success",
|
||||||
"format": book_format,
|
"format": book_format,
|
||||||
"message": _('Book Format Successfully Deleted')}])
|
"message": _('Book Format Successfully Deleted')}])
|
||||||
else:
|
else:
|
||||||
flash(_('Book Format Successfully Deleted'), category="success")
|
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:
|
else:
|
||||||
if json_response:
|
if json_response:
|
||||||
return json.dumps([warning, {"location": url_for('web.index'),
|
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())
|
result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper())
|
||||||
if not result:
|
if not result:
|
||||||
if json_response:
|
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",
|
"type": "danger",
|
||||||
"format": "",
|
"format": "",
|
||||||
"message": error}])
|
"message": error}])
|
||||||
else:
|
else:
|
||||||
flash(error, category="error")
|
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 error:
|
||||||
if json_response:
|
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",
|
"type": "warning",
|
||||||
"format": "",
|
"format": "",
|
||||||
"message": error}
|
"message": error}
|
||||||
@ -343,13 +343,13 @@ def delete_book_from_table(book_id, book_format, json_response):
|
|||||||
log.error_or_exception(ex)
|
log.error_or_exception(ex)
|
||||||
calibre_db.session.rollback()
|
calibre_db.session.rollback()
|
||||||
if json_response:
|
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",
|
"type": "danger",
|
||||||
"format": "",
|
"format": "",
|
||||||
"message": ex}])
|
"message": ex}])
|
||||||
else:
|
else:
|
||||||
flash(str(ex), category="error")
|
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:
|
else:
|
||||||
# book not found
|
# 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)
|
return render_delete_book_result(book_format, json_response, warning, book_id)
|
||||||
message = _("You are missing permissions to delete books")
|
message = _("You are missing permissions to delete books")
|
||||||
if json_response:
|
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",
|
"type": "danger",
|
||||||
"format": "",
|
"format": "",
|
||||||
"message": message})
|
"message": message})
|
||||||
else:
|
else:
|
||||||
flash(message, category="error")
|
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):
|
def render_edit_book(book_id):
|
||||||
@ -413,18 +413,18 @@ def render_edit_book(book_id):
|
|||||||
|
|
||||||
def edit_book_ratings(to_save, book):
|
def edit_book_ratings(to_save, book):
|
||||||
changed = False
|
changed = False
|
||||||
if to_save["rating"].strip():
|
if to_save.get("rating","").strip():
|
||||||
old_rating = False
|
old_rating = False
|
||||||
if len(book.ratings) > 0:
|
if len(book.ratings) > 0:
|
||||||
old_rating = book.ratings[0].rating
|
old_rating = book.ratings[0].rating
|
||||||
ratingx2 = int(float(to_save["rating"]) * 2)
|
rating_x2 = int(float(to_save.get("rating","")) * 2)
|
||||||
if ratingx2 != old_rating:
|
if rating_x2 != old_rating:
|
||||||
changed = True
|
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:
|
if is_rating:
|
||||||
book.ratings.append(is_rating)
|
book.ratings.append(is_rating)
|
||||||
else:
|
else:
|
||||||
new_rating = db.Ratings(rating=ratingx2)
|
new_rating = db.Ratings(rating=rating_x2)
|
||||||
book.ratings.append(new_rating)
|
book.ratings.append(new_rating)
|
||||||
if old_rating:
|
if old_rating:
|
||||||
book.ratings.remove(book.ratings[0])
|
book.ratings.remove(book.ratings[0])
|
||||||
@ -622,24 +622,26 @@ def edit_cc_data(book_id, book, to_save, cc):
|
|||||||
'custom')
|
'custom')
|
||||||
return changed
|
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):
|
def upload_single_file(file_request, book, book_id):
|
||||||
# Check and handle Uploaded file
|
# Check and handle Uploaded file
|
||||||
if 'btn-upload-format' in file_request.files:
|
requested_file = file_request.files.get('btn-upload-format', None)
|
||||||
requested_file = file_request.files['btn-upload-format']
|
if requested_file:
|
||||||
# check for empty request
|
# check for empty request
|
||||||
if requested_file.filename != '':
|
if requested_file.filename != '':
|
||||||
if not current_user.role_upload():
|
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:
|
if '.' in requested_file.filename:
|
||||||
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
||||||
if file_ext not in constants.EXTENSIONS_UPLOAD and '' not in constants.EXTENSIONS_UPLOAD:
|
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),
|
flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext),
|
||||||
category="error")
|
category="error")
|
||||||
return redirect(url_for('web.show_book', book_id=book.id))
|
return False
|
||||||
else:
|
else:
|
||||||
flash(_('File to be uploaded must have an extension'), category="error")
|
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]
|
file_name = book.path.rsplit('/', 1)[-1]
|
||||||
filepath = os.path.normpath(os.path.join(config.config_calibre_dir, book.path))
|
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)
|
os.makedirs(filepath)
|
||||||
except OSError:
|
except OSError:
|
||||||
flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
|
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:
|
try:
|
||||||
requested_file.save(saved_filename)
|
requested_file.save(saved_filename)
|
||||||
except OSError:
|
except OSError:
|
||||||
flash(_(u"Failed to store file %(file)s.", file=saved_filename), category="error")
|
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)
|
file_size = os.path.getsize(saved_filename)
|
||||||
is_format = calibre_db.get_book_format(book_id, file_ext.upper())
|
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()
|
calibre_db.session.rollback()
|
||||||
log.error_or_exception("Database error: {}".format(e))
|
log.error_or_exception("Database error: {}".format(e))
|
||||||
flash(_(u"Database error: %(error)s.", error=e.orig), category="error")
|
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
|
# Queue uploader info
|
||||||
link = '<a href="{}">{}</a>'.format(url_for('web.show_book', book_id=book.id), escape(book.title))
|
link = '<a href="{}">{}</a>'.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(
|
return uploader.process(
|
||||||
saved_filename, *os.path.splitext(requested_file.filename),
|
saved_filename, *os.path.splitext(requested_file.filename),
|
||||||
rarExecutable=config.config_rarfile_location)
|
rarExecutable=config.config_rarfile_location)
|
||||||
|
return None
|
||||||
|
|
||||||
def upload_cover(cover_request, book):
|
def upload_cover(cover_request, book):
|
||||||
if 'btn-upload-cover' in cover_request.files:
|
requested_file = cover_request.files.get('btn-upload-cover', None)
|
||||||
requested_file = cover_request.files['btn-upload-cover']
|
if requested_file:
|
||||||
# check for empty request
|
# check for empty request
|
||||||
if requested_file.filename != '':
|
if requested_file.filename != '':
|
||||||
if not current_user.role_upload():
|
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)
|
ret, message = helper.save_cover(requested_file, book.path)
|
||||||
if ret is True:
|
if ret is True:
|
||||||
helper.clear_cover_thumbnail_cache(book.id)
|
helper.clear_cover_thumbnail_cache(book.id)
|
||||||
@ -717,25 +720,6 @@ def handle_title_on_edit(book, book_title):
|
|||||||
def handle_author_on_edit(book, author_name, update_stored=True):
|
def handle_author_on_edit(book, author_name, update_stored=True):
|
||||||
# handle author(s)
|
# handle author(s)
|
||||||
input_authors, renamed = prepare_authors(author_name)
|
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')
|
change = modify_database_object(input_authors, book.authors, db.Authors, calibre_db.session, 'author')
|
||||||
|
|
||||||
@ -755,12 +739,19 @@ def handle_author_on_edit(book, author_name, update_stored=True):
|
|||||||
change = True
|
change = True
|
||||||
return input_authors, change, renamed
|
return input_authors, change, renamed
|
||||||
|
|
||||||
|
@EditBook.route("/admin/book/<int:book_id>", 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/<int:book_id>", methods=['GET', 'POST'])
|
|
||||||
|
@EditBook.route("/admin/book/<int:book_id>", methods=['POST'])
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
@edit_required
|
@edit_required
|
||||||
def edit_book(book_id):
|
def edit_book(book_id):
|
||||||
modify_date = False
|
modify_date = False
|
||||||
|
edit_error = False
|
||||||
|
|
||||||
# create the function for sorting...
|
# create the function for sorting...
|
||||||
try:
|
try:
|
||||||
@ -769,110 +760,120 @@ def edit_book(book_id):
|
|||||||
log.error_or_exception(e)
|
log.error_or_exception(e)
|
||||||
calibre_db.session.rollback()
|
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 = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
||||||
|
|
||||||
# Book not found
|
# Book not found
|
||||||
if not book:
|
if not book:
|
||||||
flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"),
|
flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"),
|
||||||
category="error")
|
category="error")
|
||||||
return redirect(url_for("web.index"))
|
return redirect(url_for("web.index"))
|
||||||
|
|
||||||
meta = upload_single_file(request, book, book_id)
|
to_save = request.form.to_dict()
|
||||||
if upload_cover(request, book) is True:
|
|
||||||
book.has_cover = 1
|
|
||||||
modify_date = True
|
|
||||||
try:
|
try:
|
||||||
to_save = request.form.to_dict()
|
# Update folder of book on local disk
|
||||||
merge_metadata(to_save, meta)
|
|
||||||
# Update book
|
|
||||||
edited_books_id = None
|
edited_books_id = None
|
||||||
|
title_author_error = None
|
||||||
# handle book title
|
# handle book title change
|
||||||
title_change = handle_title_on_edit(book, to_save["book_title"])
|
title_change = handle_title_on_edit(book, to_save["book_title"])
|
||||||
|
# handle book author change
|
||||||
input_authors, authorchange, renamed = handle_author_on_edit(book, to_save["author_name"])
|
input_authors, author_change, renamed = handle_author_on_edit(book, to_save["author_name"])
|
||||||
if authorchange or title_change:
|
if author_change or title_change:
|
||||||
edited_books_id = book.id
|
edited_books_id = book.id
|
||||||
modify_date = True
|
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:
|
if config.config_use_google_drive:
|
||||||
gdriveutils.updateGdriveCalibreFromLocal()
|
gdriveutils.updateGdriveCalibreFromLocal()
|
||||||
|
|
||||||
error = ""
|
if to_save.get("cover_url", None):
|
||||||
if edited_books_id:
|
if not current_user.role_upload():
|
||||||
error = helper.update_dir_structure(edited_books_id, config.config_calibre_dir, input_authors[0],
|
edit_error = True
|
||||||
renamed_author=renamed)
|
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:
|
# Add default series_index to book
|
||||||
if "cover_url" in to_save:
|
modify_date |= edit_book_series_index(to_save["series_index"], book)
|
||||||
if to_save["cover_url"]:
|
# Handle book comments/description
|
||||||
if not current_user.role_upload():
|
modify_date |= edit_book_comments(Markup(to_save['description']).unescape(), book)
|
||||||
calibre_db.session.rollback()
|
# Handle identifiers
|
||||||
return "", 403
|
input_identifiers = identifier_list(to_save, book)
|
||||||
if to_save["cover_url"].endswith('/static/generic_cover.jpg'):
|
modification, warning = modify_identifiers(input_identifiers, book.identifiers, calibre_db.session)
|
||||||
book.has_cover = 0
|
if warning:
|
||||||
else:
|
flash(_("Identifiers are not Case Sensitive, Overwriting Old Identifier"), category="warning")
|
||||||
result, error = helper.save_cover_from_url(to_save["cover_url"], book.path)
|
modify_date |= modification
|
||||||
if result is True:
|
# Handle book tags
|
||||||
book.has_cover = 1
|
modify_date |= edit_book_tags(to_save['tags'], book)
|
||||||
modify_date = True
|
# Handle book series
|
||||||
helper.clear_cover_thumbnail_cache(book.id)
|
modify_date |= edit_book_series(to_save["series"], book)
|
||||||
else:
|
# handle book publisher
|
||||||
flash(error, category="error")
|
modify_date |= edit_book_publisher(to_save['publisher'], book)
|
||||||
|
# handle book languages
|
||||||
# Add default series_index to book
|
try:
|
||||||
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
|
|
||||||
modify_date |= edit_book_languages(to_save['languages'], book)
|
modify_date |= edit_book_languages(to_save['languages'], book)
|
||||||
# handle book ratings
|
except ValueError as e:
|
||||||
modify_date |= edit_book_ratings(to_save, book)
|
flash(str(e), category="error")
|
||||||
# handle cc data
|
edit_error = True
|
||||||
modify_date |= edit_all_cc_data(book_id, book, to_save)
|
# 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"]:
|
if to_save.get("pubdate", None):
|
||||||
try:
|
try:
|
||||||
book.pubdate = datetime.strptime(to_save["pubdate"], "%Y-%m-%d")
|
book.pubdate = datetime.strptime(to_save["pubdate"], "%Y-%m-%d")
|
||||||
except ValueError:
|
except ValueError as e:
|
||||||
book.pubdate = db.Books.DEFAULT_PUBDATE
|
|
||||||
else:
|
|
||||||
book.pubdate = db.Books.DEFAULT_PUBDATE
|
book.pubdate = db.Books.DEFAULT_PUBDATE
|
||||||
|
flash(str(e), category="error")
|
||||||
if modify_date:
|
edit_error = True
|
||||||
book.last_modified = datetime.utcnow()
|
else:
|
||||||
kobo_sync_status.remove_synced_book(edited_books_id, all=True)
|
book.pubdate = db.Books.DEFAULT_PUBDATE
|
||||||
|
|
||||||
calibre_db.session.merge(book)
|
if modify_date:
|
||||||
calibre_db.session.commit()
|
book.last_modified = datetime.utcnow()
|
||||||
if config.config_use_google_drive:
|
kobo_sync_status.remove_synced_book(edited_books_id, all=True)
|
||||||
gdriveutils.updateGdriveCalibreFromLocal()
|
|
||||||
if "detail_view" in to_save:
|
calibre_db.session.merge(book)
|
||||||
return redirect(url_for('web.show_book', book_id=book.id))
|
calibre_db.session.commit()
|
||||||
else:
|
if config.config_use_google_drive:
|
||||||
flash(_("Metadata successfully updated"), category="success")
|
gdriveutils.updateGdriveCalibreFromLocal()
|
||||||
return render_edit_book(book_id)
|
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:
|
else:
|
||||||
calibre_db.session.rollback()
|
|
||||||
flash(error, category="error")
|
|
||||||
return render_edit_book(book_id)
|
return render_edit_book(book_id)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
log.error_or_exception("Error: {}".format(e))
|
||||||
calibre_db.session.rollback()
|
calibre_db.session.rollback()
|
||||||
flash(str(e), category="error")
|
flash(str(e), category="error")
|
||||||
return redirect(url_for('web.show_book', book_id=book.id))
|
return redirect(url_for('web.show_book', book_id=book.id))
|
||||||
@ -884,14 +885,14 @@ def edit_book(book_id):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log.error_or_exception(ex)
|
log.error_or_exception(ex)
|
||||||
calibre_db.session.rollback()
|
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))
|
return redirect(url_for('web.show_book', book_id=book.id))
|
||||||
|
|
||||||
|
|
||||||
def merge_metadata(to_save, meta):
|
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'] = ''
|
to_save['author_name'] = ''
|
||||||
if to_save['book_title'] == _(u'Unknown'):
|
if to_save.get('book_title', "") == _(u'Unknown'):
|
||||||
to_save['book_title'] = ''
|
to_save['book_title'] = ''
|
||||||
for s_field, m_field in [
|
for s_field, m_field in [
|
||||||
('tags', 'tags'), ('author_name', 'author'), ('series', 'series'),
|
('tags', 'tags'), ('author_name', 'author'), ('series', 'series'),
|
||||||
@ -1119,7 +1120,7 @@ def upload():
|
|||||||
|
|
||||||
if len(request.files.getlist("btn-upload")) < 2:
|
if len(request.files.getlist("btn-upload")) < 2:
|
||||||
if current_user.role_edit() or current_user.role_admin():
|
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')
|
return Response(json.dumps(resp), mimetype='application/json')
|
||||||
else:
|
else:
|
||||||
resp = {"location": url_for('web.show_book', book_id=book_id)}
|
resp = {"location": url_for('web.show_book', book_id=book_id)}
|
||||||
@ -1141,7 +1142,7 @@ def convert_bookformat(book_id):
|
|||||||
|
|
||||||
if (book_format_from is None) or (book_format_to is None):
|
if (book_format_from is None) or (book_format_to is None):
|
||||||
flash(_(u"Source or destination format for conversion missing"), category="error")
|
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)
|
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(),
|
rtn = helper.convert_book_format(book_id, config.config_calibre_dir, book_format_from.upper(),
|
||||||
@ -1153,7 +1154,7 @@ def convert_bookformat(book_id):
|
|||||||
category="success")
|
category="success")
|
||||||
else:
|
else:
|
||||||
flash(_(u"There was an error converting this book: %(res)s", res=rtn), category="error")
|
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/<int:c_id>")
|
@EditBook.route("/ajax/getcustomenum/<int:c_id>")
|
||||||
@ -1213,10 +1214,15 @@ def edit_list_book(param):
|
|||||||
mimetype='application/json')
|
mimetype='application/json')
|
||||||
elif param == 'title':
|
elif param == 'title':
|
||||||
sort_param = book.sort
|
sort_param = book.sort
|
||||||
handle_title_on_edit(book, vals.get('value', ""))
|
if handle_title_on_edit(book, vals.get('value', "")):
|
||||||
helper.update_dir_structure(book.id, config.config_calibre_dir)
|
rename_error = helper.update_dir_structure(book.id, config.config_calibre_dir)
|
||||||
ret = Response(json.dumps({'success': True, 'newValue': book.title}),
|
if not rename_error:
|
||||||
mimetype='application/json')
|
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':
|
elif param == 'sort':
|
||||||
book.sort = vals['value']
|
book.sort = vals['value']
|
||||||
ret = Response(json.dumps({'success': True, 'newValue': book.sort}),
|
ret = Response(json.dumps({'success': True, 'newValue': book.sort}),
|
||||||
@ -1227,11 +1233,17 @@ def edit_list_book(param):
|
|||||||
mimetype='application/json')
|
mimetype='application/json')
|
||||||
elif param == 'authors':
|
elif param == 'authors':
|
||||||
input_authors, __, renamed = handle_author_on_edit(book, vals['value'], vals.get('checkA', None) == "true")
|
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)
|
rename_error = helper.update_dir_structure(book.id, config.config_calibre_dir, input_authors[0],
|
||||||
ret = Response(json.dumps({
|
renamed_author=renamed)
|
||||||
'success': True,
|
if not rename_error:
|
||||||
'newValue': ' & '.join([author.replace('|', ',') for author in input_authors])}),
|
ret = Response(json.dumps({
|
||||||
mimetype='application/json')
|
'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':
|
elif param == 'is_archived':
|
||||||
is_archived = change_archived_books(book.id, vals['value'] == "True",
|
is_archived = change_archived_books(book.id, vals['value'] == "True",
|
||||||
message="Book {} archive bit set to: {}".format(book.id, vals['value']))
|
message="Book {} archive bit set to: {}".format(book.id, vals['value']))
|
||||||
@ -1358,8 +1370,8 @@ def table_xchange_author_title():
|
|||||||
author_names.append(authr.name.replace('|', ','))
|
author_names.append(authr.name.replace('|', ','))
|
||||||
|
|
||||||
title_change = handle_title_on_edit(book, " ".join(author_names))
|
title_change = handle_title_on_edit(book, " ".join(author_names))
|
||||||
input_authors, authorchange, renamed = handle_author_on_edit(book, authors)
|
input_authors, author_change, renamed = handle_author_on_edit(book, authors)
|
||||||
if authorchange or title_change:
|
if author_change or title_change:
|
||||||
edited_books_id = book.id
|
edited_books_id = book.id
|
||||||
modify_date = True
|
modify_date = True
|
||||||
|
|
||||||
@ -1367,8 +1379,9 @@ def table_xchange_author_title():
|
|||||||
gdriveutils.updateGdriveCalibreFromLocal()
|
gdriveutils.updateGdriveCalibreFromLocal()
|
||||||
|
|
||||||
if edited_books_id:
|
if edited_books_id:
|
||||||
helper.update_dir_structure(edited_books_id, config.config_calibre_dir, input_authors[0],
|
# toDo: Handle error
|
||||||
renamed_author=renamed)
|
edit_error = helper.update_dir_structure(edited_books_id, config.config_calibre_dir, input_authors[0],
|
||||||
|
renamed_author=renamed)
|
||||||
if modify_date:
|
if modify_date:
|
||||||
book.last_modified = datetime.utcnow()
|
book.last_modified = datetime.utcnow()
|
||||||
try:
|
try:
|
||||||
|
@ -81,7 +81,7 @@ if gdrive_support:
|
|||||||
if not logger.is_debug_enabled():
|
if not logger.is_debug_enabled():
|
||||||
logger.get('googleapiclient.discovery').setLevel(logger.logging.ERROR)
|
logger.get('googleapiclient.discovery').setLevel(logger.logging.ERROR)
|
||||||
else:
|
else:
|
||||||
log.debug("Cannot import pydrive,httplib2, using gdrive will not work: %s", importError)
|
log.debug("Cannot import pydrive, httplib2, using gdrive will not work: %s", importError)
|
||||||
|
|
||||||
|
|
||||||
class Singleton:
|
class Singleton:
|
||||||
@ -272,8 +272,7 @@ def getEbooksFolderId(drive=None):
|
|||||||
try:
|
try:
|
||||||
session.commit()
|
session.commit()
|
||||||
except OperationalError as ex:
|
except OperationalError as ex:
|
||||||
log.error("gdrive.db DB is not Writeable")
|
log.error_or_exception('Database error: %s', ex)
|
||||||
log.debug('Database error: %s', ex)
|
|
||||||
session.rollback()
|
session.rollback()
|
||||||
return gDriveId.gdrive_id
|
return gDriveId.gdrive_id
|
||||||
|
|
||||||
@ -322,8 +321,7 @@ def getFolderId(path, drive):
|
|||||||
else:
|
else:
|
||||||
currentFolderId = storedPathName.gdrive_id
|
currentFolderId = storedPathName.gdrive_id
|
||||||
except OperationalError as ex:
|
except OperationalError as ex:
|
||||||
log.error("gdrive.db DB is not Writeable")
|
log.error_or_exception('Database error: %s', ex)
|
||||||
log.debug('Database error: %s', ex)
|
|
||||||
session.rollback()
|
session.rollback()
|
||||||
except ApiRequestError as ex:
|
except ApiRequestError as ex:
|
||||||
log.error('{} {}'.format(ex.error['message'], path))
|
log.error('{} {}'.format(ex.error['message'], path))
|
||||||
@ -547,8 +545,7 @@ def deleteDatabaseOnChange():
|
|||||||
session.commit()
|
session.commit()
|
||||||
except (OperationalError, InvalidRequestError) as ex:
|
except (OperationalError, InvalidRequestError) as ex:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
log.debug('Database error: %s', ex)
|
log.error_or_exception('Database error: %s', ex)
|
||||||
log.error(u"GDrive DB is not Writeable")
|
|
||||||
|
|
||||||
|
|
||||||
def updateGdriveCalibreFromLocal():
|
def updateGdriveCalibreFromLocal():
|
||||||
@ -566,8 +563,7 @@ def updateDatabaseOnEdit(ID,newPath):
|
|||||||
try:
|
try:
|
||||||
session.commit()
|
session.commit()
|
||||||
except OperationalError as ex:
|
except OperationalError as ex:
|
||||||
log.error("gdrive.db DB is not Writeable")
|
log.error_or_exception('Database error: %s', ex)
|
||||||
log.debug('Database error: %s', ex)
|
|
||||||
session.rollback()
|
session.rollback()
|
||||||
|
|
||||||
|
|
||||||
@ -577,8 +573,7 @@ def deleteDatabaseEntry(ID):
|
|||||||
try:
|
try:
|
||||||
session.commit()
|
session.commit()
|
||||||
except OperationalError as ex:
|
except OperationalError as ex:
|
||||||
log.error("gdrive.db DB is not Writeable")
|
log.error_or_exception('Database error: %s', ex)
|
||||||
log.debug('Database error: %s', ex)
|
|
||||||
session.rollback()
|
session.rollback()
|
||||||
|
|
||||||
|
|
||||||
@ -599,8 +594,7 @@ def get_cover_via_gdrive(cover_path):
|
|||||||
try:
|
try:
|
||||||
session.commit()
|
session.commit()
|
||||||
except OperationalError as ex:
|
except OperationalError as ex:
|
||||||
log.error("gdrive.db DB is not Writeable")
|
log.error_or_exception('Database error: %s', ex)
|
||||||
log.debug('Database error: %s', ex)
|
|
||||||
session.rollback()
|
session.rollback()
|
||||||
return df.metadata.get('webContentLink')
|
return df.metadata.get('webContentLink')
|
||||||
else:
|
else:
|
||||||
|
@ -329,8 +329,9 @@ def edit_book_read_status(book_id, read_status=None):
|
|||||||
new_cc = cc_class(value=read_status or 1, book=book_id)
|
new_cc = cc_class(value=read_status or 1, book=book_id)
|
||||||
calibre_db.session.add(new_cc)
|
calibre_db.session.add(new_cc)
|
||||||
calibre_db.session.commit()
|
calibre_db.session.commit()
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError, IndexError):
|
||||||
log.error(u"Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
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)
|
return "Custom Column No.{} is not existing in calibre database".format(config.config_read_column)
|
||||||
except (OperationalError, InvalidRequestError) as ex:
|
except (OperationalError, InvalidRequestError) as ex:
|
||||||
calibre_db.session.rollback()
|
calibre_db.session.rollback()
|
||||||
@ -449,31 +450,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
|
# 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):
|
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
|
# 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:
|
if original_filepath:
|
||||||
path = original_filepath
|
path = original_filepath
|
||||||
else:
|
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
|
# Create (current) author_dir and title_dir from database
|
||||||
authordir = localbook.path.split('/')[0]
|
author_dir = local_book.path.split('/')[0]
|
||||||
titledir = localbook.path.split('/')[1]
|
title_dir = local_book.path.split('/')[1]
|
||||||
|
|
||||||
# Create new_authordir from parameter or from database
|
# Create new_author_dir from parameter or from database
|
||||||
# Create new titledir from database and add id
|
# Create new title_dir from database and add id
|
||||||
new_authordir = rename_all_authors(first_author, renamed_author, calibre_path, localbook)
|
new_author_dir = rename_all_authors(first_author, renamed_author, calibre_path, local_book)
|
||||||
if first_author:
|
if first_author:
|
||||||
if first_author.lower() in [r.lower() for r in renamed_author]:
|
if first_author.lower() in [r.lower() for r in renamed_author]:
|
||||||
if os.path.isdir(os.path.join(calibre_path, new_authordir)):
|
if os.path.isdir(os.path.join(calibre_path, new_author_dir)):
|
||||||
path = os.path.join(calibre_path, new_authordir, titledir)
|
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,
|
error = move_files_on_change(calibre_path,
|
||||||
new_authordir,
|
new_author_dir,
|
||||||
new_titledir,
|
new_title_dir,
|
||||||
localbook,
|
local_book,
|
||||||
db_filename,
|
db_filename,
|
||||||
original_filepath,
|
original_filepath,
|
||||||
path)
|
path)
|
||||||
@ -481,7 +482,7 @@ def update_dir_structure_file(book_id, calibre_path, first_author, original_file
|
|||||||
return error
|
return error
|
||||||
|
|
||||||
# Rename all files from old names to new names
|
# 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):
|
def upload_new_file_gdrive(book_id, first_author, renamed_author, title, title_dir, original_filepath, filename_ext):
|
||||||
@ -493,7 +494,7 @@ def upload_new_file_gdrive(book_id, first_author, renamed_author, title, title_d
|
|||||||
title_dir + " (" + str(book_id) + ")")
|
title_dir + " (" + str(book_id) + ")")
|
||||||
book.path = gdrive_path.replace("\\", "/")
|
book.path = gdrive_path.replace("\\", "/")
|
||||||
gd.uploadFileToEbooksFolder(os.path.join(gdrive_path, file_name).replace("\\", "/"), original_filepath)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -553,8 +554,7 @@ def move_files_on_change(calibre_path, new_authordir, new_titledir, localbook, d
|
|||||||
# change location in database to new author/title path
|
# change location in database to new author/title path
|
||||||
localbook.path = os.path.join(new_authordir, new_titledir).replace('\\', '/')
|
localbook.path = os.path.join(new_authordir, new_titledir).replace('\\', '/')
|
||||||
except OSError as ex:
|
except OSError as ex:
|
||||||
log.error("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))
|
||||||
log.debug(ex, exc_info=True)
|
|
||||||
return _("Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s",
|
return _("Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s",
|
||||||
src=path, dest=new_path, error=str(ex))
|
src=path, dest=new_path, error=str(ex))
|
||||||
return False
|
return False
|
||||||
@ -562,8 +562,8 @@ def move_files_on_change(calibre_path, new_authordir, new_titledir, localbook, d
|
|||||||
|
|
||||||
def rename_files_on_change(first_author,
|
def rename_files_on_change(first_author,
|
||||||
renamed_author,
|
renamed_author,
|
||||||
localbook,
|
local_book,
|
||||||
orignal_filepath="",
|
original_filepath="",
|
||||||
path="",
|
path="",
|
||||||
calibre_path="",
|
calibre_path="",
|
||||||
gdrive=False):
|
gdrive=False):
|
||||||
@ -571,13 +571,12 @@ def rename_files_on_change(first_author,
|
|||||||
try:
|
try:
|
||||||
clean_author_database(renamed_author, calibre_path, gdrive=gdrive)
|
clean_author_database(renamed_author, calibre_path, gdrive=gdrive)
|
||||||
if first_author and first_author not in renamed_author:
|
if first_author and first_author not in renamed_author:
|
||||||
clean_author_database([first_author], calibre_path, localbook, gdrive)
|
clean_author_database([first_author], calibre_path, local_book, gdrive)
|
||||||
if not gdrive and not renamed_author and not orignal_filepath and len(os.listdir(os.path.dirname(path))) == 0:
|
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))
|
shutil.rmtree(os.path.dirname(path))
|
||||||
except (OSError, FileNotFoundError) as ex:
|
except (OSError, FileNotFoundError) as ex:
|
||||||
log.error("Error in rename file in path %s", ex)
|
log.error_or_exception("Error in rename file in path {}".format(ex))
|
||||||
log.debug(ex, exc_info=True)
|
return _("Error in rename file in path: {}".format(str(ex)))
|
||||||
return _("Error in rename file in path: %(error)s", error=str(ex))
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@ -804,16 +803,18 @@ def save_cover_from_url(url, book_path):
|
|||||||
return save_cover(img, book_path)
|
return save_cover(img, book_path)
|
||||||
except (socket.gaierror,
|
except (socket.gaierror,
|
||||||
requests.exceptions.HTTPError,
|
requests.exceptions.HTTPError,
|
||||||
|
requests.exceptions.InvalidURL,
|
||||||
requests.exceptions.ConnectionError,
|
requests.exceptions.ConnectionError,
|
||||||
requests.exceptions.Timeout) as ex:
|
requests.exceptions.Timeout) as ex:
|
||||||
log.info(u'Cover Download Error %s', ex)
|
# "Invalid host" can be the result of a redirect response
|
||||||
|
log.error(u'Cover Download Error %s', ex)
|
||||||
return False, _("Error Downloading Cover")
|
return False, _("Error Downloading Cover")
|
||||||
except MissingDelegateError as ex:
|
except MissingDelegateError as ex:
|
||||||
log.info(u'File Format Error %s', ex)
|
log.info(u'File Format Error %s', ex)
|
||||||
return False, _("Cover Format Error")
|
return False, _("Cover Format Error")
|
||||||
except UnacceptableAddressException:
|
except UnacceptableAddressException as e:
|
||||||
log.error("Localhost was accessed for cover upload")
|
log.error("Localhost or local network was accessed for cover upload")
|
||||||
return False, _("You are not allowed to access localhost for cover uploads")
|
return False, _("You are not allowed to access localhost or the local network for cover uploads")
|
||||||
|
|
||||||
|
|
||||||
def save_cover_from_filestorage(filepath, saved_filename, img):
|
def save_cover_from_filestorage(filepath, saved_filename, img):
|
||||||
|
@ -34,7 +34,6 @@ from .pagination import Pagination
|
|||||||
from .web import render_read_books
|
from .web import render_read_books
|
||||||
from .usermanagement import load_user_from_request
|
from .usermanagement import load_user_from_request
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from sqlalchemy.orm import InstrumentedAttribute
|
|
||||||
opds = Blueprint('opds', __name__)
|
opds = Blueprint('opds', __name__)
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
@ -110,8 +110,8 @@ def get_readbooks_ids():
|
|||||||
readBooks = calibre_db.session.query(db.cc_classes[config.config_read_column])\
|
readBooks = calibre_db.session.query(db.cc_classes[config.config_read_column])\
|
||||||
.filter(db.cc_classes[config.config_read_column].value == True).all()
|
.filter(db.cc_classes[config.config_read_column].value == True).all()
|
||||||
return frozenset([x.book for x in readBooks])
|
return frozenset([x.book for x in readBooks])
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError, IndexError):
|
||||||
log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
log.error("Custom Column No.{} is not existing in calibre database".format(config.config_read_column))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Returns the template for rendering and includes the instance name
|
# Returns the template for rendering and includes the instance name
|
||||||
|
@ -296,7 +296,7 @@
|
|||||||
{% if g.user.role_edit() %}
|
{% if g.user.role_edit() %}
|
||||||
<div class="btn-toolbar" role="toolbar">
|
<div class="btn-toolbar" role="toolbar">
|
||||||
<div class="btn-group" role="group" aria-label="Edit/Delete book">
|
<div class="btn-group" role="group" aria-label="Edit/Delete book">
|
||||||
<a href="{{ url_for('edit-book.edit_book', book_id=entry.id) }}" class="btn btn-sm btn-primary" id="edit_book" role="button"><span class="glyphicon glyphicon-edit"></span> {{_('Edit Metadata')}}</a>
|
<a href="{{ url_for('edit-book.show_edit_book', book_id=entry.id) }}" class="btn btn-sm btn-primary" id="edit_book" role="button"><span class="glyphicon glyphicon-edit"></span> {{_('Edit Metadata')}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
14
cps/ub.py
14
cps/ub.py
@ -68,6 +68,7 @@ logged_in = dict()
|
|||||||
def signal_store_user_session(object, user):
|
def signal_store_user_session(object, user):
|
||||||
store_user_session()
|
store_user_session()
|
||||||
|
|
||||||
|
|
||||||
def store_user_session():
|
def store_user_session():
|
||||||
if flask_session.get('user_id', ""):
|
if flask_session.get('user_id', ""):
|
||||||
flask_session['_user_id'] = flask_session.get('user_id', "")
|
flask_session['_user_id'] = flask_session.get('user_id', "")
|
||||||
@ -86,15 +87,16 @@ def store_user_session():
|
|||||||
else:
|
else:
|
||||||
log.error("No user id in session")
|
log.error("No user id in session")
|
||||||
|
|
||||||
|
|
||||||
def delete_user_session(user_id, session_key):
|
def delete_user_session(user_id, session_key):
|
||||||
try:
|
try:
|
||||||
log.debug("Deleted session_key: " + session_key)
|
log.debug("Deleted session_key: " + session_key)
|
||||||
session.query(User_Sessions).filter(User_Sessions.user_id==user_id,
|
session.query(User_Sessions).filter(User_Sessions.user_id == user_id,
|
||||||
User_Sessions.session_key==session_key).delete()
|
User_Sessions.session_key == session_key).delete()
|
||||||
session.commit()
|
session.commit()
|
||||||
except (exc.OperationalError, exc.InvalidRequestError) as e:
|
except (exc.OperationalError, exc.InvalidRequestError) as ex:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
log.exception(e)
|
log.exception(ex)
|
||||||
|
|
||||||
|
|
||||||
def check_user_session(user_id, session_key):
|
def check_user_session(user_id, session_key):
|
||||||
@ -210,9 +212,9 @@ class UserBase:
|
|||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
session.commit()
|
session.commit()
|
||||||
except (exc.OperationalError, exc.InvalidRequestError):
|
except (exc.OperationalError, exc.InvalidRequestError) as e:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
# ToDo: Error message
|
log.error_or_exception(e)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<User %r>' % self.name
|
return '<User %r>' % self.name
|
||||||
|
20
cps/web.py
20
cps/web.py
@ -87,7 +87,7 @@ def add_security_headers(resp):
|
|||||||
csp += ''.join([' ' + host for host in config.config_trustedhosts.strip().split(',')])
|
csp += ''.join([' ' + host for host in config.config_trustedhosts.strip().split(',')])
|
||||||
csp += " 'unsafe-inline' 'unsafe-eval'; font-src 'self' data:; img-src 'self' data:"
|
csp += " 'unsafe-inline' 'unsafe-eval'; font-src 'self' data:; img-src 'self' data:"
|
||||||
resp.headers['Content-Security-Policy'] = csp
|
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'] += " *"
|
resp.headers['Content-Security-Policy'] += " *"
|
||||||
elif request.endpoint == "web.read_book":
|
elif request.endpoint == "web.read_book":
|
||||||
resp.headers['Content-Security-Policy'] += " blob:;style-src-elem 'self' blob: 'unsafe-inline';"
|
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.Books.id == db.books_series_link.c.book,
|
||||||
db.Series,
|
db.Series,
|
||||||
db.cc_classes[config.config_read_column])
|
db.cc_classes[config.config_read_column])
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError, IndexError):
|
||||||
log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
log.error("Custom Column No.{} is not existing in calibre database".format(config.config_read_column))
|
||||||
if not as_xml:
|
if not as_xml:
|
||||||
flash(_("Custom Column No.%(column)d is not existing in calibre database",
|
flash(_("Custom Column No.%(column)d is not existing in calibre database",
|
||||||
column=config.config_read_column),
|
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)
|
books = (calibre_db.session.query(db.Books, read_column.value, ub.ArchivedBook.is_archived)
|
||||||
.select_from(db.Books)
|
.select_from(db.Books)
|
||||||
.outerjoin(read_column, read_column.book == db.Books.id))
|
.outerjoin(read_column, read_column.book == db.Books.id))
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError, IndexError):
|
||||||
log.error("Custom Column No.%d is not existing in calibre database", read_column)
|
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
|
# Skip linking read column and return None instead of read status
|
||||||
books = calibre_db.session.query(db.Books, None, ub.ArchivedBook.is_archived)
|
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,
|
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:
|
else:
|
||||||
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
|
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
|
||||||
.filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True)
|
.filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True)
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError, IndexError):
|
||||||
log.error(u"Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
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",
|
flash(_("Custom Column No.%(column)d is not existing in calibre database",
|
||||||
column=config.config_read_column),
|
column=config.config_read_column),
|
||||||
category="error")
|
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)
|
query = (calibre_db.session.query(db.Books, ub.ArchivedBook.is_archived, read_column.value)
|
||||||
.select_from(db.Books)
|
.select_from(db.Books)
|
||||||
.outerjoin(read_column, read_column.book == db.Books.id))
|
.outerjoin(read_column, read_column.book == db.Books.id))
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError, IndexError):
|
||||||
log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
log.error("Custom Column No.{} is not existing in calibre database".format(config.config_read_column))
|
||||||
# Skip linking read column
|
# Skip linking read column
|
||||||
query = calibre_db.session.query(db.Books, ub.ArchivedBook.is_archived, None)
|
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,
|
query = query.outerjoin(ub.ArchivedBook, and_(db.Books.id == ub.ArchivedBook.book_id,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# GDrive Integration
|
# 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
|
gevent>20.6.0,<22.0.0
|
||||||
greenlet>=0.4.17,<1.2.0
|
greenlet>=0.4.17,<1.2.0
|
||||||
httplib2>=0.9.2,<0.21.0
|
httplib2>=0.9.2,<0.21.0
|
||||||
@ -13,7 +13,7 @@ rsa>=3.4.2,<4.9.0
|
|||||||
|
|
||||||
# Gmail
|
# Gmail
|
||||||
google-auth-oauthlib>=0.4.3,<0.6.0
|
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
|
||||||
goodreads>=0.3.2,<0.4.0
|
goodreads>=0.3.2,<0.4.0
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
APScheduler>=3.6.3,<3.8.0
|
APScheduler>=3.6.3,<3.10.0
|
||||||
Babel>=1.3,<3.0
|
Babel>=1.3,<3.0
|
||||||
Flask-Babel>=0.11.1,<2.1.0
|
Flask-Babel>=0.11.1,<2.1.0
|
||||||
Flask-Login>=0.3.2,<0.5.1
|
Flask-Login>=0.3.2,<0.5.1
|
||||||
|
Loading…
Reference in New Issue
Block a user