import os import wx wx_first_num = int( wx.__version__[0] ) if wx_first_num < 4: wx_error = 'Unfortunately, hydrus now requires the new Phoenix (4.x) version of wx.' wx_error += os.linesep * 2 wx_error += 'Please check the \'running from source\' page in the html help for more details.' raise Exception( wx_error ) import ClientCaches import ClientData import ClientDaemons import ClientDefaults import ClientGUICommon import ClientGUIMenus import ClientNetworking import ClientNetworkingBandwidth import ClientNetworkingDomain import ClientNetworkingLogin import ClientNetworkingSessions import ClientOptions import ClientPaths import ClientThreading import hashlib import HydrusConstants as HC import HydrusController import HydrusData import HydrusExceptions import HydrusGlobals as HG import HydrusNetworking import HydrusPaths import HydrusSerialisable import HydrusThreading import HydrusVideoHandling import ClientConstants as CC import ClientDB import ClientGUI import ClientGUIDialogs import ClientGUIScrolledPanelsManagement import ClientGUITopLevelWindows import gc import psutil import threading import time import traceback if not HG.twisted_is_broke: from twisted.internet import reactor, defer class Controller( HydrusController.HydrusController ): def __init__( self, db_dir, no_daemons, no_wal ): self._last_shutdown_was_bad = False self._is_booted = False self._splash = None HydrusController.HydrusController.__init__( self, db_dir, no_daemons, no_wal ) self._name = 'client' HG.client_controller = self # just to set up some defaults, in case some db update expects something for an odd yaml-loading reason self.options = ClientDefaults.GetClientDefaultOptions() self.new_options = ClientOptions.ClientOptions( self.db_dir ) HC.options = self.options self._page_key_lock = threading.Lock() self._alive_page_keys = set() self._closed_page_keys = set() self._last_mouse_position = None self._menu_open = False self._previously_idle = False self._idle_started = None self.client_files_manager = None self.services_manager = None def _InitDB( self ): return ClientDB.DB( self, self.db_dir, 'client', no_wal = self._no_wal ) def _DestroySplash( self ): if self._splash is not None: self._splash.DestroyLater() self._splash = None 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 ) ) def AcquirePageKey( self ): with self._page_key_lock: page_key = HydrusData.GenerateKey() self._alive_page_keys.add( page_key ) return page_key def CallBlockingToWx( self, func, *args, **kwargs ): def wx_code( job_key ): try: result = func( *args, **kwargs ) job_key.SetVariable( 'result', result ) except HydrusExceptions.PermissionException as e: job_key.SetVariable( 'error', e ) except Exception as e: job_key.SetVariable( 'error', e ) HydrusData.Print( 'CallBlockingToWx just caught this error:' ) HydrusData.DebugPrint( traceback.format_exc() ) finally: job_key.Finish() job_key = ClientThreading.JobKey() job_key.Begin() wx.CallAfter( wx_code, job_key ) while not job_key.IsDone(): if self._model_shutdown: raise HydrusExceptions.ShutdownException( 'Application is shutting down!' ) time.sleep( 0.05 ) if job_key.HasVariable( 'result' ): # result can be None, for wx_code that has no return variable result = job_key.GetIfHasVariable( 'result' ) return result error = job_key.GetIfHasVariable( 'error' ) if error is not None: raise error raise HydrusExceptions.ShutdownException() def CallLaterWXSafe( self, window, initial_delay, func, *args, **kwargs ): job_scheduler = self._GetAppropriateJobScheduler( initial_delay ) call = HydrusData.Call( func, *args, **kwargs ) job = ClientThreading.WXAwareJob( self, job_scheduler, window, initial_delay, call ) job_scheduler.AddJob( job ) return job def CallRepeatingWXSafe( self, window, initial_delay, period, func, *args, **kwargs ): job_scheduler = self._GetAppropriateJobScheduler( period ) call = HydrusData.Call( func, *args, **kwargs ) job = ClientThreading.WXAwareRepeatingJob( self, job_scheduler, window, initial_delay, period, call ) job_scheduler.AddJob( job ) return job def CheckAlreadyRunning( self ): while HydrusData.IsAlreadyRunning( self.db_dir, 'client' ): self.pub( 'splash_set_status_text', 'client already running' ) def wx_code(): 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.' with ClientGUIDialogs.DialogYesNo( self._splash, message, 'The client is already running.', yes_label = 'wait a bit, then try again', no_label = 'forget it' ) as dlg: if dlg.ShowModal() != wx.ID_YES: raise HydrusExceptions.PermissionException() self.CallBlockingToWx( wx_code ) for i in range( 10, 0, -1 ): if not HydrusData.IsAlreadyRunning( self.db_dir, 'client' ): break self.pub( 'splash_set_status_text', 'waiting ' + str( i ) + ' seconds' ) time.sleep( 1 ) def CheckMouseIdle( self ): mouse_position = wx.GetMousePosition() if self._last_mouse_position is None: self._last_mouse_position = mouse_position elif mouse_position != self._last_mouse_position: idle_before_position_update = self.CurrentlyIdle() self._timestamps[ 'last_mouse_action' ] = HydrusData.GetNow() self._last_mouse_position = mouse_position idle_after_position_update = self.CurrentlyIdle() move_knocked_us_out_of_idle = ( not idle_before_position_update ) and idle_after_position_update if move_knocked_us_out_of_idle: self.pub( 'set_status_bar_dirty' ) def ClosePageKeys( self, page_keys ): with self._page_key_lock: self._closed_page_keys.update( page_keys ) 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 def CurrentlyIdle( self ): if HG.force_idle_mode: self._idle_started = 0 return True if not HydrusData.TimeHasPassed( self._timestamps[ 'boot' ] + 120 ): return False idle_normal = self.options[ 'idle_normal' ] idle_period = self.options[ 'idle_period' ] idle_mouse_period = self.options[ 'idle_mouse_period' ] if idle_normal: currently_idle = True if idle_period is not None: if not HydrusData.TimeHasPassed( self._timestamps[ 'last_user_action' ] + idle_period ): currently_idle = False if idle_mouse_period is not None: if not HydrusData.TimeHasPassed( self._timestamps[ 'last_mouse_action' ] + idle_mouse_period ): currently_idle = False else: currently_idle = False turning_idle = currently_idle and not self._previously_idle self._previously_idle = currently_idle if turning_idle: self._idle_started = HydrusData.GetNow() self.pub( 'wake_daemons' ) if not currently_idle: self._idle_started = None return currently_idle def CurrentlyVeryIdle( self ): if self._idle_started is not None and HydrusData.TimeHasPassed( self._idle_started + 3600 ): return True return False def DoIdleShutdownWork( self ): stop_time = HydrusData.GetNow() + ( self.options[ 'idle_shutdown_max_minutes' ] * 60 ) self.MaintainDB( stop_time = stop_time ) if not self.options[ 'pause_repo_sync' ]: services = self.services_manager.GetServices( HC.REPOSITORIES ) for service in services: if HydrusData.TimeHasPassed( stop_time ): return service.SyncProcessUpdates( only_when_idle = False, stop_time = stop_time ) def Exit( self ): if HG.emergency_exit: self.ShutdownView() self.ShutdownModel() HydrusData.CleanRunningFile( self.db_dir, 'client' ) else: try: idle_shutdown_action = self.options[ 'idle_shutdown' ] if idle_shutdown_action in ( CC.IDLE_ON_SHUTDOWN, CC.IDLE_ON_SHUTDOWN_ASK_FIRST ): idle_shutdown_max_minutes = self.options[ 'idle_shutdown_max_minutes' ] time_to_stop = HydrusData.GetNow() + ( idle_shutdown_max_minutes * 60 ) if self.ThereIsIdleShutdownWorkDue( time_to_stop ): if idle_shutdown_action == CC.IDLE_ON_SHUTDOWN_ASK_FIRST: text = 'Is now a good time for the client to do up to ' + HydrusData.ConvertIntToPrettyString( idle_shutdown_max_minutes ) + ' minutes\' maintenance work? (Will auto-no in 15 seconds)' with ClientGUIDialogs.DialogYesNo( self._splash, text, title = 'Maintenance is due' ) as dlg_yn: job = self.CallLaterWXSafe( dlg_yn, 15, dlg_yn.EndModal, wx.ID_NO ) try: if dlg_yn.ShowModal() == wx.ID_YES: HG.do_idle_shutdown_work = True finally: job.Cancel() else: HG.do_idle_shutdown_work = True self.CallToThreadLongRunning( self.THREADExitEverything ) except: self._DestroySplash() HydrusData.DebugPrint( traceback.format_exc() ) HG.emergency_exit = True self.Exit() def GetApp( self ): return self._app def GetBandwidthManager( self ): raise NotImplementedError() def GetClipboardText( self ): if wx.TheClipboard.Open(): data = wx.TextDataObject() wx.TheClipboard.GetData( data ) wx.TheClipboard.Close() text = data.GetText() text = HydrusData.ToUnicode( text ) return text else: raise Exception( 'I could not get permission to access the clipboard.' ) def GetCommandFromShortcut( self, shortcut_names, shortcut ): return self._shortcuts_manager.GetCommand( shortcut_names, shortcut ) def GetGUI( self ): return self.gui def GetNewOptions( self ): return self.new_options def InitClientFilesManager( self ): def wx_code( missing_locations ): with ClientGUITopLevelWindows.DialogManage( None, 'repair file system' ) as dlg: panel = ClientGUIScrolledPanelsManagement.RepairFileSystemPanel( dlg, missing_locations ) dlg.SetPanel( panel ) if dlg.ShowModal() == wx.ID_OK: self.client_files_manager = ClientCaches.ClientFilesManager( self ) missing_locations = self.client_files_manager.GetMissing() else: raise HydrusExceptions.PermissionException( 'File system failed, user chose to quit.' ) return missing_locations self.client_files_manager = ClientCaches.ClientFilesManager( self ) missing_locations = self.client_files_manager.GetMissing() while len( missing_locations ) > 0: missing_locations = self.CallBlockingToWx( wx_code, missing_locations ) def InitModel( self ): self.pub( 'splash_set_title_text', u'booting db\u2026' ) HydrusController.HydrusController.InitModel( self ) self.pub( 'splash_set_status_text', u'initialising managers' ) self.pub( 'splash_set_status_subtext', u'services' ) self.services_manager = ClientCaches.ServicesManager( self ) self.pub( 'splash_set_status_subtext', u'options' ) self.options = self.Read( 'options' ) self.new_options = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS ) HC.options = self.options if self.new_options.GetBoolean( 'use_system_ffmpeg' ): if HydrusVideoHandling.FFMPEG_PATH.startswith( HC.BIN_DIR ): HydrusVideoHandling.FFMPEG_PATH = os.path.basename( HydrusVideoHandling.FFMPEG_PATH ) self.pub( 'splash_set_status_subtext', u'client files' ) self.temp_dir = ClientPaths.GetTempDir() self.InitClientFilesManager() # self.pub( 'splash_set_status_subtext', u'network' ) self.parsing_cache = ClientCaches.ParsingCache() bandwidth_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER ) if bandwidth_manager is None: bandwidth_manager = ClientNetworkingBandwidth.NetworkBandwidthManager() ClientDefaults.SetDefaultBandwidthManagerRules( bandwidth_manager ) bandwidth_manager._dirty = True wx.MessageBox( '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.' ) session_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_SESSION_MANAGER ) if session_manager is None: session_manager = ClientNetworkingSessions.NetworkSessionManager() session_manager._dirty = True wx.MessageBox( '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.' ) domain_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER ) if domain_manager is None: domain_manager = ClientNetworkingDomain.NetworkDomainManager() ClientDefaults.SetDefaultDomainManagerData( domain_manager ) domain_manager._dirty = True wx.MessageBox( '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.' ) domain_manager.Initialise() login_manager = ClientNetworkingLogin.NetworkLoginManager() self.network_engine = ClientNetworking.NetworkEngine( self, bandwidth_manager, session_manager, domain_manager, login_manager ) self.CallToThreadLongRunning( self.network_engine.MainLoop ) # self._shortcuts_manager = ClientCaches.ShortcutsManager( self ) self.local_booru_manager = ClientCaches.LocalBooruCache( self ) self.pub( 'splash_set_status_subtext', u'tag censorship' ) self._managers[ 'tag_censorship' ] = ClientCaches.TagCensorshipManager( self ) self.pub( 'splash_set_status_subtext', u'tag siblings' ) self._managers[ 'tag_siblings' ] = ClientCaches.TagSiblingsManager( self ) self.pub( 'splash_set_status_subtext', u'tag parents' ) self._managers[ 'tag_parents' ] = ClientCaches.TagParentsManager( self ) self._managers[ 'undo' ] = ClientCaches.UndoManager( self ) def wx_code(): self._caches[ 'images' ] = ClientCaches.RenderedImageCache( self ) self._caches[ 'thumbnail' ] = ClientCaches.ThumbnailCache( self ) CC.GlobalBMPs.STATICInitialise() self.pub( 'splash_set_status_subtext', u'image caches' ) self.CallBlockingToWx( wx_code ) self.sub( self, 'ToClipboard', 'clipboard' ) self.sub( self, 'RestartBooru', 'restart_booru' ) def InitView( self ): if self.options[ 'password' ] is not None: self.pub( 'splash_set_status_text', 'waiting for password' ) def wx_code_password(): while True: with wx.PasswordEntryDialog( self._splash, 'Enter your password', 'Enter password' ) as dlg: if dlg.ShowModal() == wx.ID_OK: # this can produce unicode with cyrillic or w/e keyboards, which hashlib can't handle password = HydrusData.ToByteString( dlg.GetValue() ) if hashlib.sha256( password ).digest() == self.options[ 'password' ]: break else: raise HydrusExceptions.PermissionException( 'Bad password check' ) self.CallBlockingToWx( wx_code_password ) self.pub( 'splash_set_title_text', u'booting gui\u2026' ) def wx_code_gui(): self.gui = ClientGUI.FrameGUI( self ) self.ResetIdleTimer() self.CallBlockingToWx( wx_code_gui ) # ShowText will now popup as a message, as popup message manager has overwritten the hooks HydrusController.HydrusController.InitView( self ) self._booru_port_connection = None self.RestartBooru() if not self._no_daemons: self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SynchroniseAccounts', ClientDaemons.DAEMONSynchroniseAccounts, ( 'notify_unknown_accounts', ) ) ) self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SaveDirtyObjects', ClientDaemons.DAEMONSaveDirtyObjects, ( 'important_dirt_to_clean', ), period = 30 ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'DownloadFiles', ClientDaemons.DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'SynchroniseSubscriptions', ClientDaemons.DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ), period = 14400, init_wait = 60, pre_call_wait = 3 ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'CheckImportFolders', ClientDaemons.DAEMONCheckImportFolders, ( 'notify_restart_import_folders_daemon', 'notify_new_import_folders' ), period = 180 ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'CheckExportFolders', ClientDaemons.DAEMONCheckExportFolders, ( 'notify_restart_export_folders_daemon', 'notify_new_export_folders' ), period = 180 ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'MaintainTrash', ClientDaemons.DAEMONMaintainTrash, init_wait = 120 ) ) self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'SynchroniseRepositories', ClientDaemons.DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ), period = 4 * 3600, pre_call_wait = 1 ) ) self._daemons.append( HydrusThreading.DAEMONBackgroundWorker( self, 'UPnP', ClientDaemons.DAEMONUPnP, ( 'notify_new_upnp_mappings', ), init_wait = 120, pre_call_wait = 6 ) ) self.CallRepeatingWXSafe( self, 10.0, 10.0, self.CheckMouseIdle ) if self.db.IsFirstStart(): message = 'Hi, this looks like the first time you have started the hydrus client.' message += os.linesep * 2 message += 'Don\'t forget to check out the help if you haven\'t already.' message += os.linesep * 2 message += 'To dismiss popup messages like this, right-click them.' HydrusData.ShowText( message ) if self.db.IsDBUpdated(): HydrusData.ShowText( 'The client has updated to version ' + str( HC.SOFTWARE_VERSION ) + '!' ) for message in self.db.GetInitialMessages(): HydrusData.ShowText( message ) def IsBooted( self ): return self._is_booted def LastShutdownWasBad( self ): return self._last_shutdown_was_bad def MaintainDB( self, stop_time = None ): if self.new_options.GetBoolean( 'maintain_similar_files_duplicate_pairs_during_idle' ): phashes_stop_time = stop_time if phashes_stop_time is None: phashes_stop_time = HydrusData.GetNow() + 15 self.WriteInterruptable( 'maintain_similar_files_phashes', stop_time = phashes_stop_time ) tree_stop_time = stop_time if tree_stop_time is None: tree_stop_time = HydrusData.GetNow() + 30 self.WriteInterruptable( 'maintain_similar_files_tree', stop_time = tree_stop_time, abandon_if_other_work_to_do = True ) search_distance = self.new_options.GetInteger( 'similar_files_duplicate_pairs_search_distance' ) search_stop_time = stop_time if search_stop_time is None: search_stop_time = HydrusData.GetNow() + 60 self.WriteInterruptable( 'maintain_similar_files_duplicate_pairs', search_distance, stop_time = search_stop_time, abandon_if_other_work_to_do = True ) if stop_time is None or not HydrusData.TimeHasPassed( stop_time ): self.WriteInterruptable( 'vacuum', stop_time = stop_time ) if stop_time is None or not HydrusData.TimeHasPassed( stop_time ): self.WriteInterruptable( 'analyze', stop_time = stop_time ) if stop_time is None or not HydrusData.TimeHasPassed( stop_time ): if HydrusData.TimeHasPassed( self._timestamps[ 'last_service_info_cache_fatten' ] + ( 60 * 20 ) ): self.pub( 'splash_set_status_text', 'fattening service info' ) services = self.services_manager.GetServices() for service in services: self.pub( 'splash_set_status_subtext', service.GetName() ) try: self.Read( 'service_info', service.GetServiceKey() ) except: pass # sometimes this breaks when a service has just been removed and the client is closing, so ignore the error self._timestamps[ 'last_service_info_cache_fatten' ] = HydrusData.GetNow() def MaintainMemoryFast( self ): HydrusController.HydrusController.MaintainMemoryFast( self ) self.parsing_cache.CleanCache() def MaintainMemorySlow( self ): HydrusController.HydrusController.MaintainMemorySlow( self ) if HydrusData.TimeHasPassed( self._timestamps[ 'last_page_change' ] + 30 * 60 ): self.pub( 'delete_old_closed_pages' ) self._timestamps[ 'last_page_change' ] = HydrusData.GetNow() disk_cache_maintenance_mb = self.new_options.GetNoneableInteger( 'disk_cache_maintenance_mb' ) if disk_cache_maintenance_mb is not None: if self.CurrentlyVeryIdle(): cache_period = 3600 disk_cache_stop_time = HydrusData.GetNow() + 30 elif self.CurrentlyIdle(): cache_period = 1800 disk_cache_stop_time = HydrusData.GetNow() + 10 else: cache_period = 240 disk_cache_stop_time = HydrusData.GetNow() + 2 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() def MenuIsOpen( self ): return self._menu_open def PageAlive( self, page_key ): with self._page_key_lock: return page_key in self._alive_page_keys def PageClosedButNotDestroyed( self, page_key ): with self._page_key_lock: return page_key in self._closed_page_keys def PopupMenu( self, window, menu ): if menu.GetMenuItemCount() > 0: self._menu_open = True window.PopupMenu( menu ) self._menu_open = False ClientGUIMenus.DestroyMenu( window, menu ) def PrepStringForDisplay( self, text ): return text.lower() def ProcessPubSub( self ): self.CallBlockingToWx( self._pubsub.Process ) def RefreshServices( self ): self.services_manager.RefreshServices() def ReleasePageKey( self, page_key ): with self._page_key_lock: self._alive_page_keys.discard( page_key ) self._closed_page_keys.discard( page_key ) def ResetIdleTimer( self ): self._timestamps[ 'last_user_action' ] = HydrusData.GetNow() def ResetPageChangeTimer( self ): self._timestamps[ 'last_page_change' ] = HydrusData.GetNow() def RestartBooru( self ): service = self.services_manager.GetService( CC.LOCAL_BOORU_SERVICE_KEY ) port = service.GetPort() def TWISTEDRestartServer(): def StartServer( *args, **kwargs ): try: try: connection = HydrusNetworking.GetLocalConnection( port ) connection.close() text = 'The client\'s booru server could not start because something was already bound to port ' + str( port ) + '.' text += os.linesep * 2 text += 'This usually means another hydrus client is already running and occupying that port. It could be a previous instantiation of this client that has yet to shut itself down.' text += os.linesep * 2 text += 'You can change the port this client tries to host its local server on in services->manage services.' HydrusData.ShowText( text ) except: import ClientLocalServer self._booru_port_connection = reactor.listenTCP( port, ClientLocalServer.HydrusServiceBooru( service ) ) try: connection = HydrusNetworking.GetLocalConnection( port ) connection.close() except Exception as e: text = 'Tried to bind port ' + str( port ) + ' for the local booru, but it failed:' text += os.linesep * 2 text += HydrusData.ToUnicode( e ) HydrusData.ShowText( text ) except Exception as e: wx.CallAfter( HydrusData.ShowException, e ) if self._booru_port_connection is None: if port is not None: StartServer() else: deferred = defer.maybeDeferred( self._booru_port_connection.stopListening ) if port is not None: deferred.addCallback( StartServer ) if HG.twisted_is_broke: HydrusData.ShowText( 'Twisted failed to import, so could not restart the booru! Please contact hydrus dev!' ) else: reactor.callFromThread( TWISTEDRestartServer ) def RestoreDatabase( self ): restore_intro = '' with wx.DirDialog( self.gui, 'Select backup location.' ) as dlg: if dlg.ShowModal() == wx.ID_OK: path = HydrusData.ToUnicode( dlg.GetPath() ) 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.' with ClientGUIDialogs.DialogYesNo( self.gui, text ) as dlg_yn: if dlg_yn.ShowModal() == wx.ID_YES: def THREADRestart(): wx.CallAfter( self.gui.Exit ) while not self.db.LoopIsFinished(): time.sleep( 0.1 ) self.db.RestoreBackup( path ) while not HG.shutdown_complete: time.sleep( 0.1 ) HydrusData.RestartProcess() self.CallToThreadLongRunning( THREADRestart ) def Run( self ): self._app = wx.App() self._app.locale = wx.Locale( wx.LANGUAGE_DEFAULT ) # Very important to init this here and keep it non garbage collected # do not import locale here and try anything clever--assume that bad locale formatting is due to OS-level mess-up, not mine # wx locale is supposed to set it all up nice, so if someone's doesn't, explore that and find the external solution HydrusData.Print( u'booting controller\u2026' ) self.frame_icon = wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus_32_non-transparent.png' ), wx.BITMAP_TYPE_PNG ) self.CreateSplash() self.CallToThreadLongRunning( self.THREADBootEverything ) self._app.MainLoop() HydrusData.Print( u'shutting down controller\u2026' ) def SaveDirtyObjects( self ): with HG.dirty_object_lock: dirty_services = [ service for service in self.services_manager.GetServices() if service.IsDirty() ] if len( dirty_services ) > 0: self.WriteSynchronous( 'dirty_services', dirty_services ) if self.network_engine.bandwidth_manager.IsDirty(): self.WriteSynchronous( 'serialisable', self.network_engine.bandwidth_manager ) self.network_engine.bandwidth_manager.SetClean() if self.network_engine.domain_manager.IsDirty(): self.WriteSynchronous( 'serialisable', self.network_engine.domain_manager ) self.network_engine.domain_manager.SetClean() if self.network_engine.session_manager.IsDirty(): self.WriteSynchronous( 'serialisable', self.network_engine.session_manager ) self.network_engine.session_manager.SetClean() def SetServices( self, services ): with HG.dirty_object_lock: self.WriteSynchronous( 'update_services', services ) self.services_manager.RefreshServices() def ShutdownModel( self ): if not HG.emergency_exit: self.SaveDirtyObjects() if hasattr( self, 'temp_dir' ): HydrusPaths.DeletePath( self.temp_dir ) HydrusController.HydrusController.ShutdownModel( self ) def ShutdownView( self ): if not HG.emergency_exit: self.pub( 'splash_set_status_text', 'waiting for daemons to exit' ) self._ShutdownDaemons() if HG.do_idle_shutdown_work: try: self.DoIdleShutdownWork() except: ClientData.ReportShutdownException() HydrusController.HydrusController.ShutdownView( self ) def SystemBusy( self ): if HG.force_idle_mode: return False max_cpu = self.options[ 'idle_cpu_max' ] if max_cpu is None: self._system_busy = False else: if HydrusData.TimeHasPassed( self._timestamps[ 'last_cpu_check' ] + 60 ): cpu_times = psutil.cpu_percent( percpu = True ) if True in ( cpu_time > max_cpu for cpu_time in cpu_times ): self._system_busy = True else: self._system_busy = False self._timestamps[ 'last_cpu_check' ] = HydrusData.GetNow() return self._system_busy def ThereIsIdleShutdownWorkDue( self, time_to_stop ): maintenance_due = self.Read( 'maintenance_due', time_to_stop ) if maintenance_due: return True services = self.services_manager.GetServices( HC.REPOSITORIES ) for service in services: if service.CanDoIdleShutdownWork(): return True return False def THREADBootEverything( self ): try: self.CheckAlreadyRunning() self._last_shutdown_was_bad = HydrusData.LastShutdownWasBad( self.db_dir, 'client' ) HydrusData.RecordRunningStart( self.db_dir, 'client' ) self.InitModel() self.InitView() self._is_booted = True except HydrusExceptions.PermissionException as e: HydrusData.Print( e ) HG.emergency_exit = True self.Exit() except Exception as e: text = 'A serious error occured while trying to start the program. The error will be shown next in a window. More information may have been written to client.log.' HydrusData.DebugPrint( 'If the db crashed, another error may be written just above ^.' ) HydrusData.DebugPrint( text ) HydrusData.DebugPrint( traceback.format_exc() ) wx.CallAfter( wx.MessageBox, traceback.format_exc() ) wx.CallAfter( wx.MessageBox, text ) HG.emergency_exit = True self.Exit() finally: self._DestroySplash() def THREADExitEverything( self ): try: self.pub( 'splash_set_title_text', u'shutting down gui\u2026' ) self.ShutdownView() self.pub( 'splash_set_title_text', u'shutting down db\u2026' ) self.ShutdownModel() self.pub( 'splash_set_title_text', u'cleaning up\u2026' ) self.pub( 'splash_set_status_text', u'' ) HydrusData.CleanRunningFile( self.db_dir, 'client' ) except HydrusExceptions.PermissionException: pass except HydrusExceptions.ShutdownException: pass except: ClientData.ReportShutdownException() finally: self._DestroySplash() def ToClipboard( self, data_type, data ): # need this cause can't do it in a non-gui thread if data_type == 'paths': paths = data if wx.TheClipboard.Open(): data = wx.DataObjectComposite() file_data = wx.FileDataObject() for path in paths: file_data.AddFile( path ) text_data = wx.TextDataObject( os.linesep.join( paths ) ) data.Add( file_data, True ) data.Add( text_data, False ) wx.TheClipboard.SetData( data ) wx.TheClipboard.Close() else: wx.MessageBox( 'Could not get permission to access the clipboard!' ) elif data_type == 'text': text = data if wx.TheClipboard.Open(): data = wx.TextDataObject( text ) wx.TheClipboard.SetData( data ) wx.TheClipboard.Close() else: wx.MessageBox( 'I could not get permission to access the clipboard.' ) elif data_type == 'bmp': media = data image_renderer = self.GetCache( 'images' ).GetImageRenderer( media ) def CopyToClipboard(): if wx.TheClipboard.Open(): wx_bmp = image_renderer.GetWXBitmap() data = wx.BitmapDataObject( wx_bmp ) wx.TheClipboard.SetData( data ) wx.TheClipboard.Close() else: wx.MessageBox( 'I could not get permission to access the clipboard.' ) def THREADWait(): # have to do this in thread, because the image needs the wx event queue to render start_time = time.time() while not image_renderer.IsReady(): if HydrusData.TimeHasPassed( start_time + 15 ): raise Exception( 'The image did not render in fifteen seconds, so the attempt to copy it to the clipboard was abandoned.' ) time.sleep( 0.1 ) wx.CallAfter( CopyToClipboard ) self.CallToThread( THREADWait ) def UnclosePageKeys( self, page_keys ): with self._page_key_lock: self._closed_page_keys.difference_update( page_keys ) def WaitUntilViewFree( self ): self.WaitUntilModelFree() self.WaitUntilThumbnailsFree() def WaitUntilThumbnailsFree( self ): while True: if self._view_shutdown: raise HydrusExceptions.ShutdownException( 'Application shutting down!' ) elif not self._caches[ 'thumbnail' ].DoingWork(): return else: time.sleep( 0.00001 ) def Write( self, action, *args, **kwargs ): if action == 'content_updates': self._managers[ 'undo' ].AddCommand( 'content_updates', *args, **kwargs ) return HydrusController.HydrusController.Write( self, action, *args, **kwargs )