From 72fbe75bec3f29cf9dcb485640c84d8de59752da Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 30 Jun 2018 08:18:27 +0530 Subject: [PATCH] In-server conversion basically works, phew! Now to create the UI for conversion settings. --- src/calibre/db/cache.py | 11 ++++++----- src/calibre/db/errors.py | 6 ++++++ src/calibre/srv/convert.py | 30 +++++++++++++++++------------- src/pyj/book_list/convert_book.pyj | 9 +++++---- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index dd202191f5..e2a0563723 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -20,7 +20,7 @@ from calibre.customize.ui import run_plugins_on_import, run_plugins_on_postimpor from calibre.db import SPOOL_SIZE, _get_next_series_num_for_list from calibre.db.categories import get_categories from calibre.db.locking import create_locks, DowngradeLockError, SafeReadLock -from calibre.db.errors import NoSuchFormat +from calibre.db.errors import NoSuchFormat, NoSuchBook from calibre.db.fields import create_field, IDENTITY, InvalidLinkTable from calibre.db.search import Search from calibre.db.tables import VirtualTable @@ -1340,10 +1340,9 @@ class Cache(object): user_mi = mi.get_all_user_metadata(make_copy=False) fm = self.field_metadata for key in user_mi.iterkeys(): - if (key in fm and - user_mi[key]['datatype'] == fm[key]['datatype'] and - (user_mi[key]['datatype'] != 'text' or - user_mi[key]['is_multiple'] == fm[key]['is_multiple'])): + if (key in fm and user_mi[key]['datatype'] == fm[key]['datatype'] and ( + user_mi[key]['datatype'] != 'text' or ( + user_mi[key]['is_multiple'] == fm[key]['is_multiple']))): val = mi.get(key, None) if force_changes or val is not None: protected_set_field(key, val) @@ -1397,6 +1396,8 @@ class Cache(object): fmt = check_ebook_format(stream_or_path, fmt) with self.write_lock: + if not self._has_id(book_id): + raise NoSuchBook(book_id) fmt = (fmt or '').upper() self.format_metadata_cache[book_id].pop(fmt, None) try: diff --git a/src/calibre/db/errors.py b/src/calibre/db/errors.py index 621372bb44..7ee2ea6e6f 100644 --- a/src/calibre/db/errors.py +++ b/src/calibre/db/errors.py @@ -11,3 +11,9 @@ __docformat__ = 'restructuredtext en' class NoSuchFormat(ValueError): pass + +class NoSuchBook(KeyError): + + def __init__(self, book_id): + KeyError.__init__(self, 'No book with id: {} in database'.format(book_id)) + self.book_id = book_id diff --git a/src/calibre/srv/convert.py b/src/calibre/srv/convert.py index 066917dbec..5858963722 100644 --- a/src/calibre/srv/convert.py +++ b/src/calibre/srv/convert.py @@ -9,6 +9,7 @@ import shutil import tempfile from threading import Lock +from calibre.db.errors import NoSuchBook from calibre.srv.changes import formats_added from calibre.srv.errors import BookNotFound, HTTPNotFound from calibre.srv.routes import endpoint, json @@ -25,7 +26,7 @@ class JobStatus(object): def __init__(self, job_id, book_id, tdir, library_id, pathtoebook, conversion_data): self.job_id = job_id - self.log = '' + self.log = self.traceback = '' self.book_id = book_id self.output_path = os.path.join( tdir, 'output.' + conversion_data['output_fmt'].lower()) @@ -34,9 +35,11 @@ class JobStatus(object): self.conversion_data = conversion_data self.running = self.ok = True self.last_check_at = monotonic() + self.was_aborted = False def cleanup(self): safe_delete_tree(self.tdir) + self.log = self.traceback = '' @property def current_status(self): @@ -106,7 +109,7 @@ def convert_book(path_to_ebook, opf_path, cover_path, output_fmt, recs): os.chdir(os.path.dirname(path_to_ebook)) status_file = share_open('status', 'wb') - def notification(percent, msg): + def notification(percent, msg=''): status_file.write('{}:{}|||\n'.format(percent, msg).encode('utf-8')) status_file.flush() @@ -129,11 +132,11 @@ def queue_job(ctx, rd, library_id, db, fmt, book_id, conversion_data): fd, pathtocover = tempfile.mkstemp(prefix='', suffix=('.jpg'), dir=tdir) with os.fdopen(fd, 'wb') as f: cover_copied = db.copy_cover_to(book_id, f) - cover_path = f.name if cover_copied else None + cover_path = pathtocover if cover_copied else None mi = db.get_metadata(book_id) mi.application_id = mi.uuid raw = metadata_to_opf(mi) - fd, pathtocover = tempfile.mkstemp(prefix='', suffix=('.opf'), dir=tdir) + fd, opf_path = tempfile.mkstemp(prefix='', suffix=('.opf'), dir=tdir) with os.fdopen(fd, 'wb') as metadata_file: metadata_file.write(raw) @@ -146,7 +149,7 @@ def queue_job(ctx, rd, library_id, db, fmt, book_id, conversion_data): job_id = ctx.start_job( 'Convert book %s (%s)' % (book_id, fmt), 'calibre.srv.convert', 'convert_book', args=( - pathtoebook, metadata_file.name, cover_path, conversion_data['output_fmt'], recs), + pathtoebook, opf_path, cover_path, conversion_data['output_fmt'], recs), job_done_callback=job_done ) expire_old_jobs() @@ -181,19 +184,20 @@ def conversion_status(ctx, rd, job_id): del conversion_jobs[job_id] try: - ans = {'running': False, 'ok': job_status.ok, 'was_aborted': job_status.was_aborted, - 'traceback': job_status.traceback, 'log': job_status.log} + ans = {'running': False, 'ok': job_status.ok, 'was_aborted': + job_status.was_aborted, 'traceback': job_status.traceback, + 'log': job_status.log} if job_status.ok: db, library_id = get_library_data(ctx, rd)[:2] if library_id != job_status.library_id: raise HTTPNotFound('job library_id does not match') - with db.safe_read_lock: - if not db.has_id(job_status.book_id): - raise HTTPNotFound( - 'book_id {} not found in library'.format(job_status.book_id)) - fmt = job_status.output_path.rpartition('.')[-1] + fmt = job_status.output_path.rpartition('.')[-1] + try: db.add_format(job_status.book_id, fmt, job_status.output_path) - formats_added({job_status.book_id: (fmt,)}) + except NoSuchBook: + raise HTTPNotFound( + 'book_id {} not found in library'.format(job_status.book_id)) + formats_added({job_status.book_id: (fmt,)}) ans['size'] = os.path.getsize(job_status.output_path) ans['fmt'] = fmt return ans diff --git a/src/pyj/book_list/convert_book.pyj b/src/pyj/book_list/convert_book.pyj index b6a45492ee..483792d736 100644 --- a/src/pyj/book_list/convert_book.pyj +++ b/src/pyj/book_list/convert_book.pyj @@ -66,7 +66,7 @@ def report_conversion_ajax_failure(xhr): nonlocal current_state current_state = 'configuring' apply_state_to_markup() - error_dialog(_('Failed to start conversion'), _( + error_dialog(_('Failed to convert'), _( 'Could not convert {}. Click "Show details" for more' ' information').format(conversion_data.title), xhr.error_html @@ -104,14 +104,15 @@ def on_conversion_status(end_type, xhr, ev): if response.running: c = container_for_current_state() c.querySelector('progress').value = response.percent - c.querySelector('.progress-msg').textContent = response.msg + if response.msg: + c.querySelector('.progress-msg').textContent = response.msg check_for_conversion_status() else: if response.ok: - current_state = 'converted' - apply_state_to_markup() conversion_data.fmt = response.fmt conversion_data.size = response.size + current_state = 'converted' + apply_state_to_markup() else: show_failure(response) else: