hydrus/hydrus/client/gui/ClientGUIAsync.py

310 lines
8.5 KiB
Python
Raw Normal View History

2021-04-07 21:26:45 +00:00
import os
import sys
2019-11-28 01:11:46 +00:00
import threading
2020-04-22 21:00:35 +00:00
2021-04-07 21:26:45 +00:00
from qtpy import QtWidgets as QW
from hydrus.core import HydrusData
2020-04-22 21:00:35 +00:00
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
2020-07-29 20:52:44 +00:00
2020-04-22 21:00:35 +00:00
from hydrus.client.gui import QtPorting as QP
2019-11-28 01:11:46 +00:00
2020-09-09 20:59:19 +00:00
# this does one thing neatly
class AsyncQtJob( object ):
2019-11-28 01:11:46 +00:00
2021-04-07 21:26:45 +00:00
def __init__( self, win, work_callable, publish_callable, errback_callable = None, errback_ui_cleanup_callable = None ):
2019-11-28 01:11:46 +00:00
# ultimate improvement here is to move to QObject/QThread and do the notifications through signals and slots (which will disconnect on object deletion)
self._win = win
2020-09-09 20:59:19 +00:00
self._work_callable = work_callable
self._publish_callable = publish_callable
2021-04-07 21:26:45 +00:00
self._errback_callable = errback_callable
self._errback_ui_cleanup_callable = errback_ui_cleanup_callable
def _DefaultErrback( self, etype, value, tb ):
HydrusData.ShowExceptionTuple( etype, value, tb )
message = 'An error occured in a background task. If you had UI waiting on a fetch job, the dialog/panel may need to be closed and re-opened.'
message += os.linesep * 2
message += 'The error info will show as a popup and also be printed to log. Hydev may want to know about this error, at least to improve error handling.'
message += os.linesep * 2
message += 'Error summary: {}'.format( value )
QW.QMessageBox.warning( self._win, 'Error', message )
if self._errback_ui_cleanup_callable is not None:
self._errback_ui_cleanup_callable()
2019-11-28 01:11:46 +00:00
2020-09-09 20:59:19 +00:00
def _doWork( self ):
2019-11-28 01:11:46 +00:00
2020-09-09 20:59:19 +00:00
def qt_deliver_result( result ):
if not QP.isValid( self._win ):
return
self._publish_callable( result )
2019-11-28 01:11:46 +00:00
2021-04-07 21:26:45 +00:00
try:
result = self._work_callable()
except Exception as e:
( etype, value, tb ) = sys.exc_info()
if self._errback_callable is None:
c = self._DefaultErrback
else:
c = self._errback_callable
2021-06-23 21:11:38 +00:00
try:
HG.client_controller.CallBlockingToQt( self._win, c, etype, value, tb )
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.ShutdownException ):
return
2021-04-07 21:26:45 +00:00
return
2019-11-28 01:11:46 +00:00
2020-09-09 20:59:19 +00:00
try:
HG.client_controller.CallBlockingToQt( self._win, qt_deliver_result, result )
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.ShutdownException ):
return
2021-04-07 21:26:45 +00:00
except Exception as e:
( etype, value, tb ) = sys.exc_info()
if self._errback_callable is None:
c = self._DefaultErrback
else:
c = self._errback_callable
2021-06-23 21:11:38 +00:00
try:
HG.client_controller.CallBlockingToQt( self._win, c, etype, value, tb )
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.ShutdownException ):
return
2021-04-07 21:26:45 +00:00
2019-11-28 01:11:46 +00:00
2020-09-09 20:59:19 +00:00
def start( self ):
2019-11-28 01:11:46 +00:00
2020-09-09 20:59:19 +00:00
HG.client_controller.CallToThread( self._doWork )
2019-11-28 01:11:46 +00:00
2020-09-09 20:59:19 +00:00
# this can refresh dirty stuff n times and won't spam work
class AsyncQtUpdater( object ):
def __init__( self, win, loading_callable, work_callable, publish_callable ):
# ultimate improvement here is to move to QObject/QThread and do the notifications through signals and slots (which will disconnect on object deletion)
self._win = win
self._loading_callable = loading_callable
self._work_callable = work_callable
self._publish_callable = publish_callable
2019-11-28 01:11:46 +00:00
2020-09-09 20:59:19 +00:00
self._calllater_waiting = False
self._work_needs_to_restart = False
self._is_working = False
self._lock = threading.Lock()
2019-11-28 01:11:46 +00:00
def _doWork( self ):
2020-04-22 21:00:35 +00:00
def qt_deliver_result( result ):
2019-11-28 01:11:46 +00:00
2020-04-22 21:00:35 +00:00
if self._win is None or not QP.isValid( self._win ):
2019-11-28 01:11:46 +00:00
self._win = None
return
2020-09-09 20:59:19 +00:00
self._publish_callable( result )
2019-11-28 01:11:46 +00:00
with self._lock:
self._calllater_waiting = False
self._work_needs_to_restart = False
self._is_working = True
try:
2020-09-09 20:59:19 +00:00
result = self._work_callable()
2019-11-28 01:11:46 +00:00
try:
2020-04-22 21:00:35 +00:00
HG.client_controller.CallBlockingToQt( self._win, qt_deliver_result, result )
2019-11-28 01:11:46 +00:00
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.ShutdownException ):
self._win = None
return
finally:
with self._lock:
self._is_working = False
if self._work_needs_to_restart and not self._calllater_waiting:
QP.CallAfter( self.update )
def _startWork( self ):
HG.client_controller.CallToThread( self._doWork )
def update( self ):
2020-04-22 21:00:35 +00:00
if self._win is None or not QP.isValid( self._win ):
2019-11-28 01:11:46 +00:00
self._win = None
return
with self._lock:
if self._is_working:
self._work_needs_to_restart = True
elif not self._calllater_waiting:
2020-09-09 20:59:19 +00:00
self._loading_callable()
2019-11-28 01:11:46 +00:00
self._calllater_waiting = True
self._startWork()
2020-09-09 20:59:19 +00:00
2019-11-28 01:11:46 +00:00
class FastThreadToGUIUpdater( object ):
def __init__( self, win, func ):
self._win = win
self._func = func
self._lock = threading.Lock()
self._args = None
self._kwargs = None
self._callafter_waiting = False
self._work_needs_to_restart = False
self._is_working = False
def QtDoIt( self ):
2020-04-22 21:00:35 +00:00
if self._win is None or not QP.isValid( self._win ):
2019-11-28 01:11:46 +00:00
self._win = None
return
with self._lock:
self._callafter_waiting = False
self._work_needs_to_restart = False
self._is_working = True
args = self._args
kwargs = self._kwargs
try:
self._func( *args, **kwargs )
except HydrusExceptions.ShutdownException:
pass
finally:
with self._lock:
self._is_working = False
if self._work_needs_to_restart and not self._callafter_waiting:
self._callafter_waiting = True
QP.CallAfter( self.QtDoIt )
# the point here is that we can spam this a hundred times a second, updating the args and kwargs, and Qt will catch up to it when it can
# if Qt feels like running fast, it'll update at 60fps
# if not, we won't get bungled up with 10,000+ pubsub events in the event queue
def Update( self, *args, **kwargs ):
2019-12-05 05:29:32 +00:00
if self._win is None:
2019-11-28 01:11:46 +00:00
return
with self._lock:
self._args = args
self._kwargs = kwargs
if self._is_working:
self._work_needs_to_restart = True
2021-04-28 21:43:16 +00:00
elif not ( self._callafter_waiting or HG.view_shutdown ):
2019-11-28 01:11:46 +00:00
QP.CallAfter( self.QtDoIt )