hydrus/hydrus/client/metadata/ClientTagsHandling.py

1087 lines
37 KiB
Python
Raw Normal View History

import collections
2020-10-21 22:22:10 +00:00
import random
import threading
2020-10-21 22:22:10 +00:00
import time
import typing
from hydrus.core import HydrusConstants as HC
2020-10-21 22:22:10 +00:00
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusSerialisable
2021-04-07 21:26:45 +00:00
from hydrus.core import HydrusTags
from hydrus.client import ClientConstants as CC
2022-01-19 21:28:59 +00:00
from hydrus.client import ClientLocation
class TagAutocompleteOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_TAG_AUTOCOMPLETE_OPTIONS
SERIALISABLE_NAME = 'Tag Autocomplete Options'
2022-12-21 22:00:27 +00:00
SERIALISABLE_VERSION = 5
def __init__( self, service_key: typing.Optional[ bytes ] = None ):
if service_key is None:
service_key = CC.COMBINED_TAG_SERVICE_KEY
HydrusSerialisable.SerialisableBase.__init__( self )
self._service_key = service_key
self._write_autocomplete_tag_domain = self._service_key
2022-03-23 20:57:10 +00:00
self._override_write_autocomplete_location_context = True
if service_key == CC.DEFAULT_LOCAL_TAG_SERVICE_KEY:
2022-05-25 21:30:53 +00:00
self._write_autocomplete_location_context = ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY )
else:
2022-03-23 20:57:10 +00:00
self._write_autocomplete_location_context = ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_FILE_SERVICE_KEY )
self._search_namespaces_into_full_tags = False
2022-12-21 22:00:27 +00:00
self._unnamespaced_search_gives_any_namespace_wildcards = False
self._namespace_bare_fetch_all_allowed = False
self._namespace_fetch_all_allowed = False
self._fetch_all_allowed = False
2020-12-23 23:07:58 +00:00
self._fetch_results_automatically = True
self._exact_match_character_threshold = 2
def _GetSerialisableInfo( self ):
serialisable_service_key = self._service_key.hex()
serialisable_write_autocomplete_tag_domain = self._write_autocomplete_tag_domain.hex()
2022-03-23 20:57:10 +00:00
serialisable_write_autocomplete_location_context = self._write_autocomplete_location_context.GetSerialisableTuple()
serialisable_info = [
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
2022-03-23 20:57:10 +00:00
self._override_write_autocomplete_location_context,
serialisable_write_autocomplete_location_context,
self._search_namespaces_into_full_tags,
2022-12-21 22:00:27 +00:00
self._unnamespaced_search_gives_any_namespace_wildcards,
self._namespace_bare_fetch_all_allowed,
self._namespace_fetch_all_allowed,
2020-12-23 23:07:58 +00:00
self._fetch_all_allowed,
self._fetch_results_automatically,
self._exact_match_character_threshold
]
return serialisable_info
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
[
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
2022-03-23 20:57:10 +00:00
self._override_write_autocomplete_location_context,
serialisable_write_autocomplete_location_context,
self._search_namespaces_into_full_tags,
2022-12-21 22:00:27 +00:00
self._unnamespaced_search_gives_any_namespace_wildcards,
self._namespace_bare_fetch_all_allowed,
self._namespace_fetch_all_allowed,
2020-12-23 23:07:58 +00:00
self._fetch_all_allowed,
self._fetch_results_automatically,
self._exact_match_character_threshold
] = serialisable_info
self._service_key = bytes.fromhex( serialisable_service_key )
self._write_autocomplete_tag_domain = bytes.fromhex( serialisable_write_autocomplete_tag_domain )
2022-03-23 20:57:10 +00:00
self._write_autocomplete_location_context = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_write_autocomplete_location_context )
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
[
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
2022-03-23 20:57:10 +00:00
override_write_autocomplete_location_context,
serialisable_write_autocomplete_file_domain,
search_namespaces_into_full_tags,
namespace_fetch_all_allowed,
fetch_all_allowed
] = old_serialisable_info
namespace_bare_fetch_all_allowed = False
new_serialisable_info = [
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
2022-03-23 20:57:10 +00:00
override_write_autocomplete_location_context,
serialisable_write_autocomplete_file_domain,
search_namespaces_into_full_tags,
namespace_bare_fetch_all_allowed,
namespace_fetch_all_allowed,
fetch_all_allowed
]
return ( 2, new_serialisable_info )
2020-12-23 23:07:58 +00:00
if version == 2:
[
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
2022-03-23 20:57:10 +00:00
override_write_autocomplete_location_context,
2020-12-23 23:07:58 +00:00
serialisable_write_autocomplete_file_domain,
search_namespaces_into_full_tags,
namespace_bare_fetch_all_allowed,
namespace_fetch_all_allowed,
fetch_all_allowed
] = old_serialisable_info
fetch_results_automatically = True
exact_match_character_threshold = 2
new_serialisable_info = [
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
2022-03-23 20:57:10 +00:00
override_write_autocomplete_location_context,
2020-12-23 23:07:58 +00:00
serialisable_write_autocomplete_file_domain,
search_namespaces_into_full_tags,
namespace_bare_fetch_all_allowed,
namespace_fetch_all_allowed,
fetch_all_allowed,
fetch_results_automatically,
exact_match_character_threshold
]
return ( 3, new_serialisable_info )
2022-03-23 20:57:10 +00:00
if version == 3:
[
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
override_write_autocomplete_location_context,
serialisable_write_autocomplete_file_domain,
search_namespaces_into_full_tags,
namespace_bare_fetch_all_allowed,
namespace_fetch_all_allowed,
fetch_all_allowed,
fetch_results_automatically,
exact_match_character_threshold
] = old_serialisable_info
file_service_key = bytes.fromhex( serialisable_write_autocomplete_file_domain )
location_context = ClientLocation.LocationContext.STATICCreateSimple( file_service_key )
serialisable_write_autocomplete_location_context = location_context.GetSerialisableTuple()
new_serialisable_info = [
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
override_write_autocomplete_location_context,
serialisable_write_autocomplete_location_context,
search_namespaces_into_full_tags,
namespace_bare_fetch_all_allowed,
namespace_fetch_all_allowed,
fetch_all_allowed,
fetch_results_automatically,
exact_match_character_threshold
]
return ( 4, new_serialisable_info )
2022-12-21 22:00:27 +00:00
if version == 4:
[
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
override_write_autocomplete_location_context,
serialisable_write_autocomplete_location_context,
search_namespaces_into_full_tags,
namespace_bare_fetch_all_allowed,
namespace_fetch_all_allowed,
fetch_all_allowed,
fetch_results_automatically,
exact_match_character_threshold
] = old_serialisable_info
unnamespaced_search_gives_any_namespace_wildcards = False
new_serialisable_info = [
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
override_write_autocomplete_location_context,
serialisable_write_autocomplete_location_context,
search_namespaces_into_full_tags,
unnamespaced_search_gives_any_namespace_wildcards,
namespace_bare_fetch_all_allowed,
namespace_fetch_all_allowed,
fetch_all_allowed,
fetch_results_automatically,
exact_match_character_threshold
]
return ( 5, new_serialisable_info )
2020-12-23 23:07:58 +00:00
def FetchAllAllowed( self ):
return self._fetch_all_allowed
2020-12-23 23:07:58 +00:00
def FetchResultsAutomatically( self ):
return self._fetch_results_automatically
def GetExactMatchCharacterThreshold( self ):
return self._exact_match_character_threshold
def GetServiceKey( self ):
return self._service_key
2022-03-23 20:57:10 +00:00
def GetWriteAutocompleteLocationContext( self ) -> ClientLocation.LocationContext:
2022-03-23 20:57:10 +00:00
return self._write_autocomplete_location_context
2022-03-23 20:57:10 +00:00
def GetWriteAutocompleteSearchDomain( self, location_context: ClientLocation.LocationContext ):
tag_service_key = self._service_key
if self._service_key != CC.COMBINED_TAG_SERVICE_KEY:
2022-03-23 20:57:10 +00:00
if self._override_write_autocomplete_location_context:
2022-03-23 20:57:10 +00:00
location_context = self._write_autocomplete_location_context.Duplicate()
tag_service_key = self._write_autocomplete_tag_domain
2022-01-19 21:28:59 +00:00
if location_context.IsAllKnownFiles() and tag_service_key == CC.COMBINED_TAG_SERVICE_KEY: # ruh roh
2022-01-19 21:28:59 +00:00
location_context = ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
2022-01-19 21:28:59 +00:00
return ( location_context, tag_service_key )
def GetWriteAutocompleteTagDomain( self ):
return self._write_autocomplete_tag_domain
def NamespaceBareFetchAllAllowed( self ):
return self._namespace_bare_fetch_all_allowed
def NamespaceFetchAllAllowed( self ):
return self._namespace_fetch_all_allowed
2022-03-23 20:57:10 +00:00
def OverridesWriteAutocompleteLocationContext( self ):
2022-03-23 20:57:10 +00:00
return self._override_write_autocomplete_location_context
def SearchNamespacesIntoFullTags( self ):
return self._search_namespaces_into_full_tags
2022-12-21 22:00:27 +00:00
def UnnamespacedSearchGivesAnyNamespaceWildcards( self ) -> bool:
return self._unnamespaced_search_gives_any_namespace_wildcards
2020-12-23 23:07:58 +00:00
def SetExactMatchCharacterThreshold( self, exact_match_character_threshold: typing.Optional[ int ] ):
self._exact_match_character_threshold = exact_match_character_threshold
def SetFetchResultsAutomatically( self, fetch_results_automatically: bool ):
self._fetch_results_automatically = fetch_results_automatically
2022-12-21 22:00:27 +00:00
def SetUnnamespacedSearchGivesAnyNamespaceWildcards( self, unnamespaced_search_gives_any_namespace_wildcards: bool ):
self._unnamespaced_search_gives_any_namespace_wildcards = unnamespaced_search_gives_any_namespace_wildcards
def SetTuple( self,
write_autocomplete_tag_domain: bytes,
2022-03-23 20:57:10 +00:00
override_write_autocomplete_location_context: bool,
write_autocomplete_location_context: ClientLocation.LocationContext,
search_namespaces_into_full_tags: bool,
namespace_bare_fetch_all_allowed: bool,
namespace_fetch_all_allowed: bool,
fetch_all_allowed: bool
):
self._write_autocomplete_tag_domain = write_autocomplete_tag_domain
2022-03-23 20:57:10 +00:00
self._override_write_autocomplete_location_context = override_write_autocomplete_location_context
self._write_autocomplete_location_context = write_autocomplete_location_context
self._search_namespaces_into_full_tags = search_namespaces_into_full_tags
self._namespace_bare_fetch_all_allowed = namespace_bare_fetch_all_allowed
self._namespace_fetch_all_allowed = namespace_fetch_all_allowed
self._fetch_all_allowed = fetch_all_allowed
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_TAG_AUTOCOMPLETE_OPTIONS ] = TagAutocompleteOptions
2020-10-21 22:22:10 +00:00
class TagDisplayMaintenanceManager( object ):
def __init__( self, controller ):
self._controller = controller
self._service_keys_to_needs_work = {}
self._go_faster = set()
self._last_loop_work_time = 0.5
self._shutdown = False
self._mainloop_finished = False
self._wake_event = threading.Event()
self._new_data_event = threading.Event()
2020-12-02 22:04:38 +00:00
self._last_last_new_data_event_time = 0
self._last_new_data_event_time = 0
2020-10-21 22:22:10 +00:00
self._lock = threading.Lock()
self._controller.sub( self, 'Shutdown', 'shutdown' )
self._controller.sub( self, 'NotifyNewDisplayData', 'notify_new_tag_display_application' )
def _GetAfterWorkWaitTime( self, service_key, expected_work_time, actual_work_time ):
2020-10-21 22:22:10 +00:00
with self._lock:
if service_key in self._go_faster:
if service_key in self._service_keys_to_needs_work and not self._service_keys_to_needs_work[ service_key ]:
self._go_faster.discard( service_key )
return 0.1
if self._controller.CurrentlyIdle():
return 0.5
else:
2022-07-13 21:35:17 +00:00
if actual_work_time > expected_work_time * 10:
# if suddenly a job blats the user for ten seconds or _ten minutes_ during normal time, we are going to take a big break
2022-07-13 21:35:17 +00:00
work_rest_ratio = 30
else:
2022-07-13 21:35:17 +00:00
work_rest_ratio = 9
2020-10-21 22:22:10 +00:00
2022-07-13 21:35:17 +00:00
return max( actual_work_time, expected_work_time ) * work_rest_ratio
2020-10-21 22:22:10 +00:00
def _GetServiceKeyToWorkOn( self ):
if len( self._go_faster ) > 0:
service_keys_that_need_work = list( self._go_faster )
else:
service_keys_that_need_work = [ service_key for ( service_key, needs_work ) in self._service_keys_to_needs_work.items() if needs_work ]
if len( service_keys_that_need_work ) == 0:
raise HydrusExceptions.NotFoundException( 'No service keys need work!' )
( service_key, ) = random.sample( service_keys_that_need_work, 1 )
return service_key
def _GetWorkTime( self, service_key ):
with self._lock:
if service_key in self._go_faster:
2020-12-02 22:04:38 +00:00
ideally = 30
2020-10-21 22:22:10 +00:00
base = max( 0.5, self._last_loop_work_time )
accelerating_time = min( base * 1.2, ideally )
return accelerating_time
if self._controller.CurrentlyIdle():
return 15
else:
2022-07-13 21:35:17 +00:00
return 0.1
2020-10-21 22:22:10 +00:00
def _WorkPermitted( self ):
if len( self._go_faster ) > 0:
return True
2020-12-02 22:04:38 +00:00
# we are getting new display data pretty fast. if it is streaming in, let's take a break
if not HydrusData.TimeHasPassed( self._last_last_new_data_event_time + 10 ):
return False
2020-10-21 22:22:10 +00:00
if self._controller.CurrentlyIdle():
if self._controller.new_options.GetBoolean( 'tag_display_maintenance_during_idle' ):
return True
else:
if self._controller.new_options.GetBoolean( 'tag_display_maintenance_during_active' ):
return True
return False
def _WorkToDo( self ):
can_do_work = False
service_keys = self._controller.services_manager.GetServiceKeys( HC.REAL_TAG_SERVICES )
for service_key in service_keys:
if service_key not in self._service_keys_to_needs_work:
status = self._controller.Read( 'tag_display_maintenance_status', service_key )
2021-08-04 21:59:55 +00:00
work_to_do = status[ 'num_siblings_to_sync' ] + status[ 'num_parents_to_sync' ] > 0
sync_halted = len( status[ 'waiting_on_tag_repos' ] ) > 0
self._service_keys_to_needs_work[ service_key ] = work_to_do and not sync_halted
2020-10-21 22:22:10 +00:00
if self._service_keys_to_needs_work[ service_key ]:
can_do_work = True
return can_do_work
def CurrentlyGoingFaster( self, service_key ):
with self._lock:
return service_key in self._go_faster
def FlipSyncFaster( self, service_key ):
with self._lock:
if service_key in self._go_faster:
self._go_faster.discard( service_key )
else:
self._go_faster.add( service_key )
self._controller.pub( 'notify_new_tag_display_sync_status', service_key )
self.Wake()
2022-04-06 20:40:17 +00:00
def GetName( self ):
2023-01-04 22:22:08 +00:00
return 'tag display sync'
2022-04-06 20:40:17 +00:00
2020-10-21 22:22:10 +00:00
def IsShutdown( self ):
return self._mainloop_finished
def MainLoop( self ):
try:
INIT_WAIT = 10
self._wake_event.wait( INIT_WAIT )
2022-01-19 21:28:59 +00:00
while not ( HG.started_shutdown or self._shutdown ):
2020-10-21 22:22:10 +00:00
2021-11-24 21:59:58 +00:00
self._controller.WaitUntilViewFree()
2020-10-21 22:22:10 +00:00
if self._WorkPermitted() and self._WorkToDo():
try:
service_key = self._GetServiceKeyToWorkOn()
except HydrusExceptions.NotFoundException:
2023-01-04 22:22:08 +00:00
self._wake_event.wait( 5 )
self._wake_event.clear()
2020-10-21 22:22:10 +00:00
continue
work_time = self._GetWorkTime( service_key )
start_time = HydrusData.GetNowPrecise()
2020-10-21 22:22:10 +00:00
still_needs_work = self._controller.WriteSynchronous( 'sync_tag_display_maintenance', service_key, work_time )
finish_time = HydrusData.GetNowPrecise()
total_time_took = finish_time - start_time
2020-10-21 22:22:10 +00:00
self._service_keys_to_needs_work[ service_key ] = still_needs_work
wait_time = self._GetAfterWorkWaitTime( service_key, work_time, total_time_took )
2020-10-21 22:22:10 +00:00
self._last_loop_work_time = work_time
else:
wait_time = 10
self._wake_event.wait( wait_time )
self._wake_event.clear()
if self._new_data_event.is_set():
2020-12-02 22:04:38 +00:00
time.sleep( 1 )
self._last_last_new_data_event_time = self._last_new_data_event_time
self._last_new_data_event_time = HydrusData.GetNow()
2020-10-21 22:22:10 +00:00
self._service_keys_to_needs_work = {}
self._new_data_event.clear()
finally:
self._mainloop_finished = True
def NotifyNewDisplayData( self ):
self._new_data_event.set()
self.Wake()
def Shutdown( self ):
self._shutdown = True
self.Wake()
def Start( self ):
self._controller.CallToThreadLongRunning( self.MainLoop )
def Wake( self ):
self._wake_event.set()
class TagDisplayManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_TAG_DISPLAY_MANAGER
SERIALISABLE_NAME = 'Tag Display Manager'
2020-08-19 22:38:20 +00:00
SERIALISABLE_VERSION = 4
def __init__( self ):
HydrusSerialisable.SerialisableBase.__init__( self )
2021-04-07 21:26:45 +00:00
service_keys_to_tag_filters_defaultdict = lambda: collections.defaultdict( HydrusTags.TagFilter )
self._tag_display_types_to_service_keys_to_tag_filters = collections.defaultdict( service_keys_to_tag_filters_defaultdict )
self._tag_service_keys_to_tag_autocomplete_options = dict()
self._lock = threading.Lock()
self._dirty = False
def _GetSerialisableInfo( self ):
serialisable_tag_display_types_to_service_keys_to_tag_filters = []
for ( tag_display_type, service_keys_to_tag_filters ) in self._tag_display_types_to_service_keys_to_tag_filters.items():
serialisable_service_keys_to_tag_filters = [ ( service_key.hex(), tag_filter.GetSerialisableTuple() ) for ( service_key, tag_filter ) in service_keys_to_tag_filters.items() ]
serialisable_tag_display_types_to_service_keys_to_tag_filters.append( ( tag_display_type, serialisable_service_keys_to_tag_filters ) )
serialisable_tag_autocomplete_options = HydrusSerialisable.SerialisableList( self._tag_service_keys_to_tag_autocomplete_options.values() ).GetSerialisableTuple()
serialisable_info = [
serialisable_tag_display_types_to_service_keys_to_tag_filters,
2020-08-19 22:38:20 +00:00
serialisable_tag_autocomplete_options
]
return serialisable_info
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
[
serialisable_tag_display_types_to_service_keys_to_tag_filters,
2020-08-19 22:38:20 +00:00
serialisable_tag_autocomplete_options
] = serialisable_info
for ( tag_display_type, serialisable_service_keys_to_tag_filters ) in serialisable_tag_display_types_to_service_keys_to_tag_filters:
for ( serialisable_service_key, serialisable_tag_filter ) in serialisable_service_keys_to_tag_filters:
service_key = bytes.fromhex( serialisable_service_key )
tag_filter = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_tag_filter )
self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ] = tag_filter
self._tag_service_keys_to_tag_autocomplete_options = { tag_autocomplete_options.GetServiceKey() : tag_autocomplete_options for tag_autocomplete_options in HydrusSerialisable.CreateFromSerialisableTuple( serialisable_tag_autocomplete_options ) }
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
serialisable_tag_display_types_to_service_keys_to_tag_filters = old_serialisable_info
tag_autocomplete_options_list = HydrusSerialisable.SerialisableList()
new_serialisable_info = [
serialisable_tag_display_types_to_service_keys_to_tag_filters,
tag_autocomplete_options_list.GetSerialisableTuple()
]
return ( 2, new_serialisable_info )
if version == 2:
[
serialisable_tag_display_types_to_service_keys_to_tag_filters,
serialisable_tag_autocomplete_options
] = old_serialisable_info
service_keys_to_ordered_sibling_service_keys = collections.defaultdict( list )
service_keys_to_ordered_parent_service_keys = collections.defaultdict( list )
serialisable_service_keys_to_ordered_sibling_service_keys = HydrusSerialisable.SerialisableBytesDictionary( service_keys_to_ordered_sibling_service_keys ).GetSerialisableTuple()
serialisable_service_keys_to_ordered_parent_service_keys = HydrusSerialisable.SerialisableBytesDictionary( service_keys_to_ordered_parent_service_keys ).GetSerialisableTuple()
new_serialisable_info = [
serialisable_tag_display_types_to_service_keys_to_tag_filters,
serialisable_tag_autocomplete_options,
serialisable_service_keys_to_ordered_sibling_service_keys,
serialisable_service_keys_to_ordered_parent_service_keys
]
return ( 3, new_serialisable_info )
2020-08-19 22:38:20 +00:00
if version == 3:
# took it out again lmao, down to the db
2020-08-19 22:38:20 +00:00
[
serialisable_tag_display_types_to_service_keys_to_tag_filters,
serialisable_tag_autocomplete_options,
serialisable_service_keys_to_ordered_sibling_service_keys,
serialisable_service_keys_to_ordered_parent_service_keys
] = old_serialisable_info
new_serialisable_info = [
serialisable_tag_display_types_to_service_keys_to_tag_filters,
serialisable_tag_autocomplete_options
]
2020-08-19 22:38:20 +00:00
return ( 4, new_serialisable_info )
def ClearTagDisplayOptions( self ):
with self._lock:
2021-04-07 21:26:45 +00:00
service_keys_to_tag_filters_defaultdict = lambda: collections.defaultdict( HydrusTags.TagFilter )
self._tag_display_types_to_service_keys_to_tag_filters = collections.defaultdict( service_keys_to_tag_filters_defaultdict )
self._tag_service_keys_to_tag_autocomplete_options = dict()
def SetClean( self ):
with self._lock:
self._dirty = False
def SetDirty( self ):
with self._lock:
self._dirty = True
def FilterTags( self, tag_display_type, service_key, tags ):
with self._lock:
if service_key in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
tag_filter = self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ]
tags = tag_filter.Filter( tags )
if service_key != CC.COMBINED_TAG_SERVICE_KEY and CC.COMBINED_TAG_SERVICE_KEY in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
tag_filter = self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ CC.COMBINED_TAG_SERVICE_KEY ]
tags = tag_filter.Filter( tags )
return tags
def FiltersTags( self, tag_display_type, service_key ):
with self._lock:
if service_key in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
return True
if service_key != CC.COMBINED_TAG_SERVICE_KEY and CC.COMBINED_TAG_SERVICE_KEY in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
return True
return False
def GetTagAutocompleteOptions( self, service_key: bytes ):
with self._lock:
if service_key not in self._tag_service_keys_to_tag_autocomplete_options:
tag_autocomplete_options = TagAutocompleteOptions( service_key )
self._tag_service_keys_to_tag_autocomplete_options[ service_key ] = tag_autocomplete_options
return self._tag_service_keys_to_tag_autocomplete_options[ service_key ]
def GetTagFilter( self, tag_display_type, service_key ):
with self._lock:
return self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ].Duplicate()
def HideTag( self, tag_display_type, service_key, tag ):
with self._lock:
tag_filter = self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ]
2021-04-07 21:26:45 +00:00
tag_filter.SetRule( tag, HC.FILTER_BLACKLIST )
self._dirty = True
def IsDirty( self ):
with self._lock:
return self._dirty
def SetTagAutocompleteOptions( self, tag_autocomplete_options: TagAutocompleteOptions ):
with self._lock:
self._tag_service_keys_to_tag_autocomplete_options[ tag_autocomplete_options.GetServiceKey() ] = tag_autocomplete_options
def SetTagFilter( self, tag_display_type, service_key, tag_filter ):
with self._lock:
if tag_filter.AllowsEverything():
if service_key in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
del self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ]
self._dirty = True
else:
self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ] = tag_filter
self._dirty = True
def TagOK( self, tag_display_type, service_key, tag ):
return len( self.FilterTags( tag_display_type, service_key, ( tag, ) ) ) > 0
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_TAG_DISPLAY_MANAGER ] = TagDisplayManager
2020-10-21 22:22:10 +00:00
class TagParentsStructure( object ):
def __init__( self ):
2020-12-02 22:04:38 +00:00
self._descendants_to_ancestors = collections.defaultdict( set )
self._ancestors_to_descendants = collections.defaultdict( set )
2020-10-21 22:22:10 +00:00
# some sort of structure for 'bad cycles' so we can later raise these to the user to fix
def AddPair( self, child: object, parent: object ):
# disallowed parents are:
# A -> A
# larger loops
if child == parent:
return
2020-12-02 22:04:38 +00:00
if parent in self._descendants_to_ancestors:
2020-10-21 22:22:10 +00:00
2020-12-02 22:04:38 +00:00
if child in self._descendants_to_ancestors[ parent ]:
2020-10-21 22:22:10 +00:00
# this is a loop!
return
2020-12-02 22:04:38 +00:00
if child in self._descendants_to_ancestors and parent in self._descendants_to_ancestors[ child ]:
2020-10-21 22:22:10 +00:00
# already in
return
2020-12-02 22:04:38 +00:00
# let's now gather all ancestors of parent and all descendants of child
2020-10-21 22:22:10 +00:00
new_ancestors = { parent }
2020-12-02 22:04:38 +00:00
if parent in self._descendants_to_ancestors:
2020-10-21 22:22:10 +00:00
2020-12-02 22:04:38 +00:00
new_ancestors.update( self._descendants_to_ancestors[ parent ] )
2020-10-21 22:22:10 +00:00
new_descendants = { child }
2020-12-02 22:04:38 +00:00
if child in self._ancestors_to_descendants:
2020-10-21 22:22:10 +00:00
2020-12-02 22:04:38 +00:00
new_descendants.update( self._ancestors_to_descendants[ child ] )
2020-10-21 22:22:10 +00:00
# every (grand)parent now gets all new (grand)kids
for ancestor in new_ancestors:
2020-12-02 22:04:38 +00:00
self._ancestors_to_descendants[ ancestor ].update( new_descendants )
2020-10-21 22:22:10 +00:00
# every (grand)kid now gets all new (grand)parents
for descendant in new_descendants:
2020-12-02 22:04:38 +00:00
self._descendants_to_ancestors[ descendant ].update( new_ancestors )
2020-10-21 22:22:10 +00:00
def GetTagsToAncestors( self ):
2020-12-02 22:04:38 +00:00
return self._descendants_to_ancestors
2020-10-21 22:22:10 +00:00
def IterateDescendantAncestorPairs( self ):
2020-12-02 22:04:38 +00:00
for ( descandant, ancestors ) in self._descendants_to_ancestors.items():
2020-10-21 22:22:10 +00:00
for ancestor in ancestors:
yield ( descandant, ancestor )
class TagSiblingsStructure( object ):
def __init__( self ):
self._bad_tags_to_good_tags = {}
self._bad_tags_to_ideal_tags = {}
self._ideal_tags_to_all_worse_tags = collections.defaultdict( set )
# some sort of structure for 'bad cycles' so we can later raise these to the user to fix
def AddPair( self, bad_tag: object, good_tag: object ):
# disallowed siblings are:
# A -> A
# larger loops
# A -> C when A -> B already exists
if bad_tag == good_tag:
return
if bad_tag in self._bad_tags_to_good_tags:
return
joining_existing_chain = good_tag in self._bad_tags_to_ideal_tags
extending_existing_chain = bad_tag in self._ideal_tags_to_all_worse_tags
if extending_existing_chain and joining_existing_chain:
2020-08-19 22:38:20 +00:00
joined_chain_ideal = self._bad_tags_to_ideal_tags[ good_tag ]
if joined_chain_ideal == bad_tag:
# we found a cycle, as the ideal of the chain we are joining is our bad tag
# basically the chain we are joining and the chain we are extending are the same one
return
# now compute our ideal
ideal_tags_that_need_updating = set()
if joining_existing_chain:
# our ideal will be the end of that chain
ideal_tag = self._bad_tags_to_ideal_tags[ good_tag ]
else:
ideal_tag = good_tag
self._bad_tags_to_good_tags[ bad_tag ] = good_tag
self._bad_tags_to_ideal_tags[ bad_tag ] = ideal_tag
self._ideal_tags_to_all_worse_tags[ ideal_tag ].add( bad_tag )
if extending_existing_chain:
# the existing chain needs its ideal updating
old_ideal_tag = bad_tag
bad_tags_that_need_updating = self._ideal_tags_to_all_worse_tags[ old_ideal_tag ]
for bad_tag_that_needs_updating in bad_tags_that_need_updating:
self._bad_tags_to_ideal_tags[ bad_tag_that_needs_updating ] = ideal_tag
self._ideal_tags_to_all_worse_tags[ ideal_tag ].update( bad_tags_that_need_updating )
del self._ideal_tags_to_all_worse_tags[ old_ideal_tag ]
def GetBadTagsToIdealTags( self ):
return self._bad_tags_to_ideal_tags
2020-10-21 22:22:10 +00:00