Content server: Allow adding or removing formats to a book via the edit metadata page. Fixes #1831304 [Request: Delete Formats on Content Server](https://bugs.launchpad.net/calibre/+bug/1831304)

This commit is contained in:
Kovid Goyal 2019-07-10 10:41:22 +05:30
parent 46afc39739
commit b0d18ffb41
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 98 additions and 2 deletions

View File

@ -18,8 +18,9 @@ from calibre.srv.routes import endpoint, json, msgpack_or_json
from calibre.srv.utils import get_db, get_library_data from calibre.srv.utils import get_db, get_library_data
from calibre.utils.imghdr import what from calibre.utils.imghdr import what
from calibre.utils.serialize import MSGPACK_MIME, json_loads, msgpack_loads from calibre.utils.serialize import MSGPACK_MIME, json_loads, msgpack_loads
from polyglot.builtins import iteritems from calibre.utils.speedups import ReadOnlyFileBuffer
from polyglot.binary import from_base64_bytes from polyglot.binary import from_base64_bytes
from polyglot.builtins import iteritems
receive_data_methods = {'GET', 'POST'} receive_data_methods = {'GET', 'POST'}
@ -171,6 +172,25 @@ def cdb_set_fields(ctx, rd, book_id, library_id):
raise HTTPBadRequest('Cover data must be either JPEG or PNG') raise HTTPBadRequest('Cover data must be either JPEG or PNG')
dirtied |= db.set_cover({book_id: cdata}) dirtied |= db.set_cover({book_id: cdata})
added_formats = changes.pop('added_formats', False)
if added_formats:
for data in added_formats:
try:
fmt = data['ext'].upper()
except Exception:
raise HTTPBadRequest('Format has no extension')
if fmt:
try:
fmt_data = from_base64_bytes(data['data_url'].split(',', 1)[-1])
except Exception:
raise HTTPBadRequest('Format data is not valid base64 encoded data')
if db.add_format(book_id, fmt, ReadOnlyFileBuffer(fmt_data)):
dirtied.add(book_id)
removed_formats = changes.pop('removed_formats', False)
if removed_formats:
db.remove_formats({book_id: list(removed_formats)})
dirtied.add(book_id)
for field, value in iteritems(changes): for field, value in iteritems(changes):
dirtied |= db.set_field(field, {book_id: value}) dirtied |= db.set_field(field, {book_id: value})
ctx.notify_changes(db.backend.library_path, metadata(dirtied)) ctx.notify_changes(db.backend.library_path, metadata(dirtied))

View File

@ -36,7 +36,7 @@ from utils import (
from widgets import create_button from widgets import create_button
CLASS_NAME = 'edit-metadata-panel' CLASS_NAME = 'edit-metadata-panel'
IGNORED_FIELDS = {'formats', 'sort', 'uuid', 'id', 'urls_from_identifiers', 'lang_names', 'last_modified', 'path', 'marked', 'size', 'ondevice', 'cover', 'au_map', 'isbn'} IGNORED_FIELDS = {'sort', 'uuid', 'id', 'urls_from_identifiers', 'lang_names', 'last_modified', 'path', 'marked', 'size', 'ondevice', 'cover', 'au_map', 'isbn'}
def identity(x): def identity(x):
return x return x
value_to_json = identity value_to_json = identity
@ -69,6 +69,22 @@ def resolved_metadata(mi, field):
return mi[field] return mi[field]
def resolved_formats(val):
val = list(val or v'[]')
if changes.added_formats:
for data in changes.added_formats:
ext = data.ext.toUpperCase()
if ext and ext not in val:
val.push(ext)
if changes.removed_formats:
for fmt in changes.removed_formats:
fmt = fmt.toUpperCase()
if fmt in val:
val.remove(fmt)
val.sort()
return val
def truncated_html(val): def truncated_html(val):
ans = val.replace(/<[^>]+>/g, '') ans = val.replace(/<[^>]+>/g, '')
if ans.length > 40: if ans.length > 40:
@ -574,6 +590,54 @@ def cover_edit(container_id, book_id, field, fm, div, mi):
# }}} # }}}
# Formats edit {{{
def format_added(top_container_id, book_id, container_id, files):
nonlocal has_changes
container = document.getElementById(container_id)
if not container:
return
if not files[0]:
return
added = changes.added_formats or v'[]'
for file in files:
ext = file.name.rpartition('.')[-1]
data = {'name': file.name, 'size': file.size, 'type': file.type, 'data_url': None, 'ext': ext}
added.push(data)
r = FileReader()
r.onload = def(evt):
data.data_url = evt.target.result
r.readAsDataURL(file)
changes.added_formats = added
has_changes = True
on_close(top_container_id)
def remove_format(top_container_id, book_id, fmt):
nonlocal has_changes
has_changes = True
removed_formats = changes.removed_formats or v'[]'
removed_formats.push(fmt.toUpperCase())
changes.removed_formats = removed_formats
on_close(top_container_id)
def formats_edit(container_id, book_id, field, fm, div, mi):
upload_files_widget(div, format_added.bind(None, container_id, book_id), _(
'Add a format by <a>selecting the book file</a> or drag and drop of the book file here.'),
single_file=True)
remove_buttons = E.div(style='padding: 1rem; display: flex; flex-wrap: wrap; align-content: flex-start')
formats = resolved_formats(mi.formats)
for i, fmt in enumerate(formats):
remove_buttons.appendChild(create_button(
_('Remove {}').format(fmt.upper()), action=remove_format.bind(None, container_id, book_id, fmt.upper())))
remove_buttons.lastChild.style.marginBottom = '1ex'
remove_buttons.lastChild.style.marginRight = '1rem'
div.appendChild(remove_buttons)
# }}}
def edit_field(container_id, book_id, field): def edit_field(container_id, book_id, field):
nonlocal value_to_json nonlocal value_to_json
fm = library_data.field_metadata[field] fm = library_data.field_metadata[field]
@ -593,6 +657,8 @@ def edit_field(container_id, book_id, field):
multiple_line_edit(' & ', '&', container_id, book_id, field, fm, d, mi) multiple_line_edit(' & ', '&', container_id, book_id, field, fm, d, mi)
elif field is 'cover': elif field is 'cover':
cover_edit(container_id, book_id, field, fm, d, mi) cover_edit(container_id, book_id, field, fm, d, mi)
elif field is 'formats':
formats_edit(container_id, book_id, field, fm, d, mi)
elif fm.datatype is 'series': elif fm.datatype is 'series':
series_edit(container_id, book_id, field, fm, d, mi) series_edit(container_id, book_id, field, fm, d, mi)
elif fm.datatype is 'datetime': elif fm.datatype is 'datetime':
@ -765,6 +831,14 @@ def render_metadata(mi, table, container_id, book_id): # {{{
else: else:
add_row(name, None) add_row(name, None)
def process_formats(field, fm, name, val):
val = resolved_formats(val)
if val.length:
join = fm.is_multiple.list_to_ui if fm.is_multiple else None
add_row(name, val, join=join)
else:
add_row(name, None)
def process_field(field, fm): def process_field(field, fm):
name = fm.name or field name = fm.name or field
datatype = fm.datatype datatype = fm.datatype
@ -785,6 +859,8 @@ def render_metadata(mi, table, container_id, book_id): # {{{
func = process_publisher func = process_publisher
elif field is 'languages': elif field is 'languages':
func = process_languages func = process_languages
elif field is 'formats':
func = process_formats
elif datatype is 'datetime': elif datatype is 'datetime':
func = process_datetime func = process_datetime
elif datatype is 'series': elif datatype is 'series':