hydrus/include/ServerController.py

474 lines
14 KiB
Python
Executable File

from . import HydrusConstants as HC
from . import HydrusController
from . import HydrusData
from . import HydrusExceptions
from . import HydrusGlobals as HG
from . import HydrusNetworking
from . import HydrusSessions
from . import HydrusThreading
import os
from . import ServerDB
from . import ServerServer
import requests
import sys
import time
import traceback
import twisted.internet.ssl
from twisted.internet import threads, reactor, 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 = input()
if len( answer ) > 0:
answer = answer[0]
if answer == 's':
return 'stop'
elif answer == 'r':
return 'restart'
HG.shutting_down_due_to_already_running = True
raise HydrusExceptions.ShutdownException( 'Exiting!' )
else:
return action
elif action == 'stop':
if already_running:
return action
else:
raise HydrusExceptions.ShutdownException( '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.ShutdownException( 'Could not figure out the existing server\'s ports, so could not shut it down!' )
session = requests.Session()
session.verify = False
for port in ports:
try:
r = session.get( 'https://127.0.0.1:' + str( port ) + '/' )
server_name = r.headers[ 'Server' ]
except:
text = 'Could not contact existing server\'s port ' + str( port ) + '!'
text += os.linesep
text += traceback.format_exc()
raise HydrusExceptions.ShutdownException( text )
if 'server administration' in server_name:
port_found = True
HydrusData.Print( 'Sending shut down instruction\u2026' )
r = session.post( 'https://127.0.0.1:' + str( port ) + '/shutdown' )
if not r.ok:
text = 'When told to shut down, the existing server gave an error!'
text += os.linesep
text += r.text
raise HydrusExceptions.ShutdownException( text )
time_waited = 0
while HydrusData.IsAlreadyRunning( db_dir, 'server' ):
time.sleep( 1 )
time_waited += 1
if time_waited > 20:
raise HydrusExceptions.ShutdownException( 'Attempted to shut the existing server down, but it took too long!' )
break
if not port_found:
raise HydrusExceptions.ShutdownException( '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 ):
HydrusController.HydrusController.__init__( self, db_dir )
self._name = 'server'
self._shutdown = False
HG.server_controller = self
def _GetUPnPServices( self ):
return self._services
def _InitDB( self ):
return ServerDB.DB( self, self.db_dir, 'server' )
def DeleteOrphans( self ):
self.WriteSynchronous( 'delete_orphans' )
def Exit( self ):
HydrusData.Print( 'Shutting down daemons\u2026' )
self.ShutdownView()
HydrusData.Print( 'Shutting down db\u2026' )
self.ShutdownModel()
if not HG.shutting_down_due_to_already_running:
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._services = self.Read( 'services' )
[ self._admin_service ] = [ service for service in self._services if service.GetServiceType() == HC.SERVER_ADMIN ]
self.server_session_manager = HydrusSessions.HydrusSessionManagerServer()
self._service_keys_to_connected_ports = {}
def InitView( self ):
HydrusController.HydrusController.InitView( self )
port = self._admin_service.GetPort()
if HydrusNetworking.LocalPortInUse( port ):
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:
self.SetRunningTwistedServices( self._services )
#
job = self.CallRepeating( 5.0, 600.0, self.SyncRepositories )
self._daemon_jobs[ 'sync_repositories' ] = job
job = self.CallRepeating( 0.0, 30.0, self.SaveDirtyObjects )
self._daemon_jobs[ 'save_dirty_objects' ] = job
job = self.CallRepeating( 0.0, 86400.0, self.DeleteOrphans )
self._daemon_jobs[ 'delete_orphans' ] = job
def JustWokeFromSleep( self ):
return False
def MaintainDB( self, maintenance_mode = HC.MAINTENANCE_FORCED, stop_time = None ):
stop_time = HydrusData.GetNow() + 10
self.WriteSynchronous( 'analyze', maintenance_mode = maintenance_mode, stop_time = 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( 'Initialising db\u2026' )
self.InitModel()
HydrusData.Print( 'Initialising daemons\u2026' )
self.InitView()
HydrusData.Print( 'Server is running. Press Ctrl+C to quit.' )
try:
while not HG.model_shutdown and not self._shutdown:
time.sleep( 1 )
except KeyboardInterrupt:
HydrusData.Print( 'Received a keyboard interrupt\u2026' )
HydrusData.Print( '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 SetRunningTwistedServices( self, services ):
def TWISTEDDoIt():
def StartServices( *args, **kwargs ):
HydrusData.Print( 'Starting services\u2026' )
for service in services:
service_key = service.GetServiceKey()
service_type = service.GetServiceType()
try:
port = service.GetPort()
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 )
if not HydrusNetworking.LocalPortInUse( port ):
raise Exception( 'Tried to bind port {} for "{}" but it failed.'.format( port, service.GetName() ) )
except Exception as e:
HydrusData.Print( traceback.format_exc() )
HydrusData.Print( 'Services started' )
if len( self._service_keys_to_connected_ports ) > 0:
HydrusData.Print( 'Stopping services\u2026' )
deferreds = []
for port in self._service_keys_to_connected_ports.values():
deferred = defer.maybeDeferred( port.stopListening )
deferreds.append( deferred )
self._service_keys_to_connected_ports = {}
deferred = defer.DeferredList( deferreds )
if len( services ) > 0:
deferred.addCallback( StartServices )
elif len( services ) > 0:
StartServices()
threads.blockingCallFromThread( reactor, TWISTEDDoIt )
def SetServices( self, services ):
# doesn't need the dirty_object_lock because the caller takes it
# first test available ports
my_ports = { s.GetPort() for s in self._services }
for service in services:
port = service.GetPort()
if port not in my_ports and HydrusNetworking.LocalPortInUse( port ):
raise HydrusExceptions.ServerException( 'Something was already bound to port ' + str( port ) )
#
self._services = services
self.CallToThread( self.services_upnp_manager.SetServices, self._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 ] )
self.SetRunningTwistedServices( self._services )
def ShutdownView( self ):
self.SetRunningTwistedServices( [] )
HydrusController.HydrusController.ShutdownView( self )
def ShutdownFromServer( self ):
HydrusData.Print( 'Received a server shut down request\u2026' )
self._shutdown = True
def SyncRepositories( self ):
if HG.server_busy:
return
repositories = [ service for service in self._services if service.GetServiceType() in HC.REPOSITORIES ]
for service in repositories:
service.Sync()