hydrus/hydrus/client/ClientController.py

2474 lines
84 KiB
Python
Raw Normal View History

2020-05-20 21:36:02 +00:00
import hashlib
2018-03-22 00:03:33 +00:00
import os
2023-04-12 20:34:43 +00:00
import typing
2020-05-20 21:36:02 +00:00
import psutil
import signal
2019-11-14 03:56:30 +00:00
import sys
2020-05-20 21:36:02 +00:00
import threading
import time
import traceback
2019-11-14 03:56:30 +00:00
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
2020-05-20 21:36:02 +00:00
2020-07-29 20:52:44 +00:00
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusController
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusThreading
2023-04-19 20:38:13 +00:00
from hydrus.core import HydrusTime
2021-04-07 21:26:45 +00:00
from hydrus.core.networking import HydrusNetworking
2020-07-29 20:52:44 +00:00
2020-04-22 21:00:35 +00:00
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientDaemons
from hydrus.client import ClientDefaults
from hydrus.client import ClientFiles
2024-02-07 21:22:05 +00:00
from hydrus.client import ClientGlobals as CG
2020-05-20 21:36:02 +00:00
from hydrus.client import ClientOptions
from hydrus.client import ClientServices
2020-05-20 21:36:02 +00:00
from hydrus.client import ClientThreading
2023-06-28 20:29:14 +00:00
from hydrus.client.caches import ClientCaches
2021-01-27 22:14:03 +00:00
from hydrus.client.db import ClientDB
2023-12-06 22:13:50 +00:00
from hydrus.client.gui import ClientGUIDialogsMessage
from hydrus.client.gui import ClientGUISplash
2020-05-20 21:36:02 +00:00
from hydrus.client.gui import QtPorting as QP
2024-02-07 21:22:05 +00:00
from hydrus.client.interfaces import ClientControllerInterface
2013-02-19 00:11:43 +00:00
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
2022-07-25 15:33:44 +00:00
PubSubEventType = QP.registerEventType()
2020-01-29 22:08:37 +00:00
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 ):
2023-06-21 19:50:13 +00:00
try:
2020-01-22 21:04:43 +00:00
2023-06-21 19:50:13 +00:00
if event.type() == PubSubEventType and isinstance( event, PubSubEvent ):
2020-01-29 22:08:37 +00:00
2023-06-21 19:50:13 +00:00
if self._pubsub.WorkToDo():
self._pubsub.Process()
2020-01-29 22:08:37 +00:00
2023-06-21 19:50:13 +00:00
event.accept()
return True
except Exception as e:
2020-01-22 21:04:43 +00:00
2023-06-21 19:50:13 +00:00
HydrusData.ShowException( e )
2020-02-12 22:50:37 +00:00
2020-01-22 21:04:43 +00:00
return True
return False
2020-02-12 22:50:37 +00:00
2020-04-01 21:51:42 +00:00
def MessageHandler( msg_type, context, text ):
2022-07-25 15:33:44 +00:00
if msg_type not in ( QC.QtMsgType.QtDebugMsg, QC.QtMsgType.QtInfoMsg ):
2020-04-01 21:51:42 +00:00
# Set a breakpoint here to be able to see where the warnings originate from.
HydrusData.Print( text )
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' )
2024-04-03 21:15:48 +00:00
if HC.PLATFORM_LINUX:
self.setDesktopFileName( 'io.github.hydrusnetwork.hydrus' )
2019-11-14 03:56:30 +00:00
self.setApplicationVersion( str( HC.SOFTWARE_VERSION ) )
2019-07-24 21:39:02 +00:00
2020-04-01 21:51:42 +00:00
QC.qInstallMessageHandler( MessageHandler )
2019-07-24 21:39:02 +00:00
self.setQuitOnLastWindowClosed( False )
2019-07-24 21:39:02 +00:00
self.setQuitLockEnabled( False )
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
2021-02-17 18:22:44 +00:00
self.aboutToQuit.connect( self.EventAboutToQuit )
2019-07-24 21:39:02 +00:00
2019-11-14 03:56:30 +00:00
2021-02-17 18:22:44 +00:00
def EventAboutToQuit( self ):
2019-07-24 21:39:02 +00:00
# If a user log-off causes the OS to call the Qt Application's quit/exit func, we still want to save and close nicely
2021-02-17 18:22:44 +00:00
# once this lad is done, we are OUT of the mainloop, so if this is called and we are actually waiting on THREADExitEverything, let's wait a bit more
# on Windows, the logoff routine kills the process once all top level windows are dead, so we have no chance to do post-app work lmaooooooooo
# since splash screen may not appear in some cases, I now keep main gui alive but hidden until the quit call. it is never deleteLater'd
# this is also called explicitly right at the end of the program. I set setQuitonLastWindowClosed False and then call quit explicitly, so it needs to be idempotent on the exit calls
2024-02-07 21:22:05 +00:00
if CG.client_controller is not None:
2019-08-21 21:34:01 +00:00
2024-02-07 21:22:05 +00:00
if CG.client_controller.ProgramIsShuttingDown():
2021-02-17 18:22:44 +00:00
2023-04-19 20:38:13 +00:00
screw_it_time = HydrusTime.GetNow() + 30
2021-02-17 18:22:44 +00:00
2024-02-07 21:22:05 +00:00
while not CG.client_controller.ProgramIsShutDown():
2021-02-17 18:22:44 +00:00
time.sleep( 0.5 )
2023-04-19 20:38:13 +00:00
if HydrusTime.TimeHasPassed( screw_it_time ):
2021-02-17 18:22:44 +00:00
return
else:
2019-11-14 03:56:30 +00:00
2024-02-07 21:22:05 +00:00
CG.client_controller.SetDoingFastExit( True )
2024-02-07 21:22:05 +00:00
CG.client_controller.Exit()
2019-11-14 03:56:30 +00:00
2020-06-17 21:31:54 +00:00
2019-08-21 21:34:01 +00:00
2019-07-24 21:39:02 +00:00
2024-02-07 21:22:05 +00:00
class Controller( ClientControllerInterface.ClientControllerInterface, 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
2020-03-25 21:15:57 +00:00
self._qt_app_running = False
2017-05-10 21:33:58 +00:00
self._is_booted = False
self._program_is_shutting_down = False
2021-02-17 18:22:44 +00:00
self._program_is_shut_down = False
self._restore_backup_path = None
2017-05-10 21:33:58 +00:00
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 )
2024-02-07 21:22:05 +00:00
ClientControllerInterface.ClientControllerInterface.__init__( self )
2015-09-16 18:11:00 +00:00
2017-03-08 23:23:12 +00:00
self._name = 'client'
2024-02-07 21:22:05 +00:00
CG.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
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
2020-09-02 21:10:41 +00:00
self.frame_splash_status.Reset()
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 ) )
2024-01-17 18:57:00 +00:00
def _GetWakeDelayPeriodMS( self ):
2021-02-11 01:59:52 +00:00
2024-01-17 18:57:00 +00:00
return HydrusTime.MillisecondiseS( self.new_options.GetInteger( 'wake_delay_period' ) )
2021-02-11 01:59:52 +00:00
def _PublishShutdownSubtext( self, text ):
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( text )
2017-11-08 22:07:12 +00:00
def _ReportShutdownDaemonsStatus( self ):
2021-12-22 22:31:23 +00:00
names = sorted( ( name for ( name, job ) in self._daemon_jobs.items() if job.CurrentlyWorking() ) )
2017-11-08 22:07:12 +00:00
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( ', '.join( names ) )
2017-11-08 22:07:12 +00:00
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() )
if not self._doing_fast_exit:
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( 'shutdown error', text )
self.BlockingSafeShowCriticalMessage( 'shutdown error', traceback.format_exc() )
self._doing_fast_exit = True
2019-11-14 03:56:30 +00:00
2022-04-06 20:40:17 +00:00
def _ReportShutdownManagersStatus( self ):
managers = [ self.subscriptions_manager, self.tag_display_maintenance_manager ]
names = sorted( ( manager.GetName() for manager in managers if not manager.IsShutdown() ) )
self.frame_splash_status.SetSubtext( ', '.join( names ) )
2021-02-11 01:59:52 +00:00
def _ShowJustWokeToUser( self ):
2023-11-15 22:40:54 +00:00
def do_it( job_status: ClientThreading.JobStatus ):
2021-02-11 01:59:52 +00:00
2022-01-19 21:28:59 +00:00
while not HG.started_shutdown:
2021-02-11 01:59:52 +00:00
with self._sleep_lock:
2023-11-15 22:40:54 +00:00
if job_status.IsCancelled():
2021-02-11 01:59:52 +00:00
2024-01-17 18:57:00 +00:00
self.TouchTime( 'now_awake' )
2021-02-11 01:59:52 +00:00
2023-11-15 22:40:54 +00:00
job_status.SetStatusText( 'enabling I/O now' )
2021-02-11 01:59:52 +00:00
2023-11-29 22:27:53 +00:00
job_status.FinishAndDismiss()
2021-02-11 01:59:52 +00:00
return
2024-01-17 18:57:00 +00:00
wake_time_ms = self.GetTimestampMS( 'now_awake' )
2021-02-11 01:59:52 +00:00
2024-01-17 18:57:00 +00:00
if HydrusTime.TimeHasPassedMS( wake_time_ms ):
2021-02-11 01:59:52 +00:00
2023-11-29 22:27:53 +00:00
job_status.FinishAndDismiss()
2021-02-11 01:59:52 +00:00
return
else:
2024-01-17 18:57:00 +00:00
wake_time = HydrusTime.SecondiseMS( wake_time_ms )
2023-11-15 22:40:54 +00:00
job_status.SetStatusText( 'enabling I/O {}'.format( HydrusTime.TimestampToPrettyTimeDelta( wake_time, just_now_threshold = 0 ) ) )
2021-02-11 01:59:52 +00:00
time.sleep( 0.5 )
2023-11-15 22:40:54 +00:00
job_status = ClientThreading.JobStatus( cancellable = True )
2021-02-11 01:59:52 +00:00
2023-11-15 22:40:54 +00:00
job_status.SetStatusTitle( 'just woke up from sleep' )
2021-02-11 01:59:52 +00:00
2023-11-15 22:40:54 +00:00
self.pub( 'message', job_status )
2021-02-11 01:59:52 +00:00
2023-11-15 22:40:54 +00:00
self.CallToThread( do_it, job_status )
2021-02-11 01:59:52 +00:00
2020-10-21 22:22:10 +00:00
def _ShutdownManagers( self ):
2019-12-05 05:29:32 +00:00
2023-08-09 21:12:17 +00:00
self.database_maintenance_manager.Shutdown()
2022-01-26 21:57:04 +00:00
self.files_maintenance_manager.Shutdown()
self.quick_download_manager.Shutdown()
2020-10-21 22:22:10 +00:00
managers = [ self.subscriptions_manager, self.tag_display_maintenance_manager ]
for manager in managers:
manager.Shutdown()
2019-12-05 05:29:32 +00:00
2023-04-19 20:38:13 +00:00
started = HydrusTime.GetNow()
2019-12-05 05:29:32 +00:00
2020-10-21 22:22:10 +00:00
while False in ( manager.IsShutdown() for manager in managers ):
2019-12-05 05:29:32 +00:00
2022-04-06 20:40:17 +00:00
self._ReportShutdownManagersStatus()
2019-12-05 05:29:32 +00:00
time.sleep( 0.1 )
2023-04-19 20:38:13 +00:00
if HydrusTime.TimeHasPassed( started + 30 ):
2019-12-05 05:29:32 +00:00
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
2023-12-13 22:29:24 +00:00
def BlockingSafeShowCriticalMessage( self, title: str, message: str ):
2023-12-06 22:13:50 +00:00
ClientGUIDialogsMessage.ShowCritical( self.gui, title, message )
2023-12-13 22:29:24 +00:00
def BlockingSafeShowMessage( self, message: str ):
2023-12-06 22:13:50 +00:00
ClientGUIDialogsMessage.ShowInformation( self.gui, message )
2020-03-04 22:12:53 +00:00
def CallBlockingToQt( self, win, func, *args, **kwargs ):
2015-08-26 21:18:39 +00:00
2023-11-15 22:40:54 +00:00
def qt_code( win: QW.QWidget, job_status: ClientThreading.JobStatus ):
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:
2021-09-22 21:12:34 +00:00
raise HydrusExceptions.QtDeadWindowException( 'Parent Window was destroyed before Qt command was called!' )
2019-11-28 01:11:46 +00:00
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
2023-11-15 22:40:54 +00:00
job_status.SetVariable( 'result', result )
2015-08-26 21:18:39 +00:00
2020-07-22 20:59:16 +00:00
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.DBCredentialsException, HydrusExceptions.ShutdownException, HydrusExceptions.CancelledException ) as e:
2015-12-23 22:51:04 +00:00
2023-11-15 22:40:54 +00:00
job_status.SetErrorException( e )
2015-12-23 22:51:04 +00:00
2015-08-26 21:18:39 +00:00
except Exception as e:
2023-11-15 22:40:54 +00:00
job_status.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:
2023-11-15 22:40:54 +00:00
job_status.Finish()
2016-07-06 21:13:15 +00:00
2015-08-26 21:18:39 +00:00
2023-11-29 22:27:53 +00:00
job_status = ClientThreading.JobStatus( cancellable = True, cancel_on_shutdown = False )
2015-08-26 21:18:39 +00:00
2023-11-15 22:40:54 +00:00
QP.CallAfter( qt_code, win, job_status )
2015-08-26 21:18:39 +00:00
2021-11-10 21:53:57 +00:00
# I think in some cases with the splash screen we may actually be pushing stuff here after model shutdown
# but I also don't want a hang, as we have seen with some GUI async job that got fired on shutdown and it seems some event queue was halted or deadlocked
# so, we'll give it 16ms to work, then we'll start testing for shutdown hang
2024-02-21 21:09:02 +00:00
done_event = job_status.GetDoneEvent()
2023-11-15 22:40:54 +00:00
while not job_status.IsDone():
2015-08-26 21:18:39 +00:00
2024-02-21 21:09:02 +00:00
done_event.wait( 1.0 )
2021-11-10 21:53:57 +00:00
if HG.model_shutdown or not self._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
2023-11-15 22:40:54 +00:00
if job_status.HasVariable( 'result' ):
2016-01-06 21:17:20 +00:00
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
2023-11-15 22:40:54 +00:00
result = job_status.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
2023-11-15 22:40:54 +00:00
if job_status.HadError():
2016-01-06 21:17:20 +00:00
2023-11-15 22:40:54 +00:00
e = job_status.GetErrorException()
2020-03-04 22:12:53 +00:00
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
2021-06-23 21:11:38 +00:00
def CallAfterQtSafe( self, window, label, func, *args, **kwargs ) -> ClientThreading.QtAwareJob:
2020-03-11 21:52:11 +00:00
2021-06-23 21:11:38 +00:00
return self.CallLaterQtSafe( window, 0, label, func, *args, **kwargs )
2020-03-11 21:52:11 +00:00
2021-06-23 21:11:38 +00:00
def CallLaterQtSafe( self, window, initial_delay, label, 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 )
2021-06-23 21:11:38 +00:00
# we set a label so the call won't have to look at Qt objects for a label in the wrong place
2018-02-14 21:47:18 +00:00
call = HydrusData.Call( func, *args, **kwargs )
2021-06-23 21:11:38 +00:00
call.SetLabel( label )
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
2021-06-23 21:11:38 +00:00
def CallRepeatingQtSafe( self, window, initial_delay, period, label, func, *args, **kwargs ) -> ClientThreading.QtAwareRepeatingJob:
2018-02-14 21:47:18 +00:00
2018-05-23 21:05:06 +00:00
job_scheduler = self._GetAppropriateJobScheduler( period )
2021-06-23 21:11:38 +00:00
# we set a label so the call won't have to look at Qt objects for a label in the wrong place
2018-02-14 21:47:18 +00:00
call = HydrusData.Call( func, *args, **kwargs )
2021-06-23 21:11:38 +00:00
call.SetLabel( label )
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 ):
if self._program_is_shutting_down:
2019-07-24 21:39:02 +00:00
return
2019-07-24 21:39:02 +00:00
if sig == signal.SIGINT:
if self.gui is not None and QP.isValid( self.gui ):
2019-12-18 22:06:34 +00:00
event = QG.QCloseEvent()
QW.QApplication.instance().postEvent( self.gui, event )
else:
2022-07-25 15:33:44 +00:00
QP.CallAfter( QW.QApplication.instance().exit )
2019-12-18 22:06:34 +00:00
2019-07-24 21:39:02 +00:00
elif sig == signal.SIGTERM:
2022-06-29 20:52:53 +00:00
self.Exit()
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
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetText( '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
2020-05-20 21:36:02 +00:00
from hydrus.client.gui import ClientGUIDialogsQuick
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
raise HydrusExceptions.ShutdownException()
2015-09-02 23:16:09 +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
2020-11-11 22:20:16 +00:00
self.frame_splash_status.SetText( 'waiting {} seconds'.format( i ) )
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
2024-01-17 18:57:00 +00:00
self.TouchTime( 'last_mouse_action' )
2015-10-28 21:29:05 +00:00
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
2022-01-26 21:57:04 +00:00
def ClipboardHasImage( self ):
try:
self.GetClipboardImage()
return True
except HydrusExceptions.DataMissing:
return False
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 )
2020-04-01 21:51:42 +00:00
def CreateSplash( self, title ):
2018-01-10 22:41:51 +00:00
if self._splash is not None:
self._DestroySplash()
2018-01-10 22:41:51 +00:00
try:
2020-09-02 21:10:41 +00:00
self.frame_splash_status.Reset()
self._splash = ClientGUISplash.FrameSplash( self, title, self.frame_splash_status )
2018-01-10 22:41:51 +00:00
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 ):
if self._program_is_shutting_down:
2019-08-21 21:34:01 +00:00
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
2024-01-17 18:57:00 +00:00
if not HydrusTime.TimeHasPassedMS( self.GetBootTimestampMS() + ( 120 * 1000 ) ):
2016-02-17 22:06:47 +00:00
return False
2017-10-18 19:41:25 +00:00
idle_normal = self.options[ 'idle_normal' ]
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
2024-01-17 18:57:00 +00:00
idle_period_ms = HydrusTime.MillisecondiseS( self.options[ 'idle_period' ] )
2021-06-09 20:28:09 +00:00
2024-01-17 18:57:00 +00:00
if idle_period_ms is not None:
2015-10-28 21:29:05 +00:00
2024-01-17 18:57:00 +00:00
if not HydrusTime.TimeHasPassedMS( self.GetTimestampMS( 'last_user_action' ) + idle_period_ms ):
2016-01-13 22:08:19 +00:00
currently_idle = False
2015-10-28 21:29:05 +00:00
2024-01-17 18:57:00 +00:00
idle_mouse_period_ms = HydrusTime.MillisecondiseS( self.options[ 'idle_mouse_period' ] )
2021-06-09 20:28:09 +00:00
2024-01-17 18:57:00 +00:00
if idle_mouse_period_ms is not None:
2015-10-28 21:29:05 +00:00
2024-01-17 18:57:00 +00:00
if not HydrusTime.TimeHasPassedMS( self.GetTimestampMS( 'last_mouse_action' ) + idle_mouse_period_ms ):
2021-06-09 20:28:09 +00:00
currently_idle = False
2024-01-17 18:57:00 +00:00
idle_mode_client_api_timeout_ms = HydrusTime.MillisecondiseS( self.new_options.GetNoneableInteger( 'idle_mode_client_api_timeout' ) )
2021-06-09 20:28:09 +00:00
2024-01-17 18:57:00 +00:00
if idle_mode_client_api_timeout_ms is not None:
2021-06-09 20:28:09 +00:00
2024-01-17 18:57:00 +00:00
if not HydrusTime.TimeHasPassedMS( self.GetTimestampMS( 'last_client_api_action' ) + idle_mode_client_api_timeout_ms ):
2016-01-13 22:08:19 +00:00
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
2023-04-19 20:38:13 +00:00
self._idle_started = HydrusTime.GetNow()
2016-05-11 18:16:39 +00:00
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 ):
if self._program_is_shutting_down:
2019-08-21 21:34:01 +00:00
2019-12-05 05:29:32 +00:00
return False
2019-08-21 21:34:01 +00:00
2023-04-19 20:38:13 +00:00
if self._idle_started is not None and HydrusTime.TimeHasPassed( self._idle_started + 3600 ):
2016-05-11 18:16:39 +00:00
return True
return False
2015-08-26 21:18:39 +00:00
def DoIdleShutdownWork( self ):
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'db' )
2020-01-02 03:05:35 +00:00
2023-04-19 20:38:13 +00:00
stop_time = HydrusTime.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
2022-04-20 20:18:56 +00:00
if not self.new_options.GetBoolean( '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:
2023-04-19 20:38:13 +00:00
if HydrusTime.TimeHasPassed( stop_time ):
2015-08-26 21:18:39 +00:00
return
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( '{} processing'.format( service.GetName() ) )
2020-01-02 03:05:35 +00:00
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
2021-07-28 21:12:00 +00:00
self.Write( 'register_shutdown_work' )
2018-09-26 19:05:12 +00:00
2014-02-19 22:37:23 +00:00
2015-08-26 21:18:39 +00:00
def Exit( self ):
# this is idempotent and does not stop. we are shutting down now or in the very near future
if self._program_is_shutting_down:
2019-11-14 03:56:30 +00:00
return
2019-11-14 03:56:30 +00:00
self._program_is_shutting_down = True
2019-08-21 21:34:01 +00:00
if not self._is_booted:
2015-09-02 23:16:09 +00:00
self._doing_fast_exit = True
2018-01-10 22:41:51 +00:00
try:
2015-09-02 23:16:09 +00:00
if not self._doing_fast_exit:
2016-01-06 21:17:20 +00:00
self.CreateSplash( 'hydrus client exiting' )
2016-01-06 21:17:20 +00:00
2019-06-19 22:08:48 +00:00
if HG.do_idle_shutdown_work:
self._splash.ShowCancelShutdownButton()
2019-06-19 22:08:48 +00:00
if self.gui is not None and QP.isValid( self.gui ):
2016-01-13 22:08:19 +00:00
2023-09-06 19:49:46 +00:00
self.frame_splash_status.SetTitleText( 'saving and hiding gui' + HC.UNICODE_ELLIPSIS )
2023-06-07 20:07:22 +00:00
2021-02-17 18:22:44 +00:00
self.gui.SaveAndHide()
2019-12-05 05:29:32 +00:00
2015-08-26 21:18:39 +00:00
2020-07-15 20:52:09 +00:00
except Exception:
self._ReportShutdownException()
if self._doing_fast_exit:
2023-09-06 19:49:46 +00:00
HydrusData.DebugPrint( 'doing fast shutdown' + HC.UNICODE_ELLIPSIS )
self.THREADExitEverything()
else:
self.CallToThreadLongRunning( self.THREADExitEverything )
2015-08-26 21:18:39 +00:00
2013-02-19 00:11:43 +00:00
2021-10-13 20:16:57 +00:00
def FlipQueryPlannerMode( self ):
if not HG.query_planner_mode:
2023-04-19 20:38:13 +00:00
now = HydrusTime.GetNow()
2021-10-13 20:16:57 +00:00
HG.query_planner_start_time = now
HG.query_planner_query_count = 0
HG.query_planner_mode = True
HydrusData.ShowText( 'Query Planner mode on!' )
else:
HG.query_planner_mode = False
HG.queries_planned = set()
HydrusData.ShowText( 'Query Planning done: {} queries analyzed'.format( HydrusData.ToHumanInt( HG.query_planner_query_count ) ) )
def FlipProfileMode( self ):
if not HG.profile_mode:
2023-04-19 20:38:13 +00:00
now = HydrusTime.GetNow()
2021-10-13 20:16:57 +00:00
with HG.profile_counter_lock:
HG.profile_start_time = now
HG.profile_slow_count = 0
HG.profile_fast_count = 0
HG.profile_mode = True
HydrusData.ShowText( 'Profile mode on!' )
else:
HG.profile_mode = False
with HG.profile_counter_lock:
( slow, fast ) = ( HG.profile_slow_count, HG.profile_fast_count )
HydrusData.ShowText( 'Profiling done: {} slow jobs, {} fast jobs'.format( HydrusData.ToHumanInt( slow ), HydrusData.ToHumanInt( fast ) ) )
2022-01-26 21:57:04 +00:00
def GetClipboardImage( self ):
clipboard_image = QW.QApplication.clipboard().image()
if clipboard_image is None or clipboard_image.isNull():
raise HydrusExceptions.DataMissing( 'No bitmap on the clipboard!' )
return clipboard_image
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()
2022-01-26 21:57:04 +00:00
if clipboard_text is None:
2017-12-13 22:33:07 +00:00
2019-11-14 03:56:30 +00:00
raise HydrusExceptions.DataMissing( 'No text on the clipboard!' )
2020-07-29 20:52:44 +00:00
2019-11-14 03:56:30 +00:00
return clipboard_text
2017-12-13 22:33:07 +00:00
2020-03-18 21:35:57 +00:00
def GetDefaultMPVConfPath( self ):
return os.path.join( HC.STATIC_DIR, 'mpv-conf', 'default_mpv.conf' )
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
2020-07-15 20:52:09 +00:00
def GetMainTLW( self ):
if self.gui is not None:
return self.gui
elif self._splash is not None:
return self._splash
else:
return None
2020-03-18 21:35:57 +00:00
def GetMPVConfPath( self ):
return os.path.join( self.db_dir, 'mpv.conf' )
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 ):
2023-08-30 16:25:24 +00:00
def qt_code( missing_subfolders ):
2017-04-05 21:16:40 +00:00
2023-11-22 22:29:14 +00:00
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
2020-04-29 21:44:12 +00:00
with ClientGUITopLevelWindowsPanels.DialogManage( None, 'repair file system' ) as dlg:
2017-04-05 21:16:40 +00:00
2023-11-22 22:29:14 +00:00
from hydrus.client.gui import ClientGUIScrolledPanelsManagement
2023-08-30 16:25:24 +00:00
panel = ClientGUIScrolledPanelsManagement.RepairFileSystemPanel( dlg, missing_subfolders )
2017-04-05 21:16:40 +00:00
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
2023-08-30 16:25:24 +00:00
missing_subfolders = self.client_files_manager.GetMissingSubfolders()
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
2023-08-30 16:25:24 +00:00
return missing_subfolders
2018-05-16 20:09:50 +00:00
2019-05-22 22:35:06 +00:00
self.client_files_manager = ClientFiles.ClientFilesManager( self )
2023-08-30 16:25:24 +00:00
missing_subfolders = self.client_files_manager.GetMissingSubfolders()
2018-05-16 20:09:50 +00:00
2023-08-30 16:25:24 +00:00
while len( missing_subfolders ) > 0:
2018-05-16 20:09:50 +00:00
2023-08-30 16:25:24 +00:00
missing_subfolders = self.CallBlockingToQt( self._splash, qt_code, missing_subfolders )
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
2023-11-22 22:29:14 +00:00
from hydrus.client import ClientManagers
2023-09-06 19:49:46 +00:00
self.frame_splash_status.SetTitleText( 'booting db' + HC.UNICODE_ELLIPSIS )
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
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( '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
2024-01-03 21:21:53 +00:00
from hydrus.core.files import HydrusVideoHandling
2023-11-22 22:29:14 +00:00
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 )
2022-06-29 20:52:53 +00:00
self.frame_splash_status.SetSubtext( 'image caches' )
self._caches[ 'images' ] = ClientCaches.ImageRendererCache( self )
self._caches[ 'image_tiles' ] = ClientCaches.ImageTileCache( self )
self._caches[ 'thumbnail' ] = ClientCaches.ThumbnailCache( self )
self.frame_splash_status.SetText( 'initialising managers' )
# careful: outside of qt since they don't need qt for init, seems ok _for now_
self.bitmap_manager = ClientManagers.BitmapManager( self )
self.frame_splash_status.SetSubtext( 'services' )
self.services_manager = ClientServices.ServicesManager( self )
2020-07-15 20:52:09 +00:00
# important this happens before repair, as repair dialog has a column list lmao
2023-11-22 22:29:14 +00:00
from hydrus.client.gui.lists import ClientGUIListManager
2020-07-15 20:52:09 +00:00
column_list_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_COLUMN_LIST_MANAGER )
if column_list_manager is None:
column_list_manager = ClientGUIListManager.ColumnListManager()
column_list_manager._dirty = True
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( 'Problem loading object', 'Your list manager was missing on boot! I have recreated a new empty one with default settings. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2020-07-15 20:52:09 +00:00
self.column_list_manager = column_list_manager
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( '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
#
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'network' )
2017-11-08 22:07:12 +00:00
2021-10-13 20:16:57 +00:00
if self.new_options.GetBoolean( 'boot_with_network_traffic_paused' ):
2024-02-07 21:22:05 +00:00
CG.client_controller.new_options.SetBoolean( 'pause_all_new_network_traffic', True )
2021-10-13 20:16:57 +00:00
2018-04-25 22:07:52 +00:00
self.parsing_cache = ClientCaches.ParsingCache()
2019-01-30 22:14:54 +00:00
2023-11-22 22:29:14 +00:00
from hydrus.client import ClientAPI
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
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( '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
2023-11-22 22:29:14 +00:00
from hydrus.client.networking import ClientNetworkingBandwidth
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
2021-01-13 21:48:58 +00:00
tracker_containers = self.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER_TRACKER_CONTAINER )
bandwidth_manager.SetTrackerContainers( tracker_containers )
2017-08-16 21:58:06 +00:00
ClientDefaults.SetDefaultBandwidthManagerRules( bandwidth_manager )
2021-01-13 21:48:58 +00:00
bandwidth_manager.SetDirty()
2017-10-11 17:38:14 +00:00
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( 'Problem loading object', 'Your bandwidth manager was missing on boot! I have recreated a new one. It may have your bandwidth record, but some/all may be missing. Your rules have been reset to default. 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
2023-11-22 22:29:14 +00:00
from hydrus.client.networking import ClientNetworkingSessions
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
2021-01-13 21:48:58 +00:00
session_containers = self.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_SESSION_MANAGER_SESSION_CONTAINER )
session_manager.SetSessionContainers( session_containers )
session_manager.SetDirty()
2017-10-11 17:38:14 +00:00
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( 'Problem loading object', 'Your session manager was missing on boot! I have recreated a new one. It may have your sessions, or some/all may be missing. 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
2023-11-22 22:29:14 +00:00
from hydrus.client.networking import ClientNetworkingDomain
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
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( '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()
2023-11-22 22:29:14 +00:00
from hydrus.client.networking import ClientNetworkingLogin
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
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( '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
2023-11-22 22:29:14 +00:00
from hydrus.client.networking import ClientNetworking
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
#
2023-11-22 22:29:14 +00:00
from hydrus.client import ClientDownloading
2019-07-31 22:01:02 +00:00
self.quick_download_manager = ClientDownloading.QuickDownloadManager( self )
self.CallToThreadLongRunning( self.quick_download_manager.MainLoop )
#
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
#
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'tag display' )
2017-11-08 22:07:12 +00:00
# note that this has to be made before siblings/parents managers, as they rely on it
2023-11-22 22:29:14 +00:00
from hydrus.client.metadata import ClientTagsHandling
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 = ClientTagsHandling.TagDisplayManager()
2019-10-02 23:38:59 +00:00
tag_display_manager._dirty = True
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( '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-10-21 22:22:10 +00:00
self.tag_display_maintenance_manager = ClientTagsHandling.TagDisplayMaintenanceManager( self )
self.tag_display_maintenance_manager.Start()
2020-03-11 21:52:11 +00:00
#
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'favourite searches' )
2020-03-11 21:52:11 +00:00
2023-11-22 22:29:14 +00:00
from hydrus.client.search import ClientSearch
2020-03-11 21:52:11 +00:00
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
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( '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.' )
2020-03-11 21:52:11 +00:00
self.favourite_search_manager = favourite_search_manager
#
2019-12-05 05:29:32 +00:00
self._managers[ 'undo' ] = ClientManagers.UndoManager( self )
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
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetText( '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:
2023-11-22 22:29:14 +00:00
from hydrus.client.gui import ClientGUIDialogs
2020-05-06 21:31:41 +00:00
with ClientGUIDialogs.DialogTextEntry( self._splash, 'Enter your password.', allow_blank = False, 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:
2020-05-06 21:31:41 +00:00
raise HydrusExceptions.DBCredentialsException( 'Bad password check' )
2015-09-02 23:16:09 +00:00
2020-03-25 21:15:57 +00:00
self.CallBlockingToQt( self._splash, qt_code_password )
2015-09-02 23:16:09 +00:00
2023-09-06 19:49:46 +00:00
self.frame_splash_status.SetTitleText( 'booting gui' + HC.UNICODE_ELLIPSIS )
2015-09-02 23:16:09 +00:00
2024-02-07 21:22:05 +00:00
subscriptions = CG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION )
2020-06-11 12:01:08 +00:00
2023-11-22 22:29:14 +00:00
self.files_maintenance_manager = ClientFiles.FilesMaintenanceManager( self )
from hydrus.client import ClientDBMaintenanceManager
self.database_maintenance_manager = ClientDBMaintenanceManager.DatabaseMaintenanceManager( self )
from hydrus.client.importing import ClientImportSubscriptions
2020-06-11 12:01:08 +00:00
self.subscriptions_manager = ClientImportSubscriptions.SubscriptionsManager( self, subscriptions )
2019-12-05 05:29:32 +00:00
2023-11-22 22:29:14 +00:00
def qt_code_style():
2020-03-25 21:15:57 +00:00
2023-11-22 22:29:14 +00:00
from hydrus.client.gui import ClientGUIStyle
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:
2023-04-26 21:10:03 +00:00
HydrusData.Print( 'Could not load Qt style: {}'.format( repr( e ) ) )
2019-12-05 05:29:32 +00:00
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:
2023-04-26 21:10:03 +00:00
HydrusData.Print( 'Could not load Qt stylesheet: {}'.format( repr( e ) ) )
2019-12-05 05:29:32 +00:00
2023-11-22 22:29:14 +00:00
self.CallBlockingToQt( self._splash, qt_code_style )
def qt_code_pregui():
2024-02-07 21:22:05 +00:00
shortcut_sets = CG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT_SET )
2023-11-22 22:29:14 +00:00
from hydrus.client.gui import ClientGUIShortcuts
ClientGUIShortcuts.ShortcutsManager( shortcut_sets = shortcut_sets )
ClientGUIShortcuts.SetMouseLabels( self.new_options.GetBoolean( 'call_mouse_buttons_primary_secondary' ) )
self.CallBlockingToQt( self._splash, qt_code_pregui )
from hydrus.client.gui import ClientGUIPopupMessages
# got to keep this before gui instantiation, since it uses it
self.job_status_popup_queue = ClientGUIPopupMessages.JobStatusPopupQueue()
def qt_code_gui():
2023-11-15 22:40:54 +00:00
2023-11-22 22:29:14 +00:00
from hydrus.client.gui import ClientGUI
2023-11-15 22:40:54 +00:00
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
2023-11-22 22:29:14 +00:00
# ShowText will hereafter popup as a message, as the GUI's popup message manager has overwritten the hooks
2016-08-24 18:36:56 +00:00
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
2024-03-13 20:33:53 +00:00
job = self.CallRepeatingQtSafe( self.gui, 10.0, 10.0, 'repeating mouse idle check', 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.'
2023-11-29 22:27:53 +00:00
message += '\n' * 2
2021-08-18 21:10:01 +00:00
message += 'Don\'t forget to check out the help if you haven\'t already, by clicking help->help--it has an extensive \'getting started\' section, including how to update and the importance of backing up your database.'
2023-11-29 22:27:53 +00:00
message += '\n' * 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 )
2023-11-29 22:27:53 +00:00
if HC.WE_SWITCHED_TO_USERPATH:
2023-12-06 22:13:50 +00:00
message = f'Hey, it also looks like the default database location at "{HC.DEFAULT_DB_DIR}" was not writable-to, so the client has fallen back to using your userpath at "{HC.USERPATH_DB_DIR}"'
2023-11-29 22:27:53 +00:00
message += '\n' * 2
message += 'If that is fine with you, no problem. But if you were expecting to load an existing database and the above "first start" popup is a surprise, then your old db path is probably read-only. Fix that and try again. If it helps, hit up help->about to see the directories hydrus is currently using.'
HydrusData.ShowText( message )
2016-08-24 18:36:56 +00:00
2017-07-12 20:03:45 +00:00
if self.db.IsDBUpdated():
2016-08-24 18:36:56 +00:00
2020-11-11 22:20:16 +00:00
HydrusData.ShowText( 'The client has updated to version {}!'.format( HC.SOFTWARE_VERSION ) )
2016-08-24 18:36:56 +00:00
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
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
2023-04-19 20:38:13 +00:00
tree_stop_time = HydrusTime.GetNow() + 30
2017-01-25 22:56:55 +00:00
2019-07-31 22:01:02 +00:00
2020-05-20 21:36:02 +00:00
self.WriteSynchronous( 'maintain_similar_files_tree', maintenance_mode = maintenance_mode, stop_time = tree_stop_time )
2019-07-31 22:01:02 +00:00
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
2024-04-03 21:15:48 +00:00
work_done = False
2017-01-25 22:56:55 +00:00
2024-04-03 21:15:48 +00:00
still_work_to_do = True
2017-01-25 22:56:55 +00:00
2024-04-03 21:15:48 +00:00
while still_work_to_do:
2017-01-25 22:56:55 +00:00
2024-04-03 21:15:48 +00:00
search_distance = CG.client_controller.new_options.GetInteger( 'similar_files_duplicate_pairs_search_distance' )
start_time = HydrusTime.GetNowPrecise()
work_time_ms = CG.client_controller.new_options.GetInteger( 'potential_duplicates_search_work_time_ms' )
work_time = work_time_ms / 1000
( still_work_to_do, num_done ) = CG.client_controller.WriteSynchronous( 'maintain_similar_files_search_for_potential_duplicates', search_distance, maintenance_mode = maintenance_mode, work_time_float = work_time )
if num_done > 0:
work_done = True
if not still_work_to_do:
break
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
break
time_it_took = HydrusTime.GetNowPrecise() - start_time
rest_ratio = CG.client_controller.new_options.GetInteger( 'potential_duplicates_search_rest_percentage' ) / 100
reasonable_work_time = min( 5 * work_time, time_it_took )
time.sleep( reasonable_work_time * rest_ratio )
2017-01-25 22:56:55 +00:00
2024-04-03 21:15:48 +00:00
if work_done:
from hydrus.client import ClientDuplicates
ClientDuplicates.DuplicatesManager.instance().RefreshMaintenanceNumbers()
2020-12-09 22:18:48 +00:00
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
2021-06-02 21:59:19 +00:00
self.WriteSynchronous( 'analyze', maintenance_mode = maintenance_mode, stop_time = stop_time )
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
2015-08-26 21:18:39 +00:00
2021-06-02 21:59:19 +00:00
def MaintainHashedSerialisables( self ):
2019-06-19 22:08:48 +00:00
2021-06-02 21:59:19 +00:00
self.WriteSynchronous( 'maintain_hashed_serialisables' )
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
2024-01-17 18:57:00 +00:00
if HydrusTime.TimeHasPassedMS( self.GetTimestampMS( 'last_page_change' ) + 30 * 60000 ):
2017-05-10 21:33:58 +00:00
2018-01-10 22:41:51 +00:00
self.pub( 'delete_old_closed_pages' )
2015-08-26 21:18:39 +00:00
2024-01-17 18:57:00 +00:00
self.TouchTime( 'last_page_change' )
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
2023-07-19 20:38:06 +00:00
def PageAliveAndNotClosed( self, page_key ):
with self._page_key_lock:
return page_key in self._alive_page_keys and page_key not in self._closed_page_keys
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
2023-07-19 20:38:06 +00:00
def PageDestroyed( self, page_key ):
with self._page_key_lock:
return page_key not in self._alive_page_keys and page_key not in self._closed_page_keys
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
2021-02-17 18:22:44 +00:00
def ProgramIsShutDown( self ):
return self._program_is_shut_down
def ProgramIsShuttingDown( self ):
return self._program_is_shutting_down
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() )
def RefreshServices( self ):
self.services_manager.RefreshServices()
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 )
2021-09-22 21:12:34 +00:00
def ReportLastSessionLoaded( self, gui_session ):
if self._last_last_session_hash is None:
self._last_last_session_hash = gui_session.GetSerialisedHash()
2022-04-06 20:40:17 +00:00
def ReportFirstSessionInitialised( self ):
2020-01-02 03:05:35 +00:00
2021-12-22 22:31:23 +00:00
job = self.CallRepeating( 5.0, 3600.0, self.SynchroniseAccounts )
job.ShouldDelayOnWakeup( True )
job.WakeOnPubSub( 'notify_account_sync_due' )
job.WakeOnPubSub( 'notify_network_traffic_unpaused' )
self._daemon_jobs[ 'synchronise_accounts' ] = job
2023-11-22 22:29:14 +00:00
from hydrus.core.networking import HydrusNetwork
2021-12-22 22:31:23 +00:00
job = self.CallRepeating( 5.0, HydrusNetwork.UPDATE_CHECKING_PERIOD, self.SynchroniseRepositories )
job.ShouldDelayOnWakeup( True )
job.WakeOnPubSub( 'notify_restart_repo_sync' )
job.WakeOnPubSub( 'notify_new_permissions' )
job.WakeOnPubSub( 'wake_idle_workers' )
job.WakeOnPubSub( 'notify_network_traffic_unpaused' )
self._daemon_jobs[ 'synchronise_repositories' ] = job
2020-01-02 03:05:35 +00:00
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
2021-12-22 22:31:23 +00:00
job = self.CallRepeating( 30.0, 3600.0, ClientDaemons.DAEMONMaintainTrash )
job.ShouldDelayOnWakeup( True )
self._daemon_jobs[ 'maintain_trash' ] = job
job = self.CallRepeating( 0.0, 30.0, self.SaveDirtyObjectsImportant )
job.WakeOnPubSub( 'important_dirt_to_clean' )
self._daemon_jobs[ 'save_dirty_objects_important' ] = job
job = self.CallRepeating( 0.0, 300.0, self.SaveDirtyObjectsInfrequent )
self._daemon_jobs[ 'save_dirty_objects_infrequent' ] = job
job = self.CallRepeating( 30.0, 86400.0, self.client_files_manager.DoDeferredPhysicalDeletes )
job.WakeOnPubSub( 'notify_new_physical_file_deletes' )
self._daemon_jobs[ 'deferred_physical_deletes' ] = job
2021-06-02 21:59:19 +00:00
job = self.CallRepeating( 30.0, 600.0, self.MaintainHashedSerialisables )
job.WakeOnPubSub( 'maintain_hashed_serialisables' )
job.ShouldDelayOnWakeup( True )
self._daemon_jobs[ 'maintain_hashed_serialisables' ] = job
2023-11-22 22:29:14 +00:00
self.files_maintenance_manager.Start()
self.database_maintenance_manager.Start()
2020-01-02 03:05:35 +00:00
self.subscriptions_manager.Start()
2021-06-09 20:28:09 +00:00
def ResetIdleTimerFromClientAPI( self ):
2024-01-17 18:57:00 +00:00
self.TouchTime( 'last_client_api_action' )
2021-06-09 20:28:09 +00:00
2015-08-26 21:18:39 +00:00
def ResetPageChangeTimer( self ):
2024-01-17 18:57:00 +00:00
self.TouchTime( 'last_page_change' )
2015-08-26 21:18:39 +00:00
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
2021-03-17 21:59:28 +00:00
self.CallToThreadLongRunning( self.SetRunningTwistedServices, services )
2013-09-25 20:20:10 +00:00
2014-01-08 18:40:02 +00:00
def RestoreDatabase( self ):
2020-04-22 21:00:35 +00:00
from hydrus.client.gui 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
2020-11-11 22:20:16 +00:00
text = 'Are you sure you want to restore a backup from "{}"?'.format( path )
2014-12-10 22:02:39 +00:00
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
self._restore_backup_path = path
self._doing_fast_exit = False
HG.do_idle_shutdown_work = False
HG.restart = True
2019-09-05 00:05:32 +00:00
self.Exit()
2019-09-05 00:05:32 +00:00
2015-09-02 23:16:09 +00:00
def Run( self ):
2023-11-22 22:29:14 +00:00
from hydrus.client.gui import QtInit
QtInit.MonkeyPatchMissingMethods()
2019-11-20 23:10:46 +00:00
2020-04-22 21:00:35 +00:00
from hydrus.client.gui import ClientGUICore
2020-03-04 22:12:53 +00:00
ClientGUICore.GUICore()
2022-11-16 21:34:30 +00:00
if HC.PLATFORM_WINDOWS and HC.RUNNING_FROM_SOURCE:
2022-10-26 20:43:00 +00:00
try:
# this makes the 'application user model' of the program unique, allowing instantiations to group on their own taskbar icon
# also allows the window icon to go to the taskbar icon, instead of python if you are running from source
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( 'hydrus network client' )
except:
pass
2020-01-29 22:08:37 +00:00
self.app = App( self._pubsub, sys.argv )
2018-01-10 22:41:51 +00:00
2020-04-08 21:10:11 +00:00
self.main_qt_thread = self.app.thread()
2023-09-06 19:49:46 +00:00
HydrusData.Print( 'booting controller' + HC.UNICODE_ELLIPSIS )
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
2020-09-02 21:10:41 +00:00
self.frame_splash_status = ClientGUISplash.FrameSplashStatus()
2020-04-01 21:51:42 +00:00
self.CreateSplash( 'hydrus client booting' )
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
2020-03-25 21:15:57 +00:00
self._qt_app_running = True
2019-11-14 03:56:30 +00:00
try:
self.app.exec_()
finally:
2020-03-25 21:15:57 +00:00
self._qt_app_running = False
2019-11-14 03:56:30 +00:00
2015-09-02 23:16:09 +00:00
2023-09-06 19:49:46 +00:00
HydrusData.DebugPrint( 'shutting down controller' + HC.UNICODE_ELLIPSIS )
2015-09-02 23:16:09 +00:00
2020-07-29 20:52:44 +00:00
def SaveDirtyObjectsImportant( self ):
2017-03-02 02:14:56 +00:00
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-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'services' )
2020-01-02 03:05:35 +00:00
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-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'client api manager' )
2020-01-02 03:05:35 +00:00
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-10-11 17:38:14 +00:00
if self.network_engine.domain_manager.IsDirty():
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'domain manager' )
2020-01-02 03:05:35 +00:00
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-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'login manager' )
2020-01-02 03:05:35 +00:00
2018-10-24 21:34:02 +00:00
self.WriteSynchronous( 'serialisable', self.network_engine.login_manager )
self.network_engine.login_manager.SetClean()
2020-03-11 21:52:11 +00:00
if self.favourite_search_manager.IsDirty():
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'favourite searches manager' )
2020-03-11 21:52:11 +00:00
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-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'tag display manager' )
2020-01-02 03:05:35 +00:00
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
2020-07-29 20:52:44 +00:00
def SaveDirtyObjectsInfrequent( self ):
with HG.dirty_object_lock:
if self.column_list_manager.IsDirty():
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'column list manager' )
2020-07-29 20:52:44 +00:00
self.WriteSynchronous( 'serialisable', self.column_list_manager )
self.column_list_manager.SetClean()
2021-01-13 21:48:58 +00:00
if self.network_engine.bandwidth_manager.IsDirty() or self.network_engine.bandwidth_manager.HasDirtyTrackerContainers():
2020-07-29 20:52:44 +00:00
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'bandwidth manager' )
2020-07-29 20:52:44 +00:00
self.WriteSynchronous( 'serialisable', self.network_engine.bandwidth_manager )
self.network_engine.bandwidth_manager.SetClean()
2021-01-13 21:48:58 +00:00
if self.network_engine.session_manager.IsDirty() or self.network_engine.session_manager.HasDirtySessionContainers():
2020-07-29 20:52:44 +00:00
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'session manager' )
2020-07-29 20:52:44 +00:00
self.WriteSynchronous( 'serialisable', self.network_engine.session_manager )
self.network_engine.session_manager.SetClean()
2019-12-11 23:18:37 +00:00
def SaveGUISession( self, session ):
name = session.GetName()
2021-09-22 21:12:34 +00:00
if name == CC.LAST_SESSION_SESSION_NAME:
2019-12-11 23:18:37 +00:00
2021-06-02 21:59:19 +00:00
session_hash = session.GetSerialisedHash()
2019-12-11 23:18:37 +00:00
2021-06-02 21:59:19 +00:00
# keep this in. we still don't want to overwrite backups if no changes have occurred
2019-12-11 23:18:37 +00:00
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 ):
2023-09-06 19:49:46 +00:00
HydrusData.Print( 'starting services' + HC.UNICODE_ELLIPSIS )
2019-08-15 00:40:48 +00:00
for service in services:
service_key = service.GetServiceKey()
service_type = service.GetServiceType()
name = service.GetName()
port = service.GetPort()
allow_non_local_connections = service.AllowsNonLocalConnections()
use_https = service.UseHTTPS()
2019-08-15 00:40:48 +00:00
if port is None:
continue
try:
if use_https:
import twisted.internet.ssl
( 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 )
2021-04-07 21:26:45 +00:00
from hydrus.client.networking import ClientLocalServer
2019-08-15 00:40:48 +00:00
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 )
2023-02-15 21:26:44 +00:00
else:
raise NotImplementedError( 'Unknown service type!' )
2019-08-15 00:40:48 +00:00
2020-04-08 21:10:11 +00:00
ipv6_port = None
try:
2023-04-19 20:38:13 +00:00
if allow_non_local_connections:
2023-10-11 20:46:40 +00:00
interface = '::'
2023-04-19 20:38:13 +00:00
else:
2023-10-11 20:46:40 +00:00
interface = '::1'
2023-04-19 20:38:13 +00:00
if use_https:
2023-04-19 20:38:13 +00:00
ipv6_port = reactor.listenSSL( port, http_factory, context_factory, interface = interface )
else:
2023-04-19 20:38:13 +00:00
ipv6_port = reactor.listenTCP( port, http_factory, interface = interface )
2020-04-08 21:10:11 +00:00
except Exception as e:
HydrusData.Print( 'Could not bind to IPv6:' )
HydrusData.Print( str( e ) )
ipv4_port = None
try:
2023-04-19 20:38:13 +00:00
if allow_non_local_connections:
interface = ''
else:
interface = '127.0.0.1'
if use_https:
2023-04-19 20:38:13 +00:00
ipv4_port = reactor.listenSSL( port, http_factory, context_factory, interface = interface )
else:
2023-04-19 20:38:13 +00:00
ipv4_port = reactor.listenTCP( port, http_factory, interface = interface )
2020-04-08 21:10:11 +00:00
except:
if ipv6_port is None:
raise
self._service_keys_to_connected_ports[ service_key ] = ( ipv4_port, ipv6_port )
2019-08-15 00:40:48 +00:00
2021-04-07 21:26:45 +00:00
if HydrusNetworking.LocalPortInUse( port ):
HydrusData.Print( 'Running "{}" on port {}.'.format( name, port ) )
else:
2019-08-15 00:40:48 +00:00
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:
2023-09-06 19:49:46 +00:00
HydrusData.Print( 'stopping services' + HC.UNICODE_ELLIPSIS )
2019-08-15 00:40:48 +00:00
deferreds = []
2023-02-15 21:26:44 +00:00
for ( existing_ipv4_port, existing_ipv6_port ) in self._service_keys_to_connected_ports.values():
2019-08-15 00:40:48 +00:00
2023-02-15 21:26:44 +00:00
if existing_ipv4_port is not None:
2020-04-08 21:10:11 +00:00
2023-02-15 21:26:44 +00:00
deferred = defer.maybeDeferred( existing_ipv4_port.stopListening )
2020-04-08 21:10:11 +00:00
deferreds.append( deferred )
2019-08-15 00:40:48 +00:00
2023-02-15 21:26:44 +00:00
if existing_ipv6_port is not None:
2020-04-08 21:10:11 +00:00
2023-02-15 21:26:44 +00:00
deferred = defer.maybeDeferred( existing_ipv6_port.stopListening )
2020-04-08 21:10:11 +00:00
deferreds.append( deferred )
2019-08-15 00:40:48 +00:00
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! Specific error has been printed to log. Please contact hydrus dev!' )
HydrusData.Print( HG.twisted_is_broke_exception )
2019-08-15 00:40:48 +00:00
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
previous_services = self.services_manager.GetServices()
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 ) ]
2021-03-17 21:59:28 +00:00
self.CallToThreadLongRunning( self.services_upnp_manager.SetServices, upnp_services )
2019-02-13 22:26:43 +00:00
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 ):
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetText( 'saving and exiting objects' )
2019-12-05 05:29:32 +00:00
2019-11-14 03:56:30 +00:00
if self._is_booted:
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( 'file viewing stats flush' )
2020-01-02 03:05:35 +00:00
2019-11-14 03:56:30 +00:00
self.file_viewing_stats_manager.Flush()
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( '' )
2020-01-02 03:05:35 +00:00
2020-07-29 20:52:44 +00:00
self.SaveDirtyObjectsImportant()
self.SaveDirtyObjectsInfrequent()
2019-11-14 03:56:30 +00:00
2017-07-05 21:09:28 +00:00
HydrusController.HydrusController.ShutdownModel( self )
2015-09-02 23:16:09 +00:00
def ShutdownView( self ):
if not self._doing_fast_exit:
2015-09-02 23:16:09 +00:00
2020-10-21 22:22:10 +00:00
self.frame_splash_status.SetText( 'waiting for managers to exit' )
2019-12-05 05:29:32 +00:00
2020-10-21 22:22:10 +00:00
self._ShutdownManagers()
2019-12-05 05:29:32 +00:00
2022-08-03 20:59:51 +00:00
self.frame_splash_status.SetText( 'waiting for workers to exit' )
2015-09-02 23:16:09 +00:00
2016-01-06 21:17:20 +00:00
self._ShutdownDaemons()
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( '' )
2019-12-05 05:29:32 +00:00
2017-05-10 21:33:58 +00:00
if HG.do_idle_shutdown_work:
2015-09-02 23:16:09 +00:00
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetText( 'waiting for idle shutdown work' )
2019-12-05 05:29:32 +00:00
2017-03-29 19:39:34 +00:00
try:
self.DoIdleShutdownWork()
2020-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( '' )
2019-12-11 23:18:37 +00:00
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-09-02 21:10:41 +00:00
self.frame_splash_status.SetSubtext( '' )
2020-01-02 03:05:35 +00:00
2019-09-25 21:34:18 +00:00
try:
2022-08-03 20:59:51 +00:00
self.frame_splash_status.SetText( 'waiting for services to exit' )
2019-12-11 23:18:37 +00:00
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 ):
2024-02-07 21:22:05 +00:00
if CG.client_controller.new_options.GetBoolean( 'pause_all_new_network_traffic' ):
2021-12-22 22:31:23 +00:00
return
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()
def SynchroniseRepositories( self ):
2024-02-07 21:22:05 +00:00
if CG.client_controller.new_options.GetBoolean( 'pause_all_new_network_traffic' ):
2021-12-22 22:31:23 +00:00
return
2022-04-20 20:18:56 +00:00
if not self.new_options.GetBoolean( 'pause_repo_sync' ):
services = self.services_manager.GetServices( HC.REPOSITORIES, randomised = True )
for service in services:
if HydrusThreading.IsThreadShuttingDown():
return
2022-04-20 20:18:56 +00:00
if self.new_options.GetBoolean( 'pause_repo_sync' ):
return
service.SyncRemote()
service.SyncProcessUpdates( maintenance_mode = HC.MAINTENANCE_IDLE )
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
2021-07-28 21:12:00 +00:00
system_busy_cpu_percent = self.new_options.GetInteger( 'system_busy_cpu_percent' )
system_busy_cpu_count = self.new_options.GetNoneableInteger( 'system_busy_cpu_count' )
2015-08-26 21:18:39 +00:00
2021-07-28 21:12:00 +00:00
if system_busy_cpu_count 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
2024-01-17 18:57:00 +00:00
if HydrusTime.TimeHasPassedMS( self.GetTimestampMS( 'last_cpu_check' ) + 60000 ):
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
2021-07-28 21:12:00 +00:00
if len( [ 1 for cpu_time in cpu_times if cpu_time > system_busy_cpu_percent ] ) >= system_busy_cpu_count:
2015-12-02 22:32:18 +00:00
self._system_busy = True
else:
self._system_busy = False
2015-08-26 21:18:39 +00:00
2024-01-17 18:57:00 +00:00
self.TouchTime( 'last_cpu_check' )
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()
2022-07-25 15:33:44 +00:00
QP.CallAfter( QW.QApplication.exit )
2019-10-16 20:47:55 +00:00
return
try:
self.RecordRunningStart()
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
2020-05-06 21:31:41 +00:00
except ( HydrusExceptions.DBCredentialsException, 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
self.CleanRunningFile()
2019-11-28 01:11:46 +00:00
2022-07-25 15:33:44 +00:00
QP.CallAfter( QW.QApplication.exit )
2015-04-08 18:10:50 +00:00
2024-02-07 21:22:05 +00:00
except HydrusExceptions.DBVersionException as e:
self.BlockingSafeShowCriticalMessage( 'database version error', str( e ) )
QP.CallAfter( QW.QApplication.exit, 1 )
2023-07-05 20:52:58 +00:00
except HydrusExceptions.DBAccessException as e:
trace = traceback.format_exc()
HydrusData.DebugPrint( trace )
text = 'A serious problem occurred while trying to start the program. Full details have been written to the log. The error is:'
text += '\n' * 2
text += str( e )
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( 'boot error', text )
2023-07-05 20:52:58 +00:00
QP.CallAfter( QW.QApplication.exit, 1 )
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
2022-05-18 20:18:25 +00:00
trace = traceback.format_exc()
HydrusData.DebugPrint( trace )
2016-01-13 22:08:19 +00:00
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( 'boot error', text )
2016-01-13 22:08:19 +00:00
2022-05-18 20:18:25 +00:00
if 'malformed' in trace:
hell_message = 'Looking at it, it looks like your database may be malformed! This is a serious error. Please check "/install_dir/db/help my db is broke.txt" as soon as you can for the next steps. The specific error will now follow.'
HydrusData.DebugPrint( hell_message )
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( 'boot error', hell_message )
2022-05-18 20:18:25 +00:00
2023-12-06 22:13:50 +00:00
self.BlockingSafeShowCriticalMessage( 'boot error', trace )
2016-01-13 22:08:19 +00:00
QP.CallAfter( QW.QApplication.exit, 1 )
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:
2022-01-19 21:28:59 +00:00
HG.started_shutdown = True
2023-09-06 19:49:46 +00:00
self.frame_splash_status.SetTitleText( 'shutting down gui' + HC.UNICODE_ELLIPSIS )
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
2023-09-06 19:49:46 +00:00
self.frame_splash_status.SetTitleText( 'shutting down db' + HC.UNICODE_ELLIPSIS )
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
if self._restore_backup_path is not None:
2023-09-06 19:49:46 +00:00
self.frame_splash_status.SetTitleText( 'restoring backup' + HC.UNICODE_ELLIPSIS )
self.db.RestoreBackup( self._restore_backup_path )
2023-09-06 19:49:46 +00:00
self.frame_splash_status.SetTitleText( 'cleaning up' + HC.UNICODE_ELLIPSIS )
2017-08-09 21:33:51 +00:00
self.CleanRunningFile()
2016-06-15 18:59:44 +00:00
2020-05-06 21:31:41 +00:00
except ( HydrusExceptions.DBCredentialsException, 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:
QW.QApplication.instance().setProperty( 'exit_complete', True )
2020-01-02 03:05:35 +00:00
self._DestroySplash()
2021-02-17 18:22:44 +00:00
self._program_is_shut_down = True
2022-07-25 15:33:44 +00:00
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':
2023-06-28 20:29:14 +00:00
( media, optional_target_resolution_tuple ) = data
2017-12-13 22:33:07 +00:00
image_renderer = self.GetCache( 'images' ).GetImageRenderer( media )
def CopyToClipboard():
2023-06-28 20:29:14 +00:00
if optional_target_resolution_tuple is None:
target_resolution = None
else:
target_resolution = QC.QSize( optional_target_resolution_tuple[0], optional_target_resolution_tuple[1] )
2021-05-05 20:12:11 +00:00
# this is faster than qpixmap, which converts to a qimage anyway
2023-06-28 20:29:14 +00:00
qt_image = image_renderer.GetQtImage( target_resolution = target_resolution ).copy()
2019-11-14 03:56:30 +00:00
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():
2023-04-19 20:38:13 +00:00
if HydrusTime.TimeHasPassed( start_time + 15 ):
2017-12-13 22:33:07 +00:00
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
2021-03-17 21:59:28 +00:00
self.CallToThreadLongRunning( THREADWait )
2017-12-13 22:33:07 +00:00
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 ):
2022-06-08 19:46:00 +00:00
if 'thumbnail' in self._caches:
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':
2022-06-08 19:46:00 +00:00
if 'undo' in self._managers:
self._managers[ 'undo' ].AddCommand( 'content_updates', *args, **kwargs )
2017-12-06 22:06:56 +00:00
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