hydrus/include/ClientController.py

405 lines
13 KiB
Python
Raw Normal View History

2013-02-19 00:11:43 +00:00
import gc
import HydrusConstants as HC
2013-07-24 20:26:00 +00:00
import HydrusExceptions
2013-02-19 00:11:43 +00:00
import HydrusImageHandling
2013-03-15 02:38:12 +00:00
import HydrusSessions
2013-07-17 20:56:13 +00:00
import HydrusTags
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
2013-02-19 00:11:43 +00:00
import os
2013-08-28 21:31:52 +00:00
import random
import shutil
2013-04-03 20:56:07 +00:00
import sqlite3
2013-07-24 20:26:00 +00:00
import sys
2013-02-19 00:11:43 +00:00
import threading
import time
import traceback
import wx
import wx.richtext
ID_ANIMATED_EVENT_TIMER = wx.NewId()
ID_MAINTENANCE_EVENT_TIMER = wx.NewId()
class Controller( wx.App ):
2013-08-14 20:21:49 +00:00
def _Read( self, action, *args, **kwargs ): return self._db.Read( action, HC.HIGH_PRIORITY, *args, **kwargs )
2013-05-08 20:31:00 +00:00
2013-07-31 21:26:38 +00:00
def _Write( self, action, priority, synchronous, *args, **kwargs ): return self._db.Write( action, priority, synchronous, *args, **kwargs )
2013-05-08 20:31:00 +00:00
2013-02-19 00:11:43 +00:00
def ClearCaches( self ):
self._thumbnail_cache.Clear()
self._fullscreen_image_cache.Clear()
self._preview_image_cache.Clear()
def Clipboard( self, type, data ):
# need this cause can't do it in a non-gui thread
if type == 'paths':
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
2013-03-23 17:57:29 +00:00
elif type == 'text':
text = data
if wx.TheClipboard.Open():
data = wx.TextDataObject( text )
wx.TheClipboard.SetData( data )
wx.TheClipboard.Close()
else: wx.MessageBox( 'I could not get permission to access the clipboard.' )
2013-02-19 00:11:43 +00:00
2013-03-15 02:38:12 +00:00
def DeleteSessionKey( self, service_identifier ): self._session_manager.DeleteSessionKey( service_identifier )
2013-02-19 00:11:43 +00:00
def EventAnimatedTimer( self, event ):
del gc.garbage[:]
HC.pubsub.pub( 'animated_tick' )
def EventMaintenanceTimer( self, event ):
2013-07-31 21:26:38 +00:00
if HC.GetNow() - self._last_idle_time > 60 * 60: # a long time, so we probably just woke up from a sleep
2013-03-15 02:38:12 +00:00
2013-07-31 21:26:38 +00:00
self._last_idle_time = HC.GetNow()
2013-03-15 02:38:12 +00:00
2013-07-31 21:26:38 +00:00
if HC.GetNow() - self._last_idle_time > 20 * 60: # 20 mins since last user-initiated db request
2013-02-19 00:11:43 +00:00
self.MaintainDB()
def EventPubSub( self, event ):
pubsubs_queue = HC.pubsub.GetQueue()
( callable, args, kwargs ) = pubsubs_queue.get()
2013-08-28 21:31:52 +00:00
HC.busy_doing_pubsub = True
2013-02-19 00:11:43 +00:00
try: callable( *args, **kwargs )
except wx._core.PyDeadObjectError: pass
2013-09-04 16:48:44 +00:00
except TypeError as e:
if '_wxPyDeadObject' not in str( e ): raise
2013-08-28 21:31:52 +00:00
finally:
pubsubs_queue.task_done()
HC.busy_doing_pubsub = False
2013-02-19 00:11:43 +00:00
def GetFullscreenImageCache( self ): return self._fullscreen_image_cache
def GetGUI( self ): return self._gui
def GetLog( self ): return self._log
def GetPreviewImageCache( self ): return self._preview_image_cache
2013-03-15 02:38:12 +00:00
def GetSessionKey( self, service_identifier ): return self._session_manager.GetSessionKey( service_identifier )
2013-05-15 18:58:14 +00:00
def GetTagParentsManager( self ): return self._tag_parents_manager
2013-05-08 20:31:00 +00:00
def GetTagSiblingsManager( self ): return self._tag_siblings_manager
2013-04-03 20:56:07 +00:00
2013-02-19 00:11:43 +00:00
def GetThumbnailCache( self ): return self._thumbnail_cache
2013-05-08 20:31:00 +00:00
def GetWebCookies( self, name ): return self._web_session_manager.GetCookies( name )
2013-02-19 00:11:43 +00:00
def MaintainDB( self ):
2013-07-24 20:26:00 +00:00
sys.stdout.flush()
sys.stderr.flush()
2013-07-10 20:25:57 +00:00
gc.collect()
2013-07-31 21:26:38 +00:00
now = HC.GetNow()
2013-02-19 00:11:43 +00:00
shutdown_timestamps = self.Read( 'shutdown_timestamps' )
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] > 86400 * 5: self.Write( 'vacuum' )
2013-05-08 20:31:00 +00:00
# try no fatten, since we made the recent A/C changes
#if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_FATTEN_AC_CACHE ] > 50000: self.Write( 'fatten_autocomplete_cache' )
2013-02-19 00:11:43 +00:00
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_DELETE_ORPHANS ] > 86400 * 3: self.Write( 'delete_orphans' )
def OnInit( self ):
2013-07-10 20:25:57 +00:00
HC.app = self
2013-02-19 00:11:43 +00:00
try:
2013-08-28 21:31:52 +00:00
try:
def make_temp_files_deletable( function_called, path, traceback_gumpf ):
os.chmod( path, stat.S_IWRITE )
function_called( path ) # try again
if os.path.exists( HC.TEMP_DIR ): shutil.rmtree( HC.TEMP_DIR, onerror = make_temp_files_deletable )
except: pass
try:
if not os.path.exists( HC.TEMP_DIR ): os.mkdir( HC.TEMP_DIR )
except: pass
2013-02-19 00:11:43 +00:00
self._splash = ClientGUI.FrameSplash()
self.SetSplashText( 'log' )
self._log = CC.Log()
self.SetSplashText( 'db' )
2013-05-29 20:19:54 +00:00
db_initialised = False
while not db_initialised:
try:
self._db = ClientDB.DB()
db_initialised = True
2013-07-24 20:26:00 +00:00
except HydrusExceptions.DBAccessException as e:
2013-05-29 20:19:54 +00:00
2013-09-04 16:48:44 +00:00
print( repr( HC.u( e ) ) )
2013-05-29 20:19:54 +00:00
message = 'This instance of the client had a problem connecting to the database, which probably means an old instance is still closing.'
message += os.linesep + os.linesep
2013-06-12 22:53:31 +00:00
message += 'If the old instance does not close for a _very_ long time, you can usually safely force-close it from task manager.'
2013-05-29 20:19:54 +00:00
with ClientGUIDialogs.DialogYesNo( None, message, yes_label = 'wait a bit, then try again', no_label = 'quit now' ) as dlg:
if dlg.ShowModal() == wx.ID_YES: time.sleep( 3 )
else: return False
2013-02-19 00:11:43 +00:00
2013-08-28 21:31:52 +00:00
threading.Thread( target = self._db.MainLoop, name = 'Database Main Loop' ).start()
2013-03-15 02:38:12 +00:00
self._session_manager = HydrusSessions.HydrusSessionManagerClient()
2013-04-03 20:56:07 +00:00
self._web_session_manager = CC.WebSessionManagerClient()
2013-07-17 20:56:13 +00:00
self._tag_parents_manager = HydrusTags.TagParentsManager()
self._tag_siblings_manager = HydrusTags.TagSiblingsManager()
2013-07-10 20:25:57 +00:00
self._undo_manager = CC.UndoManager()
2013-03-15 02:38:12 +00:00
2013-02-19 00:11:43 +00:00
self.SetSplashText( 'caches' )
2013-08-14 20:21:49 +00:00
self._fullscreen_image_cache = CC.RenderedImageCache( 'fullscreen' )
self._preview_image_cache = CC.RenderedImageCache( 'preview' )
2013-02-19 00:11:43 +00:00
2013-08-14 20:21:49 +00:00
self._thumbnail_cache = CC.ThumbnailCache()
2013-02-19 00:11:43 +00:00
CC.GlobalBMPs.STATICInitialise()
self.SetSplashText( 'gui' )
self._gui = ClientGUI.FrameGUI()
HC.pubsub.sub( self, 'Clipboard', 'clipboard' )
self.Bind( HC.EVT_PUBSUB, self.EventPubSub )
# 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.Bind( wx.EVT_TIMER, self.EventAnimatedTimer, id = ID_ANIMATED_EVENT_TIMER )
self._animated_event_timer = wx.Timer( self, ID_ANIMATED_EVENT_TIMER )
self._animated_event_timer.Start( 1000, wx.TIMER_CONTINUOUS )
self.SetSplashText( 'starting daemons' )
2013-04-03 20:56:07 +00:00
if HC.is_first_start: self._gui.DoFirstStart()
2013-08-14 20:21:49 +00:00
if HC.is_db_updated: wx.CallAfter( HC.pubsub.pub, 'message', HC.Message( HC.MESSAGE_TYPE_TEXT, 'The client has updated to version ' + HC.u( HC.SOFTWARE_VERSION ) + '!' ) )
2013-04-03 20:56:07 +00:00
2013-08-28 21:31:52 +00:00
self._db.StartServer()
self._db.StartDaemons()
2013-02-19 00:11:43 +00:00
self._last_idle_time = 0.0
self.Bind( wx.EVT_TIMER, self.EventMaintenanceTimer, id = ID_MAINTENANCE_EVENT_TIMER )
self._maintenance_event_timer = wx.Timer( self, ID_MAINTENANCE_EVENT_TIMER )
self._maintenance_event_timer.Start( 20 * 60000, wx.TIMER_CONTINUOUS )
2013-05-29 20:19:54 +00:00
except sqlite3.OperationalError as e:
2013-07-31 21:26:38 +00:00
message = 'Database error!' + os.linesep + os.linesep + traceback.format_exc()
2013-05-29 20:19:54 +00:00
print message
2013-04-03 20:56:07 +00:00
wx.MessageBox( message )
return False
2013-07-24 20:26:00 +00:00
except HydrusExceptions.PermissionException as e: pass
2013-02-19 00:11:43 +00:00
except:
wx.MessageBox( 'Woah, bad error:' + os.linesep + os.linesep + traceback.format_exc() )
try: self._splash.Close()
except: pass
return False
self._splash.Close()
return True
def PrepStringForDisplay( self, text ):
2013-08-14 20:21:49 +00:00
if HC.options[ 'gui_capitalisation' ]: return text
2013-02-19 00:11:43 +00:00
else: return text.lower()
2013-04-24 21:23:53 +00:00
def Read( self, action, *args, **kwargs ):
2013-07-31 21:26:38 +00:00
self._last_idle_time = HC.GetNow()
2013-04-24 21:23:53 +00:00
return self._Read( action, *args, **kwargs )
2013-07-31 21:26:38 +00:00
def ReadDaemon( self, action, *args, **kwargs ):
result = self._Read( action, *args, **kwargs )
time.sleep( 0.1 )
return result
2013-04-24 21:23:53 +00:00
2013-02-19 00:11:43 +00:00
def SetSplashText( self, text ):
self._splash.SetText( text )
self.Yield() # this processes the event queue immediately, so the paint event can occur
2013-08-28 21:31:52 +00:00
def StartFileQuery( self, query_key, search_context ):
threading.Thread( target = self.THREADDoFileQuery, name = 'file query', args = ( query_key, search_context ) ).start()
def THREADDoFileQuery( self, query_key, search_context ):
try:
query_hash_ids = HC.app.Read( 'file_query_ids', search_context )
query_hash_ids = list( query_hash_ids )
random.shuffle( query_hash_ids )
limit = search_context.GetSystemPredicates().GetLimit()
if limit is not None: query_hash_ids = query_hash_ids[ : limit ]
file_service_identifier = search_context.GetFileServiceIdentifier()
include_current_tags = search_context.IncludeCurrentTags()
media_results = []
include_pending_tags = search_context.IncludePendingTags()
i = 0
base = 256
while i < len( query_hash_ids ):
if query_key.IsCancelled(): return
if i == 0: ( last_i, i ) = ( 0, base )
else: ( last_i, i ) = ( i, i + base )
sub_query_hash_ids = query_hash_ids[ last_i : i ]
more_media_results = HC.app.Read( 'media_results_from_ids', file_service_identifier, sub_query_hash_ids )
media_results.extend( more_media_results )
HC.pubsub.pub( 'set_num_query_results', len( media_results ), len( query_hash_ids ) )
HC.app.WaitUntilGoodTimeToUseGUIThread()
HC.pubsub.pub( 'file_query_done', query_key, media_results )
except Exception as e: HC.ShowException( e )
2013-02-19 00:11:43 +00:00
def WaitUntilGoodTimeToUseGUIThread( self ):
pubsubs_queue = HC.pubsub.GetQueue()
while True:
if HC.shutdown: raise Exception( 'Client shutting down!' )
2013-08-28 21:31:52 +00:00
elif pubsubs_queue.qsize() == 0 and not HC.busy_doing_pubsub: return
2013-05-29 20:19:54 +00:00
else: time.sleep( 0.0001 )
2013-02-19 00:11:43 +00:00
def Write( self, action, *args, **kwargs ):
2013-07-31 21:26:38 +00:00
self._last_idle_time = HC.GetNow()
2013-02-19 00:11:43 +00:00
2013-07-10 22:14:47 +00:00
if False and action == 'content_updates': self._undo_manager.AddCommand( 'content_updates', *args, **kwargs )
2013-07-10 20:25:57 +00:00
2013-07-31 21:26:38 +00:00
return self._Write( action, HC.HIGH_PRIORITY, False, *args, **kwargs )
2013-02-19 00:11:43 +00:00
2013-08-14 20:21:49 +00:00
def WriteSynchronous( self, action, *args, **kwargs ):
2013-07-31 21:26:38 +00:00
result = self._Write( action, HC.LOW_PRIORITY, True, *args, **kwargs )
time.sleep( 0.1 )
return result
2013-02-19 00:11:43 +00:00