hydrus/include/ServerController.py

479 lines
14 KiB
Python
Executable File

import HydrusConstants as HC
import HydrusController
import HydrusData
import HydrusExceptions
import HydrusGlobals as HG
import HydrusNetworking
import HydrusSessions
import HydrusThreading
import os
import ServerDaemons
import ServerDB
import ServerServer
import sys
import time
import traceback
import twisted.internet.ssl
from twisted.internet import reactor
from twisted.internet import defer
def ProcessStartingAction( db_dir, action ):
already_running = HydrusData.IsAlreadyRunning( db_dir, 'server' )
if action == 'start':
if already_running:
HydrusData.Print( 'The server is already running. Would you like to [s]top it, [r]estart it, or e[x]it?' )
answer = raw_input()
if len( answer ) > 0:
answer = answer[0]
if answer == 's':
return 'stop'
elif answer == 'r':
return 'restart'
raise HydrusExceptions.PermissionException( 'Exiting!' )
else:
return action
elif action == 'stop':
if already_running:
return action
else:
raise HydrusExceptions.PermissionException( 'The server is not running, so it cannot be stopped!' )
elif action == 'restart':
if already_running:
return action
else:
return 'start'
def ShutdownSiblingInstance( db_dir ):
port_found = False
ports = HydrusData.GetSiblingProcessPorts( db_dir, 'server' )
if ports is None:
raise HydrusExceptions.PermissionException( 'Could not figure out the existing server\'s ports, so could not shut it down!' )
for port in ports:
try:
connection = HydrusNetworking.GetLocalConnection( port, https = True )
connection.request( 'GET', '/' )
response = connection.getresponse()
response.read()
server_name = response.getheader( 'Server' )
except:
text = 'Could not contact existing server\'s port ' + str( port ) + '!'
text += os.linesep
text += traceback.format_exc()
raise HydrusExceptions.PermissionException( text )
if 'server administration' in server_name:
port_found = True
HydrusData.Print( u'Sending shut down instruction\u2026' )
connection.request( 'POST', '/shutdown' )
response = connection.getresponse()
result = response.read()
if response.status != 200:
text = 'When told to shut down, the existing server gave an error!'
text += os.linesep
text += result
raise HydrusExceptions.PermissionException( text )
time_waited = 0
while HydrusData.IsAlreadyRunning( db_dir, 'server' ):
time.sleep( 1 )
time_waited += 1
if time_waited > 20:
raise HydrusExceptions.PermissionException( 'Attempted to shut the existing server down, but it took too long!' )
break
if not port_found:
raise HydrusExceptions.PermissionException( 'The existing server did not have an administration service!' )
HydrusData.Print( 'The existing server is shut down!' )
class Controller( HydrusController.HydrusController ):
def __init__( self, db_dir, no_daemons, no_wal ):
HydrusController.HydrusController.__init__( self, db_dir, no_daemons, no_wal )
self._name = 'server'
self._shutdown = False
HG.server_controller = self
def _InitDB( self ):
return ServerDB.DB( self, self.db_dir, 'server', no_wal = self._no_wal )
def StartService( self, service ):
def TWISTEDDoIt():
service_key = service.GetServiceKey()
service_type = service.GetServiceType()
def Start( *args, **kwargs ):
try:
port = service.GetPort()
try:
connection = HydrusNetworking.GetLocalConnection( port )
connection.close()
raise Exception( 'Something was already bound to port ' + str( port ) )
except:
if service_type == HC.SERVER_ADMIN:
http_factory = ServerServer.HydrusServiceAdmin( service )
elif service_type == HC.FILE_REPOSITORY:
http_factory = ServerServer.HydrusServiceRepositoryFile( service )
elif service_type == HC.TAG_REPOSITORY:
http_factory = ServerServer.HydrusServiceRepositoryTag( service )
else:
return
( ssl_cert_path, ssl_key_path ) = self.db.GetSSLPaths()
sslmethod = twisted.internet.ssl.SSL.TLSv1_2_METHOD
context_factory = twisted.internet.ssl.DefaultOpenSSLContextFactory( ssl_key_path, ssl_cert_path, sslmethod )
self._service_keys_to_connected_ports[ service_key ] = reactor.listenSSL( port, http_factory, context_factory )
try:
connection = HydrusNetworking.GetLocalConnection( port )
connection.close()
except:
raise Exception( 'Tried to bind port ' + str( port ) + ' but it failed.' )
except Exception as e:
HydrusData.Print( traceback.format_exc() )
if service_key in self._service_keys_to_connected_ports:
deferred = defer.maybeDeferred( self._service_keys_to_connected_ports[ service_key ].stopListening )
deferred.addCallback( Start )
else:
Start()
reactor.callFromThread( TWISTEDDoIt )
def StopService( self, service_key ):
def TWISTEDDoIt():
deferred = defer.maybeDeferred( self._service_keys_to_connected_ports[ service_key ].stopListening )
del self._service_keys_to_connected_ports[ service_key ]
reactor.callFromThread( TWISTEDDoIt )
def Exit( self ):
HydrusData.Print( u'Shutting down daemons and services\u2026' )
self.ShutdownView()
HydrusData.Print( u'Shutting down db\u2026' )
self.ShutdownModel()
HydrusData.CleanRunningFile( self.db_dir, 'server' )
def GetFilesDir( self ):
return self.db.GetFilesDir()
def GetServices( self ):
return list( self._services )
def InitModel( self ):
HydrusController.HydrusController.InitModel( self )
self.server_session_manager = HydrusSessions.HydrusSessionManagerServer()
self._service_keys_to_connected_ports = {}
def InitView( self ):
HydrusController.HydrusController.InitView( self )
if not self._no_daemons:
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'DeleteOrphans', ServerDaemons.DAEMONDeleteOrphans, period = 86400 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'GenerateUpdates', ServerDaemons.DAEMONGenerateUpdates, period = 600, init_wait = 10 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SaveDirtyObjects', ServerDaemons.DAEMONSaveDirtyObjects, period = 30 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'UPnP', ServerDaemons.DAEMONUPnP, ( 'notify_new_options', ), period = 43200 ) )
#
self._services = self.Read( 'services' )
[ self._admin_service ] = [ service for service in self._services if service.GetServiceType() == HC.SERVER_ADMIN ]
port = self._admin_service.GetPort()
already_bound = False
try:
connection = HydrusNetworking.GetLocalConnection( port )
connection.close()
already_bound = True
except:
pass
if already_bound:
HydrusData.Print( 'Something is already bound to port ' + str( port ) + ', so your administration service cannot be started. Please quit the server and retry once the port is clear.' )
else:
for service in self._services:
self.StartService( service )
def JustWokeFromSleep( self ):
return False
def MaintainDB( self, stop_time = None ):
stop_time = HydrusData.GetNow() + 10
self.WriteSynchronous( 'analyze', stop_time )
def ReportDataUsed( self, num_bytes ):
self._admin_service.ServerReportDataUsed( num_bytes )
def ReportRequestUsed( self ):
self._admin_service.ServerReportRequestUsed()
def Run( self ):
HydrusData.RecordRunningStart( self.db_dir, 'server' )
HydrusData.Print( u'Initialising db\u2026' )
self.InitModel()
HydrusData.Print( u'Initialising daemons and services\u2026' )
self.InitView()
HydrusData.Print( 'Server is running. Press Ctrl+C to quit.' )
try:
while not self._model_shutdown and not self._shutdown:
time.sleep( 1 )
except KeyboardInterrupt:
HydrusData.Print( u'Received a keyboard interrupt\u2026' )
HydrusData.Print( u'Shutting down controller\u2026' )
self.Exit()
def SaveDirtyObjects( self ):
with HG.dirty_object_lock:
dirty_services = [ service for service in self._services if service.IsDirty() ]
if len( dirty_services ) > 0:
self.WriteSynchronous( 'dirty_services', dirty_services )
dirty_accounts = self.server_session_manager.GetDirtyAccounts()
if len( dirty_accounts ) > 0:
self.WriteSynchronous( 'dirty_accounts', dirty_accounts )
def ServerBandwidthOK( self ):
return self._admin_service.ServerBandwidthOK()
def SetServices( self, services ):
# doesn't need the dirty_object_lock because the caller takes it
self._services = services
[ self._admin_service ] = [ service for service in self._services if service.GetServiceType() == HC.SERVER_ADMIN ]
current_service_keys = set( self._service_keys_to_connected_ports.keys() )
future_service_keys = set( [ service.GetServiceKey() for service in self._services ] )
stop_service_keys = current_service_keys.difference( future_service_keys )
for service_key in stop_service_keys:
self.StopService( service_key )
for service in self._services:
self.StartService( service )
def ShutdownView( self ):
for service in self._services:
service_key = service.GetServiceKey()
if service_key in self._service_keys_to_connected_ports:
self.StopService( service_key )
HydrusController.HydrusController.ShutdownView( self )
def ShutdownFromServer( self ):
HydrusData.Print( u'Received a server shut down request\u2026' )
self._shutdown = True
def SyncRepositories( self ):
repositories = [ service for service in self._services if service.GetServiceType() in HC.REPOSITORIES ]
for service in repositories:
service.Sync()