Version 71

This commit is contained in:
Hydrus 2013-05-29 15:19:54 -05:00
parent f8117fe392
commit bcfce756b7
22 changed files with 1491 additions and 1200 deletions

View File

@ -8,6 +8,28 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 71</h3></li>
<ul>
<li>collapsed the four mappings tables into two tables</li>
<li>merged the two active_mappings tables into the mappings table</li>
<li>made a _great_ number of changes to how mappings and active_mappings are stored and processed throughout</li>
<li>'active' and 'null' nomenclature is now 'combined'; null service_ids are now just ints</li>
<li>improved deletepending so it isn't so tough on the a/c cache</li>
<li>tags regex dialog entries 'for all files' and 'just for this file' was all broke</li>
<li>A/C read now bumps the exact match of the entry to the top of the list, if its count is non-zero</li>
<li>fixed A/C for weird-character queries, like '['</li>
<li>the dumper now makes success/error noises as appropriate</li>
<li>you can turn these noises off in the new sound tab in file->options</li>
<li>screwed around with garbage collection while checking mimetypes during pre-import</li>
<li>adding a tag parent will spam-add the actual parent tags to every child instance for the appropriate service</li>
<li>updated db diagrams</li>
<li>revised my sibling chain collapsing algorithm</li>
<li>locked db on init dialog message is improved a little</li>
<li>system:limit added to combined file service searches</li>
<li>num pending menu counts now split into with (pending/petitioned)</li>
<li>corrected a server db index oversight</li>
<li>newgrounds title tag fixed</li>
</ul>
<li><h3>version 70</h3></li>
<ul>
<li>tag parents db stuff</li>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 134 KiB

View File

@ -316,14 +316,14 @@ def GetFilePath( hash, mime ):
return HC.CLIENT_FILES_DIR + os.path.sep + first_two_chars + os.path.sep + hash_encoded + HC.mime_ext_lookup[ mime ]
def GetMediasTagCount( pool, tag_service_identifier = HC.NULL_SERVICE_IDENTIFIER ):
def GetMediasTagCount( pool, tag_service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER ):
all_tags = []
tags_managers = []
for media in pool:
if media.IsCollection(): all_tags.extend( media.GetSingletonsTags() )
else: all_tags.append( media.GetTags() )
if media.IsCollection(): tags_managers.extend( media.GetSingletonsTagsManagers() )
else: tags_managers.append( media.GetTagsManager() )
current_tags_to_count = collections.Counter()
@ -331,15 +331,16 @@ def GetMediasTagCount( pool, tag_service_identifier = HC.NULL_SERVICE_IDENTIFIER
pending_tags_to_count = collections.Counter()
petitioned_tags_to_count = collections.Counter()
for tags in all_tags:
for tags_manager in tags_managers:
if tag_service_identifier == HC.NULL_SERVICE_IDENTIFIER: ( current, deleted, pending, petitioned ) = tags.GetUnionCDPP()
else: ( current, deleted, pending, petitioned ) = tags.GetCDPP( tag_service_identifier )
statuses_to_tags = tags_manager.GetStatusesToTags( tag_service_identifier )
current_tags_to_count.update( current )
deleted_tags_to_count.update( pending )
pending_tags_to_count.update( pending )
petitioned_tags_to_count.update( petitioned )
current_tags_to_count.update( statuses_to_tags[ HC.CURRENT ] )
deleted_tags_to_count.update( statuses_to_tags[ HC.DELETED ] )
deleted_tags_to_count.update( statuses_to_tags[ HC.DELETED_PENDING ] )
pending_tags_to_count.update( statuses_to_tags[ HC.DELETED_PENDING ] )
pending_tags_to_count.update( statuses_to_tags[ HC.PENDING ] )
petitioned_tags_to_count.update( statuses_to_tags[ HC.PETITIONED ] )
return ( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count )
@ -380,17 +381,47 @@ def IterateAllThumbnailPaths():
for path in next_paths: yield dir + os.path.sep + path
def MediaIntersectCDPPTagServiceIdentifiers( media, service_identifier ):
def IntersectTags( tags_managers, service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER ):
all_tag_cdpps = [ m.GetTags().GetCDPP( service_identifier ) for m in media ]
current = list( HC.IntelligentMassIntersect( ( cdpp[0] for cdpp in all_tag_cdpps ) ) )
deleted = list( HC.IntelligentMassIntersect( ( cdpp[1] for cdpp in all_tag_cdpps ) ) )
pending = list( HC.IntelligentMassIntersect( ( cdpp[2] for cdpp in all_tag_cdpps ) ) )
petitioned = list( HC.IntelligentMassIntersect( ( cdpp[3] for cdpp in all_tag_cdpps ) ) )
current = list( HC.IntelligentMassIntersect( ( tags_manager.GetCurrent( service_identifier ) for tags_manager in tags_managers ) ) )
deleted = list( HC.IntelligentMassIntersect( ( tags_manager.GetDeleted( service_identifier ) for tags_manager in tags_managers ) ) )
pending = list( HC.IntelligentMassIntersect( ( tags_manager.GetPending( service_identifier ) for tags_manager in tags_managers ) ) )
petitioned = list( HC.IntelligentMassIntersect( ( tags_manager.GetPetitioned( service_identifier ) for tags_manager in tags_managers ) ) )
return ( current, deleted, pending, petitioned )
def MergeTags( tags_managers ):
# [[( s_i, s_t_t )]]
s_i_s_t_t_tupled = ( tags_manager.GetServiceIdentifiersToStatusesToTags().items() for tags_manager in tags_managers )
# [(s_i, s_t_t)]
flattened_s_i_s_t_t = itertools.chain.from_iterable( s_i_s_t_t_tupled )
# s_i : [s_t_t]
s_i_s_t_t_dict = HC.BuildKeyToListDict( flattened_s_i_s_t_t )
# now let's merge so we have s_i : s_t_t
merged_tags = {}
for ( service_identifier, several_statuses_to_tags ) in s_i_s_t_t_dict.items():
# [[( status, tags )]]
s_t_t_tupled = ( s_t_t.items() for s_t_t in several_statuses_to_tags )
# [( status, tags )]
flattened_s_t_t = itertools.chain.from_iterable( s_t_t_tupled )
statuses_to_tags = collections.defaultdict( set )
for ( status, tags ) in flattened_s_t_t: statuses_to_tags[ status ].update( tags )
merged_tags[ service_identifier ] = statuses_to_tags
return TagsManager( wx.GetApp().Read( 'tag_service_precedence' ), merged_tags )
def ParseImportablePaths( raw_paths, include_subdirs = True ):
file_paths = []
@ -450,6 +481,8 @@ def ParseImportablePaths( raw_paths, include_subdirs = True ):
progress.Destroy()
gc.collect()
good_paths_info = []
odd_paths = []
@ -459,6 +492,8 @@ def ParseImportablePaths( raw_paths, include_subdirs = True ):
for ( i, path ) in enumerate( file_paths ):
if i % 500 == 0: gc.collect()
( should_continue, skip ) = progress.Update( i, 'Done ' + str( i ) + '/' + str( num_file_paths ) )
if not should_continue: break
@ -860,217 +895,6 @@ class CDPPFileServiceIdentifiers():
self._petitioned.discard( service_identifier )
class CDPPTagServiceIdentifiers():
def __init__( self, tag_service_precedence, service_identifiers_to_cdpp ):
self._tag_service_precedence = tag_service_precedence
self._service_identifiers_to_cdpp = service_identifiers_to_cdpp
self._Recalc()
def _Recalc( self ):
self._current = set()
self._deleted = set()
self._pending = set()
self._petitioned = set()
t_s_p = list( self._tag_service_precedence )
t_s_p.reverse()
for service_identifier in t_s_p:
if service_identifier in self._service_identifiers_to_cdpp:
( current, deleted, pending, petitioned ) = self._service_identifiers_to_cdpp[ service_identifier ]
# the difference_update stuff is making active_mappings from tag_service_precedence
self._current.update( current )
self._current.difference_update( deleted )
self._deleted.update( deleted )
self._deleted.difference_update( current )
self._pending.update( pending )
self._pending.difference_update( deleted )
self._petitioned.update( petitioned )
self._petitioned.difference_update( current )
self._creators = set()
self._series = set()
self._titles = set()
self._volumes = set()
self._chapters = set()
self._pages = set()
for tag in self._current | self._pending:
if tag.startswith( 'creator:' ): self._creators.add( tag.split( 'creator:', 1 )[1] )
elif tag.startswith( 'series:' ): self._series.add( tag.split( 'series:', 1 )[1] )
elif tag.startswith( 'title:' ): self._titles.add( tag.split( 'title:', 1 )[1] )
elif tag.startswith( 'volume:' ): self._volumes.add( int( tag.split( 'volume:', 1 )[1] ) )
elif tag.startswith( 'chapter:' ): self._chapters.add( int( tag.split( 'chapter:', 1 )[1] ) )
elif tag.startswith( 'page:' ): self._pages.add( int( tag.split( 'page:', 1 )[1] ) )
def GetCSTVCP( self ): return ( self._creators, self._series, self._titles, self._volumes, self._chapters, self._pages )
def DeletePending( self, service_identifier ):
if service_identifier in self._service_identifiers_to_cdpp:
( current, deleted, pending, petitioned ) = self._service_identifiers_to_cdpp[ service_identifier ]
if len( pending ) > 0 or len( petitioned ) > 0:
self._service_identifiers_to_cdpp[ service_identifier ] = ( current, deleted, set(), set() )
self._Recalc()
def GetCDPP( self, service_identifier ):
if service_identifier in self._service_identifiers_to_cdpp: return self._service_identifiers_to_cdpp[ service_identifier ]
else: return ( set(), set(), set(), set() )
def GetNamespaceSlice( self, namespaces ): return frozenset( [ tag for tag in list( self._current ) + list( self._pending ) if True in ( tag.startswith( namespace + ':' ) for namespace in namespaces ) ] )
def GetNumTags( self, tag_service_identifier, include_current_tags = True, include_pending_tags = False ):
num_tags = 0
if tag_service_identifier == HC.NULL_SERVICE_IDENTIFIER:
if include_current_tags: num_tags += len( self._current )
if include_pending_tags: num_tags += len( self._pending )
else:
( current, deleted, pending, petitioned ) = self.GetCDPP( tag_service_identifier )
if include_current_tags: num_tags += len( current )
if include_pending_tags: num_tags += len( pending )
return num_tags
def GetServiceIdentifiersToCDPP( self ): return self._service_identifiers_to_cdpp
def GetUnionCDPP( self ): return ( self._current, self._deleted, self._pending, self._petitioned )
def HasTag( self, tag ): return tag in self._current or tag in self._pending
def ProcessContentUpdate( self, content_update ):
service_identifier = content_update.GetServiceIdentifier()
if service_identifier in self._service_identifiers_to_cdpp: ( current, deleted, pending, petitioned ) = self._service_identifiers_to_cdpp[ service_identifier ]
else:
( current, deleted, pending, petitioned ) = ( set(), set(), set(), set() )
self._service_identifiers_to_cdpp[ service_identifier ] = ( current, deleted, pending, petitioned )
action = content_update.GetAction()
if action == HC.CONTENT_UPDATE_ADD:
tag = content_update.GetInfo()
current.add( tag )
deleted.discard( tag )
pending.discard( tag )
elif action == HC.CONTENT_UPDATE_DELETE:
tag = content_update.GetInfo()
deleted.add( tag )
current.discard( tag )
petitioned.discard( tag )
elif action == HC.CONTENT_UPDATE_EDIT_LOG:
edit_log = content_update.GetInfo()
for ( action, info ) in edit_log:
if action == HC.CONTENT_UPDATE_ADD:
tag = info
current.add( tag )
deleted.discard( tag )
pending.discard( tag )
elif action == HC.CONTENT_UPDATE_DELETE:
tag = info
deleted.add( tag )
current.discard( tag )
petitioned.discard( tag )
elif action == HC.CONTENT_UPDATE_PENDING:
tag = info
if tag not in current: pending.add( tag )
elif action == HC.CONTENT_UPDATE_RESCIND_PENDING:
tag = info
pending.discard( tag )
elif action == HC.CONTENT_UPDATE_PETITION:
( tag, reason ) = info
if tag not in deleted: petitioned.add( tag )
elif action == HC.CONTENT_UPDATE_RESCIND_PETITION:
tag = info
petitioned.discard( tag )
self._Recalc()
def ResetService( self, service_identifier ):
if service_identifier in self._service_identifiers_to_cdpp:
( current, deleted, pending, petitioned ) = self._service_identifiers_to_cdpp[ service_identifier ]
self._service_identifiers_to_cdpp[ service_identifier ] = ( set(), set(), pending, set() )
self._Recalc()
class LocalRatings():
# c for current; feel free to rename this stupid thing
@ -1482,6 +1306,7 @@ class FileQueryResult():
HC.pubsub.sub( self, 'ProcessContentUpdates', 'content_updates_data' )
HC.pubsub.sub( self, 'ProcessServiceUpdate', 'service_update_data' )
HC.pubsub.sub( self, 'RecalcCombinedTags', 'new_tag_service_precedence' )
def __iter__( self ):
@ -1574,7 +1399,7 @@ class FileQueryResult():
class FileSearchContext():
def __init__( self, file_service_identifier = HC.LOCAL_FILE_SERVICE_IDENTIFIER, tag_service_identifier = HC.NULL_SERVICE_IDENTIFIER, include_current_tags = True, include_pending_tags = True, predicates = [] ):
def __init__( self, file_service_identifier = HC.LOCAL_FILE_SERVICE_IDENTIFIER, tag_service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER, include_current_tags = True, include_pending_tags = True, predicates = [] ):
self._file_service_identifier = file_service_identifier
self._tag_service_identifier = tag_service_identifier
@ -2145,7 +1970,7 @@ class MediaResult():
def __init__( self, tuple ):
# hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tag_service_identifiers_cdpp, file_service_identifiers_cdpp, local_ratings, remote_ratings
# hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tags_manager, file_service_identifiers_cdpp, local_ratings, remote_ratings
self._tuple = tuple
@ -2154,9 +1979,9 @@ class MediaResult():
service_type = service_identifier.GetType()
( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tag_service_identifiers_cdpp, file_service_identifiers_cdpp, local_ratings, remote_ratings ) = self._tuple
( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tags_manager, file_service_identifiers_cdpp, local_ratings, remote_ratings ) = self._tuple
if service_type == HC.TAG_REPOSITORY: tag_service_identifiers_cdpp.DeletePending( service_identifier )
if service_type == HC.TAG_REPOSITORY: tags_manager.DeletePending( service_identifier )
elif service_type in ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ): file_service_identifiers_cdpp.DeletePending( service_identifier )
@ -2180,7 +2005,7 @@ class MediaResult():
def GetSize( self ): return self._tuple[2]
def GetTags( self ): return self._tuple[10]
def GetTagsManager( self ): return self._tuple[10]
def GetTimestamp( self ): return self._tuple[4]
@ -2188,13 +2013,13 @@ class MediaResult():
def ProcessContentUpdate( self, content_update ):
( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tag_service_identifiers_cdpp, file_service_identifiers_cdpp, local_ratings, remote_ratings ) = self._tuple
( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tags_manager, file_service_identifiers_cdpp, local_ratings, remote_ratings ) = self._tuple
service_identifier = content_update.GetServiceIdentifier()
service_type = service_identifier.GetType()
if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ): tag_service_identifiers_cdpp.ProcessContentUpdate( content_update )
if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ): tags_manager.ProcessContentUpdate( content_update )
elif service_type in ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ):
if service_type == HC.LOCAL_FILE:
@ -2206,7 +2031,7 @@ class MediaResult():
elif action == HC.CONTENT_UPDATE_INBOX: inbox = True
elif action == HC.CONTENT_UPDATE_DELETE: inbox = False
self._tuple = ( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tag_service_identifiers_cdpp, file_service_identifiers_cdpp, local_ratings, remote_ratings )
self._tuple = ( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tags_manager, file_service_identifiers_cdpp, local_ratings, remote_ratings )
file_service_identifiers_cdpp.ProcessContentUpdate( content_update )
@ -2222,9 +2047,9 @@ class MediaResult():
service_type = service_identifier.GetType()
( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tag_service_identifiers_cdpp, file_service_identifiers_cdpp, local_ratings, remote_ratings ) = self._tuple
( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tags_manager, file_service_identifiers_cdpp, local_ratings, remote_ratings ) = self._tuple
if service_type == HC.TAG_REPOSITORY: tag_service_identifiers_cdpp.ResetService( service_identifier )
if service_type in ( HC.TAG_REPOSITORY, HC.COMBINED_TAG ): tags_manager.ResetService( service_identifier )
elif service_type == HC.FILE_REPOSITORY: file_service_identifiers_cdpp.ResetService( service_identifier )
@ -2644,6 +2469,324 @@ class ServiceRemoteRestrictedDepotMessage( ServiceRemoteRestrictedDepot ):
def Decrypt( self, encrypted_message ): return HydrusMessageHandling.UnpackageDeliveredMessage( encrypted_message, self._private_key )
class TagsManager():
def __init__( self, tag_service_precedence, service_identifiers_to_statuses_to_tags ):
self._tag_service_precedence = tag_service_precedence
self._service_identifiers_to_statuses_to_tags = service_identifiers_to_statuses_to_tags
self._cstvcp_initialised = False
def _RecalcCombined( self ):
t_s_p = list( self._tag_service_precedence )
t_s_p.reverse()
combined_current = set()
combined_pending = set()
for service_identifier in t_s_p:
if service_identifier in self._service_identifiers_to_statuses_to_tags:
statuses_to_tags = self._service_identifiers_to_statuses_to_tags[ service_identifier ]
combined_current.update( statuses_to_tags[ HC.CURRENT ] )
combined_current.difference_update( statuses_to_tags[ HC.DELETED ] )
combined_current.difference_update( statuses_to_tags[ HC.DELETED_PENDING ] )
combined_pending.update( statuses_to_tags[ HC.DELETED_PENDING ] )
combined_pending.update( statuses_to_tags[ HC.PENDING ] )
combined_statuses_to_tags = collections.defaultdict( set )
combined_statuses_to_tags[ HC.CURRENT ] = combined_current
combined_statuses_to_tags[ HC.PENDING ] = combined_pending
self._service_identifiers_to_statuses_to_tags[ HC.COMBINED_TAG_SERVICE_IDENTIFIER ] = combined_statuses_to_tags
if self._cstvcp_initialised: self._RecalcCSTVCP()
def _RecalcCSTVCP( self ):
self._creators = set()
self._series = set()
self._titles = set()
self._volumes = set()
self._chapters = set()
self._pages = set()
if HC.COMBINED_TAG_SERVICE_IDENTIFIER in self._service_identifiers_to_statuses_to_tags:
combined_statuses_to_tags = self._service_identifiers_to_statuses_to_tags[ HC.COMBINED_TAG_SERVICE_IDENTIFIER ]
combined_current = combined_statuses_to_tags[ HC.CURRENT ]
combined_pending = combined_statuses_to_tags[ HC.PENDING ]
for tag in combined_current.union( combined_pending ):
if ':' in tag:
( namespace, tag ) = tag.split( ':', 1 )
if namespace == 'creator': self._creators.add( tag )
elif namespace == 'series': self._series.add( tag )
elif namespace == 'title': self._titles.add( tag )
elif namespace in ( 'volume', 'chapter', 'page' ):
try: tag = int( tag )
except: pass
if namespace == 'volume': self._volumes.add( tag )
elif namespace == 'chapter': self._chapters.add( tag )
elif namespace == 'page': self._pages.add( tag )
self._cstvcp_initialised = True
def GetCSTVCP( self ):
if not self._cstvcp_initialised: self._RecalcCSTVCP()
return ( self._creators, self._series, self._titles, self._volumes, self._chapters, self._pages )
def DeletePending( self, service_identifier ):
if service_identifier in self._service_identifiers_to_statuses_to_tags:
statuses_to_tags = self._service_identifiers_to_statuses_to_tags[ service_identifier ]
if len( statuses_to_tags[ HC.PENDING ] ) + len( statuses_to_tags[ HC.DELETED_PENDING ] ) + len( statuses_to_tags[ HC.PETITIONED ] ) > 0:
statuses_to_tags[ HC.DELETED ].update( statuses_to_tags[ HC.DELETED_PENDING ] )
statuses_to_tags[ HC.PENDING ] = set()
statuses_to_tags[ HC.DELETED_PENDING ] = set()
statuses_to_tags[ HC.PETITIONED ] = set()
self._RecalcCombined()
def GetCurrent( self, service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER ):
if service_identifier in self._service_identifiers_to_statuses_to_tags:
statuses_to_tags = self._service_identifiers_to_statuses_to_tags[ service_identifier ]
return set( statuses_to_tags[ HC.CURRENT ] )
else: return set()
def GetDeleted( self, service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER ):
if service_identifier in self._service_identifiers_to_statuses_to_tags:
statuses_to_tags = self._service_identifiers_to_statuses_to_tags[ service_identifier ]
return statuses_to_tags[ HC.DELETED ].union( statuses_to_tags[ HC.DELETED_PENDING ] )
else: return set()
def GetNamespaceSlice( self, namespaces ):
if HC.COMBINED_TAG_SERVICE_IDENTIFIER in self._service_identifiers_to_statuses_to_tags:
combined_statuses_to_tags = self._service_identifiers_to_statuses_to_tags[ HC.COMBINED_TAG_SERVICE_IDENTIFIER ]
combined_current = combined_statuses_to_tags[ HC.CURRENT ]
combined_pending = combined_statuses_to_tags[ HC.PENDING ]
return frozenset( [ tag for tag in list( combined_current ) + list( combined_pending ) if True in ( tag.startswith( namespace + ':' ) for namespace in namespaces ) ] )
else: return frozenset()
def GetNumTags( self, tag_service_identifier, include_current_tags = True, include_pending_tags = False ):
num_tags = 0
statuses_to_tags = self.GetStatusesToTags( tag_service_identifier )
if include_current_tags: num_tags += len( statuses_to_tags[ HC.CURRENT ] )
if include_pending_tags: num_tags += len( statuses_to_tags[ HC.DELETED_PENDING ] ) + len( statuses_to_tags[ HC.PENDING ] )
return num_tags
def GetPending( self, service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER ):
if service_identifier in self._service_identifiers_to_statuses_to_tags:
statuses_to_tags = self._service_identifiers_to_statuses_to_tags[ service_identifier ]
return statuses_to_tags[ HC.DELETED_PENDING ].union( statuses_to_tags[ HC.PENDING ] )
else: return set()
def GetPetitioned( self, service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER ):
if service_identifier in self._service_identifiers_to_statuses_to_tags:
statuses_to_tags = self._service_identifiers_to_statuses_to_tags[ service_identifier ]
return set( statuses_to_tags[ HC.PETITIONED ] )
else: return set()
def GetServiceIdentifiersToStatusesToTags( self ): return self._service_identifiers_to_statuses_to_tags
def GetStatusesToTags( self, service_identifier ):
if service_identifier in self._service_identifiers_to_statuses_to_tags: return self._service_identifiers_to_statuses_to_tags[ service_identifier ]
else: return collections.defaultdict( set )
def HasTag( self, tag ):
if HC.COMBINED_TAG_SERVICE_IDENTIFIER in self._service_identifiers_to_statuses_to_tags:
combined_statuses_to_tags = self._service_identifiers_to_statuses_to_tags[ HC.COMBINED_TAG_SERVICE_IDENTIFIER ]
return tag in combined_statuses_to_tags[ HC.CURRENT ] or tag in combined_statuses_to_tags[ HC.PENDING ]
else: return False
def ProcessContentUpdate( self, content_update ):
service_identifier = content_update.GetServiceIdentifier()
if service_identifier not in self._service_identifiers_to_statuses_to_tags: self._service_identifiers_to_statuses_to_tags[ service_identifier ] = collections.defaultdict( set )
statuses_to_tags = self._service_identifiers_to_statuses_to_tags[ service_identifier ]
action = content_update.GetAction()
if action == HC.CONTENT_UPDATE_ADD:
tag = content_update.GetInfo()
statuses_to_tags[ HC.CURRENT ].add( tag )
statuses_to_tags[ HC.DELETED ].discard( tag )
statuses_to_tags[ HC.DELETED_PENDING ].discard( tag )
statuses_to_tags[ HC.PENDING ].discard( tag )
elif action == HC.CONTENT_UPDATE_DELETE:
tag = content_update.GetInfo()
if tag in statuses_to_tags[ HC.PENDING ]:
statuses_to_tags[ HC.DELETED_PENDING ].add( tag )
statuses_to_tags[ HC.PENDING ].discard( tag )
else:
statuses_to_tags[ HC.DELETED ].add( tag )
statuses_to_tags[ HC.CURRENT ].discard( tag )
statuses_to_tags[ HC.PETITIONED ].discard( tag )
elif action == HC.CONTENT_UPDATE_EDIT_LOG:
edit_log = content_update.GetInfo()
for ( action, info ) in edit_log:
if action == HC.CONTENT_UPDATE_ADD:
tag = info
statuses_to_tags[ HC.CURRENT ].add( tag )
statuses_to_tags[ HC.DELETED ].discard( tag )
statuses_to_tags[ HC.DELETED_PENDING ].discard( tag )
statuses_to_tags[ HC.PENDING ].discard( tag )
elif action == HC.CONTENT_UPDATE_DELETE:
tag = info
if tag in statuses_to_tags[ HC.PENDING ]:
statuses_to_tags[ HC.DELETED_PENDING ].add( tag )
statuses_to_tags[ HC.PENDING ].discard( tag )
else:
statuses_to_tags[ HC.DELETED ].add( tag )
statuses_to_tags[ HC.CURRENT ].discard( tag )
statuses_to_tags[ HC.PETITIONED ].discard( tag )
elif action == HC.CONTENT_UPDATE_PENDING:
tag = info
if tag in statuses_to_tags[ HC.DELETED ]:
statuses_to_tags[ HC.DELETED_PENDING ].add( tag )
statuses_to_tags[ HC.DELETED ].discard( tag )
else: statuses_to_tags[ HC.PENDING ].add( tag )
elif action == HC.CONTENT_UPDATE_RESCIND_PENDING:
tag = info
if tag in statuses_to_tags[ HC.DELETED_PENDING ]:
statuses_to_tags[ HC.DELETED ].add( tag )
statuses_to_tags[ HC.DELETED_PENDING ].discard( tag )
else: statuses_to_tags[ HC.PENDING ].discard( tag )
elif action == HC.CONTENT_UPDATE_PETITION:
( tag, reason ) = info
statuses_to_tags[ HC.PETITIONED ].add( tag )
elif action == HC.CONTENT_UPDATE_RESCIND_PETITION:
tag = info
statuses_to_tags[ HC.PETITIONED ].discard( tag )
self._RecalcCombined()
def ResetService( self, service_identifier ):
self._service_identifiers_to_statuses_to_tags[ service_identifier ] = collections.defaultdict[ set ]
self._RecalcCombined()
class TagParentsManager():
def __init__( self ):
@ -2657,7 +2800,7 @@ class TagParentsManager():
def _GetParents( self, service_identifier, tag ):
if service_identifier == HC.NULL_SERVICE_IDENTIFIER: return []
if service_identifier == HC.COMBINED_TAG_SERVICE_IDENTIFIER: return []
if tag in self._parents[ service_identifier ]: still_to_process = list( self._parents[ service_identifier ][ tag ] )
else: still_to_process = []
@ -2697,7 +2840,7 @@ class TagParentsManager():
def ExpandPredicates( self, service_identifier, predicates ):
if service_identifier == HC.NULL_SERVICE_IDENTIFIER: return predicates
if service_identifier == HC.COMBINED_TAG_SERVICE_IDENTIFIER: return predicates
results = []
@ -2757,13 +2900,44 @@ class TagSiblingsManager():
t_s_p = list( self._tag_service_precedence )
t_s_p.reverse()
processed_siblings = {}
# first combine the services
# go from high precedence to low, writing A -> B
# if A map already exists, don't overwrite
# if A -> B forms a loop, don't write it
for service_identifier in t_s_p:
if service_identifier in unprocessed_siblings: processed_siblings.update( unprocessed_siblings[ service_identifier ] )
if service_identifier in unprocessed_siblings:
some_siblings = unprocessed_siblings[ service_identifier ]
for ( old, new ) in some_siblings:
if old not in processed_siblings:
next_new = new
we_have_a_loop = False
while next_new in processed_siblings:
next_new = processed_siblings[ next_new ]
if next_new == old:
we_have_a_loop = True
break
if not we_have_a_loop: processed_siblings[ old ] = new
processed_siblings.update( unprocessed_siblings[ service_identifier ] )
# now to collapse chains
@ -2775,22 +2949,17 @@ class TagSiblingsManager():
# adding A -> B
if new_tag in self._siblings: # B -> C already added, so add A -> C
if new_tag in self._siblings:
new_tag = self._siblings[ new_tag ]
# B -> F already calculated and added, so add A -> F
self._siblings[ old_tag ] = new_tag
self._siblings[ old_tag ] = self._siblings[ new_tag ]
else:
while new_tag in processed_siblings: # while B -> C, C -> D, D -> E in preprocessed list, pursue endpoint F
new_tag = processed_siblings[ new_tag ]
if new_tag == old_tag: break # we have a loop!
while new_tag in processed_siblings: new_tag = processed_siblings[ new_tag ] # pursue endpoint F
if old_tag != new_tag: self._siblings[ old_tag ] = new_tag
self._siblings[ old_tag ] = new_tag

View File

@ -5,6 +5,7 @@ import HydrusSessions
import ClientConstants as CC
import ClientDB
import ClientGUI
import ClientGUIDialogs
import os
import sqlite3
import threading
@ -157,6 +158,8 @@ class Controller( wx.App ):
try:
self._options = {} # this is for the db locked dialog
self._splash = ClientGUI.FrameSplash()
self.SetSplashText( 'log' )
@ -165,7 +168,31 @@ class Controller( wx.App ):
self.SetSplashText( 'db' )
self._db = ClientDB.DB()
db_initialised = False
while not db_initialised:
try:
self._db = ClientDB.DB()
db_initialised = True
except HC.DBAccessException as e:
print( unicode( e ) )
message = 'This instance of the client had a problem connecting to the database, which probably means an old instance is still closing.'
message += os.linesep + os.linesep
message += 'If the old instance does not close for a very long time, you can usually safely force-close it from task manager.'
with ClientGUIDialogs.DialogYesNo( None, message, yes_label = 'wait a bit, then try again', no_label = 'quit now' ) as dlg:
if dlg.ShowModal() == wx.ID_YES: time.sleep( 3 )
else: return False
self._options = self._db.Read( 'options', HC.HIGH_PRIORITY )
@ -217,11 +244,13 @@ class Controller( wx.App ):
self._maintenance_event_timer = wx.Timer( self, ID_MAINTENANCE_EVENT_TIMER )
self._maintenance_event_timer.Start( 20 * 60000, wx.TIMER_CONTINUOUS )
except sqlite3.OperationalError:
message = 'This instance of the client had a problem connecting to the database, which probably means an old instance is still closing.'
except sqlite3.OperationalError as e:
print( traceback.format_exc() )
message = 'Database error!'
message += os.linesep + os.linesep
message += 'This instance will now close. You can either wait a while and try again, or force-close the old instance through task manager and try again immediately.'
message += unicode( e )
print message
wx.MessageBox( message )
@ -274,7 +303,7 @@ class Controller( wx.App ):
if HC.shutdown: raise Exception( 'Client shutting down!' )
elif pubsubs_queue.qsize() == 0: return
else: time.sleep( 0.04 )
else: time.sleep( 0.0001 )

File diff suppressed because it is too large Load Diff

View File

@ -1255,8 +1255,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
server_admin_identifiers = [ service.GetServiceIdentifier() for service in servers_admin if service.GetAccount().HasPermission( HC.GENERAL_ADMIN ) ]
nums_pending = wx.GetApp().Read( 'nums_pending' )
menu = wx.MenuBar()
file = wx.Menu()
@ -1307,30 +1305,43 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
menu.Append( database, p( '&Database' ) )
if len( nums_pending ) > 0:
nums_pending = wx.GetApp().Read( 'nums_pending' )
pending = wx.Menu()
total_num_pending = 0
for ( service_identifier, info ) in nums_pending.items():
pending = wx.Menu()
service_type = service_identifier.GetType()
for ( service_identifier, num_pending ) in nums_pending.items():
if service_type == HC.TAG_REPOSITORY:
if num_pending > 0:
service_type = service_identifier.GetType()
submenu = wx.Menu()
submenu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'upload_pending', service_identifier ), p( '&Upload' ), p( 'Upload ' + service_identifier.GetName() + '\'s Pending and Petitions.' ) )
submenu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete_pending', service_identifier ), p( '&Forget' ), p( 'Clear ' + service_identifier.GetName() + '\'s Pending and Petitions.' ) )
pending.AppendMenu( CC.ID_NULL, p( service_identifier.GetName() + ' Pending (' + HC.ConvertIntToPrettyString( num_pending ) + ')' ), submenu )
num_pending = info[ HC.SERVICE_INFO_NUM_PENDING_MAPPINGS ]
num_petitioned = info[ HC.SERVICE_INFO_NUM_PETITIONED_MAPPINGS ]
elif service_type == HC.FILE_REPOSITORY:
num_pending = info[ HC.SERVICE_INFO_NUM_PENDING_FILES ]
num_petitioned = info[ HC.SERVICE_INFO_NUM_PETITIONED_FILES ]
num_pending_total = sum( nums_pending.values() )
if num_pending + num_petitioned > 0:
submenu = wx.Menu()
submenu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'upload_pending', service_identifier ), p( '&Upload' ), p( 'Upload ' + service_identifier.GetName() + '\'s Pending and Petitions.' ) )
submenu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete_pending', service_identifier ), p( '&Forget' ), p( 'Clear ' + service_identifier.GetName() + '\'s Pending and Petitions.' ) )
pending.AppendMenu( CC.ID_NULL, p( service_identifier.GetName() + ' Pending (' + HC.ConvertIntToPrettyString( num_pending ) + '/' + HC.ConvertIntToPrettyString( num_petitioned ) + ')' ), submenu )
menu.Append( pending, p( '&Pending (' + HC.ConvertIntToPrettyString( num_pending_total ) + ')' ) )
total_num_pending += num_pending + num_petitioned
if total_num_pending > 0: menu.Append( pending, p( '&Pending (' + HC.ConvertIntToPrettyString( total_num_pending ) + ')' ) )
else: pending.Destroy()
services = wx.Menu()
submenu = wx.Menu()

View File

@ -100,11 +100,12 @@ class Canvas():
dc.SetFont( wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) )
tags_cdpp = self._current_media.GetTags()
tags_manager = self._current_media.GetTagsManager()
( current, deleted, pending, petitioned ) = tags_cdpp.GetUnionCDPP()
current = tags_manager.GetCurrent()
pending = tags_manager.GetPending()
tags_i_want_to_display = list( current.union( pending ).union( petitioned ) )
tags_i_want_to_display = list( current.union( pending ) )
tags_i_want_to_display.sort()
@ -116,7 +117,6 @@ class Canvas():
if tag in current: display_string = tag
elif tag in pending: display_string = '(+) ' + tag
elif tag in petitioned: display_string = '(-) ' + tag
if ':' in tag:
@ -568,7 +568,7 @@ class CanvasFullscreenMediaList( ClientGUIMixins.ListeningMediaList, Canvas, Cli
collections_string = ''
( creators, series, titles, volumes, chapters, pages ) = self._current_media.GetTags().GetCSTVCP()
( creators, series, titles, volumes, chapters, pages ) = self._current_media.GetTagsManager().GetCSTVCP()
if len( creators ) > 0:
@ -1340,9 +1340,11 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaList ):
if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
tags = self._current_media.GetTags()
tags_manager = self._current_media.GetTagsManager()
( current, deleted, pending, petitioned ) = tags.GetCDPP( service_identifier )
current = tags_manager.GetCurrent()
pending = tags_manager.GetPending()
petitioned = tags_manager.GetPetitioned()
if service_type == HC.LOCAL_TAG:

View File

@ -2,6 +2,7 @@ import collections
import HydrusConstants as HC
import ClientConstants as CC
import ClientGUIMixins
import itertools
import os
import random
import time
@ -380,14 +381,12 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
self._file_service_identifier = file_service_identifier
self._tag_service_identifier = tag_service_identifier
if self._file_service_identifier == HC.NULL_SERVICE_IDENTIFIER: name = 'all known files'
else: name = self._file_service_identifier.GetName()
name = self._file_service_identifier.GetName()
self._file_repo_button = wx.Button( self._dropdown_window, label = name )
self._file_repo_button.Bind( wx.EVT_BUTTON, self.EventFileButton )
if self._tag_service_identifier == HC.NULL_SERVICE_IDENTIFIER: name = 'all known tags'
else: name = self._tag_service_identifier.GetName()
name = self._tag_service_identifier.GetName()
self._tag_repo_button = wx.Button( self._dropdown_window, label = name )
self._tag_repo_button.Bind( wx.EVT_BUTTON, self.EventTagButton )
@ -410,13 +409,13 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
def EventFileButton( self, event ):
service_identifiers = wx.GetApp().Read( 'service_identifiers', ( HC.FILE_REPOSITORY, ) )
service_identifiers = []
service_identifiers.append( HC.COMBINED_FILE_SERVICE_IDENTIFIER )
service_identifiers.append( HC.LOCAL_FILE_SERVICE_IDENTIFIER )
service_identifiers.extend( wx.GetApp().Read( 'service_identifiers', ( HC.FILE_REPOSITORY, ) ) )
menu = wx.Menu()
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', HC.NULL_SERVICE_IDENTIFIER ), 'all known files' )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', HC.LOCAL_FILE_SERVICE_IDENTIFIER ), 'local files' )
for service_identifier in service_identifiers: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_file_repository', service_identifier ), service_identifier.GetName() )
self.PopupMenu( menu )
@ -440,8 +439,7 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
self._file_service_identifier = service_identifier
if service_identifier == HC.NULL_SERVICE_IDENTIFIER: name = 'all known files'
else: name = service_identifier.GetName()
name = service_identifier.GetName()
self._file_repo_button.SetLabel( name )
@ -453,8 +451,7 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
self._tag_service_identifier = service_identifier
if service_identifier == HC.NULL_SERVICE_IDENTIFIER: name = 'all known tags'
else: name = service_identifier.GetName()
name = service_identifier.GetName()
self._tag_repo_button.SetLabel( name )
@ -482,13 +479,13 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
def EventTagButton( self, event ):
service_identifiers = wx.GetApp().Read( 'service_identifiers', ( HC.TAG_REPOSITORY, ) )
service_identifiers = []
service_identifiers.append( HC.COMBINED_TAG_SERVICE_IDENTIFIER )
service_identifiers.append( HC.LOCAL_TAG_SERVICE_IDENTIFIER )
service_identifiers.extend( wx.GetApp().Read( 'service_identifiers', ( HC.TAG_REPOSITORY, ) ) )
menu = wx.Menu()
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', HC.NULL_SERVICE_IDENTIFIER ), 'all known tags' )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', HC.LOCAL_TAG_SERVICE_IDENTIFIER ), 'local tags' )
for service_identifier in service_identifiers: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'change_tag_repository', service_identifier ), service_identifier.GetName() )
self.PopupMenu( menu )
@ -569,7 +566,7 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
self._first_letters = ''
self._current_namespace = ''
if self._file_service_identifier == HC.NULL_SERVICE_IDENTIFIER: s_i = self._tag_service_identifier
if self._file_service_identifier == HC.COMBINED_FILE_SERVICE_IDENTIFIER: s_i = self._tag_service_identifier
else: s_i = self._file_service_identifier
matches = wx.GetApp().Read( 'file_system_predicates', s_i )
@ -607,32 +604,26 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
if media is None: self._cached_results = wx.GetApp().Read( 'autocomplete_tags', file_service_identifier = self._file_service_identifier, tag_service_identifier = self._tag_service_identifier, half_complete_tag = search_text, include_current = self._include_current, include_pending = self._include_pending )
else:
all_tags = []
tags_managers = []
for m in media:
if m.IsCollection(): all_tags.extend( m.GetSingletonsTags() )
else: all_tags.append( m.GetTags() )
if m.IsCollection(): tags_managers.extend( m.GetSingletonsTagsManagers() )
else: tags_managers.append( m.GetTagsManager() )
absolutely_all_tags = []
lists_of_tags = []
if self._tag_service_identifier == HC.NULL_SERVICE_IDENTIFIER:
if self._include_current: absolutely_all_tags += [ list( current ) for ( current, deleted, pending, petitioned ) in [ tags.GetUnionCDPP() for tags in all_tags ] ]
if self._include_pending: absolutely_all_tags += [ list( pending ) for ( current, deleted, pending, petitioned ) in [ tags.GetUnionCDPP() for tags in all_tags ] ]
else:
if self._include_current: absolutely_all_tags += [ list( current ) for ( current, deleted, pending, petitioned ) in [ tags.GetCDPP( self._tag_service_identifier ) for tags in all_tags ] ]
if self._include_pending: absolutely_all_tags += [ list( pending ) for ( current, deleted, pending, petitioned ) in [ tags.GetCDPP( self._tag_service_identifier ) for tags in all_tags ] ]
if self._include_current: lists_of_tags += [ list( tags_manager.GetCurrent( self._tag_service_identifier ) ) for tags_manager in tags_managers ]
if self._include_pending: lists_of_tags += [ list( tags_manager.GetPending( self._tag_service_identifier ) ) for tags_manager in tags_managers ]
absolutely_all_tags_flat = [ tag for tags in absolutely_all_tags for tag in tags if HC.SearchEntryMatchesTag( half_complete_tag, tag ) ]
all_tags_flat_iterable = itertools.chain.from_iterable( lists_of_tags )
if self._current_namespace != '': absolutely_all_tags_flat = [ tag for tag in absolutely_all_tags_flat if tag.startswith( self._current_namespace + ':' ) ]
all_tags_flat = [ tag for tag in all_tags_flat_iterable if HC.SearchEntryMatchesTag( half_complete_tag, tag ) ]
tags_to_count = collections.Counter( absolutely_all_tags_flat )
if self._current_namespace != '': all_tags_flat = [ tag for tag in all_tags_flat if tag.startswith( self._current_namespace + ':' ) ]
tags_to_count = collections.Counter( all_tags_flat )
self._cached_results = CC.AutocompleteMatchesPredicates( self._tag_service_identifier, [ HC.Predicate( HC.PREDICATE_TYPE_TAG, ( operator, tag ), count ) for ( tag, count ) in tags_to_count.items() ] )
@ -644,6 +635,20 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
if self._current_namespace != '': matches.insert( 0, HC.Predicate( HC.PREDICATE_TYPE_NAMESPACE, ( operator, namespace ), None ) )
entry_predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( operator, search_text ), None )
try:
index = matches.index( entry_predicate )
predicate = matches[ index ]
del matches[ index ]
matches.insert( 0, predicate )
except: pass
for match in matches:
@ -684,7 +689,7 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
self._options = wx.GetApp().Read( 'options' )
if self._options[ 'show_all_tags_in_autocomplete' ]: file_service_identifier = HC.NULL_SERVICE_IDENTIFIER
if self._options[ 'show_all_tags_in_autocomplete' ]: file_service_identifier = HC.COMBINED_FILE_SERVICE_IDENTIFIER
AutoCompleteDropdownTags.__init__( self, parent, file_service_identifier, tag_service_identifier )
@ -2933,7 +2938,7 @@ class TagsBoxCPP( TagsBox ):
self._page_key = page_key
self._tag_service_identifier = HC.NULL_SERVICE_IDENTIFIER
self._tag_service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER
self._last_media = None
self._current_tags_to_count = {}
@ -3084,7 +3089,7 @@ class TagsBoxFlat( TagsBox ):
TagsBox.__init__( self, parent )
self._removed_callable = removed_callable
self._tags = []
self._tags = set()
def _RecalcTags( self ):
@ -3111,11 +3116,11 @@ class TagsBoxFlat( TagsBox ):
self._TextsHaveChanged()
def _Activate( self, s, term ):
def _Activate( self, s, tag ):
if term in self._tags:
if tag in self._tags:
self._tags.remove( term )
self._tags.discard( tag )
self._RecalcTags()
@ -3123,9 +3128,15 @@ class TagsBoxFlat( TagsBox ):
def AddTag( self, tag ):
def AddTag( self, tag, parents ):
self._tags.append( tag )
if tag in self._tags: self._tags.discard( tag )
else:
self._tags.add( tag )
self._tags.update( parents )
self._RecalcTags()

View File

@ -350,7 +350,7 @@ class DialogInputCustomFilterAction( Dialog ):
self._tag_service_identifiers = wx.Choice( self._tag_panel )
self._tag_value = wx.TextCtrl( self._tag_panel, style = wx.TE_READONLY )
self._tag_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._tag_panel, self.SetTag, HC.LOCAL_FILE_SERVICE_IDENTIFIER, HC.NULL_SERVICE_IDENTIFIER )
self._tag_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self._tag_panel, self.SetTag, HC.LOCAL_FILE_SERVICE_IDENTIFIER, HC.COMBINED_TAG_SERVICE_IDENTIFIER )
self._ok_tag = wx.Button( self._tag_panel, label = 'ok' )
self._ok_tag.Bind( wx.EVT_BUTTON, self.EventOKTag )
@ -4363,6 +4363,16 @@ class DialogManageOptionsLocal( Dialog ):
self._listbook.AddPage( self._gui_page, 'gui' )
# sound
self._sound_page = wx.Panel( self._listbook )
self._sound_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
self._play_dumper_noises = wx.CheckBox( self._sound_page, label = 'play success/fail noises when dumping' )
self._play_dumper_noises.SetValue( self._options[ 'play_dumper_noises' ] )
self._listbook.AddPage( self._sound_page, 'sound' )
# default file system predicates
system_predicates = self._options[ 'file_system_predicates' ]
@ -4733,6 +4743,14 @@ class DialogManageOptionsLocal( Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._play_dumper_noises, FLAGS_EXPAND_PERPENDICULAR )
self._sound_page.SetSizer( vbox )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._namespace_colours, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._new_namespace_colour, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._edit_namespace_colour, FLAGS_EXPAND_PERPENDICULAR )
@ -4919,6 +4937,8 @@ class DialogManageOptionsLocal( Dialog ):
def EventOK( self, event ):
self._options[ 'play_dumper_noises' ] = self._play_dumper_noises.GetValue()
self._options[ 'confirm_client_exit' ] = self._confirm_client_exit.GetValue()
self._options[ 'gui_capitalisation' ] = self._gui_capitalisation.GetValue()
self._options[ 'show_all_tags_in_autocomplete' ] = self._gui_show_all_tags_in_autocomplete.GetValue()
@ -7928,13 +7948,21 @@ class DialogManageTagServicePrecedence( Dialog ):
def EventOK( self, event ):
try:
message = 'This operation may take several minutes to complete. Are you sure?'
with DialogYesNo( self, message ) as dlg:
service_identifiers = [ self._tag_services.GetClientData( i ) for i in range( self._tag_services.GetCount() ) ]
if dlg.ShowModal() == wx.ID_YES:
try:
service_identifiers = [ self._tag_services.GetClientData( i ) for i in range( self._tag_services.GetCount() ) ]
wx.GetApp().Write( 'set_tag_service_precedence', service_identifiers )
except Exception as e: wx.MessageBox( 'Something went wrong when trying to save tag service precedence to the database: ' + unicode( e ) )
wx.GetApp().Write( 'set_tag_service_precedence', service_identifiers )
except Exception as e: wx.MessageBox( 'Something went wrong when trying to save tag service precedence to the database: ' + unicode( e ) )
self.EndModal( wx.ID_OK )
@ -8190,7 +8218,9 @@ class DialogManageTags( Dialog ):
self._account = service.GetAccount()
( self._current_tags, self._deleted_tags, self._pending_tags, self._petitioned_tags ) = CC.MediaIntersectCDPPTagServiceIdentifiers( media, tag_service_identifier )
tags_managers = [ m.GetTagsManager() for m in media ]
( self._current_tags, self._deleted_tags, self._pending_tags, self._petitioned_tags ) = CC.IntersectTags( tags_managers, tag_service_identifier )
self._current_tags.sort()
self._pending_tags.sort()
@ -9057,11 +9087,11 @@ class DialogPathsToTagsRegex( Dialog ):
def AddTag( self, tag ):
def AddTag( self, tag, parents = [] ):
if tag is not None:
self._tags.AddTag( tag )
self._tags.AddTag( tag, parents )
self._tag_box.Clear()
@ -9069,11 +9099,11 @@ class DialogPathsToTagsRegex( Dialog ):
def AddTagSingle( self, tag ):
def AddTagSingle( self, tag, parents = [] ):
if tag is not None:
self._single_tags.AddTag( tag )
self._single_tags.AddTag( tag, parents )
self._single_tag_box.Clear()
@ -9085,6 +9115,11 @@ class DialogPathsToTagsRegex( Dialog ):
if tag not in self._paths_to_single_tags[ path ]: self._paths_to_single_tags[ path ].append( tag )
for parent in parents:
if parent not in self._paths_to_single_tags[ path ]: self._paths_to_single_tags[ path ].append( parent )
self._RefreshFileList() # make this more clever
@ -9165,10 +9200,6 @@ class DialogPathsToTagsRegex( Dialog ):
else: self._single_tag_box.Disable()
single_tags = list( single_tags )
single_tags.sort()
self._single_tags.SetTags( single_tags )
@ -10214,12 +10245,12 @@ class DialogSetupExport( Dialog ):
for ( term_type, term ) in terms:
tags = media.GetTags()
tags_manager = media.GetTagsManager()
if term_type == 'string': filename += term
elif term_type == 'namespace':
tags = tags.GetNamespaceSlice( ( term, ) )
tags = tags_manager.GetNamespaceSlice( ( term, ) )
filename += ', '.join( [ tag.split( ':' )[1] for tag in tags ] )
@ -10227,7 +10258,8 @@ class DialogSetupExport( Dialog ):
if term in ( 'tags', 'nn tags' ):
( current, deleted, pending, petitioned ) = tags.GetUnionCDPP()
current = tags_manager.GetCurrent()
pending = tags_manager.GetPending()
tags = list( current.union( pending ) )
@ -10249,7 +10281,7 @@ class DialogSetupExport( Dialog ):
if ':' in term: term = term.split( ':' )[1]
if tags.HasTag( term ): filename += term
if tags_manager.HasTag( term ): filename += term

View File

@ -1,4 +1,5 @@
import HydrusConstants as HC
import HydrusAudioHandling
import HydrusDownloading
import HydrusImageHandling
import ClientConstants as CC
@ -352,6 +353,8 @@ class ManagementPanel( wx.lib.scrolledpanel.ScrolledPanel ):
wx.lib.scrolledpanel.ScrolledPanel.__init__( self, parent, style = wx.BORDER_NONE | wx.VSCROLL )
self._options = wx.GetApp().Read( 'options' )
self.SetupScrolling()
#self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
@ -360,7 +363,7 @@ class ManagementPanel( wx.lib.scrolledpanel.ScrolledPanel ):
self._page = page
self._page_key = page_key
self._file_service_identifier = file_service_identifier
self._tag_service_identifier = HC.NULL_SERVICE_IDENTIFIER
self._tag_service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER
HC.pubsub.sub( self, 'SetSearchFocus', 'set_search_focus' )
@ -628,7 +631,10 @@ class ManagementPanelDumper( ManagementPanel ):
for ( service_identifier, namespaces ) in advanced_tag_options.items():
( current, deleted, pending, petitioned ) = media.GetTags().GetCDPP( service_identifier )
tags = media.GetTagsManager()
current = tags_manager.GetCurrent( service_identifier )
pending = tags_manager.GetPending( service_identifier )
tags = current.union( pending )
@ -743,6 +749,12 @@ class ManagementPanelDumper( ManagementPanel ):
self._actually_dumping = False
if self._options[ 'play_dumper_noises' ]:
if status == 'success': HydrusAudioHandling.PlayNoise( 'success' )
else: HydrusAudioHandling.PlayNoise( 'error' )
if status == 'success':
dump_status_enum = CC.DUMPER_DUMPED_OK
@ -2592,7 +2604,7 @@ class ManagementPanelQuery( ManagementPanel ):
self._current_predicates_box = ClientGUICommon.TagsBoxPredicates( self._search_panel, self._page_key, initial_predicates )
self._searchbox = ClientGUICommon.AutoCompleteDropdownTagsRead( self._search_panel, self._page_key, self._file_service_identifier, HC.NULL_SERVICE_IDENTIFIER, self._page.GetMedia )
self._searchbox = ClientGUICommon.AutoCompleteDropdownTagsRead( self._search_panel, self._page_key, self._file_service_identifier, HC.COMBINED_TAG_SERVICE_IDENTIFIER, self._page.GetMedia )
self._search_panel.AddF( self._current_predicates_box, FLAGS_EXPAND_PERPENDICULAR )
self._search_panel.AddF( self._searchbox, FLAGS_EXPAND_PERPENDICULAR )

View File

@ -1145,7 +1145,7 @@ class MediaPanelThumbnails( MediaPanel ):
if t is not None:
if t.GetFileServiceIdentifiersCDPP().HasLocal(): self._FullScreen( t )
elif self._file_service_identifier != HC.NULL_SERVICE_IDENTIFIER: wx.GetApp().Write( 'add_downloads', self._file_service_identifier, t.GetHashes() )
elif self._file_service_identifier != HC.COMBINED_FILE_SERVICE_IDENTIFIER: wx.GetApp().Write( 'add_downloads', self._file_service_identifier, t.GetHashes() )
@ -1751,7 +1751,7 @@ class Thumbnail( Selectable ):
local = self.GetFileServiceIdentifiersCDPP().HasLocal()
( creators, series, titles, volumes, chapters, pages ) = self.GetTags().GetCSTVCP()
( creators, series, titles, volumes, chapters, pages ) = self.GetTagsManager().GetCSTVCP()
if self._hydrus_bmp is None: self._LoadFromDB()

View File

@ -119,7 +119,7 @@ class MediaList():
for media in self._singleton_media:
if len( namespaces_to_collect_by ) > 0: namespace_key = media.GetTags().GetNamespaceSlice( namespaces_to_collect_by )
if len( namespaces_to_collect_by ) > 0: namespace_key = media.GetTagsManager().GetNamespaceSlice( namespaces_to_collect_by )
else: namespace_key = None
if len( ratings_to_collect_by ) > 0:
@ -304,13 +304,13 @@ class MediaList():
def namespace_compare( x, y ):
x_tags = x.GetTags()
y_tags = y.GetTags()
x_tags_manager = x.GetTagsManager()
y_tags_manager = y.GetTagsManager()
for namespace in sort_by_data:
x_namespace_slice = x_tags.GetNamespaceSlice( ( namespace, ) )
y_namespace_slice = y_tags.GetNamespaceSlice( ( namespace, ) )
x_namespace_slice = x_tags_manager.GetNamespaceSlice( ( namespace, ) )
y_namespace_slice = y_tags_manager.GetNamespaceSlice( ( namespace, ) )
if x_namespace_slice == y_namespace_slice: continue # this covers len == 0 for both, too
else:
@ -412,7 +412,7 @@ class MediaCollection( MediaList, Media ):
self._duration = None
self._num_frames = None
self._num_words = None
self._tags = None
self._tags_manager = None
self._file_service_identifiers = None
self._RecalcInternals()
@ -438,37 +438,11 @@ class MediaCollection( MediaList, Media ):
if duration_sum > 0: self._duration = duration_sum
else: self._duration = None
# better-but-still-pretty-horrible code starts here
tags_managers = [ m.GetTagsManager() for m in self._sorted_media ]
# remember: the only time a collection is asked for its tags is by thumbnail.getbmp(), to draw series and page info
# until I make SVCP more complicated, it mostly needs only be a quick and ugly intersection
self._tags_manager = CC.MergeTags( tags_managers )
all_tags_cdpp = [ m.GetTags().GetServiceIdentifiersToCDPP() for m in self._sorted_media ]
combined_tags = collections.defaultdict( list )
for tags_cdpp in all_tags_cdpp:
for ( service_identifier, cdpp ) in tags_cdpp.items(): combined_tags[ service_identifier ].append( cdpp )
final_tags = {}
for ( service_identifier, cdpps ) in combined_tags.items():
current = list( HC.IntelligentMassIntersect( ( c for ( c, d, p, pet ) in cdpps ) ) )
deleted = []
pending = list( HC.IntelligentMassIntersect( ( p for ( c, d, p, pet ) in cdpps ) ) )
petitioned = []
final_tags[ service_identifier ] = ( current, deleted, pending, petitioned )
self._tags = CC.CDPPTagServiceIdentifiers( wx.GetApp().Read( 'tag_service_precedence' ), final_tags )
# END OF HORRIBLE CODE
# new horrible compromise
# horrible compromise
if len( self._sorted_media ) > 0: self._ratings = self._sorted_media[0].GetRatings()
else: self._ratings = ( CC.LocalRatings( {} ), CC.CPRemoteRatingsServiceIdentifiers( {} ) )
@ -541,18 +515,18 @@ class MediaCollection( MediaList, Media ):
def GetResolution( self ): return ( self._width, self._height )
def GetSingletonsTags( self ):
def GetSingletonsTagsManagers( self ):
all_tags = [ m.GetTags() for m in self._singleton_media ]
tags_managers = [ m.GetTagsManager() for m in self._singleton_media ]
for m in self._collected_media: all_tags.extend( m.GetSingletonsTags() )
for m in self._collected_media: tags_managers.extend( m.GetSingletonsTagsManagers() )
return all_tags
return tags_managers
def GetSize( self ): return self._size
def GetTags( self ): return self._tags
def GetTagsManager( self ): return self._tags_manager
def GetTimestamp( self ): return self._timestamp
@ -641,7 +615,7 @@ class MediaSingleton( Media ):
def GetPrettyInfo( self ):
( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tags, file_service_identifiers, local_ratings, remote_ratings ) = self._media_result.GetInfo()
( hash, inbox, size, mime, timestamp, width, height, duration, num_frames, num_words, tags_manager, file_service_identifiers, local_ratings, remote_ratings ) = self._media_result.GetInfo()
info_string = HC.ConvertIntToBytes( size ) + ' ' + HC.mime_string_lookup[ mime ]
@ -674,7 +648,7 @@ class MediaSingleton( Media ):
else: return size
def GetTags( self ): return self._media_result.GetTags()
def GetTagsManager( self ): return self._media_result.GetTagsManager()
def HasArchive( self ): return not self._media_result.GetInbox()

View File

@ -0,0 +1,28 @@
import HydrusConstants as HC
import mp3play
import os
import threading
import time
import traceback
import wx
parsed_noises = {}
def PlayNoise( name ):
if name not in parsed_noises:
if name == 'success': filename = 'success.mp3'
elif name == 'error': filename = 'error.mp3'
path = HC.STATIC_DIR + os.path.sep + filename
noise = mp3play.load( path )
parsed_noises[ name ] = noise
noise = parsed_noises[ name ]
noise.play()

View File

@ -30,7 +30,7 @@ TEMP_DIR = BASE_DIR + os.path.sep + 'temp'
# Misc
NETWORK_VERSION = 9
SOFTWARE_VERSION = 70
SOFTWARE_VERSION = 71
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -88,6 +88,8 @@ LOCAL_RATING_NUMERICAL = 6
LOCAL_RATING_LIKE = 7
RATING_NUMERICAL_REPOSITORY = 8
RATING_LIKE_REPOSITORY = 9
COMBINED_TAG = 10
COMBINED_FILE = 11
SERVER_ADMIN = 99
NULL_SERVICE = 100
@ -120,6 +122,7 @@ CURRENT = 0
PENDING = 1
DELETED = 2
PETITIONED = 3
DELETED_PENDING = 4
HIGH_PRIORITY = 0
LOW_PRIORITY = 2
@ -141,6 +144,10 @@ SERVICE_INFO_NUM_PENDING = 11
SERVICE_INFO_NUM_CONVERSATIONS = 12
SERVICE_INFO_NUM_UNREAD = 13
SERVICE_INFO_NUM_DRAFTS = 14
SERVICE_INFO_NUM_PENDING_MAPPINGS = 15
SERVICE_INFO_NUM_PETITIONED_MAPPINGS = 16
SERVICE_INFO_NUM_PENDING_FILES = 15
SERVICE_INFO_NUM_PETITIONED_FILES = 16
SERVICE_UPDATE_ACCOUNT = 0
SERVICE_UPDATE_DELETE_PENDING = 1
@ -477,6 +484,14 @@ def BuildKeyToListDict( pairs ):
return d
def BuildKeyToSetDict( pairs ):
d = collections.defaultdict( set )
for ( key, value ) in pairs: d[ key ].add( value )
return d
def CalculateScoreFromRating( count, rating ):
# http://www.evanmiller.org/how-not-to-sort-by-average-rating.html
@ -1601,6 +1616,8 @@ class ClientServiceIdentifier( HydrusYAMLBase ):
def __ne__( self, other ): return self.__hash__() != other.__hash__()
def GetInfo( self ): return ( self._service_key, self._type, self._name )
def GetName( self ): return self._name
def GetServiceKey( self ): return self._service_key
@ -1609,6 +1626,8 @@ class ClientServiceIdentifier( HydrusYAMLBase ):
LOCAL_FILE_SERVICE_IDENTIFIER = ClientServiceIdentifier( 'local files', LOCAL_FILE, 'local files' )
LOCAL_TAG_SERVICE_IDENTIFIER = ClientServiceIdentifier( 'local tags', LOCAL_TAG, 'local tags' )
COMBINED_FILE_SERVICE_IDENTIFIER = ClientServiceIdentifier( 'all known files', COMBINED_FILE, 'all known files' )
COMBINED_TAG_SERVICE_IDENTIFIER = ClientServiceIdentifier( 'all known tags', COMBINED_TAG, 'all known tags' )
NULL_SERVICE_IDENTIFIER = ClientServiceIdentifier( '', NULL_SERVICE, 'no service' )
class ContentUpdate():
@ -2266,6 +2285,7 @@ sqlite3.register_adapter( HydrusUpdateTagRepository, yaml.safe_dump )
# Custom Exceptions
class DBAccessException( Exception ): pass
class NetworkVersionException( Exception ): pass
class NoContentException( Exception ): pass
class NotFoundException( Exception ): pass

View File

@ -752,7 +752,7 @@ class DownloaderNewgrounds( Downloader ):
title = soup.find( 'title' )
tags.append( 'title:' + title )
tags.append( 'title:' + title.string )
except: pass

View File

@ -1542,6 +1542,8 @@ class DB( ServiceDB ):
c.execute( 'CREATE INDEX mapping_petitions_service_id_tag_id_hash_id_index ON mapping_petitions ( service_id, tag_id, hash_id );' )
c.execute( 'CREATE TABLE mappings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, tag_id INTEGER, hash_id INTEGER, account_id INTEGER, timestamp INTEGER, PRIMARY KEY( service_id, tag_id, hash_id ) );' )
c.execute( 'CREATE INDEX mappings_account_id_index ON mappings ( account_id );' )
c.execute( 'CREATE INDEX mappings_timestamp_index ON mappings ( timestamp );' )
c.execute( 'CREATE TABLE messages ( message_key BLOB_BYTES PRIMARY KEY, service_id INTEGER REFERENCES services ON DELETE CASCADE, account_id INTEGER, timestamp INTEGER );' )
c.execute( 'CREATE INDEX messages_service_id_account_id_index ON messages ( service_id, account_id );' )
@ -1565,6 +1567,22 @@ class DB( ServiceDB ):
c.execute( 'CREATE TABLE tags ( tag_id INTEGER PRIMARY KEY, tag TEXT );' )
c.execute( 'CREATE UNIQUE INDEX tags_tag_index ON tags ( tag );' )
c.execute( 'CREATE TABLE tag_siblings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, old_tag_id INTEGER, new_tag_id INTEGER, account_id INTEGER, timestamp INTEGER, PRIMARY KEY ( service_id, old_tag_id ) );' )
c.execute( 'CREATE INDEX tag_siblings_service_id_account_id_index ON tag_siblings ( service_id, account_id );' )
c.execute( 'CREATE INDEX tag_siblings_service_id_timestamp_index ON tag_siblings ( service_id, timestamp );' )
c.execute( 'CREATE TABLE deleted_tag_siblings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, old_tag_id INTEGER, new_tag_id INTEGER, reason_id INTEGER, account_id INTEGER, admin_account_id INTEGER, timestamp INTEGER, PRIMARY KEY ( service_id, old_tag_id ) );' )
c.execute( 'CREATE INDEX deleted_tag_siblings_service_id_account_id_index ON deleted_tag_siblings ( service_id, account_id );' )
c.execute( 'CREATE INDEX deleted_tag_siblings_service_id_timestamp_index ON deleted_tag_siblings ( service_id, timestamp );' )
c.execute( 'CREATE TABLE pending_tag_siblings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, old_tag_id INTEGER, new_tag_id INTEGER, account_id INTEGER, timestamp INTEGER, PRIMARY KEY ( service_id, old_tag_id, account_id ) );' )
c.execute( 'CREATE INDEX pending_tag_siblings_service_id_account_id_index ON tag_siblings ( service_id, account_id );' )
c.execute( 'CREATE INDEX pending_tag_siblings_service_id_timestamp_index ON tag_siblings ( service_id, timestamp );' )
c.execute( 'CREATE TABLE tag_sibling_petitions ( service_id INTEGER REFERENCES services ON DELETE CASCADE, account_id INTEGER, old_tag_id INTEGER, new_tag_id INTEGER, reason_id INTEGER, PRIMARY KEY ( service_id, account_id, old_tag_id, reason_id ) );' )
c.execute( 'CREATE INDEX tag_sibling_petitions_service_id_account_id_reason_id_tag_id_index ON tag_sibling_petitions ( service_id, account_id, reason_id );' )
c.execute( 'CREATE INDEX tag_sibling_petitions_service_id_tag_id_hash_id_index ON tag_sibling_petitions ( service_id, old_tag_id );' )
c.execute( 'CREATE TABLE update_cache ( service_id INTEGER REFERENCES services ON DELETE CASCADE, begin INTEGER, end INTEGER, update_key TEXT, dirty INTEGER_BOOLEAN, PRIMARY KEY( service_id, begin ) );' )
c.execute( 'CREATE UNIQUE INDEX update_cache_service_id_end_index ON update_cache ( service_id, end );' )
c.execute( 'CREATE INDEX update_cache_service_id_dirty_index ON update_cache ( service_id, dirty );' )
@ -1760,6 +1778,31 @@ class DB( ServiceDB ):
if version < 71:
try: c.execute( 'CREATE INDEX mappings_account_id_index ON mappings ( account_id );' )
except: pass
try: c.execute( 'CREATE INDEX mappings_timestamp_index ON mappings ( timestamp );' )
except: pass
'''
c.execute( 'CREATE TABLE tag_siblings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, old_tag_id INTEGER, new_tag_id INTEGER, account_id INTEGER, timestamp INTEGER, PRIMARY KEY ( service_id, old_tag_id ) );' )
c.execute( 'CREATE INDEX tag_siblings_service_id_account_id_index ON tag_siblings ( service_id, account_id );' )
c.execute( 'CREATE INDEX tag_siblings_service_id_timestamp_index ON tag_siblings ( service_id, timestamp );' )
c.execute( 'CREATE TABLE pending_tag_siblings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, old_tag_id INTEGER, new_tag_id INTEGER, account_id INTEGER, timestamp INTEGER, PRIMARY KEY ( service_id, old_tag_id, account_id ) );' )
c.execute( 'CREATE INDEX pending_tag_siblings_service_id_account_id_index ON tag_siblings ( service_id, account_id );' )
c.execute( 'CREATE INDEX pending_tag_siblings_service_id_timestamp_index ON tag_siblings ( service_id, timestamp );' )
c.execute( 'CREATE TABLE deleted_tag_siblings ( service_id INTEGER REFERENCES services ON DELETE CASCADE, old_tag_id INTEGER, new_tag_id INTEGER, reason_id INTEGER, account_id INTEGER, admin_account_id INTEGER, timestamp INTEGER, PRIMARY KEY ( service_id, old_tag_id ) );' )
c.execute( 'CREATE INDEX deleted_tag_siblings_service_id_account_id_index ON deleted_tag_siblings ( service_id, account_id );' )
c.execute( 'CREATE INDEX deleted_tag_siblings_service_id_timestamp_index ON deleted_tag_siblings ( service_id, timestamp );' )
c.execute( 'CREATE TABLE tag_sibling_petitions ( service_id INTEGER REFERENCES services ON DELETE CASCADE, account_id INTEGER, old_tag_id INTEGER, new_tag_id INTEGER, reason_id INTEGER, PRIMARY KEY ( service_id, account_id, old_tag_id, reason_id ) );' )
c.execute( 'CREATE INDEX tag_sibling_petitions_service_id_account_id_reason_id_tag_id_index ON tag_sibling_petitions ( service_id, account_id, reason_id );' )
c.execute( 'CREATE INDEX tag_sibling_petitions_service_id_tag_id_hash_id_index ON tag_sibling_petitions ( service_id, old_tag_id );' )
'''
c.execute( 'UPDATE version SET version = ?;', ( HC.SOFTWARE_VERSION, ) )
c.execute( 'COMMIT' )

BIN
static/error.mp3 Normal file

Binary file not shown.

BIN
static/success.mp3 Normal file

Binary file not shown.