hydrus/include/ClientNetworking.py

2901 lines
88 KiB
Python
Raw Normal View History

2017-06-28 20:23:21 +00:00
import ClientConstants as CC
2017-10-04 17:51:58 +00:00
import ClientNetworkingDomain
2017-06-07 22:05:15 +00:00
import collections
2017-06-28 20:23:21 +00:00
import cPickle
2017-06-21 21:15:59 +00:00
import cStringIO
2015-10-21 21:53:10 +00:00
import HydrusConstants as HC
import HydrusExceptions
2017-03-02 02:14:56 +00:00
import HydrusNetwork
2017-06-07 22:05:15 +00:00
import HydrusNetworking
2015-11-04 22:30:28 +00:00
import HydrusPaths
2015-10-21 21:53:10 +00:00
import HydrusSerialisable
2016-01-06 21:17:20 +00:00
import errno
2015-10-21 21:53:10 +00:00
import httplib
import os
2017-06-21 21:15:59 +00:00
import random
2016-02-24 21:42:54 +00:00
import requests
2017-06-21 21:15:59 +00:00
import urllib3
from urllib3.exceptions import InsecureRequestWarning
2016-01-06 21:17:20 +00:00
import socket
2015-10-21 21:53:10 +00:00
import socks
2016-09-28 18:48:01 +00:00
import ssl
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
import urllib
import urlparse
import yaml
import HydrusData
import itertools
2017-05-10 21:33:58 +00:00
import HydrusGlobals as HG
2015-10-21 21:53:10 +00:00
2017-06-21 21:15:59 +00:00
urllib3.disable_warnings( InsecureRequestWarning )
2017-01-25 22:56:55 +00:00
2015-10-21 21:53:10 +00:00
def AddHydrusCredentialsToHeaders( credentials, request_headers ):
if credentials.HasAccessKey():
access_key = credentials.GetAccessKey()
2017-03-02 02:14:56 +00:00
request_headers[ 'Hydrus-Key' ] = access_key.encode( 'hex' )
else:
raise Exception( 'No access key!' )
2015-10-21 21:53:10 +00:00
def AddHydrusSessionKeyToHeaders( service_key, request_headers ):
2017-05-10 21:33:58 +00:00
session_manager = HG.client_controller.GetClientSessionManager()
2015-10-21 21:53:10 +00:00
session_key = session_manager.GetSessionKey( service_key )
request_headers[ 'Cookie' ] = 'session_key=' + session_key.encode( 'hex' )
def AddCookiesToHeaders( cookies, request_headers ):
request_headers[ 'Cookie' ] = '; '.join( [ k + '=' + v for ( k, v ) in cookies.items() ] )
def CheckHydrusVersion( service_key, service_type, response_headers ):
service_string = HC.service_string_lookup[ service_type ]
if 'server' not in response_headers or service_string not in response_headers[ 'server' ]:
raise HydrusExceptions.WrongServiceTypeException( 'Target was not a ' + service_string + '!' )
server_header = response_headers[ 'server' ]
( service_string_gumpf, network_version ) = server_header.split( '/' )
network_version = int( network_version )
if network_version != HC.NETWORK_VERSION:
2017-03-02 02:14:56 +00:00
if network_version > HC.NETWORK_VERSION:
2015-10-21 21:53:10 +00:00
2017-03-02 02:14:56 +00:00
message = 'Your client is out of date; please download the latest release.'
2015-10-21 21:53:10 +00:00
2017-03-02 02:14:56 +00:00
else:
2015-10-21 21:53:10 +00:00
2017-03-02 02:14:56 +00:00
message = 'The server is out of date; please ask its admin to update to the latest release.'
2015-10-21 21:53:10 +00:00
2017-03-02 02:14:56 +00:00
raise HydrusExceptions.NetworkVersionException( 'Network version mismatch! The server\'s network version was ' + str( network_version ) + ', whereas your client\'s is ' + str( HC.NETWORK_VERSION ) + '! ' + message )
2015-10-21 21:53:10 +00:00
2017-09-06 20:18:20 +00:00
def CombineGETURLWithParameters( url, params_dict ):
def make_safe( text ):
# convert unicode to raw bytes
# quote that to be url-safe, ignoring the default '/' 'safe' character
return urllib.quote( HydrusData.ToByteString( text ), '' )
request_string = '&'.join( ( make_safe( key ) + '=' + make_safe( value ) for ( key, value ) in params_dict.items() ) )
return url + '?' + request_string
2017-07-05 21:09:28 +00:00
def ConvertStatusCodeAndDataIntoExceptionInfo( status_code, data ):
error_text = data
if len( error_text ) > 1024:
large_chunk = error_text[:4096]
smaller_chunk = large_chunk[:256]
HydrusData.DebugPrint( large_chunk )
error_text = 'The server\'s error text was too long to display. The first part follows, while a larger chunk has been written to the log.'
error_text += os.linesep
error_text += smaller_chunk
if status_code == 304:
eclass = HydrusExceptions.NotModifiedException
elif status_code == 401:
eclass = HydrusExceptions.PermissionException
elif status_code == 403:
eclass = HydrusExceptions.ForbiddenException
elif status_code == 404:
eclass = HydrusExceptions.NotFoundException
elif status_code == 419:
eclass = HydrusExceptions.SessionException
elif status_code == 426:
eclass = HydrusExceptions.NetworkVersionException
elif status_code >= 500:
eclass = HydrusExceptions.ServerException
else:
eclass = HydrusExceptions.NetworkException
e = eclass( error_text )
return ( e, error_text )
2016-11-02 21:09:14 +00:00
def RequestsGet( url, params = None, stream = False, headers = None ):
2016-02-24 21:42:54 +00:00
2016-11-02 21:09:14 +00:00
if headers is None:
headers = {}
headers[ 'User-Agent' ] = 'hydrus/' + str( HC.NETWORK_VERSION )
response = requests.get( url, params = params, stream = stream, headers = headers )
2016-02-24 21:42:54 +00:00
RequestsCheckResponse( response )
return response
2017-08-30 20:27:47 +00:00
# this is an old redirect thing to figure out redirected gallery page destinations without hitting them now. note the allow_redirects param
2017-05-17 21:53:02 +00:00
def RequestsGetRedirectURL( url, session = None ):
if session is None:
session = requests.Session()
response = session.get( url, allow_redirects = False )
if 'location' in response.headers:
location_header = response.headers[ 'location' ]
new_url = urlparse.urljoin( url, location_header )
return new_url
else:
return url
2016-11-02 21:09:14 +00:00
def RequestsPost( url, data = None, files = None, headers = None ):
if headers is None:
headers = {}
headers[ 'User-Agent' ] = 'hydrus/' + str( HC.NETWORK_VERSION )
2016-02-24 21:42:54 +00:00
response = requests.post( url, data = data, files = files )
RequestsCheckResponse( response )
return response
def RequestsCheckResponse( response ):
if not response.ok:
error_text = response.content
if len( error_text ) > 1024:
large_chunk = error_text[:4096]
smaller_chunk = large_chunk[:256]
HydrusData.DebugPrint( large_chunk )
error_text = 'The server\'s error text was too long to display. The first part follows, while a larger chunk has been written to the log.'
error_text += os.linesep
error_text += smaller_chunk
if response.status_code == 304:
eclass = HydrusExceptions.NotModifiedException
elif response.status_code == 401:
eclass = HydrusExceptions.PermissionException
elif response.status_code == 403:
eclass = HydrusExceptions.ForbiddenException
elif response.status_code == 404:
eclass = HydrusExceptions.NotFoundException
elif response.status_code == 419:
eclass = HydrusExceptions.SessionException
elif response.status_code == 426:
eclass = HydrusExceptions.NetworkVersionException
2017-06-07 22:05:15 +00:00
elif response.status_code >= 500:
eclass = HydrusExceptions.ServerException
2016-02-24 21:42:54 +00:00
else:
eclass = HydrusExceptions.NetworkException
raise eclass( error_text )
2015-10-21 21:53:10 +00:00
def ParseURL( url ):
try:
2017-05-17 21:53:02 +00:00
if url.startswith( '//' ):
url = url[2:]
2015-10-21 21:53:10 +00:00
starts_http = url.startswith( 'http://' )
starts_https = url.startswith( 'https://' )
2016-11-02 21:09:14 +00:00
if not starts_http and not starts_https:
url = 'http://' + url
2015-10-21 21:53:10 +00:00
parse_result = urlparse.urlparse( url )
scheme = parse_result.scheme
hostname = parse_result.hostname
port = parse_result.port
if hostname is None: location = None
else: location = ( scheme, hostname, port )
path = parse_result.path
# this happens when parsing 'index.html' rather than 'hostname/index.html' or '/index.html'
2016-11-02 21:09:14 +00:00
if not path.startswith( '/' ):
path = '/' + path
2015-10-21 21:53:10 +00:00
query = parse_result.query
2016-11-02 21:09:14 +00:00
except:
raise Exception( 'Could not parse the URL: ' + HydrusData.ToUnicode( url ) )
2015-10-21 21:53:10 +00:00
return ( location, path, query )
2017-06-07 22:05:15 +00:00
def SerialiseSession( session ):
# move this to the new sessionmanager
cookies = session.cookies.copy()
items = requests.utils.dict_from_cookiejar( cookies )
# apply these to something serialisable
# do the reverse, add_dict_to_cookiejar, to set them back again in a new session
2015-10-21 21:53:10 +00:00
def SetProxy( proxytype, host, port, username = None, password = None ):
if proxytype == 'http': proxytype = socks.PROXY_TYPE_HTTP
elif proxytype == 'socks4': proxytype = socks.PROXY_TYPE_SOCKS4
elif proxytype == 'socks5': proxytype = socks.PROXY_TYPE_SOCKS5
2015-12-23 22:51:04 +00:00
socks.setdefaultproxy( proxy_type = proxytype, addr = host, port = port, username = username, password = password )
2015-10-21 21:53:10 +00:00
socks.wrapmodule( httplib )
2017-01-25 22:56:55 +00:00
def StreamResponseToFile( job_key, response, f ):
if 'content-length' in response.headers:
gauge_range = int( response.headers[ 'content-length' ] )
else:
gauge_range = None
gauge_value = 0
try:
for chunk in response.iter_content( chunk_size = 65536 ):
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
raise HydrusExceptions.CancelledException()
f.write( chunk )
gauge_value += len( chunk )
if gauge_range is None:
text = 'downloading - ' + HydrusData.ConvertIntToBytes( gauge_value )
else:
text = 'downloading - ' + HydrusData.ConvertValueRangeToBytes( gauge_value, gauge_range )
job_key.SetVariable( 'popup_download', ( text, gauge_value, gauge_range ) )
finally:
job_key.DeleteVariable( 'popup_download' )
2015-10-21 21:53:10 +00:00
class HTTPConnectionManager( object ):
def __init__( self ):
self._connections = {}
self._lock = threading.Lock()
2017-08-09 21:33:51 +00:00
HG.client_controller.CallToThreadLongRunning( self.DAEMONMaintainConnections )
2015-10-21 21:53:10 +00:00
2016-12-14 21:19:07 +00:00
def _DoRequest( self, method, location, path, query, request_headers, body, follow_redirects = True, report_hooks = None, temp_path = None, hydrus_network = False, num_redirects_permitted = 4 ):
2015-10-21 21:53:10 +00:00
if report_hooks is None: report_hooks = []
2016-12-14 21:19:07 +00:00
connection = self._GetConnection( location, hydrus_network )
2015-10-21 21:53:10 +00:00
try:
if query == '':
path_and_query = path
else:
path_and_query = path + '?' + query
with connection.lock:
( parsed_response, redirect_info, size_of_response, response_headers, cookies ) = connection.Request( method, path_and_query, request_headers, body, report_hooks = report_hooks, temp_path = temp_path )
if redirect_info is None or not follow_redirects:
return ( parsed_response, size_of_response, response_headers, cookies )
else:
2016-10-19 20:02:56 +00:00
if num_redirects_permitted == 0:
message = 'Too many redirects!'
message += os.linesep
message += 'Location was: ' + HydrusData.ToUnicode( location ) + ' and path and query was ' + path_and_query + '.'
message += os.linesep
message += 'Redirect info was: ' + HydrusData.ToUnicode( redirect_info )
2016-10-26 20:45:34 +00:00
raise HydrusExceptions.RedirectionException( message )
2016-10-19 20:02:56 +00:00
2015-10-21 21:53:10 +00:00
( new_method, new_url ) = redirect_info
( new_location, new_path, new_query ) = ParseURL( new_url )
2016-10-26 20:45:34 +00:00
if new_location is None:
new_location = location
if new_method == method and new_location == location and new_path == path and new_query == query:
message = 'Encountered a circular redirect!'
message += os.linesep
message += 'Location was: ' + HydrusData.ToUnicode( location ) + ' and path and query was ' + path_and_query + '.'
message += os.linesep
message += 'Redirect info was: ' + HydrusData.ToUnicode( redirect_info )
raise HydrusExceptions.RedirectionException( message )
2015-10-21 21:53:10 +00:00
return self._DoRequest( new_method, new_location, new_path, new_query, request_headers, body, follow_redirects = follow_redirects, report_hooks = report_hooks, temp_path = temp_path, num_redirects_permitted = num_redirects_permitted - 1 )
except:
time.sleep( 2 )
raise
2016-12-14 21:19:07 +00:00
def _GetConnection( self, location, hydrus_network ):
2015-10-21 21:53:10 +00:00
with self._lock:
2016-12-14 21:19:07 +00:00
if ( location, hydrus_network ) not in self._connections:
2015-10-21 21:53:10 +00:00
2016-12-14 21:19:07 +00:00
connection = HTTPConnection( location, hydrus_network )
2015-10-21 21:53:10 +00:00
2016-12-14 21:19:07 +00:00
self._connections[ ( location, hydrus_network ) ] = connection
2015-10-21 21:53:10 +00:00
2016-12-14 21:19:07 +00:00
return self._connections[ ( location, hydrus_network ) ]
2015-10-21 21:53:10 +00:00
2016-12-14 21:19:07 +00:00
def Request( self, method, url, request_headers = None, body = '', return_cookies = False, report_hooks = None, temp_path = None, hydrus_network = False ):
2015-10-21 21:53:10 +00:00
if request_headers is None: request_headers = {}
( location, path, query ) = ParseURL( url )
follow_redirects = not return_cookies
2016-12-14 21:19:07 +00:00
( response, size_of_response, response_headers, cookies ) = self._DoRequest( method, location, path, query, request_headers, body, follow_redirects = follow_redirects, report_hooks = report_hooks, temp_path = temp_path, hydrus_network = hydrus_network )
2015-10-21 21:53:10 +00:00
2016-12-14 21:19:07 +00:00
if hydrus_network:
2015-10-21 21:53:10 +00:00
return ( response, size_of_response, response_headers, cookies )
elif return_cookies:
return ( response, cookies )
else:
return response
def DAEMONMaintainConnections( self ):
while True:
2017-05-10 21:33:58 +00:00
if HG.model_shutdown:
2015-11-04 22:30:28 +00:00
break
2015-10-21 21:53:10 +00:00
last_checked = 0
if HydrusData.GetNow() - last_checked > 30:
with self._lock:
connections_copy = dict( self._connections )
2016-12-14 21:19:07 +00:00
for ( ( location, hydrus_network ), connection ) in connections_copy.items():
2015-10-21 21:53:10 +00:00
with connection.lock:
if connection.IsStale():
2017-05-31 21:50:53 +00:00
connection.Close()
2016-12-14 21:19:07 +00:00
del self._connections[ ( location, hydrus_network ) ]
2017-05-31 21:50:53 +00:00
2015-10-21 21:53:10 +00:00
last_checked = HydrusData.GetNow()
2017-05-31 21:50:53 +00:00
time.sleep( 5 )
2015-10-21 21:53:10 +00:00
class HTTPConnection( object ):
2016-12-14 21:19:07 +00:00
def __init__( self, location, hydrus_network ):
2015-10-21 21:53:10 +00:00
( self._scheme, self._host, self._port ) = location
2016-12-14 21:19:07 +00:00
self._hydrus_network = hydrus_network
2015-10-21 21:53:10 +00:00
self._timeout = 30
self.lock = threading.Lock()
self._last_request_time = HydrusData.GetNow()
2017-05-31 21:50:53 +00:00
self._connection = None
2015-10-21 21:53:10 +00:00
self._RefreshConnection()
2016-11-30 20:24:17 +00:00
def _DealWithResponse( self, method, response, parsed_response, size_of_response ):
response_headers = { k : v for ( k, v ) in response.getheaders() if k != 'set-cookie' }
cookies = self._ParseCookies( response.getheader( 'set-cookie' ) )
self._last_request_time = HydrusData.GetNow()
if response.status == 200:
return ( parsed_response, None, size_of_response, response_headers, cookies )
elif response.status in ( 301, 302, 303, 307 ):
location = response.getheader( 'Location' )
if location is None:
raise Exception( 'Received an invalid redirection response.' )
else:
url = location
if ', ' in url:
url = url.split( ', ' )[0]
elif ' ' in url:
# some booru is giving daft redirect responses
HydrusData.Print( url )
url = urllib.quote( HydrusData.ToByteString( url ), safe = '/?=&' )
HydrusData.Print( url )
if not url.startswith( self._scheme ):
# assume it is like 'index.php' or '/index.php', rather than 'http://blah.com/index.php'
2017-05-10 21:33:58 +00:00
if url.startswith( '//' ):
url = self._scheme + ':' + url
else:
if not url.startswith( '/' ):
url = '/' + url
url = self._scheme + '://' + self._host + url
2016-11-30 20:24:17 +00:00
if response.status in ( 301, 307 ):
# 301: moved permanently, repeat request
# 307: moved temporarily, repeat request
redirect_info = ( method, url )
elif response.status in ( 302, 303 ):
# 302: moved temporarily, repeat request (except everyone treats it like 303 for no good fucking reason)
# 303: thanks, now go here with GET
redirect_info = ( HC.GET, url )
return ( parsed_response, redirect_info, size_of_response, response_headers, cookies )
elif response.status == 304: raise HydrusExceptions.NotModifiedException()
else:
if response.status == 401: raise HydrusExceptions.PermissionException( parsed_response )
elif response.status == 403: raise HydrusExceptions.ForbiddenException( parsed_response )
elif response.status == 404: raise HydrusExceptions.NotFoundException( parsed_response )
elif response.status == 419: raise HydrusExceptions.SessionException( parsed_response )
elif response.status == 426: raise HydrusExceptions.NetworkVersionException( parsed_response )
2017-03-02 02:14:56 +00:00
elif response.status == 509: raise HydrusExceptions.BandwidthException( parsed_response )
2016-11-30 20:24:17 +00:00
elif response.status in ( 500, 501, 502, 503 ):
server_header = response.getheader( 'Server' )
if server_header is not None and 'hydrus' in server_header:
hydrus_service = True
else:
hydrus_service = False
if response.status == 503 and hydrus_service:
raise HydrusExceptions.ServerBusyException( 'Server is busy, please try again later.' )
else:
2017-06-07 22:05:15 +00:00
raise HydrusExceptions.ServerException( parsed_response )
2016-11-30 20:24:17 +00:00
2017-06-07 22:05:15 +00:00
else:
raise HydrusExceptions.NetworkException( parsed_response )
2016-11-30 20:24:17 +00:00
def _SendRequestGetResponse( self, method, path_and_query, request_headers, body, report_hooks = None, temp_path = None, attempt_number = 1 ):
if report_hooks is None:
report_hooks = []
if 'User-Agent' not in request_headers:
request_headers[ 'User-Agent' ] = 'hydrus/' + str( HC.NETWORK_VERSION )
2017-01-25 22:56:55 +00:00
if 'Accept' not in request_headers:
request_headers[ 'Accept' ] = '*/*'
2016-11-30 20:24:17 +00:00
path_and_query = HydrusData.ToByteString( path_and_query )
request_headers = { str( k ) : str( v ) for ( k, v ) in request_headers.items() }
( response, attempt_number ) = self._GetInitialResponse( method, path_and_query, request_headers, body, attempt_number = attempt_number )
try:
( parsed_response, size_of_response ) = self._ReadResponse( method, response, report_hooks, temp_path )
return ( response, parsed_response, size_of_response )
except HydrusExceptions.ShouldReattemptNetworkException:
if method == HC.GET:
2016-12-07 22:12:52 +00:00
self._RefreshConnection()
2016-11-30 20:24:17 +00:00
return self._SendRequestGetResponse( method, path_and_query, request_headers, body, report_hooks = report_hooks, temp_path = temp_path, attempt_number = attempt_number + 1 )
else:
raise
def _GetInitialResponse( self, method, path_and_query, request_headers, body, attempt_number = 1 ):
if method == HC.GET: method_string = 'GET'
elif method == HC.POST: method_string = 'POST'
2016-01-06 21:17:20 +00:00
try:
self._connection.request( method_string, path_and_query, headers = request_headers, body = body )
2016-11-30 20:24:17 +00:00
return ( self._connection.getresponse(), attempt_number )
2016-01-06 21:17:20 +00:00
except ( httplib.CannotSendRequest, httplib.BadStatusLine ):
# for some reason, we can't send a request on the current connection, so let's make a new one and try again!
time.sleep( 1 )
if attempt_number <= 3:
self._RefreshConnection()
2016-11-30 20:24:17 +00:00
return self._GetInitialResponse( method, path_and_query, request_headers, body, attempt_number = attempt_number + 1 )
2016-01-06 21:17:20 +00:00
else:
raise
except socket.error as e:
2017-01-11 22:31:30 +00:00
if HC.PLATFORM_WINDOWS:
access_errors = [ errno.EACCES, errno.WSAEACCES ]
connection_reset_errors = [ errno.ECONNRESET, errno.WSAECONNRESET ]
else:
access_errors = [ errno.EACCES ]
connection_reset_errors = [ errno.ECONNRESET ]
if e.errno in access_errors:
2016-01-06 21:17:20 +00:00
text = 'The hydrus client did not have permission to make a connection to ' + HydrusData.ToUnicode( self._host )
if self._port is not None:
text += ' on port ' + HydrusData.ToUnicode( self._port )
text += '. This is usually due to a firewall stopping it.'
raise HydrusExceptions.FirewallException( text )
2017-01-11 22:31:30 +00:00
elif e.errno in connection_reset_errors:
2016-01-06 21:17:20 +00:00
time.sleep( 5 )
if attempt_number <= 3:
self._RefreshConnection()
2016-11-30 20:24:17 +00:00
return self._GetInitialResponse( method, path_and_query, request_headers, body, attempt_number = attempt_number + 1 )
2016-01-06 21:17:20 +00:00
else:
text = 'The hydrus client\'s connection to ' + HydrusData.ToUnicode( self._host ) + ' kept on being reset by the remote host, so the attempt was abandoned.'
raise HydrusExceptions.NetworkException( text )
2016-01-20 23:57:33 +00:00
else:
raise
2016-01-06 21:17:20 +00:00
2016-09-28 18:48:01 +00:00
except ssl.SSLEOFError:
time.sleep( 5 )
if attempt_number <= 3:
self._RefreshConnection()
2016-11-30 20:24:17 +00:00
return self._GetInitialResponse( method_string, path_and_query, request_headers, body, attempt_number = attempt_number + 1 )
2016-09-28 18:48:01 +00:00
else:
text = 'The hydrus client\'s ssl connection to ' + HydrusData.ToUnicode( self._host ) + ' kept terminating abruptly, so the attempt was abandoned.'
raise HydrusExceptions.NetworkException( text )
2016-01-06 21:17:20 +00:00
2016-11-30 20:24:17 +00:00
def _ReadResponse( self, method, response, report_hooks, temp_path = None ):
# in general, don't want to resend POSTs
if method == HC.GET:
recoverable_exc = HydrusExceptions.ShouldReattemptNetworkException
else:
recoverable_exc = HydrusExceptions.NetworkException
2016-09-07 20:01:05 +00:00
try:
if response.status == 200 and temp_path is not None:
size_of_response = self._WriteResponseToPath( response, temp_path, report_hooks )
parsed_response = 'response written to temporary file'
else:
( parsed_response, size_of_response ) = self._ParseResponse( response, report_hooks )
except socket.timeout as e:
2016-11-30 20:24:17 +00:00
raise recoverable_exc( 'Connection timed out during response read.' )
2016-09-07 20:01:05 +00:00
2016-09-14 18:03:59 +00:00
except socket.error as e:
2017-01-11 22:31:30 +00:00
if HC.PLATFORM_WINDOWS:
connection_reset_errors = [ errno.ECONNRESET, errno.WSAECONNRESET ]
else:
connection_reset_errors = [ errno.ECONNRESET ]
if e.errno in connection_reset_errors:
2016-09-14 18:03:59 +00:00
2016-11-30 20:24:17 +00:00
raise recoverable_exc( 'Connection reset by remote host.' )
else:
raise
2016-09-14 18:03:59 +00:00
2016-09-28 18:48:01 +00:00
except ssl.SSLEOFError:
2016-11-30 20:24:17 +00:00
raise recoverable_exc( 'Secure connection terminated abruptly.' )
2016-09-28 18:48:01 +00:00
2016-09-07 20:01:05 +00:00
return ( parsed_response, size_of_response )
2015-10-21 21:53:10 +00:00
def _ParseCookies( self, raw_cookies_string ):
cookies = {}
if raw_cookies_string is not None:
raw_cookie_strings = raw_cookies_string.split( ', ' )
for raw_cookie_string in raw_cookie_strings:
try:
# HSID=AYQEVnDKrdst; Domain=.foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly
if ';' in raw_cookie_string: ( raw_cookie_string, gumpf ) = raw_cookie_string.split( ';', 1 )
( cookie_name, cookie_value ) = raw_cookie_string.split( '=' )
cookies[ cookie_name ] = cookie_value
except Exception as e: pass
return cookies
def _ParseResponse( self, response, report_hooks ):
2016-09-07 20:01:05 +00:00
2015-10-21 21:53:10 +00:00
server_header = response.getheader( 'Server' )
if server_header is not None and 'hydrus' in server_header:
hydrus_service = True
else:
hydrus_service = False
content_length = response.getheader( 'Content-Length' )
if content_length is not None:
content_length = int( content_length )
for hook in report_hooks:
hook( content_length, 0 )
data = ''
2015-11-04 22:30:28 +00:00
for block in HydrusPaths.ReadFileLikeAsBlocks( response ):
2015-10-21 21:53:10 +00:00
2017-05-10 21:33:58 +00:00
if HG.model_shutdown:
2015-11-04 22:30:28 +00:00
raise HydrusExceptions.ShutdownException( 'Application is shutting down!' )
2015-10-21 21:53:10 +00:00
data += block
if content_length is not None:
for hook in report_hooks:
hook( content_length, len( data ) )
if len( data ) > content_length:
raise Exception( 'Response was longer than suggested!' )
size_of_response = len( data )
content_type = response.getheader( 'Content-Type' )
if content_type is None: parsed_response = data
else:
if '; ' in content_type: ( mime_string, additional_info ) = content_type.split( '; ', 1 )
else: ( mime_string, additional_info ) = ( content_type, '' )
if 'charset=' in additional_info:
# this does utf-8, ISO-8859-4, whatever
( gumpf, charset ) = additional_info.split( '=' )
try: parsed_response = data.decode( charset )
except: parsed_response = data
elif content_type == 'application/json':
if hydrus_service:
2017-03-02 02:14:56 +00:00
parsed_response = HydrusNetwork.ParseBodyString( data )
2015-10-21 21:53:10 +00:00
else:
parsed_response = data
elif content_type == 'text/html':
try: parsed_response = data.decode( 'utf-8' )
except: parsed_response = data
else: parsed_response = data
return ( parsed_response, size_of_response )
def _RefreshConnection( self ):
2017-05-10 21:33:58 +00:00
if self._scheme == 'http':
self._connection = httplib.HTTPConnection( self._host, self._port, timeout = self._timeout )
2016-12-14 21:19:07 +00:00
elif self._scheme == 'https':
2017-05-10 21:33:58 +00:00
new_options = HG.client_controller.GetNewOptions()
2017-03-29 19:39:34 +00:00
if self._hydrus_network or not new_options.GetBoolean( 'verify_regular_https' ):
2016-12-14 21:19:07 +00:00
# this negotiates decent encryption but won't check hostname or the certificate
context = ssl.SSLContext( ssl.PROTOCOL_SSLv23 )
2017-05-24 20:28:24 +00:00
2016-12-14 21:19:07 +00:00
context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3
self._connection = httplib.HTTPSConnection( self._host, self._port, timeout = self._timeout, context = context )
else:
2017-05-10 21:33:58 +00:00
context = ssl._create_default_https_context( cafile = requests.certs.where() )
self._connection = httplib.HTTPSConnection( self._host, self._port, timeout = self._timeout, context = context )
2016-12-14 21:19:07 +00:00
2015-10-21 21:53:10 +00:00
try:
self._connection.connect()
except Exception as e:
2015-11-04 22:30:28 +00:00
text = 'Could not connect to ' + HydrusData.ToUnicode( self._host ) + ':'
2015-10-21 21:53:10 +00:00
text += os.linesep * 2
2015-11-04 22:30:28 +00:00
text += HydrusData.ToUnicode( e )
2015-10-21 21:53:10 +00:00
2016-03-16 22:19:14 +00:00
raise HydrusExceptions.NetworkException( text )
2015-10-21 21:53:10 +00:00
def _WriteResponseToPath( self, response, temp_path, report_hooks ):
content_length = response.getheader( 'Content-Length' )
if content_length is not None: content_length = int( content_length )
size_of_response = 0
with open( temp_path, 'wb' ) as f:
2015-11-04 22:30:28 +00:00
for block in HydrusPaths.ReadFileLikeAsBlocks( response ):
2015-10-21 21:53:10 +00:00
2017-05-10 21:33:58 +00:00
if HG.model_shutdown:
2015-11-04 22:30:28 +00:00
raise HydrusExceptions.ShutdownException( 'Application is shutting down!' )
2015-10-21 21:53:10 +00:00
size_of_response += len( block )
if content_length is not None and size_of_response > content_length:
raise Exception( 'Response was longer than suggested!' )
f.write( block )
for hook in report_hooks:
if content_length is not None:
hook( content_length, size_of_response )
return size_of_response
2017-05-31 21:50:53 +00:00
def Close( self ):
if self._connection is not None:
self._connection.close()
2015-10-21 21:53:10 +00:00
def IsStale( self ):
time_since_last_request = HydrusData.GetNow() - self._last_request_time
return time_since_last_request > self._timeout
def Request( self, method, path_and_query, request_headers, body, report_hooks = None, temp_path = None ):
2016-11-30 20:24:17 +00:00
( response, parsed_response, size_of_response ) = self._SendRequestGetResponse( method, path_and_query, request_headers, body, report_hooks = report_hooks, temp_path = temp_path )
2015-10-21 21:53:10 +00:00
2016-11-30 20:24:17 +00:00
return self._DealWithResponse( method, response, parsed_response, size_of_response )
2015-10-21 21:53:10 +00:00
2017-06-28 20:23:21 +00:00
class NetworkBandwidthManager( HydrusSerialisable.SerialisableBase ):
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER
2017-06-07 22:05:15 +00:00
SERIALISABLE_VERSION = 1
def __init__( self ):
HydrusSerialisable.SerialisableBase.__init__( self )
2017-06-28 20:23:21 +00:00
self.engine = None
2017-07-05 21:09:28 +00:00
self._dirty = False
2017-06-07 22:05:15 +00:00
self._lock = threading.Lock()
2017-06-28 20:23:21 +00:00
self._network_contexts_to_bandwidth_trackers = collections.defaultdict( HydrusNetworking.BandwidthTracker )
2017-07-12 20:03:45 +00:00
self._network_contexts_to_bandwidth_rules = collections.defaultdict( HydrusNetworking.BandwidthRules )
2017-06-07 22:05:15 +00:00
2017-07-19 21:21:41 +00:00
for context_type in [ CC.NETWORK_CONTEXT_GLOBAL, CC.NETWORK_CONTEXT_HYDRUS, CC.NETWORK_CONTEXT_DOMAIN, CC.NETWORK_CONTEXT_DOWNLOADER, CC.NETWORK_CONTEXT_DOWNLOADER_QUERY, CC.NETWORK_CONTEXT_SUBSCRIPTION, CC.NETWORK_CONTEXT_THREAD_WATCHER_THREAD ]:
2017-06-28 20:23:21 +00:00
self._network_contexts_to_bandwidth_rules[ NetworkContext( context_type ) ] = HydrusNetworking.BandwidthRules()
2017-06-07 22:05:15 +00:00
2017-08-16 21:58:06 +00:00
def _CanStartRequest( self, network_contexts ):
for network_context in network_contexts:
bandwidth_rules = self._GetRules( network_context )
bandwidth_tracker = self._network_contexts_to_bandwidth_trackers[ network_context ]
if not bandwidth_rules.CanStartRequest( bandwidth_tracker ):
return False
return True
2017-06-28 20:23:21 +00:00
def _GetRules( self, network_context ):
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
if network_context not in self._network_contexts_to_bandwidth_rules:
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
network_context = NetworkContext( network_context.context_type ) # i.e. the default
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
return self._network_contexts_to_bandwidth_rules[ network_context ]
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
def _GetSerialisableInfo( self ):
2017-07-19 21:21:41 +00:00
# note this discards ephemeral network contexts, which have page_key-specific identifiers and are temporary, not meant to be hung onto forever, and are generally invisible to the user
all_serialisable_trackers = [ ( network_context.GetSerialisableTuple(), tracker.GetSerialisableTuple() ) for ( network_context, tracker ) in self._network_contexts_to_bandwidth_trackers.items() if not network_context.IsEphemeral() ]
2017-07-05 21:09:28 +00:00
all_serialisable_rules = [ ( network_context.GetSerialisableTuple(), rules.GetSerialisableTuple() ) for ( network_context, rules ) in self._network_contexts_to_bandwidth_rules.items() ]
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
return ( all_serialisable_trackers, all_serialisable_rules )
2017-06-21 21:15:59 +00:00
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
2017-07-05 21:09:28 +00:00
( all_serialisable_trackers, all_serialisable_rules ) = serialisable_info
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
for ( serialisable_network_context, serialisable_tracker ) in all_serialisable_trackers:
network_context = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_network_context )
tracker = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_tracker )
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
self._network_contexts_to_bandwidth_trackers[ network_context ] = tracker
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
for ( serialisable_network_context, serialisable_rules ) in all_serialisable_rules:
network_context = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_network_context )
rules = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_rules )
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
self._network_contexts_to_bandwidth_rules[ network_context ] = rules
2017-06-21 21:15:59 +00:00
2017-08-16 21:58:06 +00:00
def _ReportRequestUsed( self, network_contexts ):
for network_context in network_contexts:
self._network_contexts_to_bandwidth_trackers[ network_context ].ReportRequestUsed()
self._SetDirty()
2017-07-05 21:09:28 +00:00
def _SetDirty( self ):
self._dirty = True
2017-07-27 00:47:13 +00:00
def CanContinueDownload( self, network_contexts ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
with self._lock:
for network_context in network_contexts:
bandwidth_rules = self._GetRules( network_context )
bandwidth_tracker = self._network_contexts_to_bandwidth_trackers[ network_context ]
2017-07-27 00:47:13 +00:00
if not bandwidth_rules.CanContinueDownload( bandwidth_tracker ):
2017-06-28 20:23:21 +00:00
return False
return True
2017-06-21 21:15:59 +00:00
2017-08-16 21:58:06 +00:00
def CanDoWork( self, network_contexts, expected_requests = 3, expected_bytes = 1048576 ):
2017-06-21 21:15:59 +00:00
with self._lock:
2017-06-28 20:23:21 +00:00
for network_context in network_contexts:
bandwidth_rules = self._GetRules( network_context )
bandwidth_tracker = self._network_contexts_to_bandwidth_trackers[ network_context ]
2017-06-21 21:15:59 +00:00
2017-08-09 21:33:51 +00:00
if not bandwidth_rules.CanDoWork( bandwidth_tracker, expected_requests, expected_bytes ):
2017-07-27 00:47:13 +00:00
return False
return True
def CanStartRequest( self, network_contexts ):
with self._lock:
2017-08-16 21:58:06 +00:00
return self._CanStartRequest( network_contexts )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def DeleteRules( self, network_context ):
with self._lock:
2017-07-19 21:21:41 +00:00
if network_context.context_data is None:
2017-06-28 20:23:21 +00:00
return # can't delete 'default' network contexts
else:
if network_context in self._network_contexts_to_bandwidth_rules:
del self._network_contexts_to_bandwidth_rules[ network_context ]
2017-07-05 21:09:28 +00:00
self._SetDirty()
2017-06-21 21:15:59 +00:00
2017-07-19 21:21:41 +00:00
def DeleteHistory( self, network_contexts ):
with self._lock:
for network_context in network_contexts:
if network_context in self._network_contexts_to_bandwidth_trackers:
del self._network_contexts_to_bandwidth_trackers[ network_context ]
if network_context == GLOBAL_NETWORK_CONTEXT:
# just to reset it, so we have a 0 global context at all times
self._network_contexts_to_bandwidth_trackers[ GLOBAL_NETWORK_CONTEXT ] = HydrusNetworking.BandwidthTracker()
self._SetDirty()
def GetDefaultRules( self ):
with self._lock:
result = []
for ( network_context, bandwidth_rules ) in self._network_contexts_to_bandwidth_rules.items():
if network_context.IsDefault():
result.append( ( network_context, bandwidth_rules ) )
return result
2017-08-02 21:32:54 +00:00
def GetNetworkContextsForUser( self, history_time_delta_threshold = None ):
2017-07-12 20:03:45 +00:00
with self._lock:
2017-08-16 21:58:06 +00:00
result = set()
for ( network_context, bandwidth_rules ) in self._network_contexts_to_bandwidth_rules.items():
if network_context.IsDefault() or network_context.IsEphemeral():
continue
# if a context has rules but no activity, list it so the user can edit the rules if needed
# in case they set too restrictive rules on an old context and now can't get it up again with activity because of the rules!
if network_context not in self._network_contexts_to_bandwidth_trackers or self._network_contexts_to_bandwidth_trackers[ network_context ].GetUsage( HC.BANDWIDTH_TYPE_REQUESTS, None ) == 0:
result.add( network_context )
2017-07-12 20:03:45 +00:00
for ( network_context, bandwidth_tracker ) in self._network_contexts_to_bandwidth_trackers.items():
2017-07-19 21:21:41 +00:00
if network_context.IsDefault() or network_context.IsEphemeral():
2017-07-12 20:03:45 +00:00
continue
2017-07-19 21:21:41 +00:00
if network_context != GLOBAL_NETWORK_CONTEXT and history_time_delta_threshold is not None:
2017-07-12 20:03:45 +00:00
2017-07-19 21:21:41 +00:00
if bandwidth_tracker.GetUsage( HC.BANDWIDTH_TYPE_REQUESTS, history_time_delta_threshold ) == 0:
continue
2017-07-12 20:03:45 +00:00
2017-08-16 21:58:06 +00:00
result.add( network_context )
2017-07-19 21:21:41 +00:00
2017-07-12 20:03:45 +00:00
return result
2017-07-19 21:21:41 +00:00
def GetRules( self, network_context ):
with self._lock:
return self._GetRules( network_context )
2017-06-28 20:23:21 +00:00
def GetTracker( self, network_context ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
with self._lock:
2017-07-19 21:21:41 +00:00
if network_context in self._network_contexts_to_bandwidth_trackers:
return self._network_contexts_to_bandwidth_trackers[ network_context ]
else:
return HydrusNetworking.BandwidthTracker()
2017-06-28 20:23:21 +00:00
2017-06-21 21:15:59 +00:00
2017-08-09 21:33:51 +00:00
def GetWaitingEstimate( self, network_contexts ):
with self._lock:
estimates = []
for network_context in network_contexts:
bandwidth_rules = self._GetRules( network_context )
bandwidth_tracker = self._network_contexts_to_bandwidth_trackers[ network_context ]
estimates.append( bandwidth_rules.GetWaitingEstimate( bandwidth_tracker ) )
if len( estimates ) == 0:
return 0
else:
return max( estimates )
2017-07-05 21:09:28 +00:00
def IsDirty( self ):
with self._lock:
return self._dirty
2017-06-28 20:23:21 +00:00
def ReportDataUsed( self, network_contexts, num_bytes ):
2017-06-07 22:05:15 +00:00
with self._lock:
2017-06-28 20:23:21 +00:00
for network_context in network_contexts:
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
self._network_contexts_to_bandwidth_trackers[ network_context ].ReportDataUsed( num_bytes )
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
self._SetDirty()
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
def ReportRequestUsed( self, network_contexts ):
2017-06-07 22:05:15 +00:00
with self._lock:
2017-08-16 21:58:06 +00:00
self._ReportRequestUsed( network_contexts )
2017-07-05 21:09:28 +00:00
def SetClean( self ):
with self._lock:
self._dirty = False
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
def SetRules( self, network_context, bandwidth_rules ):
2017-06-07 22:05:15 +00:00
with self._lock:
2017-07-19 21:21:41 +00:00
if len( bandwidth_rules.GetRules() ) == 0:
if network_context in self._network_contexts_to_bandwidth_rules:
del self._network_contexts_to_bandwidth_rules[ network_context ]
else:
self._network_contexts_to_bandwidth_rules[ network_context ] = bandwidth_rules
self._SetDirty()
2017-08-16 21:58:06 +00:00
def TryToStartRequest( self, network_contexts ):
# this wraps canstart and reportrequest in one transaction to stop 5/1 rq/s happening due to race condition
with self._lock:
if not self._CanStartRequest( network_contexts ):
return False
self._ReportRequestUsed( network_contexts )
return True
2017-07-19 21:21:41 +00:00
def UsesDefaultRules( self, network_context ):
with self._lock:
return network_context not in self._network_contexts_to_bandwidth_rules
2017-06-28 20:23:21 +00:00
2017-07-05 21:09:28 +00:00
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER ] = NetworkBandwidthManager
2017-06-28 20:23:21 +00:00
class NetworkContext( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_CONTEXT
2017-08-02 21:32:54 +00:00
SERIALISABLE_VERSION = 2
2017-06-28 20:23:21 +00:00
def __init__( self, context_type = None, context_data = None ):
HydrusSerialisable.SerialisableBase.__init__( self )
self.context_type = context_type
self.context_data = context_data
def __eq__( self, other ):
return self.__hash__() == other.__hash__()
def __hash__( self ):
return ( self.context_type, self.context_data ).__hash__()
def __ne__( self, other ):
return self.__hash__() != other.__hash__()
2017-07-12 20:03:45 +00:00
def __repr__( self ):
return self.ToUnicode()
2017-06-28 20:23:21 +00:00
def _GetSerialisableInfo( self ):
if self.context_data is None:
2017-07-05 21:09:28 +00:00
serialisable_context_data = self.context_data
2017-06-28 20:23:21 +00:00
else:
2017-08-02 21:32:54 +00:00
if self.context_type in ( CC.NETWORK_CONTEXT_DOMAIN, CC.NETWORK_CONTEXT_SUBSCRIPTION ):
serialisable_context_data = self.context_data
else:
serialisable_context_data = self.context_data.encode( 'hex' )
2017-06-28 20:23:21 +00:00
2017-07-05 21:09:28 +00:00
return ( self.context_type, serialisable_context_data )
2017-06-28 20:23:21 +00:00
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( self.context_type, serialisable_context_data ) = serialisable_info
if serialisable_context_data is None:
self.context_data = serialisable_context_data
else:
2017-08-02 21:32:54 +00:00
if self.context_type in ( CC.NETWORK_CONTEXT_DOMAIN, CC.NETWORK_CONTEXT_SUBSCRIPTION ):
self.context_data = serialisable_context_data
else:
self.context_data = serialisable_context_data.decode( 'hex' )
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
( context_type, serialisable_context_data ) = old_serialisable_info
if serialisable_context_data is not None:
# unicode subscription names were erroring on the hex call
if context_type in ( CC.NETWORK_CONTEXT_DOMAIN, CC.NETWORK_CONTEXT_SUBSCRIPTION ):
context_data = serialisable_context_data.decode( 'hex' )
serialisable_context_data = context_data
new_serialisable_info = ( context_type, serialisable_context_data )
return ( 2, new_serialisable_info )
2017-06-07 22:05:15 +00:00
2017-07-19 21:21:41 +00:00
def IsDefault( self ):
return self.context_data is None and self.context_type != CC.NETWORK_CONTEXT_GLOBAL
def IsEphemeral( self ):
return self.context_type in ( CC.NETWORK_CONTEXT_DOWNLOADER_QUERY, CC.NETWORK_CONTEXT_THREAD_WATCHER_THREAD )
2017-07-12 20:03:45 +00:00
def ToUnicode( self ):
if self.context_data is None:
2017-07-19 21:21:41 +00:00
if self.context_type == CC.NETWORK_CONTEXT_GLOBAL:
return 'global'
else:
return CC.network_context_type_string_lookup[ self.context_type ] + ' default'
2017-07-12 20:03:45 +00:00
else:
return CC.network_context_type_string_lookup[ self.context_type ] + ': ' + HydrusData.ToUnicode( self.context_data )
2017-06-28 20:23:21 +00:00
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_CONTEXT ] = NetworkContext
GLOBAL_NETWORK_CONTEXT = NetworkContext( CC.NETWORK_CONTEXT_GLOBAL )
2017-06-07 22:05:15 +00:00
class NetworkEngine( object ):
2017-06-21 21:15:59 +00:00
MAX_JOBS = 10 # turn this into an option
2017-10-04 17:51:58 +00:00
def __init__( self, controller, bandwidth_manager, session_manager, domain_manager, login_manager ):
2017-06-28 20:23:21 +00:00
self.controller = controller
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
self.bandwidth_manager = bandwidth_manager
self.session_manager = session_manager
2017-10-04 17:51:58 +00:00
self.domain_manager = domain_manager
2017-06-28 20:23:21 +00:00
self.login_manager = login_manager
self.bandwidth_manager.engine = self
self.session_manager.engine = self
2017-10-04 17:51:58 +00:00
self.domain_manager.engine = self
2017-06-28 20:23:21 +00:00
self.login_manager.engine = self
2017-06-07 22:05:15 +00:00
self._lock = threading.Lock()
self._new_work_to_do = threading.Event()
2017-10-04 17:51:58 +00:00
self._jobs_awaiting_validity = []
self._current_validation_process = None
2017-06-21 21:15:59 +00:00
self._jobs_bandwidth_throttled = []
self._jobs_login_throttled = []
self._current_login_process = None
self._jobs_ready_to_start = []
self._jobs_downloading = []
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
self._is_running = False
self._is_shutdown = False
2017-06-07 22:05:15 +00:00
self._local_shutdown = False
def AddJob( self, job ):
with self._lock:
2017-06-28 20:23:21 +00:00
job.engine = self
2017-10-04 17:51:58 +00:00
self._jobs_awaiting_validity.append( job )
2017-06-07 22:05:15 +00:00
self._new_work_to_do.set()
2017-06-28 20:23:21 +00:00
def IsRunning( self ):
with self._lock:
return self._is_running
def IsShutdown( self ):
with self._lock:
return self._is_shutdown
2017-06-07 22:05:15 +00:00
def MainLoop( self ):
2017-10-04 17:51:58 +00:00
def ProcessValidationJob( job ):
if job.IsDone():
return False
elif job.IsAsleep():
return True
elif not job.IsValid():
if job.CanValidateInPopup():
if self._current_validation_process is None:
validation_process = job.GenerateValidationProcess
self.controller.CallToThread( validation_process )
self._current_validation_process = validation_process
job.SetStatus( u'validation presented to user\u2026' )
else:
job.SetStatus( u'waiting on user validation\u2026' )
job.Sleep( 5 )
else:
job.SetStatus( u'network context not currently valid!' )
job.Sleep( 15 )
return True
else:
self._jobs_bandwidth_throttled.append( job )
return False
def ProcessCurrentValidationJob():
if self._current_validation_process is not None:
if self._current_validation_process.IsDone():
self._current_validation_process = None
2017-07-27 00:47:13 +00:00
def ProcessBandwidthJob( job ):
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
if job.IsDone():
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
return False
elif job.IsAsleep():
return True
elif not job.BandwidthOK():
return True
else:
self._jobs_login_throttled.append( job )
return False
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
def ProcessLoginJob( job ):
if job.IsDone():
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
return False
elif job.IsAsleep():
return True
elif job.NeedsLogin():
if job.CanLogin():
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
if self._current_login_process is None:
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
login_process = job.GenerateLoginProcess()
self.controller.CallToThread( login_process.Start )
self._current_login_process = login_process
job.SetStatus( u'logging in\u2026' )
2017-06-21 21:15:59 +00:00
else:
2017-07-27 00:47:13 +00:00
job.SetStatus( u'waiting on login\u2026' )
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
job.Sleep( 5 )
2017-06-21 21:15:59 +00:00
else:
2017-07-27 00:47:13 +00:00
job.SetStatus( 'unable to login!' )
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
job.Sleep( 15 )
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
return True
else:
self._jobs_ready_to_start.append( job )
return False
def ProcessCurrentLoginJob():
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
if self._current_login_process is not None:
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
if self._current_login_process.IsDone():
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
self._current_login_process = None
2017-06-21 21:15:59 +00:00
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
def ProcessReadyJob( job ):
if job.IsDone():
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
return False
elif len( self._jobs_downloading ) < self.MAX_JOBS:
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
self.controller.CallToThread( job.Start )
self._jobs_downloading.append( job )
return False
else:
2017-08-16 21:58:06 +00:00
job.SetStatus( u'waiting for download slot\u2026' )
2017-07-27 00:47:13 +00:00
return True
def ProcessDownloadingJob( job ):
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
if job.IsDone():
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
return False
else:
return True
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
self._is_running = True
while not ( self._local_shutdown or self.controller.ModelIsShutdown() ):
2017-06-21 21:15:59 +00:00
with self._lock:
2017-10-04 17:51:58 +00:00
self._jobs_awaiting_validity = filter( ProcessValidationJob, self._jobs_awaiting_validity )
ProcessCurrentValidationJob()
2017-06-21 21:15:59 +00:00
self._jobs_bandwidth_throttled = filter( ProcessBandwidthJob, self._jobs_bandwidth_throttled )
self._jobs_login_throttled = filter( ProcessLoginJob, self._jobs_login_throttled )
ProcessCurrentLoginJob()
self._jobs_ready_to_start = filter( ProcessReadyJob, self._jobs_ready_to_start )
self._jobs_downloading = filter( ProcessDownloadingJob, self._jobs_downloading )
2017-06-07 22:05:15 +00:00
2017-08-23 21:34:25 +00:00
# we want to catch the rollover of the second for bandwidth jobs
now_with_subsecond = time.time()
subsecond_part = now_with_subsecond % 1
time_until_next_second = 1.0 - subsecond_part
self._new_work_to_do.wait( time_until_next_second )
2017-06-07 22:05:15 +00:00
self._new_work_to_do.clear()
2017-06-28 20:23:21 +00:00
self._is_running = False
self._is_shutdown = True
2017-06-07 22:05:15 +00:00
def Shutdown( self ):
self._local_shutdown = True
2017-06-28 20:23:21 +00:00
self._new_work_to_do.set()
2017-06-07 22:05:15 +00:00
class NetworkJob( object ):
2017-09-06 20:18:20 +00:00
def __init__( self, method, url, body = None, files = None, referral_url = None, temp_path = None ):
2017-06-28 20:23:21 +00:00
2017-08-16 21:58:06 +00:00
if HG.network_report_mode:
HydrusData.ShowText( 'Network Job: ' + method + ' ' + url )
2017-06-28 20:23:21 +00:00
self.engine = None
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
self._lock = threading.Lock()
2017-06-07 22:05:15 +00:00
self._method = method
self._url = url
self._body = body
2017-09-06 20:18:20 +00:00
self._files = files
2017-06-07 22:05:15 +00:00
self._referral_url = referral_url
self._temp_path = temp_path
2017-09-06 20:18:20 +00:00
self._for_login = False
2017-09-20 19:47:31 +00:00
self._current_connection_attempt_number = 1
2017-09-06 20:18:20 +00:00
self._additional_headers = {}
2017-06-07 22:05:15 +00:00
2017-08-16 21:58:06 +00:00
self._creation_time = HydrusData.GetNow()
2017-06-21 21:15:59 +00:00
self._bandwidth_tracker = HydrusNetworking.BandwidthTracker()
self._wake_time = 0
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
self._stream_io = cStringIO.StringIO()
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
self._error_exception = None
self._error_text = None
2017-09-06 20:18:20 +00:00
self._is_done_event = threading.Event()
2017-06-07 22:05:15 +00:00
self._is_done = False
self._is_cancelled = False
2017-06-28 20:23:21 +00:00
self._bandwidth_manual_override = False
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
self._last_time_ongoing_bandwidth_failed = 0
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
self._status_text = u'initialising\u2026'
self._num_bytes_read = 0
2017-07-05 21:09:28 +00:00
self._num_bytes_to_read = 1
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
self._network_contexts = self._GenerateNetworkContexts()
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
def _CanReattemptRequest( self ):
if self._method == 'GET':
max_attempts_allowed = 5
elif self._method == 'POST':
max_attempts_allowed = 1
return self._current_connection_attempt_number <= max_attempts_allowed
2017-06-28 20:23:21 +00:00
def _GenerateNetworkContexts( self ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
network_contexts = []
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
network_contexts.append( GLOBAL_NETWORK_CONTEXT )
2017-06-21 21:15:59 +00:00
2017-10-04 17:51:58 +00:00
domain = ClientNetworkingDomain.ConvertURLIntoDomain( self._url )
domains = ClientNetworkingDomain.ConvertDomainIntoAllApplicableDomains( domain )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
network_contexts.extend( ( NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, domain ) for domain in domains ) )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
return network_contexts
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
def _SendRequestAndGetResponse( self ):
with self._lock:
session = self._GetSession()
method = self._method
url = self._url
data = self._body
2017-09-06 20:18:20 +00:00
files = self._files
2017-07-27 00:47:13 +00:00
headers = {}
if self._referral_url is not None:
headers = { 'referer' : self._referral_url }
2017-09-06 20:18:20 +00:00
for ( key, value ) in self._additional_headers.items():
headers[ key ] = value
2017-09-20 19:47:31 +00:00
self._status_text = u'sending request\u2026'
2017-07-27 00:47:13 +00:00
2017-09-20 19:47:31 +00:00
timeout = HG.client_controller.GetNewOptions().GetInteger( 'network_timeout' )
2017-07-27 00:47:13 +00:00
2017-09-20 19:47:31 +00:00
response = session.request( method, url, data = data, files = files, headers = headers, stream = True, timeout = timeout )
2017-07-27 00:47:13 +00:00
return response
2017-06-21 21:15:59 +00:00
def _GetSession( self ):
2017-06-28 20:23:21 +00:00
session_network_context = self._GetSessionNetworkContext()
return self.engine.session_manager.GetSession( session_network_context )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def _GetSessionNetworkContext( self ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
return self._network_contexts[-1]
2017-06-07 22:05:15 +00:00
def _IsCancelled( self ):
if self._is_cancelled:
return True
2017-06-28 20:23:21 +00:00
if self.engine.controller.ModelIsShutdown():
2017-06-07 22:05:15 +00:00
return True
return False
2017-06-28 20:23:21 +00:00
def _IsDone( self ):
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
if self._is_done:
return True
if self.engine.controller.ModelIsShutdown():
return True
return False
def _ObeysBandwidth( self ):
2017-07-05 21:09:28 +00:00
return not ( self._bandwidth_manual_override or self._for_login )
2017-06-28 20:23:21 +00:00
def _OngoingBandwidthOK( self ):
2017-07-05 21:09:28 +00:00
now = HydrusData.GetNow()
if now == self._last_time_ongoing_bandwidth_failed: # it won't have changed, so no point spending any cpu checking
2017-06-28 20:23:21 +00:00
2017-07-05 21:09:28 +00:00
return False
2017-06-28 20:23:21 +00:00
else:
2017-07-27 00:47:13 +00:00
result = self.engine.bandwidth_manager.CanContinueDownload( self._network_contexts )
2017-07-05 21:09:28 +00:00
if not result:
self._last_time_ongoing_bandwidth_failed = now
return result
2017-06-28 20:23:21 +00:00
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
def _ReadResponse( self, response, stream_dest ):
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
with self._lock:
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
if 'content-length' in response.headers:
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
self._num_bytes_to_read = int( response.headers[ 'content-length' ] )
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
else:
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
self._num_bytes_to_read = None
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
for chunk in response.iter_content( chunk_size = 65536 ):
if self._IsCancelled():
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
return
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
stream_dest.write( chunk )
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
chunk_length = len( chunk )
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
with self._lock:
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
self._num_bytes_read += chunk_length
2017-06-21 21:15:59 +00:00
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
self._ReportDataUsed( chunk_length )
self._WaitOnOngoingBandwidth()
2017-09-06 20:18:20 +00:00
if HG.view_shutdown:
raise HydrusExceptions.ShutdownException()
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
def _ReportDataUsed( self, num_bytes ):
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
self._bandwidth_tracker.ReportDataUsed( num_bytes )
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
self.engine.bandwidth_manager.ReportDataUsed( self._network_contexts, num_bytes )
2017-06-21 21:15:59 +00:00
def _SetCancelled( self ):
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
self._is_cancelled = True
self._SetDone()
def _SetError( self, e, error ):
self._error_exception = e
self._error_text = error
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
self._SetDone()
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
def _SetDone( self ):
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
self._is_done = True
2017-09-06 20:18:20 +00:00
self._is_done_event.set()
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
def _Sleep( self, seconds ):
self._wake_time = HydrusData.GetNow() + seconds
2017-06-28 20:23:21 +00:00
def _WaitOnOngoingBandwidth( self ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
while not self._OngoingBandwidthOK() and not self._IsCancelled():
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
time.sleep( 0.1 )
2017-06-21 21:15:59 +00:00
2017-09-06 20:18:20 +00:00
def AddAdditionalHeader( self, key, value ):
with self._lock:
self._additional_headers[ key ] = value
2017-06-21 21:15:59 +00:00
def BandwidthOK( self ):
with self._lock:
2017-07-05 21:09:28 +00:00
if self._ObeysBandwidth():
2017-08-16 21:58:06 +00:00
result = self.engine.bandwidth_manager.TryToStartRequest( self._network_contexts )
2017-07-05 21:09:28 +00:00
2017-08-16 21:58:06 +00:00
if result:
self._bandwidth_tracker.ReportRequestUsed()
else:
2017-07-05 21:09:28 +00:00
2017-08-09 21:33:51 +00:00
waiting_duration = self.engine.bandwidth_manager.GetWaitingEstimate( self._network_contexts )
2017-08-23 21:34:25 +00:00
if waiting_duration < 2:
2017-08-09 21:33:51 +00:00
2017-08-16 21:58:06 +00:00
self._status_text = u'bandwidth free imminently\u2026'
2017-08-09 21:33:51 +00:00
else:
pending_timestamp = HydrusData.GetNow() + waiting_duration
waiting_str = HydrusData.ConvertTimestampToPrettyPending( pending_timestamp )
self._status_text = u'bandwidth free ' + waiting_str + u'\u2026'
2017-07-05 21:09:28 +00:00
2017-08-09 21:33:51 +00:00
if waiting_duration > 1200:
self._Sleep( 30 )
elif waiting_duration > 120:
self._Sleep( 10 )
elif waiting_duration > 10:
self._Sleep( 1 )
2017-07-05 21:09:28 +00:00
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
return result
2017-06-21 21:15:59 +00:00
else:
2017-08-16 21:58:06 +00:00
self._bandwidth_tracker.ReportRequestUsed()
self.engine.bandwidth_manager.ReportRequestUsed( self._network_contexts )
2017-06-28 20:23:21 +00:00
return True
2017-06-21 21:15:59 +00:00
2017-06-07 22:05:15 +00:00
def Cancel( self ):
2017-06-21 21:15:59 +00:00
with self._lock:
self._status_text = 'cancelled!'
self._SetCancelled()
2017-06-28 20:23:21 +00:00
def CanLogin( self ):
with self._lock:
if self._for_login:
raise Exception( 'Login jobs should not be asked if they can login!' )
else:
return self.engine.login_manager.CanLogin( self._network_contexts )
2017-10-04 17:51:58 +00:00
def CanValidateInPopup( self ):
with self._lock:
return self.engine.domain_manager.CanValidateInPopup( self._network_contexts )
2017-06-21 21:15:59 +00:00
def GenerateLoginProcess( self ):
with self._lock:
2017-06-28 20:23:21 +00:00
if self._for_login:
raise Exception( 'Login jobs should not be asked to generate login processes!' )
else:
return self.engine.login_manager.GenerateLoginProcess( self._network_contexts )
2017-06-21 21:15:59 +00:00
2017-06-07 22:05:15 +00:00
2017-10-04 17:51:58 +00:00
def GenerateValidationPopupProcess( self ):
with self._lock:
return self.engine.domain_manager.GenerateValidationPopupProcess( self._network_contexts )
2017-06-07 22:05:15 +00:00
def GetContent( self ):
2017-06-21 21:15:59 +00:00
with self._lock:
self._stream_io.seek( 0 )
return self._stream_io.read()
2017-06-07 22:05:15 +00:00
2017-08-16 21:58:06 +00:00
def GetCreationTime( self ):
with self._lock:
return self._creation_time
2017-06-21 21:15:59 +00:00
def GetErrorException( self ):
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
with self._lock:
return self._error_exception
def GetErrorText( self ):
with self._lock:
return self._error_text
2017-06-07 22:05:15 +00:00
2017-07-27 00:47:13 +00:00
def GetNetworkContexts( self ):
with self._lock:
return list( self._network_contexts )
2017-06-07 22:05:15 +00:00
def GetStatus( self ):
with self._lock:
2017-06-21 21:15:59 +00:00
return ( self._status_text, self._bandwidth_tracker.GetUsage( HC.BANDWIDTH_TYPE_DATA, 1 ), self._num_bytes_read, self._num_bytes_to_read )
2017-06-07 22:05:15 +00:00
def HasError( self ):
2017-06-21 21:15:59 +00:00
with self._lock:
2017-07-05 21:09:28 +00:00
return self._error_exception is not None
2017-06-21 21:15:59 +00:00
def IsAsleep( self ):
with self._lock:
2017-06-28 20:23:21 +00:00
return not HydrusData.TimeHasPassed( self._wake_time )
2017-06-21 21:15:59 +00:00
2017-06-07 22:05:15 +00:00
def IsCancelled( self ):
2017-06-21 21:15:59 +00:00
with self._lock:
return self._IsCancelled()
2017-06-07 22:05:15 +00:00
def IsDone( self ):
2017-06-21 21:15:59 +00:00
with self._lock:
2017-06-28 20:23:21 +00:00
return self._IsDone()
2017-06-21 21:15:59 +00:00
2017-06-07 22:05:15 +00:00
2017-10-04 17:51:58 +00:00
def IsValid( self ):
with self._lock:
return self.engine.domain_manager.IsValid( self._network_contexts )
2017-06-21 21:15:59 +00:00
def NeedsLogin( self ):
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
with self._lock:
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
if self._for_login:
return False
else:
2017-10-04 17:51:58 +00:00
return self.engine.login_manager.NeedsLogin( self._network_contexts )
2017-06-28 20:23:21 +00:00
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
def NoEngineYet( self ):
return self.engine is None
2017-08-16 21:58:06 +00:00
def ObeysBandwidth( self ):
return self._ObeysBandwidth()
2017-06-21 21:15:59 +00:00
def OverrideBandwidth( self ):
with self._lock:
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
self._bandwidth_manual_override = True
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
self._wake_time = 0
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
2017-09-06 20:18:20 +00:00
def SetForLogin( self, for_login ):
with self._lock:
self._for_login = for_login
2017-06-21 21:15:59 +00:00
def SetStatus( self, text ):
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
with self._lock:
self._status_text = text
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
def Sleep( self, seconds ):
2017-06-07 22:05:15 +00:00
with self._lock:
2017-07-05 21:09:28 +00:00
self._Sleep( seconds )
2017-06-07 22:05:15 +00:00
def Start( self ):
2017-06-21 21:15:59 +00:00
try:
2017-09-20 19:47:31 +00:00
request_completed = False
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
while not request_completed:
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
try:
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
response = self._SendRequestAndGetResponse()
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
with self._lock:
if self._body is not None:
self._ReportDataUsed( len( self._body ) )
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
if response.ok:
with self._lock:
self._status_text = u'downloading\u2026'
if self._temp_path is None:
self._ReadResponse( response, self._stream_io )
else:
with open( self._temp_path, 'wb' ) as f:
self._ReadResponse( response, f )
with self._lock:
self._status_text = 'done!'
else:
with self._lock:
self._status_text = str( response.status_code ) + ' - ' + str( response.reason )
self._ReadResponse( response, self._stream_io )
with self._lock:
self._stream_io.seek( 0 )
data = self._stream_io.read()
( e, error_text ) = ConvertStatusCodeAndDataIntoExceptionInfo( response.status_code, data )
self._SetError( e, error_text )
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
request_completed = True
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
except requests.exceptions.ConnectionError, requests.exceptions.ConnectTimeout:
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
self._current_connection_attempt_number += 1
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
if not self._CanReattemptRequest():
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
raise HydrusExceptions.NetworkException( 'Could not connect!' )
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
with self._lock:
self._status_text = u'connection failed--retrying'
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
time.sleep( 3 )
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
except requests.exceptions.ReadTimeout:
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
self._current_connection_attempt_number += 1
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
if not self._CanReattemptRequest():
raise HydrusExceptions.NetworkException( 'Connection successful, but reading response timed out!' )
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
with self._lock:
self._status_text = u'read timed out--retrying'
2017-06-21 21:15:59 +00:00
2017-09-20 19:47:31 +00:00
time.sleep( 3 )
2017-06-21 21:15:59 +00:00
except Exception as e:
with self._lock:
self._status_text = 'unexpected error!'
trace = traceback.format_exc()
2017-07-27 00:47:13 +00:00
HydrusData.Print( trace )
2017-06-21 21:15:59 +00:00
self._SetError( e, trace )
finally:
with self._lock:
self._SetDone()
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
2017-09-06 20:18:20 +00:00
def WaitUntilDone( self ):
2017-09-27 21:52:54 +00:00
while True:
self._is_done_event.wait( 5 )
if self.IsDone():
break
2017-09-06 20:18:20 +00:00
with self._lock:
if self.engine.controller.ModelIsShutdown():
raise HydrusExceptions.ShutdownException()
elif self._error_exception is not None:
2017-09-13 20:50:41 +00:00
if isinstance( self._error_exception, Exception ):
raise self._error_exception
else:
raise Exception( 'Problem in network error handling.' )
2017-09-06 20:18:20 +00:00
elif self._IsCancelled():
if self._method == 'POST':
message = 'Upload cancelled!'
else:
message = 'Download cancelled!'
raise HydrusExceptions.CancelledException( message )
2017-06-28 20:23:21 +00:00
class NetworkJobDownloader( NetworkJob ):
2017-06-21 21:15:59 +00:00
2017-09-06 20:18:20 +00:00
def __init__( self, downloader_key, method, url, body = None, referral_url = None, temp_path = None ):
2017-06-07 22:05:15 +00:00
2017-06-28 20:23:21 +00:00
self._downloader_key = downloader_key
2017-06-07 22:05:15 +00:00
2017-09-06 20:18:20 +00:00
NetworkJob.__init__( self, method, url, body = body, referral_url = referral_url, temp_path = temp_path )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def _GenerateNetworkContexts( self ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
network_contexts = NetworkJob._GenerateNetworkContexts( self )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
network_contexts.append( NetworkContext( CC.NETWORK_CONTEXT_DOWNLOADER, self._downloader_key ) )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
return network_contexts
2017-07-27 00:47:13 +00:00
def _GetSessionNetworkContext( self ):
return self._network_contexts[-2] # the domain one
2017-07-05 21:09:28 +00:00
class NetworkJobDownloaderQuery( NetworkJobDownloader ):
2017-09-06 20:18:20 +00:00
def __init__( self, downloader_page_key, downloader_key, method, url, body = None, referral_url = None, temp_path = None ):
2017-07-27 00:47:13 +00:00
self._downloader_page_key = downloader_page_key
2017-09-06 20:18:20 +00:00
NetworkJobDownloader.__init__( self, downloader_key, method, url, body = body, referral_url = referral_url, temp_path = temp_path )
2017-07-27 00:47:13 +00:00
def _GenerateNetworkContexts( self ):
network_contexts = NetworkJob._GenerateNetworkContexts( self )
network_contexts.append( NetworkContext( CC.NETWORK_CONTEXT_DOWNLOADER_QUERY, self._downloader_page_key ) )
return network_contexts
def _GetSessionNetworkContext( self ):
return self._network_contexts[-3] # the domain one
class NetworkJobDownloaderQueryTemporary( NetworkJob ):
2017-09-06 20:18:20 +00:00
def __init__( self, downloader_page_key, method, url, body = None, referral_url = None, temp_path = None ):
2017-07-05 21:09:28 +00:00
self._downloader_page_key = downloader_page_key
2017-09-06 20:18:20 +00:00
NetworkJob.__init__( self, method, url, body = body, referral_url = referral_url, temp_path = temp_path )
2017-07-05 21:09:28 +00:00
def _GenerateNetworkContexts( self ):
network_contexts = NetworkJob._GenerateNetworkContexts( self )
network_contexts.append( NetworkContext( CC.NETWORK_CONTEXT_DOWNLOADER_QUERY, self._downloader_page_key ) )
return network_contexts
def _GetSessionNetworkContext( self ):
2017-07-27 00:47:13 +00:00
return self._network_contexts[-2] # the domain one
2017-07-05 21:09:28 +00:00
2017-06-28 20:23:21 +00:00
class NetworkJobSubscription( NetworkJobDownloader ):
2017-06-21 21:15:59 +00:00
2017-09-06 20:18:20 +00:00
def __init__( self, subscription_key, downloader_key, method, url, body = None, referral_url = None, temp_path = None ):
2017-07-27 00:47:13 +00:00
self._subscription_key = subscription_key
2017-09-06 20:18:20 +00:00
NetworkJobDownloader.__init__( self, downloader_key, method, url, body = body, referral_url = referral_url, temp_path = temp_path )
2017-07-27 00:47:13 +00:00
def _GenerateNetworkContexts( self ):
network_contexts = NetworkJob._GenerateNetworkContexts( self )
network_contexts.append( NetworkContext( CC.NETWORK_CONTEXT_SUBSCRIPTION, self._subscription_key ) )
return network_contexts
def _GetSessionNetworkContext( self ):
return self._network_contexts[-3] # the domain one
class NetworkJobSubscriptionTemporary( NetworkJob ):
2017-09-06 20:18:20 +00:00
def __init__( self, subscription_key, method, url, body = None, referral_url = None, temp_path = None ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
self._subscription_key = subscription_key
2017-06-21 21:15:59 +00:00
2017-09-06 20:18:20 +00:00
NetworkJob.__init__( self, method, url, body = body, referral_url = referral_url, temp_path = temp_path )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def _GenerateNetworkContexts( self ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
network_contexts = NetworkJob._GenerateNetworkContexts( self )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
network_contexts.append( NetworkContext( CC.NETWORK_CONTEXT_SUBSCRIPTION, self._subscription_key ) )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
return network_contexts
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def _GetSessionNetworkContext( self ):
2017-06-21 21:15:59 +00:00
2017-07-27 00:47:13 +00:00
return self._network_contexts[-2] # the domain one
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
class NetworkJobHydrus( NetworkJob ):
2017-09-06 20:18:20 +00:00
def __init__( self, service_key, method, url, body = None, referral_url = None, temp_path = None ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
self._service_key = service_key
2017-06-21 21:15:59 +00:00
2017-09-06 20:18:20 +00:00
NetworkJob.__init__( self, method, url, body = body, referral_url = referral_url, temp_path = temp_path )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def _GenerateNetworkContexts( self ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
network_contexts = NetworkJob._GenerateNetworkContexts( self )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
network_contexts.append( NetworkContext( CC.NETWORK_CONTEXT_HYDRUS, self._service_key ) )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
return network_contexts
2017-06-21 21:15:59 +00:00
2017-07-19 21:21:41 +00:00
class NetworkJobThreadWatcher( NetworkJob ):
2017-09-06 20:18:20 +00:00
def __init__( self, thread_key, method, url, body = None, referral_url = None, temp_path = None ):
2017-07-19 21:21:41 +00:00
self._thread_key = thread_key
2017-09-06 20:18:20 +00:00
NetworkJob.__init__( self, method, url, body = body, referral_url = referral_url, temp_path = temp_path )
2017-07-19 21:21:41 +00:00
def _GenerateNetworkContexts( self ):
network_contexts = NetworkJob._GenerateNetworkContexts( self )
network_contexts.append( NetworkContext( CC.NETWORK_CONTEXT_THREAD_WATCHER_THREAD, self._thread_key ) )
return network_contexts
def _GetSessionNetworkContext( self ):
return self._network_contexts[-2] # the domain one
2017-06-28 20:23:21 +00:00
class NetworkLoginManager( HydrusSerialisable.SerialisableBase ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_LOGIN_MANAGER
SERIALISABLE_VERSION = 1
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def __init__( self ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
HydrusSerialisable.SerialisableBase.__init__( self )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
self.engine = None
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
self._lock = threading.Lock()
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
self._network_contexts_to_logins = {}
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
# a login has:
# a network_context it works for (PRIMARY KEY)
# a login script
# rules to check validity in cookies in a current session (fold that into the login script, which may have several stages of this)
# current user/pass/whatever
# current script validity
# current credentials validity
# recent error? some way of dealing with 'domain is currently down, so try again later'
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
# so, we fetch all the logins, ask them for the network contexts so we can set up the dict
2017-06-07 22:05:15 +00:00
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
def _GetSerialisableInfo( self ):
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
return {}
2017-06-28 20:23:21 +00:00
2017-07-05 21:09:28 +00:00
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
self._network_contexts_to_logins = {}
2017-06-28 20:23:21 +00:00
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
def CanLogin( self, network_contexts ):
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
# look them up in our structure
# if they have a login, is it valid?
# valid means we have tested credentials and it hasn't been invalidated by a parsing error or similar
# I think this just means saying Login.CanLogin( credentials )
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
return False
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
def GenerateLoginProcess( self, network_contexts ):
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
# look up the logins
# login_process = Login.GenerateLoginProcess
# return login_process
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
raise NotImplementedError()
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
def NeedsLogin( self, network_contexts ):
2017-06-07 22:05:15 +00:00
2017-07-05 21:09:28 +00:00
# look up the network contexts in our structure
# if they have a login, see if they match the 'is logged in' predicates
# otherwise:
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
return False
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_LOGIN_MANAGER ] = NetworkLoginManager
class NetworkSessionManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_SESSION_MANAGER
SERIALISABLE_VERSION = 1
def __init__( self ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
HydrusSerialisable.SerialisableBase.__init__( self )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
self.engine = None
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
self._dirty = False
2017-06-28 20:23:21 +00:00
self._lock = threading.Lock()
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
self._network_contexts_to_sessions = {}
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def _GenerateSession( self, network_context ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
session = requests.Session()
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
session.headers.update( { 'User-Agent' : 'hydrus/' + str( HC.NETWORK_VERSION ) } )
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
if network_context.context_type == CC.NETWORK_CONTEXT_HYDRUS:
session.verify = False
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
return session
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def _GetSerialisableInfo( self ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
serialisable_network_contexts_to_sessions = [ ( network_context.GetSerialisableTuple(), cPickle.dumps( session ) ) for ( network_context, session ) in self._network_contexts_to_sessions.items() ]
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
return serialisable_network_contexts_to_sessions
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
serialisable_network_contexts_to_sessions = serialisable_info
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
for ( serialisable_network_context, pickled_session ) in serialisable_network_contexts_to_sessions:
network_context = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_network_context )
2017-07-05 21:09:28 +00:00
session = cPickle.loads( str( pickled_session ) )
2017-06-28 20:23:21 +00:00
2017-07-27 00:47:13 +00:00
session.cookies.clear_session_cookies()
2017-06-28 20:23:21 +00:00
self._network_contexts_to_sessions[ network_context ] = session
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
def _SetDirty( self ):
self._dirty = True
2017-06-28 20:23:21 +00:00
def ClearSession( self, network_context ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
with self._lock:
if network_context in self._network_contexts_to_sessions:
del self._network_contexts_to_sessions[ network_context ]
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
def GetSession( self, network_context ):
2017-06-21 21:15:59 +00:00
2017-06-28 20:23:21 +00:00
with self._lock:
if network_context not in self._network_contexts_to_sessions:
self._network_contexts_to_sessions[ network_context ] = self._GenerateSession( network_context )
2017-07-05 21:09:28 +00:00
self._SetDirty()
2017-06-28 20:23:21 +00:00
return self._network_contexts_to_sessions[ network_context ]
2017-06-21 21:15:59 +00:00
2017-07-05 21:09:28 +00:00
def IsDirty( self ):
with self._lock:
return self._dirty
def SetClean( self ):
with self._lock:
self._dirty = False
2017-06-28 20:23:21 +00:00
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_SESSION_MANAGER ] = NetworkSessionManager