Version 246

This commit is contained in:
Hydrus Network Developer 2017-03-08 17:23:12 -06:00
parent 9b9388f71c
commit 2ebedbc645
48 changed files with 1556 additions and 3686 deletions

View File

@ -8,6 +8,55 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 246</h3></li>
<ul>
<li>fixed a critical bug in serverside content deserialisating that meant servers were not processing most client-submitted data properly</li>
<li>fixed a critical bug in 'all known tags' autocomplete regeneration--please run database->regen->a/c cache when it is convenient and let me know if your numbers are still off</li>
<li>fixed the pre-v238 update problem for good by abandoning update attempts and advising users to try v238 first</li>
<li>clientside invalid tags will now be collapsed like with the server last week. if a tag is invalid (typically something with an extra space, like "series: blah"), the update code will attempt to replace existing mappings with the collapsed valid version. some unusual cases may remain--they will be replaced with 'invalid namespace "series "' and similar. Please remove and replace these when convenient and contact me if there are way too many for you to deal with</li>
<li>duplicates pages now have a file domain for the filtering section, and they remember this domain through session loads</li>
<li>this file domain is accurate--counting potential duplicates and fetching pairs for 'show some pairs' only from those domains. the issue of remote files appearing should be gone!</li>
<li>there is now only one 'idle' entry in the duplicates page cog menu--it combines the three previous into one</li>
<li>fixed numerous irregularities across the wildcard code. all search input now has an implicit '*' on the end unless you put a '*' anywhere else, in which case it acts exactly as you enter it, with a non-* beginning matching beginning of string, whitespace, or colon, and non-* end matching end of string or whitespace</li>
<li>autocomplete now searches namespace, so entering 'char' will load up all the 'character:' tags along with 'series:di gi charat'. this can lag to hell and back, so it may either need some work or be optional in the future. feedback would be appreciated</li>
<li>typing 'namespace:' will include all the series tags below the special optimised 'namespace:*anything*' tag</li>
<li>autocomplete searches recognise an explicit '*' no matter where it is in the entry text. typing 'a*' will load up all the a tags and present a 'a*' wildcard option</li>
<li>quickly entering a wildcard entry will now submit the correct wildcard predicate ('rather than a literal 'hel*' or whatever tag)</li>
<li>review services panel now reports total mappings info on tag services</li>
<li>review services panel now reports total files info on file services</li>
<li>manage services's listctrl is now type | name | deletable and initially sorts by type. the strings used for hydrus service types are also improved</li>
<li>manage serverside services (called by server admins to manage their services) have fixed setnondupe port and name on edit service events</li>
<li>new popup messages will now also appear if there were previously no popup messages to display if the current focus is on a child on_top frame, such as review services (you'll now see the processing popup appear when you click 'process now' on review services)</li>
<li>the popup message manager now initialises its display window with a single message that is quickly dismissed. this helps set up some variables in a safe environment so they don't have to be generated later when the gui might be minimised or otherwise unusual</li>
<li>hid hydrus update files from 'all local files' searches</li>
<li>added 'media_view' entries for hydrus update files, just in case they are still visible in some unusual contexts (and they may be again in a future update anyway)</li>
<li>fixed 'recent tags' being returned from the database out of order</li>
<li>by default, 'recent tags' is now on for new users</li>
<li>'get tags even if file already in db' now defaults to False</li>
<li>file import status now allows a 'delete' action below the 'skip' action</li>
<li>file import status right-click event processing is more sane</li>
<li>fixed the new raw width/height sort choices, which were accidentally swapped</li>
<li>cleaned the media sort code generally</li>
<li>cleared out some redundant rows that are in some users' client_files_locations</li>
<li>namespaced predicates are no longer count-merged with their namespaceless versions in 'write' autocomplete dropdowns</li>
<li>'unknown' accounts should now properly resync after clientside service change</li>
<li>improved how registration keys are checked serverside when fetching access keys</li>
<li>fixed a v244 update problem when unexected additional tag parent/sibling petitions rows exist</li>
<li>improved my free space test code and applied it to the old v243->v244 free space test (it'll now test free space on your temporary path and report problems appropriately)</li>
<li>to improve log privacy and cleanliness, and to make it easier to report profiles, db/pubsub profiles now write to a separate log file named appropriately and labelled with the process's start time</li>
<li>profiles are more concise and formatted a little neater</li>
<li>across the program, three-period ... ellipses are now replaced with their single character unicode … counterpart (except to the console, where any instance of the unicode ellipsis will now be converted back to ...)</li>
<li>cleaned up some log printing code</li>
<li>cleaned up some experimental static serialisation code, still thinking if I like it or not</li>
<li>started on some proper unit tests for hydrus serialisable objects</li>
<li>fixed and otherwise updated a heap of unit test code to account for the v245 changes</li>
<li>cleaned up a bunch of old database table join code</li>
<li>started some databse-query-tuple-stripping code cleaning</li>
<li>deleted more old unused code</li>
<li>misc timing improvements</li>
<li>misc code cleanup experimentation</li>
<li>misc cleanup</li>
</ul>
<li><h3>version 245</h3></li>
<ul>
<li>fixed a v244 update problem for clients updating from <v238</li>

View File

@ -1987,7 +1987,10 @@ class ServicesManager( object ):
self._keys_to_services = { service.GetServiceKey() : service for service in services }
compare_function = lambda a, b: cmp( a.GetName(), b.GetName() )
def compare_function( a, b ):
return cmp( a.GetName(), b.GetName() )
self._services_sorted = list( services )
self._services_sorted.sort( cmp = compare_function )
@ -1997,7 +2000,12 @@ class ServicesManager( object ):
with self._lock:
filtered_service_keys = [ service_key for service_key in service_keys if self._keys_to_services[ service_key ].GetServiceType() in desired_types ]
def func( service_key ):
return self._keys_to_services[ service_key ].GetServiceType() in desired_types
filtered_service_keys = filter( func, service_keys )
return filtered_service_keys
@ -2007,7 +2015,12 @@ class ServicesManager( object ):
with self._lock:
filtered_service_keys = [ service_key for service_key in service_keys if service_key in self._keys_to_services ]
def func( service_key ):
return service_key in self._keys_to_services
filtered_service_keys = filter( func, service_keys )
return filtered_service_keys
@ -2045,7 +2058,12 @@ class ServicesManager( object ):
with self._lock:
services = [ service for service in self._services_sorted if service.GetServiceType() in desired_types ]
def func( service ):
return service.GetServiceType() in desired_types
services = filter( func, self._services_sorted )
if randomised:
@ -2448,11 +2466,11 @@ class TagSiblingsManager( object ):
else:
matching_keys = ClientSearch.FilterTagsBySearchEntry( service_key, search_text, siblings.keys(), search_siblings = False )
matching_keys = ClientSearch.FilterTagsBySearchText( service_key, search_text, siblings.keys(), search_siblings = False )
key_based_matching_values = { siblings[ key ] for key in matching_keys }
value_based_matching_values = ClientSearch.FilterTagsBySearchEntry( service_key, search_text, siblings.values(), search_siblings = False )
value_based_matching_values = ClientSearch.FilterTagsBySearchText( service_key, search_text, siblings.values(), search_siblings = False )
matching_values = key_based_matching_values.union( value_based_matching_values )

View File

@ -173,7 +173,8 @@ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED = 1
MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED = 2
MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED = 3
MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON = 4
MEDIA_VIEWER_ACTION_DO_NOT_SHOW = 5
MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY = 5
MEDIA_VIEWER_ACTION_DO_NOT_SHOW = 6
media_viewer_action_string_lookup = {}
@ -182,11 +183,12 @@ media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED ] =
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED ] = 'show, but initially behind an embed button'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED ] = 'show, but initially behind an embed button, and start paused'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON ] = 'show an \'open externally\' button'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_DO_NOT_SHOW ] = 'do not show in the media viewer. on thumbnail activation, open externally'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY ] = 'do not show in the media viewer. on thumbnail activation, open externally'
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_DO_NOT_SHOW ] = 'do not show at all'
static_full_support = [ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW ]
animated_full_support = [ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED, MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW ]
no_support = [ MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW ]
static_full_support = [ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY ]
animated_full_support = [ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED, MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY ]
no_support = [ MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, MEDIA_VIEWER_ACTION_DO_NOT_SHOW ]
media_viewer_capabilities = {}
@ -196,7 +198,7 @@ media_viewer_capabilities[ HC.IMAGE_GIF ] = animated_full_support
if HC.PLATFORM_WINDOWS:
media_viewer_capabilities[ HC.APPLICATION_FLASH ] = [ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW ]
media_viewer_capabilities[ HC.APPLICATION_FLASH ] = [ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, MEDIA_VIEWER_ACTION_DO_NOT_SHOW ]
else:
@ -215,7 +217,9 @@ media_viewer_capabilities[ HC.VIDEO_WMV ] = animated_full_support
media_viewer_capabilities[ HC.AUDIO_MP3 ] = no_support
media_viewer_capabilities[ HC.AUDIO_OGG ] = no_support
media_viewer_capabilities[ HC.AUDIO_FLAC ] = no_support
media_viewer_capabilities[ HC.AUDIO_WMA] = no_support
media_viewer_capabilities[ HC.AUDIO_WMA ] = no_support
media_viewer_capabilities[ HC.APPLICATION_HYDRUS_UPDATE_CONTENT ] = no_support
media_viewer_capabilities[ HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = no_support
MEDIA_VIEWER_SCALE_100 = 0
MEDIA_VIEWER_SCALE_MAX_REGULAR = 1

View File

@ -45,6 +45,8 @@ class Controller( HydrusController.HydrusController ):
HydrusController.HydrusController.__init__( self, db_dir, no_daemons, no_wal )
self._name = 'client'
HydrusGlobals.client_controller = self
# just to set up some defaults, in case some db update expects something for an odd yaml-loading reason
@ -518,7 +520,7 @@ class Controller( HydrusController.HydrusController ):
def InitModel( self ):
self.pub( 'splash_set_title_text', 'booting db...' )
self.pub( 'splash_set_title_text', u'booting db\u2026' )
self._http = ClientNetworking.HTTPConnectionManager()
@ -601,7 +603,7 @@ class Controller( HydrusController.HydrusController ):
self.CallBlockingToWx( wx_code_password )
self.pub( 'splash_set_title_text', 'booting gui...' )
self.pub( 'splash_set_title_text', u'booting gui\u2026' )
def wx_code_gui():
@ -627,7 +629,7 @@ class Controller( HydrusController.HydrusController ):
if not self._no_daemons:
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'CheckMouseIdle', ClientDaemons.DAEMONCheckMouseIdle, period = 10 ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SynchroniseAccounts', ClientDaemons.DAEMONSynchroniseAccounts, ( 'permissions_are_stale', ) ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SynchroniseAccounts', ClientDaemons.DAEMONSynchroniseAccounts, ( 'notify_unknown_accounts', ) ) )
self._daemons.append( HydrusThreading.DAEMONWorker( self, 'SaveDirtyObjects', ClientDaemons.DAEMONSaveDirtyObjects, ( 'important_dirt_to_clean', ), period = 30 ) )
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'DownloadFiles', ClientDaemons.DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) ) )
@ -688,7 +690,7 @@ class Controller( HydrusController.HydrusController ):
loaded_into_disk_cache = HydrusGlobals.client_controller.Read( 'load_into_disk_cache', stop_time = disk_cache_stop_time, caller_limit = disk_cache_maintenance_mb * 1024 * 1024 )
if self._new_options.GetBoolean( 'maintain_similar_files_phashes_during_idle' ):
if self._new_options.GetBoolean( 'maintain_similar_files_duplicate_pairs_during_idle' ):
phashes_stop_time = stop_time
@ -699,9 +701,6 @@ class Controller( HydrusController.HydrusController ):
self.WriteInterruptable( 'maintain_similar_files_phashes', stop_time = phashes_stop_time )
if self._new_options.GetBoolean( 'maintain_similar_files_tree_during_idle' ):
tree_stop_time = stop_time
if tree_stop_time is None:
@ -711,9 +710,6 @@ class Controller( HydrusController.HydrusController ):
self.WriteInterruptable( 'maintain_similar_files_tree', stop_time = tree_stop_time, abandon_if_other_work_to_do = True )
if self._new_options.GetBoolean( 'maintain_similar_files_duplicate_pairs_during_idle' ):
search_distance = self._new_options.GetInteger( 'similar_files_duplicate_pairs_search_distance' )
search_stop_time = stop_time
@ -956,7 +952,7 @@ class Controller( HydrusController.HydrusController ):
# I have had this as 'suppress' before
self._app.SetAssertMode( wx.PYAPP_ASSERT_EXCEPTION )
HydrusData.Print( 'booting controller...' )
HydrusData.Print( u'booting controller\u2026' )
self.CreateSplash()
@ -966,7 +962,7 @@ class Controller( HydrusController.HydrusController ):
self._app.MainLoop()
HydrusData.Print( 'shutting down controller...' )
HydrusData.Print( u'shutting down controller\u2026' )
def SaveDirtyObjects( self ):
@ -1144,11 +1140,11 @@ class Controller( HydrusController.HydrusController ):
try:
self.pub( 'splash_set_title_text', 'shutting down gui...' )
self.pub( 'splash_set_title_text', u'shutting down gui\u2026' )
self.ShutdownView()
self.pub( 'splash_set_title_text', 'shutting down db...' )
self.pub( 'splash_set_title_text', u'shutting down db\u2026' )
self.ShutdownModel()

File diff suppressed because it is too large Load Diff

View File

@ -353,7 +353,6 @@ def MergePredicates( predicates, add_namespaceless = False ):
return master_predicate_dict.values()
def ShowExceptionClient( e ):
@ -567,8 +566,6 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'booleans' ][ 'use_system_ffmpeg' ] = False
self._dictionary[ 'booleans' ][ 'maintain_similar_files_phashes_during_idle' ] = False
self._dictionary[ 'booleans' ][ 'maintain_similar_files_tree_during_idle' ] = False
self._dictionary[ 'booleans' ][ 'maintain_similar_files_duplicate_pairs_during_idle' ] = False
#
@ -600,7 +597,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'noneable_integers' ][ 'disk_cache_maintenance_mb' ] = 256
self._dictionary[ 'noneable_integers' ][ 'disk_cache_init_period' ] = 4
self._dictionary[ 'noneable_integers' ][ 'num_recent_tags' ] = None
self._dictionary[ 'noneable_integers' ][ 'num_recent_tags' ] = 20
self._dictionary[ 'noneable_integers' ][ 'maintenance_vacuum_period_days' ] = 30
@ -668,6 +665,9 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'media_view' ][ HC.APPLICATION_PDF ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_CONTENT ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_AVI ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_FLV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MOV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )

View File

@ -51,7 +51,7 @@ def GetClientDefaultOptions():
regex_favourites = []
regex_favourites.append( ( r'[1-9]+\d*(?=.{4}$)', r'...0074.jpg -> 74 - [1-9]+\d*(?=.{4}$)' ) )
regex_favourites.append( ( r'[1-9]+\d*(?=.{4}$)', u'\u2026' + r'0074.jpg -> 74 - [1-9]+\d*(?=.{4}$)' ) )
regex_favourites.append( ( r'[^' + os.path.sep.encode( 'string_escape' ) + ']+*(?=\s-)', r'E:\my collection\author name - v4c1p0074.jpg -> author name - [^' + os.path.sep.encode( 'string_escape' ) + ']+(?=\s-)' ) )
options[ 'regex_favourites' ] = regex_favourites

View File

@ -784,14 +784,20 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
for ( k, v ) in count.items():
if v > 100: print ( k, v )
if v > 100:
HydrusData.Print( ( k, v ) )
HydrusData.Print( 'gc classes:' )
for ( k, v ) in class_count.items():
if v > 100: print ( k, v )
if v > 100:
HydrusData.Print( ( k, v ) )
HydrusData.Print( 'uncollectable garbage: ' + HydrusData.ToUnicode( gc.garbage ) )
@ -3281,39 +3287,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._controller.CallToThread( self._THREADSyncToTagArchive, hta_path, tag_service_key, file_service_key, adding, namespaces, hashes )
'''
class FrameComposeMessage( ClientGUITopLevelWindows.Frame ):
def __init__( self, empty_draft_message ):
ClientGUITopLevelWindows.Frame.__init__( self, None, HC.app.PrepStringForDisplay( 'Compose Message' ) )
self.SetInitialSize( ( 920, 600 ) )
vbox = wx.BoxSizer( wx.VERTICAL )
self._draft_panel = ClientGUIMessages.DraftPanel( self, empty_draft_message )
vbox.AddF( self._draft_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
self.Show( True )
HC.pubsub.sub( self, 'DeleteConversation', 'delete_conversation_gui' )
HC.pubsub.sub( self, 'DeleteDraft', 'delete_draft_gui' )
def DeleteConversation( self, conversation_key ):
if self._draft_panel.GetConversationKey() == conversation_key: self.Close()
def DeleteDraft( self, draft_key ):
if draft_key == self._draft_panel.GetDraftKey(): self.Close()
'''
class FrameSplash( wx.Frame ):
WIDTH = 420

View File

@ -102,7 +102,7 @@ class AutoCompleteDropdown( wx.Panel ):
self.SetSizer( vbox )
self._cache_text = ''
self._cache_text = None
self._cached_results = []
self._initial_matches_fetched = False
@ -462,8 +462,7 @@ class AutoCompleteDropdown( wx.Panel ):
def RefreshList( self ):
self._cache_text = ''
self._current_namespace = ''
self._cache_text = None
self._UpdateList()
@ -524,7 +523,6 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
AutoCompleteDropdown.__init__( self, parent )
self._current_namespace = ''
self._current_matches = []
file_service = HydrusGlobals.client_controller.GetServicesManager().GetService( self._file_service_key )
@ -532,7 +530,6 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
tag_service = HydrusGlobals.client_controller.GetServicesManager().GetService( self._tag_service_key )
self._file_repo_button = ClientGUICommon.BetterButton( self._dropdown_window, file_service.GetName(), self.FileButtonHit )
self._file_repo_button.SetMinSize( ( 20, -1 ) )
@ -577,8 +574,7 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
self._tag_repo_button.SetLabelText( name )
self._cache_text = ''
self._current_namespace = ''
self._cache_text = None
wx.CallAfter( self.RefreshList )
@ -741,7 +737,7 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
def _BroadcastCurrentText( self ):
( inclusive, search_text, entry_predicate ) = self._ParseSearchText()
( inclusive, search_text, explicit_wildcard, cache_text, entry_predicate ) = self._ParseSearchText()
try:
@ -790,33 +786,46 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
inclusive = False
tag = raw_entry[1:]
entry_text = raw_entry[1:]
else:
inclusive = True
tag = raw_entry
entry_text = raw_entry
tag = HydrusTags.CleanTag( tag )
tag = HydrusTags.CleanTag( entry_text )
search_text = ClientSearch.ConvertTagToSearchable( tag )
explicit_wildcard = '*' in entry_text
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
search_text = ClientSearch.ConvertEntryTextToSearchText( entry_text )
sibling = siblings_manager.GetSibling( self._tag_service_key, tag )
if sibling is None:
if explicit_wildcard:
entry_predicate = ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, tag, inclusive )
cache_text = None
entry_predicate = ClientSearch.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive )
else:
entry_predicate = ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, sibling, inclusive )
cache_text = search_text[:-1] # take off the trailing '*' for the cache text
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
sibling = siblings_manager.GetSibling( self._tag_service_key, tag )
if sibling is None:
entry_predicate = ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, tag, inclusive )
else:
entry_predicate = ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, sibling, inclusive )
return ( inclusive, search_text, entry_predicate )
return ( inclusive, search_text, explicit_wildcard, cache_text, entry_predicate )
def _GenerateMatches( self ):
@ -825,21 +834,26 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
num_autocomplete_chars = HC.options[ 'num_autocomplete_chars' ]
( inclusive, search_text, entry_predicate ) = self._ParseSearchText()
( inclusive, search_text, explicit_wildcard, cache_text, entry_predicate ) = self._ParseSearchText()
if search_text in ( '', ':' ):
if search_text in ( '', ':', '*' ):
input_just_changed = self._cache_text != ''
input_just_changed = self._cache_text is not None
db_not_going_to_hang_if_we_hit_it = not HydrusGlobals.client_controller.DBCurrentlyDoingJob()
if input_just_changed or db_not_going_to_hang_if_we_hit_it or not self._initial_matches_fetched:
self._cache_text = ''
self._current_namespace = ''
self._cache_text = None
if self._file_service_key == CC.COMBINED_FILE_SERVICE_KEY: search_service_key = self._tag_service_key
else: search_service_key = self._file_service_key
if self._file_service_key == CC.COMBINED_FILE_SERVICE_KEY:
search_service_key = self._tag_service_key
else:
search_service_key = self._file_service_key
self._cached_results = HydrusGlobals.client_controller.Read( 'file_system_predicates', search_service_key )
@ -848,41 +862,13 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
else:
must_do_a_search = False
if '*' in search_text:
must_do_a_search = True
( namespace, half_complete_subtag ) = HydrusTags.SplitTag( search_text )
if namespace != '':
if namespace != self._current_namespace:
self._current_namespace = namespace # do a new search, no matter what half_complete tag is
if half_complete_subtag != '': must_do_a_search = True
else:
if self._cache_text == self._current_namespace + ':' and half_complete_subtag != '':
must_do_a_search = True
else:
self._current_namespace = namespace
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
if half_complete_subtag == '':
if False and half_complete_subtag == '':
self._cache_text = self._current_namespace + ':'
self._cache_text = None
matches = [] # a query like 'namespace:'
@ -904,20 +890,28 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
if fetch_from_db:
# if user searches 'blah', then we include 'blah (23)' for 'series:blah (10)', 'blah (13)'
# if they search for 'series:blah', then we don't!
add_namespaceless = ':' not in namespace
include_current = self._file_search_context.IncludeCurrentTags()
include_pending = self._file_search_context.IncludePendingTags()
if len( half_complete_subtag ) < num_autocomplete_chars and '*' not in search_text:
small_and_specific_search = cache_text is not None and len( cache_text ) < num_autocomplete_chars
if small_and_specific_search:
predicates = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, search_text = search_text, exact_match = True, inclusive = inclusive, include_current = include_current, include_pending = include_pending, add_namespaceless = True, collapse_siblings = True )
predicates = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, search_text = search_text, exact_match = True, inclusive = inclusive, include_current = include_current, include_pending = include_pending, add_namespaceless = add_namespaceless, collapse_siblings = True )
else:
if must_do_a_search or self._cache_text == '' or not search_text.startswith( self._cache_text ):
cache_invalid_for_this_search = cache_text is None or self._cache_text is None or not cache_text.startswith( self._cache_text )
if cache_invalid_for_this_search:
self._cache_text = search_text
self._cache_text = cache_text
self._cached_results = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, search_text = search_text, inclusive = inclusive, include_current = include_current, include_pending = include_pending, add_namespaceless = True, collapse_siblings = True )
self._cached_results = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, search_text = search_text, inclusive = inclusive, include_current = include_current, include_pending = include_pending, add_namespaceless = add_namespaceless, collapse_siblings = True )
predicates = self._cached_results
@ -949,7 +943,7 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
current_tags_flat_iterable = itertools.chain.from_iterable( lists_of_current_tags )
current_tags_flat = ClientSearch.FilterTagsBySearchEntry( self._tag_service_key, search_text, current_tags_flat_iterable )
current_tags_flat = ClientSearch.FilterTagsBySearchText( self._tag_service_key, search_text, current_tags_flat_iterable )
current_tags_to_count.update( current_tags_flat )
@ -962,7 +956,7 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
pending_tags_flat_iterable = itertools.chain.from_iterable( lists_of_pending_tags )
pending_tags_flat = ClientSearch.FilterTagsBySearchEntry( self._tag_service_key, search_text, pending_tags_flat_iterable )
pending_tags_flat = ClientSearch.FilterTagsBySearchText( self._tag_service_key, search_text, pending_tags_flat_iterable )
pending_tags_to_count.update( pending_tags_flat )
@ -976,7 +970,7 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
predicates = siblings_manager.CollapsePredicates( self._tag_service_key, predicates )
if self._current_namespace == '':
if namespace == '':
predicates = ClientData.MergePredicates( predicates, add_namespaceless = True )
@ -984,32 +978,24 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
self._next_updatelist_is_probably_fast = True
matches = ClientSearch.FilterPredicatesBySearchEntry( self._tag_service_key, search_text, predicates )
matches = ClientSearch.FilterPredicatesBySearchText( self._tag_service_key, search_text, predicates )
matches = ClientSearch.SortPredicates( matches )
if self._include_unusual_predicate_types:
if self._current_namespace != '':
if '*' not in self._current_namespace and half_complete_subtag == '':
matches.insert( 0, ClientSearch.Predicate( HC.PREDICATE_TYPE_NAMESPACE, self._current_namespace, inclusive ) )
if half_complete_subtag != '':
if '*' in self._current_namespace or ( '*' in half_complete_subtag and half_complete_subtag != '*' ):
matches.insert( 0, ClientSearch.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive ) )
elif '*' in search_text:
if explicit_wildcard:
matches.insert( 0, ClientSearch.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive ) )
else:
if namespace != '' and half_complete_subtag in ( '', '*' ):
matches.insert( 0, ClientSearch.Predicate( HC.PREDICATE_TYPE_NAMESPACE, namespace, inclusive ) )
for match in matches:
@ -1131,7 +1117,16 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
tag = HydrusTags.CleanTag( raw_entry )
search_text = ClientSearch.ConvertTagToSearchable( tag )
search_text = ClientSearch.ConvertEntryTextToSearchText( raw_entry )
if ClientSearch.IsComplexWildcard( search_text ):
cache_text = None
else:
cache_text = search_text[:-1] # take off the trailing '*' for the cache text
entry_predicate = ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, tag )
@ -1148,12 +1143,12 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
sibling_predicate = None
return ( search_text, entry_predicate, sibling_predicate )
return ( search_text, cache_text, entry_predicate, sibling_predicate )
def _BroadcastCurrentText( self ):
( search_text, entry_predicate, sibling_predicate ) = self._ParseSearchText()
( search_text, cache_text, entry_predicate, sibling_predicate ) = self._ParseSearchText()
try:
@ -1173,12 +1168,11 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
num_autocomplete_chars = HC.options[ 'num_autocomplete_chars' ]
( search_text, entry_predicate, sibling_predicate ) = self._ParseSearchText()
( search_text, cache_text, entry_predicate, sibling_predicate ) = self._ParseSearchText()
if search_text in ( '', ':' ):
if search_text in ( '', ':', '*' ):
self._cache_text = ''
self._current_namespace = ''
self._cache_text = None
matches = []
@ -1186,31 +1180,19 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
must_do_a_search = False
( namespace, half_complete_subtag ) = HydrusTags.SplitTag( search_text )
small_and_specific_search = cache_text is not None and len( cache_text ) < num_autocomplete_chars
if namespace != '':
if half_complete_subtag != '' and namespace != self._current_namespace:
self._current_namespace = namespace # do a new search, no matter what half_complete tag is
must_do_a_search = True
else:
self._current_namespace = namespace
if len( half_complete_subtag ) < num_autocomplete_chars and '*' not in search_text:
if small_and_specific_search:
predicates = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, search_text = search_text, exact_match = True, add_namespaceless = False, collapse_siblings = False )
else:
if must_do_a_search or self._cache_text == '' or not half_complete_subtag.startswith( self._cache_text ):
cache_invalid_for_this_search = cache_text is None or self._cache_text is None or not cache_text.startswith( self._cache_text )
if must_do_a_search or cache_invalid_for_this_search:
self._cache_text = half_complete_subtag
self._cache_text = cache_text
self._cached_results = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, search_text = search_text, add_namespaceless = False, collapse_siblings = False )
@ -1220,7 +1202,7 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
self._next_updatelist_is_probably_fast = True
matches = ClientSearch.FilterPredicatesBySearchEntry( self._tag_service_key, half_complete_subtag, predicates )
matches = ClientSearch.FilterPredicatesBySearchText( self._tag_service_key, search_text, predicates )
matches = ClientSearch.SortPredicates( matches )

View File

@ -67,7 +67,7 @@ def CalculateCanvasZooms( canvas, media, show_action ):
return ( 1.0, 1.0 )
if show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON ):
if show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
return ( 1.0, 1.0 )
@ -176,7 +176,7 @@ def CalculateCanvasZooms( canvas, media, show_action ):
def CalculateMediaContainerSize( media, zoom, action ):
if action == CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW:
if action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
raise Exception( 'This media should not be shown in the media viewer!' )
@ -1165,7 +1165,7 @@ class Canvas( wx.Window ):
def _IsZoomable( self ):
return self._GetShowAction( self._current_display_media ) not in ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW )
return self._GetShowAction( self._current_display_media ) not in ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW )
def _ManageRatings( self ):
@ -1546,7 +1546,7 @@ class Canvas( wx.Window ):
media = None
elif self._GetShowAction( media.GetDisplayMedia() ) == CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW:
elif self._GetShowAction( media.GetDisplayMedia() ) in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
media = None
@ -1874,7 +1874,6 @@ class CanvasPanel( Canvas ):
elif command == 'copy_bmp': self._CopyBMPToClipboard()
elif command == 'copy_files': self._CopyFileToClipboard()
elif command == 'copy_hash': self._CopyHashToClipboard( data )
elif command == 'copy_local_url': self._CopyLocalUrlToClipboard()
elif command == 'copy_path': self._CopyPathToClipboard()
elif command == 'delete': self._Delete( data )
elif command == 'inbox': self._Inbox()
@ -2969,7 +2968,6 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
elif command == 'copy_bmp': self._CopyBMPToClipboard()
elif command == 'copy_files': self._CopyFileToClipboard()
elif command == 'copy_hash': self._CopyHashToClipboard( data )
elif command == 'copy_local_url': self._CopyLocalUrlToClipboard()
elif command == 'copy_path': self._CopyPathToClipboard()
elif command == 'delete': self._Delete( data )
elif command == 'fullscreen_switch': self.GetParent().FullscreenSwitch()
@ -3410,7 +3408,6 @@ class CanvasMediaListCustomFilter( CanvasMediaListNavigable ):
elif command == 'copy_bmp': self._CopyBMPToClipboard()
elif command == 'copy_files': self._CopyFileToClipboard()
elif command == 'copy_hash': self._CopyHashToClipboard( data )
elif command == 'copy_local_url': self._CopyLocalUrlToClipboard()
elif command == 'copy_path': self._CopyPathToClipboard()
elif command == 'delete': self._Delete( data )
elif command == 'fullscreen_switch': self.GetParent().FullscreenSwitch()
@ -3673,7 +3670,7 @@ class MediaContainer( wx.Window ):
self._embed_button.Hide()
if self._show_action == CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW:
if self._show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
raise Exception( 'This media should not be shown in the media viewer!' )

View File

@ -5,6 +5,7 @@ import ClientData
import ClientConstants as CC
import ClientGUIMenus
import ClientRatings
import ClientThreading
import itertools
import os
import random
@ -641,8 +642,8 @@ class ExportPatternButton( wx.Button ):
if id == self.ID_HASH: phrase = '{hash}'
if id == self.ID_TAGS: phrase = '{tags}'
if id == self.ID_NN_TAGS: phrase = '{nn tags}'
if id == self.ID_NAMESPACE: phrase = '[...]'
if id == self.ID_TAG: phrase = '(...)'
if id == self.ID_NAMESPACE: phrase = u'[\u2026]'
if id == self.ID_TAG: phrase = u'(\u2026)'
else: event.Skip()
if phrase is not None: HydrusGlobals.client_controller.pub( 'clipboard', 'text', phrase )
@ -662,11 +663,11 @@ class ExportPatternButton( wx.Button ):
menu.AppendSeparator()
menu.Append( self.ID_NAMESPACE, 'all instances of a particular namespace - [...]' )
menu.Append( self.ID_NAMESPACE, u'all instances of a particular namespace - [\u2026]' )
menu.AppendSeparator()
menu.Append( self.ID_TAG, 'a particular tag, if the file has it - (...)' )
menu.Append( self.ID_TAG, u'a particular tag, if the file has it - (\u2026)' )
HydrusGlobals.client_controller.PopupMenu( self, menu )
@ -1976,6 +1977,18 @@ class PopupMessageManager( wx.Frame ):
self._timer.Start( 500, wx.TIMER_CONTINUOUS )
job_key = ClientThreading.JobKey()
job_key.SetVariable( 'popup_text_1', u'initialising popup message manager\u2026' )
wx.CallAfter( self.AddMessage, job_key )
wx.CallAfter( self._Update )
wx.CallAfter( job_key.Delete )
wx.CallAfter( self._Update )
def _CheckPending( self ):
@ -2066,11 +2079,23 @@ class PopupMessageManager( wx.Frame ):
current_focus_tlp = wx.GetTopLevelParent( wx.Window.FindFocus() )
gui_is_active = current_focus_tlp in ( self, parent )
main_gui_is_active = current_focus_tlp in ( self, parent )
on_top_frame_is_active = False
if not main_gui_is_active:
c_f_tlp_is_child_frame_of_main_gui = isinstance( current_focus_tlp, wx.Frame ) and current_focus_tlp.GetParent() == parent
if c_f_tlp_is_child_frame_of_main_gui and current_focus_tlp.GetWindowStyle() & wx.FRAME_FLOAT_ON_PARENT == wx.FRAME_FLOAT_ON_PARENT:
on_top_frame_is_active = True
if new_options.GetBoolean( 'hide_message_manager_on_gui_deactive' ):
if gui_is_active:
if main_gui_is_active:
# gui can have focus even while minimised to the taskbar--let's not show in this case
if not self.IsShown() and parent.IsIconized():
@ -2118,7 +2143,7 @@ class PopupMessageManager( wx.Frame ):
# Unhiding tends to raise the main gui tlp, which is annoying if a media viewer window has focus
show_is_not_annoying = gui_is_active or self._DisplayingError()
show_is_not_annoying = main_gui_is_active or on_top_frame_is_active or self._DisplayingError()
ok_to_show = show_is_not_annoying and not going_to_bug_out_at_hide_or_show
@ -2160,6 +2185,38 @@ class PopupMessageManager( wx.Frame ):
def _Update( self ):
if HydrusGlobals.view_shutdown:
self._timer.Stop()
self.Destroy()
return
sizer_items = self._message_vbox.GetChildren()
for sizer_item in sizer_items:
message_window = sizer_item.GetWindow()
if message_window.IsDeleted():
message_window.TryToDismiss()
break
else:
message_window.Update()
self._SizeAndPositionAndShow()
def AddMessage( self, job_key ):
try:
@ -2247,34 +2304,7 @@ class PopupMessageManager( wx.Frame ):
try:
if HydrusGlobals.view_shutdown:
self._timer.Stop()
self.Destroy()
return
sizer_items = self._message_vbox.GetChildren()
for sizer_item in sizer_items:
message_window = sizer_item.GetWindow()
if message_window.IsDeleted():
message_window.TryToDismiss()
break
else:
message_window.Update()
self._SizeAndPositionAndShow()
self._Update()
except wx.PyDeadObjectError:
@ -2850,8 +2880,8 @@ class RegexButton( wx.Button ):
submenu.Append( self.ID_REGEX_BACKSPACE, r'backspace character - \\' )
submenu.Append( self.ID_REGEX_BEGINNING, r'beginning of line - ^' )
submenu.Append( self.ID_REGEX_END, r'end of line - $' )
submenu.Append( self.ID_REGEX_SET, r'any of these - [...]' )
submenu.Append( self.ID_REGEX_NOT_SET, r'anything other than these - [^...]' )
submenu.Append( self.ID_REGEX_SET, u'any of these - [\u2026]' )
submenu.Append( self.ID_REGEX_NOT_SET, u'anything other than these - [^\u2026]' )
submenu.AppendSeparator()
@ -2867,10 +2897,10 @@ class RegexButton( wx.Button ):
submenu.AppendSeparator()
submenu.Append( self.ID_REGEX_LOOKAHEAD, r'the next characters are: (non-consuming) - (?=...)' )
submenu.Append( self.ID_REGEX_NEGATIVE_LOOKAHEAD, r'the next characters are not: (non-consuming) - (?!...)' )
submenu.Append( self.ID_REGEX_LOOKBEHIND, r'the previous characters are: (non-consuming) - (?<=...)' )
submenu.Append( self.ID_REGEX_NEGATIVE_LOOKBEHIND, r'the previous characters are not: (non-consuming) - (?<!...)' )
submenu.Append( self.ID_REGEX_LOOKAHEAD, u'the next characters are: (non-consuming) - (?=\u2026)' )
submenu.Append( self.ID_REGEX_NEGATIVE_LOOKAHEAD, u'the next characters are not: (non-consuming) - (?!\u2026)' )
submenu.Append( self.ID_REGEX_LOOKBEHIND, u'the previous characters are: (non-consuming) - (?<=\u2026)' )
submenu.Append( self.ID_REGEX_NEGATIVE_LOOKBEHIND, u'the previous characters are not: (non-consuming) - (?<!\u2026)' )
submenu.AppendSeparator()
@ -2910,8 +2940,8 @@ class RegexButton( wx.Button ):
elif id == self.ID_REGEX_BACKSPACE: phrase = r'\\'
elif id == self.ID_REGEX_BEGINNING: phrase = r'^'
elif id == self.ID_REGEX_END: phrase = r'$'
elif id == self.ID_REGEX_SET: phrase = r'[...]'
elif id == self.ID_REGEX_NOT_SET: phrase = r'[^...]'
elif id == self.ID_REGEX_SET: phrase = u'[\u2026]'
elif id == self.ID_REGEX_NOT_SET: phrase = u'[^\u2026]'
elif id == self.ID_REGEX_0_OR_MORE_GREEDY: phrase = r'*'
elif id == self.ID_REGEX_1_OR_MORE_GREEDY: phrase = r'+'
elif id == self.ID_REGEX_0_OR_1_GREEDY: phrase = r'?'
@ -2921,10 +2951,10 @@ class RegexButton( wx.Button ):
elif id == self.ID_REGEX_EXACTLY_M: phrase = r'{m}'
elif id == self.ID_REGEX_M_TO_N_GREEDY: phrase = r'{m,n}'
elif id == self.ID_REGEX_M_TO_N_MINIMAL: phrase = r'{m,n}?'
elif id == self.ID_REGEX_LOOKAHEAD: phrase = r'(?=...)'
elif id == self.ID_REGEX_NEGATIVE_LOOKAHEAD: phrase = r'(?!...)'
elif id == self.ID_REGEX_LOOKBEHIND: phrase = r'(?<=...)'
elif id == self.ID_REGEX_NEGATIVE_LOOKBEHIND: phrase = r'(?<!...)'
elif id == self.ID_REGEX_LOOKAHEAD: phrase = u'(?=\u2026)'
elif id == self.ID_REGEX_NEGATIVE_LOOKAHEAD: phrase = u'(?!\u2026)'
elif id == self.ID_REGEX_LOOKBEHIND: phrase = u'(?<=\u2026)'
elif id == self.ID_REGEX_NEGATIVE_LOOKBEHIND: phrase = u'(?<!\u2026)'
elif id == self.ID_REGEX_NUMBER_WITHOUT_ZEROES: phrase = r'[1-9]+\d*'
elif id == self.ID_REGEX_FILENAME: phrase = '(?<=' + os.path.sep.encode( 'string_escape' ) + r')[^' + os.path.sep.encode( 'string_escape' ) + ']*?(?=\..*$)'
elif id == self.ID_REGEX_MANAGE_FAVOURITES:
@ -3378,161 +3408,6 @@ class SaneListCtrlForSingleObject( SaneListCtrl ):
self._objects_to_data_indices[ obj ] = data_index
class SeedCacheControl( SaneListCtrlForSingleObject ):
def __init__( self, parent, seed_cache ):
height = 300
columns = [ ( 'source', -1 ), ( 'status', 90 ), ( 'added', 150 ), ( 'last modified', 150 ), ( 'note', 200 ) ]
SaneListCtrlForSingleObject.__init__( self, parent, height, columns )
self._seed_cache = seed_cache
for seed in self._seed_cache.GetSeeds():
self._AddSeed( seed )
self.Bind( wx.EVT_MENU, self.EventMenu )
self.Bind( wx.EVT_RIGHT_DOWN, self.EventShowMenu )
HydrusGlobals.client_controller.sub( self, 'NotifySeedUpdated', 'seed_cache_seed_updated' )
def _AddSeed( self, seed ):
sort_tuple = self._seed_cache.GetSeedInfo( seed )
( display_tuple, sort_tuple ) = self._GetListCtrlTuples( seed )
self.Append( display_tuple, sort_tuple, seed )
def _GetListCtrlTuples( self, seed ):
sort_tuple = self._seed_cache.GetSeedInfo( seed )
( seed, status, added_timestamp, last_modified_timestamp, note ) = sort_tuple
pretty_seed = HydrusData.ToUnicode( seed )
pretty_status = CC.status_string_lookup[ status ]
pretty_added = HydrusData.ConvertTimestampToPrettyAgo( added_timestamp )
pretty_modified = HydrusData.ConvertTimestampToPrettyAgo( last_modified_timestamp )
pretty_note = note.split( os.linesep )[0]
display_tuple = ( pretty_seed, pretty_status, pretty_added, pretty_modified, pretty_note )
return ( display_tuple, sort_tuple )
def _CopySelectedNotes( self ):
notes = []
for seed in self.GetObjects( only_selected = True ):
( seed, status, added_timestamp, last_modified_timestamp, note ) = self._seed_cache.GetSeedInfo( seed )
if note != '':
notes.append( note )
if len( notes ) > 0:
separator = os.linesep * 2
text = separator.join( notes )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', text )
def _CopySelectedSeeds( self ):
seeds = self.GetObjects( only_selected = True )
if len( seeds ) > 0:
separator = os.linesep * 2
text = separator.join( seeds )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', text )
def _SetSelected( self, status_to_set ):
seeds_to_reset = self.GetObjects( only_selected = True )
for seed in seeds_to_reset:
self._seed_cache.UpdateSeedStatus( seed, status_to_set )
def EventMenu( self, event ):
action = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
if action is not None:
( command, data ) = action
if command == 'copy_seed_notes': self._CopySelectedNotes()
elif command == 'copy_seeds': self._CopySelectedSeeds()
elif command == 'set_seed_unknown': self._SetSelected( CC.STATUS_UNKNOWN )
elif command == 'set_seed_skipped': self._SetSelected( CC.STATUS_SKIPPED )
else: event.Skip()
def EventShowMenu( self, event ):
menu = wx.Menu()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_seeds' ), 'copy sources' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_seed_notes' ), 'copy notes' )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'set_seed_skipped' ), 'skip' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'set_seed_unknown' ), 'try again' )
HydrusGlobals.client_controller.PopupMenu( self, menu )
def NotifySeedUpdated( self, seed ):
if self._seed_cache.HasSeed( seed ):
if self.HasObject( seed ):
index = self.GetIndexFromObject( seed )
( display_tuple, sort_tuple ) = self._GetListCtrlTuples( seed )
self.UpdateRow( index, display_tuple, sort_tuple, seed )
else:
self._AddSeed( seed )
else:
if self.HasObject( seed ):
index = self.GetIndexFromObject( seed )
self.DeleteItem( index )
class Shortcut( wx.TextCtrl ):
def __init__( self, parent, modifier = wx.ACCEL_NORMAL, key = wx.WXK_F7 ):

View File

@ -1,10 +1,14 @@
import ClientCaches
import ClientConstants as CC
import ClientGUICommon
import ClientGUIDialogs
import ClientGUIScrolledPanels
import ClientGUITopLevelWindows
import HydrusConstants as HC
import HydrusData
import HydrusGlobals
import HydrusNetworking
import os
import wx
class BandwidthRulesCtrl( ClientGUICommon.StaticBox ):
@ -219,3 +223,192 @@ class BandwidthRulesCtrl( ClientGUICommon.StaticBox ):
return ( bandwidth_type, time_delta, max_allowed )
class SeedCacheControl( ClientGUICommon.SaneListCtrlForSingleObject ):
def __init__( self, parent, seed_cache ):
height = 300
columns = [ ( 'source', -1 ), ( 'status', 90 ), ( 'added', 150 ), ( 'last modified', 150 ), ( 'note', 200 ) ]
ClientGUICommon.SaneListCtrlForSingleObject.__init__( self, parent, height, columns )
self._seed_cache = seed_cache
for seed in self._seed_cache.GetSeeds():
self._AddSeed( seed )
self.Bind( wx.EVT_MENU, self.EventMenu )
self.Bind( wx.EVT_RIGHT_DOWN, self.EventShowMenu )
HydrusGlobals.client_controller.sub( self, 'NotifySeedUpdated', 'seed_cache_seed_updated' )
def _AddSeed( self, seed ):
sort_tuple = self._seed_cache.GetSeedInfo( seed )
( display_tuple, sort_tuple ) = self._GetListCtrlTuples( seed )
self.Append( display_tuple, sort_tuple, seed )
def _GetListCtrlTuples( self, seed ):
sort_tuple = self._seed_cache.GetSeedInfo( seed )
( seed, status, added_timestamp, last_modified_timestamp, note ) = sort_tuple
pretty_seed = HydrusData.ToUnicode( seed )
pretty_status = CC.status_string_lookup[ status ]
pretty_added = HydrusData.ConvertTimestampToPrettyAgo( added_timestamp )
pretty_modified = HydrusData.ConvertTimestampToPrettyAgo( last_modified_timestamp )
pretty_note = note.split( os.linesep )[0]
display_tuple = ( pretty_seed, pretty_status, pretty_added, pretty_modified, pretty_note )
return ( display_tuple, sort_tuple )
def _CopySelectedNotes( self ):
notes = []
for seed in self.GetObjects( only_selected = True ):
( seed, status, added_timestamp, last_modified_timestamp, note ) = self._seed_cache.GetSeedInfo( seed )
if note != '':
notes.append( note )
if len( notes ) > 0:
separator = os.linesep * 2
text = separator.join( notes )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', text )
def _CopySelectedSeeds( self ):
seeds = self.GetObjects( only_selected = True )
if len( seeds ) > 0:
separator = os.linesep * 2
text = separator.join( seeds )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', text )
def _DeleteSelected( self ):
seeds_to_delete = self.GetObjects( only_selected = True )
for seed in seeds_to_delete:
self._seed_cache.RemoveSeed( seed )
def _SetSelected( self, status_to_set ):
seeds_to_reset = self.GetObjects( only_selected = True )
for seed in seeds_to_reset:
self._seed_cache.UpdateSeedStatus( seed, status_to_set )
def EventMenu( self, event ):
action = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
if action is not None:
( command, data ) = action
if command == 'copy_seed_notes': self._CopySelectedNotes()
elif command == 'copy_seeds': self._CopySelectedSeeds()
elif command == 'delete_seeds':
message = 'Are you sure you want to delete all the selected entries?'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._DeleteSelected()
elif command == 'set_seed_unknown': self._SetSelected( CC.STATUS_UNKNOWN )
elif command == 'set_seed_skipped': self._SetSelected( CC.STATUS_SKIPPED )
else: event.Skip()
def _ShowMenuIfNeeded( self ):
if self.GetSelectedItemCount() > 0:
menu = wx.Menu()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_seeds' ), 'copy sources' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_seed_notes' ), 'copy notes' )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'set_seed_unknown' ), 'try again' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'set_seed_skipped' ), 'skip' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'delete_seeds' ), 'delete' )
HydrusGlobals.client_controller.PopupMenu( self, menu )
def EventShowMenu( self, event ):
wx.CallAfter( self._ShowMenuIfNeeded )
event.Skip() # let the right click event go through before doing menu, in case selection should happen
def NotifySeedUpdated( self, seed ):
if self._seed_cache.HasSeed( seed ):
if self.HasObject( seed ):
index = self.GetIndexFromObject( seed )
( display_tuple, sort_tuple ) = self._GetListCtrlTuples( seed )
self.UpdateRow( index, display_tuple, sort_tuple, seed )
else:
self._AddSeed( seed )
else:
if self.HasObject( seed ):
index = self.GetIndexFromObject( seed )
self.DeleteItem( index )

View File

@ -4318,8 +4318,6 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
removed_callable = lambda tags: 1
self._old_siblings = ClientGUIListBoxes.ListBoxTagsStringsAddRemove( self, self._service_key, show_sibling_text = False )
self._new_sibling = wx.StaticText( self )

View File

@ -16,6 +16,7 @@ import ClientGUICommon
import ClientGUIDialogs
import ClientGUIListBoxes
import ClientGUIMedia
import ClientGUIMenus
import ClientGUIScrolledPanelsEdit
import ClientGUITopLevelWindows
import ClientImporting
@ -77,6 +78,8 @@ def CreateManagementControllerDuplicateFilter():
management_controller = CreateManagementController( MANAGEMENT_TYPE_DUPLICATE_FILTER )
management_controller.SetKey( 'duplicate_filter_file_domain', CC.LOCAL_FILE_SERVICE_KEY )
return management_controller
def CreateManagementControllerImportGallery( gallery_identifier ):
@ -537,11 +540,21 @@ class ManagementController( HydrusSerialisable.SerialisableBase ):
return ( self._management_type, serialisable_keys, serialisable_simples, serialisable_serialisables )
def _InitialiseDefaults( self ):
if self._management_type == MANAGEMENT_TYPE_DUPLICATE_FILTER:
self._keys[ 'duplicate_filter_file_domain' ] = CC.LOCAL_FILE_SERVICE_KEY
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( self._management_type, serialisable_keys, serialisable_simples, serialisables ) = serialisable_info
self._keys = { name : key.decode( 'hex' ) for ( name, key ) in serialisable_keys.items() }
self._InitialiseDefaults()
self._keys.update( { name : key.decode( 'hex' ) for ( name, key ) in serialisable_keys.items() } )
if 'file_service' in self._keys:
@ -551,9 +564,9 @@ class ManagementController( HydrusSerialisable.SerialisableBase ):
self._simples = dict( serialisable_simples )
self._simples.update( dict( serialisable_simples ) )
self._serialisables = { name : HydrusSerialisable.CreateFromSerialisableTuple( value ) for ( name, value ) in serialisables.items() }
self._serialisables.update( { name : HydrusSerialisable.CreateFromSerialisableTuple( value ) for ( name, value ) in serialisables.items() } )
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
@ -627,6 +640,8 @@ class ManagementController( HydrusSerialisable.SerialisableBase ):
self._management_type = management_type
self._InitialiseDefaults()
def SetVariable( self, name, value ):
@ -715,9 +730,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
menu_items.append( ( 'normal', 'refresh', 'This panel does not update itself when files are added or deleted elsewhere in the client. Hitting this will refresh the numbers from the database.', self._RefreshAndUpdateStatus ) )
menu_items.append( ( 'normal', 'reset potential duplicates', 'This will delete all the potential duplicate pairs found so far and reset their files\' search status.', self._ResetUnknown ) )
menu_items.append( ( 'separator', 0, 0, 0 ) )
menu_items.append( ( 'check', 'regenerate file information in normal db maintenance', 'Tell the client to include file phash regeneration in its normal db maintenance cycles, whether you have that set to idle or shutdown time.', 'maintain_similar_files_phashes_during_idle' ) )
menu_items.append( ( 'check', 'rebalance tree in normal db maintenance', 'Tell the client to balance the tree in its normal db maintenance cycles, whether you have that set to idle or shutdown time. It will not occur whille there are phashes still to regenerate.', 'maintain_similar_files_tree_during_idle' ) )
menu_items.append( ( 'check', 'find duplicate pairs at the current distance in normal db maintenance', 'Tell the client to find duplicate pairs in its normal db maintenance cycles, whether you have that set to idle or shutdown time. It will not occur whille there are phashes still to regenerate or if the tree still needs rebalancing.', 'maintain_similar_files_duplicate_pairs_during_idle' ) )
menu_items.append( ( 'check', 'search for duplicate pairs at the current distance during normal db maintenance', 'Tell the client to find duplicate pairs in its normal db maintenance cycles, whether you have that set to idle or shutdown time.', 'maintain_similar_files_duplicate_pairs_during_idle' ) )
self._cog_button = ClientGUICommon.MenuBitmapButton( self, CC.GlobalBMPs.cog, menu_items )
@ -755,6 +768,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
self._filtering_panel = ClientGUICommon.StaticBox( self, 'filtering' )
self._file_domain_button = ClientGUICommon.BetterButton( self._filtering_panel, 'file domain', self._FileDomainButtonHit )
self._num_unknown_duplicates = wx.StaticText( self._filtering_panel )
self._num_same_file_duplicates = wx.StaticText( self._filtering_panel )
self._num_alternate_duplicates = wx.StaticText( self._filtering_panel )
@ -766,6 +780,10 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
self._search_distance_spinctrl.SetValue( new_options.GetInteger( 'similar_files_duplicate_pairs_search_distance' ) )
duplicate_filter_file_domain = management_controller.GetKey( 'duplicate_filter_file_domain' )
self._SetFileDomain( duplicate_filter_file_domain ) # this spawns a refreshandupdatestatus
#
gridbox_1 = wx.FlexGridSizer( 0, 3 )
@ -801,6 +819,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
#
self._filtering_panel.AddF( self._file_domain_button, CC.FLAGS_EXPAND_PERPENDICULAR )
self._filtering_panel.AddF( self._num_unknown_duplicates, CC.FLAGS_EXPAND_PERPENDICULAR )
self._filtering_panel.AddF( self._num_same_file_duplicates, CC.FLAGS_EXPAND_PERPENDICULAR )
self._filtering_panel.AddF( self._num_alternate_duplicates, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -820,9 +839,27 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
self.Bind( wx.EVT_TIMER, self.TIMEREventUpdateDBJob, id = ID_TIMER_UPDATE )
self._update_db_job_timer = wx.Timer( self, id = ID_TIMER_UPDATE )
#
def _FileDomainButtonHit( self ):
self._RefreshAndUpdateStatus()
services_manager = HydrusGlobals.client_controller.GetServicesManager()
services = []
services.append( services_manager.GetService( CC.LOCAL_FILE_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.TRASH_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
menu = wx.Menu()
for service in services:
call = HydrusData.Call( self._SetFileDomain, service.GetServiceKey() )
ClientGUIMenus.AppendMenuItem( self, menu, service.GetName(), 'Set the filtering file domain.', call )
HydrusGlobals.client_controller.PopupMenu( self._file_domain_button, menu )
def _RebalanceTree( self ):
@ -862,6 +899,19 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
self._StartStopDBJob()
def _SetFileDomain( self, service_key ):
self._management_controller.SetKey( 'duplicate_filter_file_domain', service_key )
services_manager = HydrusGlobals.client_controller.GetServicesManager()
service = services_manager.GetService( service_key )
self._file_domain_button.SetLabelText( service.GetName() )
self._RefreshAndUpdateStatus()
def _SetSearchDistance( self, value ):
self._search_distance_spinctrl.SetValue( value )
@ -871,7 +921,9 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
def _ShowSomeDupes( self ):
hashes = self._controller.Read( 'some_dupes' )
duplicate_filter_file_domain = self._management_controller.GetKey( 'duplicate_filter_file_domain' )
hashes = self._controller.Read( 'some_dupes', duplicate_filter_file_domain )
media_results = self._controller.Read( 'media_results', hashes )
@ -926,6 +978,15 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
def _RefreshAndUpdateStatus( self ):
duplicate_filter_file_domain = self._management_controller.GetKey( 'duplicate_filter_file_domain' )
self._similar_files_maintenance_status = self._controller.Read( 'similar_files_maintenance_status', duplicate_filter_file_domain )
self._UpdateStatus()
def _UpdateJob( self ):
if self._job_key.TimeRunning() > 30:
@ -982,16 +1043,9 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
def _RefreshAndUpdateStatus( self ):
self._similar_files_maintenance_status = self._controller.Read( 'similar_files_maintenance_status' )
self._UpdateStatus()
def _UpdateStatus( self ):
( searched_distances_to_count, duplicate_types_to_count, num_phashes_to_regen, num_branches_to_regen ) = self._similar_files_maintenance_status
( num_phashes_to_regen, num_branches_to_regen, searched_distances_to_count, duplicate_types_to_count ) = self._similar_files_maintenance_status
self._cog_button.Enable()

View File

@ -489,7 +489,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
media_show_action = new_options.GetMediaShowAction( display_media.GetMime() )
if media_show_action == CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW:
if media_show_action == CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY:
hash = display_media.GetHash()
mime = display_media.GetMime()
@ -502,6 +502,10 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
return
elif media_show_action == CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW:
return
media_results = self.GenerateMediaResults( discriminant = CC.DISCRIMINANT_LOCAL, for_media_viewer = True )
@ -2129,7 +2133,6 @@ class MediaPanelThumbnails( MediaPanel ):
elif command == 'copy_hash': self._CopyHashToClipboard( data )
elif command == 'copy_hashes': self._CopyHashesToClipboard( data )
elif command == 'copy_known_urls': self._CopyKnownURLsToClipboard()
elif command == 'copy_local_url': self._CopyLocalUrlToClipboard()
elif command == 'copy_hashes': self._CopyHashesToClipboard( data )
elif command == 'copy_service_filename': self._CopyServiceFilenameToClipboard( data )
elif command == 'copy_service_filenames': self._CopyServiceFilenamesToClipboard( data )

View File

@ -65,9 +65,9 @@ def AppendMenuLabel( menu, label, description = None ):
def BindMenuItem( event_handler, menu, menu_item, callable, *args, **kwargs ):
l_callable = GetLambdaCallable( callable, *args, **kwargs )
event_callable = GetEventCallable( callable, *args, **kwargs )
event_handler.Bind( wx.EVT_MENU, l_callable, source = menu_item )
event_handler.Bind( wx.EVT_MENU, event_callable, source = menu_item )
menus_to_menu_item_data[ menu ].add( ( menu_item, event_handler ) )
@ -101,11 +101,14 @@ def DestroyMenu( menu ):
menu.Destroy()
def GetLambdaCallable( callable, *args, **kwargs ):
def GetEventCallable( callable, *args, **kwargs ):
l_callable = lambda event: callable( *args, **kwargs )
def event_callable( event ):
callable( *args, **kwargs )
return l_callable
return event_callable
def SanitiseLabel( label ):

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,12 @@
import ClientConstants as CC
import ClientGUICommon
import ClientGUIDialogs
import ClientThreading
import HydrusConstants as HC
import HydrusData
import HydrusExceptions
import HydrusGlobals
import HydrusNATPunch
import HydrusNetwork
import HydrusPaths
import os
@ -87,36 +89,7 @@ class ReviewServicePanel( wx.Panel ):
service_info = self._controller.Read( 'service_info', self._service_key )
if service_type in HC.FILE_SERVICES:
num_files = service_info[ HC.SERVICE_INFO_NUM_FILES ]
total_size = service_info[ HC.SERVICE_INFO_TOTAL_SIZE ]
self._files_text.SetLabelText( HydrusData.ConvertIntToPrettyString( num_files ) + ' files, totalling ' + HydrusData.ConvertIntToBytes( total_size ) )
if service_type in ( HC.COMBINED_LOCAL_FILE, HC.FILE_REPOSITORY ):
num_deleted_files = service_info[ HC.SERVICE_INFO_NUM_DELETED_FILES ]
self._deleted_files_text.SetLabelText( HydrusData.ConvertIntToPrettyString( num_deleted_files ) + ' deleted files' )
elif service_type in HC.TAG_SERVICES:
num_files = service_info[ HC.SERVICE_INFO_NUM_FILES ]
num_tags = service_info[ HC.SERVICE_INFO_NUM_TAGS ]
num_mappings = service_info[ HC.SERVICE_INFO_NUM_MAPPINGS ]
self._tags_text.SetLabelText( HydrusData.ConvertIntToPrettyString( num_files ) + ' hashes, ' + HydrusData.ConvertIntToPrettyString( num_tags ) + ' tags, totalling ' + HydrusData.ConvertIntToPrettyString( num_mappings ) + ' mappings' )
if service_type == HC.TAG_REPOSITORY:
num_deleted_mappings = service_info[ HC.SERVICE_INFO_NUM_DELETED_MAPPINGS ]
self._deleted_tags_text.SetLabelText( HydrusData.ConvertIntToPrettyString( num_deleted_mappings ) + ' deleted mappings' )
elif service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
if service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
num_ratings = service_info[ HC.SERVICE_INFO_NUM_FILES ]
@ -510,7 +483,7 @@ class ReviewServicePanel( wx.Panel ):
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
self._name_and_type = wx.StaticText( self )
self._file_info_st = wx.StaticText( self )
#
@ -518,16 +491,14 @@ class ReviewServicePanel( wx.Panel ):
#
self.AddF( self._name_and_type, CC.FLAGS_EXPAND_PERPENDICULAR )
self.AddF( self._file_info_st, CC.FLAGS_EXPAND_PERPENDICULAR )
HydrusGlobals.client_controller.sub( self, 'Update', 'service_updated' )
def _Refresh( self ):
# put this fetch on a thread, since it'll have to go to the db
self._name_and_type.SetLabelText( 'This service has files. This box will regain its old information in a later version.' )
HydrusGlobals.client_controller.CallToThread( self.THREADUpdateTagInfo )
def Update( self, service ):
@ -540,6 +511,25 @@ class ReviewServicePanel( wx.Panel ):
def THREADUpdateTagInfo( self ):
service_info = HydrusGlobals.client_controller.Read( 'service_info', self._service.GetServiceKey() )
num_files = service_info[ HC.SERVICE_INFO_NUM_FILES ]
total_size = service_info[ HC.SERVICE_INFO_TOTAL_SIZE ]
text = HydrusData.ConvertIntToPrettyString( num_files ) + ' files, totalling ' + HydrusData.ConvertIntToBytes( total_size )
if self._service.GetServiceType() in ( HC.COMBINED_LOCAL_FILE, HC.FILE_REPOSITORY ):
num_deleted_files = service_info[ HC.SERVICE_INFO_NUM_DELETED_FILES ]
text += ' - ' + HydrusData.ConvertIntToPrettyString( num_deleted_files ) + ' deleted files'
wx.CallAfter( self._file_info_st.SetLabelText, text )
class _ServiceRemotePanel( ClientGUICommon.StaticBox ):
@ -777,7 +767,7 @@ class ReviewServicePanel( wx.Panel ):
self._refresh_account_button.Disable()
self._refresh_account_button.SetLabelText( 'fetching...' )
self._refresh_account_button.SetLabelText( u'fetching\u2026' )
HydrusGlobals.client_controller.CallToThread( do_it )
@ -805,10 +795,12 @@ class ReviewServicePanel( wx.Panel ):
self._content_panel = wx.Panel( self )
self._metadata_st = wx.StaticText( self )
self._download_progress = ClientGUICommon.TextAndGauge( self )
self._processing_progress = ClientGUICommon.TextAndGauge( self )
self._sync_now_button = ClientGUICommon.BetterButton( self, 'sync now', self._SyncNow )
self._sync_now_button = ClientGUICommon.BetterButton( self, 'process now', self._SyncNow )
self._pause_play_button = ClientGUICommon.BetterButton( self, 'pause', self._PausePlay )
self._export_updates_button = ClientGUICommon.BetterButton( self, 'export updates', self._ExportUpdates )
@ -824,6 +816,7 @@ class ReviewServicePanel( wx.Panel ):
hbox.AddF( self._pause_play_button, CC.FLAGS_LONE_BUTTON )
hbox.AddF( self._export_updates_button, CC.FLAGS_LONE_BUTTON )
self.AddF( self._metadata_st, CC.FLAGS_EXPAND_PERPENDICULAR )
self.AddF( self._download_progress, CC.FLAGS_EXPAND_PERPENDICULAR )
self.AddF( self._processing_progress, CC.FLAGS_EXPAND_PERPENDICULAR )
self.AddF( hbox, CC.FLAGS_BUTTON_SIZER )
@ -909,7 +902,7 @@ class ReviewServicePanel( wx.Panel ):
path = HydrusData.ToUnicode( dlg.GetPath() )
self._export_updates_button.SetLabelText( 'exporting...' )
self._export_updates_button.SetLabelText( u'exporting\u2026' )
self._export_updates_button.Disable()
HydrusGlobals.client_controller.CallToThread( do_it, path )
@ -948,6 +941,8 @@ class ReviewServicePanel( wx.Panel ):
self._pause_play_button.SetLabelText( 'pause' )
self._metadata_st.SetLabelText( self._service.GetNextUpdateDueString() )
HydrusGlobals.client_controller.CallToThread( self.THREADFetchUpdateProgress )
@ -1118,7 +1113,7 @@ class ReviewServicePanel( wx.Panel ):
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
self._name_and_type = wx.StaticText( self )
self._tag_info_st = wx.StaticText( self )
#
@ -1126,16 +1121,14 @@ class ReviewServicePanel( wx.Panel ):
#
self.AddF( self._name_and_type, CC.FLAGS_EXPAND_PERPENDICULAR )
self.AddF( self._tag_info_st, CC.FLAGS_EXPAND_PERPENDICULAR )
HydrusGlobals.client_controller.sub( self, 'Update', 'service_updated' )
def _Refresh( self ):
# put this fetch on a thread, since it'll have to go to the db
self._name_and_type.SetLabelText( 'This service has tags. This box will regain its old information in a later version.' )
HydrusGlobals.client_controller.CallToThread( self.THREADUpdateTagInfo )
def Update( self, service ):
@ -1148,4 +1141,24 @@ class ReviewServicePanel( wx.Panel ):
def THREADUpdateTagInfo( self ):
service_info = HydrusGlobals.client_controller.Read( 'service_info', self._service.GetServiceKey() )
num_files = service_info[ HC.SERVICE_INFO_NUM_FILES ]
num_tags = service_info[ HC.SERVICE_INFO_NUM_TAGS ]
num_mappings = service_info[ HC.SERVICE_INFO_NUM_MAPPINGS ]
text = HydrusData.ConvertIntToPrettyString( num_mappings ) + ' total mappings involving ' + HydrusData.ConvertIntToPrettyString( num_tags ) + ' different tags on ' + HydrusData.ConvertIntToPrettyString( num_files ) + ' different files'
if self._service.GetServiceType() == HC.TAG_REPOSITORY:
num_deleted_mappings = service_info[ HC.SERVICE_INFO_NUM_DELETED_MAPPINGS ]
text += ' - ' + HydrusData.ConvertIntToPrettyString( num_deleted_mappings ) + ' deleted mappings'
wx.CallAfter( self._tag_info_st.SetLabelText, text )

View File

@ -194,7 +194,10 @@ class PanelPredicateSystemHash( PanelPredicateSystem ):
def GetInfo( self ):
hex_filter = lambda c: c in string.hexdigits
def hex_filter( c ):
return c in string.hexdigits
hash = filter( hex_filter, self._hash.GetValue().lower() )
@ -685,7 +688,10 @@ class PanelPredicateSystemSimilarTo( PanelPredicateSystem ):
def GetInfo( self ):
hex_filter = lambda c: c in string.hexdigits
def hex_filter( c ):
return c in string.hexdigits
hash = filter( hex_filter, self._hash.GetValue().lower() )

View File

@ -246,7 +246,7 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
self._media_show_action.Append( CC.media_viewer_action_string_lookup[ action ], action )
if action != CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW:
if action != CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY:
self._preview_show_action.Append( CC.media_viewer_action_string_lookup[ action ], action )
@ -420,7 +420,7 @@ class EditSeedCachePanel( ClientGUIScrolledPanels.EditPanel ):
self._seed_cache = seed_cache
self._text = wx.StaticText( self, label = 'initialising' )
self._seed_cache_control = ClientGUICommon.SeedCacheControl( self, self._seed_cache )
self._seed_cache_control = ClientGUIControls.SeedCacheControl( self, self._seed_cache )
vbox = wx.BoxSizer( wx.VERTICAL )

View File

@ -26,6 +26,7 @@ import HydrusNetwork
import HydrusNetworking
import HydrusPaths
import HydrusSerialisable
import HydrusTagArchive
import HydrusTags
import itertools
import os
@ -233,7 +234,7 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
ClientGUIScrolledPanels.ManagePanel.__init__( self, parent )
self._listctrl = ClientGUICommon.SaneListCtrlForSingleObject( self, 400, [ ( 'name', -1 ), ( 'type', 220 ), ( 'deletable', 120 ) ], delete_key_callback = self._Delete, activation_callback = self._Edit )
self._listctrl = ClientGUICommon.SaneListCtrlForSingleObject( self, 400, [ ( 'type', 220 ), ( 'name', -1 ), ( 'deletable', 120 ) ], delete_key_callback = self._Delete, activation_callback = self._Edit )
menu_items = []
@ -279,8 +280,8 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
def _ConvertServiceToTuples( self, service ):
name = service.GetName()
service_type = service.GetServiceType()
name = service.GetName()
deletable = service_type in HC.ADDREMOVABLE_SERVICES
pretty_service_type = HC.service_string_lookup[ service_type ]
@ -294,7 +295,7 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
pretty_deletable = ''
return ( ( name, pretty_service_type, pretty_deletable ), ( name, pretty_service_type, deletable ) )
return ( ( pretty_service_type, name, pretty_deletable ), ( pretty_service_type, name, deletable ) )
def _Add( self, service_type ):
@ -562,53 +563,6 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
def EventCheckService( self, event ):
service = self.GetValue()
try:
root = service.Request( HC.GET, '' )
except HydrusExceptions.WrongServiceTypeException:
wx.MessageBox( 'Connection was made, but the service was not a ' + HC.service_string_lookup[ self._service_type ] + '.' )
return
except Exception as e:
HydrusData.ShowException( e )
wx.MessageBox( 'Could not connect!' )
return
if service_type in HC.RESTRICTED_SERVICES:
credentials = service.GetCredentials()
if not credentials.HasAccessKey():
wx.MessageBox( 'No access key!' )
return
response = service.Request( HC.GET, 'access_key_verification' )
if not response[ 'verified' ]:
wx.MessageBox( 'That access key was not recognised!' )
return
wx.MessageBox( 'Everything looks ok!' )
def GetValue( self ):
name = self._service_panel.GetValue()
@ -895,7 +849,7 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
service.SetCredentials( credentials )
self._register.Disable()
self._register.SetLabel( 'fetching...' )
self._register.SetLabel( u'fetching\u2026' )
HydrusGlobals.client_controller.CallToThread( do_it, service, registration_key )
@ -980,9 +934,7 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
account = HydrusNetwork.Account.GenerateUnknownAccount()
dictionary_part[ 'account' ] = account.ToSerialisableTuple()
HydrusGlobals.client_controller.pub( 'permissions_are_stale' )
dictionary_part[ 'account' ] = HydrusNetwork.Account.GenerateSerialisableTupleFromAccount( account )
session_manager = HydrusGlobals.client_controller.GetClientSessionManager()
@ -2608,7 +2560,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._media_zooms.SetValue( ','.join( ( str( media_zoom ) for media_zoom in media_zooms ) ) )
mimes_in_correct_order = ( HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_GIF, HC.APPLICATION_FLASH, HC.APPLICATION_PDF, HC.VIDEO_AVI, HC.VIDEO_FLV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_MPEG, HC.VIDEO_WEBM, HC.VIDEO_WMV, HC.AUDIO_MP3, HC.AUDIO_OGG, HC.AUDIO_FLAC, HC.AUDIO_WMA )
mimes_in_correct_order = ( HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_GIF, HC.APPLICATION_FLASH, HC.APPLICATION_PDF, HC.APPLICATION_HYDRUS_UPDATE_CONTENT, HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS, HC.VIDEO_AVI, HC.VIDEO_FLV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_MPEG, HC.VIDEO_WEBM, HC.VIDEO_WMV, HC.AUDIO_MP3, HC.AUDIO_OGG, HC.AUDIO_FLAC, HC.AUDIO_WMA )
for mime in mimes_in_correct_order:
@ -2658,8 +2610,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
pretty_media_show_action = CC.media_viewer_action_string_lookup[ media_show_action ]
pretty_preview_show_action = CC.media_viewer_action_string_lookup[ preview_show_action ]
no_show_actions = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON )
no_show = media_show_action in CC.no_support and preview_show_action in CC.no_support
if no_show:
@ -3761,6 +3711,8 @@ class ManageServerServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
service = self._services_listctrl.GetObject( index )
original_name = service.GetName()
with ClientGUITopLevelWindows.DialogEdit( self, 'edit serverside service' ) as dlg_edit:
panel = ClientGUIScrolledPanelsEdit.EditServersideService( dlg_edit, service )
@ -3773,7 +3725,10 @@ class ManageServerServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
edited_service = panel.GetValue()
self._services_listctrl.SetNonDupeName( edited_service )
if edited_service.GetName() != original_name:
self._services_listctrl.SetNonDupeName( edited_service )
self._SetNonDupePort( edited_service )
@ -3807,7 +3762,7 @@ class ManageServerServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
def _SetNonDupePort( self, new_service ):
existing_ports = [ service.GetPort() for service in self._services_listctrl.GetObjects() ]
existing_ports = [ service.GetPort() for service in self._services_listctrl.GetObjects() if service.GetServiceKey() != new_service.GetServiceKey() ]
new_port = new_service.GetPort()

View File

@ -168,9 +168,12 @@ def GenerateShapePerceptualHashes( path ):
dct_88_boolean = dct_88 > median
# convert TTTFTFTF to 11101010 by repeatedly shifting answer and adding 0 or 1
# you can even go ( a << 1 ) + b and leave out the initial param on the latel reduce call as bools act like ints for this
# you can even go ( a << 1 ) + b and leave out the initial param on the reduce call as bools act like ints for this
# but let's not go crazy for another two nanoseconds
collapse_bools_to_binary_uint = lambda a, b: ( a << 1 ) + int( b )
def collapse_bools_to_binary_uint( a, b ):
return ( a << 1 ) + int( b )
bytes = []

View File

@ -54,7 +54,7 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
self._pending_queries = []
self._get_tags_if_redundant = True
self._get_tags_if_redundant = False
self._file_limit = HC.options[ 'gallery_file_limit' ]
self._gallery_paused = False
self._files_paused = False

View File

@ -438,11 +438,17 @@ class MediaList( object ):
if sort_by_data == CC.SORT_BY_RANDOM:
sort_function = lambda x: random.random()
def sort_key( x ):
return random.random()
elif sort_by_data in ( CC.SORT_BY_SMALLEST, CC.SORT_BY_LARGEST ):
sort_function = lambda x: deal_with_none( x.GetSize() )
def sort_key( x ):
return deal_with_none( x.GetSize() )
if sort_by_data == CC.SORT_BY_LARGEST:
@ -451,7 +457,10 @@ class MediaList( object ):
elif sort_by_data in ( CC.SORT_BY_SHORTEST, CC.SORT_BY_LONGEST ):
sort_function = lambda x: deal_with_none( x.GetDuration() )
def sort_key( x ):
return deal_with_none( x.GetDuration() )
if sort_by_data == CC.SORT_BY_LONGEST:
@ -473,7 +482,10 @@ class MediaList( object ):
file_service_key = self._file_service_key
sort_function = lambda x: deal_with_none( x.GetTimestamp( file_service_key ) )
def sort_key( x ):
return deal_with_none( x.GetTimestamp( file_service_key ) )
if sort_by_data == CC.SORT_BY_NEWEST:
@ -482,7 +494,10 @@ class MediaList( object ):
elif sort_by_data in ( CC.SORT_BY_HEIGHT_ASC, CC.SORT_BY_HEIGHT_DESC ):
sort_function = lambda x: deal_with_none( x.GetResolution()[0] )
def sort_key( x ):
return deal_with_none( x.GetResolution()[1] )
if sort_by_data == CC.SORT_BY_HEIGHT_DESC:
@ -491,7 +506,10 @@ class MediaList( object ):
elif sort_by_data in ( CC.SORT_BY_WIDTH_ASC, CC.SORT_BY_WIDTH_DESC ):
sort_function = lambda x: deal_with_none( x.GetResolution()[1] )
def sort_key( x ):
return deal_with_none( x.GetResolution()[0] )
if sort_by_data == CC.SORT_BY_WIDTH_DESC:
@ -500,7 +518,7 @@ class MediaList( object ):
elif sort_by_data in ( CC.SORT_BY_RATIO_ASC, CC.SORT_BY_RATIO_DESC ):
def sort_function( x ):
def sort_key( x ):
( width, height ) = x.GetResolution()
@ -521,7 +539,7 @@ class MediaList( object ):
elif sort_by_data in ( CC.SORT_BY_NUM_PIXELS_ASC, CC.SORT_BY_NUM_PIXELS_DESC ):
def sort_function( x ):
def sort_key( x ):
( width, height ) = x.GetResolution()
@ -542,25 +560,28 @@ class MediaList( object ):
elif sort_by_data == CC.SORT_BY_MIME:
sort_function = lambda x: x.GetMime()
def sort_key( x ):
return x.GetMime()
elif sort_by_type == 'namespaces':
def namespace_sort_function( namespaces, x ):
namespaces = sort_by_data
def sort_key( x ):
x_tags_manager = x.GetTagsManager()
return [ x_tags_manager.GetComparableNamespaceSlice( ( namespace, ) ) for namespace in namespaces ]
sort_function = lambda x: namespace_sort_function( sort_by_data, x )
elif sort_by_type in ( 'rating_descend', 'rating_ascend' ):
service_key = sort_by_data
def ratings_sort_function( service_key, x ):
def sort_key( x ):
x_ratings_manager = x.GetRatingsManager()
@ -569,15 +590,13 @@ class MediaList( object ):
return rating
sort_function = lambda x: ratings_sort_function( service_key, x )
if sort_by_type == 'rating_descend':
reverse = True
return ( sort_function, reverse )
return ( sort_key, reverse )
def _RecalcHashes( self ):
@ -824,7 +843,7 @@ class MediaList( object ):
media_show_action = new_options.GetMediaShowAction( media.GetMime() )
if media_show_action == CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW:
if media_show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
continue
@ -979,15 +998,15 @@ class MediaList( object ):
sort_by_fallback = sort_choices[ 0 ]
( sort_function, reverse ) = self._GetSortFunction( sort_by_fallback )
( sort_key, reverse ) = self._GetSortFunction( sort_by_fallback )
self._sorted_media.sort( sort_function, reverse = reverse )
self._sorted_media.sort( sort_key, reverse = reverse )
# this is a stable sort, so the fallback order above will remain for equal items
( sort_function, reverse ) = self._GetSortFunction( self._sort_by )
( sort_key, reverse ) = self._GetSortFunction( self._sort_by )
self._sorted_media.sort( sort_function = sort_function, reverse = reverse )
self._sorted_media.sort( sort_key = sort_key, reverse = reverse )
class ListeningMediaList( MediaList ):
@ -1682,9 +1701,12 @@ class SortedList( object ):
def __init__( self, initial_items = None ):
if initial_items is None: initial_items = []
if initial_items is None:
initial_items = []
self._sort_function = lambda x: x
self._sort_key = None
self._sort_reverse = False
self._sorted_list = list( initial_items )
@ -1779,20 +1801,20 @@ class SortedList( object ):
self._DirtyIndices()
def sort( self, sort_function = None, reverse = False ):
def sort( self, sort_key = None, reverse = False ):
if sort_function is None:
if sort_key is None:
sort_function = self._sort_function
sort_key = self._sort_key
reverse = self._sort_reverse
else:
self._sort_function = sort_function
self._sort_key = sort_key
self._sort_reverse = reverse
self._sorted_list.sort( key = sort_function, reverse = reverse )
self._sorted_list.sort( key = sort_key, reverse = reverse )
self._DirtyIndices()

View File

@ -13,9 +13,9 @@ IGNORED_TAG_SEARCH_CHARACTERS_UNICODE_TRANSLATE = { ord( char ) : None for char
def ConvertTagToSearchable( tag ):
while tag.endswith( '*' ):
if tag == '':
tag = tag[:-1]
return ''
if not isinstance( tag, unicode ):
@ -23,9 +23,29 @@ def ConvertTagToSearchable( tag ):
tag = HydrusData.ToUnicode( tag )
return tag.translate( IGNORED_TAG_SEARCH_CHARACTERS_UNICODE_TRANSLATE )
tag = tag.translate( IGNORED_TAG_SEARCH_CHARACTERS_UNICODE_TRANSLATE )
def FilterPredicatesBySearchEntry( service_key, search_entry, predicates ):
while '**' in tag:
tag = tag.replace( '**', '*' )
return tag
def ConvertEntryTextToSearchText( entry_text ):
entry_text = HydrusTags.CleanTag( entry_text )
entry_text = ConvertTagToSearchable( entry_text )
if not IsComplexWildcard( entry_text ) and not entry_text.endswith( '*' ):
entry_text = entry_text + u'*'
return entry_text
def FilterPredicatesBySearchText( service_key, search_text, predicates ):
tags_to_predicates = {}
@ -39,59 +59,48 @@ def FilterPredicatesBySearchEntry( service_key, search_entry, predicates ):
matching_tags = FilterTagsBySearchEntry( service_key, search_entry, tags_to_predicates.keys() )
matching_tags = FilterTagsBySearchText( service_key, search_text, tags_to_predicates.keys() )
matches = [ tags_to_predicates[ tag ] for tag in matching_tags ]
return matches
def FilterTagsBySearchEntry( service_key, search_entry, tags, search_siblings = True ):
def FilterTagsBySearchText( service_key, search_text, tags, search_siblings = True ):
def compile_re( s ):
num_stars = s.count( '*' )
is_wildcard_search = ( num_stars == 1 and not s.endswith( '*' ) ) or num_stars > 1
regular_parts_of_s = s.split( '*' )
escaped_parts_of_s = [ re.escape( part ) for part in regular_parts_of_s ]
escaped_parts_of_s = map( re.escape, regular_parts_of_s )
s = '.*'.join( escaped_parts_of_s )
if is_wildcard_search:
# \A is start of string
# \Z is end of string
# \s is whitespace
if s.startswith( '.*' ):
return re.compile( s, flags = re.UNICODE )
beginning = '(\\A|:)'
else:
return re.compile( '(\\A|\\s)' + s + '(\\s|\\Z)', flags = re.UNICODE )
beginning = '(\\A|:|\\s)'
search_entry = ConvertTagToSearchable( search_entry )
( namespace, half_complete_subtag ) = HydrusTags.SplitTag( search_entry )
if namespace != '':
if s.endswith( '.*' ):
end = '\\Z' # end of string
else:
end = '(\\s|\\Z)' # whitespace or end of string
search_namespace = True
namespace_re_predicate = compile_re( ConvertTagToSearchable( namespace ) )
else:
search_namespace = False
namespace_re_predicate = None
return re.compile( beginning + s + end, flags = re.UNICODE )
if '*' not in half_complete_subtag:
half_complete_subtag += '*'
half_complete_subtag_re_predicate = compile_re( half_complete_subtag )
re_predicate = compile_re( search_text )
sibling_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
@ -108,30 +117,11 @@ def FilterTagsBySearchEntry( service_key, search_entry, tags, search_siblings =
possible_tags = [ tag ]
possible_tags = map( ConvertTagToSearchable, possible_tags )
for possible_tag in possible_tags:
( possible_namespace, possible_subtag ) = HydrusTags.SplitTag( possible_tag )
if possible_namespace != '':
possible_namespace = ConvertTagToSearchable( possible_namespace )
if search_namespace and re.search( namespace_re_predicate, possible_namespace ) is None:
continue
else:
if search_namespace:
continue
possible_subtag = ConvertTagToSearchable( possible_subtag )
if re.search( half_complete_subtag_re_predicate, possible_subtag ) is not None:
if re.search( re_predicate, possible_tag ) is not None:
result.append( tag )
@ -142,6 +132,22 @@ def FilterTagsBySearchEntry( service_key, search_entry, tags, search_siblings =
return result
def IsComplexWildcard( search_text ):
num_stars = search_text.count( '*' )
if num_stars > 1:
return True
if num_stars == 1 and not search_text.endswith( '*' ):
return True
return False
def SortPredicates( predicates ):
def cmp_func( x, y ): return cmp( x.GetCount(), y.GetCount() )

View File

@ -33,7 +33,7 @@ def GenerateDefaultServiceDictionary( service_type ):
if service_type in HC.RESTRICTED_SERVICES:
dictionary[ 'account' ] = HydrusNetwork.Account.GenerateUnknownAccount().ToSerialisableTuple()
dictionary[ 'account' ] = HydrusNetwork.Account.GenerateSerialisableTupleFromAccount( HydrusNetwork.Account.GenerateUnknownAccount() )
dictionary[ 'next_account_sync' ] = 0
if service_type in HC.REPOSITORIES:
@ -596,7 +596,7 @@ class ServiceRestricted( ServiceRemote ):
dictionary = ServiceRemote._GetSerialisableDictionary( self )
dictionary[ 'account' ] = self._account.ToSerialisableTuple()
dictionary[ 'account' ] = HydrusNetwork.Account.GenerateSerialisableTupleFromAccount( self._account )
dictionary[ 'next_account_sync' ] = self._next_account_sync
return dictionary
@ -940,6 +940,14 @@ class ServiceRepository( ServiceRestricted ):
return processing_value < range
def GetNextUpdateDueString( self ):
with self._lock:
return self._metadata.GetNextUpdateDueString( from_client = True )
def GetTagArchiveSync( self ):
with self._lock:

View File

@ -49,7 +49,7 @@ options = {}
# Misc
NETWORK_VERSION = 18
SOFTWARE_VERSION = 245
SOFTWARE_VERSION = 246
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -261,9 +261,9 @@ service_string_lookup = {}
service_string_lookup[ TAG_REPOSITORY ] = 'hydrus tag repository'
service_string_lookup[ FILE_REPOSITORY ] = 'hydrus file repository'
service_string_lookup[ LOCAL_FILE_DOMAIN ] = 'hydrus local file domain'
service_string_lookup[ LOCAL_FILE_TRASH_DOMAIN ] = 'hydrus trash domain'
service_string_lookup[ COMBINED_LOCAL_FILE ] = 'hydrus local file service'
service_string_lookup[ LOCAL_FILE_DOMAIN ] = 'local file domain'
service_string_lookup[ LOCAL_FILE_TRASH_DOMAIN ] = 'local trash file domain'
service_string_lookup[ COMBINED_LOCAL_FILE ] = 'virtual combined local file service'
service_string_lookup[ MESSAGE_DEPOT ] = 'hydrus message depot'
service_string_lookup[ LOCAL_TAG ] = 'local tag service'
service_string_lookup[ LOCAL_RATING_NUMERICAL ] = 'local numerical rating service'
@ -334,6 +334,7 @@ SERVICE_INFO_NUM_PETITIONED_TAG_SIBLINGS = 18
SERVICE_INFO_NUM_PENDING_TAG_PARENTS = 19
SERVICE_INFO_NUM_PETITIONED_TAG_PARENTS = 20
SERVICE_INFO_NUM_SHARES = 21
SERVICE_INFO_NUM_VIEWABLE_FILES = 22
SERVICE_UPDATE_DELETE_PENDING = 0
SERVICE_UPDATE_RESET = 1

View File

@ -23,6 +23,8 @@ class HydrusController( object ):
HydrusGlobals.controller = self
self._name = 'hydrus'
self._db_dir = db_dir
self._no_daemons = no_daemons
self._no_wal = no_wal
@ -249,6 +251,24 @@ class HydrusController( object ):
raise NotImplementedError()
def PrintProfile( self, summary, profile_text ):
boot_pretty_timestamp = time.strftime( '%Y-%m-%d %H-%M-%S', time.localtime( self._timestamps[ 'boot' ] ) )
profile_log_filename = self._name + ' profile - ' + boot_pretty_timestamp + '.log'
profile_log_path = os.path.join( self._db_dir, profile_log_filename )
with open( profile_log_path, 'a' ) as f:
prefix = time.strftime( '%Y/%m/%d %H:%M:%S: ', time.localtime() )
f.write( prefix + summary )
f.write( os.linesep * 2 )
f.write( profile_text )
def ProcessPubSub( self ):
self._currently_doing_pubsub = True

View File

@ -47,37 +47,11 @@ def CanVacuum( db_path, stop_time = None ):
temp_dir = tempfile.gettempdir()
( db_dir, db_filename ) = os.path.split( db_path )
temp_disk_free_space = HydrusPaths.GetFreeSpace( temp_dir )
( has_space, reason ) = HydrusPaths.HasSpaceForDBTransaction( db_dir, db_size )
a = HydrusPaths.GetDevice( temp_dir )
b = HydrusPaths.GetDevice( db_dir )
if HydrusPaths.GetDevice( temp_dir ) == HydrusPaths.GetDevice( db_dir ):
if temp_disk_free_space < db_size * 2.2:
return False
else:
if temp_disk_free_space < db_size * 1.1:
return False
db_disk_free_space = HydrusPaths.GetFreeSpace( db_dir )
if db_disk_free_space < db_size * 1.1:
return False
return True
return has_space
except Exception as e:
@ -206,7 +180,15 @@ class HydrusDB( object ):
( version, ) = self._c.execute( 'SELECT version FROM version;' ).fetchone()
if version < HC.SOFTWARE_VERSION - 50: raise Exception( 'Your current version of hydrus ' + str( version ) + ' is too old for this version ' + str( HC.SOFTWARE_VERSION ) + ' to update. Please try updating with version ' + str( version + 45 ) + ' or earlier first.' )
if version < HC.SOFTWARE_VERSION - 50:
raise Exception( 'Your current database version of hydrus ' + str( version ) + ' is too old for this software version ' + str( HC.SOFTWARE_VERSION ) + ' to update. Please try updating with version ' + str( version + 45 ) + ' or earlier first.' )
if version < 238:
raise Exception( 'Unfortunately, this software cannot update your database. Please try installing version 238 first.' )
while version < HC.SOFTWARE_VERSION:
@ -585,6 +567,20 @@ class HydrusDB( object ):
return [ row for row in self._SelectFromList( select_statement, xs ) ]
def _STL( self, iterable_cursor ):
# strip singleton tuples to a list
return [ item for ( item, ) in iterable_cursor ]
def _STS( self, iterable_cursor ):
# strip singleton tuples to a set
return { item for ( item, ) in iterable_cursor }
def _UpdateDB( self, version ):
raise NotImplementedError()
@ -662,9 +658,11 @@ class HydrusDB( object ):
if HydrusGlobals.db_profile_mode:
HydrusData.ShowText( 'Profiling ' + job.ToString() )
summary = 'Profiling ' + job.ToString()
HydrusData.Profile( 'self._ProcessJob( job )', globals(), locals() )
HydrusData.ShowText( summary )
HydrusData.Profile( summary, 'self._ProcessJob( job )', globals(), locals() )
else:

View File

@ -709,7 +709,12 @@ def IntelligentMassIntersect( sets_to_reduce ):
sets_to_reduce = list( sets_to_reduce )
sets_to_reduce.sort( cmp = lambda x, y: cmp( len( x ), len( y ) ) )
def get_len( item ):
return len( item )
sets_to_reduce.sort( key = get_len )
for set_to_reduce in sets_to_reduce:
@ -831,7 +836,7 @@ def MergeKeyToListDicts( key_to_list_dicts ):
def Print( text ):
print( ToByteString( text ) )
print( ToUnicode( text ) )
ShowText = Print
@ -864,7 +869,7 @@ def PrintException( e, do_wait = True ):
ShowException = PrintException
def Profile( code, g, l ):
def Profile( summary, code, g, l ):
profile = cProfile.Profile()
@ -878,38 +883,19 @@ def Profile( code, g, l ):
stats.sort_stats( 'tottime' )
output.seek( 0 )
output.write( 'Stats' )
output.write( os.linesep )
output.write( os.linesep * 2 )
stats.print_stats()
output.seek( 0 )
DebugPrint( output.read() )
output.seek( 0 )
output.write( 'Callers' )
output.write( os.linesep )
output.write( os.linesep * 2 )
stats.print_callers()
output.seek( 0 )
DebugPrint( output.read() )
output.seek( 0 )
output.write( 'Callees' )
output.write( os.linesep )
stats.print_callees()
output.seek( 0 )
DebugPrint( output.read() )
HydrusGlobals.controller.PrintProfile( summary, output.read() )
def RandomPop( population ):

View File

@ -163,7 +163,10 @@ def GetFileInfo( path ):
mime = GetMime( path )
if mime not in HC.ALLOWED_MIMES: raise HydrusExceptions.MimeException( 'Filetype is not permitted!' )
if mime not in HC.ALLOWED_MIMES:
raise HydrusExceptions.MimeException( 'Filetype is not permitted!' )
width = None
height = None

View File

@ -113,13 +113,15 @@ class HydrusLogger( object ):
prefix = time.strftime( '%Y/%m/%d %H:%M:%S: ', time.localtime() )
message = HydrusData.ToByteString( prefix + value )
message = prefix + value
if not self._problem_with_previous_stdout:
stdout_message = HydrusData.ToByteString( message.replace( u'\u2026', '...' ) )
try:
self._previous_sys_stdout.write( message )
self._previous_sys_stdout.write( stdout_message )
except IOError:
@ -127,7 +129,9 @@ class HydrusLogger( object ):
self._log_file.write( message )
log_message = HydrusData.ToByteString( message )
self._log_file.write( log_message )

View File

@ -16,7 +16,7 @@ def ConvertContentsToClientToServerContentUpdatePackage( action, contents, reaso
hash_ids_to_hashes = {}
hash_i = 0
content_data_dict = GetEmptyDataDict()
content_data_dict = HydrusData.GetEmptyDataDict()
for content in contents:
@ -211,12 +211,12 @@ def DumpToBodyString( args ):
if 'account' in args:
args[ 'account' ] = args[ 'account' ].ToSerialisableTuple()
args[ 'account' ] = Account.GenerateSerialisableTupleFromAccount( args[ 'account' ] )
if 'accounts' in args:
args[ 'accounts' ] = [ account.ToSerialisableTuple() for account in args[ 'accounts' ] ]
args[ 'accounts' ] = map( Account.GenerateSerialisableTupleFromAccount, args[ 'accounts' ] )
if 'account_types' in args:
@ -399,7 +399,17 @@ def ParseGETArgs( requests_args ):
class Account( object ):
def __init__( self, account_key, account_type, created, expires, dictionary ):
def __init__( self, account_key, account_type, created, expires, banned_info = None, bandwidth_tracker = None ):
if banned_info is None:
banned_info = None # stupid, but keep it in case we change this
if bandwidth_tracker is None:
bandwidth_tracker = HydrusNetworking.BandwidthTracker()
HydrusSerialisable.SerialisableBase.__init__( self )
@ -409,8 +419,8 @@ class Account( object ):
self._account_type = account_type
self._created = created
self._expires = expires
self._LoadFromDictionary( dictionary )
self._banned_info = banned_info
self._bandwidth_tracker = bandwidth_tracker
self._dirty = False
@ -425,24 +435,6 @@ class Account( object ):
return self.__repr__()
def _GetSerialisableDictionary( self ):
dictionary = HydrusSerialisable.SerialisableDictionary()
dictionary[ 'banned_info' ] = self._banned_info
dictionary[ 'bandwidth_tracker' ] = self._bandwidth_tracker
return dictionary
def _LoadFromDictionary( self, dictionary ):
self._banned_info = dictionary[ 'banned_info' ]
self._bandwidth_tracker = dictionary[ 'bandwidth_tracker' ]
def _GetBannedString( self ):
if self._banned_info is None:
@ -706,25 +698,11 @@ class Account( object ):
def ToSerialisableTuple( self ):
with self._lock:
dictionary = self._GetSerialisableDictionary()
dictionary_string = dictionary.DumpToString()
return ( self._account_key.encode( 'hex' ), self._account_type.ToSerialisableTuple(), self._created, self._expires, dictionary_string )
def ToTuple( self ):
with self._lock:
dictionary = self._GetSerialisableDictionary()
return ( self._account_key, self._account_type, self._created, self._expires, dictionary )
return ( self._account_key, self._account_type, self._created, self._expires, self._banned_info, self._bandwidth_tracker )
@ -745,18 +723,44 @@ class Account( object ):
account_type = AccountType.GenerateAccountTypeFromSerialisableTuple( account_type_serialisable_tuple )
dictionary = HydrusSerialisable.CreateFromString( dictionary_string )
return Account( account_key, account_type, created, expires, dictionary )
return Account.GenerateAccountFromTuple( ( account_key, account_type, created, expires, dictionary ) )
@staticmethod
def GenerateNewAccount( account_key, account_type, created, expires ):
def GenerateAccountFromTuple( ( account_key, account_type, created, expires, dictionary ) ):
banned_info = dictionary[ 'banned_info' ]
bandwidth_tracker = dictionary[ 'bandwidth_tracker' ]
return Account( account_key, account_type, created, expires, banned_info, bandwidth_tracker )
@staticmethod
def GenerateSerialisableTupleFromAccount( account ):
( account_key, account_type, created, expires, dictionary ) = Account.GenerateTupleFromAccount( account )
account_key_encoded = account_key.encode( 'hex' )
serialisable_account_type = account_type.ToSerialisableTuple()
dictionary_string = dictionary.DumpToString()
return ( account_key_encoded, serialisable_account_type, created, expires, dictionary_string )
@staticmethod
def GenerateTupleFromAccount( account ):
( account_key, account_type, created, expires, banned_info, bandwidth_tracker ) = account.ToTuple()
dictionary = HydrusSerialisable.SerialisableDictionary()
dictionary[ 'banned_info' ] = None
dictionary[ 'bandwidth_tracker' ] = HydrusNetworking.BandwidthTracker()
dictionary[ 'banned_info' ] = banned_info
return Account( account_key, account_type, created, expires, dictionary )
dictionary[ 'bandwidth_tracker' ] = bandwidth_tracker
return ( account_key, account_type, created, expires, dictionary )
@staticmethod
@ -766,7 +770,7 @@ class Account( object ):
created = 0
expires = None
unknown_account = Account.GenerateNewAccount( account_key, account_type, created, expires )
unknown_account = Account( account_key, account_type, created, expires )
return unknown_account
@ -998,7 +1002,7 @@ class ClientToServerUpdate( HydrusSerialisable.SerialisableBase ):
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
for ( action, serialisable_contents_and_reasons ) in self._actions_to_contents_and_reasons.items():
for ( action, serialisable_contents_and_reasons ) in serialisable_info:
contents_and_reasons = [ ( HydrusSerialisable.CreateFromSerialisableTuple( serialisable_content ), reason ) for ( serialisable_content, reason ) in serialisable_contents_and_reasons ]
@ -1655,6 +1659,18 @@ class Metadata( HydrusSerialisable.SerialisableBase ):
self._update_hashes = set()
def _GetNextUpdateDueTime( self, from_client = False ):
delay = 0
if from_client:
delay = self.CLIENT_DELAY
return self._next_update_due + delay
def _GetSerialisableInfo( self ):
serialisable_metadata = [ ( update_index, [ update_hash.encode( 'hex' ) for update_hash in update_hashes ], begin, end ) for ( update_index, ( update_hashes, begin, end ) ) in self._metadata.items() ]
@ -1729,18 +1745,20 @@ class Metadata( HydrusSerialisable.SerialisableBase ):
def GetNextUpdateDueTime( self, from_client = False ):
def GetNextUpdateDueString( self, from_client = False ):
with self._lock:
delay = 0
if from_client:
if self._next_update_due == 0:
delay = self.CLIENT_DELAY
return 'have not yet synced metadata'
else:
update_due = self._GetNextUpdateDueTime( from_client )
return 'next update due ' + HydrusData.ConvertTimestampToPrettyPending( update_due )
return self._next_update_due + delay
@ -1851,14 +1869,9 @@ class Metadata( HydrusSerialisable.SerialisableBase ):
with self._lock:
delay = 0
next_update_due_time = self._GetNextUpdateDueTime( from_client )
if from_client:
delay = self.CLIENT_DELAY
return HydrusData.TimeHasPassed( self._next_update_due + delay )
return HydrusData.TimeHasPassed( next_update_due_time )

View File

@ -268,6 +268,43 @@ def GetTempPath( suffix = '' ):
return tempfile.mkstemp( suffix = suffix, prefix = 'hydrus' )
def HasSpaceForDBTransaction( db_dir, num_bytes ):
temp_dir = tempfile.gettempdir()
temp_disk_free_space = GetFreeSpace( temp_dir )
a = GetDevice( temp_dir )
b = GetDevice( db_dir )
if GetDevice( temp_dir ) == GetDevice( db_dir ):
space_needed = int( num_bytes * 2.2 )
if temp_disk_free_space < space_needed:
return ( False, 'I believe you need about ' + HydrusData.ConvertIntToBytes( space_needed ) + ' on your db\'s partition, which I think also holds your temporary path, but you only seem to have ' + HydrusData.ConvertIntToBytes( temp_disk_free_space ) + '.' )
else:
space_needed = int( num_bytes * 1.1 )
if temp_disk_free_space < space_needed:
return ( False, 'I believe you need about ' + HydrusData.ConvertIntToBytes( space_needed ) + ' on your temporary path\'s partition, which I think is ' + temp_dir + ', but you only seem to have ' + HydrusData.ConvertIntToBytes( temp_disk_free_space ) + '.' )
db_disk_free_space = GetFreeSpace( db_dir )
if db_disk_free_space < space_needed:
return ( False, 'I believe you need about ' + HydrusData.ConvertIntToBytes( space_needed ) + ' on your db\'s partition, but you only seem to have ' + HydrusData.ConvertIntToBytes( db_disk_free_space ) + '.' )
return ( True, 'You seem to have enough space!' )
def LaunchDirectory( path ):
def do_it():

View File

@ -107,18 +107,18 @@ class HydrusPubSub( object ):
if HydrusGlobals.pubsub_profile_mode:
text = 'Profiling ' + topic + ': ' + repr( callable )
summary = 'Profiling ' + topic + ': ' + repr( callable )
if topic == 'message':
HydrusData.Print( text )
HydrusData.Print( summary )
else:
HydrusData.ShowText( text )
HydrusData.ShowText( summary )
HydrusData.Profile( 'callable( *args, **kwargs )', globals(), locals() )
HydrusData.Profile( summary, 'callable( *args, **kwargs )', globals(), locals() )
else:
@ -165,4 +165,4 @@ class HydrusPubSub( object ):
self._topics_to_method_names[ topic ].add( method_name )

View File

@ -231,7 +231,7 @@ class HydrusTagArchive( object ):
try: hash_id = self._GetHashId( hash, read_only = True )
except: return []
result = { tag for ( tag, ) in self._c.execute( 'SELECT tag FROM mappings, tags USING ( tag_id ) WHERE hash_id = ?;', ( hash_id, ) ) }
result = { tag for ( tag, ) in self._c.execute( 'SELECT tag FROM mappings NATURAL JOIN tags WHERE hash_id = ?;', ( hash_id, ) ) }
return result

View File

@ -137,8 +137,6 @@ def SortNumericTags( tags ):
def CheckTagNotEmpty( tag ):
empty_tag = False
( namespace, subtag ) = SplitTag( tag )
if subtag == '':

View File

@ -112,7 +112,7 @@ def ShutdownSiblingInstance( db_dir ):
port_found = True
HydrusData.Print( 'Sending shut down instruction...' )
HydrusData.Print( u'Sending shut down instruction\u2026' )
connection.request( 'POST', '/shutdown' )
@ -160,6 +160,8 @@ class Controller( HydrusController.HydrusController ):
HydrusController.HydrusController.__init__( self, db_dir, no_daemons, no_wal )
self._name = 'server'
HydrusGlobals.server_controller = self
@ -260,11 +262,11 @@ class Controller( HydrusController.HydrusController ):
def Exit( self ):
HydrusData.Print( 'Shutting down daemons and services...' )
HydrusData.Print( u'Shutting down daemons and services\u2026' )
self.ShutdownView()
HydrusData.Print( 'Shutting down db...' )
HydrusData.Print( u'Shutting down db\u2026' )
self.ShutdownModel()
@ -360,11 +362,11 @@ class Controller( HydrusController.HydrusController ):
HydrusData.RecordRunningStart( self._db_dir, 'server' )
HydrusData.Print( 'Initialising db...' )
HydrusData.Print( u'Initialising db\u2026' )
self.InitModel()
HydrusData.Print( 'Initialising daemons and services...' )
HydrusData.Print( u'Initialising daemons and services\u2026' )
self.InitView()
@ -384,7 +386,7 @@ class Controller( HydrusController.HydrusController ):
interrupt_received = True
HydrusData.Print( 'Received a keyboard interrupt...' )
HydrusData.Print( u'Received a keyboard interrupt\u2026' )
def do_it():
@ -396,7 +398,7 @@ class Controller( HydrusController.HydrusController ):
HydrusData.Print( 'Shutting down controller...' )
HydrusData.Print( u'Shutting down controller\u2026' )
def SaveDirtyObjects( self ):
@ -463,7 +465,7 @@ class Controller( HydrusController.HydrusController ):
def ShutdownFromServer( self ):
HydrusData.Print( 'Received a server shut down request...' )
HydrusData.Print( u'Received a server shut down request\u2026' )
def do_it():

View File

@ -195,7 +195,7 @@ class DB( HydrusDB.HydrusDB ):
[ registration_key ] = self._GenerateRegistrationKeys( service_id, 1, service_admin_account_type_id, None, force_registration_key )
access_key = self._GetAccessKey( registration_key )
access_key = self._GetAccessKey( service_key, registration_key )
if service_type in HC.REPOSITORIES:
@ -471,14 +471,16 @@ class DB( HydrusDB.HydrusDB ):
return [ registration_key for ( registration_key, account_key, access_key ) in keys ]
def _GetAccessKey( self, registration_key ):
def _GetAccessKey( self, service_key, registration_key ):
service_id = self._GetServiceId( service_key )
# we generate a new access_key every time this is requested so that no one with access to the registration key can peek at the access_key before the legit user fetches it for real
# the reg_key is deleted when the last-requested access_key is used to create a session, which calls getaccountkeyfromaccesskey
registration_key_sha256 = hashlib.sha256( registration_key ).digest()
result = self._c.execute( 'SELECT 1 FROM registration_keys WHERE registration_key = ?;', ( sqlite3.Binary( registration_key_sha256 ), ) ).fetchone()
result = self._c.execute( 'SELECT 1 FROM registration_keys WHERE service_id = ? AND registration_key = ?;', ( service_id, sqlite3.Binary( registration_key_sha256 ) ) ).fetchone()
if result is None:
@ -487,7 +489,7 @@ class DB( HydrusDB.HydrusDB ):
new_access_key = os.urandom( HC.HYDRUS_KEY_LENGTH )
self._c.execute( 'UPDATE registration_keys SET access_key = ? WHERE registration_key = ?;', ( sqlite3.Binary( new_access_key ), sqlite3.Binary( registration_key_sha256 ) ) )
self._c.execute( 'UPDATE registration_keys SET access_key = ? WHERE service_id = ? AND registration_key = ?;', ( sqlite3.Binary( new_access_key ), service_id, sqlite3.Binary( registration_key_sha256 ) ) )
return new_access_key
@ -500,7 +502,7 @@ class DB( HydrusDB.HydrusDB ):
dictionary = HydrusSerialisable.CreateFromString( dictionary_string )
return HydrusNetwork.Account( account_key, account_type, created, expires, dictionary )
return HydrusNetwork.Account.GenerateAccountFromTuple( ( account_key, account_type, created, expires, dictionary ) )
def _GetAccountFromAccountKey( self, service_key, account_key ):
@ -543,9 +545,9 @@ class DB( HydrusDB.HydrusDB ):
created = HydrusData.GetNow()
account = HydrusNetwork.Account.GenerateNewAccount( account_key, account_type, created, expires )
account = HydrusNetwork.Account( account_key, account_type, created, expires )
( account_key, account_type, created, expires, dictionary ) = account.ToTuple()
( account_key, account_type, created, expires, dictionary ) = HydrusNetwork.Account.GenerateTupleFromAccount( account )
dictionary_string = dictionary.DumpToString()
@ -628,7 +630,7 @@ class DB( HydrusDB.HydrusDB ):
account_info = {}
account_info[ 'account' ] = subject_account.ToSerialisableTuple()
account_info[ 'account' ] = HydrusNetwork.Account.GenerateSerialisableTupleFromAccount( subject_account )
return account_info
@ -1283,7 +1285,9 @@ class DB( HydrusDB.HydrusDB ):
else:
deleted_service_hash_ids = ( service_hash_id for ( service_hash_id, ) in self._c.execute( 'SELECT service_hash_id FROM ' + deleted_mappings_table_name + ' WHERE service_tag_id = ? AND service_hash_id = ?;', ( ( service_tag_id, service_hash_id ) for service_hash_id in service_hash_ids ) ) )
select_statement = 'SELECT service_hash_id FROM ' + deleted_mappings_table_name + ' WHERE service_tag_id = ' + str( service_tag_id ) + ' AND service_hash_id IN %s;'
deleted_service_hash_ids = ( service_hash_id for ( service_hash_id, ) in self._SelectFromList( select_statement, service_hash_ids ) )
service_hash_ids = set( service_hash_ids ).difference( deleted_service_hash_ids )
@ -1588,7 +1592,7 @@ class DB( HydrusDB.HydrusDB ):
now = HydrusData.GetNow()
self._c.execute( 'INSERT OR IGNORE INTO ' + deleted_tag_siblings_table_name + ' ( bad_tag_id, good_tag_id, account_id, sibling_timestamp ) VALUES ( ?, ?, ?, ? );', ( bad_service_tag_id, good_service_tag_id, account_id, now ) )
self._c.execute( 'INSERT OR IGNORE INTO ' + deleted_tag_siblings_table_name + ' ( bad_service_tag_id, good_service_tag_id, account_id, sibling_timestamp ) VALUES ( ?, ?, ?, ? );', ( bad_service_tag_id, good_service_tag_id, account_id, now ) )
def _RepositoryDenyFilePetition( self, service_id, service_hash_ids ):
@ -1962,7 +1966,7 @@ class DB( HydrusDB.HydrusDB ):
( current_mappings_table_name, deleted_mappings_table_name, pending_mappings_table_name, petitioned_mappings_table_name ) = GenerateRepositoryMappingsTableNames( service_id )
master_hash_ids = [ master_hash_id for ( master_hash_id, ) in self._c.execute( 'SELECT master_hash_id FROM ' + hash_id_map_table_name + ' NATURAL JOIN ' + current_mappings_table_name + ' WHERE service_tag_id = ?;', ( service_id, service_tag_id ) ) ]
master_hash_ids = [ master_hash_id for ( master_hash_id, ) in self._c.execute( 'SELECT master_hash_id FROM ' + hash_id_map_table_name + ' NATURAL JOIN ' + current_mappings_table_name + ' WHERE service_tag_id = ?;', ( service_tag_id, ) ) ]
return master_hash_ids
@ -3068,7 +3072,7 @@ class DB( HydrusDB.HydrusDB ):
for account in accounts:
( account_key, account_type, created, expires, dictionary ) = account.ToTuple()
( account_key, account_type, created, expires, dictionary ) = HydrusNetwork.Account.GenerateTupleFromAccount( account )
account_type_key = account_type.GetAccountTypeKey()

View File

@ -33,7 +33,7 @@ class HydrusResourceAccessKey( HydrusServerResources.HydrusResource ):
registration_key = request.hydrus_args[ 'registration_key' ]
access_key = HydrusGlobals.server_controller.Read( 'access_key', registration_key )
access_key = HydrusGlobals.server_controller.Read( 'access_key', self._service_key, registration_key )
body = HydrusNetwork.DumpToBodyString( { 'access_key' : access_key } )

View File

@ -55,12 +55,11 @@ class TestManagers( unittest.TestCase ):
def test_services( self ):
def test_service( service, key, service_type, name, info ):
def test_service( service, key, service_type, name ):
self.assertEqual( service.GetServiceKey(), key )
self.assertEqual( service.GetServiceType(), service_type )
self.assertEqual( service.GetName(), name )
self.assertEqual( service.GetInfo(), info )
repo_key = HydrusData.GenerateKey()
@ -86,7 +85,7 @@ class TestManagers( unittest.TestCase ):
service = services_manager.GetService( repo_key )
test_service( service, repo_key, repo_type, repo_name, repo_info )
test_service( service, repo_key, repo_type, repo_name )
service = services_manager.GetService( other_key )

View File

@ -86,11 +86,11 @@ class TestClientDB( unittest.TestCase ):
self._clear_db()
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'c' )
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'c*' )
self.assertEqual( result, [] )
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'series:' )
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'series:*' )
self.assertEqual( result, [] )
@ -118,7 +118,7 @@ class TestClientDB( unittest.TestCase ):
# cars
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'c', add_namespaceless = True )
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'c*', add_namespaceless = True )
preds = set()
@ -131,7 +131,7 @@ class TestClientDB( unittest.TestCase ):
# cars
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'c', add_namespaceless = False )
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'c*', add_namespaceless = False )
preds = set()
@ -144,13 +144,17 @@ class TestClientDB( unittest.TestCase ):
#
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'ser' )
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'ser*' )
self.assertEqual( result, [] )
preds = set()
preds.add( ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, 'series:cars', min_current_count = 1 ) )
self.assertEqual( set( result ), preds )
#
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'series:c' )
result = self._read( 'autocomplete_predicates', tag_service_key = CC.LOCAL_TAG_SERVICE_KEY, search_text = 'series:c*' )
pred = ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, 'series:cars', min_current_count = 1 )
@ -895,7 +899,11 @@ class TestClientDB( unittest.TestCase ):
result = self._read( 'pending', service_key )
self.assertIsInstance( result, HydrusNetwork.ClientToServerContentUpdatePackage )
self.assertIsInstance( result, HydrusNetwork.ClientToServerUpdate )
self.assertTrue( result.HasContent() )
self.assertEqual( set( result.GetHashes() ), set( hashes ) )
#
@ -965,7 +973,7 @@ class TestClientDB( unittest.TestCase ):
result_service_keys = { service.GetServiceKey() for service in result }
self.assertItemsEqual( { CC.TRASH_SERVICE_KEY, CC.LOCAL_FILE_SERVICE_KEY, CC.COMBINED_LOCAL_FILE_SERVICE_KEY, CC.LOCAL_TAG_SERVICE_KEY }, result_service_keys )
self.assertItemsEqual( { CC.TRASH_SERVICE_KEY, CC.LOCAL_FILE_SERVICE_KEY, CC.LOCAL_UPDATE_SERVICE_KEY, CC.COMBINED_LOCAL_FILE_SERVICE_KEY, CC.LOCAL_TAG_SERVICE_KEY }, result_service_keys )
#
@ -1234,8 +1242,8 @@ class TestServerDB( unittest.TestCase ):
r_key = r_keys[0]
access_key = self._read( 'access_key', r_key )
access_key_2 = self._read( 'access_key', r_key )
access_key = self._read( 'access_key', self._tag_service_key, r_key )
access_key_2 = self._read( 'access_key', self._tag_service_key, r_key )
self.assertNotEqual( access_key, access_key_2 )
@ -1257,7 +1265,7 @@ class TestServerDB( unittest.TestCase ):
def _test_init_server_admin( self ):
result = self._read( 'init' ) # an access key
result = self._read( 'access_key', HC.SERVER_ADMIN_KEY, 'init' )
self.assertEqual( type( result ), str )
self.assertEqual( len( result ), 32 )

View File

@ -166,10 +166,10 @@ class TestNonDBDialogs( unittest.TestCase ):
list_of_tuples = [ ( 'a', 123 ), ( 'b', 456 ), ( 'c', 789 ) ]
with ClientGUIDialogs.DialogSelectFromList( None, 'select from a list of strings', [ 'a', 'b', 'c' ] ) as dlg:
with ClientGUIDialogs.DialogSelectFromList( None, 'select from a list of strings', list_of_tuples ) as dlg:
wx.CallLater( 500, dlg._list.Select, 1 )
wx.CallLater( 1000, PressKey, dlg._strings, wx.WXK_RETURN )
wx.CallLater( 1000, PressKey, dlg._list, wx.WXK_RETURN )
result = dlg.ShowModal()

View File

@ -0,0 +1,86 @@
import ClientConstants as CC
import ClientSearch
import HydrusConstants as HC
import HydrusNetwork
import HydrusSerialisable
import unittest
class TestServer( unittest.TestCase ):
def _dump_and_load_and_test( self, obj, test_func ):
serialisable_tuple = obj.GetSerialisableTuple()
self.assertIsInstance( serialisable_tuple, tuple )
if isinstance( obj, HydrusSerialisable.SerialisableBaseNamed ):
( serialisable_type, name, version, serialisable_info ) = serialisable_tuple
elif isinstance( obj, HydrusSerialisable.SerialisableBase ):
( serialisable_type, version, serialisable_info ) = serialisable_tuple
self.assertEqual( serialisable_type, obj.SERIALISABLE_TYPE )
self.assertEqual( version, obj.SERIALISABLE_VERSION )
dupe_obj = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_tuple )
self.assertIsNot( obj, dupe_obj )
test_func( obj, dupe_obj )
#
json_string = obj.DumpToString()
self.assertIsInstance( json_string, str )
dupe_obj = HydrusSerialisable.CreateFromString( json_string )
self.assertIsNot( obj, dupe_obj )
test_func( obj, dupe_obj )
#
network_string = obj.DumpToNetworkString()
self.assertIsInstance( network_string, str )
dupe_obj = HydrusSerialisable.CreateFromNetworkString( network_string )
self.assertIsNot( obj, dupe_obj )
test_func( obj, dupe_obj )
def test_basics( self ):
d = HydrusSerialisable.SerialisableDictionary()
d[ 1 ] = 2
d[ 3 ] = 'test1'
d[ 'test2' ] = 4
d[ 'test3' ] = 5
d[ 6 ] = HydrusSerialisable.SerialisableDictionary( { i : 'test' + str( i ) for i in range( 20 ) } )
d[ ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, 'test pred 1' ) ] = 56
d[ ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, 'test pred 2' ) ] = HydrusSerialisable.SerialisableList( [ ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, 'test' + str( i ) ) for i in range( 10 ) ] )
def test( obj, dupe_obj ):
self.assertEqual( len( obj.items() ), len( dupe_obj.items() ) )
for ( key, value ) in obj.items():
self.assertEqual( value, dupe_obj[ key ] )
self._dump_and_load_and_test( d, test )

View File

@ -3,6 +3,7 @@ import ClientData
import ClientFiles
import ClientLocalServer
import ClientMedia
import ClientServices
import hashlib
import httplib
import HydrusConstants as HC
@ -45,9 +46,11 @@ class TestServer( unittest.TestCase ):
services = []
self._file_service = HydrusNetwork.GenerateService( HydrusData.GenerateKey(), HC.FILE_REPOSITORY, 'file repo' )
self._tag_service = HydrusNetwork.GenerateService( HydrusData.GenerateKey(), HC.TAG_REPOSITORY, 'tag repo' )
self._admin_service = HydrusNetwork.GenerateService( HydrusData.GenerateKey(), HC.SERVER_ADMIN, 'server admin' )
self._file_service = HydrusNetwork.GenerateService( HydrusData.GenerateKey(), HC.FILE_REPOSITORY, 'file repo', HC.DEFAULT_SERVICE_PORT + 1 )
self._tag_service = HydrusNetwork.GenerateService( HydrusData.GenerateKey(), HC.TAG_REPOSITORY, 'tag repo', HC.DEFAULT_SERVICE_PORT )
self._admin_service = HydrusNetwork.GenerateService( HydrusData.GenerateKey(), HC.SERVER_ADMIN, 'server admin', HC.DEFAULT_SERVER_ADMIN_PORT )
self._local_booru = ClientServices.GenerateService( HydrusData.GenerateKey(), HC.LOCAL_BOORU, 'local booru' )
services_manager = HydrusGlobals.test_controller.GetServicesManager()
@ -82,12 +85,11 @@ class TestServer( unittest.TestCase ):
context_factory = twisted.internet.ssl.DefaultOpenSSLContextFactory( self._ssl_key_path, self._ssl_cert_path )
reactor.listenSSL( HC.DEFAULT_SERVER_ADMIN_PORT, ServerServer.HydrusServiceAdmin( self._admin_service.GetServiceKey(), HC.SERVER_ADMIN, 'hello' ), context_factory )
reactor.listenSSL( HC.DEFAULT_SERVICE_PORT, ServerServer.HydrusServiceRepositoryFile( self._file_service.GetServiceKey(), HC.FILE_REPOSITORY, 'hello' ), context_factory )
reactor.listenSSL( HC.DEFAULT_SERVICE_PORT + 1, ServerServer.HydrusServiceRepositoryTag( self._tag_service.GetServiceKey(), HC.TAG_REPOSITORY, 'hello' ), context_factory )
reactor.listenSSL( HC.DEFAULT_SERVER_ADMIN_PORT, ServerServer.HydrusServiceAdmin( self._admin_service ), context_factory )
reactor.listenSSL( HC.DEFAULT_SERVICE_PORT, ServerServer.HydrusServiceRepositoryFile( self._file_service ), context_factory )
reactor.listenSSL( HC.DEFAULT_SERVICE_PORT + 1, ServerServer.HydrusServiceRepositoryTag( self._tag_service ), context_factory )
reactor.listenTCP( HC.DEFAULT_LOCAL_FILE_PORT, ClientLocalServer.HydrusServiceLocal( CC.COMBINED_LOCAL_FILE_SERVICE_KEY, HC.COMBINED_LOCAL_FILE, 'hello' ) )
reactor.listenTCP( HC.DEFAULT_LOCAL_BOORU_PORT, ClientLocalServer.HydrusServiceBooru( CC.LOCAL_BOORU_SERVICE_KEY, HC.LOCAL_BOORU, 'hello' ) )
reactor.listenTCP( HC.DEFAULT_LOCAL_BOORU_PORT, ClientLocalServer.HydrusServiceBooru( self._local_booru ) )
reactor.callFromThread( TWISTEDSetup )
@ -136,51 +138,6 @@ class TestServer( unittest.TestCase ):
self.assertEqual( data, favicon )
def _test_local_file( self, host, port ):
connection = httplib.HTTPConnection( host, port, timeout = 10 )
#
client_files_default = os.path.join( TestConstants.DB_DIR, 'client_files' )
hash_encoded = self._file_hash.encode( 'hex' )
prefix = hash_encoded[:2]
path = os.path.join( client_files_default, 'f' + prefix, hash_encoded + '.jpg' )
with open( path, 'wb' ) as f: f.write( EXAMPLE_FILE )
connection.request( 'GET', '/file?hash=' + self._file_hash.encode( 'hex' ) )
response = connection.getresponse()
data = response.read()
self.assertEqual( data, EXAMPLE_FILE )
try: os.remove( path )
except: pass
#
path = os.path.join( client_files_default, 't' + prefix, hash_encoded + '.thumbnail' )
with open( path, 'wb' ) as f: f.write( EXAMPLE_THUMBNAIL )
connection.request( 'GET', '/thumbnail?hash=' + self._file_hash.encode( 'hex' ) )
response = connection.getresponse()
data = response.read()
self.assertEqual( data, EXAMPLE_THUMBNAIL )
try: os.remove( path )
except: pass
def _test_file_repo( self, service, host, port ):
info = service.GetInfo()
@ -577,15 +534,6 @@ class TestServer( unittest.TestCase ):
pass
def test_local_service( self ):
host = '127.0.0.1'
port = HC.DEFAULT_LOCAL_FILE_PORT
self._test_basics( host, port, https = False )
self._test_local_file( host, port )
def test_repository_file( self ):
host = '127.0.0.1'

View File

@ -710,15 +710,15 @@ class TestTagSiblings( unittest.TestCase ):
def test_autocomplete( self ):
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'ishy' ) ), set( [ 'ishygddt', 'i sure hope you guys don\'t do that' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'i su' ) ), set( [ 'ishygddt', 'i sure hope you guys don\'t do that' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'ishy*' ) ), set( [ 'ishygddt', 'i sure hope you guys don\'t do that' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'i su*' ) ), set( [ 'ishygddt', 'i sure hope you guys don\'t do that' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'ayan' ) ), set( [ 'character:rei ayanami', 'character:ayanami rei' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'rei' ) ), set( [ 'character:rei ayanami', 'character:ayanami rei' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'character:ayan' ) ), set( [ 'character:rei ayanami', 'character:ayanami rei' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'character:rei' ) ), set( [ 'character:rei ayanami', 'character:ayanami rei' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'ayan*' ) ), set( [ 'character:rei ayanami', 'character:ayanami rei' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'rei*' ) ), set( [ 'character:rei ayanami', 'character:ayanami rei' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'character:ayan*' ) ), set( [ 'character:rei ayanami', 'character:ayanami rei' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'character:rei*' ) ), set( [ 'character:rei ayanami', 'character:ayanami rei' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._second_key, 'ishy' ) ), set() )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._second_key, 'ishy*' ) ), set() )
def test_collapse_predicates( self ):
@ -747,7 +747,7 @@ class TestTagSiblings( unittest.TestCase ):
def test_chain( self ):
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'chai' ) ), set( [ 'chain_a', 'chain_b', 'chain_c' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'chai*' ) ), set( [ 'chain_a', 'chain_b', 'chain_c' ] ) )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._first_key, 'chain_a' ), 'chain_c' )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._first_key, 'chain_b' ), 'chain_c' )
@ -763,7 +763,7 @@ class TestTagSiblings( unittest.TestCase ):
def test_current( self ):
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._second_key, 'curr' ) ), set( [ 'current_a', 'current_b' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._second_key, 'curr*' ) ), set( [ 'current_a', 'current_b' ] ) )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._second_key, 'current_a' ), 'current_b' )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._second_key, 'current_b' ), None )
@ -780,7 +780,7 @@ class TestTagSiblings( unittest.TestCase ):
def test_deleted( self ):
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'dele' ) ), set() )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'dele*' ) ), set() )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._first_key, 'deleted_a' ), None )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._first_key, 'deleted_b' ), None )
@ -795,7 +795,7 @@ class TestTagSiblings( unittest.TestCase ):
def test_no_loop( self ):
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'loop' ) ), set( [ 'loop_a', 'loop_b', 'loop_c' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'loop*' ) ), set( [ 'loop_a', 'loop_b', 'loop_c' ] ) )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._first_key, 'closed_loop' ), None )
@ -808,7 +808,7 @@ class TestTagSiblings( unittest.TestCase ):
def test_not_exist( self ):
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._second_key, 'not_' ) ), set() )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._second_key, 'not_*' ) ), set() )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._second_key, 'not_exist' ), None )
@ -821,7 +821,7 @@ class TestTagSiblings( unittest.TestCase ):
def test_pending_overwrite( self ):
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._second_key, 'pend' ) ), set( [ 'pending_a', 'pending_b' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._second_key, 'pend*' ) ), set( [ 'pending_a', 'pending_b' ] ) )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._second_key, 'pending_a' ), 'pending_b' )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._second_key, 'pending_b' ), None )
@ -838,7 +838,7 @@ class TestTagSiblings( unittest.TestCase ):
def test_petitioned_no_overwrite( self ):
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._second_key, 'petitioned_a' ) ), set( [ 'petitioned_a', 'petitioned_b' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._second_key, 'petitioned_a*' ) ), set( [ 'petitioned_a', 'petitioned_b' ] ) )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._second_key, 'petitioned_a' ), 'petitioned_b' )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._second_key, 'petitioned_b' ), None )
@ -855,7 +855,7 @@ class TestTagSiblings( unittest.TestCase ):
def test_tree( self ):
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'tree' ) ), set( [ 'tree_1', 'tree_2', 'tree_3', 'tree_4', 'tree_5', 'tree_6' ] ) )
self.assertEqual( set( self._tag_siblings_manager.GetAutocompleteSiblings( self._first_key, 'tree*' ) ), set( [ 'tree_1', 'tree_2', 'tree_3', 'tree_4', 'tree_5', 'tree_6' ] ) )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._first_key, 'tree_1' ), 'tree_6' )
self.assertEqual( self._tag_siblings_manager.GetSibling( self._first_key, 'tree_2' ), 'tree_6' )

View File

@ -83,7 +83,7 @@ try:
if action in ( 'start', 'restart' ):
HydrusData.Print( 'Initialising controller...' )
HydrusData.Print( u'Initialising controller\u2026' )
threading.Thread( target = reactor.run, kwargs = { 'installSignalHandlers' : 0 } ).start()

12
test.py
View File

@ -24,6 +24,7 @@ from include import TestDB
from include import TestFunctions
from include import TestClientImageHandling
from include import TestHydrusNATPunch
from include import TestHydrusSerialisable
from include import TestHydrusServer
from include import TestHydrusSessions
from include import TestHydrusTags
@ -248,7 +249,15 @@ class Controller( object ):
return HydrusGlobals.model_shutdown
def Read( self, name, *args, **kwargs ): return self._reads[ name ]
def Read( self, name, *args, **kwargs ):
return self._reads[ name ]
def RequestMade( self, num_bytes ):
pass
def ResetIdleTimer( self ): pass
@ -269,6 +278,7 @@ class Controller( object ):
if run_all or only_run == 'functions': suites.append( unittest.TestLoader().loadTestsFromModule( TestFunctions ) )
if run_all or only_run == 'image': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientImageHandling ) )
if run_all or only_run == 'nat': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusNATPunch ) )
if run_all or only_run == 'serialisable': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusSerialisable ) )
if run_all or only_run == 'server': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusServer ) )
if run_all or only_run == 'sessions': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusSessions ) )
if run_all or only_run == 'tags': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusTags ) )