310 lines
8.5 KiB
Python
310 lines
8.5 KiB
Python
import os
|
|
import sys
|
|
import threading
|
|
|
|
from qtpy import QtWidgets as QW
|
|
|
|
from hydrus.core import HydrusData
|
|
from hydrus.core import HydrusExceptions
|
|
from hydrus.core import HydrusGlobals as HG
|
|
|
|
from hydrus.client.gui import QtPorting as QP
|
|
|
|
# this does one thing neatly
|
|
class AsyncQtJob( object ):
|
|
|
|
def __init__( self, win, work_callable, publish_callable, errback_callable = None, errback_ui_cleanup_callable = None ):
|
|
|
|
# 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._work_callable = work_callable
|
|
self._publish_callable = publish_callable
|
|
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()
|
|
|
|
|
|
|
|
def _doWork( self ):
|
|
|
|
def qt_deliver_result( result ):
|
|
|
|
if not QP.isValid( self._win ):
|
|
|
|
return
|
|
|
|
|
|
self._publish_callable( result )
|
|
|
|
|
|
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
|
|
|
|
|
|
try:
|
|
|
|
HG.client_controller.CallBlockingToQt( self._win, c, etype, value, tb )
|
|
|
|
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.ShutdownException ):
|
|
|
|
return
|
|
|
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
|
HG.client_controller.CallBlockingToQt( self._win, qt_deliver_result, result )
|
|
|
|
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.ShutdownException ):
|
|
|
|
return
|
|
|
|
except Exception as e:
|
|
|
|
( etype, value, tb ) = sys.exc_info()
|
|
|
|
if self._errback_callable is None:
|
|
|
|
c = self._DefaultErrback
|
|
|
|
else:
|
|
|
|
c = self._errback_callable
|
|
|
|
|
|
try:
|
|
|
|
HG.client_controller.CallBlockingToQt( self._win, c, etype, value, tb )
|
|
|
|
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.ShutdownException ):
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
def start( self ):
|
|
|
|
HG.client_controller.CallToThread( self._doWork )
|
|
|
|
|
|
# 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
|
|
|
|
self._calllater_waiting = False
|
|
self._work_needs_to_restart = False
|
|
self._is_working = False
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
|
def _doWork( self ):
|
|
|
|
def qt_deliver_result( result ):
|
|
|
|
if self._win is None or not QP.isValid( self._win ):
|
|
|
|
self._win = None
|
|
|
|
return
|
|
|
|
|
|
self._publish_callable( result )
|
|
|
|
|
|
with self._lock:
|
|
|
|
self._calllater_waiting = False
|
|
self._work_needs_to_restart = False
|
|
self._is_working = True
|
|
|
|
|
|
try:
|
|
|
|
result = self._work_callable()
|
|
|
|
try:
|
|
|
|
HG.client_controller.CallBlockingToQt( self._win, qt_deliver_result, result )
|
|
|
|
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 ):
|
|
|
|
if self._win is None or not QP.isValid( self._win ):
|
|
|
|
self._win = None
|
|
|
|
return
|
|
|
|
|
|
with self._lock:
|
|
|
|
if self._is_working:
|
|
|
|
self._work_needs_to_restart = True
|
|
|
|
elif not self._calllater_waiting:
|
|
|
|
self._loading_callable()
|
|
|
|
self._calllater_waiting = True
|
|
|
|
self._startWork()
|
|
|
|
|
|
|
|
|
|
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 ):
|
|
|
|
if self._win is None or not QP.isValid( self._win ):
|
|
|
|
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 ):
|
|
|
|
if self._win is None:
|
|
|
|
return
|
|
|
|
|
|
with self._lock:
|
|
|
|
self._args = args
|
|
self._kwargs = kwargs
|
|
|
|
if self._is_working:
|
|
|
|
self._work_needs_to_restart = True
|
|
|
|
elif not ( self._callafter_waiting or HG.view_shutdown ):
|
|
|
|
QP.CallAfter( self.QtDoIt )
|
|
|
|
|
|
|
|
|