Version 135
This commit is contained in:
parent
5662e56649
commit
33bfb28972
|
@ -8,6 +8,25 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 135</h3></li>
|
||||
<ul>
|
||||
<li>added a menu option to any tag's right-click menu to open a new search page for that tag</li>
|
||||
<li>added a subscription cache to the client database to speed up subs checking</li>
|
||||
<li>added a filter to the message popup system so those annoying and 99% pointless PyDeadObjectErrors will not display. they will still be written to the log</li>
|
||||
<li>cleaned up a couple of temporary file deletion errors</li>
|
||||
<li>improved some more temp file deletion error handling</li>
|
||||
<li>fixed a bug that I think was stopping file repositories from being deleted</li>
|
||||
<li>improved the manage services db edit log</li>
|
||||
<li>fixed a bad comparison that was causing superfluous edit actions after a manage services dialog ok</li>
|
||||
<li>fixed a new bug related to displaying non text in the popup system</li>
|
||||
<li>added a 'just woke from sleep' check to all daemons, so CPU heavy stuff like repository sync will not initialise if you just woke your computer. the grace period lasts about ten minutes</li>
|
||||
<li>retuned the way the subscrption daemon initialises (it'll now wait two minutes after startup before firing)</li>
|
||||
<li>fixed a typo that was causing fatten service info to fire more often than it should</li>
|
||||
<li>added a yes/no warning to options ok when the thumbnail dimensions have been changed</li>
|
||||
<li>added a popup message when thumbnail dimensions have been changed to report on deletion progress</li>
|
||||
<li>added account testing to my server db testing suite</li>
|
||||
<li>improved the security of the registration_key->access_key transaction; it'll now generate a new access_key with every call</li>
|
||||
</ul>
|
||||
<li><h3>version 134</h3></li>
|
||||
<ul>
|
||||
<li>updated to wx 3.0.1.1</li>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<body>
|
||||
<div class="content">
|
||||
<p><b>access key</b> A 32-byte identifier-password that gives you access to an account that has certain permissions with a repository. Usually represented as a 64-character hex string like so: 7ce4dbf18f7af8b420ee942bae42030aab344e91dc0e839260fcd71a4c9879e3</p>
|
||||
<p><b>account key</b> A 32-byte identifier for a hydrus service account. Usually represented as a 64-character hex string like so: 0c3b554cb6fe7d55c945df88b2f6cf6ca0ae40824bca7534aa2fd483da7fb219</p>
|
||||
<p><b>account key</b> A 32-byte identifier for a hydrus service account. Usually represented as a 64-character hex string like so: 207d592682a7962564d52d2480f05e72a272443017553cedbd8af0fecc7b6e0a</p>
|
||||
<p><b>address</b> The pairing of both a server's host (be that an IP or a domain) with its port number, like so: 74.125.225.18:80, or google.com:80</p>
|
||||
<p><b>archive</b> The store of files you have chosen to keep.</p>
|
||||
<p><b>file repository</b> A service in the hydrus network that hosts files.</p>
|
||||
|
|
|
@ -161,7 +161,8 @@ The database will be locked while the backup occurs, which may lock up your gui
|
|||
|
||||
os.chmod( path, stat.S_IWRITE )
|
||||
|
||||
function_called( path ) # try again
|
||||
try: function_called( path ) # try again
|
||||
except: pass
|
||||
|
||||
|
||||
if os.path.exists( HC.TEMP_DIR ): shutil.rmtree( HC.TEMP_DIR, onerror = make_temp_files_deletable )
|
||||
|
@ -248,6 +249,8 @@ The database will be locked while the backup occurs, which may lock up your gui
|
|||
self._db.StartDaemons()
|
||||
|
||||
|
||||
def JustWokeFromSleep( self ): return self._just_woke_from_sleep
|
||||
|
||||
def MaintainDB( self ):
|
||||
|
||||
sys.stdout.flush()
|
||||
|
@ -270,14 +273,16 @@ The database will be locked while the backup occurs, which may lock up your gui
|
|||
|
||||
for service in services: self.Read( 'service_info', service.GetServiceKey() )
|
||||
|
||||
self._timestamps[ 'service_info_cache_fatten' ] = HC.GetNow()
|
||||
self._timestamps[ 'last_service_info_cache_fatten' ] = HC.GetNow()
|
||||
|
||||
|
||||
HC.pubsub.pub( 'clear_closed_pages' )
|
||||
|
||||
|
||||
def OnInit( self ):
|
||||
self.SetAssertMode(wx.PYAPP_ASSERT_SUPPRESS)
|
||||
|
||||
self.SetAssertMode( wx.PYAPP_ASSERT_SUPPRESS )
|
||||
|
||||
HC.app = self
|
||||
HC.http = HydrusNetworking.HTTPConnectionManager()
|
||||
|
||||
|
@ -285,6 +290,8 @@ The database will be locked while the backup occurs, which may lock up your gui
|
|||
|
||||
self._timestamps[ 'boot' ] = HC.GetNow()
|
||||
|
||||
self._just_woke_from_sleep = False
|
||||
|
||||
self._local_service = None
|
||||
self._booru_service = None
|
||||
|
||||
|
@ -546,9 +553,10 @@ Once it is done, the client will restart.'''
|
|||
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 + ( 5 * 60 ): return
|
||||
if HC.GetNow() - last_time_this_ran > MAINTENANCE_PERIOD + ( 5 * 60 ): self._just_woke_from_sleep = True
|
||||
else: self._just_woke_from_sleep = False
|
||||
|
||||
if self.CurrentlyIdle(): self.MaintainDB()
|
||||
if not self._just_woke_from_sleep and self.CurrentlyIdle(): self.MaintainDB()
|
||||
|
||||
|
||||
def WaitUntilGoodTimeToUseGUIThread( self ):
|
||||
|
|
|
@ -256,7 +256,8 @@ class MessageDB( object ):
|
|||
|
||||
except: pass
|
||||
|
||||
os.remove( temp_path )
|
||||
try: os.remove( temp_path )
|
||||
except: pass # sometimes this fails, I think due to old handles not being cleaned up fast enough. np--it'll be cleaned up later
|
||||
|
||||
|
||||
hash_ids = self._GetHashIds( c, attachment_hashes )
|
||||
|
@ -1588,6 +1589,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
if dump_name is None: c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ?;', ( dump_type, ) )
|
||||
else:
|
||||
|
||||
if dump_type == YAML_DUMP_ID_SUBSCRIPTION and dump_name in self._subscriptions_cache: del self._subscriptions_cache[ dump_name ]
|
||||
|
||||
if dump_type == YAML_DUMP_ID_LOCAL_BOORU: dump_name = dump_name.encode( 'hex' )
|
||||
|
||||
c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ? AND dump_name = ?;', ( dump_type, dump_name ) )
|
||||
|
@ -2965,6 +2968,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
else:
|
||||
|
||||
if dump_type == YAML_DUMP_ID_SUBSCRIPTION and dump_name in self._subscriptions_cache: return self._subscriptions_cache[ dump_name ]
|
||||
|
||||
if dump_type == YAML_DUMP_ID_LOCAL_BOORU: dump_name = dump_name.encode( 'hex' )
|
||||
|
||||
result = c.execute( 'SELECT dump FROM yaml_dumps WHERE dump_type = ? AND dump_name = ?;', ( dump_type, dump_name ) ).fetchone()
|
||||
|
@ -2981,6 +2986,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
else: ( result, ) = result
|
||||
|
||||
if dump_type == YAML_DUMP_ID_SUBSCRIPTION: self._subscriptions_cache[ dump_name ] = result
|
||||
|
||||
|
||||
return result
|
||||
|
||||
|
@ -3771,6 +3778,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
def _SetYAMLDump( self, c, dump_type, dump_name, data ):
|
||||
|
||||
if dump_type == YAML_DUMP_ID_SUBSCRIPTION: self._subscriptions_cache[ dump_name ] = data
|
||||
|
||||
if dump_type == YAML_DUMP_ID_LOCAL_BOORU: dump_name = dump_name.encode( 'hex' )
|
||||
|
||||
c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ? AND dump_name = ?;', ( dump_type, dump_name ) )
|
||||
|
@ -4197,17 +4206,19 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
recalc_combined_mappings = False
|
||||
message = None
|
||||
|
||||
for ( action, details ) in edit_log:
|
||||
for entry in edit_log:
|
||||
|
||||
action = entry.GetAction()
|
||||
|
||||
if action == HC.ADD:
|
||||
|
||||
( service_key, service_type, name, info ) = details
|
||||
( service_key, service_type, name, info ) = entry.GetData()
|
||||
|
||||
self._AddService( c, service_key, service_type, name, info )
|
||||
|
||||
elif action == HC.DELETE:
|
||||
|
||||
service_key = details
|
||||
service_key = entry.GetIdentifier()
|
||||
|
||||
service_id = self._GetServiceId( c, service_key )
|
||||
|
||||
|
@ -4249,7 +4260,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
elif action == HC.EDIT:
|
||||
|
||||
( service_key, service_type, new_name, info_update ) = details
|
||||
( service_key, service_type, new_name, info_update ) = entry.GetData()
|
||||
|
||||
service_id = self._GetServiceId( c, service_key )
|
||||
|
||||
|
@ -4309,6 +4320,8 @@ class DB( ServiceDB ):
|
|||
self._jobs = Queue.PriorityQueue()
|
||||
self._pubsubs = []
|
||||
|
||||
self._subscriptions_cache = {}
|
||||
|
||||
self._currently_doing_job = False
|
||||
|
||||
self._InitDB()
|
||||
|
@ -4659,12 +4672,23 @@ class DB( ServiceDB ):
|
|||
|
||||
if resize_thumbs:
|
||||
|
||||
thumbnail_paths = [ path for path in CC.IterateAllThumbnailPaths() if path.endswith( '_resized' ) ]
|
||||
message = HC.Message( HC.MESSAGE_TYPE_TEXT, { 'text' : 'deleting old thumbnails: initialising' } )
|
||||
|
||||
for path in thumbnail_paths: os.remove( path )
|
||||
HC.pubsub.pub( 'message', message )
|
||||
|
||||
thumbnail_paths = ( path for path in CC.IterateAllThumbnailPaths() if path.endswith( '_resized' ) )
|
||||
|
||||
for ( i, path ) in enumerate( thumbnail_paths ):
|
||||
|
||||
os.remove( path )
|
||||
|
||||
if i % 100 == 0: message.SetInfo( 'text', 'deleting old thumbnails: done ' + HC.ConvertIntToPrettyString( i ) )
|
||||
|
||||
|
||||
self.pub_after_commit( 'thumbnail_resize' )
|
||||
|
||||
message.SetInfo( 'text', 'deleting old thumbnails: done' )
|
||||
|
||||
|
||||
self.pub_after_commit( 'notify_new_options' )
|
||||
|
||||
|
@ -4734,31 +4758,6 @@ class DB( ServiceDB ):
|
|||
|
||||
def _UpdateDB( self, c, version ):
|
||||
|
||||
if version == 84:
|
||||
|
||||
boorus = []
|
||||
|
||||
name = 'e621'
|
||||
search_url = 'https://e621.net/post/index?page=%index%&tags=%tags%'
|
||||
search_separator = '%20'
|
||||
advance_by_page_num = True
|
||||
thumb_classname = 'thumb'
|
||||
image_id = None
|
||||
image_data = 'Download'
|
||||
tag_classnames_to_namespaces = { 'tag-type-general categorized-tag' : '', 'tag-type-character categorized-tag' : 'character', 'tag-type-copyright categorized-tag' : 'series', 'tag-type-artist categorized-tag' : 'creator', 'tag-type-species categorized-tag' : 'species' }
|
||||
|
||||
boorus.append( CC.Booru( name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ) )
|
||||
|
||||
for booru in boorus:
|
||||
|
||||
name = booru.GetName()
|
||||
|
||||
c.execute( 'DELETE FROM boorus WHERE name = ?;', ( name, ) )
|
||||
|
||||
c.execute( 'INSERT INTO boorus VALUES ( ?, ? );', ( name, booru ) )
|
||||
|
||||
|
||||
|
||||
if version == 87:
|
||||
|
||||
c.execute( 'CREATE TABLE namespace_blacklists ( service_id INTEGER PRIMARY KEY REFERENCES services ON DELETE CASCADE, blacklist INTEGER_BOOLEAN, namespaces TEXT_YAML );' )
|
||||
|
@ -5426,7 +5425,6 @@ class DB( ServiceDB ):
|
|||
elif action == 'status_num_inbox': result = self._DoStatusNumInbox( c, *args, **kwargs )
|
||||
elif action == 'subscription_names': result = self._GetYAMLDumpNames( c, YAML_DUMP_ID_SUBSCRIPTION )
|
||||
elif action == 'subscription': result = self._GetYAMLDump( c, YAML_DUMP_ID_SUBSCRIPTION, *args, **kwargs )
|
||||
elif action == 'subscriptions': result = self._GetYAMLDump( c, YAML_DUMP_ID_SUBSCRIPTION, *args, **kwargs )
|
||||
elif action == 'tag_censorship': result = self._GetTagCensorship( c, *args, **kwargs )
|
||||
elif action == 'tag_parents': result = self._GetTagParents( c, *args, **kwargs )
|
||||
elif action == 'tag_siblings': result = self._GetTagSiblings( c, *args, **kwargs )
|
||||
|
@ -5629,7 +5627,7 @@ class DB( ServiceDB ):
|
|||
HydrusThreading.DAEMONWorker( 'ResizeThumbnails', DAEMONResizeThumbnails, period = 3600 * 24, init_wait = 600 )
|
||||
HydrusThreading.DAEMONWorker( 'SynchroniseAccounts', DAEMONSynchroniseAccounts, ( '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' ), period = 360 )
|
||||
HydrusThreading.DAEMONWorker( 'SynchroniseSubscriptions', DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ), period = 360, init_wait = 120 )
|
||||
HydrusThreading.DAEMONWorker( 'UPnP', DAEMONUPnP, ( 'notify_new_upnp_mappings', ), pre_callable_wait = 10 )
|
||||
HydrusThreading.DAEMONQueue( 'FlushRepositoryUpdates', DAEMONFlushServiceUpdates, 'service_updates_delayed', period = 5 )
|
||||
|
||||
|
@ -5894,7 +5892,8 @@ def DAEMONDownloadFiles():
|
|||
|
||||
HC.app.WriteSynchronous( 'import_file', temp_path )
|
||||
|
||||
os.remove( temp_path )
|
||||
try: os.remove( temp_path )
|
||||
except: pass # sometimes this fails, I think due to old handles not being cleaned up fast enough. np--it'll be cleaned up later
|
||||
|
||||
break
|
||||
|
||||
|
@ -6770,7 +6769,8 @@ def DAEMONSynchroniseSubscriptions():
|
|||
|
||||
( status, hash ) = HC.app.WriteSynchronous( 'import_file', temp_path, advanced_import_options = advanced_import_options, service_keys_to_tags = service_keys_to_tags, url = url )
|
||||
|
||||
os.remove( temp_path )
|
||||
try: os.remove( temp_path )
|
||||
except: pass # sometimes this fails, I think due to old handles not being cleaned up fast enough. np--it'll be cleaned up later
|
||||
|
||||
if status in ( 'successful', 'redundant' ): successful_hashes.add( hash )
|
||||
|
||||
|
|
|
@ -348,7 +348,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
info[ 'port' ] = 45871
|
||||
info[ 'access_key' ] = '4a285629721ca442541ef2c15ea17d1f7f7578b0c3f4f5f2a05f8f0ab297786f'.decode( 'hex' )
|
||||
|
||||
edit_log.append( ( HC.ADD, ( service_key, service_type, name, info ) ) )
|
||||
edit_log.append( HC.EditLogActionAdd( ( service_key, service_type, name, info ) ) )
|
||||
|
||||
service_key = os.urandom( 32 )
|
||||
service_type = HC.FILE_REPOSITORY
|
||||
|
@ -360,7 +360,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
info[ 'port' ] = 45872
|
||||
info[ 'access_key' ] = '8f8a3685abc19e78a92ba61d84a0482b1cfac176fd853f46d93fe437a95e40a5'.decode( 'hex' )
|
||||
|
||||
edit_log.append( ( HC.ADD, ( service_key, service_type, name, info ) ) )
|
||||
edit_log.append( HC.EditLogActionAdd( ( service_key, service_type, name, info ) ) )
|
||||
|
||||
HC.app.Write( 'update_services', edit_log )
|
||||
|
||||
|
@ -443,7 +443,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
info[ 'port' ] = port
|
||||
info[ 'access_key' ] = ''
|
||||
|
||||
edit_log.append( ( HC.ADD, ( admin_service_key, service_type, name, info ) ) )
|
||||
edit_log.append( HC.EditLogActionAdd( ( admin_service_key, service_type, name, info ) ) )
|
||||
|
||||
HC.app.Write( 'update_services', edit_log )
|
||||
|
||||
|
@ -481,7 +481,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
info_update = { 'access_key' : access_key }
|
||||
|
||||
edit_log = [ ( HC.EDIT, ( admin_service_key, service_type, name, info_update ) ) ]
|
||||
edit_log = [ HC.EditLogActionEdit( admin_service_key, ( admin_service_key, service_type, name, info_update ) ) ]
|
||||
|
||||
HC.app.Write( 'update_services', edit_log )
|
||||
|
||||
|
@ -2870,7 +2870,7 @@ class FrameReviewServices( ClientGUICommon.Frame ):
|
|||
|
||||
info_update = { 'access_key' : access_key }
|
||||
|
||||
edit_log = [ ( HC.EDIT, ( service_key, service_type, name, info_update ) ) ]
|
||||
edit_log = [ HC.EditLogActionEdit( service_key, ( service_key, service_type, name, info_update ) ) ]
|
||||
|
||||
HC.app.Write( 'update_services', edit_log )
|
||||
|
||||
|
|
|
@ -2010,13 +2010,13 @@ class ListBox( wx.ScrolledWindow ):
|
|||
|
||||
term = self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ]
|
||||
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_term' ), 'copy ' + term )
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_term' ), 'copy "' + term + '"' )
|
||||
|
||||
if ':' in term:
|
||||
|
||||
sub_term = term.split( ':', 1 )[1]
|
||||
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_sub_term' ), 'copy ' + sub_term )
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_sub_term' ), 'copy "' + sub_term + '"' )
|
||||
|
||||
|
||||
self.PopupMenu( menu )
|
||||
|
@ -2831,7 +2831,7 @@ class PopupMessageText( PopupMessage ):
|
|||
|
||||
def Update( self ):
|
||||
|
||||
text = self._ProcessText( self._message.GetInfo( 'text' ) )
|
||||
text = self._ProcessText( HC.u( self._message.GetInfo( 'text' ) ) )
|
||||
|
||||
if self._text.GetLabel() != text: self._text.SetLabel( text )
|
||||
|
||||
|
@ -2951,6 +2951,20 @@ class PopupMessageManager( wx.Frame ):
|
|||
except: print( repr( message_string ) )
|
||||
|
||||
|
||||
def _ShouldDisplayMessage( self, message ):
|
||||
|
||||
message_type = message.GetType()
|
||||
|
||||
if message_type == HC.MESSAGE_TYPE_ERROR:
|
||||
|
||||
( etype, value, trace ) = message.GetInfo( 'error' )
|
||||
|
||||
if etype == wx.PyDeadObjectError: return False
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _SizeAndPositionAndShow( self ):
|
||||
|
||||
try:
|
||||
|
@ -3001,9 +3015,12 @@ class PopupMessageManager( wx.Frame ):
|
|||
|
||||
self._PrintMessage( message )
|
||||
|
||||
self._pending_messages.append( message )
|
||||
|
||||
self._CheckPending()
|
||||
if self._ShouldDisplayMessage( message ):
|
||||
|
||||
self._pending_messages.append( message )
|
||||
|
||||
self._CheckPending()
|
||||
|
||||
|
||||
except:
|
||||
|
||||
|
@ -4096,6 +4113,12 @@ class TagsBox( ListBox ):
|
|||
|
||||
elif command == 'copy_all_tags': HC.pubsub.pub( 'clipboard', 'text', os.linesep.join( self._GetAllTagsForClipboard() ) )
|
||||
elif command == 'copy_all_tags_with_counts': HC.pubsub.pub( 'clipboard', 'text', os.linesep.join( self._GetAllTagsForClipboard( with_counts = True ) ) )
|
||||
elif command == 'new_search_page_with_term':
|
||||
|
||||
term = self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ]
|
||||
|
||||
HC.pubsub.pub( 'new_page_query', HC.LOCAL_FILE_SERVICE_KEY, initial_predicates = [ HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', term ) ) ] )
|
||||
|
||||
elif command in ( 'parent', 'sibling' ):
|
||||
|
||||
tag = self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ]
|
||||
|
@ -4130,6 +4153,18 @@ class TagsBox( ListBox ):
|
|||
|
||||
menu = wx.Menu()
|
||||
|
||||
if self._current_selected_index is not None:
|
||||
|
||||
term = self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ]
|
||||
|
||||
if type( term ) in ( str, unicode ):
|
||||
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'new_search_page_with_term' ), 'open a new search page for "' + term + '"' )
|
||||
|
||||
menu.AppendSeparator()
|
||||
|
||||
|
||||
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_all_tags' ), 'copy all tags' )
|
||||
if self.has_counts: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_all_tags_with_counts' ), 'copy all tags with counts' )
|
||||
|
||||
|
@ -4139,13 +4174,13 @@ class TagsBox( ListBox ):
|
|||
|
||||
if type( term ) in ( str, unicode ):
|
||||
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_term' ), 'copy ' + term )
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_term' ), 'copy "' + term + '"' )
|
||||
|
||||
if ':' in term:
|
||||
|
||||
sub_term = term.split( ':', 1 )[1]
|
||||
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_sub_term' ), 'copy ' + sub_term )
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_sub_term' ), 'copy "' + sub_term + '"' )
|
||||
|
||||
|
||||
menu.AppendSeparator()
|
||||
|
|
|
@ -3759,7 +3759,19 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
HC.options[ 'preview_cache_size' ] = self._preview_cache_size.GetValue() * 1048576
|
||||
HC.options[ 'fullscreen_cache_size' ] = self._fullscreen_cache_size.GetValue() * 1048576
|
||||
|
||||
HC.options[ 'thumbnail_dimensions' ] = [ self._thumbnail_width.GetValue(), self._thumbnail_height.GetValue() ]
|
||||
new_thumbnail_dimensions = [ self._thumbnail_width.GetValue(), self._thumbnail_height.GetValue() ]
|
||||
|
||||
if new_thumbnail_dimensions != HC.options[ 'thumbnail_dimensions' ]:
|
||||
|
||||
text = 'You have changed the thumbnail dimensions, which will mean deleting all the old resized thumbnails right now, during which time the database will be locked. If you have tens or hundreds of thousands of files, this could take a long time.'
|
||||
text += os.linesep * 2
|
||||
text += 'Are you sure you want to change your thumbnail dimensions?'
|
||||
|
||||
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_YES: HC.options[ 'thumbnail_dimensions' ] = new_thumbnail_dimensions
|
||||
|
||||
|
||||
|
||||
HC.options[ 'num_autocomplete_chars' ] = self._num_autocomplete_chars.GetValue()
|
||||
|
||||
|
@ -4941,7 +4953,7 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
info[ 'upper' ] = 5
|
||||
|
||||
|
||||
self._edit_log.append( ( HC.ADD, ( service_key, service_type, name, info ) ) )
|
||||
self._edit_log.append( HC.EditLogActionAdd( ( service_key, service_type, name, info ) ) )
|
||||
|
||||
page = self._Panel( services_listbook, service_key, service_type, name, info )
|
||||
|
||||
|
@ -5022,7 +5034,12 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
for page in all_pages:
|
||||
|
||||
if page.HasChanges(): self._edit_log.append( ( HC.EDIT, page.GetInfo() ) )
|
||||
if page.HasChanges():
|
||||
|
||||
( service_key, service_type, name, info ) = page.GetInfo()
|
||||
|
||||
self._edit_log.append( HC.EditLogActionEdit( service_key, ( service_key, service_type, name, info ) ) )
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -5068,15 +5085,14 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
|
||||
self._edit_log.append( ( HC.DELETE, service_key ) )
|
||||
|
||||
services_listbook.DeleteCurrentPage()
|
||||
|
||||
if dlg.ShowModal() != wx.ID_YES: return
|
||||
|
||||
|
||||
|
||||
self._edit_log.append( HC.EditLogActionDelete( service_key ) )
|
||||
|
||||
services_listbook.DeleteCurrentPage()
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -5152,7 +5168,7 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
else:
|
||||
|
||||
self._edit_log.append( ( HC.ADD, ( service_key, service_type, name, info ) ) )
|
||||
self._edit_log.append( HC.EditLogActionAdd( ( service_key, service_type, name, info ) ) )
|
||||
|
||||
page = self._Panel( services_listbook, service_key, service_type, name, info )
|
||||
|
||||
|
@ -5395,8 +5411,6 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
if name == '': raise Exception( 'Please enter a name' )
|
||||
|
||||
info = {}
|
||||
|
||||
if service_type in HC.REMOTE_SERVICES:
|
||||
|
||||
connection_string = self._service_credentials.GetValue()
|
||||
|
|
|
@ -64,7 +64,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 15
|
||||
SOFTWARE_VERSION = 134
|
||||
SOFTWARE_VERSION = 135
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
@ -1753,6 +1753,56 @@ class ContentUpdate( object ):
|
|||
|
||||
def ToTuple( self ): return ( self._data_type, self._action, self._row )
|
||||
|
||||
class EditLogAction( object ):
|
||||
|
||||
yaml_tag = u'!EditLogAction'
|
||||
|
||||
def __init__( self, action ): self._action = action
|
||||
|
||||
def GetAction( self ): return self._action
|
||||
|
||||
class EditLogActionAdd( EditLogAction ):
|
||||
|
||||
yaml_tag = u'!EditLogActionAdd'
|
||||
|
||||
def __init__( self, data ):
|
||||
|
||||
EditLogAction.__init__( self, ADD )
|
||||
|
||||
self._data = data
|
||||
|
||||
|
||||
def GetData( self ): return self._data
|
||||
|
||||
class EditLogActionDelete( EditLogAction ):
|
||||
|
||||
yaml_tag = u'!EditLogActionDelete'
|
||||
|
||||
def __init__( self, identifier ):
|
||||
|
||||
EditLogAction.__init__( self, DELETE )
|
||||
|
||||
self._identifier = identifier
|
||||
|
||||
|
||||
def GetIdentifier( self ): return self._identifier
|
||||
|
||||
class EditLogActionEdit( EditLogAction ):
|
||||
|
||||
yaml_tag = u'!EditLogActionEdit'
|
||||
|
||||
def __init__( self, identifier, data ):
|
||||
|
||||
EditLogAction.__init__( self, EDIT )
|
||||
|
||||
self._identifier = identifier
|
||||
self._data = data
|
||||
|
||||
|
||||
def GetData( self ): return self._data
|
||||
|
||||
def GetIdentifier( self ): return self._identifier
|
||||
|
||||
class JobDatabase( object ):
|
||||
|
||||
yaml_tag = u'!JobDatabase'
|
||||
|
|
|
@ -57,6 +57,13 @@ class DAEMONQueue( DAEMON ):
|
|||
self._event.clear()
|
||||
|
||||
|
||||
while HC.app.JustWokeFromSleep():
|
||||
|
||||
if HC.shutdown: return
|
||||
|
||||
time.sleep( 10 )
|
||||
|
||||
|
||||
items = []
|
||||
|
||||
while not self._queue.empty(): items.append( self._queue.get() )
|
||||
|
@ -93,6 +100,13 @@ class DAEMONWorker( DAEMON ):
|
|||
|
||||
time.sleep( self._pre_callable_wait )
|
||||
|
||||
while HC.app.JustWokeFromSleep():
|
||||
|
||||
if HC.shutdown: return
|
||||
|
||||
time.sleep( 10 )
|
||||
|
||||
|
||||
try: self._callable()
|
||||
except Exception as e:
|
||||
|
||||
|
|
|
@ -108,6 +108,8 @@ class Controller( wx.App ):
|
|||
|
||||
def GetManager( self, manager_type ): return self._managers[ manager_type ]
|
||||
|
||||
def JustWokeFromSleep( self ): return False
|
||||
|
||||
def OnInit( self ):
|
||||
|
||||
HC.app = self
|
||||
|
|
|
@ -1121,10 +1121,17 @@ class ServiceDB( FileDB, MessageDB, TagDB ):
|
|||
|
||||
def _GetAccessKey( self, c, registration_key ):
|
||||
|
||||
try: ( access_key, ) = c.execute( 'SELECT access_key FROM registration_keys WHERE registration_key = ?;', ( sqlite3.Binary( hashlib.sha256( registration_key ).digest() ), ) ).fetchone()
|
||||
# we generate a new access_key every time this is requested so that if the registration_key leaks, no one grab the access_key before the legit user does
|
||||
# the reg_key is deleted when the last-requested access_key is used to create a session, which calls getaccountkeyfromaccesskey
|
||||
|
||||
try: ( one, ) = c.execute( 'SELECT 1 FROM registration_keys WHERE registration_key = ?;', ( sqlite3.Binary( hashlib.sha256( registration_key ).digest() ), ) ).fetchone()
|
||||
except: raise HydrusExceptions.ForbiddenException( 'The service could not find that registration key in its database.' )
|
||||
|
||||
return access_key
|
||||
new_access_key = os.urandom( HC.HYDRUS_KEY_LENGTH )
|
||||
|
||||
c.execute( 'UPDATE registration_keys SET access_key = ? WHERE registration_key = ?;', ( sqlite3.Binary( new_access_key ), sqlite3.Binary( hashlib.sha256( registration_key ).digest() ) ) )
|
||||
|
||||
return new_access_key
|
||||
|
||||
|
||||
def _GetAccount( self, c, account_key ):
|
||||
|
|
|
@ -58,7 +58,8 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
os.chmod( path, stat.S_IWRITE )
|
||||
|
||||
function_called( path ) # try again
|
||||
try: function_called( path ) # try again
|
||||
except: pass
|
||||
|
||||
|
||||
if os.path.exists( HC.DB_DIR ): shutil.rmtree( HC.DB_DIR, onerror = make_temp_files_deletable )
|
||||
|
@ -1016,10 +1017,10 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
edit_log = []
|
||||
|
||||
edit_log.append( ( HC.ADD, new_tag_repo ) )
|
||||
edit_log.append( ( HC.ADD, other_new_tag_repo ) )
|
||||
edit_log.append( ( HC.ADD, new_local_like ) )
|
||||
edit_log.append( ( HC.ADD, new_local_numerical ) )
|
||||
edit_log.append( HC.EditLogActionAdd( new_tag_repo ) )
|
||||
edit_log.append( HC.EditLogActionAdd( other_new_tag_repo ) )
|
||||
edit_log.append( HC.EditLogActionAdd( new_local_like ) )
|
||||
edit_log.append( HC.EditLogActionAdd( new_local_numerical ) )
|
||||
|
||||
self._write( 'update_services', edit_log )
|
||||
|
||||
|
@ -1043,8 +1044,8 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
edit_log = []
|
||||
|
||||
edit_log.append( ( HC.DELETE, new_local_like[0] ) )
|
||||
edit_log.append( ( HC.EDIT, other_new_tag_repo_updated ) )
|
||||
edit_log.append( HC.EditLogActionDelete( new_local_like[0] ) )
|
||||
edit_log.append( HC.EditLogActionEdit( other_new_tag_repo_updated[0], other_new_tag_repo_updated ) )
|
||||
|
||||
self._write( 'update_services', edit_log )
|
||||
|
||||
|
@ -1056,7 +1057,7 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
edit_log = []
|
||||
|
||||
edit_log.append( ( HC.DELETE, other_new_tag_repo_updated[0] ) )
|
||||
edit_log.append( HC.EditLogActionDelete( other_new_tag_repo_updated[0] ) )
|
||||
|
||||
self._write( 'update_services', edit_log )
|
||||
|
||||
|
@ -1143,7 +1144,8 @@ class TestServerDB( unittest.TestCase ):
|
|||
|
||||
os.chmod( path, stat.S_IWRITE )
|
||||
|
||||
function_called( path ) # try again
|
||||
try: function_called( path ) # try again
|
||||
except: pass
|
||||
|
||||
|
||||
if os.path.exists( HC.DB_DIR ): shutil.rmtree( HC.DB_DIR, onerror = make_temp_files_deletable )
|
||||
|
@ -1155,15 +1157,75 @@ class TestServerDB( unittest.TestCase ):
|
|||
|
||||
def _test_account_creation( self ):
|
||||
|
||||
# create new account types
|
||||
# edit the account types
|
||||
# create rkeys
|
||||
# create akeys
|
||||
# test successive rkey fetch gives new akeys
|
||||
# get session with akey
|
||||
# make sure rkey is now dead
|
||||
result = self._read( 'account_types', self._tag_service_key )
|
||||
|
||||
pass
|
||||
( service_admin_at, ) = result
|
||||
|
||||
self.assertEqual( service_admin_at.GetTitle(), 'service admin' )
|
||||
self.assertEqual( service_admin_at.GetPermissions(), [ HC.GET_DATA, HC.POST_DATA, HC.POST_PETITIONS, HC.RESOLVE_PETITIONS, HC.MANAGE_USERS, HC.GENERAL_ADMIN ] )
|
||||
self.assertEqual( service_admin_at.GetMaxBytes(), None )
|
||||
self.assertEqual( service_admin_at.GetMaxRequests(), None )
|
||||
|
||||
#
|
||||
|
||||
user_at = HC.AccountType( 'user', [ HC.GET_DATA, HC.POST_DATA ], ( 50000, 500 ) )
|
||||
|
||||
edit_log = [ ( HC.ADD, user_at ) ]
|
||||
|
||||
self._write( 'account_types', self._tag_service_key, edit_log )
|
||||
|
||||
result = self._read( 'account_types', self._tag_service_key )
|
||||
|
||||
( at_1, at_2 ) = result
|
||||
|
||||
d = { at_1.GetTitle() : at_1, at_2.GetTitle() : at_2 }
|
||||
|
||||
at = d[ 'user' ]
|
||||
|
||||
self.assertEqual( at.GetPermissions(), [ HC.GET_DATA, HC.POST_DATA ] )
|
||||
self.assertEqual( at.GetMaxBytes(), 50000 )
|
||||
self.assertEqual( at.GetMaxRequests(), 500 )
|
||||
|
||||
#
|
||||
|
||||
user_at_diff = HC.AccountType( 'user different', [ HC.GET_DATA ], ( 40000, None ) )
|
||||
|
||||
edit_log = [ ( HC.EDIT, ( 'user', user_at_diff ) ) ]
|
||||
|
||||
self._write( 'account_types', self._tag_service_key, edit_log )
|
||||
|
||||
result = self._read( 'account_types', self._tag_service_key )
|
||||
|
||||
( at_1, at_2 ) = result
|
||||
|
||||
d = { at_1.GetTitle() : at_1, at_2.GetTitle() : at_2 }
|
||||
|
||||
at = d[ 'user different' ]
|
||||
|
||||
self.assertEqual( at.GetPermissions(), [ HC.GET_DATA ] )
|
||||
self.assertEqual( at.GetMaxBytes(), 40000 )
|
||||
self.assertEqual( at.GetMaxRequests(), None )
|
||||
|
||||
#
|
||||
|
||||
r_keys = self._read( 'registration_keys', self._tag_service_key, 5, 'user different', 86400 * 365 )
|
||||
|
||||
self.assertEqual( len( r_keys ), 5 )
|
||||
|
||||
for r_key in r_keys: self.assertEqual( len( r_key ), 32 )
|
||||
|
||||
r_key = r_keys[0]
|
||||
|
||||
access_key = self._read( 'access_key', r_key )
|
||||
access_key_2 = self._read( 'access_key', r_key )
|
||||
|
||||
self.assertNotEqual( access_key, access_key_2 )
|
||||
|
||||
self.assertRaises( HydrusExceptions.ForbiddenException, self._read, 'account_key_from_access_key', self._tag_service_key, access_key )
|
||||
|
||||
account_key = self._read( 'account_key_from_access_key', self._tag_service_key, access_key_2 )
|
||||
|
||||
self.assertRaises( HydrusExceptions.ForbiddenException, self._read, 'access_key', r_key )
|
||||
|
||||
|
||||
def _test_content_creation( self ):
|
||||
|
|
Loading…
Reference in New Issue