diff --git a/src/calibre/gui2/actions/toc_edit.py b/src/calibre/gui2/actions/toc_edit.py index 8ec4fa43b7..91ff110e9a 100644 --- a/src/calibre/gui2/actions/toc_edit.py +++ b/src/calibre/gui2/actions/toc_edit.py @@ -6,6 +6,7 @@ __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' import os +from itertools import count from collections import OrderedDict from qt.core import ( QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon, QLabel, QTimer @@ -100,6 +101,7 @@ class ToCEditAction(InterfaceAction): self.do_edit(book_id_map) def genesis(self): + self.shm_count = count() self.qaction.triggered.connect(self.edit_books) self.jobs = [] @@ -146,34 +148,51 @@ class ToCEditAction(InterfaceAction): self.do_one(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 path = db.format(book_id, fmt, index_is_id=True, as_path=True) title = db.title(book_id, index_is_id=True) + ' [%s]'%fmt - data = {'path': path, 'title': title} - self.gui.job_manager.launch_gui_app('toc-dialog', kwargs=data) - job = data.copy() - job.update({'book_id': book_id, 'fmt': fmt, 'library_id': db.new_api.library_id, 'started': False, 'start_time': monotonic()}) + job = {'path': path, 'title': title} + data = json.dumps(job).encode('utf-8') + header = struct.pack('>II', 0, 0) + 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.check_for_completions() 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): path = job['path'] - started_path = path + '.started' - result_path = path + '.result' - if job['started'] and os.path.exists(result_path): - self.jobs.remove(job) - ret = -1 - - def read(result_path): - nonlocal ret - with open(result_path) as f: - ret = int(f.read().strip()) - - retry_on_fail(read, result_path) - retry_on_fail(os.remove, result_path) - if ret == 0: + shm = job['shm'] + shm.seek(0) + state, ok = struct.unpack('>II', shm.read(struct.calcsize('>II'))) + if state == 0: + # not started + if monotonic() - job['start_time'] > 120: + remove_job(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) + elif state == 1: + # running + pass + elif state == 2: + # finished + job['shm'].already_unlinked = True + remove_job(job) + if ok == 1: db = self.gui.current_db if db.new_api.library_id != job['library_id']: error_dialog(self.gui, _('Library changed'), _( @@ -181,16 +200,6 @@ class ToCEditAction(InterfaceAction): ' the calibre library has changed.').format(job['title']), show=True) else: 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: QTimer.singleShot(100, self.check_for_completions) diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index 39918ddc83..9d81ea2153 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -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 os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None) reset_base_dir() @@ -1154,19 +1158,28 @@ def main(path=None, title=None): # prevents them from being grouped with viewer/editor process when # launched from within calibre, as both use calibre-parallel.exe 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 app - raise SystemExit(ret) + raise SystemExit(0 if ok else 1) if __name__ == '__main__':