Backup metadata 4th step

This commit is contained in:
Ozzie Isaacs 2022-09-19 22:39:40 +02:00
parent 26be5ee237
commit c10708ed07
7 changed files with 85 additions and 37 deletions

View File

@ -136,28 +136,38 @@ def admin_forbidden():
@admin_required @admin_required
def shutdown(): def shutdown():
task = request.get_json().get('parameter', -1) task = request.get_json().get('parameter', -1)
showtext = {} show_text = {}
if task in (0, 1): # valid commandos received if task in (0, 1): # valid commandos received
# close all database connections # close all database connections
calibre_db.dispose() calibre_db.dispose()
ub.dispose() ub.dispose()
if task == 0: if task == 0:
showtext['text'] = _(u'Server restarted, please reload page') show_text['text'] = _(u'Server restarted, please reload page')
else: else:
showtext['text'] = _(u'Performing shutdown of server, please close window') show_text['text'] = _(u'Performing shutdown of server, please close window')
# stop gevent/tornado server # stop gevent/tornado server
web_server.stop(task == 0) web_server.stop(task == 0)
return json.dumps(showtext) return json.dumps(show_text)
if task == 2: if task == 2:
log.warning("reconnecting to calibre database") log.warning("reconnecting to calibre database")
calibre_db.reconnect_db(config, ub.app_DB_path) calibre_db.reconnect_db(config, ub.app_DB_path)
showtext['text'] = _(u'Reconnect successful') show_text['text'] = _(u'Reconnect successful')
return json.dumps(showtext) return json.dumps(show_text)
showtext['text'] = _(u'Unknown command') show_text['text'] = _(u'Unknown command')
return json.dumps(showtext), 400 return json.dumps(show_text), 400
@admi.route("/metadata_backup", methods=["POST"])
@login_required
@admin_required
def queue_metadata_backup():
show_text = {}
log.warning("Queuing all books for metadata backup")
helper.set_all_metadata_dirty()
show_text['text'] = _(u'Books successfully queued fo Metadata Backup')
return json.dumps(show_text)
# method is available without login and not protected by CSRF to make it easy reachable, is per default switched off # method is available without login and not protected by CSRF to make it easy reachable, is per default switched off

View File

@ -399,7 +399,7 @@ class CustomColumns(Base):
display_dict = json.loads(self.display) display_dict = json.loads(self.display)
return display_dict return display_dict
def to_json(self, value, extra): def to_json(self, value, extra, sequence):
content = dict() content = dict()
content['table'] = "custom_column_" + str(self.id) content['table'] = "custom_column_" + str(self.id)
content['column'] = "value" content['column'] = "value"
@ -417,7 +417,7 @@ class CustomColumns(Base):
content['category_sort'] = "value" content['category_sort'] = "value"
content['is_csp'] = False content['is_csp'] = False
content['is_editable'] = self.editable content['is_editable'] = self.editable
content['rec_index'] = self.id + 22 # toDo why ?? content['rec_index'] = sequence + 22 # toDo why ??
content['#value#'] = value content['#value#'] = value
content['#extra#'] = extra content['#extra#'] = extra
content['is_multiple2'] = {} content['is_multiple2'] = {}

View File

@ -1037,6 +1037,7 @@ def update_thumbnail_cache():
def set_all_metadata_dirty(): def set_all_metadata_dirty():
WorkerThread.add(None, TaskBackupMetadata(export_language=get_locale(), WorkerThread.add(None, TaskBackupMetadata(export_language=get_locale(),
translated_title=_("cover"), translated_title=_("Cover"),
set_dirty=True), set_dirty=True,
task_message=N_("Queue all books for metadata backup")),
hidden=False) hidden=False)

View File

@ -34,7 +34,7 @@ def get_scheduled_tasks(reconnect=True):
# ToDo make configurable. Generate metadata.opf file for each changed book # ToDo make configurable. Generate metadata.opf file for each changed book
if True: if True:
tasks.append([lambda: TaskBackupMetadata(), "en", 'backup metadata', False]) tasks.append([lambda: TaskBackupMetadata("en"), 'backup metadata', False])
# Generate all missing book cover thumbnails # Generate all missing book cover thumbnails
if config.schedule_generate_book_covers: if config.schedule_generate_book_covers:

View File

@ -503,6 +503,23 @@ $(function() {
} }
}); });
}); });
$("#metadata_backup").click(function() {
$("#DialogHeader").addClass("hidden");
$("#DialogFinished").addClass("hidden");
$("#DialogContent").html("");
$("#spinner2").show();
$.ajax({
method: "post",
contentType: "application/json; charset=utf-8",
dataType: "json",
url: getPath() + "/metadata_backup",
success: function success(data) {
$("#spinner2").hide();
$("#DialogContent").html(data.text);
$("#DialogFinished").removeClass("hidden");
}
});
});
$("#perform_update").click(function() { $("#perform_update").click(function() {
$("#DialogHeader").removeClass("hidden"); $("#DialogHeader").removeClass("hidden");
$("#spinner2").show(); $("#spinner2").show();

View File

@ -42,7 +42,7 @@ NSMAP = {'dc': PURL_NAMESPACE, 'opf': OPF_NAMESPACE}
class TaskBackupMetadata(CalibreTask): class TaskBackupMetadata(CalibreTask):
def __init__(self, export_language="en", def __init__(self, export_language="en",
translated_title="cover", translated_title="Cover",
set_dirty=False, set_dirty=False,
task_message=N_('Backing up Metadata')): task_message=N_('Backing up Metadata')):
super(TaskBackupMetadata, self).__init__(task_message) super(TaskBackupMetadata, self).__init__(task_message)
@ -50,7 +50,7 @@ class TaskBackupMetadata(CalibreTask):
self.calibre_db = db.CalibreDB(expire_on_commit=False, init=True) self.calibre_db = db.CalibreDB(expire_on_commit=False, init=True)
self.export_language = export_language self.export_language = export_language
self.translated_title = translated_title self.translated_title = translated_title
self.set_dirty=set_dirty self.set_dirty = set_dirty
def run(self, worker_thread): def run(self, worker_thread):
if self.set_dirty: if self.set_dirty:
@ -62,7 +62,8 @@ class TaskBackupMetadata(CalibreTask):
try: try:
books = self.calibre_db.session.query(db.Books).all() books = self.calibre_db.session.query(db.Books).all()
for book in books: for book in books:
self.calibre_db.set_metadata_dirty(book) self.calibre_db.set_metadata_dirty(book.id)
self.calibre_db.session.commit()
self._handleSuccess() self._handleSuccess()
except Exception as ex: except Exception as ex:
self.log.debug('Error adding book for backup: ' + str(ex)) self.log.debug('Error adding book for backup: ' + str(ex))
@ -74,21 +75,25 @@ class TaskBackupMetadata(CalibreTask):
try: try:
metadata_backup = self.calibre_db.session.query(db.Metadata_Dirtied).all() metadata_backup = self.calibre_db.session.query(db.Metadata_Dirtied).all()
custom_columns = self.calibre_db.session.query(db.CustomColumns).order_by(db.CustomColumns.label).all() custom_columns = self.calibre_db.session.query(db.CustomColumns).order_by(db.CustomColumns.label).all()
count = len(metadata_backup)
i = 0
for backup in metadata_backup: for backup in metadata_backup:
book = self.calibre_db.session.query(db.Books).filter(db.Books.id == backup.book).one_or_none() book = self.calibre_db.session.query(db.Books).filter(db.Books.id == backup.book).one_or_none()
self.calibre_db.session.query(db.Metadata_Dirtied).filter(db.Metadata_Dirtied == backup.id).delete() self.calibre_db.session.query(db.Metadata_Dirtied).filter(
db.Metadata_Dirtied.book == backup.book).delete()
self.calibre_db.session.commit() self.calibre_db.session.commit()
if book: if book:
self.open_metadata(book, custom_columns) self.open_metadata(book, custom_columns)
self._handleSuccess()
self.calibre_db.session.close()
else: else:
self.log.error("Book {} not found in database".format(backup.book)) self.log.error("Book {} not found in database".format(backup.book))
self._handleError("Book {} not found in database".format(backup.book)) # self._handleError("Book {} not found in database".format(backup.book))
i += 1
self.progress = (1.0 / count) * i
self._handleSuccess()
self.calibre_db.session.close() self.calibre_db.session.close()
except Exception as ex: except Exception as ex:
self.log.debug('Error creating metadata backup: ' + str(ex)) self.log.debug('Error creating metadata backup for book {}: '.format(book.id) + str(ex))
self._handleError('Error creating metadata backup: ' + str(ex)) self._handleError('Error creating metadata backup: ' + str(ex))
self.calibre_db.session.rollback() self.calibre_db.session.rollback()
self.calibre_db.session.close() self.calibre_db.session.close()
@ -155,35 +160,36 @@ class TaskBackupMetadata(CalibreTask):
title.text = book.title title.text = book.title
for author in book.authors: for author in book.authors:
creator = etree.SubElement(metadata, PURL + "creator", nsmap=NSMAP) creator = etree.SubElement(metadata, PURL + "creator", nsmap=NSMAP)
creator.text = str(author) creator.text = str(author.name)
creator.set(OPF + "file-as", book.author_sort) # ToDo Check creator.set(OPF + "file-as", book.author_sort) # ToDo Check
creator.set(OPF + "role", "aut") creator.set(OPF + "role", "aut")
contributor = etree.SubElement(metadata, PURL + "contributor", nsmap=NSMAP) contributor = etree.SubElement(metadata, PURL + "contributor", nsmap=NSMAP)
contributor.text = "calibre (5.7.2) [https://calibre-ebook.com]" contributor.text = "calibre (5.7.2) [https://calibre-ebook.com]"
contributor.set(OPF + "file-as", "calibre") # ToDo Check contributor.set(OPF + "file-as", "calibre") # ToDo Check
contributor.set(OPF + "role", "bpk") contributor.set(OPF + "role", "bkp")
date = etree.SubElement(metadata, PURL + "date", nsmap=NSMAP) date = etree.SubElement(metadata, PURL + "date", nsmap=NSMAP)
date.text = '{d.year:04}-{d.month:02}-{d.day:02}T{d.hour:02}:{d.minute:02}:{d.second:02}'.format(d=book.pubdate) date.text = '{d.year:04}-{d.month:02}-{d.day:02}T{d.hour:02}:{d.minute:02}:{d.second:02}'.format(d=book.pubdate)
if book.comments:
for b in book.comments:
description = etree.SubElement(metadata, PURL + "description", nsmap=NSMAP)
description.text = b.text
if not book.languages: if not book.languages:
language = etree.SubElement(metadata, PURL + "language", nsmap=NSMAP) language = etree.SubElement(metadata, PURL + "language", nsmap=NSMAP)
language.text = self.export_language language.text = self.export_language
else: else:
for b in book.languages: for b in book.languages:
language = etree.SubElement(metadata, PURL + "language", nsmap=NSMAP) language = etree.SubElement(metadata, PURL + "language", nsmap=NSMAP)
language.text = str(b.languages) language.text = str(b.lang_code)
for b in book.tags: for b in book.tags:
subject = etree.SubElement(metadata, PURL + "subject", nsmap=NSMAP) subject = etree.SubElement(metadata, PURL + "subject", nsmap=NSMAP)
subject.text = str(b.tags) subject.text = str(b.name)
if book.comments:
description = etree.SubElement(metadata, PURL + "description", nsmap=NSMAP)
description.text = escape(str(book.comments))
etree.SubElement(metadata, "meta", name="calibre:author_link_map", etree.SubElement(metadata, "meta", name="calibre:author_link_map",
content="{" + escape(",".join(['"' + str(a) + '":""' for a in book.authors])) + "}", content="{" + ", ".join(['"' + str(a.name) + '": ""' for a in book.authors]) + "}",
nsmap=NSMAP) nsmap=NSMAP)
for b in book.series:
etree.SubElement(metadata, "meta", name="calibre:series", etree.SubElement(metadata, "meta", name="calibre:series",
content=str(book.series), content=str(str(b.name)),
nsmap=NSMAP) nsmap=NSMAP)
etree.SubElement(metadata, "meta", name="calibre:series_index", etree.SubElement(metadata, "meta", name="calibre:series_index",
content=str(book.series_index), content=str(book.series_index),
@ -195,6 +201,7 @@ class TaskBackupMetadata(CalibreTask):
etree.SubElement(metadata, "meta", name="calibre:title_sort", etree.SubElement(metadata, "meta", name="calibre:title_sort",
content=book.sort, content=book.sort,
nsmap=NSMAP) nsmap=NSMAP)
sequence = 0
for cc in custom_columns: for cc in custom_columns:
value = None value = None
extra = None extra = None
@ -203,8 +210,9 @@ class TaskBackupMetadata(CalibreTask):
value = cc_entry[0].get("value") value = cc_entry[0].get("value")
extra = cc_entry[0].get("extra") extra = cc_entry[0].get("extra")
etree.SubElement(metadata, "meta", name="calibre:user_metadata:#{}".format(cc.label), etree.SubElement(metadata, "meta", name="calibre:user_metadata:#{}".format(cc.label),
content=escape(cc.to_json(value, extra)), content=cc.to_json(value, extra, sequence),
nsmap=NSMAP) nsmap=NSMAP)
sequence += 1
# generate guide element and all sub elements of it # generate guide element and all sub elements of it
# Title is translated from default export language # Title is translated from default export language
@ -213,15 +221,24 @@ class TaskBackupMetadata(CalibreTask):
# prepare finalize everything and output # prepare finalize everything and output
doc = etree.ElementTree(package) doc = etree.ElementTree(package)
# doc = etree.tostring(package, xml_declaration=True, encoding='utf-8', pretty_print=True) # .replace(b""", b""")
try: try:
with open(book_metadata_filepath, 'wb') as f: with open(book_metadata_filepath, 'wb') as f:
# f.write(doc)
doc.write(f, xml_declaration=True, encoding='utf-8', pretty_print=True) doc.write(f, xml_declaration=True, encoding='utf-8', pretty_print=True)
except Exception: except Exception:
# ToDo: Folder not writeable error # ToDo: Folder not writeable error
pass pass
@property @property
def name(self): def name(self):
return "Backing up Metadata" return "Metadata backup"
# needed for logging
def __str__(self):
if self.set_dirty:
return "Queue all books for metadata backup"
else:
return "Perform metadata backup"
@property @property
def is_cancellable(self): def is_cancellable(self):

View File

@ -207,6 +207,9 @@
<div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart')}}</div> <div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart')}}</div>
<div class="btn btn-default" id="admin_stop" data-toggle="modal" data-target="#ShutdownDialog">{{_('Shutdown')}}</div> <div class="btn btn-default" id="admin_stop" data-toggle="modal" data-target="#ShutdownDialog">{{_('Shutdown')}}</div>
</div> </div>
<div class="row form-group">
<div class="btn btn-default" id="metadata_backup" data-toggle="modal" data-target="#StatusDialog">{{_('Queue all books for metadata backup')}}</div>
</div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">