Make communication with toc editor process more robust

A crash on dialog close no longer affects writing the result. And avoid
using the racy lock files for ipc.
This commit is contained in:
Kovid Goyal 2021-05-25 22:11:19 +05:30
parent 2c802b7839
commit e1a3458c51
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 42 additions and 37 deletions

View File

@ -152,37 +152,31 @@ class ToCEditAction(InterfaceAction):
self.check_for_completions()
def check_for_completions(self):
from calibre.utils.lock import lock_file
from calibre.utils.filenames import retry_on_fail
for job in tuple(self.jobs):
lock_path = job['path'] + '.lock'
if job['started']:
if not os.path.exists(lock_path):
self.jobs.remove(job)
continue
try:
lf = lock_file(lock_path, timeout=0.01, sleep_time=0.005)
except EnvironmentError:
continue
else:
self.jobs.remove(job)
ret = int(lf.read().decode('ascii'))
lf.close()
os.remove(lock_path)
if ret == 0:
db = self.gui.current_db
if db.new_api.library_id != job['library_id']:
error_dialog(self.gui, _('Library changed'), _(
'Cannot save changes made to {0} by the ToC editor as'
' the calibre library has changed.').format(job['title']), show=True)
else:
db.new_api.add_format(job['book_id'], job['fmt'], job['path'], run_hooks=False)
os.remove(job['path'])
started_path = job['path'] + '.started'
result_path = job['path'] + '.result'
if job['started'] and os.path.exists(result_path):
self.jobs.remove(job)
with open(result_path) as f:
ret = int(f.read().strip())
retry_on_fail(os.remove, result_path)
if ret == 0:
db = self.gui.current_db
if db.new_api.library_id != job['library_id']:
error_dialog(self.gui, _('Library changed'), _(
'Cannot save changes made to {0} by the ToC editor as'
' the calibre library has changed.').format(job['title']), show=True)
else:
db.new_api.add_format(job['book_id'], job['fmt'], job['path'], run_hooks=False)
os.remove(job['path'])
else:
if monotonic() - job['start_time'] > 10:
self.jobs.remove(job)
continue
if os.path.exists(lock_path):
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)

View File

@ -5,6 +5,7 @@
import os
import sys
import tempfile
import textwrap
from functools import partial
from qt.core import (
@ -29,7 +30,7 @@ from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.gui2.toc.location import ItemEdit
from calibre.ptempfile import reset_base_dir
from calibre.utils.config import JSONConfig
from calibre.utils.lock import ExclusiveFile
from calibre.utils.filenames import atomic_rename
from calibre.utils.logging import GUILog
from polyglot.builtins import map, range, unicode_type
@ -985,8 +986,9 @@ class TOCEditor(QDialog): # {{{
explode_done = pyqtSignal(object)
writing_done = pyqtSignal(object)
def __init__(self, pathtobook, title=None, parent=None, prefs=None):
def __init__(self, pathtobook, title=None, parent=None, prefs=None, write_result_to=None):
QDialog.__init__(self, parent)
self.write_result_to = write_result_to
self.prefs = prefs or te_prefs
self.pathtobook = pathtobook
self.working = True
@ -1071,7 +1073,7 @@ class TOCEditor(QDialog): # {{{
' more information.')%self.book_title, det_msg=tb, show=True)
super(TOCEditor, self).reject()
return
self.write_result(0)
super(TOCEditor, self).accept()
def reject(self):
@ -1083,8 +1085,17 @@ class TOCEditor(QDialog): # {{{
else:
self.working = False
self.prefs['toc_editor_window_geom'] = bytearray(self.saveGeometry())
self.write_result(1)
super(TOCEditor, self).reject()
def write_result(self, res):
if self.write_result_to:
with tempfile.NamedTemporaryFile(dir=os.path.dirname(self.write_result_to), delete=False) as f:
src = f.name
f.write(str(res).encode('utf-8'))
f.flush()
atomic_rename(src, self.write_result_to)
def start(self):
t = Thread(target=self.explode)
t.daemon = True
@ -1140,15 +1151,15 @@ def main(path=None, title=None):
# launched from within calibre, as both use calibre-parallel.exe
set_app_uid(TOC_DIALOG_APP_UID)
with ExclusiveFile(path + '.lock') as wf:
override = 'calibre-gui' if islinux else None
app = Application([], override_program_name=override)
d = TOCEditor(path, title=title)
d.start()
ret = 1
if d.exec_() == QDialog.DialogCode.Accepted:
ret = 0
wf.write('{}'.format(ret).encode('ascii'))
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)