1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-09-21 03:39:46 +00:00

Upload (multiple) book formats with progress and merge the corresponding metadata into the book

This commit is contained in:
Ozzie Isaacs 2024-08-17 15:34:50 +02:00
parent d8d9d405e9
commit 1eb77c9cd4
7 changed files with 291 additions and 277 deletions

View File

@ -567,7 +567,7 @@ def update_view_configuration():
_config_string(to_save, "config_calibre_web_title") _config_string(to_save, "config_calibre_web_title")
_config_string(to_save, "config_columns_to_ignore") _config_string(to_save, "config_columns_to_ignore")
if _config_string(to_save, "config_title_regex"): if _config_string(to_save, "config_title_regex"):
calibre_db.update_title_sort(config) calibre_db.create_functions(config)
if not check_valid_read_column(to_save.get("config_read_column", "0")): if not check_valid_read_column(to_save.get("config_read_column", "0")):
flash(_("Invalid Read Column"), category="error") flash(_("Invalid Read Column"), category="error")

View File

@ -24,6 +24,7 @@ from datetime import datetime, timezone
from urllib.parse import quote from urllib.parse import quote
import unidecode import unidecode
from weakref import WeakSet from weakref import WeakSet
from uuid import uuid4
from sqlite3 import OperationalError as sqliteOperationalError from sqlite3 import OperationalError as sqliteOperationalError
from sqlalchemy import create_engine from sqlalchemy import create_engine
@ -533,7 +534,7 @@ class CalibreDB:
def init_session(self, expire_on_commit=True): def init_session(self, expire_on_commit=True):
self.session = self.session_factory() self.session = self.session_factory()
self.session.expire_on_commit = expire_on_commit self.session.expire_on_commit = expire_on_commit
self.update_title_sort(self.config) self.create_functions(self.config)
@classmethod @classmethod
def setup_db_cc_classes(cls, cc): def setup_db_cc_classes(cls, cc):
@ -901,7 +902,8 @@ class CalibreDB:
def get_typeahead(self, database, query, replace=('', ''), tag_filter=true()): def get_typeahead(self, database, query, replace=('', ''), tag_filter=true()):
query = query or '' query = query or ''
self.session.connection().connection.connection.create_function("lower", 1, lcase) self.create_functions()
# self.session.connection().connection.connection.create_function("lower", 1, lcase)
entries = self.session.query(database).filter(tag_filter). \ entries = self.session.query(database).filter(tag_filter). \
filter(func.lower(database.name).ilike("%" + query + "%")).all() filter(func.lower(database.name).ilike("%" + query + "%")).all()
# json_dumps = json.dumps([dict(name=escape(r.name.replace(*replace))) for r in entries]) # json_dumps = json.dumps([dict(name=escape(r.name.replace(*replace))) for r in entries])
@ -909,7 +911,8 @@ class CalibreDB:
return json_dumps return json_dumps
def check_exists_book(self, authr, title): def check_exists_book(self, authr, title):
self.session.connection().connection.connection.create_function("lower", 1, lcase) self.create_functions()
# self.session.connection().connection.connection.create_function("lower", 1, lcase)
q = list() q = list()
author_terms = re.split(r'\s*&\s*', authr) author_terms = re.split(r'\s*&\s*', authr)
for author_term in author_terms: for author_term in author_terms:
@ -920,7 +923,8 @@ class CalibreDB:
def search_query(self, term, config, *join): def search_query(self, term, config, *join):
strip_whitespaces(term).lower() strip_whitespaces(term).lower()
self.session.connection().connection.connection.create_function("lower", 1, lcase) self.create_functions()
# self.session.connection().connection.connection.create_function("lower", 1, lcase)
q = list() q = list()
author_terms = re.split("[, ]+", term) author_terms = re.split("[, ]+", term)
for author_term in author_terms: for author_term in author_terms:
@ -1018,7 +1022,7 @@ class CalibreDB:
lang.name = isoLanguages.get_language_name(get_locale(), lang.lang_code) lang.name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
return sorted(languages, key=lambda x: x.name, reverse=reverse_order) return sorted(languages, key=lambda x: x.name, reverse=reverse_order)
def update_title_sort(self, config, conn=None): def create_functions(self, config=None):
# user defined sort function for calibre databases (Series, etc.) # user defined sort function for calibre databases (Series, etc.)
def _title_sort(title): def _title_sort(title):
# calibre sort stuff # calibre sort stuff
@ -1031,12 +1035,15 @@ class CalibreDB:
try: try:
# sqlalchemy <1.4.24 and sqlalchemy 2.0 # sqlalchemy <1.4.24 and sqlalchemy 2.0
conn = conn or self.session.connection().connection.driver_connection conn = self.session.connection().connection.driver_connection
except AttributeError: except AttributeError:
# sqlalchemy >1.4.24 # sqlalchemy >1.4.24
conn = conn or self.session.connection().connection.connection conn = self.session.connection().connection.connection
try: try:
conn.create_function("title_sort", 1, _title_sort) if config:
conn.create_function("title_sort", 1, _title_sort)
conn.create_function('uuid4', 0, lambda: str(uuid4()))
conn.create_function("lower", 1, lcase)
except sqliteOperationalError: except sqliteOperationalError:
pass pass

View File

@ -24,7 +24,7 @@ import os
from datetime import datetime, timezone from datetime import datetime, timezone
import json import json
from shutil import copyfile from shutil import copyfile
from uuid import uuid4
from markupsafe import escape, Markup # dependency of flask from markupsafe import escape, Markup # dependency of flask
from functools import wraps from functools import wraps
@ -97,135 +97,7 @@ def show_edit_book(book_id):
@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 return do_edit_book(book_id)
edit_error = False
# create the function for sorting...
calibre_db.update_title_sort(config)
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
# Book not found
if not book:
flash(_("Oops! Selected book is unavailable. File does not exist or is not accessible"),
category="error")
return redirect(url_for("web.index"))
to_save = request.form.to_dict()
try:
# Update folder of book on local disk
edited_books_id = None
title_author_error = None
# handle book title change
title_change = handle_title_on_edit(book, to_save["book_title"])
# handle book author change
input_authors, author_change = 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.get_book_path(),
input_authors[0])
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 covers from local disk
cover_upload_success = upload_cover(request, book)
if cover_upload_success:
book.has_cover = 1
modify_date = True
meta ={}
# upload new covers or new file formats to google drive
if config.config_use_google_drive:
gdriveutils.updateGdriveCalibreFromLocal()
if to_save.get("cover_url", None):
if not current_user.role_upload():
edit_error = True
flash(_("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"].strip(), book.path)
if result is True:
book.has_cover = 1
modify_date = True
helper.replace_cover_thumbnail_cache(book.id)
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
try:
modify_date |= edit_book_languages(to_save['languages'], book)
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.get("pubdate", None):
try:
book.pubdate = datetime.strptime(to_save["pubdate"], "%Y-%m-%d")
except ValueError as e:
book.pubdate = db.Books.DEFAULT_PUBDATE
flash(str(e), category="error")
edit_error = True
else:
book.pubdate = db.Books.DEFAULT_PUBDATE
if modify_date:
book.last_modified = datetime.now(timezone.utc)
kobo_sync_status.remove_synced_book(edited_books_id, all=True)
calibre_db.set_metadata_dirty(book.id)
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:
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))
except (OperationalError, IntegrityError, StaleDataError, InterfaceError) as e:
log.error_or_exception("Database error: {}".format(e))
calibre_db.session.rollback()
flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e), category="error")
return redirect(url_for('web.show_book', book_id=book.id))
except Exception as ex:
log.error_or_exception(ex)
calibre_db.session.rollback()
flash(_("Error editing book: {}".format(ex)), category="error")
return redirect(url_for('web.show_book', book_id=book.id))
@editbook.route("/upload", methods=["POST"]) @editbook.route("/upload", methods=["POST"])
@ -233,41 +105,14 @@ def edit_book(book_id):
@upload_required @upload_required
def upload(): def upload():
if len(request.files.getlist("btn-upload-format")): if len(request.files.getlist("btn-upload-format")):
# create the function for sorting...
calibre_db.update_title_sort(config)
book_id = request.form.get('book_id', -1) book_id = request.form.get('book_id', -1)
return do_edit_book(book_id, request.files.getlist("btn-upload-format"))
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
# Book not found
if not book:
flash(_("Oops! Selected book is unavailable. File does not exist or is not accessible"),
category="error")
return redirect(url_for("web.index"))
# handle upload other formats from local disk
for requested_file in request.files.getlist("btn-upload-format"):
meta = upload_single_file(requested_file, book, book_id)
# save data to database, reread data
calibre_db.session.commit()
resp = {"location": url_for('edit-book.show_edit_book', book_id=book_id)}
return Response(json.dumps(resp), mimetype='application/json')
# only merge metadata if file was uploaded and no error occurred (meta equals not false or none)
elif len(request.files.getlist("btn-upload")): elif len(request.files.getlist("btn-upload")):
for requested_file in request.files.getlist("btn-upload"): for requested_file in request.files.getlist("btn-upload"):
try: try:
modify_date = False modify_date = False
# create the function for sorting... # create the function for sorting...
calibre_db.update_title_sort(config) calibre_db.create_functions(config)
try:
# sqlalchemy 2.0
uuid_func = calibre_db.session.connection().connection.driver_connection
except AttributeError:
uuid_func = calibre_db.session.connection().connection.connection
uuid_func.create_function('uuid4', 0,lambda: str(uuid4()))
meta, error = file_handling_on_upload(requested_file) meta, error = file_handling_on_upload(requested_file)
if error: if error:
return error return error
@ -592,22 +437,163 @@ def table_xchange_author_title():
return "" return ""
def do_edit_book(book_id, upload_formats=None):
modify_date = False
edit_error = False
# create the function for sorting...
calibre_db.create_functions(config)
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
# Book not found
if not book:
flash(_("Oops! Selected book is unavailable. File does not exist or is not accessible"),
category="error")
return redirect(url_for("web.index"))
to_save = request.form.to_dict()
try:
# Update folder of book on local disk
edited_books_id = None
title_author_error = None
upload_format = False
# handle book title change
if "book_title" in to_save:
title_change = handle_title_on_edit(book, to_save["book_title"])
# handle book author change
if not upload_formats:
input_authors, author_change = 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.get_book_path(),
input_authors[0])
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 book ratings
modify_date |= edit_book_ratings(to_save, book)
meta = True
else:
# handle upload other formats from local disk
meta = upload_single_file(upload_formats, book, book_id)
# only merge metadata if file was uploaded and no error occurred (meta equals not false or none)
if meta:
upload_format = 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()
if to_save.get("cover_url", None):
if not current_user.role_upload():
edit_error = True
flash(_("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"].strip(), book.path)
if result is True:
book.has_cover = 1
modify_date = True
helper.replace_cover_thumbnail_cache(book.id)
else:
flash(error, category="error")
# Add default series_index to book
modify_date |= edit_book_series_index(to_save.get("series_index"), book)
# Handle book comments/description
modify_date |= edit_book_comments(Markup(to_save.get('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.get('tags'), book)
# Handle book series
modify_date |= edit_book_series(to_save.get("series"), book)
# handle book publisher
modify_date |= edit_book_publisher(to_save.get('publisher'), book)
# handle book languages
try:
modify_date |= edit_book_languages(to_save.get('languages'), book, upload_format)
except ValueError as e:
flash(str(e), category="error")
edit_error = True
# handle cc data
modify_date |= edit_all_cc_data(book_id, book, to_save)
if to_save.get("pubdate") is not None:
if to_save.get("pubdate"):
try:
book.pubdate = datetime.strptime(to_save["pubdate"], "%Y-%m-%d")
except ValueError as e:
book.pubdate = db.Books.DEFAULT_PUBDATE
flash(str(e), category="error")
edit_error = True
else:
book.pubdate = db.Books.DEFAULT_PUBDATE
if modify_date:
book.last_modified = datetime.now(timezone.utc)
kobo_sync_status.remove_synced_book(edited_books_id, all=True)
calibre_db.set_metadata_dirty(book.id)
calibre_db.session.merge(book)
calibre_db.session.commit()
if config.config_use_google_drive:
gdriveutils.updateGdriveCalibreFromLocal()
if upload_formats:
resp = {"location": url_for('edit-book.show_edit_book', book_id=book_id)}
return Response(json.dumps(resp), mimetype='application/json')
# if meta is not False \
if 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:
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))
except (OperationalError, IntegrityError, StaleDataError, InterfaceError) as e:
log.error_or_exception("Database error: {}".format(e))
calibre_db.session.rollback()
flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e), category="error")
return redirect(url_for('web.show_book', book_id=book.id))
except Exception as ex:
log.error_or_exception(ex)
calibre_db.session.rollback()
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): def merge_metadata(to_save, meta):
if to_save.get('author_name', "") == _('Unknown'): if not to_save.get("languages") and meta.languages:
to_save['author_name'] = ''
if to_save.get('book_title', "") == _('Unknown'):
to_save['book_title'] = ''
if not to_save["languages"] and meta.languages:
upload_language = True upload_language = True
else: else:
upload_language = False upload_language = False
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'),
('series_index', 'series_id'), ('languages', 'languages'), ('series_index', 'series_id'), ('languages', 'languages'),
('book_title', 'title')]: ('book_title', 'title'), ('description', 'description'),]:
to_save[s_field] = to_save[s_field] or getattr(meta, m_field, '') val = getattr(meta, m_field, '')
to_save["description"] = to_save["description"] or Markup( if val:
getattr(meta, 'description', '')).unescape() to_save[s_field] = val
return upload_language return upload_language
def identifier_list(to_save, book): def identifier_list(to_save, book):
@ -1019,84 +1005,93 @@ def edit_book_ratings(to_save, book):
def edit_book_tags(tags, book): def edit_book_tags(tags, book):
input_tags = tags.split(',') if tags:
input_tags = list(map(lambda it: strip_whitespaces(it), input_tags)) input_tags = tags.split(',')
# Remove duplicates input_tags = list(map(lambda it: strip_whitespaces(it), input_tags))
input_tags = helper.uniq(input_tags) # Remove duplicates
return modify_database_object(input_tags, book.tags, db.Tags, calibre_db.session, 'tags') input_tags = helper.uniq(input_tags)
return modify_database_object(input_tags, book.tags, db.Tags, calibre_db.session, 'tags')
return False
def edit_book_series(series, book): def edit_book_series(series, book):
input_series = [strip_whitespaces(series)] if series:
input_series = [x for x in input_series if x != ''] input_series = [strip_whitespaces(series)]
return modify_database_object(input_series, book.series, db.Series, calibre_db.session, 'series') input_series = [x for x in input_series if x != '']
return modify_database_object(input_series, book.series, db.Series, calibre_db.session, 'series')
return False
def edit_book_series_index(series_index, book): def edit_book_series_index(series_index, book):
# Add default series_index to book if series_index:
modify_date = False # Add default series_index to book
series_index = series_index or '1' modify_date = False
if not series_index.replace('.', '', 1).isdigit(): series_index = series_index or '1'
flash(_("Seriesindex: %(seriesindex)s is not a valid number, skipping", seriesindex=series_index), category="warning") if not series_index.replace('.', '', 1).isdigit():
return False flash(_("Seriesindex: %(seriesindex)s is not a valid number, skipping", seriesindex=series_index), category="warning")
if str(book.series_index) != series_index: return False
book.series_index = series_index if str(book.series_index) != series_index:
modify_date = True book.series_index = series_index
return modify_date modify_date = True
return modify_date
return False
# Handle book comments/description # Handle book comments/description
def edit_book_comments(comments, book): def edit_book_comments(comments, book):
modify_date = False if comments is not None:
if comments: modify_date = False
comments = clean_string(comments, book.id)
if len(book.comments):
if book.comments[0].text != comments:
book.comments[0].text = comments
modify_date = True
else:
if comments: if comments:
book.comments.append(db.Comments(comment=comments, book=book.id)) comments = clean_string(comments, book.id)
modify_date = True if len(book.comments):
return modify_date if book.comments[0].text != comments:
book.comments[0].text = comments
modify_date = True
else:
if comments:
book.comments.append(db.Comments(comment=comments, book=book.id))
modify_date = True
return modify_date
def edit_book_languages(languages, book, upload_mode=False, invalid=None): def edit_book_languages(languages, book, upload_mode=False, invalid=None):
input_languages = languages.split(',') if languages:
unknown_languages = [] input_languages = languages.split(',')
if not upload_mode: unknown_languages = []
input_l = isoLanguages.get_language_codes(get_locale(), input_languages, unknown_languages) if not upload_mode:
else: input_l = isoLanguages.get_language_codes(get_locale(), input_languages, unknown_languages)
input_l = isoLanguages.get_valid_language_codes(get_locale(), input_languages, unknown_languages)
for lang in unknown_languages:
log.error("'%s' is not a valid language", lang)
if isinstance(invalid, list):
invalid.append(lang)
else: else:
raise ValueError(_("'%(langname)s' is not a valid language", langname=lang)) input_l = isoLanguages.get_valid_language_codes(get_locale(), input_languages, unknown_languages)
# ToDo: Not working correct for lang in unknown_languages:
if upload_mode and len(input_l) == 1: log.error("'%s' is not a valid language", lang)
# If the language of the file is excluded from the users view, it's not imported, to allow the user to view if isinstance(invalid, list):
# the book it's language is set to the filter language invalid.append(lang)
if input_l[0] != current_user.filter_language() and current_user.filter_language() != "all": else:
input_l[0] = calibre_db.session.query(db.Languages). \ raise ValueError(_("'%(langname)s' is not a valid language", langname=lang))
filter(db.Languages.lang_code == current_user.filter_language()).first().lang_code # ToDo: Not working correct
# Remove duplicates if upload_mode and len(input_l) == 1:
input_l = helper.uniq(input_l) # If the language of the file is excluded from the users view, it's not imported, to allow the user to view
return modify_database_object(input_l, book.languages, db.Languages, calibre_db.session, 'languages') # the book it's language is set to the filter language
if input_l[0] != current_user.filter_language() and current_user.filter_language() != "all":
input_l[0] = calibre_db.session.query(db.Languages). \
filter(db.Languages.lang_code == current_user.filter_language()).first().lang_code
# Remove duplicates
input_l = helper.uniq(input_l)
return modify_database_object(input_l, book.languages, db.Languages, calibre_db.session, 'languages')
return False
def edit_book_publisher(publishers, book): def edit_book_publisher(publishers, book):
changed = False
if publishers: if publishers:
publisher = strip_whitespaces(publishers) changed = False
if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name): if publishers:
changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session, publisher = strip_whitespaces(publishers)
'publisher') if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name):
elif len(book.publishers): changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session,
changed |= modify_database_object([], book.publishers, db.Publishers, calibre_db.session, 'publisher') 'publisher')
return changed elif len(book.publishers):
changed |= modify_database_object([], book.publishers, db.Publishers, calibre_db.session, 'publisher')
return changed
return False
def edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string): def edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string):
changed = False changed = False
@ -1177,61 +1172,68 @@ def edit_cc_data(book_id, book, to_save, cc):
changed = False changed = False
for c in cc: for c in cc:
cc_string = "custom_column_" + str(c.id) cc_string = "custom_column_" + str(c.id)
if not c.is_multiple: if to_save.get(cc_string):
if len(getattr(book, cc_string)) > 0: if not c.is_multiple:
cc_db_value = getattr(book, cc_string)[0].value if len(getattr(book, cc_string)) > 0:
else: cc_db_value = getattr(book, cc_string)[0].value
cc_db_value = None
if strip_whitespaces(to_save[cc_string]):
if c.datatype in ['int', 'bool', 'float', "datetime", "comments"]:
change, to_save = edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string)
else: else:
change, to_save = edit_cc_data_string(book, c, to_save, cc_db_value, cc_string) cc_db_value = None
changed |= change if strip_whitespaces(to_save[cc_string]):
if c.datatype in ['int', 'bool', 'float', "datetime", "comments"]:
change, to_save = edit_cc_data_value(book_id, book, c, to_save, cc_db_value, cc_string)
else:
change, to_save = edit_cc_data_string(book, c, to_save, cc_db_value, cc_string)
changed |= change
else:
if cc_db_value is not None:
# remove old cc_val
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
if not del_cc.books or len(del_cc.books) == 0:
calibre_db.session.delete(del_cc)
changed = True
else: else:
if cc_db_value is not None: input_tags = to_save[cc_string].split(',')
# remove old cc_val input_tags = list(map(lambda it: strip_whitespaces(it), input_tags))
del_cc = getattr(book, cc_string)[0] changed |= modify_database_object(input_tags,
getattr(book, cc_string).remove(del_cc) getattr(book, cc_string),
if not del_cc.books or len(del_cc.books) == 0: db.cc_classes[c.id],
calibre_db.session.delete(del_cc) calibre_db.session,
changed = True 'custom')
else:
input_tags = to_save[cc_string].split(',')
input_tags = list(map(lambda it: strip_whitespaces(it), input_tags))
changed |= modify_database_object(input_tags,
getattr(book, cc_string),
db.cc_classes[c.id],
calibre_db.session,
'custom')
return changed return changed
# returns None if no file is uploaded # returns None if no file is uploaded
# returns False if an error occurs, in all other cases the ebook metadata is returned # returns False if an error occurs, in all other cases the ebook metadata is returned
def upload_single_file(requested_file, book, book_id): def upload_single_file(requested_files, book, book_id):
# Check and handle Uploaded file # Check and handle Uploaded file
# requested_file = file_request.files.get('btn-upload-format', None) # requested_file = file_request.files.get('btn-upload-format', None)
# ToDo: Handle multiple files
meta = {}
allowed_extensions = config.config_upload_formats.split(',') allowed_extensions = config.config_upload_formats.split(',')
if requested_file: for requested_file in requested_files:
if config.config_check_extensions and allowed_extensions != ['']: if config.config_check_extensions and allowed_extensions != ['']:
if not validate_mime_type(requested_file, allowed_extensions): if not validate_mime_type(requested_file, allowed_extensions):
flash(_("File type isn't allowed to be uploaded to this server"), category="error") flash(_("File type isn't allowed to be uploaded to this server"), category="error")
return False continue
# return False
# 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():
flash(_("User has no rights to upload additional file formats"), category="error") flash(_("User has no rights to upload additional file formats"), category="error")
return False continue
# 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 allowed_extensions and '' not in allowed_extensions: if file_ext not in allowed_extensions and '' not in allowed_extensions:
flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext), flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext),
category="error") category="error")
return False continue
# 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 False continue
# return False
file_name = book.path.rsplit('/', 1)[-1] file_name = book.path.rsplit('/', 1)[-1]
filepath = os.path.normpath(os.path.join(config.get_book_path(), book.path)) filepath = os.path.normpath(os.path.join(config.get_book_path(), book.path))
@ -1244,12 +1246,14 @@ def upload_single_file(requested_file, book, book_id):
except OSError: except OSError:
flash(_("Failed to create path %(path)s (Permission denied).", path=filepath), flash(_("Failed to create path %(path)s (Permission denied).", path=filepath),
category="error") category="error")
return False continue
# return False
try: try:
requested_file.save(saved_filename) requested_file.save(saved_filename)
except OSError: except OSError:
flash(_("Failed to store file %(file)s.", file=saved_filename), category="error") flash(_("Failed to store file %(file)s.", file=saved_filename), category="error")
return False continue
# 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())
@ -1262,13 +1266,14 @@ def upload_single_file(requested_file, book, book_id):
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name) db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
calibre_db.session.add(db_format) calibre_db.session.add(db_format)
calibre_db.session.commit() calibre_db.session.commit()
calibre_db.update_title_sort(config) calibre_db.create_functions(config)
except (OperationalError, IntegrityError, StaleDataError) as e: except (OperationalError, IntegrityError, StaleDataError) as e:
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(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e), flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e),
category="error") category="error")
return False # return redirect(url_for('web.show_book', book_id=book.id)) continue
# 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))
@ -1278,7 +1283,7 @@ def upload_single_file(requested_file, 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),
rar_executable=config.config_rarfile_location) rar_executable=config.config_rarfile_location)
return None return meta if len(meta) else False
def upload_cover(cover_request, book): def upload_cover(cover_request, book):

View File

@ -328,7 +328,7 @@ def edit_book_read_status(book_id, read_status=None):
ub.session_commit("Book {} readbit toggled".format(book_id)) ub.session_commit("Book {} readbit toggled".format(book_id))
else: else:
try: try:
calibre_db.update_title_sort(config) calibre_db.create_functions(config)
book = calibre_db.get_filtered_book(book_id) book = calibre_db.get_filtered_book(book_id)
book_read_status = getattr(book, 'custom_column_' + str(config.config_read_column)) book_read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
if len(book_read_status): if len(book_read_status):

View File

@ -244,7 +244,8 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
pagination = None pagination = None
cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase) calibre_db.create_functions()
# calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
query = calibre_db.generate_linked_query(config.config_read_column, db.Books) query = calibre_db.generate_linked_query(config.config_read_column, db.Books)
q = query.outerjoin(db.books_series_link, db.Books.id == db.books_series_link.c.book)\ q = query.outerjoin(db.books_series_link, db.Books.id == db.books_series_link.c.book)\
.outerjoin(db.Series)\ .outerjoin(db.Series)\

View File

@ -130,7 +130,7 @@ $(".container-fluid").bind('drop', function (e) {
} }
}); });
if (dt.files.length) { if (dt.files.length) {
if($("btn-upload-format").length) { if($("#btn-upload-format").length) {
$("#btn-upload-format")[0].files = dt.files; $("#btn-upload-format")[0].files = dt.files;
$("#form-upload-format").submit(); $("#form-upload-format").submit();
} else { } else {

View File

@ -299,7 +299,8 @@ def get_languages_json():
def get_matching_tags(): def get_matching_tags():
tag_dict = {'tags': []} tag_dict = {'tags': []}
q = calibre_db.session.query(db.Books).filter(calibre_db.common_filters(True)) q = calibre_db.session.query(db.Books).filter(calibre_db.common_filters(True))
calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase) calibre_db.create_functions()
# calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
author_input = request.args.get('author_name') or '' author_input = request.args.get('author_name') or ''
title_input = request.args.get('book_title') or '' title_input = request.args.get('book_title') or ''
include_tag_inputs = request.args.getlist('include_tag') or '' include_tag_inputs = request.args.getlist('include_tag') or ''