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