diff --git a/help/changelog.html b/help/changelog.html
index 15480e95..4cffd33e 100755
--- a/help/changelog.html
+++ b/help/changelog.html
@@ -8,6 +8,24 @@
changelog
+ version 125
+
+ - moved client splash screen and client boot to application event loop (i.e. your mouse won't hourglass over it now)
+ - splash screen is now draggable
+ - added splash screen for client shutdown
+ - cleaned up shutdown procedure
+ - added detailed A/C timing options
+ - created a new text entry dialog that won't allow ok on empty text
+ - hence fixed a startup bug when a session was created with no name (and likely others!)
+ - further tweaked idle calculation to improve maintenance routine
+ - revised a bunch of the maintenance timer logic
+ - fixed some bugs in the maintenance timer logic
+ - fixed a daemon error reporting typo
+ - fixed another way session data usage tracking could split, server-side
+ - brushed some more dust off the server-side session code
+ - improved some bad image processing nomenclature
+ - took first steps in service_identifier replacement rewrite
+
version 124
- fixed some more broken gifs (those with funky transparency, I think)
diff --git a/include/ClientConstants.py b/include/ClientConstants.py
index 5d459af2..2065dcf3 100755
--- a/include/ClientConstants.py
+++ b/include/ClientConstants.py
@@ -191,6 +191,7 @@ CLIENT_DEFAULT_OPTIONS[ 'password' ] = None
CLIENT_DEFAULT_OPTIONS[ 'num_autocomplete_chars' ] = 2
CLIENT_DEFAULT_OPTIONS[ 'gui_capitalisation' ] = False
CLIENT_DEFAULT_OPTIONS[ 'default_gui_session' ] = 'just a blank page'
+CLIENT_DEFAULT_OPTIONS[ 'ac_timings' ] = ( 3, 500, 250 )
system_predicates = {}
@@ -2283,17 +2284,19 @@ class Service( HC.HydrusYAMLBase ):
yaml_tag = u'!Service'
- def __init__( self, service_identifier, info ):
+ def __init__( self, service_key, service_type, name, info ):
HC.HydrusYAMLBase.__init__( self )
- self._service_identifier = service_identifier
+ self._key = service_key
+ self._type = service_type
+ self._name = name
self._info = info
HC.pubsub.sub( self, 'ProcessServiceUpdates', 'service_updates_data' )
- def __hash__( self ): return self._service_identifier.__hash__()
+ def __hash__( self ): return self._key.__hash__()
def CanDownload( self ): return self._info[ 'account' ].HasPermission( HC.GET_DATA ) and not self.HasRecentError()
@@ -2326,20 +2329,26 @@ class Service( HC.HydrusYAMLBase ):
else: return self._info[ key ]
+ def GetKey( self ): return self._key
+
def GetLikeDislike( self ): return ( self._info[ 'like' ], self._info[ 'dislike' ] )
def GetLowerUpper( self ): return ( self._info[ 'lower' ], self._info[ 'upper' ] )
+ def GetName( self ): return self._name
+
def GetRecentErrorPending( self ):
if 'account' in self._info and self._info[ 'account' ].HasPermission( HC.GENERAL_ADMIN ): return HC.ConvertTimestampToPrettyPending( self._info[ 'last_error' ] + 600 )
else: return HC.ConvertTimestampToPrettyPending( self._info[ 'last_error' ] + 3600 * 4 )
- def GetServiceIdentifier( self ): return self._service_identifier
+ def GetServiceIdentifier( self ): return HC.ClientServiceIdentifier( self._key, self._type, self._name )
def GetTimestamps( self ): return ( self._info[ 'first_timestamp' ], self._info[ 'next_download_timestamp' ], self._info[ 'next_processing_timestamp' ] )
+ def GetType( self ): return self._type
+
def GetUpdateStatus( self ):
account = self._info[ 'account' ]
@@ -2384,9 +2393,7 @@ class Service( HC.HydrusYAMLBase ):
def IsInitialised( self ):
- service_type = self._service_identifier.GetType()
-
- if service_type == HC.SERVER_ADMIN: return 'access_key' in self._info
+ if self._type == HC.SERVER_ADMIN: return 'access_key' in self._info
else: return True
@@ -2398,7 +2405,7 @@ class Service( HC.HydrusYAMLBase ):
for service_update in service_updates:
- if service_identifier == self._service_identifier:
+ if service_identifier.GetServiceKey() == self._key:
( action, row ) = service_update.ToTuple()
@@ -2420,9 +2427,7 @@ class Service( HC.HydrusYAMLBase ):
num_bytes = row
- service_type = self._service_identifier.GetType()
-
- if service_type == HC.LOCAL_BOORU:
+ if self._type == HC.LOCAL_BOORU:
self._info[ 'used_monthly_data' ] += num_bytes
self._info[ 'used_monthly_requests' ] += 1
@@ -2462,7 +2467,7 @@ class Service( HC.HydrusYAMLBase ):
if command in ( 'access_key', 'init' ): pass
elif command in ( 'session_key', 'access_key_verification' ): HydrusNetworking.AddHydrusCredentialsToHeaders( credentials, request_headers )
- else: HydrusNetworking.AddHydrusSessionKeyToHeaders( self._service_identifier, request_headers )
+ else: HydrusNetworking.AddHydrusSessionKeyToHeaders( self.GetServiceIdentifier(), request_headers )
if command == 'backup': long_timeout = True
else: long_timeout = False
@@ -2506,12 +2511,12 @@ class Service( HC.HydrusYAMLBase ):
( response, size_of_response, response_headers, cookies ) = HC.http.Request( method, url, request_headers, body, report_hooks = report_hooks, response_to_path = response_to_path, return_everything = True, long_timeout = long_timeout )
- HydrusNetworking.CheckHydrusVersion( self._service_identifier, response_headers )
+ HydrusNetworking.CheckHydrusVersion( self.GetServiceIdentifier(), response_headers )
if method == HC.GET: data_used = size_of_response
elif method == HC.POST: data_used = len( body )
- HydrusNetworking.DoHydrusBandwidth( self._service_identifier, method, command, data_used )
+ HydrusNetworking.DoHydrusBandwidth( self.GetServiceIdentifier(), method, command, data_used )
if return_cookies: return ( response, cookies )
else: return response
@@ -2524,15 +2529,15 @@ class Service( HC.HydrusYAMLBase ):
session_manager = HC.app.GetManager( 'hydrus_sessions' )
- session_manager.DeleteSessionKey( self._service_identifier )
+ session_manager.DeleteSessionKey( self.GetServiceIdentifier() )
- HC.app.Write( 'service_updates', { self._service_identifier : [ HC.ServiceUpdate( HC.SERVICE_UPDATE_ERROR, HC.u( e ) ) ] } )
+ HC.app.Write( 'service_updates', { self.GetServiceIdentifier() : [ HC.ServiceUpdate( HC.SERVICE_UPDATE_ERROR, HC.u( e ) ) ] } )
if isinstance( e, ( HydrusExceptions.PermissionException, HydrusExceptions.NetworkVersionException ) ):
- HC.app.Write( 'service_updates', { self._service_identifier : [ HC.ServiceUpdate( HC.SERVICE_UPDATE_ACCOUNT, HC.GetUnknownAccount() ) ] } )
+ HC.app.Write( 'service_updates', { self.GetServiceIdentifier() : [ HC.ServiceUpdate( HC.SERVICE_UPDATE_ACCOUNT, HC.GetUnknownAccount() ) ] } )
raise
@@ -2549,6 +2554,52 @@ class Service( HC.HydrusYAMLBase ):
if credentials.HasAccessKey(): self._info[ 'access_key' ] = credentials.GetAccessKey()
+class ServiceManager( object ):
+
+ def __init__( self ):
+
+ self._lock = threading.Lock()
+
+ self.RefreshServices()
+
+ HC.pubsub.sub( self, 'RefreshServices', 'notify_new_services_data' )
+
+
+ def GetName( self, service_key ):
+
+ with self._lock:
+
+ service = self._keys_to_services[ service_key ]
+
+ return service.GetName()
+
+
+
+ def GetService( self, service_key ):
+
+ with self._lock: return self._keys_to_services[ service_key ]
+
+
+ def GetType( self, service_key ):
+
+ with self._lock:
+
+ service = self._keys_to_services[ service_key ]
+
+ return service.GetType()
+
+
+
+ def RefreshServices( self ):
+
+ with self._lock:
+
+ services = HC.app.Read( 'services' )
+
+ self._keys_to_services = { service.GetKey() : service for service in services }
+
+
+
class ThumbnailCache( object ):
def __init__( self ):
diff --git a/include/ClientController.py b/include/ClientController.py
index dbba5994..f4e3bde6 100755
--- a/include/ClientController.py
+++ b/include/ClientController.py
@@ -31,6 +31,8 @@ from twisted.internet import defer
ID_ANIMATED_EVENT_TIMER = wx.NewId()
ID_MAINTENANCE_EVENT_TIMER = wx.NewId()
+MAINTENANCE_PERIOD = 12 * 60
+
class Controller( wx.App ):
def _Read( self, action, *args, **kwargs ): return self._db.Read( action, HC.HIGH_PRIORITY, *args, **kwargs )
@@ -110,15 +112,7 @@ The database will be locked while the backup occurs, which may lock up your gui
- def CurrentlyIdle( self ):
-
- if HC.GetNow() - self._last_idle_time > 60 * 60: # a long time, so we probably just woke up from a sleep
-
- self._last_idle_time = HC.GetNow()
-
-
- return HC.GetNow() - self._last_idle_time > 20 * 60 # 20 mins since last user-initiated db request
-
+ def CurrentlyIdle( self ): return HC.GetNow() - self._timestamps[ 'last_user_db_use' ] > 30 * 60 # 30 mins since last user-initiated media_results query
def EventPubSub( self, event ):
@@ -128,6 +122,8 @@ The database will be locked while the backup occurs, which may lock up your gui
finally: HC.currently_doing_pubsub = False
+ def GetDB( self ): return self._db
+
def GetFullscreenImageCache( self ): return self._fullscreen_image_cache
def GetGUI( self ): return self._gui
@@ -140,6 +136,117 @@ The database will be locked while the backup occurs, which may lock up your gui
def GetThumbnailCache( self ): return self._thumbnail_cache
+ def InitCheckPassword( self ):
+
+ while True:
+
+ with wx.PasswordEntryDialog( None, 'Enter your password', 'Enter password' ) as dlg:
+
+ if dlg.ShowModal() == wx.ID_OK:
+
+ if hashlib.sha256( dlg.GetValue() ).digest() == HC.options[ 'password' ]: break
+
+ else: raise HydrusExceptions.PermissionException()
+
+
+
+
+ def InitDB( self ):
+
+ self._log = CC.Log()
+
+ 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
+
+ db_initialised = False
+
+ while not db_initialised:
+
+ try:
+
+ self._db = ClientDB.DB()
+
+ db_initialised = True
+
+ except HydrusExceptions.DBAccessException as e:
+
+ try: print( HC.u( e ) )
+ except: print( repr( HC.u( e ) ) )
+
+ 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
+ message += 'If the old instance does not close for a _very_ long time, you can usually safely force-close it from task manager.'
+
+ with ClientGUIDialogs.DialogYesNo( None, message, yes_label = 'wait a bit, then try again', no_label = 'forget it' ) as dlg:
+
+ if dlg.ShowModal() == wx.ID_YES: time.sleep( 3 )
+ else: raise HydrusExceptions.PermissionException()
+
+
+
+
+ threading.Thread( target = self._db.MainLoop, name = 'Database Main Loop' ).start()
+
+
+ def InitGUI( self ):
+
+ self._managers = {}
+
+ self._managers[ 'hydrus_sessions' ] = HydrusSessions.HydrusSessionManagerClient()
+ self._managers[ 'local_booru' ] = CC.LocalBooruCache()
+ self._managers[ 'services' ] = CC.ServiceManager()
+ self._managers[ 'tag_censorship' ] = HydrusTags.TagCensorshipManager()
+ self._managers[ 'tag_siblings' ] = HydrusTags.TagSiblingsManager()
+ self._managers[ 'tag_parents' ] = HydrusTags.TagParentsManager()
+ self._managers[ 'undo' ] = CC.UndoManager()
+ self._managers[ 'web_sessions' ] = HydrusSessions.WebSessionManagerClient()
+
+ self._fullscreen_image_cache = CC.RenderedImageCache( 'fullscreen' )
+ self._preview_image_cache = CC.RenderedImageCache( 'preview' )
+
+ self._thumbnail_cache = CC.ThumbnailCache()
+
+ CC.GlobalBMPs.STATICInitialise()
+
+ self._gui = ClientGUI.FrameGUI()
+
+ HC.pubsub.sub( self, 'Clipboard', 'clipboard' )
+ HC.pubsub.sub( self, 'RestartServer', 'restart_server' )
+ HC.pubsub.sub( self, 'RestartBooru', 'restart_booru' )
+
+ self.Bind( wx.EVT_TIMER, self.TIMEREventMaintenance, id = ID_MAINTENANCE_EVENT_TIMER )
+
+ self._maintenance_event_timer = wx.Timer( self, ID_MAINTENANCE_EVENT_TIMER )
+ self._maintenance_event_timer.Start( MAINTENANCE_PERIOD * 1000, wx.TIMER_CONTINUOUS )
+
+ # 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() )
+
+ if HC.is_first_start: wx.CallAfter( self._gui.DoFirstStart )
+ if HC.is_db_updated: wx.CallLater( 1, HC.ShowText, 'The client has updated to version ' + HC.u( HC.SOFTWARE_VERSION ) + '!' )
+
+ self.RestartServer()
+ self.RestartBooru()
+ self._db.StartDaemons()
+
+
def MaintainDB( self ):
sys.stdout.flush()
@@ -154,12 +261,18 @@ The database will be locked while the backup occurs, which may lock up your gui
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] > 86400 * 5: self.Write( 'vacuum' )
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_DELETE_ORPHANS ] > 86400 * 3: self.Write( 'delete_orphans' )
- if now - self._timestamps[ 'service_info_cache_fatten' ] > 60 * 20:
+ if now - self._timestamps[ 'last_service_info_cache_fatten' ] > 60 * 20:
+
+ HC.pubsub.pub( 'set_splash_text', 'fattening service info' )
service_identifiers = self.Read( 'service_identifiers' )
for service_identifier in service_identifiers: self.Read( 'service_info', service_identifier )
+ self._timestamps[ 'service_info_cache_fatten' ] = HC.GetNow()
+
+
+ HC.pubsub.pub( 'clear_closed_pages' )
def OnInit( self ):
@@ -174,156 +287,25 @@ The database will be locked while the backup occurs, which may lock up your gui
self._local_service = None
self._booru_service = None
- init_result = True
+ self.Bind( HC.EVT_PUBSUB, self.EventPubSub )
try:
- 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
+ splash = ClientGUI.FrameSplash( 'boot' )
- try:
-
- if not os.path.exists( HC.TEMP_DIR ): os.mkdir( HC.TEMP_DIR )
-
- except: pass
+ return True
- self._splash = ClientGUI.FrameSplash()
-
- self.SetSplashText( 'log' )
-
- self._log = CC.Log()
-
- self.SetSplashText( 'db' )
-
- db_initialised = False
-
- while not db_initialised:
-
- try:
-
- self._db = ClientDB.DB()
-
- db_initialised = True
-
- except HydrusExceptions.DBAccessException as e:
-
- try: print( HC.u( e ) )
- except: print( repr( HC.u( e ) ) )
-
- 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
- message += 'If the old instance does not close for a _very_ long time, you can usually safely force-close it from task manager.'
-
- with ClientGUIDialogs.DialogYesNo( None, message, yes_label = 'wait a bit, then try again', no_label = 'forget it' ) as dlg:
-
- if dlg.ShowModal() == wx.ID_YES: time.sleep( 3 )
- else: raise HydrusExceptions.PermissionException()
-
-
-
-
- threading.Thread( target = self._db.MainLoop, name = 'Database Main Loop' ).start()
-
- if HC.options[ 'password' ] is not None:
-
- self.SetSplashText( 'waiting for password' )
-
- while True:
-
- with wx.PasswordEntryDialog( None, 'Enter your password', 'Enter password' ) as dlg:
-
- if dlg.ShowModal() == wx.ID_OK:
-
- if hashlib.sha256( dlg.GetValue() ).digest() == HC.options[ 'password' ]: break
-
- else: raise HydrusExceptions.PermissionException()
-
-
-
-
- self.SetSplashText( 'caches and managers' )
-
- self._managers = {}
-
- self._managers[ 'hydrus_sessions' ] = HydrusSessions.HydrusSessionManagerClient()
- self._managers[ 'tag_censorship' ] = HydrusTags.TagCensorshipManager()
- self._managers[ 'tag_siblings' ] = HydrusTags.TagSiblingsManager()
- self._managers[ 'tag_parents' ] = HydrusTags.TagParentsManager()
- self._managers[ 'undo' ] = CC.UndoManager()
- self._managers[ 'web_sessions' ] = HydrusSessions.WebSessionManagerClient()
- self._managers[ 'local_booru' ] = CC.LocalBooruCache()
-
- self._fullscreen_image_cache = CC.RenderedImageCache( 'fullscreen' )
- self._preview_image_cache = CC.RenderedImageCache( 'preview' )
-
- self._thumbnail_cache = CC.ThumbnailCache()
-
- CC.GlobalBMPs.STATICInitialise()
-
- self.SetSplashText( 'gui' )
-
- self._gui = ClientGUI.FrameGUI()
-
- HC.pubsub.sub( self, 'Clipboard', 'clipboard' )
- HC.pubsub.sub( self, 'RestartServer', 'restart_server' )
- HC.pubsub.sub( self, 'RestartBooru', 'restart_booru' )
-
- 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.SetSplashText( 'starting daemons' )
-
- if HC.is_first_start: self._gui.DoFirstStart()
- if HC.is_db_updated: wx.CallLater( 1, HC.ShowText, 'The client has updated to version ' + HC.u( HC.SOFTWARE_VERSION ) + '!' )
-
- self.RestartServer()
- self.RestartBooru()
- self._db.StartDaemons()
-
- self._last_idle_time = 0.0
-
- self.Bind( wx.EVT_TIMER, self.TIMEREventMaintenance, 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 )
-
- except sqlite3.OperationalError as e:
-
- message = 'Database error!' + os.linesep + os.linesep + traceback.format_exc()
-
- print message
-
- wx.MessageBox( message )
-
- init_result = False
-
- except HydrusExceptions.PermissionException as e: init_result = False
except:
- wx.MessageBox( 'Woah, bad error:' + os.linesep + os.linesep + traceback.format_exc() )
+ print( 'There was an error trying to start the splash screen!' )
- init_result = False
+ print( traceback.format_exc() )
- finally:
-
- try: wx.CallAfter( self._splash.Destroy )
+ try: wx.CallAfter( splash.Destroy )
except: pass
-
- return init_result
+ return False
+
def PrepStringForDisplay( self, text ):
@@ -334,7 +316,7 @@ The database will be locked while the backup occurs, which may lock up your gui
def Read( self, action, *args, **kwargs ):
- if action == 'media_results': self._last_idle_time = HC.GetNow()
+ if action == 'media_results': self._timestamps[ 'last_user_db_use' ] = HC.GetNow()
return self._Read( action, *args, **kwargs )
@@ -503,7 +485,7 @@ Once it is done, the client will restart.'''
self._db.Shutdown()
- while not self._db.GetLoopFinished(): time.sleep( 0.1 )
+ while not self._db.LoopIsFinished(): time.sleep( 0.1 )
self._db.RestoreBackup( path )
@@ -518,12 +500,6 @@ Once it is done, the client will restart.'''
- def SetSplashText( self, text ):
-
- self._splash.SetText( text )
- self.Yield() # this processes the event queue immediately, so the paint event can occur
-
-
def StartFileQuery( self, query_key, search_context ): HydrusThreading.CallToThread( self.THREADDoFileQuery, query_key, search_context )
def THREADDoFileQuery( self, query_key, search_context ):
@@ -577,12 +553,15 @@ Once it is done, the client will restart.'''
def TIMEREventMaintenance( self, event ):
- gc.collect()
+ last_time_this_ran = self._timestamps[ 'last_check_idle_time' ]
+
+ self._timestamps[ 'last_check_idle_time' ] = HC.GetNow()
+
+ # this tests if we probably just woke up from a sleep
+ if HC.GetNow() - last_time_this_ran > MAINTENANCE_PERIOD * 1.2: return
if self.CurrentlyIdle(): self.MaintainDB()
- HC.pubsub.pub( 'clear_closed_pages' )
-
def WaitUntilGoodTimeToUseGUIThread( self ):
@@ -596,8 +575,6 @@ Once it is done, the client will restart.'''
def Write( self, action, *args, **kwargs ):
- self._last_idle_time = HC.GetNow()
-
if action == 'content_updates': self._managers[ 'undo' ].AddCommand( 'content_updates', *args, **kwargs )
return self._Write( action, HC.HIGH_PRIORITY, False, *args, **kwargs )
diff --git a/include/ClientDB.py b/include/ClientDB.py
index a3026b1c..96d871ed 100755
--- a/include/ClientDB.py
+++ b/include/ClientDB.py
@@ -1505,6 +1505,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
def _DeleteOrphans( self, c ):
+ HC.pubsub.pub( 'set_splash_text', 'deleting orphan files' )
+
# careful of the .encode( 'hex' ) business here!
# files
@@ -2670,9 +2672,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
( service_key, service_type, name, info ) = result
- service_identifier = HC.ClientServiceIdentifier( service_key, service_type, name )
-
- service = CC.Service( service_identifier, info )
+ service = CC.Service( service_key, service_type, name, info )
return service
@@ -3856,6 +3856,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
self.pub_service_updates_after_commit( { service_identifier : [ HC.ServiceUpdate( HC.SERVICE_UPDATE_RESET ) ] } )
self.pub_after_commit( 'notify_new_pending' )
+ self.pub_after_commit( 'notify_new_services_data' )
+ self.pub_after_commit( 'notify_new_services_gui' )
self.pub_after_commit( 'permissions_are_stale' )
HC.ShowText( 'reset ' + service_name )
@@ -4388,7 +4390,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
self.pub_after_commit( 'notify_new_pending' )
- self.pub_after_commit( 'notify_new_services' )
+ self.pub_after_commit( 'notify_new_services_data' )
+ self.pub_after_commit( 'notify_new_services_gui' )
def _UpdateServices( self, c, edit_log ):
@@ -4479,7 +4482,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
self.pub_after_commit( 'notify_new_pending' )
- self.pub_after_commit( 'notify_new_services' )
+ self.pub_after_commit( 'notify_new_services_data' )
+ self.pub_after_commit( 'notify_new_services_gui' )
def _UpdateServiceInfo( self, c, service_id, update ):
@@ -4521,7 +4525,7 @@ class DB( ServiceDB ):
while version < HC.SOFTWARE_VERSION:
- HC.app.SetSplashText( 'updating db to v' + HC.u( version + 1 ) )
+ HC.pubsub.pub( 'set_splash_text', 'updating db to v' + HC.u( version + 1 ) )
time.sleep( 2 )
@@ -4993,7 +4997,7 @@ class DB( ServiceDB ):
i += 1
- if i % 100 == 0: HC.app.SetSplashText( 'reprocessing thumbs: ' + HC.ConvertIntToPrettyString( i ) )
+ if i % 100 == 0: HC.pubsub.pub( 'set_splash_text', 'reprocessing thumbs: ' + HC.ConvertIntToPrettyString( i ) )
except: print( 'When updating to v118, ' + path + '\'s phash could not be recalculated.' )
@@ -5034,7 +5038,7 @@ class DB( ServiceDB ):
i += 1
- if i % 100 == 0: HC.app.SetSplashText( 'creating video thumbs: ' + HC.ConvertIntToPrettyString( i ) )
+ if i % 100 == 0: HC.pubsub.pub( 'set_splash_text', 'creating video thumbs: ' + HC.ConvertIntToPrettyString( i ) )
except:
print( traceback.format_exc())
@@ -5316,7 +5320,7 @@ class DB( ServiceDB ):
for i in range( 0, len( all_local_files ), 100 ):
- HC.app.SetSplashText( 'updating db to v29 ' + HC.u( i ) + '/' + HC.u( len( all_local_files ) ) )
+ HC.pubsub.pub( 'set_splash_text', 'updating db to v29 ' + HC.u( i ) + '/' + HC.u( len( all_local_files ) ) )
local_files_subset = all_local_files[ i : i + 100 ]
@@ -5366,7 +5370,7 @@ class DB( ServiceDB ):
for i in range( 0, len( all_thumbnails ), 500 ):
- HC.app.SetSplashText( 'updating db to v30 ' + HC.u( i ) + '/' + HC.u( len( all_thumbnails ) ) )
+ HC.pubsub.pub( 'set_splash_text', 'updating db to v30 ' + HC.u( i ) + '/' + HC.u( len( all_thumbnails ) ) )
thumbnails_subset = all_thumbnails[ i : i + 500 ]
@@ -5439,7 +5443,7 @@ class DB( ServiceDB ):
for i in range( 0, len( hashes ), 100 ):
- HC.app.SetSplashText( 'updating db to v33 ' + HC.u( i ) + '/' + HC.u( len( hashes ) ) )
+ HC.pubsub.pub( 'set_splash_text', 'updating db to v33 ' + HC.u( i ) + '/' + HC.u( len( hashes ) ) )
hashes_subset = hashes[ i : i + 100 ]
@@ -5477,7 +5481,7 @@ class DB( ServiceDB ):
# can't do it inside transaction
- HC.app.SetSplashText( 'consolidating db - preparing' )
+ HC.pubsub.pub( 'set_splash_text', 'consolidating db - preparing' )
c.execute( 'ATTACH database "' + main_db_path + '" as main_db;' )
c.execute( 'ATTACH database "' + files_info_db_path + '" as files_info_db;' )
@@ -5509,7 +5513,7 @@ class DB( ServiceDB ):
c.execute( 'DELETE FROM main.options;' )
- HC.app.SetSplashText( 'consolidating db - 1/4' )
+ HC.pubsub.pub( 'set_splash_text', 'consolidating db - 1/4' )
c.execute( 'REPLACE INTO main.accounts SELECT * FROM main_db.accounts;' )
c.execute( 'REPLACE INTO main.addresses SELECT * FROM main_db.addresses;' )
@@ -5528,18 +5532,18 @@ class DB( ServiceDB ):
c.execute( 'REPLACE INTO main.tags SELECT * FROM main_db.tags;' )
# don't do version, lol
- HC.app.SetSplashText( 'consolidating db - 2/4' )
+ HC.pubsub.pub( 'set_splash_text', 'consolidating db - 2/4' )
c.execute( 'REPLACE INTO main.deleted_mappings SELECT * FROM mappings_db.deleted_mappings;' )
c.execute( 'REPLACE INTO main.mappings SELECT * FROM mappings_db.mappings;' )
c.execute( 'REPLACE INTO main.mapping_petitions SELECT * FROM mappings_db.mapping_petitions;' )
c.execute( 'REPLACE INTO main.pending_mappings SELECT * FROM mappings_db.pending_mappings;' )
- HC.app.SetSplashText( 'consolidating db - 3/4' )
+ HC.pubsub.pub( 'set_splash_text', 'consolidating db - 3/4' )
c.execute( 'REPLACE INTO main.active_mappings SELECT * FROM active_mappings_db.active_mappings;' )
- HC.app.SetSplashText( 'consolidating db - 4/4' )
+ HC.pubsub.pub( 'set_splash_text', 'consolidating db - 4/4' )
c.execute( 'REPLACE INTO main.deleted_files SELECT * FROM files_info_db.deleted_files;' )
c.execute( 'REPLACE INTO main.files_info SELECT * FROM files_info_db.files_info;' )
@@ -5551,7 +5555,7 @@ class DB( ServiceDB ):
c.execute( 'COMMIT' )
- HC.app.SetSplashText( 'consolidating db - cleaning up' )
+ HC.pubsub.pub( 'set_splash_text', 'consolidating db - cleaning up' )
c.execute( 'DETACH database main_db;' )
c.execute( 'DETACH database files_info_db;' )
@@ -5959,7 +5963,7 @@ class DB( ServiceDB ):
c.execute( 'DELETE FROM active_pending_mappings WHERE tag_id = ?;', ( tag_id, ) )
- HC.app.SetSplashText( 'making new cache, may take a minute' )
+ HC.pubsub.pub( 'set_splash_text', 'making new cache, may take a minute' )
c.execute( 'CREATE TABLE existing_tags ( namespace_id INTEGER, tag_id INTEGER, PRIMARY KEY( namespace_id, tag_id ) );' )
c.execute( 'CREATE INDEX existing_tags_tag_id_index ON existing_tags ( tag_id );' )
@@ -6024,7 +6028,7 @@ class DB( ServiceDB ):
if version == 51:
- HC.app.SetSplashText( 'making new indices' )
+ HC.pubsub.pub( 'set_splash_text', 'making new indices' )
c.execute( 'DROP INDEX mappings_namespace_id_index;' )
c.execute( 'DROP INDEX mappings_tag_id_index;' )
@@ -6032,7 +6036,7 @@ class DB( ServiceDB ):
c.execute( 'CREATE INDEX mappings_service_id_tag_id_index ON mappings ( service_id, tag_id );' )
c.execute( 'CREATE INDEX mappings_service_id_hash_id_index ON mappings ( service_id, hash_id );' )
- HC.app.SetSplashText( 'making some more new indices' )
+ HC.pubsub.pub( 'set_splash_text', 'making some more new indices' )
c.execute( 'DROP INDEX pending_mappings_namespace_id_index;' )
c.execute( 'DROP INDEX pending_mappings_tag_id_index;' )
@@ -6187,7 +6191,7 @@ class DB( ServiceDB ):
if version == 64:
- HC.app.SetSplashText( 'renaming db files' )
+ HC.pubsub.pub( 'set_splash_text', 'renaming db files' )
filenames = dircache.listdir( HC.CLIENT_FILES_DIR )
@@ -6214,7 +6218,7 @@ class DB( ServiceDB ):
i += 1
- if i % 250 == 0: HC.app.SetSplashText( 'renaming file ' + HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( filenames ) ) )
+ if i % 250 == 0: HC.pubsub.pub( 'set_splash_text', 'renaming file ' + HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( filenames ) ) )
c.execute( 'CREATE TABLE subscriptions ( subscriptions TEXT_YAML );' )
@@ -6251,7 +6255,7 @@ class DB( ServiceDB ):
#
- HC.app.SetSplashText( 'creating new db directories' )
+ HC.pubsub.pub( 'set_splash_text', 'creating new db directories' )
hex_chars = '0123456789abcdef'
@@ -6266,7 +6270,7 @@ class DB( ServiceDB ):
if not os.path.exists( dir ): os.mkdir( dir )
- HC.app.SetSplashText( 'generating file cache' )
+ HC.pubsub.pub( 'set_splash_text', 'generating file cache' )
filenames = dircache.listdir( HC.CLIENT_FILES_DIR )
@@ -6288,10 +6292,10 @@ class DB( ServiceDB ):
i += 1
- if i % 100 == 0: HC.app.SetSplashText( 'moving files - ' + HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( filenames ) ) )
+ if i % 100 == 0: HC.pubsub.pub( 'set_splash_text', 'moving files - ' + HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( filenames ) ) )
- HC.app.SetSplashText( 'generating thumbnail cache' )
+ HC.pubsub.pub( 'set_splash_text', 'generating thumbnail cache' )
filenames = dircache.listdir( HC.CLIENT_THUMBNAILS_DIR )
@@ -6313,7 +6317,7 @@ class DB( ServiceDB ):
i += 1
- if i % 100 == 0: HC.app.SetSplashText( 'moving thumbnails - ' + HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( filenames ) ) )
+ if i % 100 == 0: HC.pubsub.pub( 'set_splash_text', 'moving thumbnails - ' + HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( filenames ) ) )
@@ -6958,7 +6962,7 @@ class DB( ServiceDB ):
for ( i, service_id ) in enumerate( service_ids ):
- HC.app.SetSplashText( 'copying mappings ' + str( i ) + '/' + str( len( service_ids ) ) )
+ HC.pubsub.pub( 'set_splash_text', 'copying mappings ' + str( i ) + '/' + str( len( service_ids ) ) )
c.execute( 'INSERT INTO processed_mappings SELECT * FROM mappings WHERE service_id = ?;', ( service_id, ) )
@@ -6976,7 +6980,7 @@ class DB( ServiceDB ):
for ( i, filename ) in enumerate( current_updates ):
- if i % 100 == 0: HC.app.SetSplashText( 'renaming updates ' + str( i ) + '/' + str( len( current_updates ) ) )
+ if i % 100 == 0: HC.pubsub.pub( 'set_splash_text', 'renaming updates ' + str( i ) + '/' + str( len( current_updates ) ) )
( service_key_hex, gumpf ) = filename.split( '_' )
@@ -7024,6 +7028,8 @@ class DB( ServiceDB ):
def _Vacuum( self ):
+ HC.pubsub.pub( 'set_splash_text', 'vacuuming db' )
+
( db, c ) = self._GetDBCursor()
c.execute( 'VACUUM' )
@@ -7035,8 +7041,6 @@ class DB( ServiceDB ):
HC.ShowText( 'vacuumed successfully' )
- def GetLoopFinished( self ): return self._loop_finished
-
def pub_after_commit( self, topic, *args, **kwargs ): self._pubsubs.append( ( topic, args, kwargs ) )
def pub_content_updates_after_commit( self, service_identifiers_to_content_updates ):
@@ -7051,6 +7055,8 @@ class DB( ServiceDB ):
self.pub_after_commit( 'service_updates_gui', service_identifiers_to_service_updates )
+ def LoopIsFinished( self ): return self._loop_finished
+
def MainLoop( self ):
def ProcessJob( c, job ):
@@ -7304,7 +7310,7 @@ class DB( ServiceDB ):
HydrusThreading.DAEMONWorker( 'DownloadFiles', DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) )
HydrusThreading.DAEMONWorker( 'DownloadThumbnails', DAEMONDownloadThumbnails, ( 'notify_new_permissions', 'notify_new_thumbnails' ) )
HydrusThreading.DAEMONWorker( 'ResizeThumbnails', DAEMONResizeThumbnails, init_wait = 600 )
- HydrusThreading.DAEMONWorker( 'SynchroniseAccounts', DAEMONSynchroniseAccounts, ( 'notify_new_services', 'permissions_are_stale' ) )
+ HydrusThreading.DAEMONWorker( 'SynchroniseAccounts', DAEMONSynchroniseAccounts, ( 'notify_new_services_gui', 'permissions_are_stale' ) )
HydrusThreading.DAEMONWorker( 'SynchroniseRepositories', DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ) )
HydrusThreading.DAEMONWorker( 'SynchroniseSubscriptions', DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ) )
HydrusThreading.DAEMONWorker( 'UPnP', DAEMONUPnP, ( 'notify_new_upnp_mappings', ), pre_callable_wait = 10 )
diff --git a/include/ClientGUI.py b/include/ClientGUI.py
index f9950b04..6fdd9c57 100755
--- a/include/ClientGUI.py
+++ b/include/ClientGUI.py
@@ -6,6 +6,7 @@ import ClientGUIDialogs
import ClientGUIDialogsManage
import ClientGUIPages
import HydrusDownloading
+import HydrusExceptions
import HydrusFileHandling
import HydrusImageHandling
import HydrusNATPunch
@@ -13,6 +14,7 @@ import HydrusThreading
import itertools
import os
import random
+import sqlite3
import subprocess
import sys
import threading
@@ -98,7 +100,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
HC.pubsub.sub( self, 'NotifyNewOptions', 'notify_new_options' )
HC.pubsub.sub( self, 'NotifyNewPending', 'notify_new_pending' )
HC.pubsub.sub( self, 'NotifyNewPermissions', 'notify_new_permissions' )
- HC.pubsub.sub( self, 'NotifyNewServices', 'notify_new_services' )
+ HC.pubsub.sub( self, 'NotifyNewServices', 'notify_new_services_gui' )
HC.pubsub.sub( self, 'NotifyNewSessions', 'notify_new_sessions' )
HC.pubsub.sub( self, 'NotifyNewUndo', 'notify_new_undo' )
HC.pubsub.sub( self, 'RefreshStatusBar', 'refresh_status' )
@@ -317,7 +319,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
def _AccountInfo( self, service_identifier ):
- with wx.TextEntryDialog( self, 'Access key' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter the account\'s access key.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -603,7 +605,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
def _FetchIP( self, service_identifier ):
- with wx.TextEntryDialog( self, 'File Hash' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter the file\'s hash.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -1227,7 +1229,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
service = HC.app.Read( 'service', service_identifier )
- with wx.TextEntryDialog( self, 'Enter the access key for the account to be modified' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter the access key for the account to be modified.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -1387,7 +1389,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
def _PostNews( self, service_identifier ):
- with wx.TextEntryDialog( self, 'Enter the news you would like to post.' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter the news you would like to post.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -1494,7 +1496,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
while True:
- with wx.TextEntryDialog( self, 'enter a name for the new session', 'name session' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter a name for the new session.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -1544,7 +1546,7 @@ Do not ever forget your password! If you do, you'll have to manually insert a ya
The password is cleartext here but obscured in the entry dialog. Enter a blank password to remove.'''
- with wx.TextEntryDialog( self, message, 'Enter new password' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, message, allow_blank = True ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -1580,7 +1582,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
def _StartURLDownload( self ):
- with wx.TextEntryDialog( self, 'Enter URL' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter URL.' ) as dlg:
result = dlg.ShowModal()
@@ -1601,7 +1603,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
def _StartYoutubeDownload( self ):
- with wx.TextEntryDialog( self, 'Enter YouTube URL' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter YouTube URL.' ) as dlg:
result = dlg.ShowModal()
@@ -1704,41 +1706,17 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
- page = self._notebook.GetCurrentPage()
-
- if page is not None:
+ try: splash = FrameSplash( 'exit' )
+ except:
- ( HC.options[ 'hpos' ], HC.options[ 'vpos' ] ) = page.GetSashPositions()
+ print( 'There was an error trying to start the splash screen!' )
-
- HC.app.Write( 'save_options' )
-
- self._SaveGUISession( 'last session' )
-
- for page in [ self._notebook.GetPage( i ) for i in range( self._notebook.GetPageCount() ) ]:
+ print( traceback.format_exc() )
- try: page.TestAbleToClose()
- except Exception as e: return
+ try: wx.CallAfter( splash.Destroy )
+ except: pass
- for page in [ self._notebook.GetPage( i ) for i in range( self._notebook.GetPageCount() ) ]:
-
- try: page.CleanBeforeDestroy()
- except: return
-
-
- self._message_manager.CleanBeforeDestroy()
- self._message_manager.Hide()
-
- self.Hide()
-
- # for some insane reason, the read makes the controller block until the writes are done!??!
- # hence the hide, to make it appear the destroy is actually happening on time
-
- HC.app.MaintainDB()
-
- wx.CallAfter( self.Destroy )
-
def EventFocus( self, event ):
@@ -2078,6 +2056,35 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
+ def Shutdown( self ):
+
+ self._message_manager.Hide()
+
+ self.Hide()
+
+ self._message_manager.CleanBeforeDestroy()
+
+ for page in [ self._notebook.GetPage( i ) for i in range( self._notebook.GetPageCount() ) ]: page.CleanBeforeDestroy()
+
+ page = self._notebook.GetCurrentPage()
+
+ if page is not None:
+
+ ( HC.options[ 'hpos' ], HC.options[ 'vpos' ] ) = page.GetSashPositions()
+
+
+ HC.app.Write( 'save_options' )
+
+ self._SaveGUISession( 'last session' )
+
+ wx.CallAfter( self.Destroy )
+
+
+ def TestAbleToClose( self ):
+
+ for page in [ self._notebook.GetPage( i ) for i in range( self._notebook.GetPageCount() ) ]: page.TestAbleToClose()
+
+
class FrameComposeMessage( ClientGUICommon.Frame ):
def __init__( self, empty_draft_message ):
@@ -2167,7 +2174,7 @@ class FrameReviewServices( ClientGUICommon.Frame ):
self.Show( True )
- HC.pubsub.sub( self, 'RefreshServices', 'notify_new_services' )
+ HC.pubsub.sub( self, 'RefreshServices', 'notify_new_services_gui' )
wx.CallAfter( self.Raise )
@@ -2877,40 +2884,182 @@ class FrameReviewServices( ClientGUICommon.Frame ):
class FrameSplash( ClientGUICommon.Frame ):
- def __init__( self ):
+ def __init__( self, action ):
- wx.Frame.__init__( self, None, style = wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED, title = 'hydrus client' )
+ wx.Frame.__init__( self, None, style = wx.FRAME_NO_TASKBAR, title = 'hydrus client' )
- self._bmp = wx.EmptyBitmap( 154, 220, 32 ) # 32 bit for transparency?
+ self._bmp = wx.EmptyBitmap( 154, 220, 24 )
self.SetSize( ( 154, 220 ) )
self.Center()
+ self._last_drag_coordinates = None
+ self._total_drag_delta = ( 0, 0 )
+ self._initial_position = self.GetPosition()
+
# this is 124 x 166
self._hydrus = wx.Image( HC.STATIC_DIR + os.path.sep + 'hydrus_splash.png', type=wx.BITMAP_TYPE_PNG ).ConvertToBitmap()
- dc = wx.BufferedDC( wx.ClientDC( self ), self._bmp )
-
- dc.SetBackground( wx.Brush( wx.WHITE ) )
-
- dc.Clear()
-
- dc.DrawBitmap( self._hydrus, 15, 15 )
-
self.Bind( wx.EVT_PAINT, self.EventPaint )
+ self.Bind( wx.EVT_MOTION, self.EventDrag )
+ self.Bind( wx.EVT_LEFT_DOWN, self.EventDragBegin )
+ self.Bind( wx.EVT_LEFT_UP, self.EventDragEnd )
+
+ if action == 'boot':
+
+ self.SetText( 'initialising startup' )
+
+ my_thread = threading.Thread( target = self.BootApp, name = 'Application Boot Thread' )
+
+ elif action == 'exit':
+
+ self.SetText( 'initialising shutdown' )
+
+ my_thread = threading.Thread( target = self.ExitApp, name = 'Application Boot Thread' )
+
self.Show( True )
- self.Bind( wx.EVT_MOUSE_EVENTS, self.OnMouseEvents )
+ HC.pubsub.sub( self, 'SetText', 'set_splash_text' )
+
+ wx.CallAfter( my_thread.start )
+
+
+ def BootApp( self ):
+
+ try:
+
+ wx.CallAfter( self.SetText, 'booting db' )
+
+ HC.app.InitDB()
+
+ if HC.options[ 'password' ] is not None:
+
+ wx.CallAfter( self.SetText, 'waiting for password' )
+
+ HC.app.InitCheckPassword()
+
+
+ wx.CallAfter( self.SetText, 'booting gui' )
+
+ wx.CallAfter( HC.app.InitGUI )
+
+ time.sleep( 1 )
+
+ except sqlite3.OperationalError as e:
+
+ text = 'Database error!' + os.linesep + os.linesep + traceback.format_exc()
+
+ print( text )
+
+ wx.CallAfter( wx.MessageBox, text )
+
+ except HydrusExceptions.PermissionException as e: pass
+ except:
+
+ text = 'Woah, bad error during startup!' + os.linesep * 2 + traceback.format_exc()
+
+ print( text )
+
+ wx.CallAfter( wx.MessageBox, text )
+
+ finally:
+
+ try: wx.CallAfter( self.Destroy )
+ except: pass
+
+
+
+ def EventDrag( self, event ):
+
+ if event.Dragging() and self._last_drag_coordinates is not None:
+
+ ( old_x, old_y ) = self._last_drag_coordinates
+
+ ( x, y ) = event.GetPosition()
+
+ ( delta_x, delta_y ) = ( x - old_x, y - old_y )
+
+ ( old_delta_x, old_delta_y ) = self._total_drag_delta
+
+ self._total_drag_delta = ( old_delta_x + delta_x, old_delta_y + delta_y )
+
+ ( init_x, init_y ) = self._initial_position
+
+ ( total_delta_x, total_delta_y ) = self._total_drag_delta
+
+ self.SetPosition( ( init_x + total_delta_x, init_y + total_delta_y ) )
+
+
+
+ def EventDragBegin( self, event ):
+
+ self._last_drag_coordinates = event.GetPosition()
+
+ event.Skip()
+
+
+ def EventDragEnd( self, event ):
+
+ self._last_drag_coordinates = None
+
+ event.Skip()
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._bmp )
- def OnMouseEvents( self, event ): pass
+ def ExitApp( self ):
+
+ try:
+
+ wx.CallAfter( self.SetText, 'exiting gui' )
+
+ gui = HC.app.GetGUI()
+
+ try: gui.TestAbleToClose()
+ except: return
+
+ gui.Shutdown()
+
+ wx.CallAfter( self.SetText, 'exiting db' )
+
+ db = HC.app.GetDB()
+
+ HC.app.MaintainDB()
+
+ db.Shutdown()
+
+ while not db.LoopIsFinished(): time.sleep( 0.1 )
+
+ except sqlite3.OperationalError as e:
+
+ text = 'Database error!' + os.linesep + os.linesep + traceback.format_exc()
+
+ print( text )
+
+ wx.CallAfter( wx.MessageBox, text )
+
+ except HydrusExceptions.PermissionException as e: pass
+ except:
+
+ text = 'Woah, bad error during shutdown!' + os.linesep * 2 + 'You may need to quit the program from task manager.' + os.linesep * 2 + traceback.format_exc()
+
+ print( text )
+
+ wx.CallAfter( wx.MessageBox, text )
+
+ finally:
+
+ try: wx.CallAfter( self.Destroy )
+ except: pass
+
+
def SetText( self, text ):
+ print( text )
+
dc = wx.BufferedDC( wx.ClientDC( self ), self._bmp )
dc.SetBackground( wx.Brush( wx.WHITE ) )
@@ -2927,4 +3076,4 @@ class FrameSplash( ClientGUICommon.Frame ):
dc.DrawText( text, x, 200 )
-
+
\ No newline at end of file
diff --git a/include/ClientGUICanvas.py b/include/ClientGUICanvas.py
index eda2dc27..30f40bfa 100755
--- a/include/ClientGUICanvas.py
+++ b/include/ClientGUICanvas.py
@@ -1401,7 +1401,7 @@ class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaList ):
if interval is None:
- with wx.TextEntryDialog( self, 'Enter the interval, in seconds', defaultValue='15' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter the interval, in seconds.', default = '15' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -1750,7 +1750,7 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaList ):
message = 'Enter a reason for this tag to be removed. A janitor will review your petition.'
- with wx.TextEntryDialog( self, message ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
diff --git a/include/ClientGUICommon.py b/include/ClientGUICommon.py
index 69982bba..7cf1ae5d 100755
--- a/include/ClientGUICommon.py
+++ b/include/ClientGUICommon.py
@@ -292,9 +292,11 @@ class AutoCompleteDropdown( wx.TextCtrl ):
num_chars = len( self.GetValue() )
+ ( char_limit, long_wait, short_wait ) = HC.options[ 'ac_timings' ]
+
if num_chars == 0: self._UpdateList()
- elif num_chars < 3: self._lag_timer.Start( 500, wx.TIMER_ONE_SHOT )
- else: self._lag_timer.Start( 250, wx.TIMER_ONE_SHOT )
+ elif num_chars < char_limit: self._lag_timer.Start( long_wait, wx.TIMER_ONE_SHOT )
+ else: self._lag_timer.Start( short_wait, wx.TIMER_ONE_SHOT )
def TIMEREventDropdownHide( self, event ):
diff --git a/include/ClientGUIDialogs.py b/include/ClientGUIDialogs.py
index 397cc4c4..41ce8e7f 100755
--- a/include/ClientGUIDialogs.py
+++ b/include/ClientGUIDialogs.py
@@ -2560,7 +2560,7 @@ class DialogInputLocalFiles( Dialog ):
while aes_key is None:
- with wx.TextEntryDialog( HC.app.GetTopWindow(), 'Please enter the key for ' + path ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( HC.app.GetTopWindow(), 'Please enter the key for ' + path + '.' ) as dlg:
result = dlg.ShowModal()
@@ -3589,7 +3589,7 @@ class DialogModifyAccounts( Dialog ):
def EventBan( self, event ):
- with wx.TextEntryDialog( self, 'Enter reason for the ban' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter reason for the ban.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK: self._DoModification( HC.BAN, reason = dlg.GetValue() )
@@ -3608,7 +3608,7 @@ class DialogModifyAccounts( Dialog ):
def EventSuperban( self, event ):
- with wx.TextEntryDialog( self, 'Enter reason for the superban' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter reason for the superban.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK: self._DoModification( HC.SUPERBAN, reason = dlg.GetValue() )
@@ -4602,11 +4602,12 @@ class DialogRegisterService( Dialog ):
return
- service_identifier = HC.ClientServiceIdentifier( os.urandom( 32 ), self._service_type, 'temp registering service' )
+ service_key = os.urandom( 32 )
+ name = 'temp registering service'
info = { 'host' : host, 'port' : port }
- service = CC.Service( service_identifier, info )
+ service = CC.Service( service_key, self._service_type, name, info )
response = service.Request( HC.GET, 'access_key', request_headers = { 'Hydrus-Key' : registration_key_encoded } )
@@ -5148,7 +5149,7 @@ class DialogSetupCustomFilterActions( Dialog ):
existing_names = { self._favourites.GetString( i ) for i in range( self._favourites.GetCount() ) }
- with wx.TextEntryDialog( self, 'Enter name for these favourite actions' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter name for these favourite actions.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -5506,6 +5507,90 @@ class DialogSetupExport( Dialog ):
self._tags_box.SetTagsByMedia( all_media )
+class DialogTextEntry( Dialog ):
+
+ def __init__( self, parent, message, default = '', allow_blank = False ):
+
+ def InitialiseControls():
+
+ self._text = wx.TextCtrl( self, style = wx.TE_PROCESS_ENTER )
+ self._text.Bind( wx.EVT_CHAR, self.EventChar )
+ self._text.Bind( wx.EVT_TEXT_ENTER, self.EventEnter )
+
+ self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
+ self._ok.SetForegroundColour( ( 0, 128, 0 ) )
+
+ self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
+ self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
+
+
+ def PopulateControls():
+
+ self._text.SetValue( default )
+
+ self._CheckText()
+
+
+ def ArrangeControls():
+
+ hbox = wx.BoxSizer( wx.HORIZONTAL )
+
+ hbox.AddF( self._ok, FLAGS_SMALL_INDENT )
+ hbox.AddF( self._cancel, FLAGS_SMALL_INDENT )
+
+ st_message = wx.StaticText( self, label = message )
+
+ st_message.Wrap( 480 )
+
+ vbox = wx.BoxSizer( wx.VERTICAL )
+
+ vbox.AddF( st_message, FLAGS_BIG_INDENT )
+ vbox.AddF( self._text, FLAGS_EXPAND_BOTH_WAYS )
+ vbox.AddF( hbox, FLAGS_BUTTON_SIZERS )
+
+ self.SetSizer( vbox )
+
+ ( x, y ) = self.GetEffectiveMinSize()
+
+ x = max( x, 250 )
+
+ self.SetInitialSize( ( x, y ) )
+
+
+ Dialog.__init__( self, parent, 'enter text', position = 'center' )
+
+ self._allow_blank = allow_blank
+
+ InitialiseControls()
+
+ PopulateControls()
+
+ ArrangeControls()
+
+
+ def _CheckText( self ):
+
+ if not self._allow_blank:
+
+ if self._text.GetValue() == '': self._ok.Disable()
+ else: self._ok.Enable()
+
+
+
+ def EventChar( self, event ):
+
+ wx.CallAfter( self._CheckText )
+
+ event.Skip()
+
+
+ def EventEnter( self, event ):
+
+ if self._text.GetValue() != '': self.EndModal( wx.ID_OK )
+
+
+ def GetValue( self ): return self._text.GetValue()
+
class DialogYesNo( Dialog ):
def __init__( self, parent, message, yes_label = 'yes', no_label = 'no' ):
diff --git a/include/ClientGUIDialogsManage.py b/include/ClientGUIDialogsManage.py
index 6ebbf6e3..8c70e042 100644
--- a/include/ClientGUIDialogsManage.py
+++ b/include/ClientGUIDialogsManage.py
@@ -469,7 +469,7 @@ class DialogManageBoorus( ClientGUIDialogs.Dialog ):
def EventAdd( self, event ):
- with wx.TextEntryDialog( self, 'Enter new booru\'s name' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter new booru\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -1009,7 +1009,7 @@ class DialogManageContacts( ClientGUIDialogs.Dialog ):
return
- with wx.TextEntryDialog( self, 'Enter contact\'s address in the form contact_key@hostname:port' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter contact\'s address in the form contact_key@hostname:port.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -1062,7 +1062,7 @@ class DialogManageContacts( ClientGUIDialogs.Dialog ):
return
- with wx.TextEntryDialog( self, 'Enter new contact\'s name' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter new contact\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -1833,7 +1833,7 @@ class DialogManageImageboards( ClientGUIDialogs.Dialog ):
def EventAdd( self, event ):
- with wx.TextEntryDialog( self, 'Enter new site\'s name' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter new site\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -2039,7 +2039,7 @@ class DialogManageImageboards( ClientGUIDialogs.Dialog ):
def EventAdd( self, event ):
- with wx.TextEntryDialog( self, 'Enter new imageboard\'s name' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter new imageboard\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -2814,6 +2814,15 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self._num_autocomplete_chars = wx.SpinCtrl( self._file_page, min = 1, max = 100 )
self._num_autocomplete_chars.SetToolTipString( 'how many characters you enter before the gui fetches autocomplete results from the db' + os.linesep + 'increase this if you find autocomplete results are slow' )
+ self._autocomplete_long_wait = wx.SpinCtrl( self._file_page, min = 0, max = 10000 )
+ self._autocomplete_long_wait.SetToolTipString( 'how long the gui will wait, after you enter a character, before it queries the db with what you have entered so far' )
+
+ self._autocomplete_short_wait_chars = wx.SpinCtrl( self._file_page, min = 1, max = 100 )
+ self._autocomplete_short_wait_chars.SetToolTipString( 'how many characters you enter before the gui starts waiting the short time before querying the db' )
+
+ self._autocomplete_short_wait = wx.SpinCtrl( self._file_page, min = 0, max = 10000 )
+ self._autocomplete_short_wait.SetToolTipString( 'how long the gui will wait, after you enter a lot of characters, before it queries the db with what you have entered so far' )
+
self._listbook.AddPage( self._file_page, 'files and memory' )
# gui
@@ -3007,6 +3016,14 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
self._num_autocomplete_chars.SetValue( HC.options[ 'num_autocomplete_chars' ] )
+ ( char_limit, long_wait, short_wait ) = HC.options[ 'ac_timings' ]
+
+ self._autocomplete_long_wait.SetValue( long_wait )
+
+ self._autocomplete_short_wait_chars.SetValue( char_limit )
+
+ self._autocomplete_short_wait.SetValue( short_wait )
+
#
gui_sessions = HC.app.Read( 'gui_sessions' )
@@ -3183,6 +3200,15 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
gridbox.AddF( wx.StaticText( self._file_page, label = 'Autocomplete character threshold: ' ), FLAGS_MIXED )
gridbox.AddF( self._num_autocomplete_chars, FLAGS_MIXED )
+ gridbox.AddF( wx.StaticText( self._file_page, label = 'Autocomplete long wait (ms): ' ), FLAGS_MIXED )
+ gridbox.AddF( self._autocomplete_long_wait, FLAGS_MIXED )
+
+ gridbox.AddF( wx.StaticText( self._file_page, label = 'Autocomplete short wait threshold: ' ), FLAGS_MIXED )
+ gridbox.AddF( self._autocomplete_short_wait_chars, FLAGS_MIXED )
+
+ gridbox.AddF( wx.StaticText( self._file_page, label = 'Autocomplete short wait (ms): ' ), FLAGS_MIXED )
+ gridbox.AddF( self._autocomplete_short_wait, FLAGS_MIXED )
+
self._file_page.SetSizer( gridbox )
#
@@ -3574,6 +3600,14 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
HC.options[ 'num_autocomplete_chars' ] = self._num_autocomplete_chars.GetValue()
+ long_wait = self._autocomplete_long_wait.GetValue()
+
+ char_limit = self._autocomplete_short_wait_chars.GetValue()
+
+ short_wait = self._autocomplete_short_wait.GetValue()
+
+ HC.options[ 'ac_timings' ] = ( char_limit, long_wait, short_wait )
+
HC.options[ 'namespace_colours' ] = self._namespace_colours.GetNamespaceColours()
sort_by_choices = []
@@ -4668,7 +4702,7 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
def EventAdd( self, event ):
- with wx.TextEntryDialog( self, 'Enter new service\'s name' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter new service\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -5143,10 +5177,12 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
def EventCheckService( self, event ):
( service_identifier, info ) = self.GetInfo()
-
- service_type = service_identifier.GetType()
- service = CC.Service( service_identifier, info )
+ service_key = service_identifier.GetServiceKey()
+ service_type = service_identifier.GetType()
+ name = service_identifier.GetName()
+
+ service = CC.Service( service_key, service_type, name, info )
try: root = service.Request( HC.GET, '' )
except HydrusExceptions.WrongServiceTypeException:
@@ -5423,7 +5459,7 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
def EventAdd( self, event ):
- with wx.TextEntryDialog( self, 'Enter name for subscription' ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter name for subscription.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
@@ -6317,7 +6353,7 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
message = 'Enter a reason for this pair to be removed. A janitor will review your petition.'
- with wx.TextEntryDialog( self, message ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
else: return
@@ -6373,7 +6409,7 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
message = 'Enter a reason for ' + pair_string + ' to be added. A janitor will review your petition.'
- with wx.TextEntryDialog( self, message ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
else: return
@@ -6787,7 +6823,7 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
message = 'Enter a reason for this pair to be removed. A janitor will review your petition.'
- with wx.TextEntryDialog( self, message ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
else: return
@@ -6843,7 +6879,7 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
message = 'Enter a reason for ' + pair_string + ' to be added. A janitor will review your petition.'
- with wx.TextEntryDialog( self, message ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
else: return
@@ -7489,7 +7525,7 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
message = 'Enter a reason for this tag to be removed. A janitor will review your petition.'
- with wx.TextEntryDialog( self, message ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
else: return
diff --git a/include/ClientGUIMedia.py b/include/ClientGUIMedia.py
index 23e75132..7cc6338a 100755
--- a/include/ClientGUIMedia.py
+++ b/include/ClientGUIMedia.py
@@ -477,7 +477,7 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ):
if len( hashes ) == 1: message = 'Enter a reason for this file to be removed from ' + file_service_identifier.GetName() + '.'
else: message = 'Enter a reason for these ' + HC.ConvertIntToPrettyString( len( hashes ) ) + ' files to be removed from ' + file_service_identifier.GetName() + '.'
- with wx.TextEntryDialog( self, message ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
diff --git a/include/ClientGUIMessages.py b/include/ClientGUIMessages.py
index 0c481fdf..3411a7da 100755
--- a/include/ClientGUIMessages.py
+++ b/include/ClientGUIMessages.py
@@ -982,7 +982,7 @@ class DraftBodyPanel( wx.Panel ):
if text_attribute.HasURL(): initial_url = text_attribute.GetURL()
else: initial_url = 'http://'
- with wx.TextEntryDialog( self, 'Enter url', defaultValue = initial_url ) as dlg:
+ with ClientGUIDialogs.DialogTextEntry( self, 'Enter url.', default = initial_url ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
diff --git a/include/HydrusConstants.py b/include/HydrusConstants.py
index 31d97420..874f2e1e 100755
--- a/include/HydrusConstants.py
+++ b/include/HydrusConstants.py
@@ -64,7 +64,7 @@ options = {}
# Misc
NETWORK_VERSION = 13
-SOFTWARE_VERSION = 124
+SOFTWARE_VERSION = 125
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
diff --git a/include/HydrusImageHandling.py b/include/HydrusImageHandling.py
index 4f634c95..e4beea3c 100755
--- a/include/HydrusImageHandling.py
+++ b/include/HydrusImageHandling.py
@@ -38,16 +38,16 @@ def ConvertToPngIfBmp( path ):
os.remove( temp_path )
-def EfficientlyResizeCVImage( cv_image, ( target_x, target_y ) ):
+def EfficientlyResizeNumpyImage( numpy_image, ( target_x, target_y ) ):
- ( im_y, im_x, depth ) = cv_image.shape
+ ( im_y, im_x, depth ) = numpy_image.shape
- if target_x >= im_x and target_y >= im_y: return cv_image
+ if target_x >= im_x and target_y >= im_y: return numpy_image
- result = cv_image
+ result = numpy_image
# this seems to slow things down a lot, at least for cv!
- #if im_x > 2 * target_x and im_y > 2 * target_y: result = cv2.resize( cv_image, ( 2 * target_x, 2 * target_y ), interpolation = cv2.INTER_NEAREST )
+ #if im_x > 2 * target_x and im_y > 2 * target_y: result = cv2.resize( numpy_image, ( 2 * target_x, 2 * target_y ), interpolation = cv2.INTER_NEAREST )
return cv2.resize( result, ( target_x, target_y ), interpolation = cv2.INTER_LINEAR )
@@ -64,15 +64,15 @@ def EfficientlyResizePILImage( pil_image, ( target_x, target_y ) ):
return pil_image.resize( ( target_x, target_y ), PILImage.ANTIALIAS )
-def EfficientlyThumbnailCVImage( cv_image, ( target_x, target_y ) ):
+def EfficientlyThumbnailNumpyImage( numpy_image, ( target_x, target_y ) ):
- ( im_y, im_x, depth ) = cv_image.shape
+ ( im_y, im_x, depth ) = numpy_image.shape
- if target_x >= im_x and target_y >= im_y: return cv_image
+ if target_x >= im_x and target_y >= im_y: return numpy_image
( target_x, target_y ) = GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) )
- return cv2.resize( cv_image, ( target_x, target_y ), interpolation = cv2.INTER_AREA )
+ return cv2.resize( numpy_image, ( target_x, target_y ), interpolation = cv2.INTER_AREA )
def EfficientlyThumbnailPILImage( pil_image, ( target_x, target_y ) ):
@@ -85,24 +85,24 @@ def EfficientlyThumbnailPILImage( pil_image, ( target_x, target_y ) ):
pil_image.thumbnail( ( target_x, target_y ), PILImage.ANTIALIAS )
-def GenerateCVImage( path ):
+def GenerateNumpyImage( path ):
- cv_image = cv2.imread( self._path, flags = -1 ) # flags = -1 loads alpha channel, if present
+ numpy_image = cv2.imread( self._path, flags = -1 ) # flags = -1 loads alpha channel, if present
- ( y, x, depth ) = cv_image.shape
+ ( y, x, depth ) = numpy_image.shape
if depth == 4: raise Exception( 'CV is bad at alpha!' )
- else: cv_image = cv2.cvtColor( cv_image, cv2.COLOR_BGR2RGB )
+ else: numpy_image = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2RGB )
- return cv_image
+ return numpy_image
def GenerateHydrusBitmap( path ):
try:
- cv_image = GenerateCVImage( path )
+ numpy_image = GenerateNumpyImage( path )
- return GenerateHydrusBitmapFromNumPyImage( cv_image )
+ return GenerateHydrusBitmapFromNumPyImage( numpy_image )
except:
@@ -111,12 +111,12 @@ def GenerateHydrusBitmap( path ):
return GenerateHydrusBitmapFromPILImage( pil_image )
-def GenerateHydrusBitmapFromNumPyImage( cv_image ):
+def GenerateHydrusBitmapFromNumPyImage( numpy_image ):
- ( y, x, depth ) = cv_image.shape
+ ( y, x, depth ) = numpy_image.shape
- if depth == 4: return HydrusBitmap( cv_image.data, wx.BitmapBufferFormat_RGBA, ( x, y ) )
- else: return HydrusBitmap( cv_image.data, wx.BitmapBufferFormat_RGB, ( x, y ) )
+ if depth == 4: return HydrusBitmap( numpy_image.data, wx.BitmapBufferFormat_RGBA, ( x, y ) )
+ else: return HydrusBitmap( numpy_image.data, wx.BitmapBufferFormat_RGB, ( x, y ) )
def GenerateNumPyImageFromPILImage( pil_image ):
@@ -154,9 +154,9 @@ def GenerateHydrusBitmapFromPILImage( pil_image ):
def GeneratePerceptualHash( path ):
- cv_image = cv2.imread( path, cv2.CV_LOAD_IMAGE_UNCHANGED )
+ numpy_image = cv2.imread( path, cv2.CV_LOAD_IMAGE_UNCHANGED )
- ( y, x, depth ) = cv_image.shape
+ ( y, x, depth ) = numpy_image.shape
if depth == 4:
@@ -164,15 +164,15 @@ def GeneratePerceptualHash( path ):
white = numpy.ones( ( x, y ) ) * 255
- # create weight and transform cv_image to greyscale
+ # create weight and transform numpy_image to greyscale
- cv_alpha = cv_image[ :, :, 3 ]
+ numpy_alpha = numpy_image[ :, :, 3 ]
- cv_image_bgr = cv_image[ :, :, :3 ]
+ numpy_image_bgr = numpy_image[ :, :, :3 ]
- cv_image_gray = cv2.cvtColor( cv_image_bgr, cv2.COLOR_BGR2GRAY )
+ numpy_image_gray = cv2.cvtColor( numpy_image_bgr, cv2.COLOR_BGR2GRAY )
- cv_image_result = numpy.empty( ( y, x ), numpy.float32 )
+ numpy_image_result = numpy.empty( ( y, x ), numpy.float32 )
# paste greyscale onto the white
@@ -182,33 +182,33 @@ def GeneratePerceptualHash( path ):
for j in range( x ):
- opacity = float( cv_alpha[ i, j ] ) / 255.0
+ opacity = float( numpy_alpha[ i, j ] ) / 255.0
- grey_part = cv_image_gray[ i, j ] * opacity
+ grey_part = numpy_image_gray[ i, j ] * opacity
white_part = 255 * ( 1 - opacity )
pixel = grey_part + white_part
- cv_image_result[ i, j ] = pixel
+ numpy_image_result[ i, j ] = pixel
- cv_image_gray = cv_image_result
+ numpy_image_gray = numpy_image_result
# use 255 for white weight, alpha for image weight
else:
- cv_image_gray = cv2.cvtColor( cv_image, cv2.COLOR_BGR2GRAY )
+ numpy_image_gray = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2GRAY )
- cv_image_tiny = cv2.resize( cv_image_gray, ( 32, 32 ), interpolation = cv2.INTER_AREA )
+ numpy_image_tiny = cv2.resize( numpy_image_gray, ( 32, 32 ), interpolation = cv2.INTER_AREA )
# convert to float and calc dct
- cv_image_tiny_float = numpy.float32( cv_image_tiny )
+ numpy_image_tiny_float = numpy.float32( numpy_image_tiny )
- dct = cv2.dct( cv_image_tiny_float )
+ dct = cv2.dct( numpy_image_tiny_float )
# take top left 8x8 of dct
@@ -282,19 +282,19 @@ def old_GeneratePerceptualHash( path ):
# convert to mat
- cv_thumbnail_8 = cv.CreateMatHeader( 32, 32, cv.CV_8UC1 )
+ numpy_thumbnail_8 = cv.CreateMatHeader( 32, 32, cv.CV_8UC1 )
- cv.SetData( cv_thumbnail_8, thumbnail.tostring() )
+ cv.SetData( numpy_thumbnail_8, thumbnail.tostring() )
- cv_thumbnail_32 = cv.CreateMat( 32, 32, cv.CV_32FC1 )
+ numpy_thumbnail_32 = cv.CreateMat( 32, 32, cv.CV_32FC1 )
- cv.Convert( cv_thumbnail_8, cv_thumbnail_32 )
+ cv.Convert( numpy_thumbnail_8, numpy_thumbnail_32 )
# compute dct
dct = cv.CreateMat( 32, 32, cv.CV_32FC1 )
- cv.DCT( cv_thumbnail_32, dct, cv.CV_DXT_FORWARD )
+ cv.DCT( numpy_thumbnail_32, dct, cv.CV_DXT_FORWARD )
# take top left 8x8 of dct
@@ -594,11 +594,11 @@ class ImageContainer( RasterContainer ):
try:
- cv_image = GenerateCVImage( self._path )
+ numpy_image = GenerateNumpyImage( self._path )
- resized_cv_image = EfficientlyResizeCVImage( cv_image, self._target_resolution )
+ resized_numpy_image = EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
- return GenerateHydrusBitmapFromNumPyImage( resized_cv_image )
+ return GenerateHydrusBitmapFromNumPyImage( resized_numpy_image )
except:
diff --git a/include/HydrusPubSub.py b/include/HydrusPubSub.py
index eec6b126..8759ac99 100755
--- a/include/HydrusPubSub.py
+++ b/include/HydrusPubSub.py
@@ -106,13 +106,9 @@ class HydrusPubSub( object ):
with self._lock:
- # this stops the pubsubs started at the beginning of the program screwing with the queue
- if HC.app.IsMainLoopRunning():
-
- self._pubsubs.append( ( topic, args, kwargs ) )
-
- wx.PostEvent( HC.app, PubSubEvent() )
-
+ self._pubsubs.append( ( topic, args, kwargs ) )
+
+ wx.PostEvent( HC.app, PubSubEvent() )
diff --git a/include/HydrusSessions.py b/include/HydrusSessions.py
index e41013c6..09d564a4 100755
--- a/include/HydrusSessions.py
+++ b/include/HydrusSessions.py
@@ -141,25 +141,10 @@ class HydrusSessionManagerServer( object ):
def __init__( self ):
- existing_sessions = HC.app.Read( 'sessions' )
-
- self._account_ids_to_session_keys = collections.defaultdict( HC.default_dict_set )
-
- self._account_ids_to_accounts = collections.defaultdict( dict )
-
- self._sessions = collections.defaultdict( dict )
-
- for ( session_key, service_identifier, account, expiry ) in existing_sessions:
-
- self._sessions[ service_identifier ][ session_key ] = ( account, expiry )
-
- account_id = account.GetAccountId()
-
- self._account_ids_to_session_keys[ service_identifier ][ account_id ].add( session_key )
-
-
self._lock = threading.Lock()
+ self.RefreshAllAccounts()
+
HC.pubsub.sub( self, 'RefreshAllAccounts', 'update_all_session_accounts' )
@@ -245,21 +230,26 @@ class HydrusSessionManagerServer( object ):
def RefreshAllAccounts( self ):
- existing_sessions = HC.app.Read( 'sessions' )
-
- self._account_ids_to_session_keys = collections.defaultdict( HC.default_dict_set )
-
- self._account_ids_to_accounts = collections.defaultdict( dict )
-
- self._sessions = collections.defaultdict( dict )
-
- for ( session_key, service_identifier, account, expiry ) in existing_sessions:
+ with self._lock:
- self._sessions[ service_identifier ][ session_key ] = ( account, expiry )
+ existing_sessions = HC.app.Read( 'sessions' )
- account_id = account.GetAccountId()
+ self._account_ids_to_session_keys = collections.defaultdict( HC.default_dict_set )
- self._account_ids_to_session_keys[ service_identifier ][ account_id ].add( session_key )
+ self._account_ids_to_accounts = collections.defaultdict( dict )
+
+ self._sessions = collections.defaultdict( dict )
+
+ for ( session_key, service_identifier, account, expiry ) in existing_sessions:
+
+ self._sessions[ service_identifier ][ session_key ] = ( account, expiry )
+
+ account_id = account.GetAccountId()
+
+ self._account_ids_to_session_keys[ service_identifier ][ account_id ].add( session_key )
+
+ self._account_ids_to_accounts[ service_identifier ][ account_id ] = account
+
diff --git a/include/HydrusThreading.py b/include/HydrusThreading.py
index e34ed270..4353cfc9 100644
--- a/include/HydrusThreading.py
+++ b/include/HydrusThreading.py
@@ -96,7 +96,7 @@ class DAEMONWorker( DAEMON ):
try: self._callable()
except Exception as e:
- HC.ShowText( 'Daemon ' + name + ' encountered an exception:' )
+ HC.ShowText( 'Daemon ' + self._name + ' encountered an exception:' )
HC.ShowException( e )
diff --git a/include/HydrusVideoHandling.py b/include/HydrusVideoHandling.py
index eb6c069f..5c31ac53 100755
--- a/include/HydrusVideoHandling.py
+++ b/include/HydrusVideoHandling.py
@@ -654,7 +654,7 @@ class GIFRenderer( object ):
if self._cv_mode:
- ( retval, cv_image ) = self._cv_video.read()
+ ( retval, numpy_image ) = self._cv_video.read()
if not retval:
@@ -676,7 +676,7 @@ class GIFRenderer( object ):
else: self._pil_canvas = self._pil_image
- cv_image = HydrusImageHandling.GenerateNumPyImageFromPILImage( self._pil_canvas )
+ numpy_image = HydrusImageHandling.GenerateNumPyImageFromPILImage( self._pil_canvas )
self._next_render_index = ( self._next_render_index + 1 ) % self._num_frames
@@ -700,7 +700,7 @@ class GIFRenderer( object ):
- return cv_image
+ return numpy_image
def _InitialiseCV( self ):
@@ -739,13 +739,13 @@ class GIFRenderer( object ):
try:
- cv_image = self._GetCurrentFrame()
+ numpy_image = self._GetCurrentFrame()
- cv_image = HydrusImageHandling.EfficientlyResizeCVImage( cv_image, self._target_resolution )
+ numpy_image = HydrusImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
- cv_image = cv2.cvtColor( cv_image, cv2.COLOR_BGR2RGB )
+ numpy_image = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2RGB )
- self._last_frame = cv_image
+ self._last_frame = numpy_image
except HydrusExceptions.CantRenderWithCVException:
@@ -757,19 +757,19 @@ class GIFRenderer( object ):
return self._RenderCurrentFrame()
- else: cv_image = self._last_frame
+ else: numpy_image = self._last_frame
else:
- cv_image = self._GetCurrentFrame()
+ numpy_image = self._GetCurrentFrame()
- cv_image = HydrusImageHandling.EfficientlyResizeCVImage( cv_image, self._target_resolution )
+ numpy_image = HydrusImageHandling.EfficientlyResizeNumpyImage( numpy_image, self._target_resolution )
- self._last_frame = cv_image
+ self._last_frame = numpy_image
- return cv_image
+ return numpy_image
def _RewindGIF( self ):
diff --git a/include/ServerController.py b/include/ServerController.py
index 78580256..3b29ec5b 100755
--- a/include/ServerController.py
+++ b/include/ServerController.py
@@ -47,12 +47,12 @@ class Controller( wx.App ):
try:
+ self.Bind( HC.EVT_PUBSUB, self.EventPubSub )
+
self._db = ServerDB.DB()
self.Bind( wx.EVT_MENU, self.EventExit, id=wx.ID_EXIT )
- self.Bind( HC.EVT_PUBSUB, self.EventPubSub )
-
self._managers = {}
self._managers[ 'restricted_services_sessions' ] = HydrusSessions.HydrusSessionManagerServer()
diff --git a/include/TestDB.py b/include/TestDB.py
index 0fc74dcb..e55e68b8 100644
--- a/include/TestDB.py
+++ b/include/TestDB.py
@@ -51,7 +51,7 @@ class TestClientDB( unittest.TestCase ):
self._db.Shutdown()
- while not self._db.GetLoopFinished(): time.sleep( 0.1 )
+ while not self._db.LoopIsFinished(): time.sleep( 0.1 )
def make_temp_files_deletable( function_called, path, traceback_gumpf ):
diff --git a/include/TestHydrusServer.py b/include/TestHydrusServer.py
index 84712201..836c3569 100644
--- a/include/TestHydrusServer.py
+++ b/include/TestHydrusServer.py
@@ -128,15 +128,13 @@ class TestServer( unittest.TestCase ):
def _test_file_repo( self, host, port ):
- service_identifier = HC.ClientServiceIdentifier( os.urandom( 32 ), HC.FILE_REPOSITORY, 'service' )
-
info = {}
info[ 'host' ] = host
info[ 'port' ] = port
info[ 'access_key' ] = self._access_key
- service = CC.Service( service_identifier, info )
+ service = CC.Service( os.urandom( 32 ), HC.FILE_REPOSITORY, 'service', info )
# file
@@ -292,15 +290,13 @@ class TestServer( unittest.TestCase ):
def _test_repo( self, host, port, service_type ):
- service_identifier = HC.ClientServiceIdentifier( os.urandom( 32 ), service_type, 'service' )
-
info = {}
info[ 'host' ] = host
info[ 'port' ] = port
info[ 'access_key' ] = self._access_key
- service = CC.Service( service_identifier, info )
+ service = CC.Service( os.urandom( 32 ), service_type, 'service', info )
# news
@@ -366,14 +362,14 @@ class TestServer( unittest.TestCase ):
def _test_restricted( self, host, port, service_type ):
- service_identifier = HC.ClientServiceIdentifier( os.urandom( 32 ), service_type, 'service' )
+ service_key = os.urandom( 32 )
info = {}
info[ 'host' ] = host
info[ 'port' ] = port
- service = CC.Service( service_identifier, info )
+ service = CC.Service( service_key, service_type, 'service', info )
# access_key
@@ -391,7 +387,7 @@ class TestServer( unittest.TestCase ):
info[ 'access_key' ] = self._access_key
- service = CC.Service( service_identifier, info )
+ service = CC.Service( service_key, service_type, 'service', info )
# set up session
@@ -399,7 +395,7 @@ class TestServer( unittest.TestCase ):
account = self._account
- service_for_session_manager = CC.Service( service_identifier, info )
+ service_for_session_manager = CC.Service( service_key, service_type, 'service', info )
HC.app.SetRead( 'service', service_for_session_manager )
@@ -482,14 +478,14 @@ class TestServer( unittest.TestCase ):
def _test_server_admin( self, host, port ):
- service_identifier = HC.ClientServiceIdentifier( os.urandom( 32 ), HC.SERVER_ADMIN, 'service' )
+ service_key = os.urandom( 32 )
info = {}
info[ 'host' ] = host
info[ 'port' ] = port
- service = CC.Service( service_identifier, info )
+ service = CC.Service( service_key, HC.SERVER_ADMIN, 'service', info )
# init
@@ -505,7 +501,7 @@ class TestServer( unittest.TestCase ):
info[ 'access_key' ] = self._access_key
- service = CC.Service( service_identifier, info )
+ service = CC.Service( service_key, HC.SERVER_ADMIN, 'service', info )
# backup
diff --git a/test.py b/test.py
index a5678277..a71408d5 100644
--- a/test.py
+++ b/test.py
@@ -78,7 +78,11 @@ class App( wx.App ):
info[ 'max_monthly_data' ] = None
info[ 'used_monthly_data' ] = 0
- service = CC.Service( HC.LOCAL_BOORU_SERVICE_IDENTIFIER, info )
+ service_key = HC.LOCAL_BOORU_SERVICE_IDENTIFIER.GetServiceKey()
+ service_type = HC.LOCAL_BOORU_SERVICE_IDENTIFIER.GetType()
+ name = HC.LOCAL_BOORU_SERVICE_IDENTIFIER.GetName()
+
+ service = CC.Service( service_key, service_type, name, info )
HC.app.SetRead( 'service', service )