hydrus/include/ClientController.py

1737 lines
57 KiB
Python
Raw Normal View History

2018-03-22 00:03:33 +00:00
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 )
2019-01-23 22:19:16 +00:00
from . import ClientAPI
2019-01-09 22:59:03 +00:00
from . import ClientCaches
from . import ClientData
from . import ClientDaemons
from . import ClientDefaults
2019-07-31 22:01:02 +00:00
from . import ClientDownloading
2019-05-22 22:35:06 +00:00
from . import ClientFiles
2019-01-09 22:59:03 +00:00
from . import ClientGUIMenus
2019-06-05 19:42:39 +00:00
from . import ClientGUIShortcuts
2019-01-09 22:59:03 +00:00
from . import ClientNetworking
from . import ClientNetworkingBandwidth
from . import ClientNetworkingDomain
from . import ClientNetworkingLogin
from . import ClientNetworkingSessions
from . import ClientOptions
from . import ClientPaths
from . import ClientThreading
2013-11-06 18:22:07 +00:00
import hashlib
2019-01-09 22:59:03 +00:00
from . import HydrusConstants as HC
from . import HydrusController
from . import HydrusData
from . import HydrusExceptions
from . import HydrusGlobals as HG
from . import HydrusNetworking
from . import HydrusPaths
from . import HydrusSerialisable
from . import HydrusThreading
from . import HydrusVideoHandling
from . import ClientConstants as CC
from . import ClientDB
from . import ClientGUI
from . import ClientGUIDialogs
2019-07-17 22:10:19 +00:00
from . import ClientGUIDialogsQuick
2019-01-09 22:59:03 +00:00
from . import ClientGUIScrolledPanelsManagement
from . import ClientGUITopLevelWindows
2018-01-03 22:37:30 +00:00
import gc
2015-08-19 21:48:21 +00:00
import psutil
2019-07-24 21:39:02 +00:00
import signal
2015-04-08 18:10:50 +00:00
import threading
2013-02-19 00:11:43 +00:00
import time
import traceback
2017-10-25 21:45:15 +00:00
if not HG.twisted_is_broke:
2019-08-15 00:40:48 +00:00
from twisted.internet import threads, reactor, defer
2017-10-25 21:45:15 +00:00
2019-07-24 21:39:02 +00:00
class App( wx.App ):
def __init__( self, *args, **kwargs ):
wx.App.__init__( self, *args, **kwargs )
self.Bind( wx.EVT_QUERY_END_SESSION, self.EventQueryEndSession )
self.Bind( wx.EVT_END_SESSION, self.EventEndSession )
def EventEndSession( self, event ):
HG.emergency_exit = True
HG.client_controller.gui.Exit()
2019-08-21 21:34:01 +00:00
if event.CanVeto(): # if any more time is offered, take it
event.Veto()
2019-07-24 21:39:02 +00:00
def EventQueryEndSession( self, event ):
HG.emergency_exit = True
HG.client_controller.gui.Exit()
2019-08-21 21:34:01 +00:00
if event.CanVeto(): # if any more time is offered, take it
event.Veto()
2019-07-24 21:39:02 +00:00
2015-04-01 20:44:54 +00:00
class Controller( HydrusController.HydrusController ):
2014-12-03 22:56:40 +00:00
2019-03-20 21:22:10 +00:00
def __init__( self, db_dir ):
2015-09-16 18:11:00 +00:00
2016-08-31 19:55:14 +00:00
self._last_shutdown_was_bad = False
2017-05-10 21:33:58 +00:00
self._is_booted = False
2017-07-27 00:47:13 +00:00
self._splash = None
2019-03-20 21:22:10 +00:00
HydrusController.HydrusController.__init__( self, db_dir )
2015-09-16 18:11:00 +00:00
2017-03-08 23:23:12 +00:00
self._name = 'client'
2017-05-10 21:33:58 +00:00
HG.client_controller = self
2015-09-16 18:11:00 +00:00
2016-02-03 22:12:53 +00:00
# just to set up some defaults, in case some db update expects something for an odd yaml-loading reason
2017-10-18 19:41:25 +00:00
self.options = ClientDefaults.GetClientDefaultOptions()
2019-03-27 22:01:02 +00:00
self.new_options = ClientOptions.ClientOptions()
2016-02-03 22:12:53 +00:00
2017-10-18 19:41:25 +00:00
HC.options = self.options
2016-02-03 22:12:53 +00:00
2018-05-23 21:05:06 +00:00
self._page_key_lock = threading.Lock()
2019-01-16 22:40:53 +00:00
self._thread_slots[ 'watcher_files' ] = ( 0, 15 )
self._thread_slots[ 'watcher_check' ] = ( 0, 5 )
self._thread_slots[ 'gallery_files' ] = ( 0, 15 )
self._thread_slots[ 'gallery_search' ] = ( 0, 5 )
2018-05-23 21:05:06 +00:00
self._alive_page_keys = set()
self._closed_page_keys = set()
2015-10-28 21:29:05 +00:00
self._last_mouse_position = None
2015-11-25 22:00:57 +00:00
self._menu_open = False
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
2015-09-16 18:11:00 +00:00
2015-09-02 23:16:09 +00:00
def _InitDB( self ):
2019-03-20 21:22:10 +00:00
return ClientDB.DB( self, self.db_dir, 'client' )
2015-09-02 23:16:09 +00:00
2018-06-06 21:27:02 +00:00
def _InitTempDir( self ):
2019-05-01 21:24:42 +00:00
self.temp_dir = HydrusPaths.GetTempDir()
2018-06-06 21:27:02 +00:00
2017-07-27 00:47:13 +00:00
def _DestroySplash( self ):
2019-04-17 21:51:50 +00:00
def wx_code( splash ):
if splash:
splash.Hide()
splash.DestroyLater()
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-04-17 21:51:50 +00:00
wx.CallAfter( wx_code, splash )
2017-07-27 00:47:13 +00:00
2019-02-13 22:26:43 +00:00
def _GetUPnPServices( self ):
return self.services_manager.GetServices( ( HC.LOCAL_BOORU, HC.CLIENT_API_SERVICE ) )
2017-11-08 22:07:12 +00:00
def _ReportShutdownDaemonsStatus( self ):
names = { daemon.name for daemon in self._daemons if daemon.is_alive() }
names = list( names )
names.sort()
self.pub( 'splash_set_status_subtext', ', '.join( names ) )
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
2019-01-30 22:14:54 +00:00
def CallBlockingToWX( self, win, func, *args, **kwargs ):
2015-08-26 21:18:39 +00:00
2019-01-30 22:14:54 +00:00
def wx_code( win, job_key ):
2015-08-26 21:18:39 +00:00
try:
2019-01-30 22:14:54 +00:00
if win is not None and not win:
raise HydrusExceptions.WXDeadWindowException( 'Parent Window was destroyed before wx command was called!' )
2017-01-25 22:56:55 +00:00
result = func( *args, **kwargs )
2015-08-26 21:18:39 +00:00
job_key.SetVariable( 'result', result )
2019-02-06 22:41:35 +00:00
except ( HydrusExceptions.WXDeadWindowException, HydrusExceptions.InsufficientCredentialsException, HydrusExceptions.ShutdownException ) as e:
2015-12-23 22:51:04 +00:00
job_key.SetVariable( 'error', e )
2015-08-26 21:18:39 +00:00
except Exception as e:
job_key.SetVariable( 'error', e )
2019-01-30 22:14:54 +00:00
HydrusData.Print( 'CallBlockingToWX just caught this error:' )
2015-09-02 23:16:09 +00:00
HydrusData.DebugPrint( traceback.format_exc() )
2016-07-06 21:13:15 +00:00
finally:
job_key.Finish()
2015-08-26 21:18:39 +00:00
2019-01-23 22:19:16 +00:00
job_key = ClientThreading.JobKey( cancel_on_shutdown = False )
2015-08-26 21:18:39 +00:00
job_key.Begin()
2019-01-30 22:14:54 +00:00
wx.CallAfter( wx_code, win, job_key )
2015-08-26 21:18:39 +00:00
while not job_key.IsDone():
2019-07-31 22:01:02 +00:00
if HG.model_shutdown:
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
time.sleep( 0.05 )
2016-01-06 21:17:20 +00:00
if job_key.HasVariable( 'result' ):
2016-09-28 18:48:01 +00:00
# result can be None, for wx_code that has no return variable
2016-01-06 21:17:20 +00:00
2016-09-28 18:48:01 +00:00
result = job_key.GetIfHasVariable( 'result' )
2016-01-06 21:17:20 +00:00
2016-09-28 18:48:01 +00:00
return result
2016-01-06 21:17:20 +00:00
2016-09-28 18:48:01 +00:00
error = job_key.GetIfHasVariable( 'error' )
if error is not None:
2016-01-06 21:17:20 +00:00
2016-09-28 18:48:01 +00:00
raise error
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
2018-05-16 20:09:50 +00:00
def CallLaterWXSafe( self, window, initial_delay, func, *args, **kwargs ):
2018-02-14 21:47:18 +00:00
2018-05-23 21:05:06 +00:00
job_scheduler = self._GetAppropriateJobScheduler( initial_delay )
2018-02-14 21:47:18 +00:00
call = HydrusData.Call( func, *args, **kwargs )
2018-05-23 21:05:06 +00:00
job = ClientThreading.WXAwareJob( 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
2018-05-16 20:09:50 +00:00
def CallRepeatingWXSafe( self, window, initial_delay, period, func, *args, **kwargs ):
2018-02-14 21:47:18 +00:00
2018-05-23 21:05:06 +00:00
job_scheduler = self._GetAppropriateJobScheduler( period )
2018-02-14 21:47:18 +00:00
call = HydrusData.Call( func, *args, **kwargs )
2018-05-23 21:05:06 +00:00
job = ClientThreading.WXAwareRepeatingJob( 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 sig == signal.SIGINT:
event = wx.CloseEvent( wx.wxEVT_CLOSE_WINDOW, -1 )
wx.QueueEvent( self.gui, event )
2015-09-02 23:16:09 +00:00
def CheckAlreadyRunning( self ):
2019-07-17 22:10:19 +00:00
2017-07-12 20:03:45 +00:00
while HydrusData.IsAlreadyRunning( self.db_dir, 'client' ):
2015-09-02 23:16:09 +00:00
2015-09-23 21:21:02 +00:00
self.pub( 'splash_set_status_text', 'client already running' )
2015-09-02 23:16:09 +00:00
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.'
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' )
if result != wx.ID_YES:
2015-09-02 23:16:09 +00:00
2019-07-17 22:10:19 +00:00
HG.shutting_down_due_to_already_running = True
raise HydrusExceptions.ShutdownException()
2015-09-02 23:16:09 +00:00
2019-01-30 22:14:54 +00:00
self.CallBlockingToWX( self._splash, wx_code )
2015-09-02 23:16:09 +00:00
for i in range( 10, 0, -1 ):
2017-07-12 20:03:45 +00:00
if not HydrusData.IsAlreadyRunning( self.db_dir, 'client' ):
2015-09-02 23:16:09 +00:00
break
2015-09-23 21:21:02 +00:00
self.pub( 'splash_set_status_text', 'waiting ' + str( i ) + ' seconds' )
2015-09-02 23:16:09 +00:00
time.sleep( 1 )
2015-10-28 21:29:05 +00:00
def CheckMouseIdle( self ):
mouse_position = wx.GetMousePosition()
if self._last_mouse_position is None:
self._last_mouse_position = mouse_position
elif mouse_position != self._last_mouse_position:
2016-12-14 21:19:07 +00:00
idle_before_position_update = self.CurrentlyIdle()
2015-10-28 21:29:05 +00:00
self._timestamps[ 'last_mouse_action' ] = HydrusData.GetNow()
self._last_mouse_position = mouse_position
2016-12-14 21:19:07 +00:00
idle_after_position_update = self.CurrentlyIdle()
2015-10-28 21:29:05 +00:00
2016-12-14 21:19:07 +00:00
move_knocked_us_out_of_idle = ( not idle_before_position_update ) and idle_after_position_update
if move_knocked_us_out_of_idle:
2015-10-28 21:29:05 +00:00
2018-05-23 21:05:06 +00:00
self.pub( 'set_status_bar_dirty' )
2015-10-28 21:29:05 +00:00
2018-05-23 21:05:06 +00:00
def ClosePageKeys( self, page_keys ):
with self._page_key_lock:
self._closed_page_keys.update( page_keys )
2018-01-10 22:41:51 +00:00
def CreateSplash( self ):
try:
self._splash = ClientGUI.FrameSplash( self )
except:
HydrusData.Print( 'There was an error trying to start the splash screen!' )
HydrusData.Print( traceback.format_exc() )
raise
2014-12-03 22:56:40 +00:00
def CurrentlyIdle( self ):
2019-08-21 21:34:01 +00:00
if HG.program_is_shutting_down:
return True
2017-05-10 21:33:58 +00:00
if HG.force_idle_mode:
2016-01-13 22:08:19 +00:00
2016-05-11 18:16:39 +00:00
self._idle_started = 0
2016-01-13 22:08:19 +00:00
return True
2016-02-17 22:06:47 +00:00
if not HydrusData.TimeHasPassed( self._timestamps[ 'boot' ] + 120 ):
return False
2017-10-18 19:41:25 +00:00
idle_normal = self.options[ 'idle_normal' ]
idle_period = self.options[ 'idle_period' ]
idle_mouse_period = self.options[ 'idle_mouse_period' ]
2015-10-28 21:29:05 +00:00
2015-12-02 22:32:18 +00:00
if idle_normal:
2015-08-19 21:48:21 +00:00
2016-01-13 22:08:19 +00:00
currently_idle = True
2015-12-02 22:32:18 +00:00
2016-01-13 22:08:19 +00:00
if idle_period is not None:
2015-10-28 21:29:05 +00:00
2016-01-13 22:08:19 +00:00
if not HydrusData.TimeHasPassed( self._timestamps[ 'last_user_action' ] + idle_period ):
currently_idle = False
2015-10-28 21:29:05 +00:00
2016-01-13 22:08:19 +00:00
if idle_mouse_period is not None:
2015-10-28 21:29:05 +00:00
2016-01-13 22:08:19 +00:00
if not HydrusData.TimeHasPassed( self._timestamps[ 'last_mouse_action' ] + idle_mouse_period ):
currently_idle = False
2015-10-28 21:29:05 +00:00
2015-08-19 21:48:21 +00:00
2016-01-13 22:08:19 +00:00
else:
2015-10-28 21:29:05 +00:00
2016-01-13 22:08:19 +00:00
currently_idle = False
2015-10-28 21:29:05 +00:00
2016-01-13 22:08:19 +00:00
2016-05-11 18:16:39 +00:00
turning_idle = currently_idle and not self._previously_idle
2016-01-13 22:08:19 +00:00
self._previously_idle = currently_idle
if turning_idle:
2015-10-28 21:29:05 +00:00
2016-05-11 18:16:39 +00:00
self._idle_started = HydrusData.GetNow()
2016-01-13 22:08:19 +00:00
self.pub( 'wake_daemons' )
2015-10-28 21:29:05 +00:00
2015-08-19 21:48:21 +00:00
2016-05-11 18:16:39 +00:00
if not currently_idle:
self._idle_started = None
2016-01-13 22:08:19 +00:00
return currently_idle
2015-08-26 21:18:39 +00:00
2016-05-11 18:16:39 +00:00
def CurrentlyVeryIdle( self ):
2019-08-21 21:34:01 +00:00
if HG.program_is_shutting_down:
return True
2016-05-11 18:16:39 +00:00
if self._idle_started is not None and HydrusData.TimeHasPassed( self._idle_started + 3600 ):
return True
return False
2015-08-26 21:18:39 +00:00
def DoIdleShutdownWork( self ):
2017-10-18 19:41:25 +00:00
stop_time = HydrusData.GetNow() + ( self.options[ 'idle_shutdown_max_minutes' ] * 60 )
2015-08-26 21:18:39 +00:00
2019-06-19 22:08:48 +00:00
self.MaintainDB( maintenance_mode = HC.MAINTENANCE_SHUTDOWN, stop_time = stop_time )
2015-08-26 21:18:39 +00:00
2017-10-18 19:41:25 +00:00
if not self.options[ 'pause_repo_sync' ]:
2015-08-19 21:48:21 +00:00
2017-06-28 20:23:21 +00:00
services = self.services_manager.GetServices( HC.REPOSITORIES )
2015-08-26 21:18:39 +00:00
for service in services:
if HydrusData.TimeHasPassed( stop_time ):
return
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
2019-05-22 22:35:06 +00:00
if self.new_options.GetBoolean( 'file_maintenance_on_shutdown' ):
2019-06-19 22:08:48 +00:00
self.files_maintenance_manager.DoMaintenance( maintenance_mode = HC.MAINTENANCE_SHUTDOWN, stop_time = stop_time )
2019-05-22 22:35:06 +00:00
2018-09-26 19:05:12 +00:00
self.Write( 'last_shutdown_work_time', HydrusData.GetNow() )
2014-02-19 22:37:23 +00:00
2015-08-26 21:18:39 +00:00
def Exit( self ):
2019-08-21 21:34:01 +00:00
HG.program_is_shutting_down = True
2017-05-10 21:33:58 +00:00
if HG.emergency_exit:
2015-09-02 23:16:09 +00:00
2019-07-24 21:39:02 +00:00
HydrusData.DebugPrint( 'doing fast shutdown\u2026' )
2016-01-06 21:17:20 +00:00
self.ShutdownView()
self.ShutdownModel()
2015-09-02 23:16:09 +00:00
2019-07-17 22:10:19 +00:00
if not HG.shutting_down_due_to_already_running:
HydrusData.CleanRunningFile( self.db_dir, 'client' )
2018-01-10 22:41:51 +00:00
2016-01-06 21:17:20 +00:00
else:
2015-09-02 23:16:09 +00:00
2016-01-06 21:17:20 +00:00
try:
2018-09-26 19:05:12 +00:00
last_shutdown_work_time = self.Read( 'last_shutdown_work_time' )
2019-06-19 22:08:48 +00:00
idle_shutdown_action = self.options[ 'idle_shutdown' ]
auto_shutdown_work_ok_by_user = idle_shutdown_action in ( CC.IDLE_ON_SHUTDOWN, CC.IDLE_ON_SHUTDOWN_ASK_FIRST )
2018-09-26 19:05:12 +00:00
shutdown_work_period = self.new_options.GetInteger( 'shutdown_work_period' )
2019-06-19 22:08:48 +00:00
auto_shutdown_work_due = HydrusData.TimeHasPassed( last_shutdown_work_time + shutdown_work_period )
2018-09-26 19:05:12 +00:00
2019-06-19 22:08:48 +00:00
manual_shutdown_work_not_already_set = not HG.do_idle_shutdown_work
we_can_turn_on_auto_shutdown_work = auto_shutdown_work_ok_by_user and auto_shutdown_work_due and manual_shutdown_work_not_already_set
2016-01-06 21:17:20 +00:00
2019-06-19 22:08:48 +00:00
if we_can_turn_on_auto_shutdown_work:
2016-01-13 22:08:19 +00:00
2017-10-18 19:41:25 +00:00
idle_shutdown_max_minutes = self.options[ 'idle_shutdown_max_minutes' ]
2016-08-10 19:04:08 +00:00
time_to_stop = HydrusData.GetNow() + ( idle_shutdown_max_minutes * 60 )
2018-09-12 21:36:26 +00:00
work_to_do = self.GetIdleShutdownWorkDue( time_to_stop )
if len( work_to_do ) > 0:
2016-01-13 22:08:19 +00:00
if idle_shutdown_action == CC.IDLE_ON_SHUTDOWN_ASK_FIRST:
2018-07-04 20:48:28 +00:00
text = 'Is now a good time for the client to do up to ' + HydrusData.ToHumanInt( idle_shutdown_max_minutes ) + ' minutes\' maintenance work? (Will auto-no in 15 seconds)'
2018-09-12 21:36:26 +00:00
text += os.linesep * 2
text += 'The outstanding jobs appear to be:'
text += os.linesep * 2
text += os.linesep.join( work_to_do )
2016-01-13 22:08:19 +00:00
with ClientGUIDialogs.DialogYesNo( self._splash, text, title = 'Maintenance is due' ) as dlg_yn:
2018-02-14 21:47:18 +00:00
job = self.CallLaterWXSafe( dlg_yn, 15, dlg_yn.EndModal, wx.ID_NO )
2017-07-19 21:21:41 +00:00
2018-02-14 21:47:18 +00:00
try:
2016-01-13 22:08:19 +00:00
2018-02-14 21:47:18 +00:00
if dlg_yn.ShowModal() == wx.ID_YES:
HG.do_idle_shutdown_work = True
2018-09-26 19:05:12 +00:00
else:
2019-01-23 22:19:16 +00:00
# if they said no, don't keep asking
2018-09-26 19:05:12 +00:00
self.Write( 'last_shutdown_work_time', HydrusData.GetNow() )
2018-02-14 21:47:18 +00:00
finally:
job.Cancel()
2016-01-13 22:08:19 +00:00
2017-07-19 21:21:41 +00:00
2016-01-13 22:08:19 +00:00
else:
2017-05-10 21:33:58 +00:00
HG.do_idle_shutdown_work = True
2016-01-13 22:08:19 +00:00
2016-01-06 21:17:20 +00:00
2019-06-19 22:08:48 +00:00
if HG.do_idle_shutdown_work:
self._splash.MakeCancelShutdownButton()
2017-08-09 21:33:51 +00:00
self.CallToThreadLongRunning( self.THREADExitEverything )
2016-01-06 21:17:20 +00:00
2016-01-13 22:08:19 +00:00
except:
2016-01-06 21:17:20 +00:00
2017-07-27 00:47:13 +00:00
self._DestroySplash()
2016-01-06 21:17:20 +00:00
2016-01-13 22:08:19 +00:00
HydrusData.DebugPrint( traceback.format_exc() )
2017-05-10 21:33:58 +00:00
HG.emergency_exit = True
2016-01-13 22:08:19 +00:00
self.Exit()
2016-01-06 21:17:20 +00:00
2015-08-26 21:18:39 +00:00
2013-02-19 00:11:43 +00:00
2017-01-25 22:56:55 +00:00
def GetApp( self ):
return self._app
2017-12-13 22:33:07 +00:00
def GetClipboardText( self ):
if wx.TheClipboard.Open():
2019-06-26 21:27:18 +00:00
try:
2019-06-19 22:08:48 +00:00
2019-06-26 21:27:18 +00:00
if not wx.TheClipboard.IsSupported( wx.DataFormat( wx.DF_TEXT ) ):
raise HydrusExceptions.DataMissing( 'No text on the clipboard!' )
data = wx.TextDataObject()
success = wx.TheClipboard.GetData( data )
finally:
wx.TheClipboard.Close()
2019-06-19 22:08:48 +00:00
2017-12-13 22:33:07 +00:00
2019-06-19 22:08:48 +00:00
if not success:
raise HydrusExceptions.DataMissing( 'No text on the clipboard!' )
2017-12-13 22:33:07 +00:00
text = data.GetText()
return text
else:
2019-06-19 22:08:48 +00:00
raise HydrusExceptions.DataMissing( 'I could not get permission to access the clipboard.' )
2017-12-13 22:33:07 +00:00
2017-04-19 20:58:30 +00:00
def GetCommandFromShortcut( self, shortcut_names, shortcut ):
2019-06-05 19:42:39 +00:00
return self.shortcuts_manager.GetCommand( shortcut_names, shortcut )
2017-04-19 20:58:30 +00:00
2017-06-28 20:23:21 +00:00
def GetGUI( self ):
2017-07-19 21:21:41 +00:00
return self.gui
2017-06-28 20:23:21 +00:00
2013-10-09 18:13:42 +00:00
2018-09-12 21:36:26 +00:00
def GetIdleShutdownWorkDue( self, time_to_stop ):
work_to_do = []
work_to_do.extend( self.Read( 'maintenance_due', time_to_stop ) )
services = self.services_manager.GetServices( HC.REPOSITORIES )
for service in services:
if service.CanDoIdleShutdownWork():
2018-09-19 21:54:51 +00:00
work_to_do.append( service.GetName() + ' repository processing' )
2018-09-12 21:36:26 +00:00
2019-05-22 22:35:06 +00:00
if self.new_options.GetBoolean( 'file_maintenance_on_shutdown' ):
work_to_do.extend( self.files_maintenance_manager.GetIdleShutdownWorkDue() )
2018-09-12 21:36:26 +00:00
return work_to_do
2015-10-14 21:02:25 +00:00
def GetNewOptions( self ):
2017-10-18 19:41:25 +00:00
return self.new_options
2015-10-14 21:02:25 +00:00
2017-04-05 21:16:40 +00:00
def InitClientFilesManager( self ):
2018-05-16 20:09:50 +00:00
def wx_code( missing_locations ):
2017-04-05 21:16:40 +00:00
2017-04-19 20:58:30 +00:00
with ClientGUITopLevelWindows.DialogManage( None, 'repair file system' ) as dlg:
2017-04-05 21:16:40 +00:00
panel = ClientGUIScrolledPanelsManagement.RepairFileSystemPanel( dlg, missing_locations )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
2019-05-22 22:35:06 +00:00
self.client_files_manager = ClientFiles.ClientFilesManager( self )
2017-04-05 21:16:40 +00:00
2017-06-28 20:23:21 +00:00
missing_locations = self.client_files_manager.GetMissing()
2017-04-05 21:16:40 +00:00
else:
2019-02-06 22:41:35 +00:00
raise HydrusExceptions.ShutdownException( 'File system failed, user chose to quit.' )
2017-04-05 21:16:40 +00:00
2018-05-16 20:09:50 +00:00
return missing_locations
2019-05-22 22:35:06 +00:00
self.client_files_manager = ClientFiles.ClientFilesManager( self )
self.files_maintenance_manager = ClientFiles.FilesMaintenanceManager( self )
2018-05-16 20:09:50 +00:00
missing_locations = self.client_files_manager.GetMissing()
while len( missing_locations ) > 0:
2019-01-30 22:14:54 +00:00
missing_locations = self.CallBlockingToWX( self._splash, wx_code, missing_locations )
2018-05-16 20:09:50 +00:00
2017-04-05 21:16:40 +00:00
2015-09-02 23:16:09 +00:00
def InitModel( self ):
2014-08-06 20:29:17 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_title_text', 'booting db\u2026' )
2014-08-06 20:29:17 +00:00
2015-09-02 23:16:09 +00:00
HydrusController.HydrusController.InitModel( self )
2015-08-26 21:18:39 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_text', 'initialising managers' )
2017-11-08 22:07:12 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'services' )
2017-11-08 22:07:12 +00:00
2017-06-28 20:23:21 +00:00
self.services_manager = ClientCaches.ServicesManager( self )
2017-05-10 21:33:58 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'options' )
2017-11-08 22:07:12 +00:00
2017-10-18 19:41:25 +00:00
self.options = self.Read( 'options' )
self.new_options = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS )
2015-08-26 21:18:39 +00:00
2017-10-18 19:41:25 +00:00
HC.options = self.options
2015-10-21 21:53:10 +00:00
2017-10-18 19:41:25 +00:00
if self.new_options.GetBoolean( 'use_system_ffmpeg' ):
2016-12-07 22:12:52 +00:00
if HydrusVideoHandling.FFMPEG_PATH.startswith( HC.BIN_DIR ):
HydrusVideoHandling.FFMPEG_PATH = os.path.basename( HydrusVideoHandling.FFMPEG_PATH )
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'client files' )
2017-11-08 22:07:12 +00:00
2017-04-05 21:16:40 +00:00
self.InitClientFilesManager()
2015-12-02 22:32:18 +00:00
2017-07-05 21:09:28 +00:00
#
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'network' )
2017-11-08 22:07:12 +00:00
2018-04-25 22:07:52 +00:00
self.parsing_cache = ClientCaches.ParsingCache()
2019-01-30 22:14:54 +00:00
2019-01-23 22:19:16 +00:00
client_api_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_API_MANAGER )
if client_api_manager is None:
client_api_manager = ClientAPI.APIManager()
client_api_manager._dirty = True
2019-02-27 23:03:30 +00:00
wx.SafeShowMessage( 'Problem loading object', 'Your client api manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2019-01-23 22:19:16 +00:00
self.client_api_manager = client_api_manager
2018-04-25 22:07:52 +00:00
2017-07-05 21:09:28 +00:00
bandwidth_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER )
2017-08-16 21:58:06 +00:00
if bandwidth_manager is None:
2018-04-18 22:10:15 +00:00
bandwidth_manager = ClientNetworkingBandwidth.NetworkBandwidthManager()
2017-08-16 21:58:06 +00:00
ClientDefaults.SetDefaultBandwidthManagerRules( bandwidth_manager )
2017-10-11 17:38:14 +00:00
bandwidth_manager._dirty = True
2019-02-27 23:03:30 +00:00
wx.SafeShowMessage( 'Problem loading object', 'Your bandwidth manager was missing on boot! I have recreated a new empty one with default rules. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2017-08-16 21:58:06 +00:00
2017-07-05 21:09:28 +00:00
session_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_SESSION_MANAGER )
2017-08-16 21:58:06 +00:00
if session_manager is None:
2018-04-18 22:10:15 +00:00
session_manager = ClientNetworkingSessions.NetworkSessionManager()
2017-08-16 21:58:06 +00:00
2017-10-11 17:38:14 +00:00
session_manager._dirty = True
2019-02-27 23:03:30 +00:00
wx.SafeShowMessage( 'Problem loading object', 'Your session manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2017-08-16 21:58:06 +00:00
2017-10-11 17:38:14 +00:00
domain_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER )
if domain_manager is None:
2017-10-18 19:41:25 +00:00
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
2017-10-11 17:38:14 +00:00
2018-01-10 22:41:51 +00:00
ClientDefaults.SetDefaultDomainManagerData( domain_manager )
2017-10-11 17:38:14 +00:00
domain_manager._dirty = True
2019-02-27 23:03:30 +00:00
wx.SafeShowMessage( 'Problem loading object', 'Your domain manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2017-10-11 17:38:14 +00:00
2017-10-04 17:51:58 +00:00
2017-12-06 22:06:56 +00:00
domain_manager.Initialise()
2018-10-24 21:34:02 +00:00
login_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_LOGIN_MANAGER )
if login_manager is None:
login_manager = ClientNetworkingLogin.NetworkLoginManager()
ClientDefaults.SetDefaultLoginManagerScripts( login_manager )
login_manager._dirty = True
2019-02-27 23:03:30 +00:00
wx.SafeShowMessage( 'Problem loading object', 'Your login manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
2018-10-24 21:34:02 +00:00
login_manager.Initialise()
2017-07-05 21:09:28 +00:00
2017-10-04 17:51:58 +00:00
self.network_engine = ClientNetworking.NetworkEngine( self, bandwidth_manager, session_manager, domain_manager, login_manager )
2017-07-05 21:09:28 +00:00
2017-08-09 21:33:51 +00:00
self.CallToThreadLongRunning( self.network_engine.MainLoop )
2017-07-05 21:09:28 +00:00
#
2019-07-31 22:01:02 +00:00
self.quick_download_manager = ClientDownloading.QuickDownloadManager( self )
self.CallToThreadLongRunning( self.quick_download_manager.MainLoop )
#
2019-06-05 19:42:39 +00:00
self.shortcuts_manager = ClientGUIShortcuts.ShortcutsManager( self )
2017-04-19 20:58:30 +00:00
2017-12-06 22:06:56 +00:00
self.local_booru_manager = ClientCaches.LocalBooruCache( self )
2017-11-08 22:07:12 +00:00
2018-12-05 22:35:30 +00:00
self.file_viewing_stats_manager = ClientCaches.FileViewingStatsManager( self )
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'tag censorship' )
2017-11-08 22:07:12 +00:00
2019-04-17 21:51:50 +00:00
self.tag_censorship_manager = ClientCaches.TagCensorshipManager( self )
2017-11-08 22:07:12 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'tag siblings' )
2017-11-08 22:07:12 +00:00
2019-04-17 21:51:50 +00:00
self.tag_siblings_manager = ClientCaches.TagSiblingsManager( self )
2017-11-08 22:07:12 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'tag parents' )
2017-11-08 22:07:12 +00:00
2019-04-17 21:51:50 +00:00
self.tag_parents_manager = ClientCaches.TagParentsManager( self )
2015-11-25 22:00:57 +00:00
self._managers[ 'undo' ] = ClientCaches.UndoManager( self )
2014-08-06 20:29:17 +00:00
2015-09-02 23:16:09 +00:00
def wx_code():
2016-08-17 20:07:22 +00:00
self._caches[ 'images' ] = ClientCaches.RenderedImageCache( self )
2015-11-25 22:00:57 +00:00
self._caches[ 'thumbnail' ] = ClientCaches.ThumbnailCache( self )
2018-11-14 23:10:55 +00:00
self.bitmap_manager = ClientCaches.BitmapManager( self )
2015-09-02 23:16:09 +00:00
CC.GlobalBMPs.STATICInitialise()
2014-08-06 20:29:17 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_status_subtext', 'image caches' )
2017-11-08 22:07:12 +00:00
2019-01-30 22:14:54 +00:00
self.CallBlockingToWX( self._splash, wx_code )
2014-08-06 20:29:17 +00:00
2017-12-13 22:33:07 +00:00
self.sub( self, 'ToClipboard', 'clipboard' )
2014-08-06 20:29:17 +00:00
2015-09-02 23:16:09 +00:00
def InitView( self ):
2014-08-06 20:29:17 +00:00
2017-10-18 19:41:25 +00:00
if self.options[ 'password' ] is not None:
2015-09-02 23:16:09 +00:00
2015-09-23 21:21:02 +00:00
self.pub( 'splash_set_status_text', 'waiting for password' )
2015-09-02 23:16:09 +00:00
def wx_code_password():
while True:
with wx.PasswordEntryDialog( self._splash, 'Enter your password', 'Enter password' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
2019-01-09 22:59:03 +00:00
password_bytes = bytes( dlg.GetValue(), 'utf-8' )
2016-11-23 20:37:53 +00:00
2019-01-09 22:59:03 +00:00
if hashlib.sha256( password_bytes ).digest() == self.options[ 'password' ]:
break
2016-11-23 20:37:53 +00:00
else:
2019-02-06 22:41:35 +00:00
raise HydrusExceptions.InsufficientCredentialsException( 'Bad password check' )
2015-09-02 23:16:09 +00:00
2019-01-30 22:14:54 +00:00
self.CallBlockingToWX( self._splash, wx_code_password )
2015-09-02 23:16:09 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_title_text', 'booting gui\u2026' )
2015-09-02 23:16:09 +00:00
def wx_code_gui():
2017-07-19 21:21:41 +00:00
self.gui = ClientGUI.FrameGUI( self )
2015-09-02 23:16:09 +00:00
self.ResetIdleTimer()
2019-01-30 22:14:54 +00:00
self.CallBlockingToWX( self._splash, wx_code_gui )
2015-09-02 23:16:09 +00:00
2016-08-24 18:36:56 +00:00
# ShowText will now popup as a message, as popup message manager has overwritten the hooks
2015-09-02 23:16:09 +00:00
HydrusController.HydrusController.InitView( self )
2019-08-15 00:40:48 +00:00
self._service_keys_to_connected_ports = {}
2014-08-06 20:29:17 +00:00
2019-08-15 00:40:48 +00:00
self.RestartClientServerServices()
2014-08-06 20:29:17 +00:00
2019-03-20 21:22:10 +00:00
if not HG.no_daemons:
2016-01-20 23:57:33 +00:00
2018-11-28 22:31:04 +00:00
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'SynchroniseSubscriptions', ClientDaemons.DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ), period = 4 * 3600, init_wait = 60, pre_call_wait = 3 ) )
2017-01-11 22:31:30 +00:00
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'MaintainTrash', ClientDaemons.DAEMONMaintainTrash, init_wait = 120 ) )
2019-05-22 22:35:06 +00:00
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'SynchroniseRepositories', ClientDaemons.DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions', 'wake_idle_workers' ), period = 4 * 3600, pre_call_wait = 1 ) )
2016-12-14 21:19:07 +00:00
2015-09-02 23:16:09 +00:00
2019-02-13 22:26:43 +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
2019-06-19 22:08:48 +00:00
job = self.CallRepeating( 60.0, 300.0, self.files_maintenance_manager.DoMaintenance, maintenance_mode = HC.MAINTENANCE_IDLE )
2019-05-22 22:35:06 +00:00
job.ShouldDelayOnWakeup( True )
job.WakeOnPubSub( 'wake_idle_workers' )
self._daemon_jobs[ 'maintain_files' ] = job
2019-02-13 22:26:43 +00:00
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
job = self.CallRepeating( 0.0, 30.0, self.SaveDirtyObjects )
job.WakeOnPubSub( 'important_dirt_to_clean' )
self._daemon_jobs[ 'save_dirty_objects' ] = job
job = self.CallRepeating( 5.0, 3600.0, self.SynchroniseAccounts )
job.ShouldDelayOnWakeup( True )
job.WakeOnPubSub( 'notify_unknown_accounts' )
self._daemon_jobs[ 'synchronise_accounts' ] = job
job = self.CallRepeatingWXSafe( self, 10.0, 10.0, self.CheckMouseIdle )
self._daemon_jobs[ 'check_mouse_idle' ] = job
2018-02-21 21:59:37 +00:00
2017-07-12 20:03:45 +00:00
if self.db.IsFirstStart():
2016-08-24 18:36:56 +00:00
message = 'Hi, this looks like the first time you have started the hydrus client.'
message += os.linesep * 2
2018-06-27 19:27:05 +00:00
message += 'Don\'t forget to check out the help if you haven\'t already--it has an extensive \'getting started\' section.'
2016-08-24 18:36:56 +00:00
message += os.linesep * 2
2016-10-19 20:02:56 +00:00
message += 'To dismiss popup messages like this, right-click them.'
2016-08-24 18:36:56 +00:00
HydrusData.ShowText( message )
2017-07-12 20:03:45 +00:00
if self.db.IsDBUpdated():
2016-08-24 18:36:56 +00:00
HydrusData.ShowText( 'The client has updated to version ' + str( HC.SOFTWARE_VERSION ) + '!' )
2015-06-03 21:05:13 +00:00
2017-07-12 20:03:45 +00:00
for message in self.db.GetInitialMessages():
2016-12-21 22:30:54 +00:00
HydrusData.ShowText( message )
2014-08-06 20:29:17 +00:00
2017-05-10 21:33:58 +00:00
def IsBooted( self ):
return self._is_booted
2016-08-31 19:55:14 +00:00
def LastShutdownWasBad( self ):
return self._last_shutdown_was_bad
2019-06-19 22:08:48 +00:00
def MaintainDB( self, maintenance_mode = HC.MAINTENANCE_IDLE, stop_time = None ):
if maintenance_mode == HC.MAINTENANCE_IDLE and not self.GoodTimeToStartBackgroundWork():
return
2019-02-27 23:03:30 +00:00
2019-06-19 22:08:48 +00:00
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
2019-02-27 23:03:30 +00:00
return
2013-02-19 00:11:43 +00:00
2019-07-31 22:01:02 +00:00
tree_stop_time = stop_time
if tree_stop_time is None:
2017-01-25 22:56:55 +00:00
2019-07-31 22:01:02 +00:00
tree_stop_time = HydrusData.GetNow() + 30
2017-01-25 22:56:55 +00:00
2019-07-31 22:01:02 +00:00
self.WriteSynchronous( 'maintain_similar_files_tree', stop_time = tree_stop_time )
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
2017-01-25 22:56:55 +00:00
2019-07-31 22:01:02 +00:00
return
2017-01-25 22:56:55 +00:00
2019-07-31 22:01:02 +00:00
if self.new_options.GetBoolean( 'maintain_similar_files_duplicate_pairs_during_idle' ):
2019-06-19 22:08:48 +00:00
2017-10-18 19:41:25 +00:00
search_distance = self.new_options.GetInteger( 'similar_files_duplicate_pairs_search_distance' )
2017-01-25 22:56:55 +00:00
search_stop_time = stop_time
if search_stop_time is None:
search_stop_time = HydrusData.GetNow() + 60
2019-07-31 22:01:02 +00:00
self.WriteSynchronous( 'maintain_similar_files_search_for_potential_duplicates', search_distance, stop_time = search_stop_time )
2018-06-20 20:20:22 +00:00
2019-06-19 22:08:48 +00:00
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
2017-03-29 19:39:34 +00:00
2019-06-19 22:08:48 +00:00
return
2017-03-29 19:39:34 +00:00
2016-01-06 21:17:20 +00:00
2019-06-19 22:08:48 +00:00
self.WriteSynchronous( 'vacuum', maintenance_mode = maintenance_mode, stop_time = stop_time )
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
2017-03-29 19:39:34 +00:00
2019-06-19 22:08:48 +00:00
return
2017-03-29 19:39:34 +00:00
2015-08-26 21:18:39 +00:00
2019-06-19 22:08:48 +00:00
self.WriteSynchronous( 'analyze', maintenance_mode = maintenance_mode, stop_time = stop_time )
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
return
2015-08-26 21:18:39 +00:00
2019-06-19 22:08:48 +00:00
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:
2014-12-17 22:35:12 +00:00
2019-06-19 22:08:48 +00:00
self.pub( 'splash_set_status_subtext', service.GetName() )
2016-05-11 18:16:39 +00:00
2019-06-19 22:08:48 +00:00
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
2016-05-11 18:16:39 +00:00
2019-06-19 22:08:48 +00:00
if self.ShouldStopThisWork( maintenance_mode, stop_time = stop_time ):
2016-05-11 18:16:39 +00:00
2019-06-19 22:08:48 +00:00
return
2016-05-11 18:16:39 +00:00
2019-06-19 22:08:48 +00:00
self._timestamps[ 'last_service_info_cache_fatten' ] = HydrusData.GetNow()
2014-08-06 20:29:17 +00:00
2015-08-26 21:18:39 +00:00
2018-04-25 22:07:52 +00:00
def MaintainMemoryFast( self ):
HydrusController.HydrusController.MaintainMemoryFast( self )
self.parsing_cache.CleanCache()
2017-07-05 21:09:28 +00:00
def MaintainMemorySlow( self ):
2015-08-26 21:18:39 +00:00
2017-07-05 21:09:28 +00:00
HydrusController.HydrusController.MaintainMemorySlow( self )
2015-08-26 21:18:39 +00:00
2017-05-10 21:33:58 +00:00
if HydrusData.TimeHasPassed( self._timestamps[ 'last_page_change' ] + 30 * 60 ):
2018-01-10 22:41:51 +00:00
self.pub( 'delete_old_closed_pages' )
2015-08-26 21:18:39 +00:00
self._timestamps[ 'last_page_change' ] = HydrusData.GetNow()
2017-10-18 19:41:25 +00:00
disk_cache_maintenance_mb = self.new_options.GetNoneableInteger( 'disk_cache_maintenance_mb' )
2017-05-10 21:33:58 +00:00
2019-07-31 22:01:02 +00:00
if disk_cache_maintenance_mb is not None and not HG.view_shutdown:
2015-08-26 21:18:39 +00:00
2019-01-23 22:19:16 +00:00
cache_period = 3600
disk_cache_stop_time = HydrusData.GetNow() + 2
2015-08-26 21:18:39 +00:00
2017-09-06 20:18:20 +00:00
if HydrusData.TimeHasPassed( self._timestamps[ 'last_disk_cache_population' ] + cache_period ):
self.Read( 'load_into_disk_cache', stop_time = disk_cache_stop_time, caller_limit = disk_cache_maintenance_mb * 1024 * 1024 )
self._timestamps[ 'last_disk_cache_population' ] = HydrusData.GetNow()
2015-08-26 21:18:39 +00:00
2019-05-29 21:34:43 +00:00
def MenubarMenuIsOpen( self ):
self._menu_open = True
def MenubarMenuIsClosed( self ):
self._menu_open = False
2015-11-25 22:00:57 +00:00
def MenuIsOpen( self ):
return self._menu_open
2018-05-23 21:05:06 +00:00
def PageAlive( self, page_key ):
2015-09-16 18:11:00 +00:00
2018-05-23 21:05:06 +00:00
with self._page_key_lock:
2016-02-17 22:06:47 +00:00
2018-05-23 21:05:06 +00:00
return page_key in self._alive_page_keys
2016-02-17 22:06:47 +00:00
2015-09-16 18:11:00 +00:00
2017-05-03 21:33:48 +00:00
def PageClosedButNotDestroyed( self, page_key ):
2015-10-07 21:56:22 +00:00
2018-05-23 21:05:06 +00:00
with self._page_key_lock:
2017-06-21 21:15:59 +00:00
2018-05-23 21:05:06 +00:00
return page_key in self._closed_page_keys
2017-06-21 21:15:59 +00:00
2015-10-07 21:56:22 +00:00
2015-11-25 22:00:57 +00:00
def PopupMenu( self, window, menu ):
if menu.GetMenuItemCount() > 0:
self._menu_open = True
window.PopupMenu( menu )
self._menu_open = False
2018-02-21 21:59:37 +00:00
ClientGUIMenus.DestroyMenu( window, menu )
2015-11-25 22:00:57 +00:00
2013-02-19 00:11:43 +00:00
def PrepStringForDisplay( self, text ):
2017-08-02 21:32:54 +00:00
return text.lower()
2017-07-27 00:47:13 +00:00
def ProcessPubSub( self ):
2019-01-30 22:14:54 +00:00
self.CallBlockingToWX( None, self._pubsub.Process )
2013-02-19 00:11:43 +00:00
2017-03-02 02:14:56 +00:00
def RefreshServices( self ):
2017-06-28 20:23:21 +00:00
self.services_manager.RefreshServices()
2017-03-02 02:14:56 +00:00
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 )
2015-08-26 21:18:39 +00:00
def ResetPageChangeTimer( self ):
self._timestamps[ 'last_page_change' ] = HydrusData.GetNow()
2019-08-15 00:40:48 +00:00
def RestartClientServerServices( self ):
2014-07-09 22:15:14 +00:00
2019-08-15 00:40:48 +00:00
services = [ self.services_manager.GetService( service_key ) for service_key in ( CC.LOCAL_BOORU_SERVICE_KEY, CC.CLIENT_API_SERVICE_KEY ) ]
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
services = [ service for service in services if service.GetPort() is not None ]
2014-07-09 22:15:14 +00:00
2019-08-15 00:40:48 +00:00
self.CallToThread( self.SetRunningTwistedServices, services )
2013-09-25 20:20:10 +00:00
2014-01-08 18:40:02 +00:00
def RestoreDatabase( self ):
2017-07-12 20:03:45 +00:00
restore_intro = ''
2017-07-19 21:21:41 +00:00
with wx.DirDialog( self.gui, 'Select backup location.' ) as dlg:
2014-01-08 18:40:02 +00:00
if dlg.ShowModal() == wx.ID_OK:
2019-01-09 22:59:03 +00:00
path = dlg.GetPath()
2014-01-08 18:40:02 +00:00
2014-12-10 22:02:39 +00:00
text = 'Are you sure you want to restore a backup from "' + path + '"?'
text += os.linesep * 2
text += 'Everything in your current database will be deleted!'
text += os.linesep * 2
text += 'The gui will shut down, and then it will take a while to complete the restore. Once it is done, the client will restart.'
2014-01-08 18:40:02 +00:00
2017-07-19 21:21:41 +00:00
with ClientGUIDialogs.DialogYesNo( self.gui, text ) as dlg_yn:
2014-01-08 18:40:02 +00:00
if dlg_yn.ShowModal() == wx.ID_YES:
2015-06-17 20:01:41 +00:00
def THREADRestart():
2017-07-12 20:03:45 +00:00
while not self.db.LoopIsFinished():
2015-12-16 22:41:06 +00:00
time.sleep( 0.1 )
2015-06-17 20:01:41 +00:00
2017-07-12 20:03:45 +00:00
self.db.RestoreBackup( path )
2015-06-17 20:01:41 +00:00
2017-05-10 21:33:58 +00:00
while not HG.shutdown_complete:
2015-12-16 22:41:06 +00:00
time.sleep( 0.1 )
2015-06-17 20:01:41 +00:00
2015-12-16 22:41:06 +00:00
HydrusData.RestartProcess()
2015-06-17 20:01:41 +00:00
2014-01-08 18:40:02 +00:00
2017-08-09 21:33:51 +00:00
self.CallToThreadLongRunning( THREADRestart )
2015-09-02 23:16:09 +00:00
2019-02-27 23:03:30 +00:00
wx.CallAfter( self.gui.Exit )
2015-09-02 23:16:09 +00:00
def Run( self ):
2019-07-24 21:39:02 +00:00
self._app = App()
2015-09-02 23:16:09 +00:00
2016-09-07 20:01:05 +00:00
self._app.locale = wx.Locale( wx.LANGUAGE_DEFAULT ) # Very important to init this here and keep it non garbage collected
2016-08-24 18:36:56 +00:00
2018-01-24 23:09:42 +00:00
# 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
2018-01-10 22:41:51 +00:00
2019-01-09 22:59:03 +00:00
HydrusData.Print( 'booting controller\u2026' )
2015-11-18 22:44:07 +00:00
2017-11-22 21:03:07 +00:00
self.frame_icon = wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus_32_non-transparent.png' ), wx.BITMAP_TYPE_PNG )
2018-01-10 22:41:51 +00:00
self.CreateSplash()
2015-09-02 23:16:09 +00:00
2019-07-24 21:39:02 +00:00
signal.signal( signal.SIGINT, self.CatchSignal )
2017-08-09 21:33:51 +00:00
self.CallToThreadLongRunning( self.THREADBootEverything )
2015-09-02 23:16:09 +00:00
self._app.MainLoop()
2019-01-23 22:19:16 +00:00
HydrusData.DebugPrint( 'shutting down controller\u2026' )
2015-09-02 23:16:09 +00:00
2017-03-02 02:14:56 +00:00
def SaveDirtyObjects( self ):
2017-05-10 21:33:58 +00:00
with HG.dirty_object_lock:
2017-03-02 02:14:56 +00:00
2017-06-28 20:23:21 +00:00
dirty_services = [ service for service in self.services_manager.GetServices() if service.IsDirty() ]
2017-03-02 02:14:56 +00:00
if len( dirty_services ) > 0:
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():
self.WriteSynchronous( 'serialisable', self.client_api_manager )
self.client_api_manager.SetClean()
2019-01-30 22:14:54 +00:00
2017-07-05 21:09:28 +00:00
if self.network_engine.bandwidth_manager.IsDirty():
self.WriteSynchronous( 'serialisable', self.network_engine.bandwidth_manager )
self.network_engine.bandwidth_manager.SetClean()
2017-10-11 17:38:14 +00:00
if self.network_engine.domain_manager.IsDirty():
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():
self.WriteSynchronous( 'serialisable', self.network_engine.login_manager )
self.network_engine.login_manager.SetClean()
2017-07-05 21:09:28 +00:00
if self.network_engine.session_manager.IsDirty():
self.WriteSynchronous( 'serialisable', self.network_engine.session_manager )
self.network_engine.session_manager.SetClean()
2017-03-02 02:14:56 +00:00
2019-08-15 00:40:48 +00:00
def SetRunningTwistedServices( self, services ):
def TWISTEDDoIt():
def StartServices( *args, **kwargs ):
HydrusData.Print( 'starting services\u2026' )
for service in services:
service_key = service.GetServiceKey()
service_type = service.GetServiceType()
name = service.GetName()
port = service.GetPort()
allow_non_local_connections = service.AllowsNonLocalConnections()
if port is None:
continue
try:
from . import ClientLocalServer
if service_type == HC.LOCAL_BOORU:
http_factory = ClientLocalServer.HydrusServiceBooru( service, allow_non_local_connections = allow_non_local_connections )
elif service_type == HC.CLIENT_API_SERVICE:
http_factory = ClientLocalServer.HydrusServiceClientAPI( service, allow_non_local_connections = allow_non_local_connections )
self._service_keys_to_connected_ports[ service_key ] = reactor.listenTCP( port, http_factory )
if not HydrusNetworking.LocalPortInUse( port ):
HydrusData.ShowText( 'Tried to bind port {} for "{}" but it failed.'.format( port, name ) )
except Exception as e:
HydrusData.ShowText( 'Could not start "{}":'.format( name ) )
HydrusData.ShowException( e )
HydrusData.Print( 'services started' )
if len( self._service_keys_to_connected_ports ) > 0:
HydrusData.Print( 'stopping services\u2026' )
deferreds = []
for port in self._service_keys_to_connected_ports.values():
deferred = defer.maybeDeferred( port.stopListening )
deferreds.append( deferred )
self._service_keys_to_connected_ports = {}
deferred = defer.DeferredList( deferreds )
if len( services ) > 0:
deferred.addCallback( StartServices )
elif len( services ) > 0:
StartServices()
if HG.twisted_is_broke:
if True in ( service.GetPort() is not None for service in services ):
HydrusData.ShowText( 'Twisted failed to import, so could not start the local booru/client api! Please contact hydrus dev!' )
else:
threads.blockingCallFromThread( reactor, TWISTEDDoIt )
2017-03-02 02:14:56 +00:00
def SetServices( self, services ):
2017-05-10 21:33:58 +00:00
with HG.dirty_object_lock:
2017-03-02 02:14:56 +00:00
2019-02-13 22:26:43 +00:00
upnp_services = [ service for service in services if service.GetServiceType() in ( HC.LOCAL_BOORU, HC.CLIENT_API_SERVICE ) ]
self.CallToThread( self.services_upnp_manager.SetServices, upnp_services )
2017-03-02 02:14:56 +00:00
self.WriteSynchronous( 'update_services', services )
2017-06-28 20:23:21 +00:00
self.services_manager.RefreshServices()
2017-03-02 02:14:56 +00:00
2019-08-15 00:40:48 +00:00
self.RestartClientServerServices()
2017-03-02 02:14:56 +00:00
2017-07-05 21:09:28 +00:00
def ShutdownModel( self ):
2019-07-31 22:01:02 +00:00
self.file_viewing_stats_manager.Flush()
self.SaveDirtyObjects()
2017-07-05 21:09:28 +00:00
HydrusController.HydrusController.ShutdownModel( self )
2015-09-02 23:16:09 +00:00
def ShutdownView( self ):
2017-05-10 21:33:58 +00:00
if not HG.emergency_exit:
2015-09-02 23:16:09 +00:00
2016-01-06 21:17:20 +00:00
self.pub( 'splash_set_status_text', 'waiting for daemons to exit' )
2015-09-02 23:16:09 +00:00
2016-01-06 21:17:20 +00:00
self._ShutdownDaemons()
2017-05-10 21:33:58 +00:00
if HG.do_idle_shutdown_work:
2015-09-02 23:16:09 +00:00
2017-03-29 19:39:34 +00:00
try:
self.DoIdleShutdownWork()
except:
ClientData.ReportShutdownException()
2015-09-02 23:16:09 +00:00
2019-08-21 21:34:01 +00:00
self.SetRunningTwistedServices( [] )
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 ):
services = self.services_manager.GetServices( HC.RESTRICTED_SERVICES )
for service in services:
if HydrusThreading.IsThreadShuttingDown():
return
service.SyncAccount()
2015-08-26 21:18:39 +00:00
def SystemBusy( self ):
2016-01-13 22:08:19 +00:00
2017-05-10 21:33:58 +00:00
if HG.force_idle_mode:
2016-01-13 22:08:19 +00:00
return False
2017-10-18 19:41:25 +00:00
max_cpu = self.options[ 'idle_cpu_max' ]
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
if max_cpu is None:
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
self._system_busy = False
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
else:
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
if HydrusData.TimeHasPassed( self._timestamps[ 'last_cpu_check' ] + 60 ):
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
cpu_times = psutil.cpu_percent( percpu = True )
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
if True in ( cpu_time > max_cpu for cpu_time in cpu_times ):
self._system_busy = True
else:
self._system_busy = False
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
self._timestamps[ 'last_cpu_check' ] = HydrusData.GetNow()
2015-08-26 21:18:39 +00:00
return self._system_busy
2013-08-28 21:31:52 +00:00
2015-04-08 18:10:50 +00:00
def THREADBootEverything( self ):
try:
2015-09-02 23:16:09 +00:00
self.CheckAlreadyRunning()
2015-04-08 18:10:50 +00:00
2017-07-12 20:03:45 +00:00
self._last_shutdown_was_bad = HydrusData.LastShutdownWasBad( self.db_dir, 'client' )
2016-08-31 19:55:14 +00:00
2017-07-12 20:03:45 +00:00
HydrusData.RecordRunningStart( self.db_dir, 'client' )
2015-04-08 18:10:50 +00:00
2015-09-02 23:16:09 +00:00
self.InitModel()
2015-05-13 20:22:39 +00:00
2015-09-02 23:16:09 +00:00
self.InitView()
2015-04-29 19:20:35 +00:00
2017-05-10 21:33:58 +00:00
self._is_booted = True
2019-02-06 22:41:35 +00:00
except ( HydrusExceptions.InsufficientCredentialsException, HydrusExceptions.ShutdownException ) as e:
2015-04-08 18:10:50 +00:00
2015-11-18 22:44:07 +00:00
HydrusData.Print( e )
2015-04-08 18:10:50 +00:00
2017-05-10 21:33:58 +00:00
HG.emergency_exit = True
2015-04-08 18:10:50 +00:00
2016-01-13 22:08:19 +00:00
self.Exit()
2015-04-08 18:10:50 +00:00
2016-01-13 22:08:19 +00:00
except Exception as e:
2018-07-18 21:07:15 +00:00
text = 'A serious error occurred while trying to start the program. The error will be shown next in a window. More information may have been written to client.log.'
2015-09-02 23:16:09 +00:00
2018-02-21 21:59:37 +00:00
HydrusData.DebugPrint( 'If the db crashed, another error may be written just above ^.' )
2015-09-02 23:16:09 +00:00
HydrusData.DebugPrint( text )
2015-04-08 18:10:50 +00:00
2016-10-19 20:02:56 +00:00
HydrusData.DebugPrint( traceback.format_exc() )
2016-01-13 22:08:19 +00:00
2019-02-27 23:03:30 +00:00
wx.SafeShowMessage( 'boot error', text )
wx.SafeShowMessage( 'boot error', traceback.format_exc() )
2016-01-13 22:08:19 +00:00
2017-05-10 21:33:58 +00:00
HG.emergency_exit = True
2016-01-13 22:08:19 +00:00
self.Exit()
2015-04-08 18:10:50 +00:00
finally:
2017-07-27 00:47:13 +00:00
self._DestroySplash()
2015-04-08 18:10:50 +00:00
def THREADExitEverything( self ):
2015-06-03 21:05:13 +00:00
2015-04-08 18:10:50 +00:00
try:
2019-01-23 22:19:16 +00:00
gc.collect()
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_title_text', 'shutting down gui\u2026' )
2015-09-23 21:21:02 +00:00
2015-09-02 23:16:09 +00:00
self.ShutdownView()
2015-04-08 18:10:50 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_title_text', 'shutting down db\u2026' )
2015-09-23 21:21:02 +00:00
2015-09-02 23:16:09 +00:00
self.ShutdownModel()
2015-04-08 18:10:50 +00:00
2019-01-09 22:59:03 +00:00
self.pub( 'splash_set_title_text', 'cleaning up\u2026' )
2017-08-09 21:33:51 +00:00
2019-07-17 22:10:19 +00:00
if not HG.shutting_down_due_to_already_running:
HydrusData.CleanRunningFile( self.db_dir, 'client' )
2016-06-15 18:59:44 +00:00
2019-02-06 22:41:35 +00:00
except ( HydrusExceptions.InsufficientCredentialsException, HydrusExceptions.ShutdownException ):
2017-07-27 00:47:13 +00:00
pass
2015-04-08 18:10:50 +00:00
except:
2017-03-29 19:39:34 +00:00
ClientData.ReportShutdownException()
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
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':
paths = data
if wx.TheClipboard.Open():
2019-06-26 21:27:18 +00:00
try:
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 )
finally:
wx.TheClipboard.Close()
2017-12-13 22:33:07 +00:00
2019-02-27 23:03:30 +00:00
else:
wx.MessageBox( 'Could not get permission to access the clipboard!' )
2017-12-13 22:33:07 +00:00
elif data_type == 'text':
text = data
if wx.TheClipboard.Open():
2019-06-26 21:27:18 +00:00
try:
data = wx.TextDataObject( text )
wx.TheClipboard.SetData( data )
finally:
wx.TheClipboard.Close()
2017-12-13 22:33:07 +00:00
2019-02-27 23:03:30 +00:00
else:
wx.MessageBox( 'I could not get permission to access the clipboard.' )
2017-12-13 22:33:07 +00:00
elif data_type == 'bmp':
media = data
image_renderer = self.GetCache( 'images' ).GetImageRenderer( media )
def CopyToClipboard():
if wx.TheClipboard.Open():
2019-06-26 21:27:18 +00:00
try:
wx_bmp = image_renderer.GetWXBitmap()
data = wx.BitmapDataObject( wx_bmp )
wx.TheClipboard.SetData( data )
finally:
wx.TheClipboard.Close()
2017-12-13 22:33:07 +00:00
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 )
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 ):
while True:
2019-07-31 22:01:02 +00:00
if HG.view_shutdown:
2017-10-04 17:51:58 +00:00
raise HydrusExceptions.ShutdownException( 'Application shutting down!' )
elif not self._caches[ 'thumbnail' ].DoingWork():
return
else:
time.sleep( 0.00001 )
2013-02-19 00:11:43 +00:00
def Write( self, action, *args, **kwargs ):
2017-12-06 22:06:56 +00:00
if action == 'content_updates':
self._managers[ 'undo' ].AddCommand( 'content_updates', *args, **kwargs )
2013-07-10 20:25:57 +00:00
2015-04-01 20:44:54 +00:00
return HydrusController.HydrusController.Write( self, action, *args, **kwargs )
2013-07-31 21:26:38 +00:00
2016-12-07 22:12:52 +00:00