hydrus/include/ClientThreading.py

584 lines
15 KiB
Python
Raw Normal View History

2019-01-09 22:59:03 +00:00
from . import HydrusExceptions
import queue
2016-02-17 22:06:47 +00:00
import threading
import time
import traceback
2019-06-19 22:08:48 +00:00
from . import HydrusConstants as HC
2019-01-09 22:59:03 +00:00
from . import HydrusData
from . import HydrusGlobals as HG
from . import HydrusThreading
2016-02-17 22:06:47 +00:00
import os
2019-11-14 03:56:30 +00:00
from . import QtPorting as QP
2016-02-17 22:06:47 +00:00
class JobKey( object ):
2019-06-19 22:08:48 +00:00
def __init__( self, pausable = False, cancellable = False, maintenance_mode = HC.MAINTENANCE_FORCED, only_start_if_unbusy = False, stop_time = None, cancel_on_shutdown = True ):
2016-02-17 22:06:47 +00:00
self._key = HydrusData.GenerateKey()
2018-02-21 21:59:37 +00:00
self._creation_time = HydrusData.GetNowFloat()
2016-02-17 22:06:47 +00:00
self._pausable = pausable
self._cancellable = cancellable
2019-06-19 22:08:48 +00:00
self._maintenance_mode = maintenance_mode
2016-02-17 22:06:47 +00:00
self._only_start_if_unbusy = only_start_if_unbusy
2016-03-30 22:56:50 +00:00
self._stop_time = stop_time
2017-08-02 21:32:54 +00:00
self._cancel_on_shutdown = cancel_on_shutdown
2016-02-17 22:06:47 +00:00
2017-01-25 22:56:55 +00:00
self._start_time = HydrusData.GetNow()
2016-02-17 22:06:47 +00:00
self._deleted = threading.Event()
2016-08-24 18:36:56 +00:00
self._deletion_time = None
2016-02-17 22:06:47 +00:00
self._begun = threading.Event()
self._done = threading.Event()
self._cancelled = threading.Event()
self._paused = threading.Event()
self._yield_pause_period = 10
2016-04-06 19:52:45 +00:00
self._next_yield_pause = HydrusData.GetNow() + self._yield_pause_period
self._bigger_pause_period = 100
self._next_bigger_pause = HydrusData.GetNow() + self._bigger_pause_period
self._longer_pause_period = 1000
self._next_longer_pause = HydrusData.GetNow() + self._longer_pause_period
2016-02-17 22:06:47 +00:00
2016-11-16 20:21:43 +00:00
self._urls = []
2016-02-17 22:06:47 +00:00
self._variable_lock = threading.Lock()
self._variables = dict()
2020-01-22 21:04:43 +00:00
def __eq__( self, other ):
if isinstance( other, JobKey ):
return self.__hash__() == other.__hash__()
return NotImplemented
2016-02-17 22:06:47 +00:00
2020-01-22 21:04:43 +00:00
def __hash__( self ):
return self._key.__hash__()
2016-02-17 22:06:47 +00:00
2016-03-30 22:56:50 +00:00
def _CheckCancelTests( self ):
if not self._cancelled.is_set():
should_cancel = False
2017-08-02 21:32:54 +00:00
if self._cancel_on_shutdown and HydrusThreading.IsThreadShuttingDown():
2016-03-30 22:56:50 +00:00
should_cancel = True
2019-06-19 22:08:48 +00:00
if HG.client_controller.ShouldStopThisWork( self._maintenance_mode, self._stop_time ):
2016-03-30 22:56:50 +00:00
should_cancel = True
if should_cancel:
self.Cancel()
2016-08-24 18:36:56 +00:00
if not self._deleted.is_set():
if self._deletion_time is not None:
if HydrusData.TimeHasPassed( self._deletion_time ):
self.Finish()
self._deleted.set()
2016-03-30 22:56:50 +00:00
2016-11-16 20:21:43 +00:00
def AddURL( self, url ):
with self._variable_lock:
self._urls.append( url )
2016-02-17 22:06:47 +00:00
def Begin( self ): self._begun.set()
def CanBegin( self ):
2016-08-24 18:36:56 +00:00
self._CheckCancelTests()
2016-03-30 22:56:50 +00:00
if self.IsCancelled():
2016-02-17 22:06:47 +00:00
return False
2017-05-10 21:33:58 +00:00
if self._only_start_if_unbusy and HG.client_controller.SystemBusy():
2016-02-17 22:06:47 +00:00
return False
return True
2016-11-16 20:21:43 +00:00
def Cancel( self, seconds = None ):
2016-02-17 22:06:47 +00:00
2016-11-16 20:21:43 +00:00
if seconds is None:
self._cancelled.set()
self.Finish()
else:
2018-02-14 21:47:18 +00:00
HG.client_controller.CallLater( seconds, self.Cancel )
2016-11-16 20:21:43 +00:00
2016-02-17 22:06:47 +00:00
2016-08-24 18:36:56 +00:00
def Delete( self, seconds = None ):
2016-02-17 22:06:47 +00:00
2016-08-24 18:36:56 +00:00
if seconds is None:
self._deletion_time = HydrusData.GetNow()
else:
self._deletion_time = HydrusData.GetNow() + seconds
2016-02-17 22:06:47 +00:00
def DeleteVariable( self, name ):
with self._variable_lock:
if name in self._variables: del self._variables[ name ]
time.sleep( 0.00001 )
2016-11-16 20:21:43 +00:00
def Finish( self, seconds = None ):
if seconds is None:
self._done.set()
else:
2018-02-14 21:47:18 +00:00
HG.client_controller.CallLater( seconds, self.Finish )
2016-11-16 20:21:43 +00:00
2016-02-17 22:06:47 +00:00
2018-02-21 21:59:37 +00:00
def GetCreationTime( self ):
return self._creation_time
2016-09-28 18:48:01 +00:00
def GetIfHasVariable( self, name ):
2016-02-17 22:06:47 +00:00
2016-09-28 18:48:01 +00:00
with self._variable_lock:
if name in self._variables:
return self._variables[ name ]
else:
return None
2016-02-17 22:06:47 +00:00
2016-11-16 20:21:43 +00:00
def GetKey( self ):
return self._key
def GetURLs( self ):
with self._variable_lock:
return list( self._urls )
2016-09-28 18:48:01 +00:00
2016-02-17 22:06:47 +00:00
def HasVariable( self, name ):
with self._variable_lock: return name in self._variables
def IsBegun( self ):
2016-08-24 18:36:56 +00:00
self._CheckCancelTests()
2016-02-17 22:06:47 +00:00
return self._begun.is_set()
def IsCancellable( self ):
2016-08-24 18:36:56 +00:00
self._CheckCancelTests()
2016-02-17 22:06:47 +00:00
return self._cancellable and not self.IsDone()
def IsCancelled( self ):
2016-03-30 22:56:50 +00:00
self._CheckCancelTests()
2017-08-02 21:32:54 +00:00
return self._cancelled.is_set()
2016-02-17 22:06:47 +00:00
def IsDeleted( self ):
2016-03-30 22:56:50 +00:00
self._CheckCancelTests()
2017-08-02 21:32:54 +00:00
return self._deleted.is_set()
2016-02-17 22:06:47 +00:00
def IsDone( self ):
2016-08-24 18:36:56 +00:00
self._CheckCancelTests()
2017-08-02 21:32:54 +00:00
return self._done.is_set()
2016-02-17 22:06:47 +00:00
2016-08-24 18:36:56 +00:00
def IsPausable( self ):
self._CheckCancelTests()
return self._pausable and not self.IsDone()
2016-02-17 22:06:47 +00:00
2016-08-24 18:36:56 +00:00
def IsPaused( self ):
self._CheckCancelTests()
return self._paused.is_set() and not self.IsDone()
2016-02-17 22:06:47 +00:00
2016-08-24 18:36:56 +00:00
def IsWorking( self ):
self._CheckCancelTests()
return self.IsBegun() and not self.IsDone()
2016-02-17 22:06:47 +00:00
def PausePlay( self ):
if self._paused.is_set(): self._paused.clear()
else: self._paused.set()
def SetCancellable( self, value ): self._cancellable = value
def SetPausable( self, value ): self._pausable = value
def SetVariable( self, name, value ):
with self._variable_lock: self._variables[ name ] = value
time.sleep( 0.00001 )
2017-01-25 22:56:55 +00:00
def TimeRunning( self ):
return HydrusData.GetNow() - self._start_time
2016-02-17 22:06:47 +00:00
def ToString( self ):
stuff_to_print = []
with self._variable_lock:
if 'popup_title' in self._variables: stuff_to_print.append( self._variables[ 'popup_title' ] )
if 'popup_text_1' in self._variables: stuff_to_print.append( self._variables[ 'popup_text_1' ] )
if 'popup_text_2' in self._variables: stuff_to_print.append( self._variables[ 'popup_text_2' ] )
if 'popup_traceback' in self._variables: stuff_to_print.append( self._variables[ 'popup_traceback' ] )
2019-01-09 22:59:03 +00:00
stuff_to_print = [ str( s ) for s in stuff_to_print ]
2016-02-17 22:06:47 +00:00
try:
return os.linesep.join( stuff_to_print )
except:
return repr( stuff_to_print )
def WaitIfNeeded( self ):
2016-04-06 19:52:45 +00:00
if HydrusData.TimeHasPassed( self._next_yield_pause ):
2016-02-17 22:06:47 +00:00
time.sleep( 0.1 )
2016-04-06 19:52:45 +00:00
self._next_yield_pause = HydrusData.GetNow() + self._yield_pause_period
if HydrusData.TimeHasPassed( self._next_bigger_pause ):
time.sleep( 1 )
self._next_bigger_pause = HydrusData.GetNow() + self._bigger_pause_period
if HydrusData.TimeHasPassed( self._longer_pause_period ):
time.sleep( 10 )
self._next_longer_pause = HydrusData.GetNow() + self._longer_pause_period
2016-02-17 22:06:47 +00:00
i_paused = False
should_quit = False
while self.IsPaused():
i_paused = True
time.sleep( 0.1 )
2016-03-30 22:56:50 +00:00
if self.IsDone():
break
2016-02-17 22:06:47 +00:00
2016-03-30 22:56:50 +00:00
if self.IsCancelled():
2016-02-17 22:06:47 +00:00
should_quit = True
return ( i_paused, should_quit )
2018-01-03 22:37:30 +00:00
2019-04-17 21:51:50 +00:00
class FileRWLock( object ):
class RLock( object ):
def __init__( self, parent ):
self.parent = parent
def __enter__( self ):
while not HydrusThreading.IsThreadShuttingDown():
with self.parent.lock:
# if there are no writers, we can start reading
if self.parent.num_waiting_writers == 0:
self.parent.num_readers += 1
return
# otherwise wait a bit
self.parent.read_available_event.wait( 1 )
self.parent.read_available_event.clear()
def __exit__( self, exc_type, exc_val, exc_tb ):
with self.parent.lock:
self.parent.num_readers -= 1
do_notify = self.parent.num_readers == 0
if do_notify:
self.parent.write_available_event.set()
class WLock( object ):
def __init__( self, parent ):
self.parent = parent
def __enter__( self ):
# let all the readers know that we are bumping up to the front of the queue
with self.parent.lock:
self.parent.num_waiting_writers += 1
while not HydrusThreading.IsThreadShuttingDown():
with self.parent.lock:
# if nothing reading or writing atm, sieze the opportunity
if self.parent.num_readers == 0 and not self.parent.there_is_an_active_writer:
self.parent.there_is_an_active_writer = True
return
# otherwise wait a bit
self.parent.write_available_event.wait( 1 )
self.parent.write_available_event.clear()
def __exit__( self, exc_type, exc_val, exc_tb ):
with self.parent.lock:
self.parent.there_is_an_active_writer = False
self.parent.num_waiting_writers -= 1
do_read_notify = self.parent.num_waiting_writers == 0 # reading is now available
do_write_notify = self.parent.num_waiting_writers > 0 # another writer is waiting
if do_read_notify:
self.parent.read_available_event.set()
if do_write_notify:
self.parent.write_available_event.set()
def __init__( self ):
self.read = self.RLock( self )
self.write = self.WLock( self )
self.lock = threading.Lock()
self.read_available_event = threading.Event()
self.write_available_event = threading.Event()
self.num_readers = 0
self.num_waiting_writers = 0
self.there_is_an_active_writer = False
2019-11-14 03:56:30 +00:00
class QtAwareJob(HydrusThreading.SchedulableJob):
2018-02-14 21:47:18 +00:00
2018-05-16 20:09:50 +00:00
def __init__( self, controller, scheduler, window, initial_delay, work_callable ):
2018-02-14 21:47:18 +00:00
2018-05-16 20:09:50 +00:00
HydrusThreading.SchedulableJob.__init__( self, controller, scheduler, initial_delay, work_callable )
2018-02-14 21:47:18 +00:00
self._window = window
def _BootWorker( self ):
2019-11-14 03:56:30 +00:00
def qt_code():
2018-02-14 21:47:18 +00:00
2019-11-14 03:56:30 +00:00
if not self._window or not QP.isValid( self._window ):
2018-02-14 21:47:18 +00:00
return
self.Work()
2019-11-14 03:56:30 +00:00
QP.CallAfter( qt_code )
2018-02-14 21:47:18 +00:00
2018-02-28 22:30:36 +00:00
def _MyWindowDead( self ):
return not self._window
2018-02-14 21:47:18 +00:00
def IsCancelled( self ):
2018-02-28 22:30:36 +00:00
my_window_dead = self._MyWindowDead()
2018-02-14 21:47:18 +00:00
if my_window_dead:
self._is_cancelled.set()
return HydrusThreading.SchedulableJob.IsCancelled( self )
2018-02-28 22:30:36 +00:00
def IsDead( self ):
return self._MyWindowDead()
2019-11-14 03:56:30 +00:00
class QtAwareRepeatingJob(HydrusThreading.RepeatingJob):
2018-02-14 21:47:18 +00:00
2018-05-16 20:09:50 +00:00
def __init__( self, controller, scheduler, window, initial_delay, period, work_callable ):
2018-02-14 21:47:18 +00:00
2018-05-16 20:09:50 +00:00
HydrusThreading.RepeatingJob.__init__( self, controller, scheduler, initial_delay, period, work_callable )
2018-02-14 21:47:18 +00:00
self._window = window
2020-01-22 21:04:43 +00:00
def _QTWork( self ):
2018-02-14 21:47:18 +00:00
2020-01-22 21:04:43 +00:00
if not self._window or not QP.isValid( self._window ):
2018-02-14 21:47:18 +00:00
2020-01-22 21:04:43 +00:00
return
2018-02-14 21:47:18 +00:00
2020-01-22 21:04:43 +00:00
self.Work()
def _BootWorker( self ):
QP.CallAfter( self._QTWork )
2018-02-14 21:47:18 +00:00
2018-02-28 22:30:36 +00:00
def _MyWindowDead( self ):
return not self._window
2018-02-14 21:47:18 +00:00
def IsCancelled( self ):
2018-02-28 22:30:36 +00:00
my_window_dead = self._MyWindowDead()
2018-02-14 21:47:18 +00:00
if my_window_dead:
self._is_cancelled.set()
return HydrusThreading.SchedulableJob.IsCancelled( self )
2018-02-28 22:30:36 +00:00
def IsDead( self ):
return self._MyWindowDead()