hydrus/include/ClientController.py

1900 lines
62 KiB
Python
Raw Normal View History

2018-03-22 00:03:33 +00:00
import os
2019-11-14 03:56:30 +00:00
import sys
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
from . import QtPorting as QP
2019-01-23 22:19:16 +00:00
from . import ClientAPI
2019-01-09 22:59:03 +00:00
from . import ClientCaches
2020-03-04 22:12:53 +00:00
from . import ClientConstants as CC
from . import ClientDB
2019-01-09 22:59:03 +00:00
from . import ClientDaemons
from . import ClientDefaults
2019-07-31 22:01:02 +00:00
from . import ClientDownloading
2019-05-22 22:35:06 +00:00
from . import ClientFiles
2020-03-04 22:12:53 +00:00
from . import ClientGUI
from . import ClientGUIDialogs
from . import ClientGUIDialogsQuick
from . import ClientGUIScrolledPanelsManagement
2019-06-05 19:42:39 +00:00
from . import ClientGUIShortcuts
2019-12-05 05:29:32 +00:00
from . import ClientGUIStyle
2020-03-04 22:12:53 +00:00
from . import ClientGUITopLevelWindows
2019-12-05 05:29:32 +00:00
from . import ClientImportSubscriptions
from . import ClientManagers
2019-01-09 22:59:03 +00:00
from . import ClientNetworking
from . import ClientNetworkingBandwidth
from . import ClientNetworkingDomain
from . import ClientNetworkingLogin
from . import ClientNetworkingSessions
from . import ClientOptions
2020-03-11 21:52:11 +00:00
from . import ClientSearch
2019-10-02 23:38:59 +00:00
from . import ClientTags
2019-01-09 22:59:03 +00:00
from . import ClientThreading
2013-11-06 18:22:07 +00:00
import hashlib
2019-01-09 22:59:03 +00:00
from . import HydrusConstants as HC
from . import HydrusController
from . import HydrusData
from . import HydrusExceptions
from . import HydrusGlobals as HG
from . import HydrusNetworking
from . import HydrusPaths
from . import HydrusSerialisable
from . import HydrusThreading
from . import HydrusVideoHandling
2018-01-03 22:37:30 +00:00
import gc
2015-08-19 21:48:21 +00:00
import psutil
2019-07-24 21:39:02 +00:00
import signal
2015-04-08 18:10:50 +00:00
import threading
2013-02-19 00:11:43 +00:00
import time
import traceback
2017-10-25 21:45:15 +00:00
if not HG.twisted_is_broke:
2019-08-15 00:40:48 +00:00
from twisted.internet import threads, reactor, defer
2017-10-25 21:45:15 +00:00
2020-01-29 22:08:37 +00:00
PubSubEventType = QC.QEvent.Type( QC.QEvent.registerEventType() )
2020-01-22 21:04:43 +00:00
class PubSubEvent( QC.QEvent ):
2020-01-29 22:08:37 +00:00
def __init__( self ):
2020-01-22 21:04:43 +00:00
2020-01-29 22:08:37 +00:00
QC.QEvent.__init__( self, PubSubEventType )
2020-01-22 21:04:43 +00:00
2020-02-19 21:48:36 +00:00
class PubSubEventCatcher( QC.QObject ):
2020-01-22 21:04:43 +00:00
2020-01-29 22:08:37 +00:00
def __init__( self, parent, pubsub ):
2020-01-22 21:04:43 +00:00
QC.QObject.__init__( self, parent )
2020-01-29 22:08:37 +00:00
self._pubsub = pubsub
2020-02-19 21:48:36 +00:00
self.installEventFilter( self )
2020-01-22 21:04:43 +00:00
def eventFilter( self, watched, event ):
2020-01-29 22:08:37 +00:00
if event.type() == PubSubEventType and isinstance( event, PubSubEvent ):
2020-01-22 21:04:43 +00:00
2020-01-29 22:08:37 +00:00
if self._pubsub.WorkToDo():
self._pubsub.Process()
2020-01-22 21:04:43 +00:00
2020-02-12 22:50:37 +00:00
event.accept()
2020-01-22 21:04:43 +00:00
return True
return False
2020-02-12 22:50:37 +00:00
2019-11-14 03:56:30 +00:00
class App( QW.QApplication ):
2019-07-24 21:39:02 +00:00
2020-01-29 22:08:37 +00:00
def __init__( self, pubsub, *args, **kwargs ):
2019-07-24 21:39:02 +00:00
2019-11-14 03:56:30 +00:00
QW.QApplication.__init__( self, *args, **kwargs )
2019-07-24 21:39:02 +00:00
2020-01-29 22:08:37 +00:00
self._pubsub = pubsub
2019-11-14 03:56:30 +00:00
self.setApplicationName( 'Hydrus Client' )
self.setApplicationVersion( str( HC.SOFTWARE_VERSION ) )
2019-07-24 21:39:02 +00:00
2019-11-14 03:56:30 +00:00
# Uncomment this to debug Qt warnings. Set a breakpoint on the print statement in QP.WarningHandler to be able to see where the warnings originate from.
QC.qInstallMessageHandler( QP.WarningHandler )
2019-07-24 21:39:02 +00:00
2019-11-14 03:56:30 +00:00
self.setQuitOnLastWindowClosed( True )
2019-07-24 21:39:02 +00:00
2020-02-19 21:48:36 +00:00
self.call_after_catcher = QP.CallAfterEventCatcher( self )
2020-01-22 21:04:43 +00:00
2020-02-19 21:48:36 +00:00
self.pubsub_catcher = PubSubEventCatcher( self, self._pubsub )
2020-01-22 21:04:43 +00:00
2019-11-14 03:56:30 +00:00
self.aboutToQuit.connect( self.EventEndSession )
2019-07-24 21:39:02 +00:00
2019-11-14 03:56:30 +00:00
def EventEndSession( self ):
2019-07-24 21:39:02 +00:00
2019-11-14 03:56:30 +00:00
# Since aboutToQuit gets called not only on external shutdown events (like user logging off), but even if we explicitely call QApplication.exit(),
# this check will make sure that we only do an emergency exit if it's really necessary (i.e. QApplication.exit() wasn't called by us).
if not QW.QApplication.instance().property( 'normal_exit' ):
2019-12-05 05:29:32 +00:00
2019-11-14 03:56:30 +00:00
HG.emergency_exit = True
2019-08-21 21:34:01 +00:00
2020-01-29 22:08:37 +00:00
if HG.client_controller.gui is not None and QP.isValid( HG.client_controller.gui ):
2019-11-14 03:56:30 +00:00
2020-01-02 03:05:35 +00:00
HG.client_controller.gui.SaveAndClose()
2019-11-14 03:56:30 +00:00
2019-08-21 21:34:01 +00:00
2019-07-24 21:39:02 +00:00
2015-04-01 20:44:54 +00:00
class Controller( HydrusController.HydrusController ):
2014-12-03 22:56:40 +00:00
2020-03-04 22:12:53 +00:00
my_instance = None
2019-03-20 21:22:10 +00:00
def __init__( self, db_dir ):
2015-09-16 18:11:00 +00:00
2016-08-31 19:55:14 +00:00
self._last_shutdown_was_bad = False
2017-05-10 21:33:58 +00:00
self._is_booted = False
2017-07-27 00:47:13 +00:00
self._splash = None
2020-01-29 22:08:37 +00:00
self.gui = None
2019-03-20 21:22:10 +00:00
HydrusController.HydrusController.__init__( self, db_dir )
2015-09-16 18:11:00 +00:00
2017-03-08 23:23:12 +00:00
self._name = 'client'
2017-05-10 21:33:58 +00:00
HG.client_controller = self
2015-09-16 18:11:00 +00:00
2016-02-03 22:12:53 +00:00
# just to set up some defaults, in case some db update expects something for an odd yaml-loading reason
2017-10-18 19:41:25 +00:00
self.options = ClientDefaults.GetClientDefaultOptions()
2019-03-27 22:01:02 +00:00
self.new_options = ClientOptions.ClientOptions()
2016-02-03 22:12:53 +00:00
2017-10-18 19:41:25 +00:00
HC.options = self.options
2016-02-03 22:12:53 +00:00
2018-05-23 21:05:06 +00:00
self._page_key_lock = threading.Lock()
2019-01-16 22:40:53 +00:00
self._thread_slots[ 'watcher_files' ] = ( 0, 15 )
self._thread_slots[ 'watcher_check' ] = ( 0, 5 )
self._thread_slots[ 'gallery_files' ] = ( 0, 15 )
self._thread_slots[ 'gallery_search' ] = ( 0, 5 )
2018-05-23 21:05:06 +00:00
self._alive_page_keys = set()
self._closed_page_keys = set()
2019-12-11 23:18:37 +00:00
self._last_last_session_hash = None
2015-10-28 21:29:05 +00:00
self._last_mouse_position = None
2016-01-13 22:08:19 +00:00
self._previously_idle = False
2016-05-11 18:16:39 +00:00
self._idle_started = None
2015-10-28 21:29:05 +00:00
2017-06-28 20:23:21 +00:00
self.client_files_manager = None
self.services_manager = None
2020-03-04 22:12:53 +00:00
Controller.my_instance = self
2015-09-16 18:11:00 +00:00
2015-09-02 23:16:09 +00:00
def _InitDB( self ):
2019-03-20 21:22:10 +00:00
return ClientDB.DB( self, self.db_dir, 'client' )
2015-09-02 23:16:09 +00:00
2018-06-06 21:27:02 +00:00
def _InitTempDir( self ):
2019-05-01 21:24:42 +00:00
self.temp_dir = HydrusPaths.GetTempDir()
2018-06-06 21:27:02 +00:00
2017-07-27 00:47:13 +00:00
def _DestroySplash( self ):
2019-11-14 03:56:30 +00:00
def qt_code( splash ):
2019-04-17 21:51:50 +00:00
2019-11-14 03:56:30 +00:00
if splash and QP.isValid( splash ):
2019-04-17 21:51:50 +00:00
2019-11-14 03:56:30 +00:00
splash.hide()
2019-04-17 21:51:50 +00:00
2019-11-14 03:56:30 +00:00
splash.close()
2019-04-17 21:51:50 +00:00
2017-07-27 00:47:13 +00:00
2019-04-17 21:51:50 +00:00
if self._splash is not None:
2019-01-23 22:19:16 +00:00
2019-04-17 21:51:50 +00:00
splash = self._splash
2017-07-27 00:47:13 +00:00
self._splash = None
2019-11-14 03:56:30 +00:00
QP.CallAfter( qt_code, splash )
2019-04-17 21:51:50 +00:00
2017-07-27 00:47:13 +00:00
2019-11-14 03:56:30 +00:00
def _GetPubsubValidCallable( self ):
return QP.isValid
2019-02-13 22:26:43 +00:00
def _GetUPnPServices( self ):
return self.services_manager.GetServices( ( HC.LOCAL_BOORU, HC.CLIENT_API_SERVICE ) )
2017-11-08 22:07:12 +00:00
def _ReportShutdownDaemonsStatus( self ):
names = { daemon.name for daemon in self._daemons if daemon.is_alive() }
names = list( names )
names.sort()
self.pub( 'splash_set_status_subtext', ', '.join( names ) )
2019-11-14 03:56:30 +00:00
def _ReportShutdownException( self ):
text = 'A serious error occurred while trying to exit the program. Its traceback may be shown next. It should have also been written to client.log. You may need to quit the program from task manager.'
HydrusData.DebugPrint( text )
HydrusData.DebugPrint( traceback.format_exc() )
self.SafeShowCriticalMessage( 'shutdown error', text )
self.SafeShowCriticalMessage( 'shutdown error', traceback.format_exc() )
2019-12-05 05:29:32 +00:00
def _ShutdownSubscriptionsManager( self ):
self.subscriptions_manager.Shutdown()
started = HydrusData.GetNow()
while not self.subscriptions_manager.IsShutdown():
time.sleep( 0.1 )
if HydrusData.TimeHasPassed( started + 30 ):
break
2020-03-04 22:12:53 +00:00
@staticmethod
def instance() -> 'Controller':
if Controller.my_instance is None:
raise Exception( 'Controller is not yet initialised!' )
else:
return Controller.my_instance
2018-05-23 21:05:06 +00:00
def AcquirePageKey( self ):
with self._page_key_lock:
page_key = HydrusData.GenerateKey()
self._alive_page_keys.add( page_key )
return page_key
2020-03-04 22:12:53 +00:00
def CallBlockingToQt( self, win, func, *args, **kwargs ):
2015-08-26 21:18:39 +00:00
2020-03-04 22:12:53 +00:00
def qt_code( win: QW.QWidget, job_key: ClientThreading.JobKey ):
2015-08-26 21:18:39 +00:00
try:
2019-11-14 03:56:30 +00:00
if win is not None and not QP.isValid( win ):
2019-01-30 22:14:54 +00:00
2019-11-28 01:11:46 +00:00
if HG.view_shutdown:
raise HydrusExceptions.ShutdownException( 'Application is shutting down!' )
else:
raise HydrusExceptions.QtDeadWindowException('Parent Window was destroyed before Qt command was called!')
2019-01-30 22:14:54 +00:00
2017-01-25 22:56:55 +00:00
result = func( *args, **kwargs )
2015-08-26 21:18:39 +00:00
job_key.SetVariable( 'result', result )
2019-11-14 03:56:30 +00:00
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.InsufficientCredentialsException, HydrusExceptions.ShutdownException ) as e:
2015-12-23 22:51:04 +00:00
2020-03-04 22:12:53 +00:00
job_key.SetErrorException( e )
2015-12-23 22:51:04 +00:00
2015-08-26 21:18:39 +00:00
except Exception as e:
2020-03-04 22:12:53 +00:00
job_key.SetErrorException( e )
2015-08-26 21:18:39 +00:00
2019-11-14 03:56:30 +00:00
HydrusData.Print( 'CallBlockingToQt just caught this error:' )
2015-09-02 23:16:09 +00:00
HydrusData.DebugPrint( traceback.format_exc() )
2016-07-06 21:13:15 +00:00
finally:
job_key.Finish()
2015-08-26 21:18:39 +00:00
2019-01-23 22:19:16 +00:00
job_key = ClientThreading.JobKey( cancel_on_shutdown = False )
2015-08-26 21:18:39 +00:00
job_key.Begin()
2019-11-14 03:56:30 +00:00
QP.CallAfter( qt_code, win, job_key )
2015-08-26 21:18:39 +00:00
while not job_key.IsDone():
2019-11-14 03:56:30 +00:00
if not HG.qt_app_running:
2015-11-04 22:30:28 +00:00
2018-01-03 22:37:30 +00:00
raise HydrusExceptions.ShutdownException( 'Application is shutting down!' )
2015-11-04 22:30:28 +00:00
2015-08-26 21:18:39 +00:00
2020-01-22 21:04:43 +00:00
time.sleep( 0.02 )
2015-08-26 21:18:39 +00:00
2016-01-06 21:17:20 +00:00
if job_key.HasVariable( 'result' ):
2019-11-14 03:56:30 +00:00
# result can be None, for qt_code that has no return variable
2016-01-06 21:17:20 +00:00
2016-09-28 18:48:01 +00:00
result = job_key.GetIfHasVariable( 'result' )
2016-01-06 21:17:20 +00:00
2016-09-28 18:48:01 +00:00
return result
2016-01-06 21:17:20 +00:00
2016-09-28 18:48:01 +00:00
2020-03-04 22:12:53 +00:00
if job_key.HadError():
2016-01-06 21:17:20 +00:00
2020-03-04 22:12:53 +00:00
e = job_key.GetErrorException()
raise e
2016-01-06 21:17:20 +00:00
2015-08-26 21:18:39 +00:00
2016-09-28 18:48:01 +00:00
raise HydrusExceptions.ShutdownException()
2015-08-26 21:18:39 +00:00
2020-03-11 21:52:11 +00:00
def CallAfterQtSafe( self, window, func, *args, **kwargs ) -> ClientThreading.QtAwareJob:
return self.CallLaterQtSafe( window, 0, func, *args, **kwargs )
2020-02-26 22:28:52 +00:00
def CallLaterQtSafe( self, window, initial_delay, func, *args, **kwargs ) -> ClientThreading.QtAwareJob:
2018-02-14 21:47:18 +00:00
2018-05-23 21:05:06 +00:00
job_scheduler = self._GetAppropriateJobScheduler( initial_delay )
2018-02-14 21:47:18 +00:00
call = HydrusData.Call( func, *args, **kwargs )
2019-11-14 03:56:30 +00:00
job = ClientThreading.QtAwareJob( self, job_scheduler, window, initial_delay, call )
2018-02-14 21:47:18 +00:00
2019-07-10 22:38:30 +00:00
if job_scheduler is not None:
job_scheduler.AddJob( job )
2018-02-14 21:47:18 +00:00
return job
2019-11-14 03:56:30 +00:00
def CallRepeatingQtSafe(self, window, initial_delay, period, func, *args, **kwargs):
2018-02-14 21:47:18 +00:00
2018-05-23 21:05:06 +00:00
job_scheduler = self._GetAppropriateJobScheduler( period )
2018-02-14 21:47:18 +00:00
call = HydrusData.Call( func, *args, **kwargs )
2019-11-14 03:56:30 +00:00
job = ClientThreading.QtAwareRepeatingJob(self, job_scheduler, window, initial_delay, period, call)
2018-02-14 21:47:18 +00:00
2019-07-10 22:38:30 +00:00
if job_scheduler is not None:
job_scheduler.AddJob( job )
2018-02-14 21:47:18 +00:00
return job
2019-07-24 21:39:02 +00:00
def CatchSignal( self, sig, frame ):
2019-12-18 22:06:34 +00:00
if sig in ( signal.SIGINT, signal.SIGTERM ):
2019-07-24 21:39:02 +00:00
2019-12-18 22:06:34 +00:00
if sig == signal.SIGTERM:
HG.emergency_exit = True
2019-07-24 21:39:02 +00:00
2019-12-18 22:06:34 +00:00
if hasattr( self, 'gui' ):
event = QG.QCloseEvent()
QW.QApplication.postEvent( self.gui, event )
2019-07-24 21:39:02 +00:00
2015-09-02 23:16:09 +00:00
def CheckAlreadyRunning( self ):
2019-07-17 22:10:19 +00:00
2017-07-12 20:03:45 +00:00
while HydrusData.IsAlreadyRunning( self.db_dir, 'client' ):
2015-09-02 23:16:09 +00:00
2015-09-23 21:21:02 +00:00
self.pub( 'splash_set_status_text', 'client already running' )
2015-09-02 23:16:09 +00:00
2019-11-14 03:56:30 +00:00
def qt_code():
2015-09-02 23:16:09 +00:00
message = 'It looks like another instance of this client is already running, so this instance cannot start.'
message += os.linesep * 2
message += 'If the old instance is closing and does not quit for a _very_ long time, it is usually safe to force-close it from task manager.'
2019-07-17 22:10:19 +00:00
result = ClientGUIDialogsQuick.GetYesNo( self._splash, message, title = 'The client is already running.', yes_label = 'wait a bit, then try again', no_label = 'forget it' )
2019-11-14 03:56:30 +00:00
if result != QW.QDialog.Accepted:
2015-09-02 23:16:09 +00:00
2019-07-17 22:10:19 +00:00
HG.shutting_down_due_to_already_running = True
raise HydrusExceptions.ShutdownException()
2015-09-02 23:16:09 +00:00
2019-11-14 03:56:30 +00:00
self.CallBlockingToQt(self._splash, qt_code)
2015-09-02 23:16:09 +00:00
for i in range( 10, 0, -1 ):
2017-07-12 20:03:45 +00:00
if not HydrusData.IsAlreadyRunning( self.db_dir, 'client' ):
2015-09-02 23:16:09 +00:00
break
2015-09-23 21:21:02 +00:00
self.pub( 'splash_set_status_text', 'waiting ' + str( i ) + ' seconds' )
2015-09-02 23:16:09 +00:00
time.sleep( 1 )
2015-10-28 21:29:05 +00:00
def CheckMouseIdle( self ):
2019-11-14 03:56:30 +00:00
mouse_position = QG.QCursor.pos()
2015-10-28 21:29:05 +00:00
if self._last_mouse_position is None:
self._last_mouse_position = mouse_position
elif mouse_position != self._last_mouse_position:
2016-12-14 21:19:07 +00:00
idle_before_position_update = self.CurrentlyIdle()
2015-10-28 21:29:05 +00:00
self._timestamps[ 'last_mouse_action' ] = HydrusData.GetNow()
self._last_mouse_position = mouse_position
2016-12-14 21:19:07 +00:00
idle_after_position_update = self.CurrentlyIdle()
2015-10-28 21:29:05 +00:00
2016-12-14 21:19:07 +00:00
move_knocked_us_out_of_idle = ( not idle_before_position_update ) and idle_after_position_update
if move_knocked_us_out_of_idle:
2015-10-28 21:29:05 +00:00
2018-05-23 21:05:06 +00:00
self.pub( 'set_status_bar_dirty' )
2015-10-28 21:29:05 +00:00
2018-05-23 21:05:06 +00:00
def ClosePageKeys( self, page_keys ):
with self._page_key_lock:
self._closed_page_keys.update( page_keys )
2018-01-10 22:41:51 +00:00
def CreateSplash( self ):
try:
self._splash = ClientGUI.FrameSplash( self )
except:
HydrusData.Print( 'There was an error trying to start the splash screen!' )
HydrusData.Print( traceback.format_exc() )
raise
2014-12-03 22:56:40 +00:00
def CurrentlyIdle( self ):
2019-08-21 21:34:01 +00:00
if HG.program_is_shutting_down:
2019-12-05 05:29:32 +00:00
return False
2019-08-21 21:34:01 +00:00
2017-05-10 21:33:58 +00:00
if HG.force_idle_mode:
2016-01-13 22:08:19 +00:00
2016-05-11 18:16:39 +00:00
self._idle_started = 0
2016-01-13 22:08:19 +00:00
return True
2016-02-17 22:06:47 +00:00
if not HydrusData.TimeHasPassed( self._timestamps[ 'boot' ] + 120 ):
return False
2017-10-18 19:41:25 +00:00
idle_normal = self.options[ 'idle_normal' ]
idle_period = self.options[ 'idle_period' ]
idle_mouse_period = self.options[ 'idle_mouse_period' ]
2015-10-28 21:29:05 +00:00
2015-12-02 22:32:18 +00:00
if idle_normal:
2015-08-19 21:48:21 +00:00
2016-01-13 22:08:19 +00:00
currently_idle = True
2015-12-02 22:32:18 +00:00
2016-01-13 22:08:19 +00:00
if idle_period is not None:
2015-10-28 21:29:05 +00:00
2016-01-13 22:08:19 +00:00
if not HydrusData.TimeHasPassed( self._timestamps[ 'last_user_action' ] + idle_period ):
currently_idle = False
2015-10-28 21:29:05 +00:00
2016-01-13 22:08:19 +00:00
if idle_mouse_period is not None:
2015-10-28 21:29:05 +00:00
2016-01-13 22:08:19 +00:00
if not HydrusData.TimeHasPassed( self._timestamps[ 'last_mouse_action' ] + idle_mouse_period ):
currently_idle = False
2015-10-28 21:29:05 +00:00
2015-08-19 21:48:21 +00:00
2016-01-13 22:08:19 +00:00
else:
2015-10-28 21:29:05 +00:00
2016-01-13 22:08:19 +00:00
currently_idle = False
2015-10-28 21:29:05 +00:00
2016-01-13 22:08:19 +00:00
2016-05-11 18:16:39 +00:00
turning_idle = currently_idle and not self._previously_idle
2016-01-13 22:08:19 +00:00
self._previously_idle = currently_idle
if turning_idle:
2015-10-28 21:29:05 +00:00
2016-05-11 18:16:39 +00:00
self._idle_started = HydrusData.GetNow()
2016-01-13 22:08:19 +00:00
self.pub( 'wake_daemons' )
2015-10-28 21:29:05 +00:00
2015-08-19 21:48:21 +00:00
2016-05-11 18:16:39 +00:00
if not currently_idle:
self._idle_started = None
2016-01-13 22:08:19 +00:00
return currently_idle
2015-08-26 21:18:39 +00:00
2016-05-11 18:16:39 +00:00
def CurrentlyVeryIdle( self ):
2019-08-21 21:34:01 +00:00
if HG.program_is_shutting_down:
2019-12-05 05:29:32 +00:00
return False
2019-08-21 21:34:01 +00:00
2016-05-11 18:16:39 +00:00
if self._idle_started is not None and HydrusData.TimeHasPassed( self._idle_started + 3600 ):
return True
return False
2015-08-26 21:18:39 +00:00
def DoIdleShutdownWork( self ):
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'db' )
2017-10-18 19:41:25 +00:00
stop_time = HydrusData.GetNow() + ( self.options[ 'idle_shutdown_max_minutes' ] * 60 )
2015-08-26 21:18:39 +00:00
2019-06-19 22:08:48 +00:00
self.MaintainDB( maintenance_mode = HC.MAINTENANCE_SHUTDOWN, stop_time = stop_time )
2015-08-26 21:18:39 +00:00
2017-10-18 19:41:25 +00:00
if not self.options[ 'pause_repo_sync' ]:
2015-08-19 21:48:21 +00:00
2020-03-11 21:52:11 +00:00
services = self.services_manager.GetServices( HC.REPOSITORIES, randomised = True )
2015-08-26 21:18:39 +00:00
for service in services:
if HydrusData.TimeHasPassed( stop_time ):
return
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', '{} processing'.format( service.GetName() ) )
2019-06-19 22:08:48 +00:00
service.SyncProcessUpdates( maintenance_mode = HC.MAINTENANCE_SHUTDOWN, stop_time = stop_time )
2015-08-26 21:18:39 +00:00
2015-08-19 21:48:21 +00:00
2014-12-03 22:56:40 +00:00
2018-09-26 19:05:12 +00:00
self.Write( 'last_shutdown_work_time', HydrusData.GetNow() )
2014-02-19 22:37:23 +00:00
2015-08-26 21:18:39 +00:00
def Exit( self ):
2019-11-14 03:56:30 +00:00
if not self._is_booted:
HG.emergency_exit = True
2019-08-21 21:34:01 +00:00
HG.program_is_shutting_down = True
2017-05-10 21:33:58 +00:00
if HG.emergency_exit:
2015-09-02 23:16:09 +00:00
2019-07-24 21:39:02 +00:00
HydrusData.DebugPrint( 'doing fast shutdown\u2026' )
2016-01-06 21:17:20 +00:00
self.ShutdownView()
self.ShutdownModel()
2015-09-02 23:16:09 +00:00
2019-10-16 20:47:55 +00:00
HydrusData.CleanRunningFile( self.db_dir, 'client' )
2018-01-10 22:41:51 +00:00
2016-01-06 21:17:20 +00:00
else:
2015-09-02 23:16:09 +00:00
2016-01-06 21:17:20 +00:00
try:
2018-09-26 19:05:12 +00:00
last_shutdown_work_time = self.Read( 'last_shutdown_work_time' )
2019-06-19 22:08:48 +00:00
idle_shutdown_action = self.options[ 'idle_shutdown' ]
auto_shutdown_work_ok_by_user = idle_shutdown_action in ( CC.IDLE_ON_SHUTDOWN, CC.IDLE_ON_SHUTDOWN_ASK_FIRST )
2018-09-26 19:05:12 +00:00
shutdown_work_period = self.new_options.GetInteger( 'shutdown_work_period' )
2019-06-19 22:08:48 +00:00
auto_shutdown_work_due = HydrusData.TimeHasPassed( last_shutdown_work_time + shutdown_work_period )
2018-09-26 19:05:12 +00:00
2019-06-19 22:08:48 +00:00
manual_shutdown_work_not_already_set = not HG.do_idle_shutdown_work
we_can_turn_on_auto_shutdown_work = auto_shutdown_work_ok_by_user and auto_shutdown_work_due and manual_shutdown_work_not_already_set
2016-01-06 21:17:20 +00:00
2019-06-19 22:08:48 +00:00
if we_can_turn_on_auto_shutdown_work:
2016-01-13 22:08:19 +00:00
2017-10-18 19:41:25 +00:00
idle_shutdown_max_minutes = self.options[ 'idle_shutdown_max_minutes' ]
2016-08-10 19:04:08 +00:00
time_to_stop = HydrusData.GetNow() + ( idle_shutdown_max_minutes * 60 )
2018-09-12 21:36:26 +00:00
work_to_do = self.GetIdleShutdownWorkDue( time_to_stop )
if len( work_to_do ) > 0:
2016-01-13 22:08:19 +00:00
if idle_shutdown_action == CC.IDLE_ON_SHUTDOWN_ASK_FIRST:
2019-09-05 00:05:32 +00:00
from . import ClientGUIDialogsQuick
2018-07-04 20:48:28 +00:00
text = 'Is now a good time for the client to do up to ' + HydrusData.ToHumanInt( idle_shutdown_max_minutes ) + ' minutes\' maintenance work? (Will auto-no in 15 seconds)'
2018-09-12 21:36:26 +00:00
text += os.linesep * 2
text += 'The outstanding jobs appear to be:'
text += os.linesep * 2
text += os.linesep.join( work_to_do )
2016-01-13 22:08:19 +00:00
2019-09-05 00:05:32 +00:00
result = ClientGUIDialogsQuick.GetYesNo( self._splash, text, title = 'Maintenance is due', auto_no_time = 15 )
2019-11-14 03:56:30 +00:00
if result == QW.QDialog.Accepted:
2016-01-13 22:08:19 +00:00
2019-09-05 00:05:32 +00:00
HG.do_idle_shutdown_work = True
2017-07-19 21:21:41 +00:00
2019-09-05 00:05:32 +00:00
else:
# if they said no, don't keep asking
self.Write( 'last_shutdown_work_time', HydrusData.GetNow() )
2017-07-19 21:21:41 +00:00
2016-01-13 22:08:19 +00:00
else:
2017-05-10 21:33:58 +00:00
HG.do_idle_shutdown_work = True
2016-01-13 22:08:19 +00:00
2016-01-06 21:17:20 +00:00
2019-06-19 22:08:48 +00:00
if HG.do_idle_shutdown_work:
self._splash.MakeCancelShutdownButton()
2017-08-09 21:33:51 +00:00
self.CallToThreadLongRunning( self.THREADExitEverything )
2016-01-06 21:17:20 +00:00
2016-01-13 22:08:19 +00:00
except:
2016-01-06 21:17:20 +00:00
2017-07-27 00:47:13 +00:00
self._DestroySplash()
2016-01-06 21:17:20 +00:00
2016-01-13 22:08:19 +00:00
HydrusData.DebugPrint( traceback.format_exc() )
2017-05-10 21:33:58 +00:00
HG.emergency_exit = True
2016-01-13 22:08:19 +00:00
self.Exit()
2019-12-05 05:29:32 +00:00
2015-08-26 21:18:39 +00:00
2013-02-19 00:11:43 +00:00
2017-12-13 22:33:07 +00:00
def GetClipboardText( self ):
2019-11-14 03:56:30 +00:00
clipboard_text = QW.QApplication.clipboard().text()
if not clipboard_text:
2017-12-13 22:33:07 +00:00
2019-11-14 03:56:30 +00:00
raise HydrusExceptions.DataMissing( 'No text on the clipboard!' )
return clipboard_text
2017-12-13 22:33:07 +00:00
2018-09-12 21:36:26 +00:00
def GetIdleShutdownWorkDue( self, time_to_stop ):
work_to_do = []
work_to_do.extend( self.Read( 'maintenance_due', time_to_stop ) )
services = self.services_manager.GetServices( HC.REPOSITORIES )
for service in services:
if service.CanDoIdleShutdownWork():
2018-09-19 21:54:51 +00:00
work_to_do.append( service.GetName() + ' repository processing' )
2018-09-12 21:36:26 +00:00
return work_to_do
2015-10-14 21:02:25 +00:00
def GetNewOptions( self ):
2017-10-18 19:41:25 +00:00
return self.new_options
2015-10-14 21:02:25 +00:00
2017-04-05 21:16:40 +00:00
def InitClientFilesManager( self ):
2019-11-14 03:56:30 +00:00
def qt_code( missing_locations ):
2017-04-05 21:16:40 +00:00
2017-04-19 20:58:30 +00:00
with ClientGUITopLevelWindows.DialogManage( None, 'repair file system' ) as dlg:
2017-04-05 21:16:40 +00:00
panel = ClientGUIScrolledPanelsManagement.RepairFileSystemPanel( dlg, missing_locations )
dlg.SetPanel( panel )
2019-11-14 03:56:30 +00:00
if dlg.exec() == QW.QDialog.Accepted:
2017-04-05 21:16:40 +00:00
2019-05-22 22:35:06 +00:00
self.client_files_manager = ClientFiles.ClientFilesManager( self )
2017-04-05 21:16:40 +00:00
2017-06-28 20:23:21 +00:00
missing_locations = self.client_files_manager.GetMissing()
2017-04-05 21:16:40 +00:00
else:
2019-02-06 22:41:35 +00:00
raise HydrusExceptions.ShutdownException( 'File system failed, user chose to quit.' )
2017-04-05 21:16:40 +00:00
2018-05-16 20:09:50 +00:00
return missing_locations
2019-05-22 22:35:06 +00:00
self.client_files_manager = ClientFiles.ClientFilesManager( self )
self.files_maintenance_manager = ClientFiles.FilesMaintenanceManager( self )
2018-05-16 20:09:50 +00:00
missing_locations = self.client_files_manager.GetMissing()
while len( missing_locations ) > 0:
2019-11-14 03:56:30 +00:00
missing_locations = self.CallBlockingToQt(self._splash, qt_code, missing_locations)
2018-05-16 20:09:50 +00:00
2017-04-05 21:16:40 +00:00
2015-09-02 23:16:09 +00:00
def InitModel( self ):
2014-08-06 20:29:17 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_title_text', 'booting db\u2026' )
2014-08-06 20:29:17 +00:00
2015-09-02 23:16:09 +00:00
HydrusController.HydrusController.InitModel( self )
2015-08-26 21:18:39 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_text', 'initialising managers' )
2017-11-08 22:07:12 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'services' )
2017-11-08 22:07:12 +00:00
2019-12-05 05:29:32 +00:00
self.services_manager = ClientManagers.ServicesManager( self )
2017-05-10 21:33:58 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'options' )
2017-11-08 22:07:12 +00:00
2017-10-18 19:41:25 +00:00
self.options = self.Read( 'options' )
self.new_options = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS )
2015-08-26 21:18:39 +00:00
2017-10-18 19:41:25 +00:00
HC.options = self.options
2015-10-21 21:53:10 +00:00
2017-10-18 19:41:25 +00:00
if self.new_options.GetBoolean( 'use_system_ffmpeg' ):
2016-12-07 22:12:52 +00:00
if HydrusVideoHandling.FFMPEG_PATH.startswith( HC.BIN_DIR ):
HydrusVideoHandling.FFMPEG_PATH = os.path.basename( HydrusVideoHandling.FFMPEG_PATH )
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'client files' )
2017-11-08 22:07:12 +00:00
2017-04-05 21:16:40 +00:00
self.InitClientFilesManager()
2015-12-02 22:32:18 +00:00
2017-07-05 21:09:28 +00:00
#
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'network' )
2017-11-08 22:07:12 +00:00
2018-04-25 22:07:52 +00:00
self.parsing_cache = ClientCaches.ParsingCache()
2019-01-30 22:14:54 +00:00
2019-01-23 22:19:16 +00:00
client_api_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_API_MANAGER )
if client_api_manager is None:
client_api_manager = ClientAPI.APIManager()
client_api_manager._dirty = True
2019-11-14 03:56:30 +00:00
self.SafeShowCriticalMessage( 'Problem loading object', 'Your client api manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2019-01-23 22:19:16 +00:00
self.client_api_manager = client_api_manager
2018-04-25 22:07:52 +00:00
2017-07-05 21:09:28 +00:00
bandwidth_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER )
2017-08-16 21:58:06 +00:00
if bandwidth_manager is None:
2018-04-18 22:10:15 +00:00
bandwidth_manager = ClientNetworkingBandwidth.NetworkBandwidthManager()
2017-08-16 21:58:06 +00:00
ClientDefaults.SetDefaultBandwidthManagerRules( bandwidth_manager )
2017-10-11 17:38:14 +00:00
bandwidth_manager._dirty = True
2019-11-14 03:56:30 +00:00
self.SafeShowCriticalMessage( 'Problem loading object', 'Your bandwidth manager was missing on boot! I have recreated a new empty one with default rules. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2017-08-16 21:58:06 +00:00
2017-07-05 21:09:28 +00:00
session_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_SESSION_MANAGER )
2017-08-16 21:58:06 +00:00
if session_manager is None:
2018-04-18 22:10:15 +00:00
session_manager = ClientNetworkingSessions.NetworkSessionManager()
2017-08-16 21:58:06 +00:00
2017-10-11 17:38:14 +00:00
session_manager._dirty = True
2019-11-14 03:56:30 +00:00
self.SafeShowCriticalMessage( 'Problem loading object', 'Your session manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2017-08-16 21:58:06 +00:00
2017-10-11 17:38:14 +00:00
domain_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER )
if domain_manager is None:
2017-10-18 19:41:25 +00:00
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
2017-10-11 17:38:14 +00:00
2018-01-10 22:41:51 +00:00
ClientDefaults.SetDefaultDomainManagerData( domain_manager )
2017-10-11 17:38:14 +00:00
domain_manager._dirty = True
2019-11-14 03:56:30 +00:00
self.SafeShowCriticalMessage( 'Problem loading object', 'Your domain manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2017-10-11 17:38:14 +00:00
2017-10-04 17:51:58 +00:00
2017-12-06 22:06:56 +00:00
domain_manager.Initialise()
2018-10-24 21:34:02 +00:00
login_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_LOGIN_MANAGER )
if login_manager is None:
login_manager = ClientNetworkingLogin.NetworkLoginManager()
ClientDefaults.SetDefaultLoginManagerScripts( login_manager )
login_manager._dirty = True
2019-11-14 03:56:30 +00:00
self.SafeShowCriticalMessage( 'Problem loading object', 'Your login manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2018-10-24 21:34:02 +00:00
login_manager.Initialise()
2017-07-05 21:09:28 +00:00
2017-10-04 17:51:58 +00:00
self.network_engine = ClientNetworking.NetworkEngine( self, bandwidth_manager, session_manager, domain_manager, login_manager )
2017-07-05 21:09:28 +00:00
2017-08-09 21:33:51 +00:00
self.CallToThreadLongRunning( self.network_engine.MainLoop )
2017-07-05 21:09:28 +00:00
#
2019-07-31 22:01:02 +00:00
self.quick_download_manager = ClientDownloading.QuickDownloadManager( self )
self.CallToThreadLongRunning( self.quick_download_manager.MainLoop )
#
2019-06-05 19:42:39 +00:00
self.shortcuts_manager = ClientGUIShortcuts.ShortcutsManager( self )
2017-04-19 20:58:30 +00:00
2017-12-06 22:06:56 +00:00
self.local_booru_manager = ClientCaches.LocalBooruCache( self )
2017-11-08 22:07:12 +00:00
2019-12-05 05:29:32 +00:00
self.file_viewing_stats_manager = ClientManagers.FileViewingStatsManager( self )
2018-12-05 22:35:30 +00:00
2020-03-11 21:52:11 +00:00
#
2019-10-02 23:38:59 +00:00
self.pub( 'splash_set_status_subtext', 'tag display' )
2017-11-08 22:07:12 +00:00
2019-10-02 23:38:59 +00:00
tag_display_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_TAG_DISPLAY_MANAGER )
if tag_display_manager is None:
tag_display_manager = ClientTags.TagDisplayManager()
tag_display_manager._dirty = True
2019-11-14 03:56:30 +00:00
self.SafeShowCriticalMessage( 'Problem loading object', 'Your tag display manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2019-10-02 23:38:59 +00:00
self.tag_display_manager = tag_display_manager
2017-11-08 22:07:12 +00:00
2020-03-11 21:52:11 +00:00
#
self.pub( 'splash_set_status_subtext', 'favourite searches' )
favourite_search_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_FAVOURITE_SEARCH_MANAGER )
if favourite_search_manager is None:
favourite_search_manager = ClientSearch.FavouriteSearchManager()
ClientDefaults.SetDefaultFavouriteSearchManagerData( favourite_search_manager )
favourite_search_manager._dirty = True
self.SafeShowCriticalMessage( 'Problem loading object', 'Your favourite searches manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
self.favourite_search_manager = favourite_search_manager
#
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'tag siblings' )
2017-11-08 22:07:12 +00:00
2019-12-05 05:29:32 +00:00
self.tag_siblings_manager = ClientManagers.TagSiblingsManager( self )
2017-11-08 22:07:12 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'tag parents' )
2017-11-08 22:07:12 +00:00
2019-12-05 05:29:32 +00:00
self.tag_parents_manager = ClientManagers.TagParentsManager( self )
self._managers[ 'undo' ] = ClientManagers.UndoManager( self )
2014-08-06 20:29:17 +00:00
2019-11-14 03:56:30 +00:00
def qt_code():
2015-09-02 23:16:09 +00:00
2016-08-17 20:07:22 +00:00
self._caches[ 'images' ] = ClientCaches.RenderedImageCache( self )
2015-11-25 22:00:57 +00:00
self._caches[ 'thumbnail' ] = ClientCaches.ThumbnailCache( self )
2019-12-05 05:29:32 +00:00
self.bitmap_manager = ClientManagers.BitmapManager( self )
2015-09-02 23:16:09 +00:00
2020-03-11 21:52:11 +00:00
CC.GlobalPixmaps()
2015-09-02 23:16:09 +00:00
2014-08-06 20:29:17 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'image caches' )
2017-11-08 22:07:12 +00:00
2019-11-14 03:56:30 +00:00
self.CallBlockingToQt(self._splash, qt_code)
2014-08-06 20:29:17 +00:00
2017-12-13 22:33:07 +00:00
self.sub( self, 'ToClipboard', 'clipboard' )
2014-08-06 20:29:17 +00:00
2015-09-02 23:16:09 +00:00
def InitView( self ):
2014-08-06 20:29:17 +00:00
2017-10-18 19:41:25 +00:00
if self.options[ 'password' ] is not None:
2015-09-02 23:16:09 +00:00
2015-09-23 21:21:02 +00:00
self.pub( 'splash_set_status_text', 'waiting for password' )
2015-09-02 23:16:09 +00:00
2019-11-14 03:56:30 +00:00
def qt_code_password():
2015-09-02 23:16:09 +00:00
while True:
2020-03-04 22:12:53 +00:00
with ClientGUIDialogs.DialogTextEntry( self._splash, 'Enter your password.', allow_blank = True, password_entry = True, min_char_width = 24 ) as dlg:
2015-09-02 23:16:09 +00:00
2019-11-14 03:56:30 +00:00
if dlg.exec() == QW.QDialog.Accepted:
2015-09-02 23:16:09 +00:00
2019-01-09 22:59:03 +00:00
password_bytes = bytes( dlg.GetValue(), 'utf-8' )
2016-11-23 20:37:53 +00:00
2019-01-09 22:59:03 +00:00
if hashlib.sha256( password_bytes ).digest() == self.options[ 'password' ]:
break
2016-11-23 20:37:53 +00:00
else:
2019-02-06 22:41:35 +00:00
raise HydrusExceptions.InsufficientCredentialsException( 'Bad password check' )
2015-09-02 23:16:09 +00:00
2019-11-14 03:56:30 +00:00
self.CallBlockingToQt(self._splash, qt_code_password)
2015-09-02 23:16:09 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_title_text', 'booting gui\u2026' )
2015-09-02 23:16:09 +00:00
2019-12-05 05:29:32 +00:00
self.subscriptions_manager = ClientImportSubscriptions.SubscriptionsManager( self )
2019-11-14 03:56:30 +00:00
def qt_code_gui():
2015-09-02 23:16:09 +00:00
2019-12-05 05:29:32 +00:00
ClientGUIStyle.InitialiseDefaults()
qt_style_name = self.new_options.GetNoneableString( 'qt_style_name' )
if qt_style_name is not None:
try:
2019-12-11 23:18:37 +00:00
ClientGUIStyle.SetStyleFromName( qt_style_name )
2019-12-05 05:29:32 +00:00
except Exception as e:
HydrusData.Print( 'Could not load Qt style: {}'.format( e ) )
qt_stylesheet_name = self.new_options.GetNoneableString( 'qt_stylesheet_name' )
2020-02-26 22:28:52 +00:00
if qt_stylesheet_name is None:
ClientGUIStyle.ClearStylesheet()
else:
2019-12-05 05:29:32 +00:00
try:
2019-12-11 23:18:37 +00:00
ClientGUIStyle.SetStylesheetFromPath( qt_stylesheet_name )
2019-12-05 05:29:32 +00:00
except Exception as e:
HydrusData.Print( 'Could not load Qt stylesheet: {}'.format( e ) )
2017-07-19 21:21:41 +00:00
self.gui = ClientGUI.FrameGUI( self )
2015-09-02 23:16:09 +00:00
self.ResetIdleTimer()
2019-12-05 05:29:32 +00:00
self.CallBlockingToQt( self._splash, qt_code_gui )
2015-09-02 23:16:09 +00:00
2016-08-24 18:36:56 +00:00
# ShowText will now popup as a message, as popup message manager has overwritten the hooks
2015-09-02 23:16:09 +00:00
HydrusController.HydrusController.InitView( self )
2019-08-15 00:40:48 +00:00
self._service_keys_to_connected_ports = {}
2014-08-06 20:29:17 +00:00
2019-08-15 00:40:48 +00:00
self.RestartClientServerServices()
2014-08-06 20:29:17 +00:00
2019-03-20 21:22:10 +00:00
if not HG.no_daemons:
2016-01-20 23:57:33 +00:00
2017-01-11 22:31:30 +00:00
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'MaintainTrash', ClientDaemons.DAEMONMaintainTrash, init_wait = 120 ) )
2019-05-22 22:35:06 +00:00
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'SynchroniseRepositories', ClientDaemons.DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions', 'wake_idle_workers' ), period = 4 * 3600, pre_call_wait = 1 ) )
2016-12-14 21:19:07 +00:00
2015-09-02 23:16:09 +00:00
2019-09-25 21:34:18 +00:00
self.files_maintenance_manager.Start()
2019-02-13 22:26:43 +00:00
job = self.CallRepeating( 0.0, 30.0, self.SaveDirtyObjects )
job.WakeOnPubSub( 'important_dirt_to_clean' )
self._daemon_jobs[ 'save_dirty_objects' ] = job
job = self.CallRepeating( 5.0, 3600.0, self.SynchroniseAccounts )
job.ShouldDelayOnWakeup( True )
job.WakeOnPubSub( 'notify_unknown_accounts' )
self._daemon_jobs[ 'synchronise_accounts' ] = job
2020-01-02 03:05:35 +00:00
job = self.CallRepeatingQtSafe( self, 10.0, 10.0, self.CheckMouseIdle )
2019-02-13 22:26:43 +00:00
self._daemon_jobs[ 'check_mouse_idle' ] = job
2018-02-21 21:59:37 +00:00
2017-07-12 20:03:45 +00:00
if self.db.IsFirstStart():
2016-08-24 18:36:56 +00:00
message = 'Hi, this looks like the first time you have started the hydrus client.'
message += os.linesep * 2
2020-01-29 22:08:37 +00:00
message += 'Don\'t forget to check out the help if you haven\'t already--it has an extensive \'getting started\' section, including how to update and the importance of backing up your database.'
2016-08-24 18:36:56 +00:00
message += os.linesep * 2
2016-10-19 20:02:56 +00:00
message += 'To dismiss popup messages like this, right-click them.'
2016-08-24 18:36:56 +00:00
HydrusData.ShowText( message )
2017-07-12 20:03:45 +00:00
if self.db.IsDBUpdated():
2016-08-24 18:36:56 +00:00
HydrusData.ShowText( 'The client has updated to version ' + str( HC.SOFTWARE_VERSION ) + '!' )
2015-06-03 21:05:13 +00:00
2017-07-12 20:03:45 +00:00
for message in self.db.GetInitialMessages():
2016-12-21 22:30:54 +00:00
HydrusData.ShowText( message )
2014-08-06 20:29:17 +00:00
2017-05-10 21:33:58 +00:00
def IsBooted( self ):
return self._is_booted
2016-08-31 19:55:14 +00:00
def LastShutdownWasBad( self ):
return self._last_shutdown_was_bad
2019-06-19 22:08:48 +00:00
def MaintainDB( self, maintenance_mode = HC.MAINTENANCE_IDLE, stop_time = None ):
if maintenance_mode == HC.MAINTENANCE_IDLE and not self.GoodTimeToStartBackgroundWork():
return
2019-02-27 23:03:30 +00:00
2019-06-19 22:08:48 +00:00
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
2019-02-27 23:03:30 +00:00
return
2013-02-19 00:11:43 +00:00
2019-07-31 22:01:02 +00:00
tree_stop_time = stop_time
if tree_stop_time is None:
2017-01-25 22:56:55 +00:00
2019-07-31 22:01:02 +00:00
tree_stop_time = HydrusData.GetNow() + 30
2017-01-25 22:56:55 +00:00
2019-07-31 22:01:02 +00:00
self.WriteSynchronous( 'maintain_similar_files_tree', stop_time = tree_stop_time )
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
2017-01-25 22:56:55 +00:00
2019-07-31 22:01:02 +00:00
return
2017-01-25 22:56:55 +00:00
2019-07-31 22:01:02 +00:00
if self.new_options.GetBoolean( 'maintain_similar_files_duplicate_pairs_during_idle' ):
2019-06-19 22:08:48 +00:00
2017-10-18 19:41:25 +00:00
search_distance = self.new_options.GetInteger( 'similar_files_duplicate_pairs_search_distance' )
2017-01-25 22:56:55 +00:00
search_stop_time = stop_time
if search_stop_time is None:
search_stop_time = HydrusData.GetNow() + 60
2019-07-31 22:01:02 +00:00
self.WriteSynchronous( 'maintain_similar_files_search_for_potential_duplicates', search_distance, stop_time = search_stop_time )
2018-06-20 20:20:22 +00:00
2019-06-19 22:08:48 +00:00
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
2017-03-29 19:39:34 +00:00
2019-06-19 22:08:48 +00:00
return
2017-03-29 19:39:34 +00:00
2016-01-06 21:17:20 +00:00
2019-06-19 22:08:48 +00:00
self.WriteSynchronous( 'vacuum', maintenance_mode = maintenance_mode, stop_time = stop_time )
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
2017-03-29 19:39:34 +00:00
2019-06-19 22:08:48 +00:00
return
2017-03-29 19:39:34 +00:00
2015-08-26 21:18:39 +00:00
2019-06-19 22:08:48 +00:00
self.WriteSynchronous( 'analyze', maintenance_mode = maintenance_mode, stop_time = stop_time )
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
return
2015-08-26 21:18:39 +00:00
2019-06-19 22:08:48 +00:00
2015-08-26 21:18:39 +00:00
2018-04-25 22:07:52 +00:00
def MaintainMemoryFast( self ):
HydrusController.HydrusController.MaintainMemoryFast( self )
self.parsing_cache.CleanCache()
2017-07-05 21:09:28 +00:00
def MaintainMemorySlow( self ):
2015-08-26 21:18:39 +00:00
2017-07-05 21:09:28 +00:00
HydrusController.HydrusController.MaintainMemorySlow( self )
2015-08-26 21:18:39 +00:00
2017-05-10 21:33:58 +00:00
if HydrusData.TimeHasPassed( self._timestamps[ 'last_page_change' ] + 30 * 60 ):
2018-01-10 22:41:51 +00:00
self.pub( 'delete_old_closed_pages' )
2015-08-26 21:18:39 +00:00
self._timestamps[ 'last_page_change' ] = HydrusData.GetNow()
2017-10-18 19:41:25 +00:00
disk_cache_maintenance_mb = self.new_options.GetNoneableInteger( 'disk_cache_maintenance_mb' )
2017-05-10 21:33:58 +00:00
2019-07-31 22:01:02 +00:00
if disk_cache_maintenance_mb is not None and not HG.view_shutdown:
2015-08-26 21:18:39 +00:00
2019-01-23 22:19:16 +00:00
cache_period = 3600
disk_cache_stop_time = HydrusData.GetNow() + 2
2015-08-26 21:18:39 +00:00
2017-09-06 20:18:20 +00:00
if HydrusData.TimeHasPassed( self._timestamps[ 'last_disk_cache_population' ] + cache_period ):
self.Read( 'load_into_disk_cache', stop_time = disk_cache_stop_time, caller_limit = disk_cache_maintenance_mb * 1024 * 1024 )
self._timestamps[ 'last_disk_cache_population' ] = HydrusData.GetNow()
2015-08-26 21:18:39 +00:00
2020-01-22 21:04:43 +00:00
def do_gui_refs( gui ):
if self.gui is not None and QP.isValid( self.gui ):
self.gui.MaintainCanvasFrameReferences()
QP.CallAfter( do_gui_refs, self.gui )
2015-08-26 21:18:39 +00:00
2018-05-23 21:05:06 +00:00
def PageAlive( self, page_key ):
2015-09-16 18:11:00 +00:00
2018-05-23 21:05:06 +00:00
with self._page_key_lock:
2016-02-17 22:06:47 +00:00
2018-05-23 21:05:06 +00:00
return page_key in self._alive_page_keys
2016-02-17 22:06:47 +00:00
2015-09-16 18:11:00 +00:00
2017-05-03 21:33:48 +00:00
def PageClosedButNotDestroyed( self, page_key ):
2015-10-07 21:56:22 +00:00
2018-05-23 21:05:06 +00:00
with self._page_key_lock:
2017-06-21 21:15:59 +00:00
2018-05-23 21:05:06 +00:00
return page_key in self._closed_page_keys
2017-06-21 21:15:59 +00:00
2015-10-07 21:56:22 +00:00
2013-02-19 00:11:43 +00:00
def PrepStringForDisplay( self, text ):
2017-08-02 21:32:54 +00:00
return text.lower()
2017-07-27 00:47:13 +00:00
def ProcessPubSub( self ):
2019-11-14 03:56:30 +00:00
self.CallBlockingToQt( self.app, self._pubsub.Process )
2013-02-19 00:11:43 +00:00
2020-01-22 21:04:43 +00:00
# this needs to be blocking in some way or the pubsub daemon goes nuts
#QW.QApplication.instance().postEvent( QW.QApplication.instance().pubsub_catcher, PubSubEvent( self._pubsub ) )
2013-02-19 00:11:43 +00:00
2017-03-02 02:14:56 +00:00
def RefreshServices( self ):
2017-06-28 20:23:21 +00:00
self.services_manager.RefreshServices()
2017-03-02 02:14:56 +00:00
2020-01-29 22:08:37 +00:00
def pub( self, *args, **kwargs ):
HydrusController.HydrusController.pub( self, *args, **kwargs )
QW.QApplication.instance().postEvent( QW.QApplication.instance().pubsub_catcher, PubSubEvent() )
2018-05-23 21:05:06 +00:00
def ReleasePageKey( self, page_key ):
with self._page_key_lock:
self._alive_page_keys.discard( page_key )
self._closed_page_keys.discard( page_key )
2020-01-02 03:05:35 +00:00
def ReportFirstSessionLoaded( self ):
job = self.CallRepeating( 5.0, 180.0, ClientDaemons.DAEMONCheckImportFolders )
job.WakeOnPubSub( 'notify_restart_import_folders_daemon' )
job.WakeOnPubSub( 'notify_new_import_folders' )
job.ShouldDelayOnWakeup( True )
self._daemon_jobs[ 'import_folders' ] = job
job = self.CallRepeating( 5.0, 180.0, ClientDaemons.DAEMONCheckExportFolders )
job.WakeOnPubSub( 'notify_restart_export_folders_daemon' )
job.WakeOnPubSub( 'notify_new_export_folders' )
job.ShouldDelayOnWakeup( True )
self._daemon_jobs[ 'export_folders' ] = job
self.subscriptions_manager.Start()
2015-08-26 21:18:39 +00:00
def ResetPageChangeTimer( self ):
self._timestamps[ 'last_page_change' ] = HydrusData.GetNow()
2019-08-15 00:40:48 +00:00
def RestartClientServerServices( self ):
2014-07-09 22:15:14 +00:00
2019-08-15 00:40:48 +00:00
services = [ self.services_manager.GetService( service_key ) for service_key in ( CC.LOCAL_BOORU_SERVICE_KEY, CC.CLIENT_API_SERVICE_KEY ) ]
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
services = [ service for service in services if service.GetPort() is not None ]
2014-07-09 22:15:14 +00:00
2019-08-15 00:40:48 +00:00
self.CallToThread( self.SetRunningTwistedServices, services )
2013-09-25 20:20:10 +00:00
2014-01-08 18:40:02 +00:00
def RestoreDatabase( self ):
2019-09-05 00:05:32 +00:00
from . import ClientGUIDialogsQuick
2017-07-12 20:03:45 +00:00
2019-11-14 03:56:30 +00:00
with QP.DirDialog( self.gui, 'Select backup location.' ) as dlg:
2014-01-08 18:40:02 +00:00
2019-11-14 03:56:30 +00:00
if dlg.exec() == QW.QDialog.Accepted:
2014-01-08 18:40:02 +00:00
2019-01-09 22:59:03 +00:00
path = dlg.GetPath()
2014-01-08 18:40:02 +00:00
2014-12-10 22:02:39 +00:00
text = 'Are you sure you want to restore a backup from "' + path + '"?'
text += os.linesep * 2
text += 'Everything in your current database will be deleted!'
text += os.linesep * 2
text += 'The gui will shut down, and then it will take a while to complete the restore. Once it is done, the client will restart.'
2014-01-08 18:40:02 +00:00
2019-09-05 00:05:32 +00:00
result = ClientGUIDialogsQuick.GetYesNo( self.gui, text )
2019-11-14 03:56:30 +00:00
if result == QW.QDialog.Accepted:
2014-01-08 18:40:02 +00:00
2019-09-05 00:05:32 +00:00
def THREADRestart():
2014-01-08 18:40:02 +00:00
2019-09-05 00:05:32 +00:00
while not self.db.LoopIsFinished():
2015-06-17 20:01:41 +00:00
2019-09-05 00:05:32 +00:00
time.sleep( 0.1 )
2015-06-17 20:01:41 +00:00
2019-09-05 00:05:32 +00:00
self.db.RestoreBackup( path )
while not HG.shutdown_complete:
2015-06-17 20:01:41 +00:00
2019-09-05 00:05:32 +00:00
time.sleep( 0.1 )
2015-06-17 20:01:41 +00:00
2014-01-08 18:40:02 +00:00
2019-09-05 00:05:32 +00:00
HydrusData.RestartProcess()
2019-02-27 23:03:30 +00:00
2015-09-02 23:16:09 +00:00
2019-09-05 00:05:32 +00:00
self.CallToThreadLongRunning( THREADRestart )
2020-01-02 03:05:35 +00:00
QP.CallAfter( self.gui.SaveAndClose )
2019-09-05 00:05:32 +00:00
2015-09-02 23:16:09 +00:00
def Run( self ):
2019-11-14 03:56:30 +00:00
QP.MonkeyPatchMissingMethods()
2019-11-20 23:10:46 +00:00
2020-03-04 22:12:53 +00:00
from . import ClientGUICore
ClientGUICore.GUICore()
2020-01-29 22:08:37 +00:00
self.app = App( self._pubsub, sys.argv )
2018-01-10 22:41:51 +00:00
2019-01-09 22:59:03 +00:00
HydrusData.Print( 'booting controller\u2026' )
2015-11-18 22:44:07 +00:00
2019-11-14 03:56:30 +00:00
self.frame_icon_pixmap = QG.QPixmap( os.path.join( HC.STATIC_DIR, 'hydrus_32_non-transparent.png' ) )
2017-11-22 21:03:07 +00:00
2018-01-10 22:41:51 +00:00
self.CreateSplash()
2015-09-02 23:16:09 +00:00
2019-07-24 21:39:02 +00:00
signal.signal( signal.SIGINT, self.CatchSignal )
2019-12-18 22:06:34 +00:00
signal.signal( signal.SIGTERM, self.CatchSignal )
2019-07-24 21:39:02 +00:00
2017-08-09 21:33:51 +00:00
self.CallToThreadLongRunning( self.THREADBootEverything )
2015-09-02 23:16:09 +00:00
2019-11-14 03:56:30 +00:00
HG.qt_app_running = True
try:
self.app.exec_()
finally:
HG.qt_app_running = False
2015-09-02 23:16:09 +00:00
2019-01-23 22:19:16 +00:00
HydrusData.DebugPrint( 'shutting down controller\u2026' )
2015-09-02 23:16:09 +00:00
2019-11-14 03:56:30 +00:00
def SafeShowCriticalMessage( self, title, message ):
HydrusData.DebugPrint( title )
HydrusData.DebugPrint( message )
if QC.QThread.currentThread() == QW.QApplication.instance().thread():
QW.QMessageBox.critical( None, title, message )
else:
self.CallBlockingToQt( self.app, QW.QMessageBox.critical, None, title, message )
2017-03-02 02:14:56 +00:00
def SaveDirtyObjects( self ):
2017-05-10 21:33:58 +00:00
with HG.dirty_object_lock:
2017-03-02 02:14:56 +00:00
2017-06-28 20:23:21 +00:00
dirty_services = [ service for service in self.services_manager.GetServices() if service.IsDirty() ]
2017-03-02 02:14:56 +00:00
if len( dirty_services ) > 0:
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'services' )
2017-03-02 02:14:56 +00:00
self.WriteSynchronous( 'dirty_services', dirty_services )
2019-01-30 22:14:54 +00:00
2019-01-23 22:19:16 +00:00
if self.client_api_manager.IsDirty():
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'client api manager' )
2019-01-23 22:19:16 +00:00
self.WriteSynchronous( 'serialisable', self.client_api_manager )
self.client_api_manager.SetClean()
2019-01-30 22:14:54 +00:00
2017-07-05 21:09:28 +00:00
if self.network_engine.bandwidth_manager.IsDirty():
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'bandwidth manager' )
2017-07-05 21:09:28 +00:00
self.WriteSynchronous( 'serialisable', self.network_engine.bandwidth_manager )
self.network_engine.bandwidth_manager.SetClean()
2017-10-11 17:38:14 +00:00
if self.network_engine.domain_manager.IsDirty():
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'domain manager' )
2017-10-11 17:38:14 +00:00
self.WriteSynchronous( 'serialisable', self.network_engine.domain_manager )
self.network_engine.domain_manager.SetClean()
2017-07-05 21:09:28 +00:00
2018-10-24 21:34:02 +00:00
if self.network_engine.login_manager.IsDirty():
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'login manager' )
2018-10-24 21:34:02 +00:00
self.WriteSynchronous( 'serialisable', self.network_engine.login_manager )
self.network_engine.login_manager.SetClean()
2017-07-05 21:09:28 +00:00
if self.network_engine.session_manager.IsDirty():
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'session manager' )
2017-07-05 21:09:28 +00:00
self.WriteSynchronous( 'serialisable', self.network_engine.session_manager )
self.network_engine.session_manager.SetClean()
2020-03-11 21:52:11 +00:00
if self.favourite_search_manager.IsDirty():
self.pub( 'splash_set_status_subtext', 'favourite searches manager' )
self.WriteSynchronous( 'serialisable', self.favourite_search_manager )
self.favourite_search_manager.SetClean()
2019-10-02 23:38:59 +00:00
if self.tag_display_manager.IsDirty():
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'tag display manager' )
2019-10-02 23:38:59 +00:00
self.WriteSynchronous( 'serialisable', self.tag_display_manager )
self.tag_display_manager.SetClean()
2017-03-02 02:14:56 +00:00
2019-12-11 23:18:37 +00:00
def SaveGUISession( self, session ):
name = session.GetName()
if name == 'last session':
session_hash = hashlib.sha256( bytes( session.DumpToString(), 'utf-8' ) ).digest()
if session_hash == self._last_last_session_hash:
return
self._last_last_session_hash = session_hash
self.WriteSynchronous( 'serialisable', session )
self.pub( 'notify_new_sessions' )
2019-08-15 00:40:48 +00:00
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()
name = service.GetName()
port = service.GetPort()
allow_non_local_connections = service.AllowsNonLocalConnections()
if port is None:
continue
try:
from . import ClientLocalServer
if service_type == HC.LOCAL_BOORU:
http_factory = ClientLocalServer.HydrusServiceBooru( service, allow_non_local_connections = allow_non_local_connections )
elif service_type == HC.CLIENT_API_SERVICE:
http_factory = ClientLocalServer.HydrusServiceClientAPI( service, allow_non_local_connections = allow_non_local_connections )
self._service_keys_to_connected_ports[ service_key ] = reactor.listenTCP( port, http_factory )
if not HydrusNetworking.LocalPortInUse( port ):
HydrusData.ShowText( 'Tried to bind port {} for "{}" but it failed.'.format( port, name ) )
except Exception as e:
HydrusData.ShowText( 'Could not start "{}":'.format( name ) )
HydrusData.ShowException( e )
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()
if HG.twisted_is_broke:
if True in ( service.GetPort() is not None for service in services ):
HydrusData.ShowText( 'Twisted failed to import, so could not start the local booru/client api! Please contact hydrus dev!' )
else:
threads.blockingCallFromThread( reactor, TWISTEDDoIt )
2017-03-02 02:14:56 +00:00
def SetServices( self, services ):
2017-05-10 21:33:58 +00:00
with HG.dirty_object_lock:
2017-03-02 02:14:56 +00:00
2019-02-13 22:26:43 +00:00
upnp_services = [ service for service in services if service.GetServiceType() in ( HC.LOCAL_BOORU, HC.CLIENT_API_SERVICE ) ]
self.CallToThread( self.services_upnp_manager.SetServices, upnp_services )
2017-03-02 02:14:56 +00:00
self.WriteSynchronous( 'update_services', services )
2017-06-28 20:23:21 +00:00
self.services_manager.RefreshServices()
2017-03-02 02:14:56 +00:00
2019-08-15 00:40:48 +00:00
self.RestartClientServerServices()
2017-03-02 02:14:56 +00:00
2017-07-05 21:09:28 +00:00
def ShutdownModel( self ):
2019-12-05 05:29:32 +00:00
self.pub( 'splash_set_status_text', 'saving and exiting objects' )
2019-11-14 03:56:30 +00:00
if self._is_booted:
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'file viewing stats flush' )
2019-11-14 03:56:30 +00:00
self.file_viewing_stats_manager.Flush()
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', '' )
2019-11-14 03:56:30 +00:00
self.SaveDirtyObjects()
2017-07-05 21:09:28 +00:00
HydrusController.HydrusController.ShutdownModel( self )
2015-09-02 23:16:09 +00:00
def ShutdownView( self ):
2017-05-10 21:33:58 +00:00
if not HG.emergency_exit:
2015-09-02 23:16:09 +00:00
2019-12-05 05:29:32 +00:00
self.pub( 'splash_set_status_text', 'waiting for subscriptions to exit' )
self._ShutdownSubscriptionsManager()
2016-01-06 21:17:20 +00:00
self.pub( 'splash_set_status_text', 'waiting for daemons to exit' )
2015-09-02 23:16:09 +00:00
2016-01-06 21:17:20 +00:00
self._ShutdownDaemons()
2019-12-05 05:29:32 +00:00
self.pub( 'splash_set_status_subtext', '' )
2017-05-10 21:33:58 +00:00
if HG.do_idle_shutdown_work:
2015-09-02 23:16:09 +00:00
2019-12-05 05:29:32 +00:00
self.pub( 'splash_set_status_text', 'waiting for idle shutdown work' )
2017-03-29 19:39:34 +00:00
try:
self.DoIdleShutdownWork()
2019-12-11 23:18:37 +00:00
self.pub( 'splash_set_status_subtext', '' )
2017-03-29 19:39:34 +00:00
except:
2019-11-14 03:56:30 +00:00
self._ReportShutdownException()
2017-03-29 19:39:34 +00:00
2015-09-02 23:16:09 +00:00
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'files maintenance manager' )
2019-09-25 21:34:18 +00:00
self.files_maintenance_manager.Shutdown()
2020-01-02 03:05:35 +00:00
self.pub( 'splash_set_status_subtext', 'download manager' )
self.quick_download_manager.Shutdown()
self.pub( 'splash_set_status_subtext', '' )
2019-09-25 21:34:18 +00:00
try:
2019-12-11 23:18:37 +00:00
self.pub( 'splash_set_status_text', 'waiting for twisted to exit' )
2019-09-25 21:34:18 +00:00
self.SetRunningTwistedServices( [] )
except:
pass # sometimes this throws a wobbler, screw it
2019-08-21 21:34:01 +00:00
2019-08-15 00:40:48 +00:00
2016-01-13 22:08:19 +00:00
HydrusController.HydrusController.ShutdownView( self )
2015-09-16 18:11:00 +00:00
2014-01-08 18:40:02 +00:00
2019-02-13 22:26:43 +00:00
def SynchroniseAccounts( self ):
2020-03-11 21:52:11 +00:00
services = self.services_manager.GetServices( HC.RESTRICTED_SERVICES, randomised = True )
2019-02-13 22:26:43 +00:00
for service in services:
if HydrusThreading.IsThreadShuttingDown():
return
service.SyncAccount()
2015-08-26 21:18:39 +00:00
def SystemBusy( self ):
2016-01-13 22:08:19 +00:00
2017-05-10 21:33:58 +00:00
if HG.force_idle_mode:
2016-01-13 22:08:19 +00:00
return False
2017-10-18 19:41:25 +00:00
max_cpu = self.options[ 'idle_cpu_max' ]
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
if max_cpu is None:
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
self._system_busy = False
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
else:
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
if HydrusData.TimeHasPassed( self._timestamps[ 'last_cpu_check' ] + 60 ):
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
cpu_times = psutil.cpu_percent( percpu = True )
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
if True in ( cpu_time > max_cpu for cpu_time in cpu_times ):
self._system_busy = True
else:
self._system_busy = False
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
self._timestamps[ 'last_cpu_check' ] = HydrusData.GetNow()
2015-08-26 21:18:39 +00:00
return self._system_busy
2013-08-28 21:31:52 +00:00
2015-04-08 18:10:50 +00:00
def THREADBootEverything( self ):
try:
2015-09-02 23:16:09 +00:00
self.CheckAlreadyRunning()
2015-04-08 18:10:50 +00:00
2019-10-16 20:47:55 +00:00
except HydrusExceptions.ShutdownException:
self._DestroySplash()
return
try:
2017-07-12 20:03:45 +00:00
self._last_shutdown_was_bad = HydrusData.LastShutdownWasBad( self.db_dir, 'client' )
2016-08-31 19:55:14 +00:00
2017-07-12 20:03:45 +00:00
HydrusData.RecordRunningStart( self.db_dir, 'client' )
2015-04-08 18:10:50 +00:00
2015-09-02 23:16:09 +00:00
self.InitModel()
2015-05-13 20:22:39 +00:00
2015-09-02 23:16:09 +00:00
self.InitView()
2015-04-29 19:20:35 +00:00
2017-05-10 21:33:58 +00:00
self._is_booted = True
2019-02-06 22:41:35 +00:00
except ( HydrusExceptions.InsufficientCredentialsException, HydrusExceptions.ShutdownException ) as e:
2015-04-08 18:10:50 +00:00
2015-11-18 22:44:07 +00:00
HydrusData.Print( e )
2015-04-08 18:10:50 +00:00
2019-11-28 01:11:46 +00:00
HydrusData.CleanRunningFile( self.db_dir, 'client' )
2019-11-14 03:56:30 +00:00
QP.CallAfter( QW.QApplication.exit, 0 )
2015-04-08 18:10:50 +00:00
2016-01-13 22:08:19 +00:00
except Exception as e:
2018-07-18 21:07:15 +00:00
text = 'A serious error occurred while trying to start the program. The error will be shown next in a window. More information may have been written to client.log.'
2015-09-02 23:16:09 +00:00
2018-02-21 21:59:37 +00:00
HydrusData.DebugPrint( 'If the db crashed, another error may be written just above ^.' )
2015-09-02 23:16:09 +00:00
HydrusData.DebugPrint( text )
2015-04-08 18:10:50 +00:00
2016-10-19 20:02:56 +00:00
HydrusData.DebugPrint( traceback.format_exc() )
2016-01-13 22:08:19 +00:00
2019-11-14 03:56:30 +00:00
self.SafeShowCriticalMessage( 'boot error', text )
2016-01-13 22:08:19 +00:00
2019-11-14 03:56:30 +00:00
self.SafeShowCriticalMessage( 'boot error', traceback.format_exc() )
2016-01-13 22:08:19 +00:00
2019-11-14 03:56:30 +00:00
QP.CallAfter( QW.QApplication.exit, 0 )
2015-04-08 18:10:50 +00:00
finally:
2017-07-27 00:47:13 +00:00
self._DestroySplash()
2015-04-08 18:10:50 +00:00
def THREADExitEverything( self ):
2015-06-03 21:05:13 +00:00
2015-04-08 18:10:50 +00:00
try:
2019-01-23 22:19:16 +00:00
gc.collect()
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_title_text', 'shutting down gui\u2026' )
2015-09-23 21:21:02 +00:00
2015-09-02 23:16:09 +00:00
self.ShutdownView()
2015-04-08 18:10:50 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_title_text', 'shutting down db\u2026' )
2015-09-23 21:21:02 +00:00
2015-09-02 23:16:09 +00:00
self.ShutdownModel()
2015-04-08 18:10:50 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_title_text', 'cleaning up\u2026' )
2017-08-09 21:33:51 +00:00
2019-10-16 20:47:55 +00:00
HydrusData.CleanRunningFile( self.db_dir, 'client' )
2016-06-15 18:59:44 +00:00
2019-02-06 22:41:35 +00:00
except ( HydrusExceptions.InsufficientCredentialsException, HydrusExceptions.ShutdownException ):
2017-07-27 00:47:13 +00:00
pass
2015-04-08 18:10:50 +00:00
except:
2019-11-14 03:56:30 +00:00
self._ReportShutdownException()
2015-04-08 18:10:50 +00:00
finally:
2019-11-14 03:56:30 +00:00
QW.QApplication.instance().setProperty( 'normal_exit', True )
2020-01-02 03:05:35 +00:00
self._DestroySplash()
QP.CallAfter( QW.QApplication.exit )
2015-04-08 18:10:50 +00:00
2017-12-13 22:33:07 +00:00
def ToClipboard( self, data_type, data ):
# need this cause can't do it in a non-gui thread
if data_type == 'paths':
2019-11-14 03:56:30 +00:00
paths = []
2017-12-13 22:33:07 +00:00
2019-11-14 03:56:30 +00:00
for path in data:
2019-02-27 23:03:30 +00:00
2019-11-14 03:56:30 +00:00
paths.append( QC.QUrl.fromLocalFile( path ) )
2019-02-27 23:03:30 +00:00
2017-12-13 22:33:07 +00:00
2019-11-14 03:56:30 +00:00
mime_data = QC.QMimeData()
mime_data.setUrls( paths )
QW.QApplication.clipboard().setMimeData( mime_data )
2017-12-13 22:33:07 +00:00
elif data_type == 'text':
text = data
2019-11-14 03:56:30 +00:00
QW.QApplication.clipboard().setText( text )
2017-12-13 22:33:07 +00:00
elif data_type == 'bmp':
media = data
image_renderer = self.GetCache( 'images' ).GetImageRenderer( media )
def CopyToClipboard():
2019-11-14 03:56:30 +00:00
qt_image = image_renderer.GetQtImage().copy()
QW.QApplication.clipboard().setImage( qt_image )
2017-12-13 22:33:07 +00:00
def THREADWait():
start_time = time.time()
while not image_renderer.IsReady():
if HydrusData.TimeHasPassed( start_time + 15 ):
2019-11-14 03:56:30 +00:00
HydrusData.ShowText( 'The image did not render in fifteen seconds, so the attempt to copy it to the clipboard was abandoned.' )
return
2017-12-13 22:33:07 +00:00
time.sleep( 0.1 )
2019-11-14 03:56:30 +00:00
QP.CallAfter( CopyToClipboard )
2017-12-13 22:33:07 +00:00
self.CallToThread( THREADWait )
2018-05-23 21:05:06 +00:00
def UnclosePageKeys( self, page_keys ):
with self._page_key_lock:
self._closed_page_keys.difference_update( page_keys )
2017-10-04 17:51:58 +00:00
def WaitUntilViewFree( self ):
self.WaitUntilModelFree()
self.WaitUntilThumbnailsFree()
def WaitUntilThumbnailsFree( self ):
2019-11-28 01:11:46 +00:00
self._caches[ 'thumbnail' ].WaitUntilFree()
2017-10-04 17:51:58 +00:00
2013-02-19 00:11:43 +00:00
def Write( self, action, *args, **kwargs ):
2017-12-06 22:06:56 +00:00
if action == 'content_updates':
self._managers[ 'undo' ].AddCommand( 'content_updates', *args, **kwargs )
2013-07-10 20:25:57 +00:00
2015-04-01 20:44:54 +00:00
return HydrusController.HydrusController.Write( self, action, *args, **kwargs )
2013-07-31 21:26:38 +00:00
2016-12-07 22:12:52 +00:00