hydrus/include/ClientController.py

1054 lines
36 KiB
Python
Raw Normal View History

2015-03-25 22:04:19 +00:00
import ClientCaches
2015-04-01 20:44:54 +00:00
import ClientData
2015-05-20 21:31:40 +00:00
import ClientDaemons
2015-10-21 21:53:10 +00:00
import ClientNetworking
2013-11-06 18:22:07 +00:00
import hashlib
2013-09-25 20:20:10 +00:00
import httplib
2015-04-01 20:44:54 +00:00
import HydrusConstants as HC
import HydrusController
import HydrusData
2013-07-24 20:26:00 +00:00
import HydrusExceptions
2015-04-01 20:44:54 +00:00
import HydrusGlobals
2014-01-29 21:59:42 +00:00
import HydrusNetworking
2015-10-21 21:53:10 +00:00
import HydrusSerialisable
2013-03-15 02:38:12 +00:00
import HydrusSessions
2013-09-25 20:20:10 +00:00
import HydrusServer
2013-07-17 20:56:13 +00:00
import HydrusTags
2014-05-21 21:37:35 +00:00
import HydrusThreading
2013-02-19 00:11:43 +00:00
import ClientConstants as CC
import ClientDB
import ClientGUI
2013-05-29 20:19:54 +00:00
import ClientGUIDialogs
2015-08-05 18:42:35 +00:00
import ClientLocalServer
2013-02-19 00:11:43 +00:00
import os
2015-08-19 21:48:21 +00:00
import psutil
2013-08-28 21:31:52 +00:00
import random
2015-04-08 18:10:50 +00:00
import sqlite3
2014-01-08 18:40:02 +00:00
import subprocess
2013-07-24 20:26:00 +00:00
import sys
2015-04-08 18:10:50 +00:00
import threading
2013-02-19 00:11:43 +00:00
import time
import traceback
import wx
2013-10-02 22:06:06 +00:00
from twisted.internet import reactor
2013-10-23 21:36:47 +00:00
from twisted.internet import defer
2013-02-19 00:11:43 +00:00
2015-04-01 20:44:54 +00:00
class Controller( HydrusController.HydrusController ):
2014-12-03 22:56:40 +00:00
2015-08-26 21:18:39 +00:00
pubsub_binding_errors_to_ignore = [ wx.PyDeadObjectError ]
2013-05-08 20:31:00 +00:00
2015-09-16 18:11:00 +00:00
def __init__( self ):
HydrusController.HydrusController.__init__( self )
HydrusGlobals.client_controller = self
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
2015-10-28 21:29:05 +00:00
2015-09-16 18:11:00 +00:00
2015-09-02 23:16:09 +00:00
def _InitDB( self ):
return ClientDB.DB( self )
2014-01-08 18:40:02 +00:00
def BackupDatabase( self ):
with wx.DirDialog( self._gui, 'Select backup location.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
2015-11-04 22:30:28 +00:00
path = HydrusData.ToUnicode( dlg.GetPath() )
2014-01-08 18:40:02 +00:00
2014-12-10 22:02:39 +00:00
text = 'Are you sure "' + path + '" is the correct directory?'
text += os.linesep * 2
text += 'Everything already in that directory will be deleted before the backup starts.'
text += os.linesep * 2
text += 'The database will be locked while the backup occurs, which may lock up your gui as well.'
2014-01-08 18:40:02 +00:00
2014-12-10 22:02:39 +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:
self.Write( 'backup', path )
2015-08-26 21:18:39 +00:00
def CallBlockingToWx( self, callable, *args, **kwargs ):
def wx_code( job_key ):
try:
result = callable( *args, **kwargs )
job_key.SetVariable( 'result', result )
except Exception as e:
job_key.SetVariable( 'error', e )
2015-11-18 22:44:07 +00:00
HydrusData.Print( 'CallBlockingToWx just caught this error:' )
2015-09-02 23:16:09 +00:00
HydrusData.DebugPrint( traceback.format_exc() )
2015-08-26 21:18:39 +00:00
finally: job_key.Finish()
2015-09-16 18:11:00 +00:00
job_key = HydrusThreading.JobKey()
2015-08-26 21:18:39 +00:00
job_key.Begin()
wx.CallAfter( wx_code, job_key )
while not job_key.IsDone():
2015-11-04 22:30:28 +00:00
if self._model_shutdown:
return
2015-08-26 21:18:39 +00:00
time.sleep( 0.05 )
if job_key.HasVariable( 'result' ): return job_key.GetVariable( 'result' )
else: raise job_key.GetVariable( 'error' )
2015-09-02 23:16:09 +00:00
def CheckAlreadyRunning( self ):
while HydrusData.IsAlreadyRunning( 'client' ):
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.'
with ClientGUIDialogs.DialogYesNo( self._splash, message, 'The client is already running.', yes_label = 'wait a bit, then try again', no_label = 'forget it' ) as dlg:
if dlg.ShowModal() != wx.ID_YES:
raise HydrusExceptions.PermissionException()
self.CallBlockingToWx( wx_code )
for i in range( 10, 0, -1 ):
if not HydrusData.IsAlreadyRunning( 'client' ):
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:
idle_before = self.CurrentlyIdle()
self._timestamps[ 'last_mouse_action' ] = HydrusData.GetNow()
self._last_mouse_position = mouse_position
idle_after = self.CurrentlyIdle()
if idle_before != idle_after:
self.pub( 'refresh_status' )
2014-09-03 20:26:49 +00:00
def Clipboard( self, data_type, data ):
2013-02-19 00:11:43 +00:00
# need this cause can't do it in a non-gui thread
2014-09-03 20:26:49 +00:00
if data_type == 'paths':
2013-02-19 00:11:43 +00:00
paths = data
if wx.TheClipboard.Open():
2013-03-15 02:38:12 +00:00
data = wx.DataObjectComposite()
2013-02-19 00:11:43 +00:00
2013-03-15 02:38:12 +00:00
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 )
2013-02-19 00:11:43 +00:00
wx.TheClipboard.SetData( data )
wx.TheClipboard.Close()
2013-03-15 02:38:12 +00:00
else: wx.MessageBox( 'Could not get permission to access the clipboard!' )
2013-02-19 00:11:43 +00:00
2014-09-03 20:26:49 +00:00
elif data_type == 'text':
2013-03-23 17:57:29 +00:00
text = data
if wx.TheClipboard.Open():
data = wx.TextDataObject( text )
wx.TheClipboard.SetData( data )
wx.TheClipboard.Close()
else: wx.MessageBox( 'I could not get permission to access the clipboard.' )
2014-11-20 01:48:04 +00:00
elif data_type == 'bmp':
media = data
2015-08-26 21:18:39 +00:00
image_container = self.GetCache( 'fullscreen' ).GetImage( media )
2014-11-20 01:48:04 +00:00
def CopyToClipboard():
if wx.TheClipboard.Open():
hydrus_bmp = image_container.GetHydrusBitmap()
wx_bmp = hydrus_bmp.GetWxBitmap()
data = wx.BitmapDataObject( wx_bmp )
wx.TheClipboard.SetData( data )
wx.TheClipboard.Close()
else: wx.MessageBox( 'I could not get permission to access the clipboard.' )
2015-08-05 18:42:35 +00:00
def THREADWait():
# have to do this in thread, because the image rendered needs the wx event queue to render
start_time = time.time()
while not image_container.IsRendered():
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 )
2015-08-26 21:18:39 +00:00
self.CallToThread( THREADWait )
2014-11-20 01:48:04 +00:00
2013-02-19 00:11:43 +00:00
2014-12-03 22:56:40 +00:00
def CurrentlyIdle( self ):
2015-12-02 22:32:18 +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
possibly_idle = False
definitely_not_idle = False
2015-12-02 22:32:18 +00:00
if idle_normal:
2015-08-19 21:48:21 +00:00
2015-12-02 22:32:18 +00:00
possibly_idle = True
if idle_period is not None:
if not HydrusData.TimeHasPassed( self._timestamps[ 'last_user_action' ] + idle_period ):
2015-10-28 21:29:05 +00:00
definitely_not_idle = True
2015-12-02 22:32:18 +00:00
if idle_mouse_period is not None:
2015-10-28 21:29:05 +00:00
2015-12-02 22:32:18 +00:00
if not HydrusData.TimeHasPassed( self._timestamps[ 'last_mouse_action' ] + idle_mouse_period ):
2015-10-28 21:29:05 +00:00
definitely_not_idle = True
2015-08-19 21:48:21 +00:00
2015-10-28 21:29:05 +00:00
if definitely_not_idle:
return False
elif possibly_idle:
return True
else:
return False
2015-08-19 21:48:21 +00:00
2015-08-26 21:18:39 +00:00
def DoHTTP( self, *args, **kwargs ): return self._http.Request( *args, **kwargs )
def DoIdleShutdownWork( self ):
stop_time = HydrusData.GetNow() + ( self._options[ 'idle_shutdown_max_minutes' ] * 60 )
self.MaintainDB()
if not self._options[ 'pause_repo_sync' ]:
2015-08-19 21:48:21 +00:00
2015-08-26 21:18:39 +00:00
services = self.GetServicesManager().GetServices( HC.REPOSITORIES )
for service in services:
if HydrusData.TimeHasPassed( stop_time ):
return
service.Sync( only_when_idle = False, stop_time = stop_time )
2015-08-19 21:48:21 +00:00
2014-12-03 22:56:40 +00:00
2014-02-19 22:37:23 +00:00
2015-08-26 21:18:39 +00:00
def Exit( self ):
2015-09-02 23:16:09 +00:00
try:
self._gui.TestAbleToClose()
except HydrusExceptions.PermissionException:
return
try:
2015-11-04 22:30:28 +00:00
self._splash = ClientGUI.FrameSplash( self )
2015-09-02 23:16:09 +00:00
2015-08-26 21:18:39 +00:00
except Exception as e:
2015-11-18 22:44:07 +00:00
HydrusData.Print( 'There was an error trying to start the splash screen!' )
2015-08-26 21:18:39 +00:00
2015-11-18 22:44:07 +00:00
HydrusData.Print( traceback.format_exc() )
2015-08-26 21:18:39 +00:00
exit_thread = threading.Thread( target = self.THREADExitEverything, name = 'Application Exit Thread' )
2015-09-02 23:16:09 +00:00
exit_thread.start()
2015-08-26 21:18:39 +00:00
2013-02-19 00:11:43 +00:00
2015-06-03 21:05:13 +00:00
def ForceIdle( self ):
2015-11-25 22:00:57 +00:00
if 'last_user_action' in self._timestamps:
del self._timestamps[ 'last_user_action' ]
if 'last_mouse_action' in self._timestamps:
del self._timestamps[ 'last_mouse_action' ]
2015-10-28 21:29:05 +00:00
self._last_mouse_position = None
2015-06-03 21:05:13 +00:00
2015-08-26 21:18:39 +00:00
self.pub( 'wake_daemons' )
self.pub( 'refresh_status' )
2015-06-03 21:05:13 +00:00
2015-09-02 23:16:09 +00:00
def ForceUnbusy( self ):
self._system_busy = False
self.pub( 'wake_daemons' )
self.pub( 'refresh_status' )
2013-02-19 00:11:43 +00:00
2015-12-02 22:32:18 +00:00
def GetClientFilesManager( self ):
return self._client_files_manager
2015-12-09 23:16:41 +00:00
def GetClientSessionManager( self ):
return self._client_session_manager
2015-09-02 23:16:09 +00:00
def GetDB( self ): return self._db
def GetGUI( self ): return self._gui
2013-10-09 18:13:42 +00:00
2015-05-13 20:22:39 +00:00
def GetOptions( self ):
return self._options
2015-10-14 21:02:25 +00:00
def GetNewOptions( self ):
return self._new_options
2015-07-01 22:02:07 +00:00
def GetServicesManager( self ):
return self._services_manager
2015-09-02 23:16:09 +00:00
def InitModel( self ):
2014-08-06 20:29:17 +00:00
2015-11-18 22:44:07 +00:00
self.pub( 'splash_set_status_text', 'booting db...' )
2014-08-06 20:29:17 +00:00
2015-10-21 21:53:10 +00:00
self._http = ClientNetworking.HTTPConnectionManager()
2015-08-26 21:18:39 +00:00
2015-09-02 23:16:09 +00:00
HydrusController.HydrusController.InitModel( self )
2015-08-26 21:18:39 +00:00
2015-09-02 23:16:09 +00:00
self._options = self.Read( 'options' )
2015-10-21 21:53:10 +00:00
self._new_options = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS )
2015-08-26 21:18:39 +00:00
2015-09-02 23:16:09 +00:00
HC.options = self._options
2015-10-21 21:53:10 +00:00
2015-11-25 22:00:57 +00:00
self._services_manager = ClientCaches.ServicesManager( self )
2014-08-13 22:18:12 +00:00
2015-12-02 22:32:18 +00:00
self._client_files_manager = ClientCaches.ClientFilesManager( self )
2015-12-09 23:16:41 +00:00
self._client_session_manager = ClientCaches.HydrusSessionManager( self )
2015-11-25 22:00:57 +00:00
self._managers[ 'local_booru' ] = ClientCaches.LocalBooruCache( self )
self._managers[ 'tag_censorship' ] = ClientCaches.TagCensorshipManager( self )
self._managers[ 'tag_siblings' ] = ClientCaches.TagSiblingsManager( self )
self._managers[ 'tag_parents' ] = ClientCaches.TagParentsManager( self )
self._managers[ 'undo' ] = ClientCaches.UndoManager( self )
self._managers[ 'web_sessions' ] = ClientCaches.WebSessionManagerClient( self )
2014-08-06 20:29:17 +00:00
2015-06-10 19:40:25 +00:00
if HC.options[ 'proxy' ] is not None:
( proxytype, host, port, username, password ) = HC.options[ 'proxy' ]
2015-10-21 21:53:10 +00:00
ClientNetworking.SetProxy( proxytype, host, port, username, password )
2015-06-10 19:40:25 +00:00
2015-09-02 23:16:09 +00:00
def wx_code():
2015-11-25 22:00:57 +00:00
self._caches[ 'fullscreen' ] = ClientCaches.RenderedImageCache( self, 'fullscreen' )
self._caches[ 'preview' ] = ClientCaches.RenderedImageCache( self, 'preview' )
self._caches[ 'thumbnail' ] = ClientCaches.ThumbnailCache( self )
2015-09-02 23:16:09 +00:00
CC.GlobalBMPs.STATICInitialise()
2014-08-06 20:29:17 +00:00
2015-09-02 23:16:09 +00:00
self.CallBlockingToWx( wx_code )
2014-08-06 20:29:17 +00:00
2015-08-26 21:18:39 +00:00
self.sub( self, 'Clipboard', 'clipboard' )
self.sub( self, 'RestartServer', 'restart_server' )
self.sub( self, 'RestartBooru', 'restart_booru' )
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
2015-09-02 23:16:09 +00:00
if self._options[ 'password' ] is not None:
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:
if hashlib.sha256( dlg.GetValue() ).digest() == self._options[ 'password' ]: break
else: raise HydrusExceptions.PermissionException( 'Bad password check' )
self.CallBlockingToWx( wx_code_password )
2015-11-18 22:44:07 +00:00
self.pub( 'splash_set_status_text', 'booting gui...' )
2015-09-02 23:16:09 +00:00
def wx_code_gui():
2015-11-04 22:30:28 +00:00
self._gui = ClientGUI.FrameGUI( self )
2015-09-02 23:16:09 +00:00
# this is because of some bug in wx C++ that doesn't add these by default
wx.richtext.RichTextBuffer.AddHandler( wx.richtext.RichTextHTMLHandler() )
wx.richtext.RichTextBuffer.AddHandler( wx.richtext.RichTextXMLHandler() )
self.ResetIdleTimer()
self.CallBlockingToWx( wx_code_gui )
HydrusController.HydrusController.InitView( self )
self._local_service = None
self._booru_service = None
2014-08-06 20:29:17 +00:00
self.RestartServer()
self.RestartBooru()
2015-09-02 23:16:09 +00:00
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckImportFolders', ClientDaemons.DAEMONCheckImportFolders, ( 'notify_restart_import_folders_daemon', 'notify_new_import_folders' ), period = 180 ) )
2015-10-28 21:29:05 +00:00
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckMouseIdle', ClientDaemons.DAEMONCheckMouseIdle, period = 10 ) )
2015-09-02 23:16:09 +00:00
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckExportFolders', ClientDaemons.DAEMONCheckExportFolders, ( 'notify_restart_export_folders_daemon', 'notify_new_export_folders' ), period = 180 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'DownloadFiles', ClientDaemons.DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ), pre_callable_wait = 0 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'MaintainTrash', ClientDaemons.DAEMONMaintainTrash, init_wait = 60 ) )
2015-12-02 22:32:18 +00:00
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'RebalanceClientFiles', ClientDaemons.DAEMONRebalanceClientFiles, period = 3600 ) )
2015-09-02 23:16:09 +00:00
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SynchroniseAccounts', ClientDaemons.DAEMONSynchroniseAccounts, ( 'permissions_are_stale', ) ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SynchroniseRepositories', ClientDaemons.DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ), period = 360 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SynchroniseSubscriptions', ClientDaemons.DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ), period = 360, init_wait = 120 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'UPnP', ClientDaemons.DAEMONUPnP, ( 'notify_new_upnp_mappings', ), init_wait = 120, pre_callable_wait = 6 ) )
self._daemons.append( HydrusThreading.DAEMONQueue( self, 'FlushRepositoryUpdates', ClientDaemons.DAEMONFlushServiceUpdates, 'service_updates_delayed', period = 5 ) )
if HydrusGlobals.is_first_start: wx.CallAfter( self._gui.DoFirstStart )
2015-11-04 22:30:28 +00:00
if HydrusGlobals.is_db_updated: wx.CallLater( 1, HydrusData.ShowText, 'The client has updated to version ' + str( HC.SOFTWARE_VERSION ) + '!' )
2015-06-03 21:05:13 +00:00
2014-08-06 20:29:17 +00:00
2013-02-19 00:11:43 +00:00
def MaintainDB( self ):
2015-03-25 22:04:19 +00:00
now = HydrusData.GetNow()
2013-02-19 00:11:43 +00:00
shutdown_timestamps = self.Read( 'shutdown_timestamps' )
2015-12-02 22:32:18 +00:00
maintenance_vacuum_period = self._options[ 'maintenance_vacuum_period' ]
if maintenance_vacuum_period is not None and maintenance_vacuum_period > 0:
2014-12-03 22:56:40 +00:00
2015-12-02 22:32:18 +00:00
if HydrusData.TimeHasPassed( shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] + maintenance_vacuum_period ):
self.WriteSynchronous( 'vacuum' )
2014-12-03 22:56:40 +00:00
2015-08-26 21:18:39 +00:00
if self._timestamps[ 'last_service_info_cache_fatten' ] == 0:
2014-08-06 20:29:17 +00:00
2015-08-26 21:18:39 +00:00
self._timestamps[ 'last_service_info_cache_fatten' ] = HydrusData.GetNow()
2015-12-02 22:32:18 +00:00
if HydrusData.TimeHasPassed( self._timestamps[ 'last_service_info_cache_fatten' ] + ( 60 * 20 ) ):
2015-08-26 21:18:39 +00:00
2015-09-23 21:21:02 +00:00
self.pub( 'splash_set_status_text', 'fattening service info' )
2014-04-23 20:56:12 +00:00
2015-07-01 22:02:07 +00:00
services = self.GetServicesManager().GetServices()
2014-04-23 20:56:12 +00:00
2014-12-17 22:35:12 +00:00
for service in services:
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
2014-04-23 20:56:12 +00:00
2015-03-25 22:04:19 +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
def MaintainMemory( self ):
HydrusController.HydrusController.MaintainMemory( self )
if self._timestamps[ 'last_page_change' ] == 0:
self._timestamps[ 'last_page_change' ] = HydrusData.GetNow()
if HydrusData.TimeHasPassed( self._timestamps[ 'last_page_change' ] + 30 * 60 ):
self.pub( 'clear_closed_pages' )
self._timestamps[ 'last_page_change' ] = HydrusData.GetNow()
2015-11-25 22:00:57 +00:00
def MenuIsOpen( self ):
return self._menu_open
2015-08-26 21:18:39 +00:00
def NotifyPubSubs( self ):
wx.CallAfter( self.ProcessPubSub )
2014-04-23 20:56:12 +00:00
2013-02-19 00:11:43 +00:00
2015-09-16 18:11:00 +00:00
def PageDeleted( self, page_key ):
return self._gui.PageDeleted( page_key )
2015-10-07 21:56:22 +00:00
def PageHidden( self, page_key ):
return self._gui.PageHidden( page_key )
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
wx.CallAfter( menu.Destroy )
2013-02-19 00:11:43 +00:00
def PrepStringForDisplay( self, text ):
2015-05-13 20:22:39 +00:00
if self._options[ 'gui_capitalisation' ]: return text
2013-02-19 00:11:43 +00:00
else: return text.lower()
2015-10-28 21:29:05 +00:00
def ResetIdleTimer( self ):
self._timestamps[ 'last_user_action' ] = HydrusData.GetNow()
2014-08-13 22:18:12 +00:00
2015-08-26 21:18:39 +00:00
def ResetPageChangeTimer( self ):
self._timestamps[ 'last_page_change' ] = HydrusData.GetNow()
2014-07-09 22:15:14 +00:00
def RestartBooru( self ):
2015-07-01 22:02:07 +00:00
service = self.GetServicesManager().GetService( CC.LOCAL_BOORU_SERVICE_KEY )
2014-07-09 22:15:14 +00:00
info = service.GetInfo()
port = info[ 'port' ]
def TWISTEDRestartServer():
def StartServer( *args, **kwargs ):
try:
try:
2015-06-10 19:40:25 +00:00
connection = HydrusNetworking.GetLocalConnection( port )
2014-07-09 22:15:14 +00:00
connection.close()
2015-11-04 22:30:28 +00:00
text = 'The client\'s booru server could not start because something was already bound to port ' + str( port ) + '.'
2014-07-09 22:15:14 +00:00
text += os.linesep * 2
text += 'This usually means another hydrus client is already running and occupying that port. It could be a previous instantiation of this client that has yet to shut itself down.'
text += os.linesep * 2
text += 'You can change the port this client tries to host its local server on in services->manage services.'
2015-03-25 22:04:19 +00:00
wx.CallLater( 1, HydrusData.ShowText, text )
2014-07-09 22:15:14 +00:00
except:
2015-08-05 18:42:35 +00:00
self._booru_service = reactor.listenTCP( port, ClientLocalServer.HydrusServiceBooru( CC.LOCAL_BOORU_SERVICE_KEY, HC.LOCAL_BOORU, 'This is the local booru.' ) )
2014-07-09 22:15:14 +00:00
try:
2015-06-10 19:40:25 +00:00
connection = HydrusNetworking.GetLocalConnection( port )
2014-07-09 22:15:14 +00:00
connection.close()
2015-06-10 19:40:25 +00:00
except Exception as e:
2014-07-09 22:15:14 +00:00
2015-11-04 22:30:28 +00:00
text = 'Tried to bind port ' + str( port ) + ' for the local booru, but it failed:'
2015-06-10 19:40:25 +00:00
text += os.linesep * 2
2015-11-04 22:30:28 +00:00
text += HydrusData.ToUnicode( e )
2014-07-09 22:15:14 +00:00
2015-03-25 22:04:19 +00:00
wx.CallLater( 1, HydrusData.ShowText, text )
2014-07-09 22:15:14 +00:00
2015-06-03 21:05:13 +00:00
except Exception as e:
wx.CallAfter( HydrusData.ShowException, e )
2014-07-09 22:15:14 +00:00
if self._booru_service is None: StartServer()
else:
deferred = defer.maybeDeferred( self._booru_service.stopListening )
deferred.addCallback( StartServer )
reactor.callFromThread( TWISTEDRestartServer )
2013-09-25 20:20:10 +00:00
def RestartServer( self ):
2015-05-13 20:22:39 +00:00
port = self._options[ 'local_port' ]
2013-09-25 20:20:10 +00:00
def TWISTEDRestartServer():
2013-10-23 21:36:47 +00:00
def StartServer( *args, **kwargs ):
2013-09-25 20:20:10 +00:00
try:
2013-10-02 22:06:06 +00:00
try:
2015-06-10 19:40:25 +00:00
connection = HydrusNetworking.GetLocalConnection( port )
2013-10-02 22:06:06 +00:00
connection.close()
2015-11-04 22:30:28 +00:00
text = 'The client\'s local server could not start because something was already bound to port ' + str( port ) + '.'
2014-05-28 21:03:24 +00:00
text += os.linesep * 2
text += 'This usually means another hydrus client is already running and occupying that port. It could be a previous instantiation of this client that has yet to shut itself down.'
text += os.linesep * 2
text += 'You can change the port this client tries to host its local server on in file->options.'
2013-10-02 22:06:06 +00:00
2015-03-25 22:04:19 +00:00
wx.CallLater( 1, HydrusData.ShowText, text )
2013-10-02 22:06:06 +00:00
except:
2015-08-05 18:42:35 +00:00
self._local_service = reactor.listenTCP( port, ClientLocalServer.HydrusServiceLocal( CC.LOCAL_FILE_SERVICE_KEY, HC.LOCAL_FILE, 'This is the local file service.' ) )
2013-10-02 22:06:06 +00:00
try:
2015-06-10 19:40:25 +00:00
connection = HydrusNetworking.GetLocalConnection( port )
2013-10-02 22:06:06 +00:00
connection.close()
2015-06-10 19:40:25 +00:00
except Exception as e:
2013-10-02 22:06:06 +00:00
2015-11-04 22:30:28 +00:00
text = 'Tried to bind port ' + str( port ) + ' for the local server, but it failed:'
2015-06-10 19:40:25 +00:00
text += os.linesep * 2
2015-11-04 22:30:28 +00:00
text += HydrusData.ToUnicode( e )
2013-10-02 22:06:06 +00:00
2015-03-25 22:04:19 +00:00
wx.CallLater( 1, HydrusData.ShowText, text )
2013-10-02 22:06:06 +00:00
2015-06-03 21:05:13 +00:00
except Exception as e:
wx.CallAfter( HydrusData.ShowException, e )
2013-09-25 20:20:10 +00:00
if self._local_service is None: StartServer()
else:
2013-10-23 21:36:47 +00:00
deferred = defer.maybeDeferred( self._local_service.stopListening )
2013-09-25 20:20:10 +00:00
2013-10-23 21:36:47 +00:00
deferred.addCallback( StartServer )
2013-09-25 20:20:10 +00:00
2013-10-02 22:06:06 +00:00
reactor.callFromThread( TWISTEDRestartServer )
2013-09-25 20:20:10 +00:00
2014-01-08 18:40:02 +00:00
def RestoreDatabase( self ):
with wx.DirDialog( self._gui, 'Select backup location.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
2015-11-04 22:30:28 +00:00
path = HydrusData.ToUnicode( 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
2014-12-10 22:02:39 +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():
2015-08-26 21:18:39 +00:00
wx.CallAfter( self.Exit )
2015-06-17 20:01:41 +00:00
while not self._db.LoopIsFinished(): time.sleep( 0.1 )
self._db.RestoreBackup( path )
cmd = [ sys.executable ]
cmd.extend( sys.argv )
subprocess.Popen( cmd )
2014-01-08 18:40:02 +00:00
2015-06-17 20:01:41 +00:00
restart_thread = threading.Thread( target = THREADRestart, name = 'Application Restart Thread' )
2014-01-08 18:40:02 +00:00
2015-09-02 23:16:09 +00:00
restart_thread.start()
def Run( self ):
self._app = wx.App()
self._app.SetAssertMode( wx.PYAPP_ASSERT_SUPPRESS )
2015-11-18 22:44:07 +00:00
HydrusData.Print( 'booting controller...' )
2015-09-02 23:16:09 +00:00
try:
2015-11-04 22:30:28 +00:00
self._splash = ClientGUI.FrameSplash( self )
2015-09-02 23:16:09 +00:00
except:
2015-11-18 22:44:07 +00:00
HydrusData.Print( 'There was an error trying to start the splash screen!' )
2015-09-02 23:16:09 +00:00
2015-11-18 22:44:07 +00:00
HydrusData.Print( traceback.format_exc() )
2015-09-02 23:16:09 +00:00
raise
boot_thread = threading.Thread( target = self.THREADBootEverything, name = 'Application Boot Thread' )
boot_thread.start()
self._app.MainLoop()
2015-11-18 22:44:07 +00:00
HydrusData.Print( 'shutting down controller...' )
2015-09-02 23:16:09 +00:00
def ShutdownView( self ):
self.CallBlockingToWx( self._gui.Shutdown )
2015-09-23 21:21:02 +00:00
self.pub( 'splash_set_status_text', 'waiting for daemons to exit' )
2015-09-02 23:16:09 +00:00
2015-09-16 18:11:00 +00:00
self._ShutdownDaemons()
2015-09-02 23:16:09 +00:00
idle_shutdown_action = self._options[ 'idle_shutdown' ]
if idle_shutdown_action in ( CC.IDLE_ON_SHUTDOWN, CC.IDLE_ON_SHUTDOWN_ASK_FIRST ):
2015-09-23 21:21:02 +00:00
self.pub( 'splash_set_status_text', 'running maintenance' )
2015-09-02 23:16:09 +00:00
self.ResetIdleTimer()
do_it = True
if CC.IDLE_ON_SHUTDOWN_ASK_FIRST:
if self.ThereIsIdleShutdownWorkDue():
def wx_code():
text = 'Is now a good time for the client to do up to ' + HydrusData.ConvertIntToPrettyString( self._options[ 'idle_shutdown_max_minutes' ] ) + ' minutes\' maintenance work?'
2014-01-08 18:40:02 +00:00
2015-09-02 23:16:09 +00:00
with ClientGUIDialogs.DialogYesNo( self._splash, text, title = 'Maintenance is due' ) as dlg_yn:
if dlg_yn.ShowModal() == wx.ID_YES:
return True
else:
return False
2014-01-08 18:40:02 +00:00
2015-09-02 23:16:09 +00:00
do_it = self.CallBlockingToWx( wx_code )
if do_it:
self.DoIdleShutdownWork()
2014-01-08 18:40:02 +00:00
2015-09-16 18:11:00 +00:00
HydrusController.HydrusController.ShutdownView( self )
2014-01-08 18:40:02 +00:00
2015-08-26 21:18:39 +00:00
def StartFileQuery( self, query_key, search_context ):
self.CallToThread( self.THREADDoFileQuery, query_key, search_context )
def SystemBusy( self ):
2015-12-02 22:32:18 +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-08-26 21:18:39 +00:00
def ThereIsIdleShutdownWorkDue( self ):
now = HydrusData.GetNow()
shutdown_timestamps = self.Read( 'shutdown_timestamps' )
2015-05-20 21:31:40 +00:00
2015-12-02 22:32:18 +00:00
maintenance_vacuum_period = self._options[ 'maintenance_vacuum_period' ]
if maintenance_vacuum_period is not None and maintenance_vacuum_period > 0:
2015-08-26 21:18:39 +00:00
2015-12-02 22:32:18 +00:00
if HydrusData.TimeHasPassed( shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] + maintenance_vacuum_period ):
2015-08-26 21:18:39 +00:00
return True
if not self._options[ 'pause_repo_sync' ]:
services = self.GetServicesManager().GetServices( HC.REPOSITORIES )
for service in services:
if service.CanDownloadUpdate() or service.CanProcessUpdate():
return True
2015-05-20 21:31:40 +00:00
2015-08-26 21:18:39 +00:00
return False
2015-05-20 21:31:40 +00:00
2013-08-28 21:31:52 +00:00
def THREADDoFileQuery( self, query_key, search_context ):
2015-03-04 22:44:32 +00:00
query_hash_ids = self.Read( 'file_query_ids', search_context )
query_hash_ids = list( query_hash_ids )
random.shuffle( query_hash_ids )
limit = search_context.GetSystemPredicates().GetLimit()
2015-10-21 21:53:10 +00:00
if limit is not None:
query_hash_ids = query_hash_ids[ : limit ]
2015-03-04 22:44:32 +00:00
service_key = search_context.GetFileServiceKey()
media_results = []
2015-04-22 22:57:25 +00:00
for sub_query_hash_ids in HydrusData.SplitListIntoChunks( query_hash_ids, 256 ):
2013-08-28 21:31:52 +00:00
2015-03-04 22:44:32 +00:00
if query_key.IsCancelled(): return
2013-08-28 21:31:52 +00:00
2015-03-04 22:44:32 +00:00
more_media_results = self.Read( 'media_results_from_ids', service_key, sub_query_hash_ids )
2013-08-28 21:31:52 +00:00
2015-03-04 22:44:32 +00:00
media_results.extend( more_media_results )
2013-08-28 21:31:52 +00:00
2015-08-26 21:18:39 +00:00
self.pub( 'set_num_query_results', len( media_results ), len( query_hash_ids ) )
2013-08-28 21:31:52 +00:00
2015-08-26 21:18:39 +00:00
self.WaitUntilPubSubsEmpty()
2013-08-28 21:31:52 +00:00
2015-03-04 22:44:32 +00:00
2015-06-24 22:10:14 +00:00
search_context.SetComplete()
2015-08-26 21:18:39 +00:00
self.pub( 'file_query_done', query_key, media_results )
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
2015-09-02 23:16:09 +00:00
HydrusData.RecordRunningStart( '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
2015-09-02 23:16:09 +00:00
except HydrusExceptions.PermissionException 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
except:
2015-06-24 22:10:14 +00:00
text = 'A serious error occured while trying to start the program. Its traceback will be shown next. It should have also been written to client.log.'
2015-04-08 18:10:50 +00:00
2015-09-02 23:16:09 +00:00
traceback.print_exc()
HydrusData.DebugPrint( text )
2015-04-08 18:10:50 +00:00
wx.CallAfter( wx.MessageBox, text )
2015-06-24 22:10:14 +00:00
wx.CallAfter( wx.MessageBox, traceback.format_exc() )
2015-04-08 18:10:50 +00:00
finally:
2015-08-26 21:18:39 +00:00
self.pub( 'splash_destroy' )
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:
2015-11-18 22:44:07 +00:00
self.pub( 'splash_set_title_text', 'shutting down gui...' )
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
2015-11-18 22:44:07 +00:00
self.pub( 'splash_set_title_text', 'shutting down db...' )
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
except HydrusExceptions.PermissionException as e: pass
except:
2015-04-22 22:57:25 +00:00
traceback.print_exc()
2015-06-24 22:10:14 +00:00
text = 'A serious error occured while trying to exit the program. Its traceback will be shown next. It should have also been written to client.log. You may need to quit the program from task manager.'
2015-04-08 18:10:50 +00:00
2015-09-02 23:16:09 +00:00
HydrusData.DebugPrint( text )
2015-04-08 18:10:50 +00:00
wx.CallAfter( wx.MessageBox, text )
finally:
2015-08-26 21:18:39 +00:00
self.pub( 'splash_destroy' )
2015-04-08 18:10:50 +00:00
2013-02-19 00:11:43 +00:00
def Write( self, action, *args, **kwargs ):
2013-12-04 22:44:16 +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
2013-02-19 00:11:43 +00:00