Version 253

This commit is contained in:
Hydrus Network Developer 2017-04-26 16:58:12 -05:00
parent d29afc70bd
commit f700ad6dc2
24 changed files with 1297 additions and 275 deletions

View File

@ -8,6 +8,33 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 253</h3></li>
<ul>
<li>created a new object to hold tag and rating merging and 'worse file' deletion options</li>
<li>wrote a dialog to edit this new object</li>
<li>established some simple defaults for this object for different duplicate status actions</li>
<li>the cog icon on the duplicates filter now lets you edit these defaults</li>
<li>the 'custom' duplicate filter action now works--first by asking you what status you want to set, and then by throwing up the new merge options dialog to tune it to whatever you like</li>
<li>wrote comprehensive unit tests for the new object</li>
<li>fixed the super slow dupe filter launch time problem</li>
<li>added a 'known urls' submenu to thumbnail and browser canvas right-click menu that lists all known urls for a file with an option to launch or copy the url</li>
<li>added known urls' hosts to the top-right canvas details background, just below where known file repos are listed</li>
<li>added the same known urls' hosts as clickable hyperlinks to the ratings hover window that pops over that top-right area</li>
<li>added 'delete from deleted files' action to the local tags's service-wide update panel. it will limit the deletion to mappings that are currently on files that have been previously completely physically deleted from the program</li>
<li>fixed namespace filtering on service-wide update panel</li>
<li>the hentai foundry downloader broke, so the update code will pause all hf subs. the solution is not trivial (it is part of the downloader overhaul), but I will try to fix it soon</li>
<li>debuted a new question-mark help button to better explain .txt tag importing on the manual import tagging dialog and the manage import folders dialog</li>
<li>fixed a small potential error due to bad parsing in the 'page of images' downloader</li>
<li>fixed a typo bug that stopped the 'delete shortcuts set' action working in the manage shortcuts dialog</li>
<li>I may have fixed an issue where the server was sometimes not shutting cleanly with a keyboardinterrupt</li>
<li>fixed the media embed button not reliably updating its thumbnail</li>
<li>fixed an issue where a dummy animation bar was displaying on embed buttons that showed static images that included transparency</li>
<li>the serious db missing tag and hash states will now not throw an error but will inform/spam the user (and hence not prohibit a boot)</li>
<li>attempting to open a second manage tags frame from the media viewer will now instead put the focus on the first (previously, multiple manage tags frames could be made)</li>
<li>misc db code cleanup that should result in faster result building in certain situations</li>
<li>misc improvements</li>
<li>misc layout fixes</li>
</ul>
<li><h3>version 252</h3></li>
<ul>
<li>the duplicate filter now processes pairs in batches and hence supports 'back' actions to revisit decisions. you will be prompted every fifty or so pairs to commit and checkpoint your progress</li>

View File

@ -245,6 +245,8 @@ SHORTCUT_MOUSE_RIGHT = 1
SHORTCUT_MOUSE_MIDDLE = 2
SHORTCUT_MOUSE_SCROLL_UP = 3
SHORTCUT_MOUSE_SCROLL_DOWN = 4
SHORTCUT_MOUSE_SCROLL_LEFT = 5
SHORTCUT_MOUSE_SCROLL_RIGHT = 6
shortcut_mouse_string_lookup = {}
@ -253,6 +255,8 @@ shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_RIGHT ] = 'right-click'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_MIDDLE ] = 'middle-click'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_UP ] = 'scroll up'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_DOWN ] = 'scroll down'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_LEFT ] = 'scroll left'
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_RIGHT ] = 'scroll right'
SHORTCUT_TYPE_KEYBOARD = 0
SHORTCUT_TYPE_MOUSE = 1
@ -499,6 +503,7 @@ class GlobalBMPs( object ):
GlobalBMPs.cog = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'cog.png' ) )
GlobalBMPs.keyboard = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'keyboard.png' ) )
GlobalBMPs.help = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'help.png' ) )
GlobalBMPs.check = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'check.png' ) )
GlobalBMPs.pause = wx.Bitmap( os.path.join( HC.STATIC_DIR, 'pause.png' ) )

View File

@ -1073,6 +1073,11 @@ class DB( HydrusDB.HydrusDB ):
pairs_of_hash_ids.append( ( smaller_hash_id, larger_hash_id ) )
if len( pairs_of_hash_ids ) >= MAX_BATCH_SIZE:
break
hash_ids_to_hashes = self._GetHashIdsToHashes( seen_hash_ids )
@ -1335,6 +1340,8 @@ class DB( HydrusDB.HydrusDB ):
with HydrusDB.TemporaryIntegerTable( self._c, rebalance_phash_ids, 'phash_id' ) as temp_table_name:
# can't turn this into selectfromlist due to the order clause. we need to do this all at once
( biggest_phash_id, ) = self._c.execute( 'SELECT phash_id FROM shape_vptree NATURAL JOIN ' + temp_table_name + ' ORDER BY inner_population + outer_population DESC;' ).fetchone()
@ -3386,7 +3393,9 @@ class DB( HydrusDB.HydrusDB ):
if result is None:
raise HydrusExceptions.DataMissing( 'File hash error in database' )
HydrusData.ShowText( 'Database hash error: hash_id ' + str( hash_id ) + ' was missing! This is a serious error that likely needs a repository reset to fix! Think about contacting hydrus dev!' )
return 'aaaaaaaaaaaaaaaa'.decode( 'hex' ) + os.urandom( 16 )
( hash, ) = result
@ -3394,7 +3403,10 @@ class DB( HydrusDB.HydrusDB ):
return hash
def _GetHashes( self, hash_ids ): return [ hash for ( hash, ) in self._c.execute( 'SELECT hash FROM hashes WHERE hash_id IN ' + HydrusData.SplayListForDB( hash_ids ) + ';' ) ]
def _GetHashes( self, hash_ids ):
return [ hash for ( hash, ) in self._c.execute( 'SELECT hash FROM hashes WHERE hash_id IN ' + HydrusData.SplayListForDB( hash_ids ) + ';' ) ]
def _GetHashId( self, hash ):
@ -3406,7 +3418,10 @@ class DB( HydrusDB.HydrusDB ):
hash_id = self._c.lastrowid
else: ( hash_id, ) = result
else:
( hash_id, ) = result
return hash_id
@ -4431,15 +4446,6 @@ class DB( HydrusDB.HydrusDB ):
return value
def _GetKnownURLs( self, hash ):
hash_id = self._GetHashId( hash )
urls = [ url for ( url, ) in self._c.execute( 'SELECT url FROM urls WHERE hash_id = ?;', ( hash_id, ) ) ]
return urls
def _GetMD5Status( self, md5 ):
result = self._c.execute( 'SELECT hash_id FROM local_hashes WHERE md5 = ?;', ( sqlite3.Binary( md5 ), ) ).fetchone()
@ -4460,47 +4466,44 @@ class DB( HydrusDB.HydrusDB ):
# get first detailed results
with HydrusDB.TemporaryIntegerTable( self._c, hash_ids, 'hash_id' ) as temp_table_name:
hash_ids_to_hashes = self._GetHashIdsToHashes( hash_ids )
hash_ids_to_info = { hash_id : ( size, mime, width, height, duration, num_frames, num_words ) for ( hash_id, size, mime, width, height, duration, num_frames, num_words ) in self._SelectFromList( 'SELECT * FROM files_info WHERE hash_id IN %s;', hash_ids ) }
hash_ids_to_current_file_service_ids_and_timestamps = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, timestamp ) ) for ( hash_id, service_id, timestamp ) in self._SelectFromList( 'SELECT hash_id, service_id, timestamp FROM current_files WHERE hash_id IN %s;', hash_ids ) ) )
hash_ids_to_deleted_file_service_ids = HydrusData.BuildKeyToListDict( self._SelectFromList( 'SELECT hash_id, service_id FROM deleted_files WHERE hash_id IN %s;', hash_ids ) )
hash_ids_to_pending_file_service_ids = HydrusData.BuildKeyToListDict( self._SelectFromList( 'SELECT hash_id, service_id FROM file_transfers WHERE hash_id IN %s;', hash_ids ) )
hash_ids_to_petitioned_file_service_ids = HydrusData.BuildKeyToListDict( self._SelectFromList( 'SELECT hash_id, service_id FROM file_petitions WHERE hash_id IN %s;', hash_ids ) )
hash_ids_to_urls = HydrusData.BuildKeyToListDict( self._SelectFromList( 'SELECT hash_id, url FROM urls WHERE hash_id IN %s;', hash_ids ) )
hash_ids_to_service_ids_and_filenames = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, filename ) ) for ( hash_id, service_id, filename ) in self._SelectFromList( 'SELECT hash_id, service_id, filename FROM service_filenames WHERE hash_id IN %s;', hash_ids ) ) )
hash_ids_to_local_ratings = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, rating ) ) for ( service_id, hash_id, rating ) in self._SelectFromList( 'SELECT service_id, hash_id, rating FROM local_ratings WHERE hash_id IN %s;', hash_ids ) ) )
tag_data = []
tag_service_ids = self._GetServiceIds( HC.TAG_SERVICES )
for tag_service_id in tag_service_ids:
hash_ids_to_info = { hash_id : ( size, mime, width, height, duration, num_frames, num_words ) for ( hash_id, size, mime, width, height, duration, num_frames, num_words ) in self._c.execute( 'SELECT * FROM files_info NATURAL JOIN ' + temp_table_name + ';' ) }
( current_mappings_table_name, deleted_mappings_table_name, pending_mappings_table_name, petitioned_mappings_table_name ) = GenerateMappingsTableNames( tag_service_id )
hash_ids_to_hashes = self._GetHashIdsToHashes( hash_ids )
hash_ids_to_current_file_service_ids_and_timestamps = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, timestamp ) ) for ( hash_id, service_id, timestamp ) in self._c.execute( 'SELECT hash_id, service_id, timestamp FROM current_files NATURAL JOIN ' + temp_table_name + ';' ) ) )
hash_ids_to_deleted_file_service_ids = HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT hash_id, service_id FROM deleted_files NATURAL JOIN ' + temp_table_name + ';' ) )
hash_ids_to_pending_file_service_ids = HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT hash_id, service_id FROM file_transfers NATURAL JOIN ' + temp_table_name + ';' ) )
hash_ids_to_petitioned_file_service_ids = HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT hash_id, service_id FROM file_petitions NATURAL JOIN ' + temp_table_name + ';' ) )
hash_ids_to_urls = HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT hash_id, url FROM urls NATURAL JOIN ' + temp_table_name + ';' ) )
hash_ids_to_service_ids_and_filenames = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, filename ) ) for ( hash_id, service_id, filename ) in self._c.execute( 'SELECT hash_id, service_id, filename FROM service_filenames NATURAL JOIN ' + temp_table_name + ';' ) ) )
hash_ids_to_local_ratings = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, rating ) ) for ( service_id, hash_id, rating ) in self._c.execute( 'SELECT service_id, hash_id, rating FROM local_ratings NATURAL JOIN ' + temp_table_name + ';' ) ) )
tag_data = []
tag_service_ids = self._GetServiceIds( HC.TAG_SERVICES )
for tag_service_id in tag_service_ids:
( current_mappings_table_name, deleted_mappings_table_name, pending_mappings_table_name, petitioned_mappings_table_name ) = GenerateMappingsTableNames( tag_service_id )
tag_data.extend( ( hash_id, ( tag_service_id, HC.CONTENT_STATUS_CURRENT, tag_id ) ) for ( hash_id, tag_id ) in self._c.execute( 'SELECT hash_id, tag_id FROM ' + current_mappings_table_name + ' NATURAL JOIN ' + temp_table_name + ';' ) )
tag_data.extend( ( hash_id, ( tag_service_id, HC.CONTENT_STATUS_DELETED, tag_id ) ) for ( hash_id, tag_id ) in self._c.execute( 'SELECT hash_id, tag_id FROM ' + deleted_mappings_table_name + ' NATURAL JOIN ' + temp_table_name + ';' ) )
tag_data.extend( ( hash_id, ( tag_service_id, HC.CONTENT_STATUS_PENDING, tag_id ) ) for ( hash_id, tag_id ) in self._c.execute( 'SELECT hash_id, tag_id FROM ' + pending_mappings_table_name + ' NATURAL JOIN ' + temp_table_name + ';' ) )
tag_data.extend( ( hash_id, ( tag_service_id, HC.CONTENT_STATUS_PETITIONED, tag_id ) ) for ( hash_id, tag_id ) in self._c.execute( 'SELECT hash_id, tag_id FROM ' + petitioned_mappings_table_name + ' NATURAL JOIN ' + temp_table_name + ';' ) )
seen_tag_ids = { tag_id for ( hash_id, ( tag_service_id, status, tag_id ) ) in tag_data }
hash_ids_to_raw_tag_data = HydrusData.BuildKeyToListDict( tag_data )
tag_ids_to_tags = self._GetTagIdsToTags( seen_tag_ids )
tag_data.extend( ( hash_id, ( tag_service_id, HC.CONTENT_STATUS_CURRENT, tag_id ) ) for ( hash_id, tag_id ) in self._SelectFromList( 'SELECT hash_id, tag_id FROM ' + current_mappings_table_name + ' WHERE hash_id IN %s;', hash_ids ) )
tag_data.extend( ( hash_id, ( tag_service_id, HC.CONTENT_STATUS_DELETED, tag_id ) ) for ( hash_id, tag_id ) in self._SelectFromList( 'SELECT hash_id, tag_id FROM ' + deleted_mappings_table_name + ' WHERE hash_id IN %s;', hash_ids ) )
tag_data.extend( ( hash_id, ( tag_service_id, HC.CONTENT_STATUS_PENDING, tag_id ) ) for ( hash_id, tag_id ) in self._SelectFromList( 'SELECT hash_id, tag_id FROM ' + pending_mappings_table_name + ' WHERE hash_id IN %s;', hash_ids ) )
tag_data.extend( ( hash_id, ( tag_service_id, HC.CONTENT_STATUS_PETITIONED, tag_id ) ) for ( hash_id, tag_id ) in self._SelectFromList( 'SELECT hash_id, tag_id FROM ' + petitioned_mappings_table_name + ' WHERE hash_id IN %s;', hash_ids ) )
seen_tag_ids = { tag_id for ( hash_id, ( tag_service_id, status, tag_id ) ) in tag_data }
hash_ids_to_raw_tag_data = HydrusData.BuildKeyToListDict( tag_data )
tag_ids_to_tags = self._GetTagIdsToTags( seen_tag_ids )
# build it
service_ids_to_service_keys = { service_id : service_key for ( service_id, service_key ) in self._c.execute( 'SELECT service_id, service_key FROM services;' ) }
@ -5330,7 +5333,9 @@ class DB( HydrusDB.HydrusDB ):
if result is None:
raise HydrusExceptions.DataMissing( 'Tag error in database' )
HydrusData.ShowText( 'Database tag error: tag_id ' + str( tag_id ) + ' was missing! This is a serious error that likely needs a tag repository reset to fix! Think about contacting hydrus dev!' )
return 'invalid namespace:invalid tag'
( namespace, subtag ) = result
@ -5348,7 +5353,9 @@ class DB( HydrusDB.HydrusDB ):
if len( results ) != len( tag_ids ):
raise HydrusExceptions.DataMissing( 'Tag error in database' )
HydrusData.ShowText( 'Database tag error: some tag_ids were missing! This is a serious error that likely needs a tag repository reset to fix! Think about contacting hydrus dev!' )
return [ 'invalid namespace:invalid tag ' + str( i ) for i in range( len( tag_ids ) ) ]
tags = [ HydrusTags.CombineTag( namespace, subtag ) for ( namespace, subtag ) in results ]
@ -6595,12 +6602,14 @@ class DB( HydrusDB.HydrusDB ):
( sub_action, sub_row ) = row
if sub_action in ( 'copy', 'delete', 'delete_deleted' ):
if sub_action in ( 'copy', 'delete', 'delete_deleted', 'delete_for_deleted_files' ):
self._c.execute( 'CREATE TEMPORARY TABLE temp_operation ( job_id INTEGER PRIMARY KEY AUTOINCREMENT, tag_id INTEGER, hash_id INTEGER );' )
( current_mappings_table_name, deleted_mappings_table_name, pending_mappings_table_name, petitioned_mappings_table_name ) = GenerateMappingsTableNames( service_id )
predicates = []
if sub_action == 'copy':
( tag, hashes, service_key_target ) = sub_row
@ -6619,10 +6628,16 @@ class DB( HydrusDB.HydrusDB ):
source_table_name = deleted_mappings_table_name
elif sub_action == 'delete_for_deleted_files':
( tag, hashes ) = sub_row
source_table_name = current_mappings_table_name + ' NATURAL JOIN deleted_files'
predicates.append( 'deleted_files.service_id = ' + str( self._combined_local_file_service_id ) )
predicates = []
do_tags_join = False
do_namespace_join = False
if tag is not None:
@ -6636,7 +6651,7 @@ class DB( HydrusDB.HydrusDB ):
elif tag_type == 'namespace':
do_tags_join = True
do_namespace_join = True
namespace = tag
@ -6646,21 +6661,21 @@ class DB( HydrusDB.HydrusDB ):
elif tag_type == 'namespaced':
do_tags_join = True
do_namespace_join = True
predicates.append( 'namespace_id != ' + str( self._null_namespace_id ) )
elif tag_type == 'unnamespaced':
do_tags_join = True
do_namespace_join = True
predicates.append( 'namespace_id = ' + str( self._null_namespace_id ) )
if do_tags_join:
if do_namespace_join:
source_table_name = source_table_name + ' NATURAL JOIN tags'
source_table_name = source_table_name + ' NATURAL JOIN namespaces'
if hashes is not None:
@ -6710,7 +6725,7 @@ class DB( HydrusDB.HydrusDB ):
self._UpdateMappings( service_id_target, **kwargs )
elif sub_action == 'delete':
elif sub_action in ( 'delete', 'delete_for_deleted_files' ):
self._UpdateMappings( service_id, deleted_mappings_ids = advanced_mappings_ids )
@ -6875,7 +6890,6 @@ class DB( HydrusDB.HydrusDB ):
notify_new_parents = True
elif data_type == HC.CONTENT_TYPE_TAG_SIBLINGS:
if action in ( HC.CONTENT_UPDATE_ADD, HC.CONTENT_UPDATE_DELETE ):
@ -6965,6 +6979,7 @@ class DB( HydrusDB.HydrusDB ):
notify_new_siblings = True
elif service_type in HC.RATINGS_SERVICES:
if action == HC.CONTENT_UPDATE_ADD:
@ -7267,7 +7282,6 @@ class DB( HydrusDB.HydrusDB ):
elif action == 'hydrus_sessions': result = self._GetHydrusSessions( *args, **kwargs )
elif action == 'imageboards': result = self._GetYAMLDump( YAML_DUMP_ID_IMAGEBOARD, *args, **kwargs )
elif action == 'is_an_orphan': result = self._IsAnOrphan( *args, **kwargs )
elif action == 'known_urls': result = self._GetKnownURLs( *args, **kwargs )
elif action == 'load_into_disk_cache': result = self._LoadIntoDiskCache( *args, **kwargs )
elif action == 'local_booru_share_keys': result = self._GetYAMLDumpNames( YAML_DUMP_ID_LOCAL_BOORU )
elif action == 'local_booru_share': result = self._GetYAMLDump( YAML_DUMP_ID_LOCAL_BOORU, *args, **kwargs )
@ -8744,6 +8758,44 @@ class DB( HydrusDB.HydrusDB ):
self._SetJSONDump( media_viewer )
if version == 252:
try:
do_the_message = False
subscriptions = self._GetJSONDumpNamed( HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION )
for subscription in subscriptions:
g_i = subscription._gallery_identifier
if g_i.GetSiteType() in ( HC.SITE_TYPE_HENTAI_FOUNDRY_ARTIST, HC.SITE_TYPE_HENTAI_FOUNDRY_ARTIST_PICTURES, HC.SITE_TYPE_HENTAI_FOUNDRY_ARTIST_SCRAPS, HC.SITE_TYPE_HENTAI_FOUNDRY_TAGS ):
subscription._paused = True
self._SetJSONDump( subscription )
do_the_message = True
if do_the_message:
message = 'The Hentai Foundry downloader broke around version 243, so all of your Hentai Foundry subscriptions have been paused!'
self.pub_initial_message( message )
except Exception as e:
HydrusData.Print( 'While attempting to pause all hentai foundry subs, I had this problem:' )
HydrusData.PrintException( e )
self._c.execute( 'UPDATE duplicate_pairs SET duplicate_type = ? WHERE duplicate_type != ?;', ( HC.DUPLICATE_UNKNOWN, HC.DUPLICATE_UNKNOWN ) )
self._controller.pub( 'splash_set_title_text', 'updated db to v' + str( version + 1 ) )
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )

View File

@ -684,6 +684,15 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
#
self._dictionary[ 'duplicate_action_options' ] = HydrusSerialisable.SerialisableDictionary()
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_BETTER ] = DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE ) ], True )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_SAME_FILE ] = DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ) ], False )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_ALTERNATE ] = DuplicateActionOptions( [], False )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_NOT_DUPLICATE ] = DuplicateActionOptions( [], False )
#
self._dictionary[ 'integers' ] = {}
self._dictionary[ 'integers' ][ 'video_buffer_size_mb' ] = 96
@ -1068,6 +1077,14 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
def GetDuplicateActionOptions( self, duplicate_status ):
with self._lock:
return self._dictionary[ 'duplicate_action_options' ][ duplicate_status ]
def GetFrameLocation( self, frame_key ):
with self._lock:
@ -1249,6 +1266,14 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
def SetDuplicateActionOptions( self, duplicate_status, duplicate_action_options ):
with self._lock:
self._dictionary[ 'duplicate_action_options' ][ duplicate_status ] = duplicate_action_options
def SetFrameLocation( self, frame_key, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ):
with self._lock:
@ -1378,6 +1403,216 @@ class Credentials( HydrusData.HydrusYAMLBase ):
def SetAccessKey( self, access_key ): self._access_key = access_key
class DuplicateActionOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_DUPLICATE_ACTION_OPTIONS
SERIALISABLE_VERSION = 1
def __init__( self, service_actions = None, delete_second_file = None ):
if service_actions is None:
service_actions = []
if delete_second_file is None:
delete_second_file = False
HydrusSerialisable.SerialisableBase.__init__( self )
self._service_actions = service_actions
self._delete_second_file = delete_second_file
def _GetSerialisableInfo( self ):
serialisable_service_actions = [ ( service_key.encode( 'hex' ), action ) for ( service_key, action ) in self._service_actions ]
return ( serialisable_service_actions, self._delete_second_file )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( serialisable_service_actions, self._delete_second_file ) = serialisable_info
self._service_actions = [ ( serialisable_service_key.decode( 'hex' ), action ) for ( serialisable_service_key, action ) in serialisable_service_actions ]
def SetTuple( self, service_actions, delete_second_file ):
self._service_actions = service_actions
self._delete_second_file = delete_second_file
def ToTuple( self ):
return ( self._service_actions, self._delete_second_file )
def ProcessPairIntoContentUpdates( self, first_media, second_media ):
content_service_keys_to_content_updates = {}
file_service_keys_to_content_updates = {}
first_hashes = first_media.GetHashes()
second_hashes = second_media.GetHashes()
services_manager = HydrusGlobals.client_controller.GetServicesManager()
for ( service_key, action ) in self._service_actions:
content_updates = []
try:
service = services_manager.GetService( service_key )
except HydrusExceptions.DataMissing:
continue
service_type = service.GetServiceType()
if service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
first_current_value = first_media.GetRatingsManager().GetRating( service_key )
second_current_value = second_media.GetRatingsManager().GetRating( service_key )
if action == HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE:
if first_current_value == second_current_value:
continue
if first_current_value is None and second_current_value is not None:
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( second_current_value, first_hashes ) ) )
elif first_current_value is not None and second_current_value is None:
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( first_current_value, second_hashes ) ) )
elif action == HC.CONTENT_MERGE_ACTION_COPY:
if first_current_value == second_current_value:
continue
if first_current_value is None and second_current_value is not None:
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( second_current_value, first_hashes ) ) )
elif action == HC.CONTENT_MERGE_ACTION_MOVE:
if second_current_value is not None:
if first_current_value is None:
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( second_current_value, first_hashes ) ) )
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( None, second_hashes ) ) )
elif service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ):
if service_type == HC.LOCAL_TAG:
add_content_action = HC.CONTENT_UPDATE_ADD
elif service_type == HC.TAG_REPOSITORY:
add_content_action = HC.CONTENT_UPDATE_PEND
first_current_tags = first_media.GetTagsManager().GetCurrent( service_key )
second_current_tags = second_media.GetTagsManager().GetCurrent( service_key )
if action == HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE:
first_needs = second_current_tags.difference( first_current_tags )
second_needs = first_current_tags.difference( second_current_tags )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, add_content_action, ( tag, first_hashes ) ) for tag in first_needs ) )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, add_content_action, ( tag, second_hashes ) ) for tag in second_needs ) )
elif action == HC.CONTENT_MERGE_ACTION_COPY:
first_needs = second_current_tags.difference( first_current_tags )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, add_content_action, ( tag, first_hashes ) ) for tag in first_needs ) )
elif service_type == HC.LOCAL_TAG and action == HC.CONTENT_MERGE_ACTION_MOVE:
first_needs = second_current_tags.difference( first_current_tags )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, add_content_action, ( tag, first_hashes ) ) for tag in first_needs ) )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_DELETE, ( tag, second_hashes ) ) for tag in second_current_tags ) )
if len( content_updates ) > 0:
content_service_keys_to_content_updates[ service_key ] = content_updates
if self._delete_second_file:
current_locations = second_media.GetLocationsManager().GetCurrent()
if CC.LOCAL_FILE_SERVICE_KEY in current_locations:
deletee_service_key = CC.LOCAL_FILE_SERVICE_KEY
elif CC.TRASH_SERVICE_KEY in current_locations:
deletee_service_key = CC.TRASH_SERVICE_KEY
else:
deletee_service_key = None
if deletee_service_key is not None:
if deletee_service_key not in file_service_keys_to_content_updates:
file_service_keys_to_content_updates[ deletee_service_key ] = []
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, second_hashes )
file_service_keys_to_content_updates[ deletee_service_key ].append( content_update )
list_of_service_keys_to_content_updates = []
if len( content_service_keys_to_content_updates ) > 0:
list_of_service_keys_to_content_updates.append( content_service_keys_to_content_updates )
if len( file_service_keys_to_content_updates ) > 0:
list_of_service_keys_to_content_updates.append( file_service_keys_to_content_updates )
return list_of_service_keys_to_content_updates
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_DUPLICATE_ACTION_OPTIONS ] = DuplicateActionOptions
class Imageboard( HydrusData.HydrusYAMLBase ):
yaml_tag = u'!Imageboard'

View File

@ -1311,7 +1311,7 @@ class GalleryHentaiFoundry( Gallery ):
# can't parse this easily normally because HF is a pain with the preview->click to see full size business.
# find http://pictures.hentai-foundry.com//
# then extend it to http://pictures.hentai-foundry.com//k/KABOS/172144.jpg
# then extend it to http://pictures.hentai-foundry.com//k/KABOS/172144/image.jpg
# the .jpg bit is what we really need, but whatever
try:

View File

@ -24,6 +24,8 @@ import HydrusPaths
import HydrusSerialisable
import HydrusTags
import os
import urlparse
import webbrowser
import wx
if HC.PLATFORM_WINDOWS: import wx.lib.flashwin
@ -1162,6 +1164,8 @@ class Canvas( wx.Window ):
self._dirty = True
self._closing = False
self._manage_tags_panel = None
self._service_keys_to_services = {}
self._current_media = None
@ -1460,7 +1464,11 @@ class Canvas( wx.Window ):
def _ManageTags( self ):
if self._current_media is not None:
if self._manage_tags_panel:
self._manage_tags_panel.SetFocus()
elif self._current_media is not None:
# take any focus away from hover window, which will mess up window order when it hides due to the new frame
self.SetFocus()
@ -1474,6 +1482,8 @@ class Canvas( wx.Window ):
manage_tags.SetPanel( panel )
self._manage_tags_panel = panel
def _OpenExternally( self ):
@ -2287,6 +2297,31 @@ class CanvasPanel( Canvas ):
ClientGUIMenus.AppendMenuItem( self, menu, 'open externally', 'Open the file in your OS\'s default program.', self._OpenExternally )
urls = self._current_media.GetLocationsManager().GetURLs()
if len( urls ) > 0:
urls = list( urls )
urls.sort()
urls_menu = wx.Menu()
urls_visit_menu = wx.Menu()
urls_copy_menu = wx.Menu()
for url in urls:
ClientGUIMenus.AppendMenuItem( self, urls_visit_menu, url, 'Open this url in your web browser.', webbrowser.open, url )
ClientGUIMenus.AppendMenuItem( self, urls_copy_menu, url, 'Copy this url to your clipboard.', HydrusGlobals.client_controller.pub, 'clipboard', 'text', url )
ClientGUIMenus.AppendMenu( urls_menu, urls_visit_menu, 'open' )
ClientGUIMenus.AppendMenu( urls_menu, urls_copy_menu, 'copy' )
ClientGUIMenus.AppendMenu( menu, urls_menu, 'known urls' )
share_menu = wx.Menu()
copy_menu = wx.Menu()
@ -2472,6 +2507,27 @@ class CanvasWithDetails( Canvas ):
current_y += text_height + 4
# urls
urls = self._current_media.GetLocationsManager().GetURLs()
urls = list( urls )
urls.sort()
for url in urls:
parse = urlparse.urlparse( url )
url_string = parse.scheme + '://' + parse.hostname
( text_width, text_height ) = dc.GetTextExtent( url_string )
dc.DrawText( url_string, client_width - text_width - 3, current_y )
current_y += text_height + 4
# ratings
services_manager = HydrusGlobals.client_controller.GetServicesManager()
@ -2647,9 +2703,19 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
def _CommitProcessed( self ):
for ( hash_pair, duplicate_status, hash_a, hash_b, merge_options ) in self._processed_pairs:
for ( hash_pair, duplicate_status, first_media, second_media, duplicate_action_options ) in self._processed_pairs:
HydrusGlobals.client_controller.WriteSynchronous( 'duplicate_pair_status', duplicate_status, hash_a, hash_b, merge_options )
list_of_service_keys_to_content_updates = duplicate_action_options.ProcessPairIntoContentUpdates( first_media, second_media )
for service_keys_to_content_updates in list_of_service_keys_to_content_updates:
HydrusGlobals.client_controller.Write( 'content_updates', service_keys_to_content_updates )
first_hash = first_media.GetHash()
second_hash = second_media.GetHash()
HydrusGlobals.client_controller.WriteSynchronous( 'duplicate_pair_status', duplicate_status, first_hash, second_hash, duplicate_action_options )
self._processed_pairs = []
@ -2662,16 +2728,35 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
def _DoCustomAction( self ):
wx.MessageBox( 'This doesn\'t do anything yet!' )
duplicate_statuses = [ HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_FILE, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_NOT_DUPLICATE ]
return
choice_tuples = [ ( HC.duplicate_status_string_lookup[ duplicate_status ], duplicate_status ) for duplicate_status in duplicate_statuses ]
# ( duplicate_status, hash_a, hash_b, merge_options ) = panel.getvalue()
# HydrusGlobals.client_controller.WriteSynchronous( 'duplicate_pair_status', duplicate_status, hash_a, hash_b, merge_options )
# launch the dialog to choose exactly what happens
# if OK on that:
self._ShowNewPair()
with ClientGUIDialogs.DialogSelectFromList( self, 'select duplicate_status', choice_tuples ) as dlg_1:
if dlg_1.ShowModal() == wx.ID_OK:
duplicate_status = dlg_1.GetChoice()
new_options = HydrusGlobals.client_controller.GetNewOptions()
duplicate_action_options = new_options.GetDuplicateActionOptions( duplicate_status )
with ClientGUITopLevelWindows.DialogEdit( self, 'edit duplicate merge options' ) as dlg_2:
panel = ClientGUIScrolledPanelsEdit.EditDuplicateActionOptionsPanel( dlg_2, duplicate_status, duplicate_action_options )
dlg_2.SetPanel( panel )
if dlg_2.ShowModal() == wx.ID_OK:
duplicate_action_options = panel.GetValue()
self._ProcessPair( duplicate_status, duplicate_action_options )
def _GenerateHoverTopFrame( self ):
@ -2698,20 +2783,13 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
def _GetMergeOptions( self, duplicate_status ):
# fetch it from client_options, given a status
return None
def _GoBack( self ):
if len( self._processed_pairs ) > 0:
self._unprocessed_pairs.append( self._current_pair )
( hash_pair, duplicate_status, hash_a, hash_b, merge_options ) = self._processed_pairs.pop()
( hash_pair, duplicate_status, first_media, second_media, duplicate_action_options ) = self._processed_pairs.pop()
self._unprocessed_pairs.append( hash_pair )
@ -2791,16 +2869,18 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
return command_processed
def _ProcessPair( self, duplicate_status ):
def _ProcessPair( self, duplicate_status, duplicate_action_options = None ):
if duplicate_action_options is None:
new_options = HydrusGlobals.client_controller.GetNewOptions()
duplicate_action_options = new_options.GetDuplicateActionOptions( duplicate_status )
other_media = self._media_list.GetNext( self._current_media )
hash_a = self._current_media.GetHash()
hash_b = other_media.GetHash()
merge_options = self._GetMergeOptions( HC.DUPLICATE_SAME_FILE )
self._processed_pairs.append( ( self._current_pair, duplicate_status, hash_a, hash_b, merge_options ) )
self._processed_pairs.append( ( self._current_pair, duplicate_status, self._current_media, other_media, duplicate_action_options ) )
self._ShowNewPair()
@ -2821,7 +2901,7 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
else:
( hash_pair, duplicate_status, hash_a, hash_b, merge_options ) = self._processed_pairs.pop()
( hash_pair, duplicate_status, first_media, second_media, duplicate_action_options ) = self._processed_pairs.pop()
self._unprocessed_pairs.append( hash_pair )
@ -3942,146 +4022,174 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
def EventShowMenu( self, event ):
services = HydrusGlobals.client_controller.GetServicesManager().GetServices()
local_ratings_services = [ service for service in services if service.GetServiceType() in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) ]
i_can_post_ratings = len( local_ratings_services ) > 0
self._last_drag_coordinates = None # to stop successive right-click drag warp bug
locations_manager = self._current_media.GetLocationsManager()
menu = wx.Menu()
for line in self._current_media.GetPrettyInfoLines():
if self._current_media is not None:
menu.Append( CC.ID_NULL, line )
services = HydrusGlobals.client_controller.GetServicesManager().GetServices()
ClientGUIMenus.AppendSeparator( menu )
if self._IsZoomable():
local_ratings_services = [ service for service in services if service.GetServiceType() in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) ]
menu.Append( CC.ID_NULL, 'current zoom: ' + ClientData.ConvertZoomToPercentage( self._current_zoom ) )
i_can_post_ratings = len( local_ratings_services ) > 0
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'zoom_in' ), 'zoom in' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'zoom_out' ), 'zoom out' )
self._last_drag_coordinates = None # to stop successive right-click drag warp bug
if self._current_media.GetMime() != HC.APPLICATION_FLASH:
locations_manager = self._current_media.GetLocationsManager()
menu = wx.Menu()
for line in self._current_media.GetPrettyInfoLines():
( my_width, my_height ) = self.GetClientSize()
( media_width, media_height ) = self._current_media.GetResolution()
if self._current_zoom == 1.0:
if media_width > my_width or media_height > my_height:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'switch_between_100_percent_and_canvas_zoom' ), 'zoom fit' )
else:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'switch_between_100_percent_and_canvas_zoom' ), 'zoom full' )
menu.Append( CC.ID_NULL, line )
ClientGUIMenus.AppendSeparator( menu )
if i_can_post_ratings:
if self._IsZoomable():
menu.Append( CC.ID_NULL, 'current zoom: ' + ClientData.ConvertZoomToPercentage( self._current_zoom ) )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'zoom_in' ), 'zoom in' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'zoom_out' ), 'zoom out' )
if self._current_media.GetMime() != HC.APPLICATION_FLASH:
( my_width, my_height ) = self.GetClientSize()
( media_width, media_height ) = self._current_media.GetResolution()
if self._current_zoom == 1.0:
if media_width > my_width or media_height > my_height:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'switch_between_100_percent_and_canvas_zoom' ), 'zoom fit' )
else:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'switch_between_100_percent_and_canvas_zoom' ), 'zoom full' )
ClientGUIMenus.AppendSeparator( menu )
manage_menu = wx.Menu()
if i_can_post_ratings:
manage_menu = wx.Menu()
manage_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'manage_file_tags' ), 'tags' )
manage_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'manage_file_ratings' ), 'ratings' )
menu.AppendMenu( CC.ID_NULL, 'manage', manage_menu )
else:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'manage_file_tags' ), 'manage tags' )
manage_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'manage_file_tags' ), 'tags' )
manage_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'manage_file_ratings' ), 'ratings' )
ClientGUIMenus.AppendSeparator( menu )
menu.AppendMenu( CC.ID_NULL, 'manage', manage_menu )
if self._current_media.HasInbox(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'archive_file' ), '&archive' )
if self._current_media.HasArchive(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'inbox_file' ), 'return to &inbox' )
else:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'remove_file_from_view' ), '&remove' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'manage_file_tags' ), 'manage tags' )
if CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'delete_file', CC.LOCAL_FILE_SERVICE_KEY ), '&delete' )
elif CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'delete_file', CC.TRASH_SERVICE_KEY ), '&delete from trash now' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'undelete' ), '&undelete' )
ClientGUIMenus.AppendSeparator( menu )
if self._current_media.HasInbox(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'archive_file' ), '&archive' )
if self._current_media.HasArchive(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'inbox_file' ), 'return to &inbox' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'remove_file_from_view' ), '&remove' )
if CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent():
ClientGUIMenus.AppendSeparator( menu )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'delete_file', CC.LOCAL_FILE_SERVICE_KEY ), '&delete' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'open_file_in_external_program' ), '&open externally' )
elif CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent():
urls = self._current_media.GetLocationsManager().GetURLs()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'delete_file', CC.TRASH_SERVICE_KEY ), '&delete from trash now' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'undelete' ), '&undelete' )
if len( urls ) > 0:
urls = list( urls )
urls.sort()
urls_menu = wx.Menu()
urls_visit_menu = wx.Menu()
urls_copy_menu = wx.Menu()
for url in urls:
ClientGUIMenus.AppendMenuItem( self, urls_visit_menu, url, 'Open this url in your web browser.', webbrowser.open, url )
ClientGUIMenus.AppendMenuItem( self, urls_copy_menu, url, 'Copy this url to your clipboard.', HydrusGlobals.client_controller.pub, 'clipboard', 'text', url )
ClientGUIMenus.AppendMenu( urls_menu, urls_visit_menu, 'open' )
ClientGUIMenus.AppendMenu( urls_menu, urls_copy_menu, 'copy' )
ClientGUIMenus.AppendMenu( menu, urls_menu, 'known urls' )
ClientGUIMenus.AppendSeparator( menu )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'open_file_in_external_program' ), '&open externally' )
share_menu = wx.Menu()
copy_menu = wx.Menu()
copy_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_files' ), 'file' )
copy_hash_menu = wx.Menu()
copy_hash_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_hash', 'sha256' ) , 'sha256 (hydrus default)' )
copy_hash_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_hash', 'md5' ) , 'md5' )
copy_hash_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_hash', 'sha1' ) , 'sha1' )
copy_hash_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_hash', 'sha512' ) , 'sha512' )
copy_menu.AppendMenu( CC.ID_NULL, 'hash', copy_hash_menu )
if self._current_media.GetMime() in HC.IMAGES and self._current_media.GetDuration() is None:
share_menu = wx.Menu()
copy_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_bmp' ), 'image' )
copy_menu = wx.Menu()
copy_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_path' ), 'path' )
share_menu.AppendMenu( CC.ID_NULL, 'copy', copy_menu )
menu.AppendMenu( CC.ID_NULL, 'share', share_menu )
ClientGUIMenus.AppendSeparator( menu )
slideshow = wx.Menu()
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 1000 ), '1 second' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 5000 ), '5 seconds' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 10000 ), '10 seconds' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 30000 ), '30 seconds' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 60000 ), '60 seconds' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 80 ), 'william gibson' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow' ), 'custom interval' )
menu.AppendMenu( CC.ID_NULL, 'start slideshow', slideshow )
if self._timer_slideshow.IsRunning():
copy_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_files' ), 'file' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow_pause_play' ), 'stop slideshow' )
copy_hash_menu = wx.Menu()
ClientGUIMenus.AppendSeparator( menu )
if self.GetParent().IsFullScreen():
copy_hash_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_hash', 'sha256' ) , 'sha256 (hydrus default)' )
copy_hash_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_hash', 'md5' ) , 'md5' )
copy_hash_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_hash', 'sha1' ) , 'sha1' )
copy_hash_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_hash', 'sha512' ) , 'sha512' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'switch_between_fullscreen_borderless_and_regular_framed_window' ), 'exit fullscreen' )
copy_menu.AppendMenu( CC.ID_NULL, 'hash', copy_hash_menu )
else:
if self._current_media.GetMime() in HC.IMAGES and self._current_media.GetDuration() is None:
copy_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_bmp' ), 'image' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'switch_between_fullscreen_borderless_and_regular_framed_window' ), 'go fullscreen' )
copy_menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_path' ), 'path' )
share_menu.AppendMenu( CC.ID_NULL, 'copy', copy_menu )
menu.AppendMenu( CC.ID_NULL, 'share', share_menu )
ClientGUIMenus.AppendSeparator( menu )
slideshow = wx.Menu()
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 1000 ), '1 second' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 5000 ), '5 seconds' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 10000 ), '10 seconds' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 30000 ), '30 seconds' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 60000 ), '60 seconds' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow', 80 ), 'william gibson' )
slideshow.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow' ), 'custom interval' )
menu.AppendMenu( CC.ID_NULL, 'start slideshow', slideshow )
if self._timer_slideshow.IsRunning():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'slideshow_pause_play' ), 'stop slideshow' )
ClientGUIMenus.AppendSeparator( menu )
if self.GetParent().IsFullScreen():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'switch_between_fullscreen_borderless_and_regular_framed_window' ), 'exit fullscreen' )
else:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'switch_between_fullscreen_borderless_and_regular_framed_window' ), 'go fullscreen' )
HydrusGlobals.client_controller.PopupMenu( self, menu )
HydrusGlobals.client_controller.PopupMenu( self, menu )
event.Skip()
@ -4256,6 +4364,7 @@ class MediaContainer( wx.Window ):
if self._media_window is None:
self._embed_button.SetSize( ( my_width, my_height ) )
self._embed_button.SetPosition( ( 0, 0 ) )
else:
@ -4492,10 +4601,13 @@ class EmbedButton( wx.Window ):
if self._thumbnail_bmp is not None:
# animations will have the animation bar space underneath in this case, so colour it in
dc.SetBackground( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
dc.DrawRectangle( 0, y - ANIMATED_SCANBAR_HEIGHT, x, ANIMATED_SCANBAR_HEIGHT )
if ShouldHaveAnimationBar( self._media ):
# animations will have the animation bar space underneath in this case, so colour it in
dc.SetBackground( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) )
dc.DrawRectangle( 0, y - ANIMATED_SCANBAR_HEIGHT, x, ANIMATED_SCANBAR_HEIGHT )
( thumb_width, thumb_height ) = self._thumbnail_bmp.GetSize()
@ -4542,7 +4654,12 @@ class EmbedButton( wx.Window ):
dc.DrawRectangle( 0, 0, x, y )
self._dirty = False
def _SetDirty( self ):
self._dirty = True
self.Refresh()
def EventEraseBackground( self, event ):
@ -4589,9 +4706,7 @@ class EmbedButton( wx.Window ):
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
self._dirty = True
self.Refresh()
self._SetDirty()
@ -4617,6 +4732,8 @@ class EmbedButton( wx.Window ):
self._thumbnail_bmp = ClientRendering.GenerateHydrusBitmap( thumbnail_path ).GetWxBitmap()
self._SetDirty()
else:
self._thumbnail_bmp = None

View File

@ -310,9 +310,9 @@ class BetterRadioBox( wx.RadioBox ):
class BetterStaticText( wx.StaticText ):
def __init__( self, parent, label = None ):
def __init__( self, parent, label = None, **kwargs ):
wx.StaticText.__init__( self, parent )
wx.StaticText.__init__( self, parent, **kwargs )
if label is not None:

View File

@ -2162,7 +2162,6 @@ class DialogPathsToTags( Dialog ):
self.SetSizer( vbox )
def _GetTags( self, index, path ):
tags = []
@ -2522,9 +2521,11 @@ class DialogPathsToTags( Dialog ):
self._checkboxes_panel = ClientGUICommon.StaticBox( self, 'misc' )
self._load_from_txt_files_checkbox = wx.CheckBox( self._checkboxes_panel, label = 'try to load tags from neighbouring .txt files' )
self._load_from_txt_files_checkbox.SetToolTipString( 'This looks for a [path].txt file, and will try to load line-separated tags from it. Look at thumbnail->share->export->files\'s txt file checkbox for an example.' )
self._load_from_txt_files_checkbox.Bind( wx.EVT_CHECKBOX, self.EventRefresh )
txt_files_help_button = ClientGUICommon.BetterBitmapButton( self._checkboxes_panel, CC.GlobalBMPs.help, self._ShowTXTHelp )
txt_files_help_button.SetToolTipString( 'Show help regarding importing tags from .txt files.' )
self._filename_namespace = wx.TextCtrl( self._checkboxes_panel )
self._filename_namespace.Bind( wx.EVT_TEXT, self.EventRefresh )
@ -2557,6 +2558,11 @@ class DialogPathsToTags( Dialog ):
self._single_tags_panel.AddF( self._single_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
self._single_tags_panel.AddF( self._single_tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
txt_hbox = wx.BoxSizer( wx.HORIZONTAL )
txt_hbox.AddF( self._load_from_txt_files_checkbox, CC.FLAGS_EXPAND_BOTH_WAYS )
txt_hbox.AddF( txt_files_help_button, CC.FLAGS_VCENTER )
filename_hbox = wx.BoxSizer( wx.HORIZONTAL )
filename_hbox.AddF( self._filename_checkbox, CC.FLAGS_EXPAND_BOTH_WAYS )
@ -2577,7 +2583,7 @@ class DialogPathsToTags( Dialog ):
dir_hbox_3.AddF( self._dir_checkbox_3, CC.FLAGS_EXPAND_BOTH_WAYS )
dir_hbox_3.AddF( self._dir_namespace_3, CC.FLAGS_EXPAND_BOTH_WAYS )
self._checkboxes_panel.AddF( self._load_from_txt_files_checkbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self._checkboxes_panel.AddF( txt_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._checkboxes_panel.AddF( filename_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._checkboxes_panel.AddF( dir_hbox_1, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._checkboxes_panel.AddF( dir_hbox_2, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
@ -2592,6 +2598,21 @@ class DialogPathsToTags( Dialog ):
self.SetSizer( hbox )
def _ShowTXTHelp( self ):
message = 'If you would like to add custom tags with your files, add a .txt file beside the file like so:'
message += os.linesep * 2
message += 'my_file.jpg'
message += os.linesep
message += 'my_file.jpg.txt'
message += os.linesep * 2
message += 'And include your tags inside the .txt file in a newline-separated list (if you know how to script, generating these files automatically from another source of tags can save a lot of time!).'
message += os.linesep * 2
message += 'Make sure you preview the results in the table above to be certain everything is parsing correctly. Until you are comfortable with this, you should test it on just one or two files.'
wx.MessageBox( message )
def EnterTags( self, tags ):
tag_parents_manager = HydrusGlobals.client_controller.GetManager( 'tag_parents' )
@ -2675,11 +2696,20 @@ class DialogPathsToTags( Dialog ):
txt_tags = [ HydrusData.ToUnicode( tag ) for tag in HydrusData.SplitByLinesep( txt_tags_string ) ]
if True in ( len( txt_tag ) > 1024 for txt_tag in txt_tags ):
HydrusData.ShowText( 'Tags were too long--I think this was not a regular text file!' )
raise Exception()
tags.extend( txt_tags )
except:
HydrusData.Print( 'Could not parse the tags from ' + txt_path + '!' )
HydrusData.ShowText( 'Could not parse the tags from ' + txt_path + '!' )
tags.append( '___had problem parsing .txt file' )

View File

@ -2543,6 +2543,9 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
self._txt_parse_button = wx.Button( self._tag_box, label = 'edit .txt parsing' )
self._txt_parse_button.Bind( wx.EVT_BUTTON, self.EventEditTxtParsing )
txt_files_help_button = ClientGUICommon.BetterBitmapButton( self._tag_box, CC.GlobalBMPs.help, self._ShowTXTHelp )
txt_files_help_button.SetToolTipString( 'Show help regarding importing tags from .txt files.' )
#
self._ok = wx.Button( self, label = 'ok' )
@ -2639,9 +2642,14 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
#
txt_hbox = wx.BoxSizer( wx.HORIZONTAL )
txt_hbox.AddF( self._txt_parse_button, CC.FLAGS_EXPAND_BOTH_WAYS )
txt_hbox.AddF( txt_files_help_button, CC.FLAGS_VCENTER )
self._tag_box.AddF( self._import_tag_options, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._tag_box.AddF( self._txt_parse_st, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._tag_box.AddF( self._txt_parse_button, CC.FLAGS_VCENTER )
self._tag_box.AddF( txt_hbox, CC.FLAGS_SIZER_VCENTER )
#
@ -2742,6 +2750,21 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
self._txt_parse_st.SetLabelText( text )
def _ShowTXTHelp( self ):
message = 'If you would like to add custom tags with your files, add a .txt file beside the file like so:'
message += os.linesep * 2
message += 'my_file.jpg'
message += os.linesep
message += 'my_file.jpg.txt'
message += os.linesep * 2
message += 'And include your tags inside the .txt file in a newline-separated list (if you know how to script, generating these files automatically from another source of tags can save a lot of time!).'
message += os.linesep * 2
message += 'If you are not absolutely comfortable with this, practise it through the manual import process.'
wx.MessageBox( message )
def EventCheckLocations( self, event ):
self._CheckLocations()

View File

@ -11,6 +11,7 @@ import HydrusConstants as HC
import HydrusData
import HydrusGlobals
import HydrusSerialisable
import urlparse
import os
import wx
@ -402,7 +403,7 @@ class FullscreenHoverFrameTop( FullscreenHoverFrame ):
return
with ClientGUITopLevelWindows.DialogEdit( self, 'manage shortcuts' ) as dlg:
with ClientGUITopLevelWindows.DialogEdit( self, 'choose shortcuts' ) as dlg:
choice_tuples = [ ( name, name, name in default_media_viewer_custom_shortcuts ) for name in custom_shortcuts_names ]
@ -499,22 +500,12 @@ class FullscreenHoverFrameTopDuplicatesFilter( FullscreenHoverFrameTop ):
def _PopulateCenterButtons( self ):
# make a merge_options serialisable object to track all this that I can plug into the dialog and obey at the db level
# store two in the options, for better and same, and then the custom just displays/produces one depending on chosen status
# cog icon launches a dialog
# file delete on better
# extend these to be per-service!
# rating merging on better (d NO) (these are mutually exclusive, so add a radio menu type or whatever)
# rating moving on better (d YES)
# rating merging on same (d YES)
# tag merging on better (d NO) (these are mutually exclusive, so add a radio menu type or whatever)
# tag moving on better (d YES)
# tag merging on same (d YES)
menu_items = []
menu_items.append( ( 'normal', 'edit tag/ratings merge options and whether to delete bad files', 'edit what happens when you filter files', self._EditMergeOptions ) )
menu_items.append( ( 'normal', 'edit duplicate action options for \'this is better\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_BETTER ) ) )
menu_items.append( ( 'normal', 'edit duplicate action options for \'exact duplicates\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_SAME_FILE ) ) )
menu_items.append( ( 'normal', 'edit duplicate action options for \'alternates\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_ALTERNATE ) ) )
menu_items.append( ( 'normal', 'edit duplicate action options for \'not duplicates\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_NOT_DUPLICATE ) ) )
cog_button = ClientGUICommon.MenuBitmapButton( self, CC.GlobalBMPs.cog, menu_items )
@ -523,9 +514,25 @@ class FullscreenHoverFrameTopDuplicatesFilter( FullscreenHoverFrameTop ):
FullscreenHoverFrameTop._PopulateCenterButtons( self )
def _EditMergeOptions( self ):
def _EditMergeOptions( self, duplicate_status ):
wx.MessageBox( 'This doesn\'t do anything yet!' )
new_options = HydrusGlobals.client_controller.GetNewOptions()
duplicate_action_options = new_options.GetDuplicateActionOptions( duplicate_status )
with ClientGUITopLevelWindows.DialogEdit( self, 'edit duplicate merge options' ) as dlg:
panel = ClientGUIScrolledPanelsEdit.EditDuplicateActionOptionsPanel( dlg, duplicate_status, duplicate_action_options )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
duplicate_action_options = panel.GetValue()
new_options.SetDuplicateActionOptions( duplicate_status, duplicate_action_options )
def _PopulateLeftButtons( self ):
@ -601,6 +608,11 @@ class FullscreenHoverFrameRatings( FullscreenHoverFrame ):
self._file_repos = wx.StaticText( self, label = '', style = wx.ALIGN_RIGHT )
# urls
self._last_seen_urls = []
self._urls_vbox = wx.BoxSizer( wx.VERTICAL )
# likes
like_hbox = wx.BoxSizer( wx.HORIZONTAL )
@ -622,6 +634,7 @@ class FullscreenHoverFrameRatings( FullscreenHoverFrame ):
vbox.AddF( self._icon_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
vbox.AddF( self._file_repos, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._urls_vbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
vbox.AddF( like_hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
numerical_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_NUMERICAL, ), randomised = False )
@ -716,6 +729,32 @@ class FullscreenHoverFrameRatings( FullscreenHoverFrame ):
self._file_repos.Show()
# urls
urls = self._current_media.GetLocationsManager().GetURLs()
urls = list( urls )
urls.sort()
if urls != self._last_seen_urls:
self._last_seen_urls = list( urls )
self._urls_vbox.Clear( deleteWindows = True )
for url in urls:
parse = urlparse.urlparse( url )
url_string = parse.scheme + '://' + parse.hostname
link = wx.HyperlinkCtrl( self, id = -1, label = url_string, url = url )
self._urls_vbox.AddF( link, CC.FLAGS_EXPAND_PERPENDICULAR )
self.Fit()

View File

@ -1150,9 +1150,9 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
num_unknown = duplicate_types_to_count[ HC.DUPLICATE_UNKNOWN ]
self._num_unknown_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( num_unknown ) + ' potential matches found.' )
self._num_better_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_BETTER ] ) + ' better/worse pairs filtered.' )
self._num_same_file_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_SAME_FILE ] ) + ' exactly similar pairs filtered.' )
self._num_alternate_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_ALTERNATE ] ) + ' alternate pairs filtered.' )
self._num_better_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_BETTER ] ) + ' better/worse pairs found.' )
self._num_same_file_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_SAME_FILE ] ) + ' exactly similar pairs found.' )
self._num_alternate_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_ALTERNATE ] ) + ' alternate pairs found.' )
if num_unknown > 0:
@ -2238,7 +2238,7 @@ class ManagementPanelPetitions( ManagementPanel ):
check_all = ClientGUICommon.BetterButton( self._petition_panel, 'check all', self._CheckAll )
check_none = ClientGUICommon.BetterButton( self._petition_panel, 'check none', self._CheckNone )
self._contents = wx.CheckListBox( self._petition_panel, size = ( -1, 300 ) )
self._contents = wx.CheckListBox( self._petition_panel )
self._contents.Bind( wx.EVT_LISTBOX_DCLICK, self.EventContentDoubleClick )
self._process = wx.Button( self._petition_panel, label = 'process' )
@ -2279,7 +2279,7 @@ class ManagementPanelPetitions( ManagementPanel ):
self._MakeCollect( vbox )
vbox.AddF( self._petitions_info_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._petition_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._petition_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self._MakeCurrentSelectionTagsBox( vbox )

View File

@ -31,6 +31,7 @@ import wx
import yaml
import HydrusData
import HydrusGlobals
import webbrowser
# Option Enums
@ -224,24 +225,6 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
HydrusGlobals.client_controller.pub( 'clipboard', 'text', hex_hashes )
def _CopyKnownURLsToClipboard( self ):
hash = self._focussed_media.GetDisplayMedia().GetHash()
known_urls = HydrusGlobals.client_controller.Read( 'known_urls', hash )
if len( known_urls ) == 0:
HydrusData.ShowText( 'No known urls!' )
else:
text = os.linesep.join( known_urls )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', text )
def _CopyPathToClipboard( self ):
display_media = self._focussed_media.GetDisplayMedia()
@ -2167,7 +2150,6 @@ class MediaPanelThumbnails( MediaPanel ):
elif command == 'copy_files': self._CopyFilesToClipboard()
elif command == 'copy_hash': self._CopyHashToClipboard( data )
elif command == 'copy_hashes': self._CopyHashesToClipboard( data )
elif command == 'copy_known_urls': self._CopyKnownURLsToClipboard()
elif command == 'copy_hashes': self._CopyHashesToClipboard( data )
elif command == 'copy_service_filename': self._CopyServiceFilenameToClipboard( data )
elif command == 'copy_service_filenames': self._CopyServiceFilenamesToClipboard( data )
@ -2860,7 +2842,7 @@ class MediaPanelThumbnails( MediaPanel ):
ClientGUIMenus.AppendMenuItem( self, menu, undelete_phrase, 'Restore the selected files back to \'my files\'.', self._Undelete )
# share
#
ClientGUIMenus.AppendSeparator( menu )
@ -2869,6 +2851,35 @@ class MediaPanelThumbnails( MediaPanel ):
ClientGUIMenus.AppendMenuItem( self, menu, 'open externally', 'Launch this file with your OS\'s default program for it.', self._OpenExternally )
#
urls = self._focussed_media.GetLocationsManager().GetURLs()
if len( urls ) > 0:
urls = list( urls )
urls.sort()
urls_menu = wx.Menu()
urls_visit_menu = wx.Menu()
urls_copy_menu = wx.Menu()
for url in urls:
ClientGUIMenus.AppendMenuItem( self, urls_visit_menu, url, 'Open this url in your web browser.', webbrowser.open, url )
ClientGUIMenus.AppendMenuItem( self, urls_copy_menu, url, 'Copy this url to your clipboard.', HydrusGlobals.client_controller.pub, 'clipboard', 'text', url )
ClientGUIMenus.AppendMenu( urls_menu, urls_visit_menu, 'open' )
ClientGUIMenus.AppendMenu( urls_menu, urls_copy_menu, 'copy' )
ClientGUIMenus.AppendMenu( menu, urls_menu, 'known urls' )
# share
share_menu = wx.Menu()
#
@ -2942,8 +2953,6 @@ class MediaPanelThumbnails( MediaPanel ):
ClientGUIMenus.AppendMenuItem( self, copy_menu, 'paths', 'Copy the selected files\' paths to the clipboard.', self._CopyPathsToClipboard )
ClientGUIMenus.AppendMenuItem( self, copy_menu, 'known urls (prototype)', 'Copy the selected file\'s known urls to the clipboard.', self._CopyKnownURLsToClipboard )
ClientGUIMenus.AppendMenu( share_menu, copy_menu, 'copy' )
#

View File

@ -1,4 +1,5 @@
import ClientConstants as CC
import ClientData
import ClientDefaults
import ClientDownloading
import ClientImporting
@ -132,6 +133,221 @@ class EditAccountTypePanel( ClientGUIScrolledPanels.EditPanel ):
return HydrusNetwork.AccountType.GenerateAccountTypeFromParameters( self._account_type_key, title, permissions, bandwidth_rules )
class EditDuplicateActionOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, duplicate_action, duplicate_action_options ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._duplicate_action = duplicate_action
self._service_actions = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'service name', -1 ), ( 'action', 240 ) ], delete_key_callback = self._Delete, activation_callback = self._Edit )
self._service_actions.SetMinSize( ( 320, 120 ) )
add_button = ClientGUICommon.BetterButton( self, 'add', self._Add )
edit_button = ClientGUICommon.BetterButton( self, 'edit', self._Edit )
delete_button = ClientGUICommon.BetterButton( self, 'delete', self._Delete )
self._delete_second_file = wx.CheckBox( self, label = 'delete worse file' )
#
( service_actions, delete_second_file ) = duplicate_action_options.ToTuple()
services_manager = HydrusGlobals.client_controller.GetServicesManager()
for ( service_key, action ) in service_actions:
if services_manager.ServiceExists( service_key ):
sort_tuple = ( service_key, action )
display_tuple = self._GetDisplayTuple( sort_tuple )
self._service_actions.Append( display_tuple, sort_tuple )
self._delete_second_file.SetValue( delete_second_file )
#
if self._duplicate_action != HC.DUPLICATE_BETTER:
self._delete_second_file.Hide()
edit_button.Hide()
button_hbox = wx.BoxSizer( wx.HORIZONTAL )
button_hbox.AddF( add_button, CC.FLAGS_VCENTER )
button_hbox.AddF( edit_button, CC.FLAGS_VCENTER )
button_hbox.AddF( delete_button, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._service_actions, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( button_hbox, CC.FLAGS_BUTTON_SIZER )
vbox.AddF( self._delete_second_file, CC.FLAGS_LONE_BUTTON )
self.SetSizer( vbox )
def _Add( self ):
existing_service_keys = set()
for ( service_key, action ) in self._service_actions.GetClientData():
existing_service_keys.add( service_key )
services_manager = HydrusGlobals.client_controller.GetServicesManager()
choice_tuples = []
for service in services_manager.GetServices( [ HC.LOCAL_TAG, HC.TAG_REPOSITORY, HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ] ):
service_key = service.GetServiceKey()
if service_key not in existing_service_keys:
name = service.GetName()
choice_tuples.append( ( name, service_key ) )
if len( choice_tuples ) == 0:
wx.MessageBox( 'You have no more tag or rating services to add! Try editing the existing ones instead!' )
else:
with ClientGUIDialogs.DialogSelectFromList( self, 'select service', choice_tuples ) as dlg_1:
if dlg_1.ShowModal() == wx.ID_OK:
service_key = dlg_1.GetChoice()
if self._duplicate_action == HC.DUPLICATE_BETTER:
service = services_manager.GetService( service_key )
if service.GetServiceType() == HC.TAG_REPOSITORY:
possible_actions = [ HC.CONTENT_MERGE_ACTION_COPY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ]
else:
possible_actions = [ HC.CONTENT_MERGE_ACTION_COPY, HC.CONTENT_MERGE_ACTION_MOVE, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ]
choice_tuples = [ ( HC.content_merge_string_lookup[ action ], action ) for action in possible_actions ]
with ClientGUIDialogs.DialogSelectFromList( self, 'select action', choice_tuples ) as dlg_2:
if dlg_2.ShowModal() == wx.ID_OK:
action = dlg_2.GetChoice()
else:
return
else:
action = HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE
sort_tuple = ( service_key, action )
display_tuple = self._GetDisplayTuple( sort_tuple )
self._service_actions.Append( display_tuple, sort_tuple )
def _Delete( self ):
with ClientGUIDialogs.DialogYesNo( self, 'Remove all selected?' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._service_actions.RemoveAllSelected()
def _Edit( self ):
all_selected = self._service_actions.GetAllSelected()
for index in all_selected:
( service_key, action ) = self._service_actions.GetClientData( index )
if self._duplicate_action == HC.DUPLICATE_BETTER:
possible_actions = [ HC.CONTENT_MERGE_ACTION_COPY, HC.CONTENT_MERGE_ACTION_MOVE, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ]
choice_tuples = [ ( HC.content_merge_string_lookup[ action ], action ) for action in possible_actions ]
with ClientGUIDialogs.DialogSelectFromList( self, 'select action', choice_tuples ) as dlg_2:
if dlg_2.ShowModal() == wx.ID_OK:
action = dlg_2.GetChoice()
else:
break
else: # This shouldn't get fired because the edit button is hidden, but w/e
action = HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE
sort_tuple = ( service_key, action )
display_tuple = self._GetDisplayTuple( sort_tuple )
self._service_actions.UpdateRow( index, display_tuple, sort_tuple )
def _GetDisplayTuple( self, sort_tuple ):
( service_key, action ) = sort_tuple
services_manager = HydrusGlobals.client_controller.GetServicesManager()
service = services_manager.GetService( service_key )
name = service.GetName()
pretty_action = HC.content_merge_string_lookup[ action ]
return ( name, pretty_action )
def GetValue( self ):
service_actions = self._service_actions.GetClientData()
delete_second_file = self._delete_second_file.GetValue()
duplicate_action_options = ClientData.DuplicateActionOptions( service_actions, delete_second_file )
return duplicate_action_options
class EditFrameLocationPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, info ):

View File

@ -3836,7 +3836,7 @@ class ManageShortcutsPanel( ClientGUIScrolledPanels.ManagePanel ):
def _Delete( self, event ):
def _Delete( self ):
with ClientGUIDialogs.DialogYesNo( self, 'Remove all selected?' ) as dlg:

View File

@ -19,6 +19,7 @@ class AdvancedContentUpdatePanel( ClientGUIScrolledPanels.ReviewPanel ):
COPY = 0
DELETE = 1
DELETE_DELETED = 2
DELETE_FOR_DELETED_FILES = 3
ALL_MAPPINGS = 0
SPECIFIC_MAPPINGS = 1
@ -67,6 +68,7 @@ class AdvancedContentUpdatePanel( ClientGUIScrolledPanels.ReviewPanel ):
self._action_dropdown.Append( 'delete', self.DELETE )
self._action_dropdown.Append( 'clear deleted record', self.DELETE_DELETED )
self._action_dropdown.Append( 'delete from deleted files', self.DELETE_FOR_DELETED_FILES )
self._action_dropdown.Select( 0 )
@ -150,7 +152,7 @@ class AdvancedContentUpdatePanel( ClientGUIScrolledPanels.ReviewPanel ):
data = self._action_dropdown.GetChoice()
if data in ( self.DELETE, self.DELETE_DELETED ):
if data in ( self.DELETE, self.DELETE_DELETED, self.DELETE_FOR_DELETED_FILES ):
self._action_text.SetLabelText( 'from ' + self._service_name )
@ -246,6 +248,10 @@ class AdvancedContentUpdatePanel( ClientGUIScrolledPanels.ReviewPanel ):
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADVANCED, ( 'delete_deleted', ( tag, self._hashes ) ) )
elif action == self.DELETE_FOR_DELETED_FILES:
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADVANCED, ( 'delete_for_deleted_files', ( tag, self._hashes ) ) )
service_keys_to_content_updates = { self._service_key : [ content_update ] }

View File

@ -1174,6 +1174,13 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
txt_tags = [ HydrusData.ToUnicode( tag ) for tag in HydrusData.SplitByLinesep( txt_tags_string ) ]
if True in ( len( txt_tag ) > 1024 for txt_tag in txt_tags ):
HydrusData.ShowText( 'Tags were too long--I think this was not a regular text file!' )
raise Exception()
txt_tags = HydrusTags.CleanTags( txt_tags )
service_keys_to_tags = { service_key : txt_tags for service_key in self._txt_parse_tag_service_keys }
@ -1498,12 +1505,12 @@ class PageOfImagesImport( HydrusSerialisable.SerialisableBase ):
if self._download_image_links:
file_urls.extend( [ urlparse.urljoin( page_url, link[ 'href' ] ) for link in links_with_images ] )
file_urls.extend( [ urlparse.urljoin( page_url, link[ 'href' ] ) for link in links_with_images if link.has_attr( 'href' ) ] )
if self._download_unlinked_images:
file_urls.extend( [ urlparse.urljoin( page_url, image[ 'src' ] ) for image in unlinked_images ] )
file_urls.extend( [ urlparse.urljoin( page_url, image[ 'src' ] ) for image in unlinked_images if image.has_attr( 'src' ) ] )
num_new = 0

View File

@ -49,7 +49,7 @@ options = {}
# Misc
NETWORK_VERSION = 18
SOFTWARE_VERSION = 252
SOFTWARE_VERSION = 253
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -70,10 +70,15 @@ bandwidth_type_string_lookup = {}
bandwidth_type_string_lookup[ BANDWIDTH_TYPE_DATA ] = 'data'
bandwidth_type_string_lookup[ BANDWIDTH_TYPE_REQUESTS ] = 'requests'
CONTENT_MERGE_ACTION_DO_NOTHING = 0
CONTENT_MERGE_ACTION_COPY = 1
CONTENT_MERGE_ACTION_MOVE = 2
CONTENT_MERGE_ACTION_UNION = 3
CONTENT_MERGE_ACTION_COPY = 0
CONTENT_MERGE_ACTION_MOVE = 1
CONTENT_MERGE_ACTION_TWO_WAY_MERGE = 2
content_merge_string_lookup = {}
content_merge_string_lookup[ CONTENT_MERGE_ACTION_COPY ] = 'copy from worse to better'
content_merge_string_lookup[ CONTENT_MERGE_ACTION_MOVE ] = 'move from worse to better'
content_merge_string_lookup[ CONTENT_MERGE_ACTION_TWO_WAY_MERGE ] = 'copy in both directions'
CONTENT_STATUS_CURRENT = 0
CONTENT_STATUS_PENDING = 1
@ -168,6 +173,17 @@ DUPLICATE_SMALLER_BETTER = 5
DUPLICATE_LARGER_BETTER = 6
DUPLICATE_WORSE = 7
duplicate_status_string_lookup = {}
duplicate_status_string_lookup[ DUPLICATE_UNKNOWN ] = 'unknown relationship'
duplicate_status_string_lookup[ DUPLICATE_NOT_DUPLICATE ] = 'not duplicates'
duplicate_status_string_lookup[ DUPLICATE_SAME_FILE ] = 'exact same files'
duplicate_status_string_lookup[ DUPLICATE_ALTERNATE ] = 'alternates'
duplicate_status_string_lookup[ DUPLICATE_BETTER ] = 'this is better'
duplicate_status_string_lookup[ DUPLICATE_SMALLER_BETTER ] = 'smaller hash_id is better'
duplicate_status_string_lookup[ DUPLICATE_LARGER_BETTER ] = 'larger hash_id is better'
duplicate_status_string_lookup[ DUPLICATE_WORSE ] = 'this is worse'
ENCODING_RAW = 0
ENCODING_HEX = 1
ENCODING_BASE64 = 2

View File

@ -1479,11 +1479,22 @@ class ContentUpdate( object ):
self._row = row
def __eq__( self, other ): return self._data_type == other._data_type and self._action == other._action and self._row == other._row
def __eq__( self, other ):
return hash( self ) == hash( other )
def __ne__( self, other ): return not self.__eq__( other )
def __repr__( self ): return 'Content Update: ' + ToUnicode( ( self._data_type, self._action, self._row ) )
def __hash__( self ):
return hash( ( self._data_type, self._action, repr( self._row ) ) )
def __repr__( self ):
return 'Content Update: ' + ToUnicode( ( self._data_type, self._action, self._row ) )
def GetHashes( self ):

View File

@ -45,6 +45,7 @@ SERIALISABLE_TYPE_BANDWIDTH_TRACKER = 39
SERIALISABLE_TYPE_CLIENT_TO_SERVER_UPDATE = 40
SERIALISABLE_TYPE_SHORTCUT = 41
SERIALISABLE_TYPE_APPLICATION_COMMAND = 42
SERIALISABLE_TYPE_DUPLICATE_ACTION_OPTIONS = 43
SERIALISABLE_TYPES_TO_OBJECT_TYPES = {}

View File

@ -386,10 +386,10 @@ class Controller( HydrusController.HydrusController ):
interrupt_received = True
HydrusData.Print( u'Received a keyboard interrupt\u2026' )
def do_it():
HydrusData.Print( u'Received a keyboard interrupt\u2026' )
self.Exit()

View File

@ -13,6 +13,20 @@ DB_DIR = None
tinest_gif = '\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x00\xFF\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00\x3B'
LOCAL_RATING_LIKE_SERVICE_KEY = HydrusData.GenerateKey()
LOCAL_RATING_NUMERICAL_SERVICE_KEY = HydrusData.GenerateKey()
def ConvertServiceKeysToContentUpdatesToComparable( service_keys_to_content_updates ):
comparable_dict = {}
for ( service_key, content_updates ) in service_keys_to_content_updates.items():
comparable_dict[ service_key ] = set( content_updates )
return comparable_dict
class FakeHTTPConnectionManager():
def __init__( self ):

View File

@ -1,13 +1,17 @@
import ClientCaches
import ClientConstants as CC
import ClientData
import ClientDefaults
import ClientDownloading
import ClientImporting
import ClientMedia
import ClientRatings
import ClientSearch
import HydrusConstants as HC
import HydrusData
import HydrusNetwork
import HydrusSerialisable
import TestConstants as TC
import os
import unittest
import wx
@ -142,6 +146,214 @@ class TestSerialisables( unittest.TestCase ):
def test_SERIALISABLE_TYPE_DUPLICATE_ACTION_OPTIONS( self ):
def test( obj, dupe_obj ):
self.assertEqual( obj.ToTuple(), dupe_obj.ToTuple() )
duplicate_action_options_delete_and_move = ClientData.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE ), ( TC.LOCAL_RATING_LIKE_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE ), ( TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE ) ], True )
duplicate_action_options_copy = ClientData.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_COPY ), ( TC.LOCAL_RATING_LIKE_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_COPY ), ( TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_COPY ) ], False )
duplicate_action_options_merge = ClientData.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ), ( TC.LOCAL_RATING_LIKE_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ), ( TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ) ], False )
inbox = True
size = 40960
mime = HC.IMAGE_JPEG
width = 640
height = 480
duration = None
num_frames = None
num_words = None
local_locations_manager = ClientMedia.LocationsManager( { CC.LOCAL_FILE_SERVICE_KEY, CC.COMBINED_LOCAL_FILE_SERVICE_KEY }, set(), set(), set() )
trash_locations_manager = ClientMedia.LocationsManager( { CC.TRASH_SERVICE_KEY, CC.COMBINED_LOCAL_FILE_SERVICE_KEY }, set(), set(), set() )
deleted_locations_manager = ClientMedia.LocationsManager( set(), { CC.COMBINED_LOCAL_FILE_SERVICE_KEY }, set(), set() )
# duplicate to generate proper dicts
one_tags_manager = ClientMedia.TagsManager( { CC.LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : { 'one' } } } ).Duplicate()
two_tags_manager = ClientMedia.TagsManager( { CC.LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : { 'two' } } } ).Duplicate()
substantial_tags_manager = ClientMedia.TagsManager( { CC.LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : { 'test tag', 'series:namespaced test tag' } } } ).Duplicate()
empty_tags_manager = ClientMedia.TagsManager( {} ).Duplicate()
one_ratings_manager = ClientRatings.RatingsManager( { TC.LOCAL_RATING_LIKE_SERVICE_KEY : 1.0, TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY : 0.8 } )
two_ratings_manager = ClientRatings.RatingsManager( { TC.LOCAL_RATING_LIKE_SERVICE_KEY : 0.0, TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY : 0.6 } )
substantial_ratings_manager = ClientRatings.RatingsManager( { TC.LOCAL_RATING_LIKE_SERVICE_KEY : 1.0, TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY : 0.8 } )
empty_ratings_manager = ClientRatings.RatingsManager( {} )
local_hash_has_values = HydrusData.GenerateKey()
media_result = ClientMedia.MediaResult( ( local_hash_has_values, inbox, size, mime, width, height, duration, num_frames, num_words, substantial_tags_manager, local_locations_manager, substantial_ratings_manager ) )
local_media_has_values = ClientMedia.MediaSingleton( media_result )
other_local_hash_has_values = HydrusData.GenerateKey()
media_result = ClientMedia.MediaResult( ( other_local_hash_has_values, inbox, size, mime, width, height, duration, num_frames, num_words, substantial_tags_manager, local_locations_manager, substantial_ratings_manager ) )
other_local_media_has_values = ClientMedia.MediaSingleton( media_result )
local_hash_empty = HydrusData.GenerateKey()
media_result = ClientMedia.MediaResult( ( local_hash_empty, inbox, size, mime, width, height, duration, num_frames, num_words, empty_tags_manager, local_locations_manager, empty_ratings_manager ) )
local_media_empty = ClientMedia.MediaSingleton( media_result )
trashed_hash_empty = HydrusData.GenerateKey()
media_result = ClientMedia.MediaResult( ( trashed_hash_empty, inbox, size, mime, width, height, duration, num_frames, num_words, empty_tags_manager, trash_locations_manager, empty_ratings_manager ) )
trashed_media_empty = ClientMedia.MediaSingleton( media_result )
deleted_hash_empty = HydrusData.GenerateKey()
media_result = ClientMedia.MediaResult( ( deleted_hash_empty, inbox, size, mime, width, height, duration, num_frames, num_words, empty_tags_manager, deleted_locations_manager, empty_ratings_manager ) )
deleted_media_empty = ClientMedia.MediaSingleton( media_result )
one_hash = HydrusData.GenerateKey()
media_result = ClientMedia.MediaResult( ( one_hash, inbox, size, mime, width, height, duration, num_frames, num_words, one_tags_manager, local_locations_manager, one_ratings_manager ) )
one_media = ClientMedia.MediaSingleton( media_result )
two_hash = HydrusData.GenerateKey()
media_result = ClientMedia.MediaResult( ( two_hash, inbox, size, mime, width, height, duration, num_frames, num_words, two_tags_manager, local_locations_manager, two_ratings_manager ) )
two_media = ClientMedia.MediaSingleton( media_result )
#
self._dump_and_load_and_test( duplicate_action_options_delete_and_move, test )
self._dump_and_load_and_test( duplicate_action_options_copy, test )
self._dump_and_load_and_test( duplicate_action_options_merge, test )
#
def assertSCUEqual( one, two ):
self.assertEqual( TC.ConvertServiceKeysToContentUpdatesToComparable( one ), TC.ConvertServiceKeysToContentUpdatesToComparable( two ) )
#
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, local_media_empty )
scu = {}
scu[ CC.LOCAL_FILE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, { local_hash_empty } ) ]
assertSCUEqual( result[0], scu )
#
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, trashed_media_empty )
scu = {}
scu[ CC.TRASH_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, { trashed_hash_empty } ) ]
assertSCUEqual( result[0], scu )
#
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, deleted_media_empty )
self.assertEqual( result, [] )
#
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, other_local_media_has_values )
scu = {}
scu[ CC.LOCAL_TAG_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_DELETE, ( 'test tag', { other_local_hash_has_values } ) ), HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_DELETE, ( 'series:namespaced test tag', { other_local_hash_has_values } ) ) ]
scu[ TC.LOCAL_RATING_LIKE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( None, { other_local_hash_has_values } ) ) ]
scu[ TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( None, { other_local_hash_has_values } ) ) ]
assertSCUEqual( result[0], scu )
scu = {}
scu[ CC.LOCAL_FILE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, { other_local_hash_has_values } ) ]
assertSCUEqual( result[1], scu )
#
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_empty, other_local_media_has_values )
scu = {}
scu[ CC.LOCAL_TAG_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( 'test tag', { local_hash_empty } ) ), HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( 'series:namespaced test tag', { local_hash_empty } ) ), HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_DELETE, ( 'test tag', { other_local_hash_has_values } ) ), HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_DELETE, ( 'series:namespaced test tag', { other_local_hash_has_values } ) ) ]
scu[ TC.LOCAL_RATING_LIKE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( 1.0, { local_hash_empty } ) ), HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( None, { other_local_hash_has_values } ) ) ]
scu[ TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( 0.8, { local_hash_empty } ) ), HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( None, { other_local_hash_has_values } ) ) ]
assertSCUEqual( result[0], scu )
scu = {}
scu[ CC.LOCAL_FILE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, { other_local_hash_has_values } ) ]
assertSCUEqual( result[1], scu )
#
#
result = duplicate_action_options_copy.ProcessPairIntoContentUpdates( local_media_has_values, local_media_empty )
self.assertEqual( result, [] )
#
result = duplicate_action_options_copy.ProcessPairIntoContentUpdates( local_media_empty, other_local_media_has_values )
scu = {}
scu[ CC.LOCAL_TAG_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( 'test tag', { local_hash_empty } ) ), HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( 'series:namespaced test tag', { local_hash_empty } ) ) ]
scu[ TC.LOCAL_RATING_LIKE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( 1.0, { local_hash_empty } ) ) ]
scu[ TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( 0.8, { local_hash_empty } ) ) ]
assertSCUEqual( result[0], scu )
#
#
result = duplicate_action_options_merge.ProcessPairIntoContentUpdates( local_media_has_values, local_media_empty )
scu = {}
scu[ CC.LOCAL_TAG_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( 'test tag', { local_hash_empty } ) ), HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( 'series:namespaced test tag', { local_hash_empty } ) ) ]
scu[ TC.LOCAL_RATING_LIKE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( 1.0, { local_hash_empty } ) ) ]
scu[ TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( 0.8, { local_hash_empty } ) ) ]
assertSCUEqual( result[0], scu )
#
result = duplicate_action_options_merge.ProcessPairIntoContentUpdates( local_media_empty, other_local_media_has_values )
scu = {}
scu[ CC.LOCAL_TAG_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( 'test tag', { local_hash_empty } ) ), HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( 'series:namespaced test tag', { local_hash_empty } ) ) ]
scu[ TC.LOCAL_RATING_LIKE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( 1.0, { local_hash_empty } ) ) ]
scu[ TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( 0.8, { local_hash_empty } ) ) ]
assertSCUEqual( result[0], scu )
#
result = duplicate_action_options_merge.ProcessPairIntoContentUpdates( one_media, two_media )
scu = {}
scu[ CC.LOCAL_TAG_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( 'one', { two_hash } ) ), HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( 'two', { one_hash } ) ) ]
assertSCUEqual( result[0], scu )
def test_SERIALISABLE_TYPE_SHORTCUT( self ):
def test( obj, dupe_obj ):

BIN
static/help.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

View File

@ -96,6 +96,8 @@ class Controller( object ):
services.append( ClientServices.GenerateService( CC.LOCAL_FILE_SERVICE_KEY, HC.LOCAL_FILE_DOMAIN, CC.LOCAL_FILE_SERVICE_KEY ) )
services.append( ClientServices.GenerateService( CC.TRASH_SERVICE_KEY, HC.LOCAL_FILE_TRASH_DOMAIN, CC.LOCAL_FILE_SERVICE_KEY ) )
services.append( ClientServices.GenerateService( CC.LOCAL_TAG_SERVICE_KEY, HC.LOCAL_TAG, CC.LOCAL_TAG_SERVICE_KEY ) )
services.append( ClientServices.GenerateService( TestConstants.LOCAL_RATING_LIKE_SERVICE_KEY, HC.LOCAL_RATING_LIKE, 'example local rating like service' ) )
services.append( ClientServices.GenerateService( TestConstants.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.LOCAL_RATING_NUMERICAL, 'example local rating numerical service' ) )
self._reads[ 'services' ] = services