mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -04:00
Linux/OSX: Shutdown gracefully on receiving interrupt or terminate signals
This commit is contained in:
parent
57efd828ef
commit
ece360f04c
@ -1,13 +1,13 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
""" The GUI """
|
""" The GUI """
|
||||||
import os, sys, Queue, threading, glob
|
import os, sys, Queue, threading, glob, signal
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from threading import RLock, Lock
|
from threading import RLock, Lock
|
||||||
from urllib import unquote
|
from urllib import unquote
|
||||||
from PyQt5.QtWidgets import QStyle # Gives a nicer error message than import from Qt
|
from PyQt5.QtWidgets import QStyle # Gives a nicer error message than import from Qt
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QFileInfo, QObject, QBuffer, Qt, QByteArray, QTranslator,
|
QFileInfo, QObject, QBuffer, Qt, QByteArray, QTranslator, QSocketNotifier,
|
||||||
QCoreApplication, QThread, QEvent, QTimer, pyqtSignal, QDateTime,
|
QCoreApplication, QThread, QEvent, QTimer, pyqtSignal, QDateTime,
|
||||||
QDesktopServices, QFileDialog, QFileIconProvider, QSettings, QIcon,
|
QDesktopServices, QFileDialog, QFileIconProvider, QSettings, QIcon,
|
||||||
QApplication, QDialog, QUrl, QFont, QFontDatabase, QLocale, QFontInfo)
|
QApplication, QDialog, QUrl, QFont, QFontDatabase, QLocale, QFontInfo)
|
||||||
@ -860,6 +860,8 @@ def setup_gui_option_parser(parser):
|
|||||||
|
|
||||||
class Application(QApplication):
|
class Application(QApplication):
|
||||||
|
|
||||||
|
shutdown_signal_received = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, args, force_calibre_style=False, override_program_name=None, headless=False, color_prefs=gprefs):
|
def __init__(self, args, force_calibre_style=False, override_program_name=None, headless=False, color_prefs=gprefs):
|
||||||
self.file_event_hook = None
|
self.file_event_hook = None
|
||||||
if override_program_name:
|
if override_program_name:
|
||||||
@ -872,6 +874,8 @@ class Application(QApplication):
|
|||||||
qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args]
|
qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args]
|
||||||
self.pi = plugins['progress_indicator'][0]
|
self.pi = plugins['progress_indicator'][0]
|
||||||
QApplication.__init__(self, qargs)
|
QApplication.__init__(self, qargs)
|
||||||
|
if not iswindows:
|
||||||
|
self.setup_unix_signals()
|
||||||
if islinux or isbsd:
|
if islinux or isbsd:
|
||||||
self.setAttribute(Qt.AA_DontUseNativeMenuBar, 'CALIBRE_NO_NATIVE_MENUBAR' in os.environ)
|
self.setAttribute(Qt.AA_DontUseNativeMenuBar, 'CALIBRE_NO_NATIVE_MENUBAR' in os.environ)
|
||||||
self.setup_styles(force_calibre_style)
|
self.setup_styles(force_calibre_style)
|
||||||
@ -1048,6 +1052,28 @@ class Application(QApplication):
|
|||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
self.setQuitOnLastWindowClosed(True)
|
self.setQuitOnLastWindowClosed(True)
|
||||||
|
|
||||||
|
def setup_unix_signals(self):
|
||||||
|
import fcntl
|
||||||
|
read_fd, write_fd = os.pipe()
|
||||||
|
cloexec_flag = getattr(fcntl, 'FD_CLOEXEC', 1)
|
||||||
|
for fd in (read_fd, write_fd):
|
||||||
|
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
||||||
|
fcntl.fcntl(fd, fcntl.F_SETFD, flags | cloexec_flag | os.O_NONBLOCK)
|
||||||
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
||||||
|
signal.signal(sig, lambda x, y: None)
|
||||||
|
signal.siginterrupt(sig, False)
|
||||||
|
signal.set_wakeup_fd(write_fd)
|
||||||
|
self.signal_notifier = QSocketNotifier(read_fd, QSocketNotifier.Read, self)
|
||||||
|
self.signal_notifier.setEnabled(True)
|
||||||
|
self.signal_notifier.activated.connect(self.signal_received, type=Qt.QueuedConnection)
|
||||||
|
|
||||||
|
def signal_received(self, read_fd):
|
||||||
|
try:
|
||||||
|
os.read(read_fd, 1024)
|
||||||
|
except EnvironmentError:
|
||||||
|
return
|
||||||
|
self.shutdown_signal_received.emit()
|
||||||
|
|
||||||
_store_app = None
|
_store_app = None
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
|
@ -86,7 +86,6 @@ class MainWindow(QMainWindow):
|
|||||||
___menu = None
|
___menu = None
|
||||||
__actions = []
|
__actions = []
|
||||||
|
|
||||||
keyboard_interrupt = pyqtSignal()
|
|
||||||
# See https://bugreports.qt-project.org/browse/QTBUG-42281
|
# See https://bugreports.qt-project.org/browse/QTBUG-42281
|
||||||
window_blocked = pyqtSignal()
|
window_blocked = pyqtSignal()
|
||||||
window_unblocked = pyqtSignal()
|
window_unblocked = pyqtSignal()
|
||||||
@ -131,8 +130,7 @@ class MainWindow(QMainWindow):
|
|||||||
sys.excepthook = ExceptionHandler(self)
|
sys.excepthook = ExceptionHandler(self)
|
||||||
|
|
||||||
def unhandled_exception(self, type, value, tb):
|
def unhandled_exception(self, type, value, tb):
|
||||||
if type == KeyboardInterrupt:
|
if type is KeyboardInterrupt:
|
||||||
self.keyboard_interrupt.emit()
|
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
sio = StringIO.StringIO()
|
sio = StringIO.StringIO()
|
||||||
|
@ -178,7 +178,7 @@ def main(control_conn, data_conn):
|
|||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
request = eintr_retry_call(control_conn.recv)
|
request = eintr_retry_call(control_conn.recv)
|
||||||
except EOFError:
|
except (KeyboardInterrupt, EOFError):
|
||||||
break
|
break
|
||||||
if request is None:
|
if request is None:
|
||||||
break
|
break
|
||||||
|
@ -71,6 +71,7 @@ def _run(args, notify=None):
|
|||||||
main = Main(opts, notify=notify)
|
main = Main(opts, notify=notify)
|
||||||
main.set_exception_handler()
|
main.set_exception_handler()
|
||||||
main.show()
|
main.show()
|
||||||
|
app.shutdown_signal_received.connect(main.boss.quit)
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
main.boss.open_book(args[1], edit_file=args[2:], clear_notify_data=False)
|
main.boss.open_book(args[1], edit_file=args[2:], clear_notify_data=False)
|
||||||
else:
|
else:
|
||||||
|
@ -397,8 +397,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
if config['autolaunch_server']:
|
if config['autolaunch_server']:
|
||||||
self.start_content_server()
|
self.start_content_server()
|
||||||
|
|
||||||
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
|
|
||||||
|
|
||||||
self.read_settings()
|
self.read_settings()
|
||||||
self.finalize_layout()
|
self.finalize_layout()
|
||||||
if self.bars_manager.showing_donate:
|
if self.bars_manager.showing_donate:
|
||||||
@ -425,6 +423,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
# Collect cycles now
|
# Collect cycles now
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
|
QApplication.instance().shutdown_signal_received.connect(self.quit)
|
||||||
if show_gui and self.gui_debug is not None:
|
if show_gui and self.gui_debug is not None:
|
||||||
QTimer.singleShot(10, self.show_gui_debug_msg)
|
QTimer.singleShot(10, self.show_gui_debug_msg)
|
||||||
|
|
||||||
@ -843,6 +842,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
|
|
||||||
def quit(self, checked=True, restart=False, debug_on_restart=False,
|
def quit(self, checked=True, restart=False, debug_on_restart=False,
|
||||||
confirm_quit=True):
|
confirm_quit=True):
|
||||||
|
if self.shutting_down:
|
||||||
|
return
|
||||||
if confirm_quit and not self.confirm_quit():
|
if confirm_quit and not self.confirm_quit():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
@ -178,6 +178,7 @@ class EbookViewer(MainWindow):
|
|||||||
self.action_reload = QAction(_('&Reload book'), self)
|
self.action_reload = QAction(_('&Reload book'), self)
|
||||||
self.action_reload.triggered.connect(self.reload_book)
|
self.action_reload.triggered.connect(self.reload_book)
|
||||||
self.action_quit.triggered.connect(self.quit)
|
self.action_quit.triggered.connect(self.quit)
|
||||||
|
QApplication.instance().shutdown_signal_received.connect(self.action_quit.trigger)
|
||||||
self.action_reference_mode.triggered[bool].connect(self.view.reference_mode)
|
self.action_reference_mode.triggered[bool].connect(self.view.reference_mode)
|
||||||
self.action_metadata.triggered[bool].connect(self.metadata.setVisible)
|
self.action_metadata.triggered[bool].connect(self.metadata.setVisible)
|
||||||
self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible)
|
self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible)
|
||||||
|
@ -337,6 +337,8 @@ def worker_main(conn):
|
|||||||
job = cPickle.loads(eintr_retry_call(conn.recv_bytes))
|
job = cPickle.loads(eintr_retry_call(conn.recv_bytes))
|
||||||
except EOFError:
|
except EOFError:
|
||||||
break
|
break
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
prints('recv() failed in worker, terminating worker', file=sys.stderr)
|
prints('recv() failed in worker, terminating worker', file=sys.stderr)
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -70,6 +70,8 @@ class OffloadWorker(object):
|
|||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
try:
|
try:
|
||||||
eintr_retry_call(self.conn.send, None)
|
eintr_retry_call(self.conn.send, None)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user