Allow changing cover on the EM page

This commit is contained in:
Kovid Goyal 2018-03-14 22:53:36 +05:30
parent 4c01e9eb65
commit 0e63bcc709
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 94 additions and 9 deletions

View File

@ -5,6 +5,7 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import os
from base64 import standard_b64decode
from functools import partial
from io import BytesIO
@ -13,10 +14,11 @@ from calibre.db.cli import module_for_cmd
from calibre.ebooks.metadata.meta import get_metadata
from calibre.srv.changes import books_added, books_deleted, metadata
from calibre.srv.errors import HTTPBadRequest, HTTPForbidden, HTTPNotFound
from calibre.srv.metadata import book_as_json
from calibre.srv.routes import endpoint, json, msgpack_or_json
from calibre.srv.utils import get_db, get_library_data
from calibre.utils.imghdr import what
from calibre.utils.serialize import MSGPACK_MIME, json_loads, msgpack_loads
from calibre.srv.metadata import book_as_json
receive_data_methods = {'GET', 'POST'}
@ -110,6 +112,17 @@ def cdb_delete_book(ctx, rd, book_ids, library_id):
return {}
@endpoint('/cdb/set-cover/{book_id}/{library_id=None}', types={'book_id': int},
needs_db_write=True, postprocess=json, methods=receive_data_methods, cache_control='no-cache')
def cdb_set_cover(ctx, rd, book_id, library_id):
db = get_db(ctx, rd, library_id)
if ctx.restriction_for(rd, db):
raise HTTPForbidden('Cannot use the add book interface with a user who has per library restrictions')
rd.request_body_file.seek(0)
dirtied = db.set_cover({book_id: rd.request_body_file})
return tuple(dirtied)
@endpoint('/cdb/set-fields/{book_id}/{library_id=None}', types={'book_id': int},
needs_db_write=True, postprocess=msgpack_or_json, methods=receive_data_methods, cache_control='no-cache')
def cdb_set_fields(ctx, rd, book_id, library_id):
@ -130,6 +143,21 @@ def cdb_set_fields(ctx, rd, book_id, library_id):
except Exception:
raise HTTPBadRequest('Invalid encoded data')
dirtied = set()
cdata = changes.pop('cover', False)
if cdata is not False:
if cdata is not None:
try:
cdata = standard_b64decode(cdata.split(',', 1)[-1].encode('ascii'))
except Exception:
raise HTTPBadRequest('Cover data is not valid base64 encoded data')
try:
fmt = what(None, cdata)
except Exception:
fmt = None
if fmt not in ('jpeg', 'png'):
raise HTTPBadRequest('Cover data must be either JPEG or PNG')
dirtied |= db.set_cover({book_id: cdata})
for field, value in changes.iteritems():
dirtied |= db.set_field(field, {book_id: value})
metadata(dirtied)

View File

@ -12,8 +12,8 @@ from book_list.book_details import (
report_load_failure
)
from book_list.library_data import (
book_metadata, current_library_id, field_names_for, library_data, load_status,
loaded_book_ids, set_book_metadata
book_metadata, cover_url, current_library_id, field_names_for, library_data,
load_status, loaded_book_ids, set_book_metadata
)
from book_list.router import back
from book_list.theme import get_color
@ -21,6 +21,9 @@ from book_list.top_bar import create_top_bar, set_title
from book_list.ui import set_panel_handler, show_panel
from date import UNDEFINED_DATE_ISO, format_date
from dom import add_extra_css, build_rule, clear, svgicon
from file_uploads import (
update_status_widget, upload_files_widget, upload_status_widget
)
from modals import error_dialog
from session import get_interface_data
from utils import (
@ -474,6 +477,29 @@ def bool_edit(container_id, book_id, field, fm, div, mi):
return val_map[x]
# }}}
# Cover edit {{{
def cover_chosen(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
file = files[0]
changes.cover = file
has_changes = True
on_close(top_container_id)
cover_chosen.counter = 0
def cover_edit(container_id, book_id, field, fm, div, mi):
upload_files_widget(div, cover_chosen.bind(None, container_id, book_id), _(
'Change the cover by <a>selecting the cover image</a> or drag and drop of the cover image here.'),
single_file=True, accept_extensions='png jpeg jpg')
# }}}
def edit_field(container_id, book_id, field):
nonlocal value_to_json
fm = library_data.field_metadata[field]
@ -491,6 +517,8 @@ def edit_field(container_id, book_id, field):
update_completions.prefix = ''
if field is 'authors':
multiple_line_edit(' & ', '&', container_id, book_id, field, fm, d, mi)
elif field is 'cover':
cover_edit(container_id, book_id, field, fm, d, mi)
elif fm.datatype is 'series':
series_edit(container_id, book_id, field, fm, d, mi)
elif fm.datatype is 'datetime':
@ -710,6 +738,21 @@ def render_metadata(mi, table, container_id, book_id): # {{{
print('Failed to render metadata field: ' + field)
traceback.print_exc()
current_edit_action = edit_field.bind(None, container_id, book_id, 'cover')
table.appendChild(E.tr(onclick=current_edit_action, E.td(_('Cover') + ':'), E.td()))
img = E.img(
style='max-width: 300px; max-height: 400px',
)
if changes.cover:
r = FileReader()
r.onload = def(evt):
img.src = evt.target.result
changes.cover = evt.target.result
r.readAsDataURL(changes.cover)
v'delete changes.cover'
else:
img.src = cover_url(book_id)
table.lastChild.lastChild.appendChild(img)
# }}}
@ -735,14 +778,22 @@ def changes_submitted(container_id, book_id, end_type, xhr, ev):
on_close(container_id)
def on_progress(container_id, book_id, loaded, total, xhr):
container = document.getElementById(container_id)
if container and total:
update_status_widget(container, loaded, total)
def submit_changes(container_id, book_id):
c = document.getElementById(container_id)
d = c.querySelector('div[data-ctype="show"]')
clear(d)
d.appendChild(E.div(style='margin: 1ex 1rem', _('Uploading changes to server, please wait...')))
data = {'changes': changes, 'loaded_book_ids': loaded_book_ids()}
w = upload_status_widget()
d.appendChild(w)
ajax_send(
f'cdb/set-fields/{book_id}/{current_library_id()}', data, changes_submitted.bind(None, container_id, book_id))
f'cdb/set-fields/{book_id}/{current_library_id()}', data, changes_submitted.bind(None, container_id, book_id), on_progress.bind(None, container_id, book_id))
def show_book(container_id, book_id):

View File

@ -5,11 +5,12 @@ from __python__ import bound_methods, hash_literals
from elementmaker import E
from gettext import gettext as _
from utils import safe_set_inner_html, human_readable
from dom import ensure_id
from utils import human_readable, safe_set_inner_html
def upload_files_widget(container, proceed, msg):
container_id = container.id
def upload_files_widget(container, proceed, msg, single_file=False, accept_extensions=None):
container_id = ensure_id(container, 'upload-files')
def files_selected():
files = this.files
@ -19,7 +20,11 @@ def upload_files_widget(container, proceed, msg):
proceed(container_id, files)
msg = msg or _('Upload books by <a>selecting the book files</a> or drag and drop of the files here')
c = E.div(E.span(), E.input(type='file', multiple=True, style='display:none', onchange=files_selected))
c = E.div(E.span(), E.input(type='file', style='display:none', onchange=files_selected))
if not single_file:
c.lastChild.setAttribute('multiple', 'multiple')
if accept_extensions:
c.lastChild.setAttribute('accept', ', '.join(['.' + x for x in accept_extensions.split(' ')]))
c.style.minHeight = '80vh'
c.style.padding = '1rem'
c.style.borderBottom = 'solid 1px currentColor'
@ -58,7 +63,8 @@ def update_status_widget(w, sent, total):
def upload_status_widget(name, job_id):
ans = E.div(style='padding: 1rem 1ex;', data_job='' + job_id)
ans.appendChild(E.h3(E.b(name)))
if name:
ans.appendChild(E.h3(E.b(name)))
ans.appendChild(E.progress())
ans.appendChild(E.span())
return ans