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