mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
More work on in-server conversion
This commit is contained in:
parent
4a2a20e550
commit
256c509238
@ -9,6 +9,7 @@ import shutil
|
|||||||
import tempfile
|
import tempfile
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
|
from calibre.srv.changes import formats_added
|
||||||
from calibre.srv.errors import BookNotFound, HTTPNotFound
|
from calibre.srv.errors import BookNotFound, HTTPNotFound
|
||||||
from calibre.srv.routes import endpoint, json
|
from calibre.srv.routes import endpoint, json
|
||||||
from calibre.srv.utils import get_library_data
|
from calibre.srv.utils import get_library_data
|
||||||
@ -166,7 +167,7 @@ def start_conversion(ctx, rd, book_id):
|
|||||||
return job_id
|
return job_id
|
||||||
|
|
||||||
|
|
||||||
@endpoint('/conversion/status/{job_id}', postprocess=json, needs_db_write=True, types={'job_id': int})
|
@endpoint('/conversion/status/{job_id}', postprocess=json, needs_db_write=True, types={'job_id': int}, methods=receive_data_methods)
|
||||||
def conversion_status(ctx, rd, job_id):
|
def conversion_status(ctx, rd, job_id):
|
||||||
with cache_lock:
|
with cache_lock:
|
||||||
job_status = conversion_jobs.get(job_id)
|
job_status = conversion_jobs.get(job_id)
|
||||||
@ -190,8 +191,11 @@ def conversion_status(ctx, rd, job_id):
|
|||||||
if not db.has_id(job_status.book_id):
|
if not db.has_id(job_status.book_id):
|
||||||
raise HTTPNotFound(
|
raise HTTPNotFound(
|
||||||
'book_id {} not found in library'.format(job_status.book_id))
|
'book_id {} not found in library'.format(job_status.book_id))
|
||||||
db.add_format(job_status.book_id, job_status.output_path.rpartition(
|
fmt = job_status.output_path.rpartition('.')[-1]
|
||||||
'.')[-1], job_status.output_path)
|
db.add_format(job_status.book_id, fmt, job_status.output_path)
|
||||||
|
formats_added({job_status.book_id: (fmt,)})
|
||||||
|
ans['size'] = os.path.getsize(job_status.output_path)
|
||||||
|
ans['fmt'] = fmt
|
||||||
return ans
|
return ans
|
||||||
finally:
|
finally:
|
||||||
job_status.cleanup()
|
job_status.cleanup()
|
||||||
|
@ -7,12 +7,13 @@ from gettext import gettext as _
|
|||||||
|
|
||||||
from ajax import ajax, ajax_send
|
from ajax import ajax, ajax_send
|
||||||
from book_list.book_details import report_load_failure
|
from book_list.book_details import report_load_failure
|
||||||
from book_list.library_data import load_status, url_books_query
|
from book_list.library_data import download_url, load_status, url_books_query
|
||||||
from book_list.router import back, report_a_load_failure
|
from book_list.router import back, open_book, report_a_load_failure
|
||||||
from book_list.top_bar import create_top_bar, set_title
|
from book_list.top_bar import create_top_bar, set_title
|
||||||
from book_list.ui import set_panel_handler
|
from book_list.ui import set_panel_handler
|
||||||
from dom import add_extra_css, build_rule, clear
|
from dom import add_extra_css, build_rule, clear
|
||||||
from utils import conditional_timeout, parse_url_params
|
from modals import error_dialog
|
||||||
|
from utils import conditional_timeout, human_readable, parse_url_params
|
||||||
from widgets import create_button
|
from widgets import create_button
|
||||||
|
|
||||||
CLASS_NAME = 'convert-book-panel'
|
CLASS_NAME = 'convert-book-panel'
|
||||||
@ -22,7 +23,8 @@ initializers = {}
|
|||||||
|
|
||||||
add_extra_css(def():
|
add_extra_css(def():
|
||||||
sel = '.' + CLASS_NAME + ' '
|
sel = '.' + CLASS_NAME + ' '
|
||||||
style = build_rule(sel, placeholder='TODO: add this')
|
style = build_rule(sel, padding='1ex 1rem')
|
||||||
|
style += build_rule(sel + 'h3', margin_bottom='1ex')
|
||||||
return style
|
return style
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,15 +39,122 @@ def container_for_current_state():
|
|||||||
return ans.querySelector(f'[data-state="{current_state}"]')
|
return ans.querySelector(f'[data-state="{current_state}"]')
|
||||||
|
|
||||||
|
|
||||||
|
def create_converted_markup():
|
||||||
|
|
||||||
|
def init(container):
|
||||||
|
clear(container)
|
||||||
|
container.appendChild(E.h3('Conversion successful!'))
|
||||||
|
fmt = conversion_data.fmt.toUpperCase()
|
||||||
|
book_id = int(conversion_data.book_id)
|
||||||
|
|
||||||
|
def read_book():
|
||||||
|
open_book(book_id, fmt)
|
||||||
|
|
||||||
|
container.appendChild(E.div(
|
||||||
|
style='margin-top: 1rem',
|
||||||
|
create_button(_('Read {}').format(fmt), 'book', read_book),
|
||||||
|
'\xa0',
|
||||||
|
create_button(_('Download {}').format(fmt), 'cloud-download', download_url(book_id, fmt),
|
||||||
|
_('File size: {}').format(human_readable(conversion_data.size)),
|
||||||
|
download_filename=f'{conversion_data.title}.{fmt.toLowerCase()}')
|
||||||
|
))
|
||||||
|
|
||||||
|
return E.div(), init
|
||||||
|
|
||||||
|
|
||||||
|
def report_conversion_ajax_failure(xhr):
|
||||||
|
nonlocal current_state
|
||||||
|
current_state = 'configuring'
|
||||||
|
apply_state_to_markup()
|
||||||
|
error_dialog(_('Failed to start conversion'), _(
|
||||||
|
'Could not convert {}. Click "Show details" for more'
|
||||||
|
' information').format(conversion_data.title),
|
||||||
|
xhr.error_html
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def show_failure(response):
|
||||||
|
nonlocal current_state
|
||||||
|
current_state = 'failed'
|
||||||
|
apply_state_to_markup()
|
||||||
|
c = container_for_current_state()
|
||||||
|
clear(c)
|
||||||
|
c.appendChild(E.h3(_('Conversion failed!')))
|
||||||
|
c.appendChild(E.div(_('Error details below...')))
|
||||||
|
c.appendChild(E.div('\xa0'))
|
||||||
|
c.appendChild(E.div())
|
||||||
|
c = c.lastChild
|
||||||
|
if response.was_aborted:
|
||||||
|
c.textContent = _(
|
||||||
|
'Conversion of {} was taking too long, and has been aborted').format(conversion_data.title)
|
||||||
|
else:
|
||||||
|
log = ''
|
||||||
|
if response.traceback:
|
||||||
|
log += response.traceback
|
||||||
|
if response.log:
|
||||||
|
log += '\n\n' + _('Conversion log')
|
||||||
|
log += response.log
|
||||||
|
c.appendChild(E.pre(log))
|
||||||
|
|
||||||
|
|
||||||
|
def on_conversion_status(end_type, xhr, ev):
|
||||||
|
nonlocal current_state
|
||||||
|
if end_type is 'load':
|
||||||
|
response = JSON.parse(xhr.responseText)
|
||||||
|
if response.running:
|
||||||
|
c = container_for_current_state()
|
||||||
|
c.querySelector('progress').value = response.percent
|
||||||
|
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
|
||||||
|
else:
|
||||||
|
show_failure(response)
|
||||||
|
else:
|
||||||
|
report_conversion_ajax_failure(xhr)
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_conversion_status():
|
||||||
|
query = url_books_query()
|
||||||
|
data = {}
|
||||||
|
ajax_send(f'/conversion/status/{conversion_data.job_id}', data, on_conversion_status, query=query)
|
||||||
|
|
||||||
|
|
||||||
def on_conversion_started(end_type, xhr, ev):
|
def on_conversion_started(end_type, xhr, ev):
|
||||||
pass
|
nonlocal current_state
|
||||||
|
if end_type is 'load':
|
||||||
|
conversion_data.job_id = JSON.parse(xhr.responseText)
|
||||||
|
check_for_conversion_status()
|
||||||
|
else:
|
||||||
|
report_conversion_ajax_failure(xhr)
|
||||||
|
|
||||||
|
|
||||||
def get_conversion_options(container):
|
def get_conversion_options(container):
|
||||||
return conversion_data.conversion_options.options
|
return conversion_data.conversion_options.options
|
||||||
|
|
||||||
|
|
||||||
|
def create_converting_markup():
|
||||||
|
ans = E.div(
|
||||||
|
E.div(
|
||||||
|
style='text-align: center; margin: auto',
|
||||||
|
_('Converting, please wait...'),
|
||||||
|
E.div(E.progress()),
|
||||||
|
E.div('\xa0', class_='progress-msg'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def init(container):
|
||||||
|
container.querySelector('progress').removeAttribute('value')
|
||||||
|
|
||||||
|
return ans, init
|
||||||
|
|
||||||
|
|
||||||
def start_conversion():
|
def start_conversion():
|
||||||
|
nonlocal current_state
|
||||||
container = document.getElementById(overall_container_id)
|
container = document.getElementById(overall_container_id)
|
||||||
data = {
|
data = {
|
||||||
'input_fmt': container.querySelector('select[name="input_formats"]').value,
|
'input_fmt': container.querySelector('select[name="input_formats"]').value,
|
||||||
@ -55,18 +164,20 @@ def start_conversion():
|
|||||||
}
|
}
|
||||||
query = url_books_query()
|
query = url_books_query()
|
||||||
ajax_send(f'/conversion/start/{conversion_data.book_id}', data, on_conversion_started, query=query)
|
ajax_send(f'/conversion/start/{conversion_data.book_id}', data, on_conversion_started, query=query)
|
||||||
|
current_state = 'converting'
|
||||||
|
apply_state_to_markup()
|
||||||
|
|
||||||
|
|
||||||
def create_configuring_markup():
|
def create_configuring_markup():
|
||||||
ignore_changes = False
|
ignore_changes = False
|
||||||
ans = E.div(class_='top')
|
ans = E.div()
|
||||||
|
|
||||||
def on_format_change():
|
def on_format_change():
|
||||||
nonlocal ignore_changes, current_state
|
nonlocal ignore_changes, current_state
|
||||||
if ignore_changes:
|
if ignore_changes:
|
||||||
return
|
return
|
||||||
input_fmt = container_for_current_state().querySelector('select[name="input_formats"').value
|
input_fmt = container_for_current_state().querySelector('select[name="input_formats"]').value
|
||||||
output_fmt = container_for_current_state().querySelector('select[name="output_formats"').value
|
output_fmt = container_for_current_state().querySelector('select[name="output_formats"]').value
|
||||||
current_state = 'initializing'
|
current_state = 'initializing'
|
||||||
conditional_timeout(overall_container_id, 5, check_for_data_loaded)
|
conditional_timeout(overall_container_id, 5, check_for_data_loaded)
|
||||||
q = parse_url_params()
|
q = parse_url_params()
|
||||||
@ -180,18 +291,27 @@ def check_for_data_loaded():
|
|||||||
|
|
||||||
def create_markup(container):
|
def create_markup(container):
|
||||||
container.appendChild(E.div(
|
container.appendChild(E.div(
|
||||||
data_state='initializing', style='margin: 1ex 1em',
|
data_state='initializing',
|
||||||
E.div(_('Loading conversion data, please wait...'))
|
E.div(_('Loading conversion data, please wait...'))
|
||||||
))
|
))
|
||||||
container.appendChild(E.div(
|
container.appendChild(E.div(data_state='load-failure'))
|
||||||
data_state='load-failure', style='margin: 1ex 1em',
|
container.appendChild(E.div(data_state='failed'))
|
||||||
))
|
|
||||||
|
|
||||||
ccm, init = create_configuring_markup()
|
ccm, init = create_configuring_markup()
|
||||||
ccm.dataset.state = 'configuring'
|
ccm.dataset.state = 'configuring'
|
||||||
container.appendChild(ccm)
|
container.appendChild(ccm)
|
||||||
initializers[ccm.dataset.state] = init
|
initializers[ccm.dataset.state] = init
|
||||||
|
|
||||||
|
conv, init = create_converting_markup()
|
||||||
|
conv.dataset.state = 'converting'
|
||||||
|
container.appendChild(conv)
|
||||||
|
initializers[conv.dataset.state] = init
|
||||||
|
|
||||||
|
conv, init = create_converted_markup()
|
||||||
|
conv.dataset.state = 'converted'
|
||||||
|
container.appendChild(conv)
|
||||||
|
initializers[conv.dataset.state] = init
|
||||||
|
|
||||||
|
|
||||||
def apply_state_to_markup():
|
def apply_state_to_markup():
|
||||||
container = document.getElementById(overall_container_id)
|
container = document.getElementById(overall_container_id)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user