hydrus/hydrus/client/networking/ClientNetworking.py

529 lines
17 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
import typing
2015-10-21 21:53:10 +00:00
2023-09-06 19:49:46 +00:00
from hydrus.core import HydrusConstants as HC
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
2023-04-19 20:38:13 +00:00
from hydrus.core import HydrusTime
2017-01-25 22:56:55 +00:00
from hydrus.client.networking import ClientNetworkingBandwidth
from hydrus.client.networking import ClientNetworkingSessions
from hydrus.client.networking import ClientNetworkingDomain
from hydrus.client.networking import ClientNetworkingJobs
from hydrus.client.networking import ClientNetworkingLogin
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
2022-03-30 20:28:13 +00:00
job_status_str_lookup = {
JOB_STATUS_AWAITING_VALIDITY : 'waiting for validation',
JOB_STATUS_AWAITING_BANDWIDTH : 'waiting for bandwidth',
JOB_STATUS_AWAITING_LOGIN : 'waiting for login',
JOB_STATUS_AWAITING_SLOT : 'waiting for free work slot',
JOB_STATUS_RUNNING : 'running'
}
2018-04-18 22:10:15 +00:00
class NetworkEngine( object ):
2017-10-25 21:45:15 +00:00
def __init__(
self,
controller,
bandwidth_manager: ClientNetworkingBandwidth.NetworkBandwidthManager,
session_manager: ClientNetworkingSessions.NetworkSessionManager,
domain_manager: ClientNetworkingDomain.NetworkDomainManager,
login_manager: ClientNetworkingLogin.NetworkLoginManager
):
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.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
2022-03-30 20:28:13 +00:00
self.MAX_JOBS = 1
self.MAX_JOBS_PER_DOMAIN = 1
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 _AssignCurrentLoginProcess( self, login_process: typing.Optional[ ClientNetworkingLogin.LoginProcess ] ):
self._current_login_process = login_process
self.login_manager.SetCurrentLoginProcess( login_process )
def AddJob( self, job: ClientNetworkingJobs.NetworkJob ):
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
def ForceLogins( self, domains_to_login: typing.Collection[ str ] ):
2018-11-21 22:22:36 +00:00
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
def IsBusy( self ) -> bool:
2018-09-19 21:54:51 +00:00
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
def IsRunning( self ) -> bool:
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
def IsShutdown( self ) -> bool:
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
def ProcessValidationJob( job: ClientNetworkingJobs.NetworkJob ):
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
2023-09-06 19:49:46 +00:00
job.SetStatus( 'validation presented to user' + HC.UNICODE_ELLIPSIS )
2018-04-18 22:10:15 +00:00
else:
2023-09-06 19:49:46 +00:00
job.SetStatus( 'waiting in user validation queue' + HC.UNICODE_ELLIPSIS )
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
def ProcessBandwidthJob( job: ClientNetworkingJobs.NetworkJob ):
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:
2023-09-06 19:49:46 +00:00
job.SetStatus( 'all new network traffic is paused' + HC.UNICODE_ELLIPSIS )
2020-07-22 20:59:16 +00:00
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._AssignCurrentLoginProcess( login_process )
2018-11-21 22:22:36 +00:00
def ProcessLoginJob( job: ClientNetworkingJobs.NetworkJob ):
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.CurrentlyNeedsLogin():
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:
job_login_network_context = job.GetLoginNetworkContext()
2018-11-28 22:31:04 +00:00
if job.IsHydrusJob():
message = f'This hydrus service "{job_login_network_context.ToString()}" could not do work because: {e}'
2018-11-28 22:31:04 +00:00
else:
message = f'This job\'s network context "{job_login_network_context.ToString()}" seems to have an invalid login. The error was: {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._AssignCurrentLoginProcess( login_process )
2018-04-18 22:10:15 +00:00
2023-09-06 19:49:46 +00:00
job.SetStatus( 'logging in' + HC.UNICODE_ELLIPSIS )
2018-04-18 22:10:15 +00:00
else:
2023-09-06 19:49:46 +00:00
job.SetStatus( 'waiting in login queue' + HC.UNICODE_ELLIPSIS )
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
self._AssignCurrentLoginProcess( None )
2017-07-19 21:21:41 +00:00
2017-07-12 20:03:45 +00:00
def ProcessReadyJob( job: ClientNetworkingJobs.NetworkJob ):
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
2023-09-06 19:49:46 +00:00
job.SetStatus( 'all new network traffic is paused' + HC.UNICODE_ELLIPSIS )
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
2023-09-06 19:49:46 +00:00
job.SetStatus( 'waiting for other jobs to finish' + HC.UNICODE_ELLIPSIS )
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: ClientNetworkingJobs.NetworkJob ):
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
2021-12-22 22:31:23 +00:00
if not self._pause_all_new_network_traffic:
self.controller.pub( 'notify_network_traffic_unpaused' )
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