hydrus/hydrus/client/networking/ClientNetworking.py

500 lines
16 KiB
Python
Raw Normal View History

2017-06-07 22:05:15 +00:00
import collections
2015-10-21 21:53:10 +00:00
import threading
import time
2017-06-21 21:15:59 +00:00
import traceback
2015-10-21 21:53:10 +00:00
2020-05-20 21:36:02 +00:00
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
2017-01-25 22:56:55 +00:00
2018-04-18 22:10:15 +00:00
JOB_STATUS_AWAITING_VALIDITY = 0
JOB_STATUS_AWAITING_BANDWIDTH = 1
JOB_STATUS_AWAITING_LOGIN = 2
JOB_STATUS_AWAITING_SLOT = 3
JOB_STATUS_RUNNING = 4
job_status_str_lookup = {}
job_status_str_lookup[ JOB_STATUS_AWAITING_VALIDITY ] = 'waiting for validation'
job_status_str_lookup[ JOB_STATUS_AWAITING_BANDWIDTH ] = 'waiting for bandwidth'
job_status_str_lookup[ JOB_STATUS_AWAITING_LOGIN ] = 'waiting for login'
job_status_str_lookup[ JOB_STATUS_AWAITING_SLOT ] = 'waiting for free work slot'
2018-04-18 22:10:15 +00:00
job_status_str_lookup[ JOB_STATUS_RUNNING ] = 'running'
class NetworkEngine( object ):
2017-10-25 21:45:15 +00:00
2018-04-18 22:10:15 +00:00
def __init__( self, controller, bandwidth_manager, session_manager, domain_manager, login_manager ):
2015-10-21 21:53:10 +00:00
2018-04-18 22:10:15 +00:00
self.controller = controller
2015-10-21 21:53:10 +00:00
2018-04-18 22:10:15 +00:00
self.bandwidth_manager = bandwidth_manager
self.session_manager = session_manager
self.domain_manager = domain_manager
self.login_manager = login_manager
2015-10-21 21:53:10 +00:00
2018-04-18 22:10:15 +00:00
self.bandwidth_manager.engine = self
self.session_manager.engine = self
self.domain_manager.engine = self
self.login_manager.engine = self
2015-10-21 21:53:10 +00:00
2018-04-18 22:10:15 +00:00
self._lock = threading.Lock()
2015-10-21 21:53:10 +00:00
2018-09-26 19:05:12 +00:00
self.RefreshOptions()
2018-04-18 22:10:15 +00:00
self._new_work_to_do = threading.Event()
2015-10-21 21:53:10 +00:00
2018-11-21 22:22:36 +00:00
self._domains_to_login = []
2018-08-22 21:10:59 +00:00
self._active_domains_counter = collections.Counter()
2018-04-18 22:10:15 +00:00
self._jobs_awaiting_validity = []
self._current_validation_process = None
self._jobs_awaiting_bandwidth = []
self._jobs_awaiting_login = []
self._current_login_process = None
self._jobs_awaiting_slot = []
self._jobs_running = []
2017-10-25 21:45:15 +00:00
2018-04-18 22:10:15 +00:00
self._pause_all_new_network_traffic = self.controller.new_options.GetBoolean( 'pause_all_new_network_traffic' )
2015-10-21 21:53:10 +00:00
2018-04-18 22:10:15 +00:00
self._is_running = False
self._is_shutdown = False
self._local_shutdown = False
2015-10-21 21:53:10 +00:00
2018-09-26 19:05:12 +00:00
self.controller.sub( self, 'RefreshOptions', 'notify_new_options' )
2018-04-18 22:10:15 +00:00
def AddJob( self, job ):
2015-10-21 21:53:10 +00:00
2018-07-04 20:48:28 +00:00
if HG.network_report_mode:
2018-08-22 21:10:59 +00:00
HydrusData.ShowText( 'Network Job Added: ' + job._method + ' ' + job._url )
2018-07-04 20:48:28 +00:00
2018-04-18 22:10:15 +00:00
with self._lock:
2015-10-21 21:53:10 +00:00
2018-04-18 22:10:15 +00:00
job.engine = self
2017-05-31 21:50:53 +00:00
2018-04-18 22:10:15 +00:00
self._jobs_awaiting_validity.append( job )
2017-05-31 21:50:53 +00:00
2018-04-18 22:10:15 +00:00
self._new_work_to_do.set()
2017-06-07 22:05:15 +00:00
2018-11-21 22:22:36 +00:00
def ForceLogins( self, domains_to_login ):
with self._lock:
self._domains_to_login.extend( domains_to_login )
self._domains_to_login = HydrusData.DedupeList( self._domains_to_login )
2018-04-18 22:10:15 +00:00
def GetJobsSnapshot( self ):
2017-08-16 21:58:06 +00:00
2018-04-18 22:10:15 +00:00
with self._lock:
2017-08-16 21:58:06 +00:00
2018-04-18 22:10:15 +00:00
jobs = []
2017-08-16 21:58:06 +00:00
2018-04-18 22:10:15 +00:00
jobs.extend( ( ( JOB_STATUS_AWAITING_VALIDITY, j ) for j in self._jobs_awaiting_validity ) )
jobs.extend( ( ( JOB_STATUS_AWAITING_BANDWIDTH, j ) for j in self._jobs_awaiting_bandwidth ) )
jobs.extend( ( ( JOB_STATUS_AWAITING_LOGIN, j ) for j in self._jobs_awaiting_login ) )
jobs.extend( ( ( JOB_STATUS_AWAITING_SLOT, j ) for j in self._jobs_awaiting_slot ) )
jobs.extend( ( ( JOB_STATUS_RUNNING, j ) for j in self._jobs_running ) )
2017-06-07 22:05:15 +00:00
2018-04-18 22:10:15 +00:00
return jobs
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
2018-09-19 21:54:51 +00:00
def IsBusy( self ):
with self._lock:
return len( self._jobs_awaiting_validity ) + len( self._jobs_awaiting_bandwidth ) + len( self._jobs_awaiting_login ) + len( self._jobs_awaiting_slot ) + len( self._jobs_running ) > 50
2018-04-18 22:10:15 +00:00
def IsRunning( self ):
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
with self._lock:
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
return self._is_running
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
def IsShutdown( self ):
2017-08-16 21:58:06 +00:00
2018-04-18 22:10:15 +00:00
with self._lock:
2017-08-16 21:58:06 +00:00
2018-04-18 22:10:15 +00:00
return self._is_shutdown
2017-08-16 21:58:06 +00:00
2017-07-05 21:09:28 +00:00
2018-04-18 22:10:15 +00:00
def MainLoop( self ):
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
def ProcessValidationJob( job ):
2017-06-28 20:23:21 +00:00
2018-04-18 22:10:15 +00:00
if job.IsDone():
return False
elif job.IsAsleep():
2017-06-28 20:23:21 +00:00
2018-04-18 22:10:15 +00:00
return True
2017-06-28 20:23:21 +00:00
2018-04-18 22:10:15 +00:00
elif not job.IsValid():
2017-06-28 20:23:21 +00:00
2018-04-18 22:10:15 +00:00
if job.CanValidateInPopup():
if self._current_validation_process is None:
validation_process = job.GenerateValidationPopupProcess()
self.controller.CallToThread( validation_process.Start )
self._current_validation_process = validation_process
2019-01-09 22:59:03 +00:00
job.SetStatus( 'validation presented to user\u2026' )
2018-04-18 22:10:15 +00:00
else:
2019-01-09 22:59:03 +00:00
job.SetStatus( 'waiting in user validation queue\u2026' )
2018-04-18 22:10:15 +00:00
job.Sleep( 5 )
return True
else:
2019-01-09 22:59:03 +00:00
error_text = 'network context not currently valid!'
2018-04-18 22:10:15 +00:00
job.SetError( HydrusExceptions.ValidationException( error_text ), error_text )
2017-06-28 20:23:21 +00:00
return False
2018-04-18 22:10:15 +00:00
else:
self._jobs_awaiting_bandwidth.append( job )
return False
2017-06-28 20:23:21 +00:00
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
def ProcessCurrentValidationJob():
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
if self._current_validation_process is not None:
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
if self._current_validation_process.IsDone():
2017-07-27 00:47:13 +00:00
2018-04-18 22:10:15 +00:00
self._current_validation_process = None
2017-07-27 00:47:13 +00:00
2017-06-28 20:23:21 +00:00
2018-04-18 22:10:15 +00:00
def ProcessBandwidthJob( job ):
2017-06-28 20:23:21 +00:00
2018-04-18 22:10:15 +00:00
if job.IsDone():
2017-06-28 20:23:21 +00:00
2018-04-18 22:10:15 +00:00
return False
elif job.IsAsleep():
return True
2020-07-22 20:59:16 +00:00
elif self._pause_all_new_network_traffic:
job.SetStatus( 'all new network traffic is paused\u2026' )
job.Sleep( 2 )
return True
2020-06-17 21:31:54 +00:00
elif not job.TryToStartBandwidth():
2018-04-18 22:10:15 +00:00
return True
2017-06-28 20:23:21 +00:00
else:
2018-04-18 22:10:15 +00:00
self._jobs_awaiting_login.append( job )
return False
2017-06-28 20:23:21 +00:00
2017-07-19 21:21:41 +00:00
2018-11-21 22:22:36 +00:00
def ProcessForceLogins():
if len( self._domains_to_login ) > 0 and self._current_login_process is None:
try:
login_domain = self._domains_to_login.pop( 0 )
login_process = self.login_manager.GenerateLoginProcessForDomain( login_domain )
except Exception as e:
HydrusData.ShowException( e )
return
self.controller.CallToThread( login_process.Start )
self._current_login_process = login_process
2018-04-18 22:10:15 +00:00
def ProcessLoginJob( job ):
2017-07-19 21:21:41 +00:00
2018-04-18 22:10:15 +00:00
if job.IsDone():
return False
elif job.IsAsleep():
return True
elif job.NeedsLogin():
2017-07-19 21:21:41 +00:00
2018-04-18 22:10:15 +00:00
try:
2017-07-19 21:21:41 +00:00
2018-04-18 22:10:15 +00:00
job.CheckCanLogin()
2017-07-19 21:21:41 +00:00
2018-04-18 22:10:15 +00:00
except Exception as e:
2018-10-31 21:41:14 +00:00
if job.WillingToWaitOnInvalidLogin():
2019-01-09 22:59:03 +00:00
job.SetStatus( str( e ) )
2018-10-31 21:41:14 +00:00
job.Sleep( 60 )
return True
else:
2018-11-28 22:31:04 +00:00
if job.IsHydrusJob():
2019-07-03 22:49:27 +00:00
message = 'This hydrus service (' + job.GetLoginNetworkContext().ToString() + ') could not do work because: {}'.format( str( e ) )
2018-11-28 22:31:04 +00:00
else:
2019-07-03 22:49:27 +00:00
message = 'This job\'s network context (' + job.GetLoginNetworkContext().ToString() + ') seems to have an invalid login. The error was: {}'.format( str( e ) )
2018-11-28 22:31:04 +00:00
job.Cancel( message )
2018-10-31 21:41:14 +00:00
return False
2017-07-19 21:21:41 +00:00
2018-04-18 22:10:15 +00:00
if self._current_login_process is None:
2018-10-17 21:00:09 +00:00
try:
login_process = job.GenerateLoginProcess()
except Exception as e:
HydrusData.ShowException( e )
2019-01-09 22:59:03 +00:00
job.SetStatus( str( e ) )
2018-10-17 21:00:09 +00:00
job.Sleep( 60 )
return True
2018-04-18 22:10:15 +00:00
self.controller.CallToThread( login_process.Start )
self._current_login_process = login_process
2019-01-09 22:59:03 +00:00
job.SetStatus( 'logging in\u2026' )
2018-04-18 22:10:15 +00:00
else:
2019-01-09 22:59:03 +00:00
job.SetStatus( 'waiting in login queue\u2026' )
2018-04-18 22:10:15 +00:00
return True
else:
self._jobs_awaiting_slot.append( job )
return False
2017-07-19 21:21:41 +00:00
2018-04-18 22:10:15 +00:00
def ProcessCurrentLoginJob():
2017-07-19 21:21:41 +00:00
2018-04-18 22:10:15 +00:00
if self._current_login_process is not None:
2017-07-19 21:21:41 +00:00
2018-04-18 22:10:15 +00:00
if self._current_login_process.IsDone():
2017-07-19 21:21:41 +00:00
2018-04-18 22:10:15 +00:00
self._current_login_process = None
2017-07-19 21:21:41 +00:00
2017-07-12 20:03:45 +00:00
2018-04-18 22:10:15 +00:00
def ProcessReadyJob( job ):
2017-08-16 21:58:06 +00:00
2018-04-18 22:10:15 +00:00
if job.IsDone():
2017-07-12 20:03:45 +00:00
2018-04-18 22:10:15 +00:00
return False
2017-07-12 20:03:45 +00:00
2018-09-12 21:36:26 +00:00
elif job.IsAsleep():
return True
2018-04-18 22:10:15 +00:00
elif len( self._jobs_running ) < self.MAX_JOBS:
if self._pause_all_new_network_traffic:
2017-07-12 20:03:45 +00:00
2019-01-09 22:59:03 +00:00
job.SetStatus( 'all new network traffic is paused\u2026' )
2017-06-21 21:15:59 +00:00
2018-09-12 21:36:26 +00:00
job.Sleep( 2 )
2018-04-18 22:10:15 +00:00
return True
2017-06-21 21:15:59 +00:00
2018-07-04 20:48:28 +00:00
elif self.controller.JustWokeFromSleep():
2019-01-09 22:59:03 +00:00
job.SetStatus( 'looks like computer just woke up, waiting a bit' )
2018-07-04 20:48:28 +00:00
2018-09-12 21:36:26 +00:00
job.Sleep( 5 )
2018-07-04 20:48:28 +00:00
return True
2018-08-22 21:10:59 +00:00
elif self._active_domains_counter[ job.GetSecondLevelDomain() ] >= self.MAX_JOBS_PER_DOMAIN:
job.SetStatus( 'waiting for other jobs on this domain to finish' )
2018-08-22 21:10:59 +00:00
2018-09-12 21:36:26 +00:00
job.Sleep( 2 )
2018-08-22 21:10:59 +00:00
return True
elif not job.TokensOK():
return True
2020-04-16 00:09:42 +00:00
elif not job.DomainOK():
return True
2018-04-18 22:10:15 +00:00
else:
2017-06-21 21:15:59 +00:00
2018-08-22 21:10:59 +00:00
if HG.network_report_mode:
HydrusData.ShowText( 'Network Job Starting: ' + job._method + ' ' + job._url )
self._active_domains_counter[ job.GetSecondLevelDomain() ] += 1
2018-04-18 22:10:15 +00:00
self.controller.CallToThread( job.Start )
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
self._jobs_running.append( job )
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
return False
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
else:
2017-06-21 21:15:59 +00:00
job.SetStatus( 'waiting for other jobs to finish\u2026' )
2017-07-27 00:47:13 +00:00
2018-04-18 22:10:15 +00:00
return True
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
def ProcessRunningJob( job ):
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
if job.IsDone():
2017-06-21 21:15:59 +00:00
2018-08-22 21:10:59 +00:00
if HG.network_report_mode:
HydrusData.ShowText( 'Network Job Done: ' + job._method + ' ' + job._url )
second_level_domain = job.GetSecondLevelDomain()
self._active_domains_counter[ second_level_domain ] -= 1
if self._active_domains_counter[ second_level_domain ] == 0:
del self._active_domains_counter[ second_level_domain ]
2018-04-18 22:10:15 +00:00
return False
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
else:
2017-09-27 21:52:54 +00:00
2018-04-18 22:10:15 +00:00
return True
2017-09-27 21:52:54 +00:00
2017-09-06 20:18:20 +00:00
2018-04-18 22:10:15 +00:00
self._is_running = True
2019-07-31 22:01:02 +00:00
while not ( self._local_shutdown or HG.model_shutdown ):
2017-09-06 20:18:20 +00:00
2018-04-18 22:10:15 +00:00
with self._lock:
2017-09-06 20:18:20 +00:00
2019-09-25 21:34:18 +00:00
self._jobs_awaiting_validity = list( filter( ProcessValidationJob, self._jobs_awaiting_validity ) )
2017-09-06 20:18:20 +00:00
2018-04-18 22:10:15 +00:00
ProcessCurrentValidationJob()
2017-09-06 20:18:20 +00:00
2019-09-25 21:34:18 +00:00
self._jobs_awaiting_bandwidth = list( filter( ProcessBandwidthJob, self._jobs_awaiting_bandwidth ) )
2017-09-06 20:18:20 +00:00
2018-11-21 22:22:36 +00:00
ProcessForceLogins()
2019-09-25 21:34:18 +00:00
self._jobs_awaiting_login = list( filter( ProcessLoginJob, self._jobs_awaiting_login ) )
2017-10-25 21:45:15 +00:00
2018-04-18 22:10:15 +00:00
ProcessCurrentLoginJob()
2017-10-25 21:45:15 +00:00
2019-09-25 21:34:18 +00:00
self._jobs_awaiting_slot = list( filter( ProcessReadyJob, self._jobs_awaiting_slot ) )
2017-10-25 21:45:15 +00:00
2019-09-25 21:34:18 +00:00
self._jobs_running = list( filter( ProcessRunningJob, self._jobs_running ) )
2017-10-25 21:45:15 +00:00
2018-04-18 22:10:15 +00:00
# we want to catch the rollover of the second for bandwidth jobs
2017-06-28 20:23:21 +00:00
2018-04-18 22:10:15 +00:00
now_with_subsecond = time.time()
subsecond_part = now_with_subsecond % 1
2017-06-28 20:23:21 +00:00
2018-04-18 22:10:15 +00:00
time_until_next_second = 1.0 - subsecond_part
2017-06-28 20:23:21 +00:00
2018-04-18 22:10:15 +00:00
self._new_work_to_do.wait( time_until_next_second )
2017-07-27 00:47:13 +00:00
2018-04-18 22:10:15 +00:00
self._new_work_to_do.clear()
2017-06-28 20:23:21 +00:00
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
self._is_running = False
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
self._is_shutdown = True
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
def PausePlayNewJobs( self ):
2018-03-22 00:03:33 +00:00
2018-04-18 22:10:15 +00:00
self._pause_all_new_network_traffic = not self._pause_all_new_network_traffic
2017-06-21 21:15:59 +00:00
2018-04-18 22:10:15 +00:00
self.controller.new_options.SetBoolean( 'pause_all_new_network_traffic', self._pause_all_new_network_traffic )
2017-06-21 21:15:59 +00:00
2018-09-26 19:05:12 +00:00
def RefreshOptions( self ):
with self._lock:
self.MAX_JOBS = self.controller.new_options.GetInteger( 'max_network_jobs' )
self.MAX_JOBS_PER_DOMAIN = self.controller.new_options.GetInteger( 'max_network_jobs_per_domain' )
2018-04-18 22:10:15 +00:00
def Shutdown( self ):
2017-07-05 21:09:28 +00:00
2018-04-18 22:10:15 +00:00
self._local_shutdown = True
2017-07-05 21:09:28 +00:00
2018-04-18 22:10:15 +00:00
self._new_work_to_do.set()
2017-07-05 21:09:28 +00:00