mirror of
https://github.com/janeczku/calibre-web
synced 2024-12-18 06:00:32 +00:00
Upload (multiple) book formats with progress and merge the corresponding metadata into the book
This commit is contained in:
parent
d8d9d405e9
commit
1eb77c9cd4
@ -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")
|
||||||
|
23
cps/db.py
23
cps/db.py
@ -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
|
||||||
|
|
||||||
|
533
cps/editbooks.py
533
cps/editbooks.py
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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)\
|
||||||
|
@ -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 {
|
||||||
|
@ -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 ''
|
||||||
|
Loading…
Reference in New Issue
Block a user