mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Use shared memory for IPC with toc editor
This commit is contained in:
parent
54fd6080fc
commit
3155a36b4b
@ -6,6 +6,7 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from itertools import count
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon, QLabel, QTimer
|
QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon, QLabel, QTimer
|
||||||
@ -100,6 +101,7 @@ class ToCEditAction(InterfaceAction):
|
|||||||
self.do_edit(book_id_map)
|
self.do_edit(book_id_map)
|
||||||
|
|
||||||
def genesis(self):
|
def genesis(self):
|
||||||
|
self.shm_count = count()
|
||||||
self.qaction.triggered.connect(self.edit_books)
|
self.qaction.triggered.connect(self.edit_books)
|
||||||
self.jobs = []
|
self.jobs = []
|
||||||
|
|
||||||
@ -146,34 +148,51 @@ class ToCEditAction(InterfaceAction):
|
|||||||
self.do_one(book_id, fmt)
|
self.do_one(book_id, fmt)
|
||||||
|
|
||||||
def do_one(self, book_id, fmt):
|
def do_one(self, book_id, fmt):
|
||||||
|
import struct, json, atexit
|
||||||
|
from calibre.utils.shm import SharedMemory
|
||||||
db = self.gui.current_db
|
db = self.gui.current_db
|
||||||
path = db.format(book_id, fmt, index_is_id=True, as_path=True)
|
path = db.format(book_id, fmt, index_is_id=True, as_path=True)
|
||||||
title = db.title(book_id, index_is_id=True) + ' [%s]'%fmt
|
title = db.title(book_id, index_is_id=True) + ' [%s]'%fmt
|
||||||
data = {'path': path, 'title': title}
|
job = {'path': path, 'title': title}
|
||||||
self.gui.job_manager.launch_gui_app('toc-dialog', kwargs=data)
|
data = json.dumps(job).encode('utf-8')
|
||||||
job = data.copy()
|
header = struct.pack('>II', 0, 0)
|
||||||
job.update({'book_id': book_id, 'fmt': fmt, 'library_id': db.new_api.library_id, 'started': False, 'start_time': monotonic()})
|
shm = SharedMemory(prefix=f'c{os.getpid()}-{next(self.shm_count)}-', size=len(data) + len(header) + SharedMemory.num_bytes_for_size)
|
||||||
|
shm.write(header)
|
||||||
|
shm.write_data_with_size(data)
|
||||||
|
shm.flush()
|
||||||
|
atexit.register(shm.close)
|
||||||
|
self.gui.job_manager.launch_gui_app('toc-dialog', kwargs={'shm_name': shm.name})
|
||||||
|
job.update({
|
||||||
|
'book_id': book_id, 'fmt': fmt, 'library_id': db.new_api.library_id, 'shm': shm, 'started': False, 'start_time': monotonic()})
|
||||||
self.jobs.append(job)
|
self.jobs.append(job)
|
||||||
self.check_for_completions()
|
self.check_for_completions()
|
||||||
|
|
||||||
def check_for_completions(self):
|
def check_for_completions(self):
|
||||||
from calibre.utils.filenames import retry_on_fail
|
import struct
|
||||||
|
|
||||||
|
def remove_job(job):
|
||||||
|
job['shm'].close()
|
||||||
|
self.jobs.remove(job)
|
||||||
|
|
||||||
for job in tuple(self.jobs):
|
for job in tuple(self.jobs):
|
||||||
path = job['path']
|
path = job['path']
|
||||||
started_path = path + '.started'
|
shm = job['shm']
|
||||||
result_path = path + '.result'
|
shm.seek(0)
|
||||||
if job['started'] and os.path.exists(result_path):
|
state, ok = struct.unpack('>II', shm.read(struct.calcsize('>II')))
|
||||||
self.jobs.remove(job)
|
if state == 0:
|
||||||
ret = -1
|
# not started
|
||||||
|
if monotonic() - job['start_time'] > 120:
|
||||||
def read(result_path):
|
remove_job(job)
|
||||||
nonlocal ret
|
error_dialog(self.gui, _('Failed to start editor'), _(
|
||||||
with open(result_path) as f:
|
'Could not edit: {}. The Table of Contents editor did not start in two minutes').format(job['title']), show=True)
|
||||||
ret = int(f.read().strip())
|
elif state == 1:
|
||||||
|
# running
|
||||||
retry_on_fail(read, result_path)
|
pass
|
||||||
retry_on_fail(os.remove, result_path)
|
elif state == 2:
|
||||||
if ret == 0:
|
# finished
|
||||||
|
job['shm'].already_unlinked = True
|
||||||
|
remove_job(job)
|
||||||
|
if ok == 1:
|
||||||
db = self.gui.current_db
|
db = self.gui.current_db
|
||||||
if db.new_api.library_id != job['library_id']:
|
if db.new_api.library_id != job['library_id']:
|
||||||
error_dialog(self.gui, _('Library changed'), _(
|
error_dialog(self.gui, _('Library changed'), _(
|
||||||
@ -181,16 +200,6 @@ class ToCEditAction(InterfaceAction):
|
|||||||
' the calibre library has changed.').format(job['title']), show=True)
|
' the calibre library has changed.').format(job['title']), show=True)
|
||||||
else:
|
else:
|
||||||
db.new_api.add_format(job['book_id'], job['fmt'], path, run_hooks=False)
|
db.new_api.add_format(job['book_id'], job['fmt'], path, run_hooks=False)
|
||||||
retry_on_fail(os.remove, path)
|
|
||||||
elif not job['started']:
|
|
||||||
if monotonic() - job['start_time'] > 120:
|
|
||||||
self.jobs.remove(job)
|
|
||||||
error_dialog(self.gui, _('Failed to start editor'), _(
|
|
||||||
'Could not edit: {}. The Table of Contents editor did not start in two minutes').format(job['title']), show=True)
|
|
||||||
continue
|
|
||||||
if os.path.exists(started_path):
|
|
||||||
job['started'] = True
|
|
||||||
retry_on_fail(os.remove, started_path)
|
|
||||||
if self.jobs:
|
if self.jobs:
|
||||||
QTimer.singleShot(100, self.check_for_completions)
|
QTimer.singleShot(100, self.check_for_completions)
|
||||||
|
|
||||||
|
@ -1145,7 +1145,11 @@ class TOCEditor(QDialog): # {{{
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
def main(path=None, title=None):
|
def main(shm_name=None):
|
||||||
|
import json
|
||||||
|
import struct
|
||||||
|
from calibre.utils.shm import SharedMemory
|
||||||
|
|
||||||
# Ensure we can continue to function if GUI is closed
|
# Ensure we can continue to function if GUI is closed
|
||||||
os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None)
|
os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None)
|
||||||
reset_base_dir()
|
reset_base_dir()
|
||||||
@ -1154,19 +1158,28 @@ def main(path=None, title=None):
|
|||||||
# prevents them from being grouped with viewer/editor process when
|
# prevents them from being grouped with viewer/editor process when
|
||||||
# launched from within calibre, as both use calibre-parallel.exe
|
# launched from within calibre, as both use calibre-parallel.exe
|
||||||
set_app_uid(TOC_DIALOG_APP_UID)
|
set_app_uid(TOC_DIALOG_APP_UID)
|
||||||
|
with SharedMemory(name=shm_name) as shm:
|
||||||
|
pos = struct.calcsize('>II')
|
||||||
|
state, ok = struct.unpack('>II', shm.read(pos))
|
||||||
|
data = json.loads(shm.read_data_with_size())
|
||||||
|
title = data['title']
|
||||||
|
path = data['path']
|
||||||
|
s = struct.pack('>I', 1)
|
||||||
|
shm.seek(0), shm.write(s), shm.flush()
|
||||||
|
|
||||||
|
override = 'calibre-gui' if islinux else None
|
||||||
|
app = Application([], override_program_name=override)
|
||||||
|
d = TOCEditor(path, title=title, write_result_to=path + '.result')
|
||||||
|
d.start()
|
||||||
|
ok = 0
|
||||||
|
if d.exec() == QDialog.DialogCode.Accepted:
|
||||||
|
ok = 1
|
||||||
|
s = struct.pack('>II', 2, ok)
|
||||||
|
shm.seek(0), shm.write(s), shm.flush()
|
||||||
|
|
||||||
with open(path + '.started', 'w'):
|
|
||||||
pass
|
|
||||||
override = 'calibre-gui' if islinux else None
|
|
||||||
app = Application([], override_program_name=override)
|
|
||||||
d = TOCEditor(path, title=title, write_result_to=path + '.result')
|
|
||||||
d.start()
|
|
||||||
ret = 1
|
|
||||||
if d.exec() == QDialog.DialogCode.Accepted:
|
|
||||||
ret = 0
|
|
||||||
del d
|
del d
|
||||||
del app
|
del app
|
||||||
raise SystemExit(ret)
|
raise SystemExit(0 if ok else 1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user