Version 164

This commit is contained in:
Hydrus 2015-07-08 16:45:38 -05:00
parent 1e162828f4
commit c985f70a4f
23 changed files with 937 additions and 561 deletions

View File

@ -8,6 +8,31 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 164</h3></li>
<ul>
<li>rewrote the drawing code for the listbox that displays tags in various ways to be a lot faster and more memory efficient</li>
<li>updated one new client mapping index that wasn't working quite as I wanted it to something more clever</li>
<li>db will be a little smaller and mappings stuff will be even faster</li>
<li>merged the two ratings system predicate input panels, so you can now select like/dislike and numerical ratings system predicates at the same time</li>
<li>fixed booru download page serialisation, which means they will save to sessions</li>
<li>prototyped trash service</li>
<li>locally deleted files will now be sent to trash</li>
<li>locally deleted files will not be removed from the existing search</li>
<li>files can be permanently deleted from trash, which will also immediately physically delete them from your hdd</li>
<li>files can be restored from trash back to the local file service</li>
<li>inbox state is now more separate from the local file service, so it will be remembered through a visit to the trash</li>
<li>improved delete code all around</li>
<li>general inbox/archive db code improvements</li>
<li>misc content update pipeline improvements</li>
<li>optimised mass-adding of files to a service (for instance, when (un)deleting a whole bunch of files!)</li>
<li>delete orphans daemon is removed--it will be replaced by a more thorough single-shot hdd/db purge like 'check file integrity'</li>
<li>files are not yet automatically removed from the trash--I will add that next week.</li>
<li>updated db access info in db folder</li>
<li>added sqlite command line executable to db folder for all platforms</li>
<li>bit of code cleaning</li>
<li>cleaned up some gui error reporting</li>
<li>might have fixed a service cache bug in the db that was causing double bandwidth reports and possible looping sync behaviour</li>
</ul>
<li><h3>version 163</h3></li>
<ul>
<li>reconfigured some important mapping indices in the client db to reduce search time for many common tag operations</li>

View File

@ -232,6 +232,7 @@ class GlobalBMPs( object ):
GlobalBMPs.collection = wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'collection.png' )
GlobalBMPs.inbox = wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'inbox.png' )
GlobalBMPs.trash = wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'trash.png' )
GlobalBMPs.archive = wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'archive.png' )
GlobalBMPs.to_inbox = wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'to_inbox.png' )
@ -265,6 +266,8 @@ LOCAL_FILE_SERVICE_KEY = 'local files'
LOCAL_BOORU_SERVICE_KEY = 'local booru'
TRASH_SERVICE_KEY = 'trash'
COMBINED_FILE_SERVICE_KEY = 'all known files'
COMBINED_TAG_SERVICE_KEY = 'all known tags'

View File

@ -286,11 +286,6 @@ class Controller( HydrusController.HydrusController ):
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] > self._options[ 'maintenance_vacuum_period' ]: self.Write( 'vacuum' )
if self._options[ 'maintenance_delete_orphans_period' ] != 0:
if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_DELETE_ORPHANS ] > self._options[ 'maintenance_delete_orphans_period' ]: self.Write( 'delete_orphans' )
if self._timestamps[ 'last_service_info_cache_fatten' ] != 0 and now - self._timestamps[ 'last_service_info_cache_fatten' ] > 60 * 20:
HydrusGlobals.pubsub.pub( 'splash_set_text', 'fattening service info' )

View File

@ -992,41 +992,65 @@ class DB( HydrusDB.HydrusDB ):
READ_WRITE_ACTIONS = [ 'service_info', 'system_predicates' ]
WRITE_SPECIAL_ACTIONS = [ 'vacuum' ]
def _AddFile( self, service_id, hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words ):
def _AddFiles( self, service_id, rows ):
result = self._c.execute( 'SELECT 1 FROM files_info WHERE service_id = ? AND hash_id = ?;', ( service_id, hash_id ) ).fetchone()
successful_hash_ids = set()
if result is None:
num_deleted = 0
delta_size = 0
num_thumbnails = 0
num_inbox = 0
for ( hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words ) in rows:
self._c.execute( 'INSERT OR IGNORE INTO files_info VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? );', ( service_id, hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words ) )
result = self._c.execute( 'SELECT 1 FROM files_info WHERE service_id = ? AND hash_id = ?;', ( service_id, hash_id ) ).fetchone()
if result is None:
self._c.execute( 'INSERT OR IGNORE INTO files_info VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? );', ( service_id, hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words ) )
delta_size += size
if mime in HC.MIMES_WITH_THUMBNAILS:
num_thumbnails += 1
successful_hash_ids.add( hash_id )
if len( successful_hash_ids ) > 0:
splayed_successful_hash_ids = HydrusData.SplayListForDB( successful_hash_ids )
num_deleted = len( self._c.execute( 'SELECT 1 FROM deleted_files WHERE service_id = ? AND hash_id IN ' + splayed_successful_hash_ids + ';', ( service_id, ) ).fetchall() )
self._c.execute( 'DELETE FROM deleted_files WHERE service_id = ? AND hash_id IN ' + splayed_successful_hash_ids + ';', ( service_id, ) )
service_info_updates = []
result = self._c.execute( 'SELECT 1 FROM deleted_files WHERE service_id = ? AND hash_id = ?;', ( service_id, hash_id ) ).fetchone()
if result is not None:
self._c.execute( 'DELETE FROM deleted_files WHERE service_id = ? AND hash_id = ?;', ( service_id, hash_id ) )
service_info_updates.append( ( -1, service_id, HC.SERVICE_INFO_NUM_DELETED_FILES ) )
if service_id != self._local_file_service_id:
if hash_id in self._inbox_hash_ids: service_info_updates.append( ( 1, service_id, HC.SERVICE_INFO_NUM_INBOX ) )
service_info_updates.append( ( size, service_id, HC.SERVICE_INFO_TOTAL_SIZE ) )
service_info_updates.append( ( 1, service_id, HC.SERVICE_INFO_NUM_FILES ) )
if mime in HC.MIMES_WITH_THUMBNAILS: service_info_updates.append( ( 1, service_id, HC.SERVICE_INFO_NUM_THUMBNAILS ) )
service_info_updates.append( ( -num_deleted, service_id, HC.SERVICE_INFO_NUM_DELETED_FILES ) )
service_info_updates.append( ( delta_size, service_id, HC.SERVICE_INFO_TOTAL_SIZE ) )
service_info_updates.append( ( len( successful_hash_ids ), service_id, HC.SERVICE_INFO_NUM_FILES ) )
service_info_updates.append( ( num_thumbnails, service_id, HC.SERVICE_INFO_NUM_THUMBNAILS ) )
service_info_updates.append( ( len( successful_hash_ids.intersection( self._inbox_hash_ids ) ), service_id, HC.SERVICE_INFO_NUM_INBOX ) )
self._c.executemany( 'UPDATE service_info SET info = info + ? WHERE service_id = ? AND info_type = ?;', service_info_updates )
self._c.execute( 'DELETE FROM file_transfers WHERE service_id = ? AND hash_id = ? ;', ( service_id, hash_id ) )
self._c.execute( 'DELETE FROM file_transfers WHERE service_id = ? AND hash_id IN ' + HydrusData.SplayListForDB( successful_hash_ids ) + ';', ( service_id, ) )
if mime in HC.MIMES_WITH_THUMBNAILS: self._c.execute( 'DELETE FROM service_info WHERE service_id = ? AND info_type = ?;', ( service_id, HC.SERVICE_INFO_NUM_THUMBNAILS_LOCAL ) )
if num_thumbnails > 0:
self._c.execute( 'DELETE FROM service_info WHERE service_id = ? AND info_type = ?;', ( service_id, HC.SERVICE_INFO_NUM_THUMBNAILS_LOCAL ) )
self._UpdateAutocompleteTagCacheFromFiles( service_id, ( hash_id, ), 1 )
self._UpdateAutocompleteTagCacheFromFiles( service_id, successful_hash_ids, 1 )
if service_id == self._local_file_service_id:
self._DeleteFiles( self._trash_service_id, successful_hash_ids )
@ -1091,25 +1115,6 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'INSERT INTO services ( service_key, service_type, name, info ) VALUES ( ?, ?, ?, ? );', ( sqlite3.Binary( service_key ), service_type, name, info ) )
service_id = self._c.lastrowid
if service_type in ( HC.TAG_REPOSITORY, HC.LOCAL_TAG ):
file_service_ids = self._GetServiceIds( ( HC.FILE_REPOSITORY, HC.LOCAL_FILE, HC.COMBINED_FILE ) )
existing_tag_ids = self._c.execute( 'SELECT namespace_id, tag_id FROM existing_tags;' ).fetchall()
inserts = ( ( file_service_id, service_id, namespace_id, tag_id, 0, 0 ) for ( file_service_id, ( namespace_id, tag_id ) ) in itertools.product( file_service_ids, existing_tag_ids ) )
elif service_type == HC.FILE_REPOSITORY:
tag_service_ids = self._GetServiceIds( ( HC.TAG_REPOSITORY, HC.LOCAL_TAG, HC.COMBINED_TAG ) )
existing_tag_ids = self._c.execute( 'SELECT namespace_id, tag_id FROM existing_tags;' ).fetchall()
inserts = ( ( service_id, tag_service_id, namespace_id, tag_id, 0, 0 ) for ( tag_service_id, ( namespace_id, tag_id ) ) in itertools.product( tag_service_ids, existing_tag_ids ) )
def _AddThumbnails( self, thumbnails ):
@ -1202,7 +1207,7 @@ class DB( HydrusDB.HydrusDB ):
HydrusGlobals.pubsub.pub( 'message', job_key )
info = self._c.execute( 'SELECT hash_id, mime FROM files_info WHERE service_id = ?;', ( self._local_file_service_id, ) ).fetchall()
info = self._c.execute( 'SELECT hash_id, mime FROM files_info WHERE service_id IN ( ?, ? );', ( self._local_file_service_id, self._trash_service_id ) ).fetchall()
missing_count = 0
deletee_hash_ids = []
@ -1253,6 +1258,7 @@ class DB( HydrusDB.HydrusDB ):
job_key.SetVariable( 'popup_text_1', prefix_string + 'deleting the incorrect records' )
self._DeleteFiles( self._local_file_service_id, deletee_hash_ids )
self._DeleteFiles( self._trash_service_id, deletee_hash_ids )
final_text = 'done! '
@ -1407,7 +1413,8 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'CREATE INDEX mappings_namespace_id_index ON mappings ( namespace_id );' )
self._c.execute( 'CREATE INDEX mappings_tag_id_index ON mappings ( tag_id );' )
self._c.execute( 'CREATE INDEX mappings_hash_id_index ON mappings ( hash_id );' )
self._c.execute( 'CREATE INDEX mappings_status_index ON mappings ( status );' )
self._c.execute( 'CREATE INDEX mappings_status_pending_index ON mappings ( status ) WHERE status = 1;' )
self._c.execute( 'CREATE INDEX mappings_status_deleted_index ON mappings ( status ) WHERE status = 2;' )
self._c.execute( 'CREATE TABLE mapping_petitions ( service_id INTEGER REFERENCES services ON DELETE CASCADE, namespace_id INTEGER, tag_id INTEGER, hash_id INTEGER, reason_id INTEGER, PRIMARY KEY( service_id, namespace_id, tag_id, hash_id, reason_id ) );' )
self._c.execute( 'CREATE INDEX mapping_petitions_hash_id_index ON mapping_petitions ( hash_id );' )
@ -1497,6 +1504,7 @@ class DB( HydrusDB.HydrusDB ):
init_service_info = []
init_service_info.append( ( CC.LOCAL_FILE_SERVICE_KEY, HC.LOCAL_FILE, CC.LOCAL_FILE_SERVICE_KEY ) )
init_service_info.append( ( CC.TRASH_SERVICE_KEY, HC.LOCAL_FILE, CC.TRASH_SERVICE_KEY ) )
init_service_info.append( ( CC.LOCAL_TAG_SERVICE_KEY, HC.LOCAL_TAG, CC.LOCAL_TAG_SERVICE_KEY ) )
init_service_info.append( ( CC.COMBINED_FILE_SERVICE_KEY, HC.COMBINED_FILE, CC.COMBINED_FILE_SERVICE_KEY ) )
init_service_info.append( ( CC.COMBINED_TAG_SERVICE_KEY, HC.COMBINED_TAG, CC.COMBINED_TAG_SERVICE_KEY ) )
@ -1524,39 +1532,55 @@ class DB( HydrusDB.HydrusDB ):
splayed_hash_ids = HydrusData.SplayListForDB( hash_ids )
if service_id == self._local_file_service_id: self._ArchiveFiles( hash_ids )
rows = self._c.execute( 'SELECT * FROM files_info WHERE service_id = ? AND hash_id IN ' + splayed_hash_ids + ';', ( service_id, ) ).fetchall()
info = self._c.execute( 'SELECT size, mime FROM files_info WHERE service_id = ? AND hash_id IN ' + splayed_hash_ids + ';', ( service_id, ) ).fetchall()
# service_id, hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words
hash_ids = { row[ 1 ] for row in rows }
total_size = sum( [ row[ 0 ] for row in info ] )
num_files = len( info )
num_thumbnails = len( [ 1 for row in info if row[ 1 ] in HC.MIMES_WITH_THUMBNAILS ] )
service_info_updates = []
service_info_updates.append( ( total_size, service_id, HC.SERVICE_INFO_TOTAL_SIZE ) )
service_info_updates.append( ( num_files, service_id, HC.SERVICE_INFO_NUM_FILES ) )
service_info_updates.append( ( num_thumbnails, service_id, HC.SERVICE_INFO_NUM_THUMBNAILS ) )
service_info_updates.append( ( -num_files, service_id, HC.SERVICE_INFO_NUM_DELETED_FILES ) ) # - because we want to increment in the following query
self._c.executemany( 'UPDATE service_info SET info = info - ? WHERE service_id = ? AND info_type = ?;', service_info_updates )
self._c.execute( 'DELETE FROM service_info WHERE service_id = ? AND info_type = ' + str( HC.SERVICE_INFO_NUM_THUMBNAILS_LOCAL ) + ';', ( service_id, ) )
self._c.execute( 'DELETE FROM files_info WHERE service_id = ? AND hash_id IN ' + splayed_hash_ids + ';', ( service_id, ) )
self._c.execute( 'DELETE FROM file_petitions WHERE service_id = ? AND hash_id IN ' + splayed_hash_ids + ';', ( service_id, ) )
invalid_hash_ids = { id for ( id, ) in self._c.execute( 'SELECT hash_id FROM deleted_files WHERE service_id = ? AND hash_id IN ' + splayed_hash_ids + ';', ( service_id, ) ) }
actual_hash_ids_i_can_delete = set( hash_ids )
actual_hash_ids_i_can_delete.difference_update( invalid_hash_ids )
self._c.executemany( 'INSERT OR IGNORE INTO deleted_files ( service_id, hash_id ) VALUES ( ?, ? );', [ ( service_id, hash_id ) for hash_id in actual_hash_ids_i_can_delete ] )
self._UpdateAutocompleteTagCacheFromFiles( service_id, actual_hash_ids_i_can_delete, -1 )
self.pub_after_commit( 'notify_new_pending' )
if len( hash_ids ) > 0:
total_size = sum( [ row[ 2 ] for row in rows ] )
num_files = len( rows )
num_thumbnails = len( [ 1 for row in rows if row[ 3 ] in HC.MIMES_WITH_THUMBNAILS ] )
num_inbox = len( hash_ids.intersection( self._inbox_hash_ids ) )
splayed_hash_ids = HydrusData.SplayListForDB( hash_ids )
service_info_updates = []
service_info_updates.append( ( -total_size, service_id, HC.SERVICE_INFO_TOTAL_SIZE ) )
service_info_updates.append( ( -num_files, service_id, HC.SERVICE_INFO_NUM_FILES ) )
service_info_updates.append( ( -num_thumbnails, service_id, HC.SERVICE_INFO_NUM_THUMBNAILS ) )
service_info_updates.append( ( -num_inbox, service_id, HC.SERVICE_INFO_NUM_INBOX ) )
service_info_updates.append( ( num_files, service_id, HC.SERVICE_INFO_NUM_DELETED_FILES ) )
self._c.executemany( 'UPDATE service_info SET info = info + ? WHERE service_id = ? AND info_type = ?;', service_info_updates )
self._c.execute( 'DELETE FROM service_info WHERE service_id = ? AND info_type = ' + str( HC.SERVICE_INFO_NUM_THUMBNAILS_LOCAL ) + ';', ( service_id, ) )
self._c.execute( 'DELETE FROM files_info WHERE service_id = ? AND hash_id IN ' + splayed_hash_ids + ';', ( service_id, ) )
self._c.execute( 'DELETE FROM file_petitions WHERE service_id = ? AND hash_id IN ' + splayed_hash_ids + ';', ( service_id, ) )
self._c.executemany( 'INSERT OR IGNORE INTO deleted_files ( service_id, hash_id ) VALUES ( ?, ? );', [ ( service_id, hash_id ) for hash_id in hash_ids ] )
self._UpdateAutocompleteTagCacheFromFiles( service_id, hash_ids, -1 )
if service_id == self._local_file_service_id:
new_rows = [ ( hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words ) for ( service_id, hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words ) in rows ]
self._AddFiles( self._trash_service_id, new_rows )
if service_id == self._trash_service_id:
self._ArchiveFiles( hash_ids )
self._DeletePhysicalFiles( hash_ids )
self.pub_after_commit( 'notify_new_pending' )
def _DeleteHydrusSessionKey( self, service_key ):
@ -1595,25 +1619,21 @@ class DB( HydrusDB.HydrusDB ):
HydrusGlobals.pubsub.pub( 'message', job_key )
# careful of the .encode( 'hex' ) business here!
# files
deleted_hash_ids = { hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM deleted_files WHERE service_id = ?;', ( self._local_file_service_id, ) ) }
deleted_hash_ids = { hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM deleted_files WHERE service_id = ?;', ( self._trash_service_id, ) ) }
pending_upload_hash_ids = { hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM file_transfers;', ) }
potentially_pending_upload_hash_ids = { hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM file_transfers;', ) }
message_attachment_hash_ids = { hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM message_attachments;' ) }
deletee_hash_ids = ( deleted_hash_ids - pending_upload_hash_ids ) - message_attachment_hash_ids
deletee_hash_ids = deleted_hash_ids.difference( potentially_pending_upload_hash_ids )
deletee_hashes = set( self._GetHashes( deletee_hash_ids ) )
local_files_hashes = ClientFiles.GetAllFileHashes()
job_key.SetVariable( 'popup_text_1', prefix + 'deleting orphan files' )
for hash in local_files_hashes & deletee_hashes:
time.sleep( 1 )
for hash in ClientFiles.IterateAllFileHashes():
( i_paused, should_quit ) = job_key.WaitIfNeeded()
@ -1622,20 +1642,23 @@ class DB( HydrusDB.HydrusDB ):
return
try: path = ClientFiles.GetFilePath( hash )
except HydrusExceptions.NotFoundException: continue
try:
if hash in deletee_hashes:
try: os.chmod( path, stat.S_IWRITE | stat.S_IREAD )
except: pass
try: path = ClientFiles.GetFilePath( hash )
except HydrusExceptions.NotFoundException: continue
os.remove( path )
except OSError:
print( 'In trying to delete the orphan ' + path + ', this error was encountered:' )
print( traceback.format_exc() )
try:
try: os.chmod( path, stat.S_IWRITE | stat.S_IREAD )
except: pass
os.remove( path )
except OSError:
print( 'In trying to delete the orphan ' + path + ', this error was encountered:' )
print( traceback.format_exc() )
@ -1728,6 +1751,65 @@ class DB( HydrusDB.HydrusDB ):
self.pub_service_updates_after_commit( { service_key : [ HydrusData.ServiceUpdate( HC.SERVICE_UPDATE_DELETE_PENDING ) ] } )
def _DeletePhysicalFiles( self, hash_ids ):
potentially_pending_upload_hash_ids = { hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM file_transfers;', ) }
deletable_file_hash_ids = hash_ids.difference( potentially_pending_upload_hash_ids )
if len( deletable_file_hash_ids ) > 0:
file_hashes = self._GetHashes( deletable_file_hash_ids )
for hash in file_hashes:
try: path = ClientFiles.GetFilePath( hash )
except HydrusExceptions.NotFoundException: continue
try:
try: os.chmod( path, stat.S_IWRITE | stat.S_IREAD )
except: pass
os.remove( path )
except OSError:
print( 'In trying to delete the orphan ' + path + ', this error was encountered:' )
print( traceback.format_exc() )
useful_thumbnail_hash_ids = { hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM files_info WHERE service_id != ? AND hash_id IN ' + HydrusData.SplayListForDB( hash_ids ) + ';', ( self._trash_service_id, ) ) }
deletable_thumbnail_hash_ids = hash_ids.difference( useful_thumbnail_hash_ids )
if len( deletable_thumbnail_hash_ids ) > 0:
thumbnail_hashes = self._GetHashes( deletable_thumbnail_hash_ids )
for hash in thumbnail_hashes:
path = ClientFiles.GetExpectedThumbnailPath( hash, True )
resized_path = ClientFiles.GetExpectedThumbnailPath( hash, False )
try:
if os.path.exists( path ): os.remove( path )
if os.path.exists( resized_path ): os.remove( resized_path )
except OSError:
print( 'In trying to delete the orphan ' + path + ' or ' + resized_path + ', this error was encountered:' )
print( traceback.format_exc() )
self._c.execute( 'DELETE from perceptual_hashes WHERE hash_id IN ' + HydrusData.SplayListForDB( deletable_thumbnail_hash_ids ) + ';' )
def _DeleteServiceInfo( self ):
self._c.execute( 'DELETE FROM service_info;' )
@ -1830,16 +1912,6 @@ class DB( HydrusDB.HydrusDB ):
job_key.Finish()
def _FattenAutocompleteCache( self ):
tag_services = self._GetServices( ( HC.TAG_REPOSITORY, HC.LOCAL_TAG, HC.COMBINED_TAG ) )
file_services = self._GetServices( ( HC.FILE_REPOSITORY, HC.LOCAL_FILE, HC.COMBINED_FILE ) )
for ( tag_service, file_service ) in itertools.product( tag_services, file_services ): self._GetAutocompletePredicates( tag_service_key = tag_service.GetServiceKey(), file_service_key = file_service.GetServiceKey(), add_namespaceless = False )
self._c.execute( 'REPLACE INTO shutdown_timestamps ( shutdown_type, timestamp ) VALUES ( ?, ? );', ( CC.SHUTDOWN_TIMESTAMP_FATTEN_AC_CACHE, HydrusData.GetNow() ) )
def _GetAutocompletePredicates( self, tag_service_key = CC.COMBINED_TAG_SERVICE_KEY, file_service_key = CC.COMBINED_FILE_SERVICE_KEY, tag = '', half_complete_tag = '', include_current = True, include_pending = True, add_namespaceless = False ):
tag_service_id = self._GetServiceId( tag_service_key )
@ -2571,7 +2643,7 @@ class DB( HydrusDB.HydrusDB ):
if must_be_local or must_not_be_local:
if file_service_id == self._local_file_service_id:
if file_service_type == HC.LOCAL_FILE:
if must_not_be_local: query_hash_ids = set()
@ -3138,7 +3210,7 @@ class DB( HydrusDB.HydrusDB ):
current_update_weight = 0
pending_dict = HydrusData.BuildKeyToListDict( [ ( ( namespace_id, tag_id ), hash_id ) for ( namespace_id, tag_id, hash_id ) in self._c.execute( 'SELECT namespace_id, tag_id, hash_id FROM mappings INDEXED BY mappings_status_index WHERE service_id = ? AND status = ?;', ( service_id, HC.PENDING ) ) ] )
pending_dict = HydrusData.BuildKeyToListDict( [ ( ( namespace_id, tag_id ), hash_id ) for ( namespace_id, tag_id, hash_id ) in self._c.execute( 'SELECT namespace_id, tag_id, hash_id FROM mappings WHERE service_id = ? AND status = ?;', ( service_id, HC.PENDING ) ) ] )
pending_chunks = []
@ -3376,13 +3448,34 @@ class DB( HydrusDB.HydrusDB ):
service_type = service.GetServiceType()
if service_type == HC.LOCAL_FILE: info_types = { HC.SERVICE_INFO_NUM_FILES, HC.SERVICE_INFO_TOTAL_SIZE, HC.SERVICE_INFO_NUM_DELETED_FILES }
elif service_type == HC.FILE_REPOSITORY: info_types = { HC.SERVICE_INFO_NUM_FILES, HC.SERVICE_INFO_TOTAL_SIZE, HC.SERVICE_INFO_NUM_DELETED_FILES, HC.SERVICE_INFO_NUM_THUMBNAILS, HC.SERVICE_INFO_NUM_THUMBNAILS_LOCAL }
elif service_type == HC.LOCAL_TAG: info_types = { HC.SERVICE_INFO_NUM_FILES, HC.SERVICE_INFO_NUM_NAMESPACES, HC.SERVICE_INFO_NUM_TAGS, HC.SERVICE_INFO_NUM_MAPPINGS }
elif service_type == HC.TAG_REPOSITORY: info_types = { HC.SERVICE_INFO_NUM_FILES, HC.SERVICE_INFO_NUM_NAMESPACES, HC.SERVICE_INFO_NUM_TAGS, HC.SERVICE_INFO_NUM_MAPPINGS, HC.SERVICE_INFO_NUM_DELETED_MAPPINGS }
elif service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ): info_types = { HC.SERVICE_INFO_NUM_FILES }
elif service_type == HC.LOCAL_BOORU: info_types = { HC.SERVICE_INFO_NUM_SHARES }
else: info_types = set()
if service_type == HC.LOCAL_FILE:
info_types = { HC.SERVICE_INFO_NUM_FILES, HC.SERVICE_INFO_TOTAL_SIZE, HC.SERVICE_INFO_NUM_DELETED_FILES }
elif service_type == HC.FILE_REPOSITORY:
info_types = { HC.SERVICE_INFO_NUM_FILES, HC.SERVICE_INFO_TOTAL_SIZE, HC.SERVICE_INFO_NUM_DELETED_FILES, HC.SERVICE_INFO_NUM_THUMBNAILS, HC.SERVICE_INFO_NUM_THUMBNAILS_LOCAL }
elif service_type == HC.LOCAL_TAG:
info_types = { HC.SERVICE_INFO_NUM_FILES, HC.SERVICE_INFO_NUM_NAMESPACES, HC.SERVICE_INFO_NUM_TAGS, HC.SERVICE_INFO_NUM_MAPPINGS }
elif service_type == HC.TAG_REPOSITORY:
info_types = { HC.SERVICE_INFO_NUM_FILES, HC.SERVICE_INFO_NUM_NAMESPACES, HC.SERVICE_INFO_NUM_TAGS, HC.SERVICE_INFO_NUM_MAPPINGS, HC.SERVICE_INFO_NUM_DELETED_MAPPINGS }
elif service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
info_types = { HC.SERVICE_INFO_NUM_FILES }
elif service_type == HC.LOCAL_BOORU:
info_types = { HC.SERVICE_INFO_NUM_SHARES }
else:
info_types = set()
service_info = self._GetServiceInfoSpecific( service_id, service_type, info_types )
@ -3844,7 +3937,7 @@ class DB( HydrusDB.HydrusDB ):
self._AddThumbnails( [ ( hash, thumbnail ) ] )
self._AddFile( self._local_file_service_id, hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words )
self._AddFiles( self._local_file_service_id, [ ( hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words ) ] )
content_update = HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_ADD, ( hash, size, mime, timestamp, width, height, duration, num_frames, num_words ) )
@ -3854,7 +3947,14 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'INSERT OR IGNORE INTO local_hashes ( hash_id, md5, sha1, sha512 ) VALUES ( ?, ?, ?, ? );', ( hash_id, sqlite3.Binary( md5 ), sqlite3.Binary( sha1 ), sqlite3.Binary( sha512 ) ) )
if not archive: self._InboxFiles( ( hash_id, ) )
if archive:
self._ArchiveFiles( ( hash_id, ) )
else:
self._InboxFiles( ( hash_id, ) )
if len( service_keys_to_tags ) > 0 and self._c.execute( 'SELECT 1 FROM files_info WHERE service_id = ? AND hash_id = ?;', ( self._local_file_service_id, hash_id ) ).fetchone() is not None:
@ -3952,6 +4052,7 @@ class DB( HydrusDB.HydrusDB ):
def _InitCaches( self ):
self._local_file_service_id = self._GetServiceId( CC.LOCAL_FILE_SERVICE_KEY )
self._trash_service_id = self._GetServiceId( CC.TRASH_SERVICE_KEY )
self._local_tag_service_id = self._GetServiceId( CC.LOCAL_TAG_SERVICE_KEY )
self._combined_file_service_id = self._GetServiceId( CC.COMBINED_FILE_SERVICE_KEY )
self._combined_tag_service_id = self._GetServiceId( CC.COMBINED_TAG_SERVICE_KEY )
@ -4019,7 +4120,7 @@ class DB( HydrusDB.HydrusDB ):
hash_id = self._GetHashId( hash )
self._AddFile( service_id, hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words )
self._AddFiles( service_id, [ ( hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words ) ] )
elif action == HC.CONTENT_UPDATE_PENDING:
@ -4075,6 +4176,7 @@ class DB( HydrusDB.HydrusDB ):
if action == HC.CONTENT_UPDATE_ARCHIVE: self._ArchiveFiles( hash_ids )
elif action == HC.CONTENT_UPDATE_INBOX: self._InboxFiles( hash_ids )
elif action == HC.CONTENT_UPDATE_DELETE: self._DeleteFiles( service_id, hash_ids )
elif action == HC.CONTENT_UPDATE_UNDELETE: self._UndeleteFiles( hash_ids )
@ -4453,6 +4555,8 @@ class DB( HydrusDB.HydrusDB ):
try: service_id = self._GetServiceId( service_key )
except HydrusExceptions.NotFoundException: continue
if service_id in self._service_cache: del self._service_cache[ service_id ]
service = self._GetService( service_id )
( service_key, service_type, name, info ) = service.ToTuple()
@ -4879,6 +4983,44 @@ class DB( HydrusDB.HydrusDB ):
self.pub_after_commit( 'notify_new_pending' )
def _UndeleteFiles( self, hash_ids ):
splayed_hash_ids = HydrusData.SplayListForDB( hash_ids )
rows = self._c.execute( 'SELECT * FROM files_info WHERE service_id = ? AND hash_id IN ' + splayed_hash_ids + ';', ( self._trash_service_id, ) ).fetchall()
# service_id, hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words
hash_ids = { row[ 1 ] for row in rows }
if len( hash_ids ) > 0:
total_size = sum( [ row[ 2 ] for row in rows ] )
num_files = len( rows )
num_thumbnails = len( [ 1 for row in rows if row[ 3 ] in HC.MIMES_WITH_THUMBNAILS ] )
num_inbox = len( hash_ids.intersection( self._inbox_hash_ids ) )
splayed_hash_ids = HydrusData.SplayListForDB( hash_ids )
service_info_updates = []
service_info_updates.append( ( -total_size, self._trash_service_id, HC.SERVICE_INFO_TOTAL_SIZE ) )
service_info_updates.append( ( -num_files, self._trash_service_id, HC.SERVICE_INFO_NUM_FILES ) )
service_info_updates.append( ( -num_thumbnails, self._trash_service_id, HC.SERVICE_INFO_NUM_THUMBNAILS ) )
service_info_updates.append( ( -num_inbox, self._trash_service_id, HC.SERVICE_INFO_NUM_INBOX ) )
self._c.executemany( 'UPDATE service_info SET info = info + ? WHERE service_id = ? AND info_type = ?;', service_info_updates )
self._c.execute( 'DELETE FROM files_info WHERE service_id = ? AND hash_id IN ' + splayed_hash_ids + ';', ( self._trash_service_id, ) )
self._UpdateAutocompleteTagCacheFromFiles( self._trash_service_id, hash_ids, -1 )
new_rows = [ ( hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words ) for ( service_id, hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words ) in rows ]
self._AddFiles( self._local_file_service_id, new_rows )
def _UpdateAutocompleteTagCacheFromFiles( self, file_service_id, hash_ids, direction ):
splayed_hash_ids = HydrusData.SplayListForDB( hash_ids )
@ -4898,18 +5040,6 @@ class DB( HydrusDB.HydrusDB ):
HydrusGlobals.pubsub.pub( 'splash_set_text', 'updating db to v' + HydrusData.ToString( version + 1 ) )
if version == 114:
service_key = CC.LOCAL_BOORU_SERVICE_KEY
service_type = HC.LOCAL_BOORU
name = CC.LOCAL_BOORU_SERVICE_KEY
info = {}
self._AddService( service_key, service_type, name, info )
self._c.execute( 'CREATE TABLE booru_shares ( service_id INTEGER REFERENCES services ( service_id ) ON DELETE CASCADE, share_key BLOB_BYTES, share TEXT_YAML, expiry INTEGER, used_monthly_data INTEGER, max_monthly_data INTEGER, ip_restriction TEXT, notes TEXT, PRIMARY KEY( service_id, share_key ) );' )
if version == 115:
for path in ClientFiles.IterateAllFilePaths():
@ -5475,6 +5605,27 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'CREATE INDEX mappings_status_index ON mappings ( status );' )
if version == 163:
self._c.execute( 'DROP INDEX mappings_status_index;' )
self._c.execute( 'CREATE INDEX mappings_status_pending_index ON mappings ( status ) WHERE status = 1;' )
self._c.execute( 'CREATE INDEX mappings_status_deleted_index ON mappings ( status ) WHERE status = 2;' )
#
info = {}
self._AddService( CC.TRASH_SERVICE_KEY, HC.LOCAL_FILE, CC.TRASH_SERVICE_KEY, info )
self._trash_service_id = self._GetServiceId( CC.TRASH_SERVICE_KEY )
self._local_file_service_id = self._GetServiceId( CC.LOCAL_FILE_SERVICE_KEY )
deleted_hash_ids = [ hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM deleted_files WHERE service_id = ?;', ( self._local_file_service_id, ) ) ]
self._c.executemany( 'INSERT OR IGNORE INTO deleted_files ( service_id, hash_id ) VALUES ( ?, ? );', ( ( self._trash_service_id, hash_id ) for hash_id in deleted_hash_ids ) )
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
HydrusGlobals.is_db_updated = True
@ -5976,8 +6127,6 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'UPDATE services SET info = ? WHERE service_id = ?;', ( info, service_id ) )
if service_id in self._service_cache: del self._service_cache[ service_id ]
def _Vacuum( self ):
@ -6031,7 +6180,6 @@ class DB( HydrusDB.HydrusDB ):
elif action == 'delete_subscription': result = self._DeleteYAMLDump( YAML_DUMP_ID_SUBSCRIPTION, *args, **kwargs )
elif action == 'export_folder': result = self._SetJSONDump( *args, **kwargs )
elif action == 'export_mappings': result = self._ExportToTagArchive( *args, **kwargs )
elif action == 'fatten_autocomplete_cache': result = self._FattenAutocompleteCache( *args, **kwargs )
elif action == 'file_integrity': result = self._CheckFileIntegrity( *args, **kwargs )
elif action == 'gui_session': result = self._SetJSONDump( *args, **kwargs )
elif action == 'hydrus_session': result = self._AddHydrusSession( *args, **kwargs )

View File

@ -190,6 +190,7 @@ def DAEMONDownloadFiles():
for service_key in service_keys:
if service_key == CC.LOCAL_FILE_SERVICE_KEY: break
elif service_key == CC.TRASH_SERVICE_KEY: continue
try: file_repository = wx.GetApp().GetServicesManager().GetService( service_key )
except HydrusExceptions.NotFoundException: continue
@ -404,7 +405,7 @@ def DAEMONSynchroniseSubscriptions():
tags = query.split( ' ' )
all_args = ( ( booru, tags ), )
all_args = ( ( booru_name, tags ), )
elif site_type == HC.SITE_TYPE_HENTAI_FOUNDRY:

View File

@ -2010,7 +2010,7 @@ class UndoManager( object ):
( data_type, action, row ) = content_update.ToTuple()
if data_type == HC.CONTENT_DATA_TYPE_FILES:
if action in ( HC.CONTENT_UPDATE_ADD, HC.CONTENT_UPDATE_DELETE, HC.CONTENT_UPDATE_RESCIND_PETITION ): continue
if action in ( HC.CONTENT_UPDATE_ADD, HC.CONTENT_UPDATE_DELETE, HC.CONTENT_UPDATE_UNDELETE, HC.CONTENT_UPDATE_RESCIND_PETITION ): continue
elif data_type == HC.CONTENT_DATA_TYPE_MAPPINGS:
if action in ( HC.CONTENT_UPDATE_RESCIND_PETITION, HC.CONTENT_UPDATE_ADVANCED ): continue

View File

@ -209,14 +209,22 @@ class GalleryParser( object ):
class GalleryParserBooru( GalleryParser ):
def __init__( self, booru, tags ):
def __init__( self, booru_name, tags ):
try:
self._booru = wx.GetApp().Read( 'remote_booru', booru_name )
except:
raise HydrusExceptions.NotFoundException( 'Attempted to find booru "' + booru_name + '", but it was missing from the database!' )
self._booru = booru
self._tags = tags
self._gallery_advance_num = None
( self._search_url, self._advance_by_page_num, self._search_separator, self._thumb_classname ) = booru.GetGalleryParsingInfo()
( self._search_url, self._advance_by_page_num, self._search_separator, self._thumb_classname ) = self._booru.GetGalleryParsingInfo()
GalleryParser.__init__( self )
@ -296,55 +304,62 @@ class GalleryParserBooru( GalleryParser ):
image_url = None
if image_id is not None:
try:
image = soup.find( id = image_id )
if image is None:
if image_id is not None:
image_string = soup.find( text = re.compile( 'Save this file' ) )
image = soup.find( id = image_id )
if image_string is None: image_string = soup.find( text = re.compile( 'Save this video' ) )
image = image_string.parent
image_url = image[ 'href' ]
else:
if image.name in ( 'img', 'video' ):
if image is None:
image_url = image[ 'src' ]
image_string = soup.find( text = re.compile( 'Save this file' ) )
if 'sample/sample-' in image_url:
if image_string is None: image_string = soup.find( text = re.compile( 'Save this video' ) )
image = image_string.parent
image_url = image[ 'href' ]
else:
if image.name in ( 'img', 'video' ):
# danbooru resized image
image_url = image[ 'src' ]
image = soup.find( id = 'image-resize-link' )
if 'sample/sample-' in image_url:
# danbooru resized image
image = soup.find( id = 'image-resize-link' )
image_url = image[ 'href' ]
elif image.name == 'a':
image_url = image[ 'href' ]
elif image.name == 'a':
if image_data is not None:
links = soup.find_all( 'a' )
for link in links:
image_url = image[ 'href' ]
if link.string == image_data: image_url = link[ 'href' ]
if image_data is not None:
except Exception as e:
links = soup.find_all( 'a' )
for link in links:
if link.string == image_data: image_url = link[ 'href' ]
raise HydrusExceptions.NotFoundException( 'Could not parse a download link for ' + url_base + '!' + os.linesep + HydrusData.ToString( e ) )
if image_url is None:
raise HydrusExceptions.NotFoundException( 'Could not find the image URL!' )
raise HydrusExceptions.NotFoundException( 'Could not parse a download link for ' + url_base + '!' )
image_url = urlparse.urljoin( url_base, image_url )

View File

@ -62,28 +62,6 @@ def GenerateExportFilename( media, terms ):
return filename
def GetAllFileHashes():
file_hashes = set()
for path in IterateAllFilePaths():
( base, filename ) = os.path.split( path )
result = filename.split( '.', 1 )
if len( result ) != 2: continue
( hash_encoded, ext ) = result
try: hash = hash_encoded.decode( 'hex' )
except TypeError: continue
file_hashes.add( hash )
return file_hashes
def GetAllPaths( raw_paths ):
file_paths = []
@ -225,6 +203,24 @@ def GetExpectedServiceUpdatePackagePath( service_key, begin ):
return HC.CLIENT_UPDATES_DIR + os.path.sep + service_key.encode( 'hex' ) + '_' + str( begin ) + '_metadata.json'
def IterateAllFileHashes():
for path in IterateAllFilePaths():
( base, filename ) = os.path.split( path )
result = filename.split( '.', 1 )
if len( result ) != 2: continue
( hash_encoded, ext ) = result
try: hash = hash_encoded.decode( 'hex' )
except TypeError: continue
yield hash
def IterateAllFilePaths():
hex_chars = '0123456789abcdef'
@ -236,6 +232,7 @@ def IterateAllFilePaths():
next_paths = dircache.listdir( dir )
for path in next_paths: yield dir + os.path.sep + path
def IterateAllThumbnailPaths():
@ -485,6 +482,8 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class LocationsManager( object ):
LOCAL_LOCATIONS = { CC.LOCAL_FILE_SERVICE_KEY, CC.TRASH_SERVICE_KEY }
def __init__( self, current, deleted, pending, petitioned ):
self._current = current
@ -502,10 +501,16 @@ class LocationsManager( object ):
def GetCDPP( self ): return ( self._current, self._deleted, self._pending, self._petitioned )
def GetCurrent( self ): return self._current
def GetCurrentRemote( self ): return self._current - set( ( CC.LOCAL_FILE_SERVICE_KEY, ) )
def GetCurrentRemote( self ):
return self._current - self.LOCAL_LOCATIONS
def GetDeleted( self ): return self._deleted
def GetDeletedRemote( self ): return self._deleted - set( ( CC.LOCAL_FILE_SERVICE_KEY, ) )
def GetDeletedRemote( self ):
return self._deleted - self.LOCAL_LOCATIONS
def GetFileRepositoryStrings( self ):
@ -548,14 +553,20 @@ class LocationsManager( object ):
def GetPending( self ): return self._pending
def GetPendingRemote( self ): return self._pending - set( ( CC.LOCAL_FILE_SERVICE_KEY, ) )
def GetPendingRemote( self ):
return self._pending - self.LOCAL_LOCATIONS
def GetPetitioned( self ): return self._petitioned
def GetPetitionedRemote( self ): return self._petitioned - set( ( CC.LOCAL_FILE_SERVICE_KEY, ) )
def GetPetitionedRemote( self ):
return self._petitioned - self.LOCAL_LOCATIONS
def HasDownloading( self ): return CC.LOCAL_FILE_SERVICE_KEY in self._pending
def HasLocal( self ): return CC.LOCAL_FILE_SERVICE_KEY in self._current
def HasLocal( self ): return len( self._current.union( self.LOCAL_LOCATIONS ) ) > 0
def ProcessContentUpdate( self, service_key, content_update ):
@ -568,6 +579,11 @@ class LocationsManager( object ):
self._deleted.discard( service_key )
self._pending.discard( service_key )
if service_key == CC.LOCAL_FILE_SERVICE_KEY:
self._current.discard( CC.TRASH_SERVICE_KEY )
elif action == HC.CONTENT_UPDATE_DELETE:
self._deleted.add( service_key )
@ -575,6 +591,17 @@ class LocationsManager( object ):
self._current.discard( service_key )
self._petitioned.discard( service_key )
if service_key == CC.LOCAL_FILE_SERVICE_KEY:
self._current.add( CC.TRASH_SERVICE_KEY )
elif action == HC.CONTENT_UPDATE_UNDELETE:
self._current.discard( CC.TRASH_SERVICE_KEY )
self._current.add( CC.LOCAL_FILE_SERVICE_KEY )
elif action == HC.CONTENT_UPDATE_PENDING:
if service_key not in self._current: self._pending.add( service_key )

View File

@ -662,6 +662,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
menu = wx.Menu()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY ), p( '&New Local Search' ), p( 'Open a new search tab for your files' ) )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'new_page_query', CC.TRASH_SERVICE_KEY ), p( '&New Trash Search' ), p( 'Open a new search tab for your recently deleted files' ) )
for service in file_repositories: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'new_page_query', service.GetServiceKey() ), p( 'New ' + service.GetName() + ' Search' ), p( 'Open a new search tab for ' + service.GetName() + '.' ) )
if len( petition_resolve_tag_services ) > 0 or len( petition_resolve_file_services ) > 0:
@ -721,7 +722,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
submenu = wx.Menu()
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'vacuum_db' ), p( '&Vacuum' ), p( 'Rebuild the Database.' ) )
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete_orphans' ), p( '&Delete Orphan Files' ), p( 'Go through the client\'s file store, deleting any files that are no longer needed.' ) )
#submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete_orphans' ), p( '&Delete Orphan Files' ), p( 'Go through the client\'s file store, deleting any files that are no longer needed.' ) )
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete_service_info' ), p( '&Clear Service Info Cache' ), p( 'Delete all cache service info, in case it has become desynchronised.' ) )
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'regenerate_thumbnails' ), p( '&Regenerate All Thumbnails' ), p( 'Delete all thumbnails and regenerate from original files.' ) )
submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'file_integrity' ), p( '&Check File Integrity' ), p( 'Review and fix all local file records.' ) )
@ -1183,7 +1184,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
booru = dlg.GetBooru()
self._NewPageImportGallery( HC.SITE_TYPE_BOORU, booru )
self._NewPageImportGallery( HC.SITE_TYPE_BOORU, booru.GetName() )
@ -2489,6 +2490,7 @@ class FrameReviewServices( ClientGUICommon.Frame ):
if service_type in ( HC.LOCAL_FILE, HC.FILE_REPOSITORY ):
self._info_panel.AddF( self._files_text, CC.FLAGS_EXPAND_PERPENDICULAR )
self._info_panel.AddF( self._deleted_files_text, CC.FLAGS_EXPAND_PERPENDICULAR )
if service_type == HC.FILE_REPOSITORY:
@ -2756,10 +2758,11 @@ class FrameReviewServices( ClientGUICommon.Frame ):
num_files = service_info[ HC.SERVICE_INFO_NUM_FILES ]
total_size = service_info[ HC.SERVICE_INFO_TOTAL_SIZE ]
num_deleted_files = service_info[ HC.SERVICE_INFO_NUM_DELETED_FILES ]
self._files_text.SetLabel( HydrusData.ConvertIntToPrettyString( num_files ) + ' files, totalling ' + HydrusData.ConvertIntToBytes( total_size ) )
num_deleted_files = service_info[ HC.SERVICE_INFO_NUM_DELETED_FILES ]
self._deleted_files_text.SetLabel( HydrusData.ConvertIntToPrettyString( num_deleted_files ) + ' deleted files' )
if service_type == HC.FILE_REPOSITORY:

View File

@ -560,11 +560,38 @@ class Canvas( object ):
HydrusGlobals.pubsub.pub( 'clipboard', 'text', path )
def _Delete( self ):
def _Delete( self, service_key = None ):
with ClientGUIDialogs.DialogYesNo( self, 'Delete this file from the database?' ) as dlg:
if service_key is None:
if dlg.ShowModal() == wx.ID_YES: wx.GetApp().Write( 'content_updates', { CC.LOCAL_FILE_SERVICE_KEY : [ HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, ( self._current_display_media.GetHash(), ) ) ] } )
locations_manager = self._current_display_media.GetLocationsManager()
if CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent():
service_key = CC.LOCAL_FILE_SERVICE_KEY
elif CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent():
service_key = CC.TRASH_SERVICE_KEY
else:
return
if service_key == CC.LOCAL_FILE_SERVICE_KEY:
text = 'Send this file to the trash?'
elif service_key == CC.TRASH_SERVICE_KEY:
text = 'Permanently delete this file?'
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
if dlg.ShowModal() == wx.ID_YES: wx.GetApp().Write( 'content_updates', { service_key : [ HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, ( self._current_display_media.GetHash(), ) ) ] } )
self.SetFocus() # annoying bug because of the modal dialog
@ -696,6 +723,16 @@ class Canvas( object ):
if new_position != self._media_container.GetPosition(): self._media_container.SetPosition( new_position )
def _Undelete( self ):
with ClientGUIDialogs.DialogYesNo( self, 'Undelete this file?' ) as dlg:
if dlg.ShowModal() == wx.ID_YES: wx.GetApp().Write( 'content_updates', { CC.TRASH_SERVICE_KEY : [ HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_UNDELETE, ( self._current_display_media.GetHash(), ) ) ] } )
self.SetFocus() # annoying bug because of the modal dialog
def _ZoomIn( self ):
if self._current_display_media is not None:
@ -1005,9 +1042,26 @@ class CanvasWithDetails( Canvas ):
icons_to_show = []
if CC.TRASH_SERVICE_KEY in self._current_media.GetLocationsManager().GetCurrent():
icons_to_show.append( CC.GlobalBMPs.trash )
if self._current_media.HasInbox():
dc.DrawBitmap( CC.GlobalBMPs.inbox, client_width - 18, 2 )
icons_to_show.append( CC.GlobalBMPs.inbox )
if len( icons_to_show ) > 0:
icon_x = 0
for icon in icons_to_show:
dc.DrawBitmap( icon, client_width + icon_x - 18, 2 )
icon_x -= 18
current_y += 18
@ -1135,11 +1189,12 @@ class CanvasPanel( Canvas, wx.Window ):
elif command == 'copy_hash': self._CopyHashToClipboard()
elif command == 'copy_local_url': self._CopyLocalUrlToClipboard()
elif command == 'copy_path': self._CopyPathToClipboard()
elif command == 'delete': self._Delete()
elif command == 'delete': self._Delete( data )
elif command == 'inbox': self._Inbox()
elif command == 'manage_ratings': self._ManageRatings()
elif command == 'manage_tags': wx.CallAfter( self._ManageTags )
elif command == 'open_externally': self._OpenExternally()
elif command == 'undelete': self._Undelete()
else: event.Skip()
@ -1151,6 +1206,8 @@ class CanvasPanel( Canvas, wx.Window ):
services = wx.GetApp().GetServicesManager().GetServices()
locations_manager = self._current_display_media.GetLocationsManager()
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
@ -1182,11 +1239,20 @@ class CanvasPanel( Canvas, wx.Window ):
if self._current_display_media.HasInbox(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'archive' ), '&archive' )
if self._current_display_media.HasArchive(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'inbox' ), 'return to &inbox' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.LOCAL_FILE_SERVICE_KEY ), '&delete' )
if CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.LOCAL_FILE_SERVICE_KEY ), '&delete' )
elif CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.TRASH_SERVICE_KEY ), '&delete from trash now' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'undelete' ), '&undelete' )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally', CC.LOCAL_FILE_SERVICE_KEY ), '&open externally' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally' ), '&open externally' )
share_menu = wx.Menu()
@ -2028,7 +2094,7 @@ class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaListNavigable ):
elif command == 'copy_hash': self._CopyHashToClipboard()
elif command == 'copy_local_url': self._CopyLocalUrlToClipboard()
elif command == 'copy_path': self._CopyPathToClipboard()
elif command == 'delete': self._Delete()
elif command == 'delete': self._Delete( data )
elif command == 'fullscreen_switch': self._FullscreenSwitch()
elif command == 'first': self._ShowFirst()
elif command == 'last': self._ShowLast()
@ -2052,6 +2118,7 @@ class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaListNavigable ):
elif command == 'remove': self._Remove()
elif command == 'slideshow': wx.CallAfter( self._StartSlideshow, data )
elif command == 'slideshow_pause_play': wx.CallAfter( self._PausePlaySlideshow )
elif command == 'undelete': self._Undelete()
elif command == 'zoom_in': self._ZoomIn()
elif command == 'zoom_out': self._ZoomOut()
elif command == 'zoom_switch': self._ZoomSwitch()
@ -2088,6 +2155,8 @@ class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaListNavigable ):
self._last_drag_coordinates = None # to stop successive right-click drag warp bug
locations_manager = self._current_display_media.GetLocationsManager()
menu = wx.Menu()
menu.Append( CC.ID_NULL, self._current_display_media.GetPrettyInfo() )
@ -2137,12 +2206,22 @@ class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaListNavigable ):
if self._current_display_media.HasInbox(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'archive' ), '&archive' )
if self._current_display_media.HasArchive(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'inbox' ), 'return to &inbox' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'remove', CC.LOCAL_FILE_SERVICE_KEY ), '&remove' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.LOCAL_FILE_SERVICE_KEY ), '&delete' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'remove' ), '&remove' )
if CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.LOCAL_FILE_SERVICE_KEY ), '&delete' )
elif CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.TRASH_SERVICE_KEY ), '&delete from trash now' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'undelete' ), '&undelete' )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally', CC.LOCAL_FILE_SERVICE_KEY ), '&open externally' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally' ), '&open externally' )
share_menu = wx.Menu()
@ -2423,7 +2502,7 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaListNavigable
elif command == 'copy_hash': self._CopyHashToClipboard()
elif command == 'copy_local_url': self._CopyLocalUrlToClipboard()
elif command == 'copy_path': self._CopyPathToClipboard()
elif command == 'delete': self._Delete()
elif command == 'delete': self._Delete( data )
elif command == 'fullscreen_switch': self._FullscreenSwitch()
elif command == 'first': self._ShowFirst()
elif command == 'last': self._ShowLast()
@ -2436,6 +2515,7 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaListNavigable
elif command == 'manage_tags': wx.CallAfter( self._ManageTags )
elif command == 'open_externally': self._OpenExternally()
elif command == 'remove': self._Remove()
elif command == 'undelete': self._Undelete()
elif command == 'zoom_in': self._ZoomIn()
elif command == 'zoom_out': self._ZoomOut()
elif command == 'zoom_switch': self._ZoomSwitch()
@ -2470,6 +2550,8 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaListNavigable
i_can_post_ratings = len( local_ratings_services ) > 0
locations_manager = self._current_display_media.GetLocationsManager()
#
self._last_drag_coordinates = None # to stop successive right-click drag warp bug
@ -2523,12 +2605,22 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaListNavigable
if self._current_display_media.HasInbox(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'archive' ), '&archive' )
if self._current_display_media.HasArchive(): menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'inbox' ), 'return to &inbox' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'remove' ), '&remove' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.LOCAL_FILE_SERVICE_KEY ), '&delete' )
if CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.LOCAL_FILE_SERVICE_KEY ), '&delete' )
elif CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent():
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.TRASH_SERVICE_KEY ), '&delete from trash now' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'undelete' ), '&undelete' )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally', CC.LOCAL_FILE_SERVICE_KEY ), '&open externally' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally' ), '&open externally' )
share_menu = wx.Menu()

View File

@ -397,9 +397,7 @@ class AutoCompleteDropdown( wx.Panel ):
if event.GetWheelRotation() > 0: command_type = wx.wxEVT_SCROLLWIN_LINEUP
else: command_type = wx.wxEVT_SCROLLWIN_LINEDOWN
scroll_event = wx.ScrollEvent( command_type )
self._dropdown_list.EventScroll( scroll_event )
wx.PostEvent( self, wx.ScrollWinEvent( command_type ) )
@ -529,6 +527,7 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
services = []
services.append( services_manager.GetService( CC.COMBINED_FILE_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.LOCAL_FILE_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.TRASH_SERVICE_KEY ) )
services.extend( services_manager.GetServices( ( HC.FILE_REPOSITORY, ) ) )
menu = wx.Menu()
@ -1944,18 +1943,19 @@ class ListBox( wx.ScrolledWindow ):
self._background_colour = wx.Colour( 255, 255, 255 )
self._current_y_offset = 0
self._drawn_up_to = 0
self._ordered_strings = []
self._strings_to_terms = {}
self._canvas_bmp = wx.EmptyBitmap( 0, 0, 24 )
self._client_bmp = wx.EmptyBitmap( 0, 0, 24 )
self._current_selected_index = None
self._current_selected_term = None
dc = self._GetScrolledDC()
self._last_virtual_size = None
self._last_view_start = None
self._dirty = True
dc = wx.MemoryDC( self._client_bmp )
dc.SetFont( wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) )
@ -1979,103 +1979,12 @@ class ListBox( wx.ScrolledWindow ):
self.Bind( wx.EVT_CHAR_HOOK, self.EventKeyDown )
self.Bind( wx.EVT_MENU, self.EventMenu )
self.Bind( wx.EVT_SCROLLWIN, self.EventScroll )
def __len__( self ): return len( self._ordered_strings )
def _Activate( self, s, term ): pass
def _DrawText( self, index ):
( my_width, my_height ) = self.GetClientSize()
dc = self._GetScrolledDC()
dc.SetFont( wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) )
i = 0
dc.SetBackground( wx.Brush( self._background_colour ) )
i = index
text = self._ordered_strings[ i ]
( r, g, b ) = self._GetTextColour( text )
text_colour = wx.Colour( r, g, b )
if i == self._current_selected_index:
dc.SetBrush( wx.Brush( text_colour ) )
text_colour = wx.WHITE
dc.SetPen( wx.TRANSPARENT_PEN )
dc.DrawRectangle( 0, i * self._text_y, my_width, self._text_y )
dc.SetTextForeground( text_colour )
( x, y ) = ( 3, i * self._text_y )
dc.DrawText( text, x, y )
def _DrawTexts( self ):
( start_x, start_y ) = self.GetViewStart()
( xUnit, yUnit ) = self.GetScrollPixelsPerUnit()
( my_width, my_height ) = self.GetClientSize()
draw_up_to = ( ( start_y + self._current_y_offset ) * yUnit ) + my_height
if draw_up_to > self._drawn_up_to:
dc = self._GetScrolledDC()
dc.SetFont( wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) )
i = 0
dc.SetBackground( wx.Brush( self._background_colour ) )
if self._drawn_up_to == 0: dc.Clear()
for ( i, text ) in enumerate( self._ordered_strings ):
if i * self._text_y < self._drawn_up_to: continue
if i * self._text_y > draw_up_to: break
( r, g, b ) = self._GetTextColour( text )
text_colour = wx.Colour( r, g, b )
if self._current_selected_index is not None and i == self._current_selected_index:
dc.SetBrush( wx.Brush( text_colour ) )
dc.SetPen( wx.TRANSPARENT_PEN )
dc.DrawRectangle( 0, i * self._text_y, my_width, self._text_y )
text_colour = wx.WHITE
dc.SetTextForeground( text_colour )
( x, y ) = ( 3, i * self._text_y )
dc.DrawText( text, x, y )
self._drawn_up_to = draw_up_to
def _GetIndexUnderMouse( self, mouse_event ):
( xUnit, yUnit ) = self.GetScrollPixelsPerUnit()
@ -2093,17 +2002,66 @@ class ListBox( wx.ScrolledWindow ):
return row_index
def _GetScrolledDC( self ):
cdc = wx.ClientDC( self )
self.DoPrepareDC( cdc ) # because this is a scrolled window
return wx.BufferedDC( cdc, self._canvas_bmp, wx.BUFFER_VIRTUAL_AREA )
def _GetTextColour( self, text ): return ( 0, 111, 250 )
def _Redraw( self, dc ):
( xUnit, yUnit ) = self.GetScrollPixelsPerUnit()
( x_scroll, y_scroll ) = self.GetViewStart()
self._last_view_start = self.GetViewStart()
y_offset = y_scroll * yUnit
( my_width, my_height ) = self.GetClientSize()
first_visible_index = y_offset / self._text_y
last_visible_index = ( y_offset + my_height ) / self._text_y
if ( y_offset + my_height ) % self._text_y != 0:
last_visible_index += 1
last_visible_index = min( last_visible_index, len( self._ordered_strings ) - 1 )
dc.SetFont( wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) )
dc.SetBackground( wx.Brush( self._background_colour ) )
dc.Clear()
for ( i, current_index ) in enumerate( range( first_visible_index, last_visible_index + 1 ) ):
text = self._ordered_strings[ current_index ]
( r, g, b ) = self._GetTextColour( text )
text_colour = wx.Colour( r, g, b )
if self._current_selected_index is not None and current_index == self._current_selected_index:
dc.SetBrush( wx.Brush( text_colour ) )
dc.SetPen( wx.TRANSPARENT_PEN )
dc.DrawRectangle( 0, i * self._text_y, my_width, self._text_y )
text_colour = wx.WHITE
dc.SetTextForeground( text_colour )
( x, y ) = ( 3, i * self._text_y )
dc.DrawText( text, x, y )
self._dirty = False
def _Select( self, index ):
old_index = self._current_selected_index
@ -2116,15 +2074,14 @@ class ListBox( wx.ScrolledWindow ):
self._current_selected_index = index
if old_index is not None: self._DrawText( old_index )
if self._current_selected_index is None: self._current_selected_term = None
if self._current_selected_index is None:
self._current_selected_term = None
else:
self._current_selected_term = self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ]
self._DrawText( self._current_selected_index )
# scroll to index, if needed
y = self._text_y * self._current_selected_index
@ -2153,11 +2110,18 @@ class ListBox( wx.ScrolledWindow ):
self._SetDirty()
def _SetDirty( self ):
self._dirty = True
self.Refresh()
def _TextsHaveChanged( self ):
self._drawn_up_to = 0
self._current_selected_index = None
if self._current_selected_term is not None:
@ -2175,12 +2139,20 @@ class ListBox( wx.ScrolledWindow ):
if self._current_selected_index is None: self._current_selected_term = None
total_height = self._text_y * len( self._ordered_strings )
( my_x, my_y ) = self.GetClientSize()
( my_x, my_y ) = self._canvas_bmp.GetSize()
total_height = max( self._text_y * len( self._ordered_strings ), my_y )
if my_y != total_height: wx.PostEvent( self, wx.SizeEvent() )
else: self._DrawTexts()
( virtual_x, virtual_y ) = self.GetVirtualSize()
if total_height != virtual_y:
wx.PostEvent( self, wx.SizeEvent() )
else:
self._SetDirty()
def EventDClick( self, event ):
@ -2293,63 +2265,37 @@ class ListBox( wx.ScrolledWindow ):
event.Skip()
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp, wx.BUFFER_VIRTUAL_AREA )
def EventPaint( self, event ):
dc = wx.BufferedPaintDC( self, self._client_bmp )
if self._dirty or self._last_view_start != self.GetViewStart():
self._Redraw( dc )
def EventResize( self, event ):
( client_x, client_y ) = self.GetClientSize()
( my_x, my_y ) = self.GetClientSize()
( my_x, my_y ) = self._canvas_bmp.GetSize()
self._num_rows_per_page = my_y / self._text_y
self._num_rows_per_page = client_y / self._text_y
ideal_virtual_size = ( my_x, max( self._text_y * len( self._ordered_strings ), my_y ) )
total_height = self._text_y * len( self._ordered_strings )
if my_x != client_x or my_y != total_height:
if ideal_virtual_size != self._last_virtual_size:
new_y = max( client_y, total_height )
self.SetVirtualSize( ideal_virtual_size )
self.SetVirtualSize( ( client_x, new_y ) )
self._last_virtual_size = ideal_virtual_size
self._canvas_bmp = wx.EmptyBitmap( client_x, new_y, 24 )
if self._client_bmp.GetSize() != ( my_x, my_y ):
self._client_bmp = wx.EmptyBitmap( my_x, my_y, 24 )
self._drawn_up_to = 0
self._SetDirty()
self._DrawTexts()
def EventScroll( self, event ):
# it seems that some scroll events happen after the viewstart has changed, some happen before
# so I have to keep track of a manual current_y_start
( start_x, start_y ) = self.GetViewStart()
( my_virtual_width, my_virtual_height ) = self.GetVirtualSize()
( my_width, my_height ) = self.GetClientSize()
( xUnit, yUnit ) = self.GetScrollPixelsPerUnit()
page_of_y_units = my_height / yUnit
event_type = event.GetEventType()
if event_type == wx.wxEVT_SCROLLWIN_LINEUP: self._current_y_offset = -1
elif event_type == wx.wxEVT_SCROLLWIN_LINEDOWN: self._current_y_offset = 1
elif event_type == wx.wxEVT_SCROLLWIN_THUMBTRACK: self._current_y_offset = 0
elif event_type == wx.wxEVT_SCROLLWIN_THUMBRELEASE: self._current_y_offset = 0
elif event_type == wx.wxEVT_SCROLLWIN_PAGEUP: self._current_y_offset = - page_of_y_units
elif event_type == wx.wxEVT_SCROLLWIN_PAGEDOWN: self._current_y_offset = page_of_y_units
elif event_type == wx.wxEVT_SCROLLWIN_TOP: self._current_y_offset = - start_y
elif event_type == wx.wxEVT_SCROLLWIN_BOTTOM: self._current_y_offset = ( my_virtual_height / yUnit ) - start_y
self._DrawTexts()
self._current_y_offset = 0
event.Skip()
def GetClientData( self, s = None ):

View File

@ -1247,11 +1247,12 @@ class DialogInputFileSystemPredicates( Dialog ):
services_manager = wx.GetApp().GetServicesManager()
ratings_like = services_manager.GetServices( ( HC.LOCAL_RATING_LIKE, ) )
ratings_numerical = services_manager.GetServices( ( HC.LOCAL_RATING_NUMERICAL, ) )
ratings_services = services_manager.GetServices( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
if len( ratings_like ) > 0: pred_classes.append( ClientGUIPredicates.PanelPredicateSystemRatingLike )
if len( ratings_numerical ) > 0: pred_classes.append( ClientGUIPredicates.PanelPredicateSystemRatingNumerical )
if len( ratings_services ) > 0:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemRating )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_SIMILAR_TO: pred_classes.append( ClientGUIPredicates.PanelPredicateSystemSimilarTo )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_SIZE: pred_classes.append( ClientGUIPredicates.PanelPredicateSystemSize )
@ -3176,7 +3177,7 @@ class DialogPageChooser( Dialog ):
file_repos = [ ( 'page_query', service_key ) for service_key in [ service.GetServiceKey() for service in self._services if service.GetServiceType() == HC.FILE_REPOSITORY ] ]
entries = [ ( 'page_query', CC.LOCAL_FILE_SERVICE_KEY ) ] + file_repos
entries = [ ( 'page_query', CC.LOCAL_FILE_SERVICE_KEY ), ( 'page_query', CC.TRASH_SERVICE_KEY ) ] + file_repos
elif menu_keyword == 'download': entries = [ ( 'page_import_url', None ), ( 'page_import_thread_watcher', None ), ( 'menu', 'gallery' ) ]
elif menu_keyword == 'gallery':
@ -3240,7 +3241,7 @@ class DialogPageChooser( Dialog ):
booru = dlg.GetBooru()
HydrusGlobals.pubsub.pub( 'new_import_gallery', HC.SITE_TYPE_BOORU, booru )
HydrusGlobals.pubsub.pub( 'new_import_gallery', HC.SITE_TYPE_BOORU, booru.GetName() )

View File

@ -7729,13 +7729,32 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
def EventDelete( self, event ):
with ClientGUIDialogs.DialogYesNo( self, 'Delete this file from the database?' ) as dlg:
locations_manager = self._current_media.GetLocationsManager()
if CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent():
text = 'Send this file to the trash?'
service_key = CC.LOCAL_FILE_SERVICE_KEY
elif CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent():
text = 'Permanently delete this file?'
service_key = CC.TRASH_SERVICE_KEY
else:
return
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._CommitCurrentChanges()
wx.GetApp().Write( 'content_updates', { CC.LOCAL_FILE_SERVICE_KEY : [ HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, ( self._current_media.GetHash(), ) ) ] } )
wx.GetApp().Write( 'content_updates', { service_key : [ HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, ( self._current_media.GetHash(), ) ) ] } )

View File

@ -411,11 +411,13 @@ class FullscreenHoverFrameRatings( FullscreenHoverFrame ):
self._icon_panel.SetBackgroundColour( wx.WHITE )
self._trash_icon = ClientGUICommon.BufferedWindowIcon( self._icon_panel, CC.GlobalBMPs.trash )
self._inbox_icon = ClientGUICommon.BufferedWindowIcon( self._icon_panel, CC.GlobalBMPs.inbox )
icon_hbox = wx.BoxSizer( wx.HORIZONTAL )
icon_hbox.AddF( ( 16, 16 ), CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
icon_hbox.AddF( self._trash_icon, CC.FLAGS_MIXED )
icon_hbox.AddF( self._inbox_icon, CC.FLAGS_MIXED )
self._icon_panel.SetSizer( icon_hbox )
@ -492,10 +494,31 @@ class FullscreenHoverFrameRatings( FullscreenHoverFrame ):
if self._current_media is not None:
if self._current_media.HasInbox():
has_inbox = self._current_media.HasInbox()
has_trash = CC.TRASH_SERVICE_KEY in self._current_media.GetLocationsManager().GetCurrent()
if has_inbox or has_trash:
self._icon_panel.Show()
if has_inbox:
self._inbox_icon.Show()
else:
self._inbox_icon.Hide()
if has_trash:
self._trash_icon.Show()
else:
self._trash_icon.Hide()
else:
self._icon_panel.Hide()

View File

@ -1935,10 +1935,10 @@ class ManagementPanelImportsGallery( ManagementPanelImports ):
def gallery_parsers_factory( raw_tags ):
booru = self._gallery_type
booru_name = self._gallery_type
tags = raw_tags.split( ' ' )
return ( ClientDownloading.GalleryParserBooru( booru, tags ), )
return ( ClientDownloading.GalleryParserBooru( booru_name, tags ), )
elif self._site_type == HC.SITE_TYPE_DEVIANT_ART:
@ -2027,9 +2027,9 @@ class ManagementPanelImportsGallery( ManagementPanelImports ):
if self._site_type == HC.SITE_TYPE_BOORU:
booru = self._gallery_type
name = self._gallery_type
name = booru.GetName()
booru = wx.GetApp().Read( 'remote_booru', name )
namespaces = booru.GetNamespaces()
initial_search_value = 'search tags'

View File

@ -94,7 +94,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
def _Archive( self ):
hashes = self._GetSelectedHashes( CC.DISCRIMINANT_INBOX )
hashes = self._GetSelectedHashes( discriminant = CC.DISCRIMINANT_INBOX )
if len( hashes ) > 0:
@ -161,8 +161,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
if len( media_results ) > 0:
try: ClientGUICanvas.CanvasFullscreenMediaListCustomFilter( self.GetTopLevelParent(), self._page_key, self._file_service_key, media_results, shortcuts )
except: wx.MessageBox( traceback.format_exc() )
ClientGUICanvas.CanvasFullscreenMediaListCustomFilter( self.GetTopLevelParent(), self._page_key, self._file_service_key, media_results, shortcuts )
@ -170,39 +169,52 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
def _Delete( self, file_service_key ):
if file_service_key == CC.LOCAL_FILE_SERVICE_KEY:
hashes = self._GetSelectedHashes( has_location = file_service_key )
num_to_delete = len( hashes )
if num_to_delete > 0:
hashes = self._GetSelectedHashes( CC.DISCRIMINANT_LOCAL )
num_to_delete = len( hashes )
if num_to_delete:
if file_service_key == CC.LOCAL_FILE_SERVICE_KEY:
if num_to_delete == 1: message = 'Are you sure you want to delete this file?'
else: message = 'Are you sure you want to delete these ' + HydrusData.ConvertIntToPrettyString( num_to_delete ) + ' files?'
if num_to_delete == 1: text = 'Are you sure you want to send this file to the trash?'
else: text = 'Are you sure you want send these ' + HydrusData.ConvertIntToPrettyString( num_to_delete ) + ' files to the trash?'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
elif file_service_key == CC.TRASH_SERVICE_KEY:
if num_to_delete == 1: text = 'Are you sure you want to permanently delete this file?'
else: text = 'Are you sure you want to permanently delete these ' + HydrusData.ConvertIntToPrettyString( num_to_delete ) + ' files?'
else:
if num_to_delete == 1: text = 'Are you sure you want to admin-delete this file?'
else: text = 'Are you sure you want to admin-delete these ' + HydrusData.ConvertIntToPrettyString( num_to_delete ) + ' files?'
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
if dlg.ShowModal() == wx.ID_YES:
if file_service_key in ( CC.LOCAL_FILE_SERVICE_KEY, CC.TRASH_SERVICE_KEY ):
self.SetFocussedMedia( self._page_key, None )
if file_service_key == CC.TRASH_SERVICE_KEY:
self.SetFocussedMedia( self._page_key, None )
try: wx.GetApp().Write( 'content_updates', { file_service_key : [ HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, hashes ) ] } )
except: wx.MessageBox( traceback.format_exc() )
wx.GetApp().Write( 'content_updates', { file_service_key : [ HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, hashes ) ] } )
else:
content_update = HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_PETITION, ( hashes, 'admin' ) )
service_keys_to_content_updates = { file_service_key : ( content_update, ) }
wx.GetApp().Write( 'content_updates', service_keys_to_content_updates )
else:
hashes = self._GetSelectedHashes()
content_update = HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_PETITION, ( hashes, 'admin' ) )
service_keys_to_content_updates = { file_service_key : ( content_update, ) }
wx.GetApp().Write( 'content_updates', service_keys_to_content_updates )
def _DeselectSelect( self, media_to_deselect, media_to_select ):
@ -245,12 +257,11 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
def _Filter( self ):
media_results = self.GenerateMediaResults( discriminant = CC.DISCRIMINANT_LOCAL, selected_media = set( self._selected_media ) )
media_results = self.GenerateMediaResults( has_location = CC.LOCAL_FILE_SERVICE_KEY, selected_media = set( self._selected_media ) )
if len( media_results ) > 0:
try: ClientGUICanvas.CanvasFullscreenMediaListFilterInbox( self.GetTopLevelParent(), self._page_key, self._file_service_key, media_results )
except: wx.MessageBox( traceback.format_exc() )
ClientGUICanvas.CanvasFullscreenMediaListFilterInbox( self.GetTopLevelParent(), self._page_key, self._file_service_key, media_results )
@ -302,11 +313,11 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
def _GetSelectedHashes( self, discriminant = None, not_uploaded_to = None ):
def _GetSelectedHashes( self, has_location = None, discriminant = None, not_uploaded_to = None ):
result = set()
for media in self._selected_media: result.update( media.GetHashes( discriminant, not_uploaded_to ) )
for media in self._selected_media: result.update( media.GetHashes( has_location, discriminant, not_uploaded_to ) )
return result
@ -381,7 +392,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
def _Inbox( self ):
hashes = self._GetSelectedHashes( CC.DISCRIMINANT_ARCHIVE )
hashes = self._GetSelectedHashes( discriminant = CC.DISCRIMINANT_ARCHIVE )
if len( hashes ) > 0:
@ -515,13 +526,9 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
if len( media_results ) > 0:
try:
service = wx.GetApp().GetServicesManager().GetService( service_key )
if service.GetServiceType() == HC.LOCAL_RATING_LIKE: ClientGUICanvas.RatingsFilterFrameLike( self.GetTopLevelParent(), self._page_key, service_key, media_results )
except: wx.MessageBox( traceback.format_exc() )
service = wx.GetApp().GetServicesManager().GetService( service_key )
if service.GetServiceType() == HC.LOCAL_RATING_LIKE: ClientGUICanvas.RatingsFilterFrameLike( self.GetTopLevelParent(), self._page_key, service_key, media_results )
@ -641,6 +648,27 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
def _Undelete( self ):
hashes = self._GetSelectedHashes( has_location = CC.TRASH_SERVICE_KEY )
num_to_undelete = len( hashes )
if num_to_undelete > 0:
if num_to_undelete == 1: text = 'Are you sure you want to undelete this file?'
else: text = 'Are you sure you want to undelete these ' + HydrusData.ConvertIntToPrettyString( num_to_undelete ) + ' files?'
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
wx.GetApp().Write( 'content_updates', { CC.TRASH_SERVICE_KEY : [ HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_UNDELETE, hashes ) ] } )
def _UploadFiles( self, file_service_key ):
hashes = self._GetSelectedHashes( not_uploaded_to = file_service_key )
@ -976,24 +1004,20 @@ class MediaPanelThumbnails( MediaPanel ):
if len( self._selected_media ) > 0:
try:
flat_media = []
for media in self._sorted_media:
flat_media = []
for media in self._sorted_media:
if media in self._selected_media:
if media in self._selected_media:
if media.IsCollection(): flat_media.extend( media.GetFlatMedia() )
else: flat_media.append( media )
if media.IsCollection(): flat_media.extend( media.GetFlatMedia() )
else: flat_media.append( media )
with ClientGUIDialogs.DialogSetupExport( None, flat_media ) as dlg: dlg.ShowModal()
self.SetFocus()
except: wx.MessageBox( traceback.format_exc() )
with ClientGUIDialogs.DialogSetupExport( None, flat_media ) as dlg: dlg.ShowModal()
self.SetFocus()
@ -1360,7 +1384,7 @@ class MediaPanelThumbnails( MediaPanel ):
if command == 'archive': self._Archive()
elif command == 'copy_bmp': self._CopyBMPToClipboard()
elif command == 'copy_files':
with wx.BusyCursor(): wx.GetApp().Write( 'copy_files', self._GetSelectedHashes( CC.DISCRIMINANT_LOCAL ) )
with wx.BusyCursor(): wx.GetApp().Write( 'copy_files', self._GetSelectedHashes( discriminant = CC.DISCRIMINANT_LOCAL ) )
elif command == 'copy_hash': self._CopyHashToClipboard()
elif command == 'copy_hashes': self._CopyHashesToClipboard()
elif command == 'copy_local_url': self._CopyLocalUrlToClipboard()
@ -1371,7 +1395,7 @@ class MediaPanelThumbnails( MediaPanel ):
elif command == 'custom_filter': self._CustomFilter()
elif command == 'delete': self._Delete( data )
elif command == 'download': wx.GetApp().Write( 'content_updates', { CC.LOCAL_FILE_SERVICE_KEY : [ HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_PENDING, self._GetSelectedHashes( CC.DISCRIMINANT_NOT_LOCAL ) ) ] } )
elif command == 'download': wx.GetApp().Write( 'content_updates', { CC.LOCAL_FILE_SERVICE_KEY : [ HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_PENDING, self._GetSelectedHashes( discriminant = CC.DISCRIMINANT_NOT_LOCAL ) ) ] } )
elif command == 'export_files': self._ExportFiles()
elif command == 'export_tags': self._ExportTags()
elif command == 'filter': self._Filter()
@ -1394,6 +1418,7 @@ class MediaPanelThumbnails( MediaPanel ):
elif command == 'select': self._Select( data )
elif command == 'share_on_local_booru': self._ShareOnLocalBooru()
elif command == 'show_selection_in_new_query_page': self._ShowSelectionInNewQueryPage()
elif command == 'undelete': self._Undelete()
elif command == 'upload': self._UploadFiles( data )
elif command == 'key_up': self._MoveFocussedThumbnail( -1, 0, False )
elif command == 'key_down': self._MoveFocussedThumbnail( 1, 0, False )
@ -1510,7 +1535,8 @@ class MediaPanelThumbnails( MediaPanel ):
all_locations_managers = [ media.GetLocationsManager() for media in self._selected_media ]
selection_has_local = True in ( locations_manager.HasLocal() for locations_manager in all_locations_managers )
selection_has_local_file_service = True in ( CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent() for locations_manager in all_locations_managers )
selection_has_trash = True in ( CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent() for locations_manager in all_locations_managers )
selection_has_inbox = True in ( media.HasInbox() for media in self._selected_media )
selection_has_archive = True in ( media.HasArchive() for media in self._selected_media )
@ -1596,6 +1622,8 @@ class MediaPanelThumbnails( MediaPanel ):
inbox_phrase = 'return selected to inbox'
remove_phrase = 'remove selected'
local_delete_phrase = 'delete selected'
trash_delete_phrase = 'delete selected from trash now'
undelete_phrase = 'undelete selected'
dump_phrase = 'dump selected to 4chan'
export_phrase = 'files'
copy_phrase = 'files'
@ -1622,6 +1650,8 @@ class MediaPanelThumbnails( MediaPanel ):
inbox_phrase = 'return to inbox'
remove_phrase = 'remove'
local_delete_phrase = 'delete'
trash_delete_phrase = 'delete from trash now'
undelete_phrase = 'undelete'
dump_phrase = 'dump to 4chan'
export_phrase = 'file'
copy_phrase = 'file'
@ -1675,7 +1705,7 @@ class MediaPanelThumbnails( MediaPanel ):
# we can download (set pending to local) when we have permission, a file is not local and not already downloading and current
if not locations_manager.HasLocal() and not locations_manager.HasDownloading(): selection_downloadable_file_service_keys.update( downloadable_file_service_keys & locations_manager.GetCurrentRemote() )
if not CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent() and not locations_manager.HasDownloading(): selection_downloadable_file_service_keys.update( downloadable_file_service_keys & locations_manager.GetCurrentRemote() )
# we can petition when we have permission and a file is current
# we can re-petition an already petitioned file
@ -1788,7 +1818,7 @@ class MediaPanelThumbnails( MediaPanel ):
#
if selection_has_local and multiple_selected:
if selection_has_local_file_service and multiple_selected:
filter_menu = wx.Menu()
@ -1801,20 +1831,27 @@ class MediaPanelThumbnails( MediaPanel ):
menu.AppendSeparator()
if selection_has_local:
if selection_has_inbox: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'archive' ), archive_phrase )
if selection_has_archive: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'inbox' ), inbox_phrase )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'remove' ), remove_phrase )
if selection_has_local_file_service:
if selection_has_inbox: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'archive' ), archive_phrase )
if selection_has_archive: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'inbox' ), inbox_phrase )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'remove' ), remove_phrase )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.LOCAL_FILE_SERVICE_KEY ), local_delete_phrase )
if selection_has_trash:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', CC.TRASH_SERVICE_KEY ), trash_delete_phrase )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'undelete' ), undelete_phrase )
# share
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally', CC.LOCAL_FILE_SERVICE_KEY ), '&open externally' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally' ), '&open externally' )
share_menu = wx.Menu()
@ -2325,8 +2362,34 @@ class Thumbnail( Selectable ):
locations_manager = self.GetLocationsManager()
if inbox: dc.DrawBitmap( CC.GlobalBMPs.inbox, width - 18, 0 )
elif CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetPending(): dc.DrawBitmap( CC.GlobalBMPs.downloading, width - 18, 0 )
icons_to_draw = []
if CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetPending():
icons_to_draw.append( CC.GlobalBMPs.downloading )
if CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent():
icons_to_draw.append( CC.GlobalBMPs.trash )
if inbox:
icons_to_draw.append( CC.GlobalBMPs.inbox )
if len( icons_to_draw ) > 0:
icon_x = 0
for icon in icons_to_draw:
dc.DrawBitmap( icon, width + icon_x - 18, 0 )
icon_x -= 18
if self._dump_status == CC.DUMPER_DUMPED_OK: dc.DrawBitmap( CC.GlobalBMPs.dump_ok, width - 18, 18 )
elif self._dump_status == CC.DUMPER_RECOVERABLE_ERROR: dc.DrawBitmap( CC.GlobalBMPs.dump_recoverable, width - 18, 18 )

View File

@ -503,7 +503,7 @@ class PanelPredicateSystemNumWords( PanelPredicateSystem ):
return info
class PanelPredicateSystemRatingLike( PanelPredicateSystem ):
class PanelPredicateSystemRating( PanelPredicateSystem ):
PREDICATE_TYPE = HC.PREDICATE_TYPE_SYSTEM_RATING
@ -511,15 +511,17 @@ class PanelPredicateSystemRatingLike( PanelPredicateSystem ):
PanelPredicateSystem.__init__( self, parent )
#
local_like_services = wx.GetApp().GetServicesManager().GetServices( ( HC.LOCAL_RATING_LIKE, ) )
self._checkboxes_to_info = {}
self._like_checkboxes_to_info = {}
self._rating_ctrls = []
self._like_rating_ctrls = []
gridbox = wx.FlexGridSizer( 0, 4 )
gridbox_like = wx.FlexGridSizer( 0, 4 )
gridbox.AddGrowableCol( 0, 1 )
gridbox_like.AddGrowableCol( 0, 1 )
for service in local_like_services:
@ -530,24 +532,68 @@ class PanelPredicateSystemRatingLike( PanelPredicateSystem ):
not_rated_checkbox = wx.CheckBox( self, label = 'not rated' )
rating_ctrl = ClientGUICommon.RatingLikeDialog( self, service_key )
self._checkboxes_to_info[ rated_checkbox ] = ( service_key, ClientRatings.SET )
self._checkboxes_to_info[ not_rated_checkbox ] = ( service_key, ClientRatings.NULL )
self._rating_ctrls.append( rating_ctrl )
self._like_checkboxes_to_info[ rated_checkbox ] = ( service_key, ClientRatings.SET )
self._like_checkboxes_to_info[ not_rated_checkbox ] = ( service_key, ClientRatings.NULL )
self._like_rating_ctrls.append( rating_ctrl )
gridbox.AddF( wx.StaticText( self, label = name ), CC.FLAGS_MIXED )
gridbox.AddF( rated_checkbox, CC.FLAGS_MIXED )
gridbox.AddF( not_rated_checkbox, CC.FLAGS_MIXED )
gridbox.AddF( rating_ctrl, CC.FLAGS_MIXED )
gridbox_like.AddF( wx.StaticText( self, label = name ), CC.FLAGS_MIXED )
gridbox_like.AddF( rated_checkbox, CC.FLAGS_MIXED )
gridbox_like.AddF( not_rated_checkbox, CC.FLAGS_MIXED )
gridbox_like.AddF( rating_ctrl, CC.FLAGS_MIXED )
self.SetSizer( gridbox )
#
local_numerical_services = wx.GetApp().GetServicesManager().GetServices( ( HC.LOCAL_RATING_NUMERICAL, ) )
self._numerical_checkboxes_to_info = {}
self._numerical_rating_ctrls_to_info = {}
gridbox_numerical = wx.FlexGridSizer( 0, 5 )
gridbox_numerical.AddGrowableCol( 0, 1 )
for service in local_numerical_services:
name = service.GetName()
service_key = service.GetServiceKey()
rated_checkbox = wx.CheckBox( self, label = 'rated' )
not_rated_checkbox = wx.CheckBox( self, label = 'not rated' )
choice = wx.Choice( self, choices=[ '>', '<', '=', u'\u2248' ] )
rating_ctrl = ClientGUICommon.RatingNumericalDialog( self, service_key )
choice.Select( 2 )
self._numerical_checkboxes_to_info[ rated_checkbox ] = ( service_key, ClientRatings.SET )
self._numerical_checkboxes_to_info[ not_rated_checkbox ] = ( service_key, ClientRatings.NULL )
self._numerical_rating_ctrls_to_info[ rating_ctrl ] = choice
gridbox_numerical.AddF( wx.StaticText( self, label = name ), CC.FLAGS_MIXED )
gridbox_numerical.AddF( rated_checkbox, CC.FLAGS_MIXED )
gridbox_numerical.AddF( not_rated_checkbox, CC.FLAGS_MIXED )
gridbox_numerical.AddF( choice, CC.FLAGS_MIXED )
gridbox_numerical.AddF( rating_ctrl, CC.FLAGS_MIXED )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( gridbox_like, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
vbox.AddF( gridbox_numerical, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.SetSizer( vbox )
def GetInfo( self ):
infos = []
for ( checkbox, ( service_key, rating_state ) ) in self._checkboxes_to_info.items():
#
for ( checkbox, ( service_key, rating_state ) ) in self._like_checkboxes_to_info.items():
if checkbox.GetValue() == True:
@ -564,7 +610,7 @@ class PanelPredicateSystemRatingLike( PanelPredicateSystem ):
for ctrl in self._rating_ctrls:
for ctrl in self._like_rating_ctrls:
rating_state = ctrl.GetRatingState()
@ -585,67 +631,9 @@ class PanelPredicateSystemRatingLike( PanelPredicateSystem ):
return infos
#
def GetPredicates( self ):
infos = self.GetInfo()
predicates = [ HydrusData.Predicate( self.PREDICATE_TYPE, info ) for info in infos ]
return predicates
class PanelPredicateSystemRatingNumerical( PanelPredicateSystem ):
PREDICATE_TYPE = HC.PREDICATE_TYPE_SYSTEM_RATING
def __init__( self, parent ):
PanelPredicateSystem.__init__( self, parent )
local_numerical_services = wx.GetApp().GetServicesManager().GetServices( ( HC.LOCAL_RATING_NUMERICAL, ) )
self._checkboxes_to_info = {}
self._rating_ctrls_to_info = {}
gridbox = wx.FlexGridSizer( 0, 5 )
gridbox.AddGrowableCol( 0, 1 )
for service in local_numerical_services:
name = service.GetName()
service_key = service.GetServiceKey()
rated_checkbox = wx.CheckBox( self, label = 'rated' )
not_rated_checkbox = wx.CheckBox( self, label = 'not rated' )
choice = wx.Choice( self, choices=[ '>', '<', '=', u'\u2248' ] )
rating_ctrl = ClientGUICommon.RatingNumericalDialog( self, service_key )
choice.Select( 2 )
self._checkboxes_to_info[ rated_checkbox ] = ( service_key, ClientRatings.SET )
self._checkboxes_to_info[ not_rated_checkbox ] = ( service_key, ClientRatings.NULL )
self._rating_ctrls_to_info[ rating_ctrl ] = choice
gridbox.AddF( wx.StaticText( self, label = name ), CC.FLAGS_MIXED )
gridbox.AddF( rated_checkbox, CC.FLAGS_MIXED )
gridbox.AddF( not_rated_checkbox, CC.FLAGS_MIXED )
gridbox.AddF( choice, CC.FLAGS_MIXED )
gridbox.AddF( rating_ctrl, CC.FLAGS_MIXED )
self.SetSizer( gridbox )
def GetInfo( self ):
infos = []
for ( checkbox, ( service_key, rating_state ) ) in self._checkboxes_to_info.items():
for ( checkbox, ( service_key, rating_state ) ) in self._numerical_checkboxes_to_info.items():
if checkbox.GetValue() == True:
@ -662,7 +650,7 @@ class PanelPredicateSystemRatingNumerical( PanelPredicateSystem ):
for ( ctrl, choice ) in self._rating_ctrls_to_info.items():
for ( ctrl, choice ) in self._numerical_rating_ctrls_to_info.items():
rating_state = ctrl.GetRatingState()
@ -678,6 +666,8 @@ class PanelPredicateSystemRatingNumerical( PanelPredicateSystem ):
#
return infos

View File

@ -179,12 +179,22 @@ class MediaList( object ):
for media in self._collected_media: media.DeletePending( service_key )
def GenerateMediaResults( self, discriminant = None, selected_media = None, unrated = None ):
def GenerateMediaResults( self, has_location = None, discriminant = None, selected_media = None, unrated = None ):
media_results = []
for media in self._sorted_media:
if has_location is not None:
locations_manager = media.GetLocationsManager()
if has_location not in locations_manager.GetCurrent():
continue
if selected_media is not None and media not in selected_media: continue
if media.IsCollection(): media_results.extend( media.GenerateMediaResults( discriminant ) )
@ -253,12 +263,19 @@ class MediaList( object ):
if data_type == HC.CONTENT_DATA_TYPE_FILES:
if action == HC.CONTENT_UPDATE_DELETE and service_key == self._file_service_key:
if action == HC.CONTENT_UPDATE_DELETE:
affected_singleton_media = self._GetMedia( hashes, 'singletons' )
affected_collected_media = [ media for media in self._collected_media if media.HasNoMedia() ]
permanently_deleted = service_key == CC.TRASH_SERVICE_KEY and self._file_service_key in ( CC.TRASH_SERVICE_KEY, CC.LOCAL_FILE_SERVICE_KEY )
self._RemoveMedia( affected_singleton_media, affected_collected_media )
repo_deleted = service_key not in ( CC.LOCAL_FILE_SERVICE_KEY, CC.TRASH_SERVICE_KEY ) and service_key == self._file_service_key
if permanently_deleted or repo_deleted:
affected_singleton_media = self._GetMedia( hashes, 'singletons' )
affected_collected_media = [ media for media in self._collected_media if media.HasNoMedia() ]
self._RemoveMedia( affected_singleton_media, affected_collected_media )
@ -542,14 +559,14 @@ class MediaCollection( MediaList, Media ):
def GetHash( self ): return self.GetDisplayMedia().GetHash()
def GetHashes( self, discriminant = None, not_uploaded_to = None ):
def GetHashes( self, has_location = None, discriminant = None, not_uploaded_to = None ):
if discriminant is None and not_uploaded_to is None: return self._hashes
if has_location is None and discriminant is None and not_uploaded_to is None: return self._hashes
else:
result = set()
for media in self._sorted_media: result.update( media.GetHashes( discriminant, not_uploaded_to ) )
for media in self._sorted_media: result.update( media.GetHashes( has_location, discriminant, not_uploaded_to ) )
return result
@ -646,20 +663,23 @@ class MediaSingleton( Media ):
def GetHash( self ): return self._media_result.GetHash()
def GetHashes( self, discriminant = None, not_uploaded_to = None ):
def GetHashes( self, has_location = None, discriminant = None, not_uploaded_to = None ):
locations_manager = self._media_result.GetLocationsManager()
if discriminant is not None:
inbox = self._media_result.GetInbox()
locations_manager = self._media_result.GetLocationsManager()
if ( discriminant == CC.DISCRIMINANT_INBOX and not inbox ) or ( discriminant == CC.DISCRIMINANT_ARCHIVE and inbox ) or ( discriminant == CC.DISCRIMINANT_LOCAL and not locations_manager.HasLocal() ) or ( discriminant == CC.DISCRIMINANT_NOT_LOCAL and locations_manager.HasLocal() ): return set()
if not_uploaded_to is not None:
if has_location is not None:
locations_manager = self._media_result.GetLocationsManager()
if has_location not in locations_manager.GetCurrent(): return set()
if not_uploaded_to is not None:
if not_uploaded_to in locations_manager.GetCurrentRemote(): return set()
@ -681,6 +701,7 @@ class MediaSingleton( Media ):
if self.HasInbox(): return 1
else: return 0
def GetNumWords( self ): return self._media_result.GetNumWords()
@ -910,12 +931,10 @@ class MediaResult( object ):
if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ): tags_manager.ProcessContentUpdate( service_key, content_update )
elif service_type in ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ):
if service_type == HC.LOCAL_FILE:
if service_key == CC.LOCAL_FILE_SERVICE_KEY:
if action == HC.CONTENT_UPDATE_ADD and not locations_manager.HasLocal(): inbox = True
elif action == HC.CONTENT_UPDATE_ARCHIVE: inbox = False
if action == HC.CONTENT_UPDATE_ARCHIVE: inbox = False
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, tags_manager, locations_manager, local_ratings, remote_ratings )

View File

@ -49,7 +49,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 163
SOFTWARE_VERSION = 164
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -80,6 +80,7 @@ CONTENT_UPDATE_RATING = 9
CONTENT_UPDATE_DENY_PEND = 11
CONTENT_UPDATE_DENY_PETITION = 12
CONTENT_UPDATE_ADVANCED = 13
CONTENT_UPDATE_UNDELETE = 14
content_update_string_lookup = {}
@ -95,6 +96,7 @@ content_update_string_lookup[ CONTENT_UPDATE_INBOX ] = 'inbox'
content_update_string_lookup[ CONTENT_UPDATE_RATING ] = 'rating'
content_update_string_lookup[ CONTENT_UPDATE_DENY_PEND ] = 'deny pend'
content_update_string_lookup[ CONTENT_UPDATE_DENY_PETITION ] = 'deny petition'
content_update_string_lookup[ CONTENT_UPDATE_UNDELETE ] = 'undelete'
IMPORT_FOLDER_TYPE_DELETE = 0
IMPORT_FOLDER_TYPE_SYNCHRONISE = 1

View File

@ -214,7 +214,7 @@ def ConvertShortcutToPrettyShortcut( modifier, key ):
def ConvertSiteTypeGalleryTypeToPrettyString( site_type, gallery_type ):
if site_type == HC.SITE_TYPE_BOORU: s = gallery_type.GetName()
if site_type == HC.SITE_TYPE_BOORU: s = gallery_type
else:
s = HC.site_type_string_lookup[ site_type ]
@ -1089,7 +1089,7 @@ class ContentUpdate( object ):
hashes = set( ( hash, ) )
elif self._action in ( HC.CONTENT_UPDATE_ARCHIVE, HC.CONTENT_UPDATE_DELETE, HC.CONTENT_UPDATE_INBOX, HC.CONTENT_UPDATE_PENDING, HC.CONTENT_UPDATE_RESCIND_PENDING, HC.CONTENT_UPDATE_RESCIND_PETITION ): hashes = self._row
elif self._action in ( HC.CONTENT_UPDATE_ARCHIVE, HC.CONTENT_UPDATE_DELETE, HC.CONTENT_UPDATE_UNDELETE, HC.CONTENT_UPDATE_INBOX, HC.CONTENT_UPDATE_PENDING, HC.CONTENT_UPDATE_RESCIND_PENDING, HC.CONTENT_UPDATE_RESCIND_PETITION ): hashes = self._row
elif self._action == HC.CONTENT_UPDATE_PETITION: ( hashes, reason ) = self._row
elif self._data_type == HC.CONTENT_DATA_TYPE_MAPPINGS:

View File

@ -177,7 +177,9 @@ class TestDownloaders( unittest.TestCase ):
#
downloader = ClientDownloading.GalleryParserBooru( ClientDefaults.GetDefaultBoorus()[ 'sankaku chan' ], [ 'animal_ears' ] )
wx.GetApp().SetRead( 'remote_booru', ClientDefaults.GetDefaultBoorus()[ 'sankaku chan' ] )
downloader = ClientDownloading.GalleryParserBooru( 'sankaku chan', [ 'animal_ears' ] )
#
@ -243,7 +245,9 @@ class TestDownloaders( unittest.TestCase ):
#
downloader = ClientDownloading.GalleryParserBooru( ClientDefaults.GetDefaultBoorus()[ 'e621' ], [ 'hair' ] )
wx.GetApp().SetRead( 'remote_booru', ClientDefaults.GetDefaultBoorus()[ 'e621' ] )
downloader = ClientDownloading.GalleryParserBooru( 'e621', [ 'hair' ] )
#

View File

@ -997,7 +997,7 @@ class TestClientDB( unittest.TestCase ):
result_service_keys = { service.GetServiceKey() for service in result }
self.assertItemsEqual( { CC.LOCAL_FILE_SERVICE_KEY, CC.LOCAL_TAG_SERVICE_KEY }, result_service_keys )
self.assertItemsEqual( { CC.TRASH_SERVICE_KEY, CC.LOCAL_FILE_SERVICE_KEY, CC.LOCAL_TAG_SERVICE_KEY }, result_service_keys )
#

BIN
static/trash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B