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 )