Version 220

This commit is contained in:
Hydrus Network Developer 2016-08-24 13:36:56 -05:00
parent 3b011b993c
commit fc0828c3ec
25 changed files with 921 additions and 803 deletions

View File

@ -8,6 +8,34 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 220</h3></li>
<ul>
<li>fixed collection selection show action lookup for the preview window</li>
<li>files added through the normal import dialog or import folders will be not be fed into the import pipeline if it looks like they are already in use by another process</li>
<li>(hence setting an import folder to a browser download destination should be more reliable)</li>
<li>rewrote some critical phash generation code that was running extremely slow when importing large pngs with transparency</li>
<li>all listctrls that have an edit button now also trigger the edit event on item activate (e.g. double-click/enter key on an item)</li>
<li>added zoom in/out/switch to edit shortcut action dropdowns, can't promise it works everywhere</li>
<li>'stopping' a gallery page search will now correctly make sure the search queue is unpaused so it can move on to any subsequent search</li>
<li>the way excess messages are added to the popup message manager is quicker, improving gui responsivity on message spam</li>
<li>fixed a potential race condition when a file import occurs at the same time as client_files rebalancing</li>
<li>fixed a potential race condition when a check_file_integrity db maintenance occurs at the same time as client_files rebalancing</li>
<li>the client will no longer suppress some core debug stuff</li>
<li>fixed an invalid event handler on canvas close bug</li>
<li>fixed a long-time wx locale issue</li>
<li>fixed a misc main gui parent assert issue</li>
<li>fixed some invalid non-wx-thread calllater calls</li>
<li>cleaned up some inartful showmessage calls</li>
<li>cleaned up some datacache init code</li>
<li>fixed some shutdown event handling</li>
<li>generally improved and fixed in some cases how threads signal job status</li>
<li>improved some thread-interaction timings</li>
<li>improved how files and thumbnails are deleted</li>
<li>file imports add their files to the client_files structure in a more sensible way</li>
<li>misc fixes</li>
<li>misc layout fixes</li>
<li>misc cleanup</li>
</ul>
<li><h3>version 219</h3></li>
<ul>
<li>wrote framework for per-mime zoom options--mimes now have separate show actions for the media viewer and the preview window and zoom in to fit</li>

View File

@ -578,18 +578,13 @@ class ClientFilesManager( object ):
def AddFile( self, hash, mime, source_path ):
def LocklessAddFile( self, hash, mime, source_path ):
with self._lock:
dest_path = self._GenerateExpectedFilePath( hash, mime )
if not os.path.exists( dest_path ):
dest_path = self._GenerateExpectedFilePath( hash, mime )
if not os.path.exists( dest_path ):
HydrusPaths.MirrorFile( source_path, dest_path )
return dest_path
HydrusPaths.MirrorFile( source_path, dest_path )
@ -597,182 +592,45 @@ class ClientFilesManager( object ):
with self._lock:
path = self._GenerateExpectedFullSizeThumbnailPath( hash )
self.LocklessAddFullSizeThumbnail( hash, thumbnail )
with open( path, 'wb' ) as f:
f.write( thumbnail )
def LocklessAddFullSizeThumbnail( self, hash, thumbnail ):
path = self._GenerateExpectedFullSizeThumbnailPath( hash )
with open( path, 'wb' ) as f:
f.write( thumbnail )
self._controller.pub( 'new_thumbnails', { hash } )
def CheckFileIntegrity( self, *args, **kwargs ):
with self._lock:
self._controller.WriteSynchronous( 'file_integrity', *args, **kwargs )
def ClearOrphans( self, move_location = None ):
job_key = ClientThreading.JobKey( cancellable = True )
job_key.SetVariable( 'popup_title', 'clearing orphans' )
job_key.SetVariable( 'popup_text_1', 'preparing' )
self._controller.pub( 'message', job_key )
orphan_paths = []
orphan_thumbnails = []
for ( i, path ) in enumerate( self._IterateAllFilePaths() ):
with self._lock:
( i_paused, should_quit ) = job_key.WaitIfNeeded()
job_key = ClientThreading.JobKey( cancellable = True )
if should_quit:
return
job_key.SetVariable( 'popup_title', 'clearing orphans' )
job_key.SetVariable( 'popup_text_1', 'preparing' )
if i % 100 == 0:
status = 'reviewed ' + HydrusData.ConvertIntToPrettyString( i ) + ' files, found ' + HydrusData.ConvertIntToPrettyString( len( orphan_paths ) ) + ' orphans'
job_key.SetVariable( 'popup_text_1', status )
self._controller.pub( 'message', job_key )
try:
is_an_orphan = False
( directory, filename ) = os.path.split( path )
should_be_a_hex_hash = filename[:64]
hash = should_be_a_hex_hash.decode( 'hex' )
is_an_orphan = HydrusGlobals.client_controller.Read( 'is_an_orphan', 'file', hash )
except:
is_an_orphan = True
orphan_paths = []
orphan_thumbnails = []
if is_an_orphan:
orphan_paths.append( path )
time.sleep( 2 )
for ( i, path ) in enumerate( self._IterateAllThumbnailPaths() ):
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
return
if i % 100 == 0:
status = 'reviewed ' + HydrusData.ConvertIntToPrettyString( i ) + ' thumbnails, found ' + HydrusData.ConvertIntToPrettyString( len( orphan_thumbnails ) ) + ' orphans'
job_key.SetVariable( 'popup_text_1', status )
try:
is_an_orphan = False
( directory, filename ) = os.path.split( path )
should_be_a_hex_hash = filename[:64]
hash = should_be_a_hex_hash.decode( 'hex' )
is_an_orphan = HydrusGlobals.client_controller.Read( 'is_an_orphan', 'thumbnail', hash )
except:
is_an_orphan = True
if is_an_orphan:
orphan_thumbnails.append( path )
time.sleep( 2 )
if len( orphan_paths ) > 0:
if move_location is None:
status = 'found ' + HydrusData.ConvertIntToPrettyString( len( orphan_paths ) ) + ' orphans, now deleting'
job_key.SetVariable( 'popup_text_1', status )
time.sleep( 5 )
for path in orphan_paths:
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
return
HydrusData.Print( 'Deleting the orphan ' + path )
status = 'deleting orphan files: ' + HydrusData.ConvertValueRangeToPrettyString( i + 1, len( orphan_paths ) )
job_key.SetVariable( 'popup_text_1', status )
HydrusPaths.DeletePath( path )
else:
status = 'found ' + HydrusData.ConvertIntToPrettyString( len( orphan_paths ) ) + ' orphans, now moving to ' + move_location
job_key.SetVariable( 'popup_text_1', status )
time.sleep( 5 )
for path in orphan_paths:
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
return
( source_dir, filename ) = os.path.split( path )
dest = os.path.join( move_location, filename )
dest = HydrusPaths.AppendPathUntilNoConflicts( dest )
HydrusData.Print( 'Moving the orphan ' + path + ' to ' + dest )
status = 'moving orphan files: ' + HydrusData.ConvertValueRangeToPrettyString( i + 1, len( orphan_paths ) )
job_key.SetVariable( 'popup_text_1', status )
HydrusPaths.MergeFile( path, dest )
if len( orphan_thumbnails ) > 0:
status = 'found ' + HydrusData.ConvertIntToPrettyString( len( orphan_thumbnails ) ) + ' orphan thumbnails, now deleting'
job_key.SetVariable( 'popup_text_1', status )
time.sleep( 5 )
for ( i, path ) in enumerate( orphan_thumbnails ):
for ( i, path ) in enumerate( self._IterateAllFilePaths() ):
( i_paused, should_quit ) = job_key.WaitIfNeeded()
@ -781,33 +639,188 @@ class ClientFilesManager( object ):
return
status = 'deleting orphan thumbnails: ' + HydrusData.ConvertValueRangeToPrettyString( i + 1, len( orphan_thumbnails ) )
if i % 100 == 0:
status = 'reviewed ' + HydrusData.ConvertIntToPrettyString( i ) + ' files, found ' + HydrusData.ConvertIntToPrettyString( len( orphan_paths ) ) + ' orphans'
job_key.SetVariable( 'popup_text_1', status )
try:
is_an_orphan = False
( directory, filename ) = os.path.split( path )
should_be_a_hex_hash = filename[:64]
hash = should_be_a_hex_hash.decode( 'hex' )
is_an_orphan = HydrusGlobals.client_controller.Read( 'is_an_orphan', 'file', hash )
except:
is_an_orphan = True
if is_an_orphan:
orphan_paths.append( path )
time.sleep( 2 )
for ( i, path ) in enumerate( self._IterateAllThumbnailPaths() ):
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
return
if i % 100 == 0:
status = 'reviewed ' + HydrusData.ConvertIntToPrettyString( i ) + ' thumbnails, found ' + HydrusData.ConvertIntToPrettyString( len( orphan_thumbnails ) ) + ' orphans'
job_key.SetVariable( 'popup_text_1', status )
try:
is_an_orphan = False
( directory, filename ) = os.path.split( path )
should_be_a_hex_hash = filename[:64]
hash = should_be_a_hex_hash.decode( 'hex' )
is_an_orphan = HydrusGlobals.client_controller.Read( 'is_an_orphan', 'thumbnail', hash )
except:
is_an_orphan = True
if is_an_orphan:
orphan_thumbnails.append( path )
time.sleep( 2 )
if len( orphan_paths ) > 0:
if move_location is None:
status = 'found ' + HydrusData.ConvertIntToPrettyString( len( orphan_paths ) ) + ' orphans, now deleting'
job_key.SetVariable( 'popup_text_1', status )
time.sleep( 5 )
for path in orphan_paths:
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
return
HydrusData.Print( 'Deleting the orphan ' + path )
status = 'deleting orphan files: ' + HydrusData.ConvertValueRangeToPrettyString( i + 1, len( orphan_paths ) )
job_key.SetVariable( 'popup_text_1', status )
HydrusPaths.DeletePath( path )
else:
status = 'found ' + HydrusData.ConvertIntToPrettyString( len( orphan_paths ) ) + ' orphans, now moving to ' + move_location
job_key.SetVariable( 'popup_text_1', status )
time.sleep( 5 )
for path in orphan_paths:
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
return
( source_dir, filename ) = os.path.split( path )
dest = os.path.join( move_location, filename )
dest = HydrusPaths.AppendPathUntilNoConflicts( dest )
HydrusData.Print( 'Moving the orphan ' + path + ' to ' + dest )
status = 'moving orphan files: ' + HydrusData.ConvertValueRangeToPrettyString( i + 1, len( orphan_paths ) )
job_key.SetVariable( 'popup_text_1', status )
HydrusPaths.MergeFile( path, dest )
if len( orphan_thumbnails ) > 0:
status = 'found ' + HydrusData.ConvertIntToPrettyString( len( orphan_thumbnails ) ) + ' orphan thumbnails, now deleting'
job_key.SetVariable( 'popup_text_1', status )
HydrusData.Print( 'Deleting the orphan ' + path )
time.sleep( 5 )
HydrusPaths.DeletePath( path )
for ( i, path ) in enumerate( orphan_thumbnails ):
( i_paused, should_quit ) = job_key.WaitIfNeeded()
if should_quit:
return
status = 'deleting orphan thumbnails: ' + HydrusData.ConvertValueRangeToPrettyString( i + 1, len( orphan_thumbnails ) )
job_key.SetVariable( 'popup_text_1', status )
HydrusData.Print( 'Deleting the orphan ' + path )
HydrusPaths.DeletePath( path )
if len( orphan_paths ) == 0 and len( orphan_thumbnails ) == 0:
if len( orphan_paths ) == 0 and len( orphan_thumbnails ) == 0:
final_text = 'no orphans found!'
else:
final_text = HydrusData.ConvertIntToPrettyString( len( orphan_paths ) ) + ' orphan files and ' + HydrusData.ConvertIntToPrettyString( len( orphan_thumbnails ) ) + ' orphan thumbnails cleared!'
final_text = 'no orphans found!'
job_key.SetVariable( 'popup_text_1', final_text )
else:
HydrusData.Print( job_key.ToString() )
final_text = HydrusData.ConvertIntToPrettyString( len( orphan_paths ) ) + ' orphan files and ' + HydrusData.ConvertIntToPrettyString( len( orphan_thumbnails ) ) + ' orphan thumbnails cleared!'
job_key.Finish()
job_key.SetVariable( 'popup_text_1', final_text )
HydrusData.Print( job_key.ToString() )
job_key.Finish()
def DeleteFiles( self, hashes ):
def DelayedDeleteFiles( self, hashes ):
time.sleep( 2 )
with self._lock:
@ -826,7 +839,9 @@ class ClientFilesManager( object ):
def DeleteThumbnails( self, hashes ):
def DelayedDeleteThumbnails( self, hashes ):
time.sleep( 2 )
with self._lock:
@ -845,24 +860,37 @@ class ClientFilesManager( object ):
with self._lock:
if mime is None:
path = self._LookForFilePath( hash )
else:
path = self._GenerateExpectedFilePath( hash, mime )
return self.LocklessGetFilePath( hash, mime )
if not os.path.exists( path ):
raise HydrusExceptions.FileMissingException( 'No file found at path + ' + path + '!' )
def ImportFile( self, *args, **kwargs ):
with self._lock:
return path
return self._controller.WriteSynchronous( 'import_file', *args, **kwargs )
def LocklessGetFilePath( self, hash, mime = None ):
if mime is None:
path = self._LookForFilePath( hash )
else:
path = self._GenerateExpectedFilePath( hash, mime )
if not os.path.exists( path ):
raise HydrusExceptions.FileMissingException( 'No file found at path + ' + path + '!' )
return path
def GetFullSizeThumbnailPath( self, hash ):
with self._lock:
@ -1092,10 +1120,10 @@ class ClientFilesManager( object ):
class DataCache( object ):
def __init__( self, controller, cache_size_key ):
def __init__( self, controller, cache_size ):
self._controller = controller
self._cache_size_key = cache_size_key
self._cache_size = cache_size
self._keys_to_data = {}
self._keys_fifo = []
@ -1157,7 +1185,7 @@ class DataCache( object ):
options = self._controller.GetOptions()
while self._total_estimated_memory_footprint > options[ self._cache_size_key ]:
while self._total_estimated_memory_footprint > self._cache_size:
self._DeleteItem()
@ -1546,7 +1574,11 @@ class RenderedImageCache( object ):
self._controller = controller
self._data_cache = DataCache( self._controller, 'fullscreen_cache_size' )
options = self._controller.GetOptions()
cache_size = options[ 'fullscreen_cache_size' ]
self._data_cache = DataCache( self._controller, cache_size )
def Clear( self ): self._data_cache.Clear()
@ -1602,7 +1634,12 @@ class ThumbnailCache( object ):
def __init__( self, controller ):
self._controller = controller
self._data_cache = DataCache( self._controller, 'thumbnail_cache_size' )
options = self._controller.GetOptions()
cache_size = options[ 'thumbnail_cache_size' ]
self._data_cache = DataCache( self._controller, cache_size )
self._client_files_manager = self._controller.GetClientFilesManager()
self._lock = threading.Lock()

View File

@ -583,6 +583,8 @@ class Controller( HydrusController.HydrusController ):
self.CallBlockingToWx( wx_code_gui )
# ShowText will now popup as a message, as popup message manager has overwritten the hooks
HydrusController.HydrusController.InitView( self )
self._local_service = None
@ -608,8 +610,21 @@ class Controller( HydrusController.HydrusController ):
self._daemons.append( HydrusThreading.DAEMONQueue( self, 'FlushRepositoryUpdates', ClientDaemons.DAEMONFlushServiceUpdates, 'service_updates_delayed', period = 5 ) )
if HydrusGlobals.is_first_start: wx.CallAfter( self._gui.DoFirstStart )
if HydrusGlobals.is_db_updated: wx.CallLater( 1, HydrusData.ShowText, 'The client has updated to version ' + str( HC.SOFTWARE_VERSION ) + '!' )
if HydrusGlobals.is_first_start:
message = 'Hi, this looks like the first time you have started the hydrus client.'
message += os.linesep * 2
message += 'Don\'t forget to check out the help if you haven\'t already.'
message += os.linesep * 2
message += 'You can right-click popup messages like this to dismiss them.'
HydrusData.ShowText( message )
if HydrusGlobals.is_db_updated:
HydrusData.ShowText( 'The client has updated to version ' + str( HC.SOFTWARE_VERSION ) + '!' )
def MaintainDB( self, stop_time = None ):
@ -709,7 +724,7 @@ class Controller( HydrusController.HydrusController ):
self._menu_open = False
wx.CallAfter( menu.Destroy )
menu.Destroy()
def PrepStringForDisplay( self, text ):
@ -753,7 +768,7 @@ class Controller( HydrusController.HydrusController ):
text += os.linesep * 2
text += 'You can change the port this client tries to host its local server on in services->manage services.'
wx.CallLater( 1, HydrusData.ShowText, text )
HydrusData.ShowText( text )
except:
@ -772,7 +787,7 @@ class Controller( HydrusController.HydrusController ):
text += os.linesep * 2
text += HydrusData.ToUnicode( e )
wx.CallLater( 1, HydrusData.ShowText, text )
HydrusData.ShowText( text )
@ -824,7 +839,7 @@ class Controller( HydrusController.HydrusController ):
text += os.linesep * 2
text += 'You can change the port this client tries to host its local server on in file->options.'
wx.CallLater( 1, HydrusData.ShowText, text )
HydrusData.ShowText( text )
except:
@ -843,7 +858,7 @@ class Controller( HydrusController.HydrusController ):
text += os.linesep * 2
text += HydrusData.ToUnicode( e )
wx.CallLater( 1, HydrusData.ShowText, text )
HydrusData.ShowText( text )
@ -924,7 +939,11 @@ class Controller( HydrusController.HydrusController ):
self._app = wx.App()
self._app.SetAssertMode( wx.PYAPP_ASSERT_SUPPRESS )
self._app.locale = wx.Locale( wx.LANGUAGE_DEFAULT ) # Very important
# I have had this as 'suppress' before
# The default is to create exceptions, and since this stuff is usually pissy locale/missing parent stuff, we don't want to kill the boot
self._app.SetAssertMode( wx.PYAPP_ASSERT_EXCEPTION )
HydrusData.Print( 'booting controller...' )

View File

@ -1316,7 +1316,7 @@ class DB( HydrusDB.HydrusDB ):
HydrusData.Print( job_key.ToString() )
wx.CallLater( 1000 * 30, job_key.Delete )
job_key.Delete( 30 )
@ -1872,7 +1872,8 @@ class DB( HydrusDB.HydrusDB ):
try:
path = client_files_manager.GetFilePath( hash, mime )
# lockless because this db call is made by the locked client files manager
path = client_files_manager.LocklessGetFilePath( hash, mime )
except HydrusExceptions.FileMissingException:
@ -2308,7 +2309,7 @@ class DB( HydrusDB.HydrusDB ):
file_hashes = self._GetHashes( deletable_file_hash_ids )
wx.CallAfter( client_files_manager.DeleteFiles, file_hashes )
self._controller.CallToThread( client_files_manager.DelayedDeleteFiles, file_hashes )
useful_thumbnail_hash_ids = { hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM current_files WHERE service_id != ? AND hash_id IN ' + HydrusData.SplayListForDB( hash_ids ) + ';', ( self._trash_service_id, ) ) }
@ -2319,7 +2320,7 @@ class DB( HydrusDB.HydrusDB ):
thumbnail_hashes = self._GetHashes( deletable_thumbnail_hash_ids )
wx.CallAfter( client_files_manager.DeleteThumbnails, thumbnail_hashes )
self._controller.CallToThread( client_files_manager.DelayedDeleteThumbnails, thumbnail_hashes )
@ -4958,7 +4959,7 @@ class DB( HydrusDB.HydrusDB ):
def _ImportFile( self, path, import_file_options = None, override_deleted = False, url = None ):
def _ImportFile( self, temp_path, import_file_options = None, override_deleted = False, url = None ):
if import_file_options is None:
@ -4967,9 +4968,9 @@ class DB( HydrusDB.HydrusDB ):
( archive, exclude_deleted_files, min_size, min_resolution ) = import_file_options.ToTuple()
HydrusImageHandling.ConvertToPngIfBmp( path )
HydrusImageHandling.ConvertToPngIfBmp( temp_path )
hash = HydrusFileHandling.GetHashFromPath( path )
hash = HydrusFileHandling.GetHashFromPath( temp_path )
hash_id = self._GetHashId( hash )
@ -4999,16 +5000,9 @@ class DB( HydrusDB.HydrusDB ):
elif status == CC.STATUS_NEW:
mime = HydrusFileHandling.GetMime( path )
mime = HydrusFileHandling.GetMime( temp_path )
client_files_manager = self._controller.GetClientFilesManager()
dest_path = client_files_manager.AddFile( hash, mime, path )
# I moved the file copy up because passing an original filename with unicode chars to getfileinfo
# was causing problems in windows.
( size, mime, width, height, duration, num_frames, num_words ) = HydrusFileHandling.GetFileInfo( dest_path )
( size, mime, width, height, duration, num_frames, num_words ) = HydrusFileHandling.GetFileInfo( temp_path )
if width is not None and height is not None:
@ -5018,8 +5012,6 @@ class DB( HydrusDB.HydrusDB ):
if width < min_x or height < min_y:
os.remove( dest_path )
raise Exception( 'Resolution too small' )
@ -5029,34 +5021,31 @@ class DB( HydrusDB.HydrusDB ):
if size < min_size:
os.remove( dest_path )
raise Exception( 'File too small' )
timestamp = HydrusData.GetNow()
client_files_manager = self._controller.GetClientFilesManager()
if mime in HC.MIMES_WITH_THUMBNAILS:
thumbnail = HydrusFileHandling.GenerateThumbnail( dest_path )
thumbnail = HydrusFileHandling.GenerateThumbnail( temp_path )
client_files_manager.AddFullSizeThumbnail( hash, thumbnail )
# lockless because this db call is made by the locked client files manager
client_files_manager.LocklessAddFullSizeThumbnail( hash, thumbnail )
if mime in ( HC.IMAGE_JPEG, HC.IMAGE_PNG ):
try:
phash = ClientImageHandling.GeneratePerceptualHash( dest_path )
self._c.execute( 'INSERT OR REPLACE INTO perceptual_hashes ( hash_id, phash ) VALUES ( ?, ? );', ( hash_id, sqlite3.Binary( phash ) ) )
except:
pass
phash = ClientImageHandling.GeneratePerceptualHash( temp_path )
self._c.execute( 'INSERT OR REPLACE INTO perceptual_hashes ( hash_id, phash ) VALUES ( ?, ? );', ( hash_id, sqlite3.Binary( phash ) ) )
# lockless because this db call is made by the locked client files manager
client_files_manager.LocklessAddFile( hash, mime, temp_path )
self._AddFilesInfo( [ ( hash_id, size, mime, width, height, duration, num_frames, num_words ) ], overwrite = True )
@ -5066,7 +5055,7 @@ class DB( HydrusDB.HydrusDB ):
self.pub_content_updates_after_commit( { CC.LOCAL_FILE_SERVICE_KEY : [ content_update ] } )
( md5, sha1, sha512 ) = HydrusFileHandling.GetExtraHashesFromPath( dest_path )
( md5, sha1, sha512 ) = HydrusFileHandling.GetExtraHashesFromPath( temp_path )
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 ) ) )
@ -6569,150 +6558,6 @@ class DB( HydrusDB.HydrusDB ):
self._controller.pub( 'splash_set_title_text', 'updating db to v' + str( version + 1 ) )
if version == 160:
self._c.execute( 'REPLACE INTO yaml_dumps VALUES ( ?, ?, ? );', ( YAML_DUMP_ID_REMOTE_BOORU, 'e621', ClientDefaults.GetDefaultBoorus()[ 'e621' ] ) )
self._c.execute( 'DROP TABLE ratings_filter;' )
if version == 161:
self._c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ?;', ( YAML_DUMP_ID_GUI_SESSION, ) )
self._c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ?;', ( YAML_DUMP_ID_EXPORT_FOLDER, ) )
#
for filename in os.listdir( HC.CLIENT_UPDATES_DIR ):
path = os.path.join( HC.CLIENT_UPDATES_DIR, filename )
with open( path, 'rb' ) as f:
inefficient_string = f.read()
try:
( dump_type, dump_version, dump ) = json.loads( inefficient_string )
serialisable_info = json.loads( dump )
better_string = json.dumps( ( dump_type, dump_version, serialisable_info ) )
with open( path, 'wb' ) as f:
f.write( better_string )
except:
continue
if version == 162:
self._c.execute( 'DROP INDEX mappings_service_id_tag_id_index;' )
self._c.execute( 'DROP INDEX mappings_service_id_hash_id_index;' )
self._c.execute( 'DROP INDEX mappings_service_id_status_index;' )
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_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 ) )
if version == 164:
self._c.execute( 'CREATE TABLE file_trash ( hash_id INTEGER PRIMARY KEY, timestamp INTEGER );' )
self._c.execute( 'CREATE INDEX file_trash_timestamp ON file_trash ( timestamp );' )
self._trash_service_id = self._GetServiceId( CC.TRASH_SERVICE_KEY )
trash_hash_ids = [ hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM files_info WHERE service_id = ?;', ( self._trash_service_id, ) ) ]
now = HydrusData.GetNow()
self._c.executemany( 'INSERT OR IGNORE INTO file_trash ( hash_id, timestamp ) VALUES ( ?, ? );', ( ( hash_id, now ) for hash_id in trash_hash_ids ) )
self._c.execute( 'DELETE FROM service_info WHERE service_id = ?;', ( self._trash_service_id, ) )
#
self._c.execute( 'DROP INDEX mappings_status_pending_index;' )
self._c.execute( 'DROP INDEX mappings_status_deleted_index;' )
self._c.execute( 'CREATE INDEX mappings_status_index ON mappings ( status );' )
#
self._c.execute( 'REPLACE INTO yaml_dumps VALUES ( ?, ?, ? );', ( YAML_DUMP_ID_REMOTE_BOORU, 'yande.re', ClientDefaults.GetDefaultBoorus()[ 'yande.re' ] ) )
if version == 169:
result = self._c.execute( 'SELECT tag_id FROM tags WHERE tag = ?;', ( '', ) ).fetchone()
if result is not None:
( tag_id, ) = result
self._c.execute( 'DELETE FROM mappings WHERE tag_id = ?;', ( tag_id, ) )
#
def iterate_all_file_paths():
client_files_default = os.path.join( self._db_dir, 'client_files' )
for prefix in HydrusData.IterateHexPrefixes():
dir = os.path.join( client_files_default, prefix )
next_paths = os.listdir( dir )
for path in next_paths:
yield os.path.join( dir, path )
for ( i, path ) in enumerate( iterate_all_file_paths() ):
try: os.chmod( path, stat.S_IWRITE | stat.S_IREAD )
except: pass
if i % 100 == 0:
self._controller.pub( 'splash_set_status_text', 'updating file permissions ' + HydrusData.ConvertIntToPrettyString( i ) )
if version == 171:
self._controller.pub( 'splash_set_status_text', 'moving updates about' )
@ -8701,7 +8546,7 @@ class DB( HydrusDB.HydrusDB ):
job_key.SetVariable( 'popup_text_1', 'done!' )
wx.CallLater( 1000 * 30, job_key.Delete )
job_key.Delete( 30 )

View File

@ -79,6 +79,8 @@ def DAEMONDownloadFiles( controller ):
if num_downloads > 0:
client_files_manager = controller.GetClientFilesManager()
successful_hashes = set()
job_key = ClientThreading.JobKey()
@ -125,7 +127,7 @@ def DAEMONDownloadFiles( controller ):
controller.WaitUntilPubSubsEmpty()
controller.WriteSynchronous( 'import_file', temp_path, override_deleted = True )
client_files_manager.ImportFile( temp_path, override_deleted = True )
successful_hashes.add( hash )

View File

@ -262,7 +262,9 @@ def THREADDownloadURL( job_key, url, url_string ):
job_key.DeleteVariable( 'popup_gauge_1' )
job_key.SetVariable( 'popup_text_1', 'importing ' + url_string )
( result, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', temp_path )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
( result, hash ) = client_files_manager.ImportFile( temp_path )
except HydrusExceptions.NetworkException:
@ -360,7 +362,9 @@ def THREADDownloadURLs( job_key, urls, title ):
job_key.SetVariable( 'popup_text_2', 'importing' )
( result, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', temp_path )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
( result, hash ) = client_files_manager.ImportFile( temp_path )
except HydrusExceptions.NetworkException:

View File

@ -53,7 +53,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
title = self._controller.PrepStringForDisplay( 'Hydrus Client' )
ClientGUITopLevelWindows.FrameThatResizes.__init__( self, None, title, 'main_gui' )
ClientGUITopLevelWindows.FrameThatResizes.__init__( self, None, title, 'main_gui', float_on_parent = False )
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.ImportFiles ) )
@ -72,7 +72,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
self._notebook = wx.Notebook( self )
self._notebook.Bind( wx.EVT_MIDDLE_DOWN, self.EventNotebookMiddleClick )
self._notebook.Bind( wx.EVT_RIGHT_DOWN, self.EventNotebookMenu )
self._notebook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventNotebookPageChanged )
self.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventNotebookPageChanged )
self._tab_right_click_index = -1
@ -124,7 +124,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
self._RefreshStatusBar()
# as we are in oninit, callafter and calllater( 0 ) are different
# as we are in oninit, callafter and calllater( 1ms ) are different
# later waits until the mainloop is running, I think.
# after seems to execute synchronously
@ -484,6 +484,8 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
def _CheckFileIntegrity( self ):
client_files_manager = self._controller.GetClientFilesManager()
message = 'This will go through all the files the database thinks it has and check that they actually exist. Any files that are missing will be deleted from the internal record.'
message += os.linesep * 2
message += 'You can perform a quick existence check, which will only look to see if a file exists, or a thorough content check, which will also make sure existing files are not corrupt or otherwise incorrect.'
@ -494,7 +496,10 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
result = dlg.ShowModal()
if result == wx.ID_YES: self._controller.Write( 'file_integrity', 'quick' )
if result == wx.ID_YES:
self._controller.CallToThread( client_files_manager.CheckFileIntegrity, 'quick' )
elif result == wx.ID_NO:
text = 'If an existing file is found to be corrupt/incorrect, would you like to move it or delete it?'
@ -511,13 +516,13 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
path = HydrusData.ToUnicode( dlg_3.GetPath() )
self._controller.Write( 'file_integrity', 'thorough', path )
self._controller.CallToThread( client_files_manager.CheckFileIntegrity, 'thorough', path )
elif result == wx.ID_NO:
self._controller.Write( 'file_integrity', 'thorough' )
self._controller.CallToThread( client_files_manager.CheckFileIntegrity, 'thorough' )
@ -663,10 +668,10 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
page.CleanBeforeDestroy()
wx.CallAfter( page.Destroy )
page.Destroy()
wx.CallAfter( gc.collect )
gc.collect()
def _FetchIP( self, service_key ):
@ -2122,7 +2127,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
HydrusData.Print( job_key.ToString() )
wx.CallLater( 1000 * 5, job_key.Delete )
job_key.Delete( 5 )
return
@ -2219,7 +2224,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
job_key.Finish()
wx.CallLater( 1000 * 5, job_key.Delete )
job_key.Delete( 5 )
def ClearClosedPages( self ):
@ -2250,17 +2255,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._DestroyPages( deletee_pages )
def DoFirstStart( self ):
message = 'Hi, this looks like the first time you have started the hydrus client.'
message += os.linesep * 2
message += 'Don\'t forget to check out the help if you haven\'t already.'
message += os.linesep * 2
message += 'You can right-click popup messages like this to dismiss them.'
HydrusData.ShowText( message )
def EventClose( self, event ):
if not event.CanVeto():
@ -2280,7 +2274,10 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
page = self._notebook.GetCurrentPage()
if page is not None: page.SetMediaFocus()
if page is not None:
page.SetMediaFocus()
def EventMenu( self, event ):
@ -2735,7 +2732,10 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
( menu, label, show ) = self._GenerateMenuInfo( name )
if HC.PLATFORM_OSX: menu.SetTitle( label ) # causes bugs in os x if this is not here
if HC.PLATFORM_OSX:
menu.SetTitle( label ) # causes bugs in os x if this is not here
( old_menu, old_label, old_show ) = self._menus[ name ]
@ -2776,10 +2776,13 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._menus[ name ] = ( menu, label, show )
wx.CallAfter( old_menu.Destroy )
old_menu.Destroy()
def RefreshStatusBar( self ): self._RefreshStatusBar()
def RefreshStatusBar( self ):
self._RefreshStatusBar()
def SaveLastSession( self ):

View File

@ -392,7 +392,7 @@ class Animation( wx.Window ):
self._current_frame_drawn = False
self._a_frame_has_been_drawn = False
wx.CallAfter( self._canvas_bmp.Destroy )
self._canvas_bmp.Destroy()
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
@ -718,7 +718,7 @@ class AnimationBar( wx.Window ):
if my_width > 0 and my_height > 0:
wx.CallAfter( self._canvas_bmp.Destroy )
self._canvas_bmp.Destroy()
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
@ -828,10 +828,6 @@ class Canvas( wx.Window ):
self._service_keys_to_services = {}
self._focus_holder = wx.Window( self )
self._focus_holder.Hide()
self._focus_holder.SetEventHandler( self )
self._current_media = None
self._current_display_media = None
self._media_container = None
@ -1009,6 +1005,11 @@ class Canvas( wx.Window ):
mime = media.GetMime()
if mime == HC.APPLICATION_HYDRUS_CLIENT_COLLECTION:
return CC.MEDIA_VIEWER_DO_NOT_SHOW
if self.PREVIEW_WINDOW:
return self._new_options.GetPreviewShowAction( mime )
@ -1385,7 +1386,7 @@ class Canvas( wx.Window ):
( my_width, my_height ) = self.GetClientSize()
wx.CallAfter( self._canvas_bmp.Destroy )
self._canvas_bmp.Destroy()
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
@ -1457,7 +1458,7 @@ class Canvas( wx.Window ):
media = None
elif self._GetShowAction( media ) == CC.MEDIA_VIEWER_DO_NOT_SHOW:
elif self._GetShowAction( media.GetDisplayMedia() ) == CC.MEDIA_VIEWER_DO_NOT_SHOW:
media = None
@ -1476,7 +1477,8 @@ class Canvas( wx.Window ):
self._media_container.Hide()
wx.CallAfter( self._media_container.Destroy )
# Another safe destroy for OS X's benefit
wx.CallLater( 500, self._media_container.Destroy )
self._media_container = None
@ -2939,9 +2941,15 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
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( 'zoom_switch' ), 'zoom fit' )
if media_width > my_width or media_height > my_height:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'zoom_switch' ), 'zoom fit' )
else:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'zoom_switch' ), 'zoom full' )
else: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'zoom_switch' ), 'zoom full' )
menu.AppendSeparator()
@ -3378,9 +3386,15 @@ class CanvasMediaListCustomFilter( CanvasMediaListNavigable ):
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( 'zoom_switch' ), 'zoom fit' )
if media_width > my_width or media_height > my_height:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'zoom_switch' ), 'zoom fit' )
else:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'zoom_switch' ), 'zoom full' )
else: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'zoom_switch' ), 'zoom full' )
menu.AppendSeparator()
@ -3940,7 +3954,7 @@ class StaticImage( wx.Window ):
wx_bitmap = wx.BitmapFromImage( image )
wx.CallAfter( image.Destroy )
image.Destroy()
else:
@ -3949,7 +3963,7 @@ class StaticImage( wx.Window ):
dc.DrawBitmap( wx_bitmap, 0, 0 )
wx.CallAfter( wx_bitmap.Destroy )
wx_bitmap.Destroy()
self._is_rendered = True
@ -4017,7 +4031,7 @@ class StaticImage( wx.Window ):
self._image_container = self._image_cache.GetImage( self._media, ( target_width, target_height ) )
wx.CallAfter( self._canvas_bmp.Destroy )
self._canvas_bmp.Destroy()
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )

View File

@ -2087,7 +2087,7 @@ class ListBook( wx.Panel ):
self._panel_sizer.Detach( page_to_delete )
wx.CallAfter( page_to_delete.Destroy )
page_to_delete.Destroy()
del self._keys_to_active_pages[ key_to_delete ]
@ -4827,6 +4827,8 @@ class PopupMessageManager( wx.Frame ):
def _CheckPending( self ):
size_and_position_needed = False
num_messages_displayed = self._message_vbox.GetItemCount()
if len( self._pending_job_keys ) > 0 and num_messages_displayed < self._max_messages_to_display:
@ -4839,6 +4841,10 @@ class PopupMessageManager( wx.Frame ):
self._message_vbox.AddF( window, CC.FLAGS_EXPAND_PERPENDICULAR )
size_and_position_needed = True
dismiss_shown_before = self._dismiss_all.IsShown()
num_messages_pending = len( self._pending_job_keys )
@ -4848,9 +4854,20 @@ class PopupMessageManager( wx.Frame ):
self._dismiss_all.Show()
else: self._dismiss_all.Hide()
else:
self._dismiss_all.Hide()
self._SizeAndPositionAndShow()
if self._dismiss_all.IsShown() != dismiss_shown_before:
size_and_position_needed = True
if size_and_position_needed:
self._SizeAndPositionAndShow()
def _SizeAndPositionAndShow( self ):
@ -4887,7 +4904,10 @@ class PopupMessageManager( wx.Frame ):
tlp.Raise()
else: self.Hide()
else:
self.Hide()
except:
@ -4919,7 +4939,10 @@ class PopupMessageManager( wx.Frame ):
self._CheckPending()
except: HydrusData.Print( traceback.format_exc() )
except:
HydrusData.Print( traceback.format_exc() )
def CleanBeforeDestroy( self ):
@ -4955,7 +4978,7 @@ class PopupMessageManager( wx.Frame ):
self._message_vbox.Detach( window )
wx.CallAfter( window.Destroy )
window.Destroy()
self._SizeAndPositionAndShow()
@ -4993,6 +5016,8 @@ class PopupMessageManager( wx.Frame ):
if HydrusGlobals.view_shutdown:
self._timer.Stop()
self.Destroy()
return
@ -5682,7 +5707,7 @@ class RegexButton( wx.Button ):
class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
def __init__( self, parent, height, columns, delete_key_callback = None, use_display_tuple_for_sort = False ):
def __init__( self, parent, height, columns, delete_key_callback = None, activation_callback = None, use_display_tuple_for_sort = False ):
num_columns = len( columns )
@ -5709,9 +5734,10 @@ class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
self.SetMinSize( ( -1, height ) )
self._delete_key_callback = delete_key_callback
self._activation_callback = activation_callback
self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown )
self.Bind( wx.EVT_LIST_ITEM_ACTIVATED, self.EventItemActivated )
def Append( self, display_tuple, client_data ):
@ -5733,13 +5759,31 @@ class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
self._next_data_index += 1
def EventItemActivated( self, event ):
if self._activation_callback is not None:
self._activation_callback()
else:
event.Skip()
def EventKeyDown( self, event ):
if event.KeyCode in CC.DELETE_KEYS:
if self._delete_key_callback is not None: self._delete_key_callback()
if self._delete_key_callback is not None:
self._delete_key_callback()
else:
event.Skip()
else: event.Skip()
def GetAllSelected( self ):

View File

@ -909,7 +909,7 @@ class DialogInputCustomFilterAction( Dialog ):
self._none_panel = ClientGUICommon.StaticBox( self, 'non-service actions' )
self._none_actions = wx.Choice( self._none_panel, choices = [ 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'delete', 'fullscreen_switch', 'frame_back', 'frame_next', 'next', 'first', 'last', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'previous', 'remove' ] )
self._none_actions = wx.Choice( self._none_panel, choices = [ 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'delete', 'fullscreen_switch', 'frame_back', 'frame_next', 'next', 'first', 'last', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'previous', 'remove', 'zoom_in', 'zoom_out', 'zoom_switch' ] )
self._ok_none = wx.Button( self._none_panel, label = 'ok' )
self._ok_none.Bind( wx.EVT_BUTTON, self.EventOKNone )
@ -1789,12 +1789,15 @@ class DialogInputLocalFiles( Dialog ):
file_paths = ClientFiles.GetAllPaths( raw_paths )
free_paths = HydrusPaths.FilterFreePaths( file_paths )
num_file_paths = len( file_paths )
num_good_files = 0
num_empty_files = 0
num_uninteresting_mime_files = 0
num_occupied_files = num_file_paths - len( free_paths )
for ( i, path ) in enumerate( file_paths ):
for ( i, path ) in enumerate( free_paths ):
if path.endswith( os.path.sep + 'Thumbs.db' ) or path.endswith( os.path.sep + 'thumbs.db' ):
@ -1850,7 +1853,7 @@ class DialogInputLocalFiles( Dialog ):
message = HydrusData.ConvertIntToPrettyString( num_good_files ) + ' files were parsed successfully'
if num_empty_files > 0 or num_uninteresting_mime_files > 0:
if num_empty_files > 0 or num_uninteresting_mime_files > 0 or num_occupied_files > 0:
if num_good_files == 0:
@ -1873,6 +1876,11 @@ class DialogInputLocalFiles( Dialog ):
bad_comments.append( HydrusData.ConvertIntToPrettyString( num_uninteresting_mime_files ) + ' had unsupported mimes' )
if num_occupied_files > 0:
bad_comments.append( HydrusData.ConvertIntToPrettyString( num_occupied_files ) + ' were probably already in use by another process' )
message += ' and '.join( bad_comments )
@ -2203,7 +2211,7 @@ class DialogInputShortcut( Dialog ):
self._shortcut = ClientGUICommon.Shortcut( self, modifier, key )
self._actions = wx.Choice( self, choices = [ 'archive', 'inbox', 'close_page', 'filter', 'fullscreen_switch', 'frame_back', 'frame_next', 'manage_ratings', 'manage_tags', 'new_page', 'refresh', 'set_media_focus', 'set_search_focus', 'show_hide_splitters', 'synchronised_wait_switch', 'next', 'first', 'last', 'undo', 'redo', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'previous', 'remove' ] )
self._actions = wx.Choice( self, choices = [ 'archive', 'inbox', 'close_page', 'filter', 'fullscreen_switch', 'frame_back', 'frame_next', 'manage_ratings', 'manage_tags', 'new_page', 'refresh', 'set_media_focus', 'set_search_focus', 'show_hide_splitters', 'synchronised_wait_switch', 'next', 'first', 'last', 'undo', 'redo', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'previous', 'remove', 'zoom_in', 'zoom_out', 'zoom_switch' ] )
self._ok = wx.Button( self, id= wx.ID_OK, label = 'Ok' )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
@ -3199,7 +3207,7 @@ class DialogPathsToTags( Dialog ):
self._quick_namespaces_panel = ClientGUICommon.StaticBox( self, 'quick namespaces' )
self._quick_namespaces_list = ClientGUICommon.SaneListCtrl( self._quick_namespaces_panel, 200, [ ( 'namespace', 80 ), ( 'regex', -1 ) ], delete_key_callback = self.DeleteQuickNamespaces )
self._quick_namespaces_list = ClientGUICommon.SaneListCtrl( self._quick_namespaces_panel, 200, [ ( 'namespace', 80 ), ( 'regex', -1 ) ], delete_key_callback = self.DeleteQuickNamespaces, activation_callback = self.EditQuickNamespaces )
self._add_quick_namespace_button = wx.Button( self._quick_namespaces_panel, label = 'add' )
self._add_quick_namespace_button.Bind( wx.EVT_BUTTON, self.EventAddQuickNamespace )
@ -3301,6 +3309,26 @@ class DialogPathsToTags( Dialog ):
self._refresh_callable()
def EditQuickNamespaces( self ):
for index in self._quick_namespaces_list.GetAllSelected():
( namespace, regex ) = self._quick_namespaces_list.GetClientData( index = index )
with DialogInputNamespaceRegex( self, namespace = namespace, regex = regex ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( namespace, regex ) = dlg.GetInfo()
self._quick_namespaces_list.UpdateRow( index, ( namespace, regex ), ( namespace, regex ) )
self._refresh_callable()
def EventAddRegex( self, event ):
regex = self._regex_box.GetValue()
@ -3345,26 +3373,14 @@ class DialogPathsToTags( Dialog ):
def EventDeleteQuickNamespace( self, event ): self.DeleteQuickNamespaces()
def EventDeleteQuickNamespace( self, event ):
self.DeleteQuickNamespaces()
def EventEditQuickNamespace( self, event ):
for index in self._quick_namespaces_list.GetAllSelected():
( namespace, regex ) = self._quick_namespaces_list.GetClientData( index = index )
with DialogInputNamespaceRegex( self, namespace = namespace, regex = regex ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( namespace, regex ) = dlg.GetInfo()
self._quick_namespaces_list.UpdateRow( index, ( namespace, regex ), ( namespace, regex ) )
self._refresh_callable()
self.EditQuickNamespaces()
def EventNumNamespaceChanged( self, event ):
@ -4630,7 +4646,7 @@ class DialogShortcuts( Dialog ):
for ( key, action ) in key_dict.items():
if action in ( 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'fullscreen_switch', 'frame_back', 'frame_next', 'next', 'first', 'last', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'previous', 'remove' ):
if action in ( 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'fullscreen_switch', 'frame_back', 'frame_next', 'next', 'first', 'last', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'previous', 'remove', 'zoom_in', 'zoom_out', 'zoom_switch' ):
service_key = None
@ -4772,7 +4788,7 @@ class DialogShortcuts( Dialog ):
self._original_shortcuts = shortcuts
self._shortcuts = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'modifier', 150 ), ( 'key', 150 ), ( 'service', -1 ), ( 'action', 250 ) ], delete_key_callback = self.RemoveShortcuts )
self._shortcuts = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'modifier', 150 ), ( 'key', 150 ), ( 'service', -1 ), ( 'action', 250 ) ], delete_key_callback = self.RemoveShortcuts, activation_callback = self.EditShortcuts )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
@ -4836,22 +4852,7 @@ class DialogShortcuts( Dialog ):
def _SortListCtrl( self ): self._shortcuts.SortListItems( 3 )
def EventAdd( self, event ):
with DialogInputCustomFilterAction( self ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( pretty_tuple, data_tuple ) = dlg.GetInfo()
self._shortcuts.Append( pretty_tuple, data_tuple )
self._SortListCtrl()
def EventEdit( self, event ):
def EditShortcuts( self ):
for index in self._shortcuts.GetAllSelected():
@ -4871,7 +4872,30 @@ class DialogShortcuts( Dialog ):
def EventRemove( self, event ): self.RemoveShortcuts()
def EventAdd( self, event ):
with DialogInputCustomFilterAction( self ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( pretty_tuple, data_tuple ) = dlg.GetInfo()
self._shortcuts.Append( pretty_tuple, data_tuple )
self._SortListCtrl()
def EventEdit( self, event ):
self.EditShortcuts()
def EventRemove( self, event ):
self.RemoveShortcuts()
def GetShortcuts( self ):

View File

@ -188,7 +188,7 @@ class DialogManageAccountTypes( ClientGUIDialogs.Dialog ):
self._account_types_panel = ClientGUICommon.StaticBox( self, 'account types' )
self._ctrl_account_types = ClientGUICommon.SaneListCtrl( self._account_types_panel, 350, [ ( 'title', 120 ), ( 'permissions', -1 ), ( 'max monthly bytes', 120 ), ( 'max monthly requests', 120 ) ], delete_key_callback = self.Delete )
self._ctrl_account_types = ClientGUICommon.SaneListCtrl( self._account_types_panel, 350, [ ( 'title', 120 ), ( 'permissions', -1 ), ( 'max monthly bytes', 120 ), ( 'max monthly requests', 120 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit )
self._add = wx.Button( self._account_types_panel, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
@ -290,40 +290,7 @@ class DialogManageAccountTypes( ClientGUIDialogs.Dialog ):
self._ctrl_account_types.RemoveAllSelected()
def EventAdd( self, event ):
with ClientGUIDialogs.DialogInputNewAccountType( self ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
account_type = dlg.GetAccountType()
title = account_type.GetTitle()
permissions = account_type.GetPermissions()
permissions_string = ', '.join( [ HC.permissions_string_lookup[ permission ] for permission in permissions ] )
max_num_bytes = account_type.GetMaxBytes()
max_num_requests = account_type.GetMaxRequests()
max_num_bytes_string = account_type.GetMaxBytesString()
max_num_requests_string = account_type.GetMaxRequestsString()
if title in self._titles_to_account_types: raise Exception( 'You already have an account type called ' + title + '; delete or edit that one first' )
self._titles_to_account_types[ title ] = account_type
self._edit_log.append( ( HC.ADD, account_type ) )
self._ctrl_account_types.Append( ( title, permissions_string, max_num_bytes_string, max_num_requests_string ), ( title, len( permissions ), max_num_bytes, max_num_requests ) )
def EventDelete( self, event ): self.Delete()
def EventEdit( self, event ):
def Edit( self ):
indices = self._ctrl_account_types.GetAllSelected()
@ -370,6 +337,47 @@ class DialogManageAccountTypes( ClientGUIDialogs.Dialog ):
def EventAdd( self, event ):
with ClientGUIDialogs.DialogInputNewAccountType( self ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
account_type = dlg.GetAccountType()
title = account_type.GetTitle()
permissions = account_type.GetPermissions()
permissions_string = ', '.join( [ HC.permissions_string_lookup[ permission ] for permission in permissions ] )
max_num_bytes = account_type.GetMaxBytes()
max_num_requests = account_type.GetMaxRequests()
max_num_bytes_string = account_type.GetMaxBytesString()
max_num_requests_string = account_type.GetMaxRequestsString()
if title in self._titles_to_account_types: raise Exception( 'You already have an account type called ' + title + '; delete or edit that one first' )
self._titles_to_account_types[ title ] = account_type
self._edit_log.append( ( HC.ADD, account_type ) )
self._ctrl_account_types.Append( ( title, permissions_string, max_num_bytes_string, max_num_requests_string ), ( title, len( permissions ), max_num_bytes, max_num_requests ) )
def EventDelete( self, event ):
self.Delete()
def EventEdit( self, event ):
self.Edit()
def EventOK( self, event ):
service = HydrusGlobals.client_controller.GetServicesManager().GetService( self._service_key )
@ -1391,7 +1399,7 @@ class DialogManageExportFolders( ClientGUIDialogs.Dialog ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage export folders' )
self._export_folders = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'path', -1 ), ( 'type', 120 ), ( 'query', 120 ), ( 'period', 120 ), ( 'phrase', 120 ) ], delete_key_callback = self.Delete, use_display_tuple_for_sort = True )
self._export_folders = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'path', -1 ), ( 'type', 120 ), ( 'query', 120 ), ( 'period', 120 ), ( 'phrase', 120 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit, use_display_tuple_for_sort = True )
export_folders = HydrusGlobals.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_EXPORT_FOLDER )
@ -1537,24 +1545,12 @@ class DialogManageExportFolders( ClientGUIDialogs.Dialog ):
return ( path, pretty_export_type, pretty_file_search_context, pretty_period, pretty_phrase )
def Delete( self ): self._export_folders.RemoveAllSelected()
def EventAdd( self, event ):
def Delete( self ):
with wx.DirDialog( self, 'Select a folder to add.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = HydrusData.ToUnicode( dlg.GetPath() )
self._AddFolder( path )
self._export_folders.RemoveAllSelected()
def EventDelete( self, event ): self.Delete()
def EventEdit( self, event ):
def Edit( self ):
indices = self._export_folders.GetAllSelected()
@ -1576,6 +1572,29 @@ class DialogManageExportFolders( ClientGUIDialogs.Dialog ):
def EventAdd( self, event ):
with wx.DirDialog( self, 'Select a folder to add.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = HydrusData.ToUnicode( dlg.GetPath() )
self._AddFolder( path )
def EventDelete( self, event ):
self.Delete()
def EventEdit( self, event ):
self.Edit()
def EventOK( self, event ):
client_data = self._export_folders.GetClientData()
@ -2482,7 +2501,7 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage import folders' )
self._import_folders = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'name', 120 ), ( 'path', -1 ), ( 'check period', 120 ) ], delete_key_callback = self.Delete )
self._import_folders = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'name', 120 ), ( 'path', -1 ), ( 'check period', 120 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit )
self._add_button = wx.Button( self, label = 'add' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
@ -2589,6 +2608,34 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
self._import_folders.RemoveAllSelected()
def Edit( self ):
indices = self._import_folders.GetAllSelected()
for index in indices:
( name, path, check_period ) = self._import_folders.GetClientData( index )
import_folder = self._names_to_import_folders[ name ]
with DialogManageImportFoldersEdit( self, import_folder ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
import_folder = dlg.GetInfo()
( name, path, check_period ) = import_folder.ToListBoxTuple()
pretty_check_period = self._GetPrettyVariables( check_period )
self._import_folders.UpdateRow( index, ( name, path, pretty_check_period ), ( name, path, check_period ) )
self._names_to_import_folders[ name ] = import_folder
def EventAdd( self, event ):
client_data = self._import_folders.GetClientData()
@ -2631,30 +2678,7 @@ class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
def EventEdit( self, event ):
indices = self._import_folders.GetAllSelected()
for index in indices:
( name, path, check_period ) = self._import_folders.GetClientData( index )
import_folder = self._names_to_import_folders[ name ]
with DialogManageImportFoldersEdit( self, import_folder ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
import_folder = dlg.GetInfo()
( name, path, check_period ) = import_folder.ToListBoxTuple()
pretty_check_period = self._GetPrettyVariables( check_period )
self._import_folders.UpdateRow( index, ( name, path, pretty_check_period ), ( name, path, check_period ) )
self._names_to_import_folders[ name ] = import_folder
self.Edit()
def EventOK( self, event ):
@ -3510,7 +3534,7 @@ class DialogManageRegexFavourites( ClientGUIDialogs.Dialog ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage regex favourites' )
self._regexes = ClientGUICommon.SaneListCtrl( self, 200, [ ( 'regex phrase', 120 ), ( 'description', -1 ) ], delete_key_callback = self.Delete )
self._regexes = ClientGUICommon.SaneListCtrl( self, 200, [ ( 'regex phrase', 120 ), ( 'description', -1 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit )
self._add_button = wx.Button( self, label = 'add' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
@ -3573,6 +3597,34 @@ class DialogManageRegexFavourites( ClientGUIDialogs.Dialog ):
self._regexes.RemoveAllSelected()
def Edit( self ):
indices = self._regexes.GetAllSelected()
for index in indices:
( regex_phrase, description ) = self._regexes.GetClientData( index )
with ClientGUIDialogs.DialogTextEntry( self, 'Update regex.', default = regex_phrase ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
regex_phrase = dlg.GetValue()
with ClientGUIDialogs.DialogTextEntry( self, 'Update description.', default = description ) as dlg_2:
if dlg_2.ShowModal() == wx.ID_OK:
description = dlg_2.GetValue()
self._regexes.UpdateRow( index, ( regex_phrase, description ), ( regex_phrase, description ) )
def EventAdd( self, event ):
with ClientGUIDialogs.DialogTextEntry( self, 'Enter regex.' ) as dlg:
@ -3601,30 +3653,7 @@ class DialogManageRegexFavourites( ClientGUIDialogs.Dialog ):
def EventEdit( self, event ):
indices = self._regexes.GetAllSelected()
for index in indices:
( regex_phrase, description ) = self._regexes.GetClientData( index )
with ClientGUIDialogs.DialogTextEntry( self, 'Update regex.', default = regex_phrase ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
regex_phrase = dlg.GetValue()
with ClientGUIDialogs.DialogTextEntry( self, 'Update description.', default = description ) as dlg_2:
if dlg_2.ShowModal() == wx.ID_OK:
description = dlg_2.GetValue()
self._regexes.UpdateRow( index, ( regex_phrase, description ), ( regex_phrase, description ) )
self.Edit()
def EventOK( self, event ):
@ -7036,7 +7065,7 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
self._mappings_list_ctrl = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'description', -1 ), ( 'internal ip', 100 ), ( 'internal port', 80 ), ( 'external ip', 100 ), ( 'external port', 80 ), ( 'protocol', 80 ), ( 'lease', 80 ) ], delete_key_callback = self.RemoveMappings )
self._mappings_list_ctrl = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'description', -1 ), ( 'internal ip', 100 ), ( 'internal port', 80 ), ( 'external ip', 100 ), ( 'external port', 80 ), ( 'protocol', 80 ), ( 'lease', 80 ) ], delete_key_callback = self.RemoveMappings, activation_callback = self.EditMappings )
self._add_custom = wx.Button( self, label = 'add custom mapping' )
self._add_custom.Bind( wx.EVT_BUTTON, self.EventAddCustomMapping )
@ -7091,6 +7120,34 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
self._mappings_list_ctrl.SortListItems( 1 )
def EditMappings( self ):
do_refresh = False
for index in self._mappings_list_ctrl.GetAllSelected():
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = self._mappings_list_ctrl.GetClientData( index )
with ClientGUIDialogs.DialogInputUPnPMapping( self, external_port, protocol, internal_port, description, duration ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( external_port, protocol, internal_port, description, duration ) = dlg.GetInfo()
HydrusNATPunch.RemoveUPnPMapping( external_port, protocol )
internal_client = HydrusNATPunch.GetLocalIP()
HydrusNATPunch.AddUPnPMapping( internal_client, internal_port, external_port, protocol, description, duration = duration )
do_refresh = True
if do_refresh: self._RefreshMappings()
def EventAddCustomMapping( self, event ):
do_refresh = False
@ -7130,30 +7187,7 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
def EventEditMapping( self, event ):
do_refresh = False
for index in self._mappings_list_ctrl.GetAllSelected():
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = self._mappings_list_ctrl.GetClientData( index )
with ClientGUIDialogs.DialogInputUPnPMapping( self, external_port, protocol, internal_port, description, duration ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( external_port, protocol, internal_port, description, duration ) = dlg.GetInfo()
HydrusNATPunch.RemoveUPnPMapping( external_port, protocol )
internal_client = HydrusNATPunch.GetLocalIP()
HydrusNATPunch.AddUPnPMapping( internal_client, internal_port, external_port, protocol, description, duration = duration )
do_refresh = True
if do_refresh: self._RefreshMappings()
self.EditMappings()
def EventOK( self, event ):
@ -7161,7 +7195,10 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
self.EndModal( wx.ID_OK )
def EventRemoveMapping( self, event ): self.RemoveMappings()
def EventRemoveMapping( self, event ):
self.RemoveMappings()
def RemoveMappings( self ):

View File

@ -280,7 +280,7 @@ def GenerateDumpMultipartFormDataCTAndBody( fields ):
dc.DrawBitmap( wx_bmp, 0, 0 )
wx.CallAfter( wx_bmp.Destroy )
wx_bmp.Destroy()
self._refresh_button.SetLabelText( 'get new captcha' )
self._refresh_button.Enable()

View File

@ -1550,7 +1550,7 @@ class MediaPanelThumbnails( MediaPanel ):
alpha_bmp = wx.BitmapFromImage( image, 32 )
wx.CallAfter( image.Destroy )
image.Destroy()
self._thumbnails_being_faded_in[ hash ] = ( bmp, alpha_bmp, thumbnail_index, thumbnail, 0 )
@ -1797,7 +1797,7 @@ class MediaPanelThumbnails( MediaPanel ):
for bmp in self._dirty_canvas_pages:
wx.CallAfter( bmp.Destroy )
bmp.Destroy()
self._dirty_canvas_pages = []

View File

@ -411,7 +411,7 @@ class ConversationPanel( wx.Panel ):
self._drafts_vbox.Detach( draft_panel )
wx.CallAfter( draft_panel.Destroy )
draft_panel.Destroy()
self._scrolling_messages_window.FitInside()

View File

@ -368,7 +368,7 @@ class ManageOptionsPanel( ManagePanel ):
wx.Panel.__init__( self, parent )
self._client_files = ClientGUICommon.SaneListCtrl( self, 200, [ ( 'path', -1 ), ( 'weight', 80 ) ] )
self._client_files = ClientGUICommon.SaneListCtrl( self, 200, [ ( 'path', -1 ), ( 'weight', 80 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
@ -457,6 +457,34 @@ class ManageOptionsPanel( ManagePanel ):
self.SetSizer( vbox )
def Delete( self ):
if len( self._client_files.GetAllSelected() ) < self._client_files.GetItemCount():
self._client_files.RemoveAllSelected()
def Edit( self ):
for i in self._client_files.GetAllSelected():
( location, weight ) = self._client_files.GetClientData( i )
with wx.NumberEntryDialog( self, 'Enter the weight of ' + location + '.', '', 'Enter Weight', value = int( weight ), min = 1, max = 256 ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
weight = dlg.GetValue()
weight = float( weight )
self._client_files.UpdateRow( i, ( location, HydrusData.ConvertIntToPrettyString( int( weight ) ) ), ( location, weight ) )
def EventAdd( self, event ):
with wx.DirDialog( self, 'Select the file location' ) as dlg:
@ -492,30 +520,12 @@ class ManageOptionsPanel( ManagePanel ):
def EventDelete( self, event ):
if len( self._client_files.GetAllSelected() ) < self._client_files.GetItemCount():
self._client_files.RemoveAllSelected()
self.Delete()
def EventEditWeight( self, event ):
for i in self._client_files.GetAllSelected():
( location, weight ) = self._client_files.GetClientData( i )
with wx.NumberEntryDialog( self, 'Enter the weight of ' + location + '.', '', 'Enter Weight', value = int( weight ), min = 1, max = 256 ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
weight = dlg.GetValue()
weight = float( weight )
self._client_files.UpdateRow( i, ( location, HydrusData.ConvertIntToPrettyString( int( weight ) ) ), ( location, weight ) )
self.Edit()
def UpdateOptions( self ):
@ -1426,7 +1436,7 @@ class ManageOptionsPanel( ManagePanel ):
frame_locations_panel = ClientGUICommon.StaticBox( self, 'frame locations' )
self._frame_locations = ClientGUICommon.SaneListCtrl( frame_locations_panel, 200, [ ( 'name', -1 ), ( 'remember size', 90 ), ( 'remember position', 90 ), ( 'last size', 90 ), ( 'last position', 90 ), ( 'default gravity', 90 ), ( 'default position', 90 ), ( 'maximised', 90 ), ( 'fullscreen', 90 ) ] )
self._frame_locations = ClientGUICommon.SaneListCtrl( frame_locations_panel, 200, [ ( 'name', -1 ), ( 'remember size', 90 ), ( 'remember position', 90 ), ( 'last size', 90 ), ( 'last position', 90 ), ( 'default gravity', 90 ), ( 'default position', 90 ), ( 'maximised', 90 ), ( 'fullscreen', 90 ) ], activation_callback = self.EditFrameLocations )
self._frame_locations_edit_button = wx.Button( frame_locations_panel, label = 'edit' )
self._frame_locations_edit_button.Bind( wx.EVT_BUTTON, self.EventEditFrameLocation )
@ -1534,7 +1544,7 @@ class ManageOptionsPanel( ManagePanel ):
return pretty_listctrl_list
def EventEditFrameLocation( self, event ):
def EditFrameLocations( self ):
for i in self._frame_locations.GetAllSelected():
@ -1559,6 +1569,11 @@ class ManageOptionsPanel( ManagePanel ):
def EventEditFrameLocation( self, event ):
self.EditFrameLocations()
def UpdateOptions( self ):
HC.options[ 'default_gui_session' ] = self._default_gui_session.GetStringSelection()
@ -1599,7 +1614,7 @@ class ManageOptionsPanel( ManagePanel ):
self._media_viewer_panel = ClientGUICommon.StaticBox( self, 'media viewer mime handling' )
self._media_viewer_options = ClientGUICommon.SaneListCtrl( self._media_viewer_panel, 300, [ ( 'mime', 150 ), ( 'media show action', 140 ), ( 'preview show action', 140 ), ( 'zoom in to fit', 80 ), ( 'half/double zoom', 80 ), ( '>100% quality', 120 ), ( '<100% quality', 120 ) ] )
self._media_viewer_options = ClientGUICommon.SaneListCtrl( self._media_viewer_panel, 300, [ ( 'mime', 150 ), ( 'media show action', 140 ), ( 'preview show action', 140 ), ( 'zoom in to fit', 80 ), ( 'half/double zoom', 80 ), ( '>100% quality', 120 ), ( '<100% quality', 120 ) ], activation_callback = self.EditMediaViewerOptions, use_display_tuple_for_sort = True )
self._media_viewer_edit_button = wx.Button( self._media_viewer_panel, label = 'edit' )
self._media_viewer_edit_button.Bind( wx.EVT_BUTTON, self.EventEditMediaViewerOptions )
@ -1647,7 +1662,7 @@ class ManageOptionsPanel( ManagePanel ):
self._media_viewer_panel.AddF( self._media_viewer_options, CC.FLAGS_EXPAND_BOTH_WAYS )
self._media_viewer_panel.AddF( self._media_viewer_edit_button, CC.FLAGS_LONE_BUTTON )
vbox.AddF( self._media_viewer_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._media_viewer_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
@ -1682,7 +1697,7 @@ class ManageOptionsPanel( ManagePanel ):
return ( pretty_mime, pretty_media_show_action, pretty_preview_show_action, pretty_zoom_in_to_fit, pretty_exact_zooms_only, pretty_zoom_in_quality, pretty_zoom_out_quality )
def EventEditMediaViewerOptions( self, event ):
def EditMediaViewerOptions( self ):
for i in self._media_viewer_options.GetAllSelected():
@ -1707,6 +1722,11 @@ class ManageOptionsPanel( ManagePanel ):
def EventEditMediaViewerOptions( self, event ):
self.EditMediaViewerOptions()
def EventZoomsChanged( self, event ):
try:
@ -1793,7 +1813,7 @@ class ManageOptionsPanel( ManagePanel ):
wx.Panel.__init__( self, parent )
self._shortcuts = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'modifier', 120 ), ( 'key', 120 ), ( 'action', -1 ) ], delete_key_callback = self.DeleteShortcuts )
self._shortcuts = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'modifier', 120 ), ( 'key', 120 ), ( 'action', -1 ) ], delete_key_callback = self.DeleteShortcuts, activation_callback = self.EditShortcuts )
self._shortcuts_add = wx.Button( self, label = 'add' )
self._shortcuts_add.Bind( wx.EVT_BUTTON, self.EventAdd )
@ -1845,6 +1865,32 @@ class ManageOptionsPanel( ManagePanel ):
self._shortcuts.RemoveAllSelected()
def EditShortcuts( self ):
indices = self._shortcuts.GetAllSelected()
for index in indices:
( modifier, key, action ) = self._shortcuts.GetClientData( index )
with ClientGUIDialogs.DialogInputShortcut( self, modifier, key, action ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( modifier, key, action ) = dlg.GetInfo()
( pretty_modifier, pretty_key ) = ClientData.ConvertShortcutToPrettyShortcut( modifier, key )
pretty_action = action
self._shortcuts.UpdateRow( index, ( pretty_modifier, pretty_key, pretty_action ), ( modifier, key, action ) )
self._SortListCtrl()
def EventAdd( self, event ):
with ClientGUIDialogs.DialogInputShortcut( self ) as dlg:
@ -1871,28 +1917,7 @@ class ManageOptionsPanel( ManagePanel ):
def EventEdit( self, event ):
indices = self._shortcuts.GetAllSelected()
for index in indices:
( modifier, key, action ) = self._shortcuts.GetClientData( index )
with ClientGUIDialogs.DialogInputShortcut( self, modifier, key, action ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( modifier, key, action ) = dlg.GetInfo()
( pretty_modifier, pretty_key ) = ClientData.ConvertShortcutToPrettyShortcut( modifier, key )
pretty_action = action
self._shortcuts.UpdateRow( index, ( pretty_modifier, pretty_key, pretty_action ), ( modifier, key, action ) )
self._SortListCtrl()
self.EditShortcuts()
def UpdateOptions( self ):
@ -3490,7 +3515,7 @@ class ReviewServices( ReviewPanel ):
self._booru_shares_panel = ClientGUICommon.StaticBox( self, 'shares' )
self._booru_shares = ClientGUICommon.SaneListCtrl( self._booru_shares_panel, -1, [ ( 'title', 110 ), ( 'text', -1 ), ( 'expires', 170 ), ( 'num files', 70 ) ], delete_key_callback = self.DeleteBoorus )
self._booru_shares = ClientGUICommon.SaneListCtrl( self._booru_shares_panel, -1, [ ( 'title', 110 ), ( 'text', -1 ), ( 'expires', 170 ), ( 'num files', 70 ) ], delete_key_callback = self.DeleteBoorus, activation_callback = self.EditBoorus )
self._booru_open_search = wx.Button( self._booru_shares_panel, label = 'open share in new page' )
self._booru_open_search.Bind( wx.EVT_BUTTON, self.EventBooruOpenSearch )
@ -3512,7 +3537,7 @@ class ReviewServices( ReviewPanel ):
self._ipfs_shares_panel = ClientGUICommon.StaticBox( self, 'pinned directories' )
self._ipfs_shares = ClientGUICommon.SaneListCtrl( self._ipfs_shares_panel, -1, [ ( 'multihash', 110 ), ( 'num files', 70 ), ( 'total size', 70 ), ( 'note', 200 ) ], delete_key_callback = self.UnpinIPFSDirectories )
self._ipfs_shares = ClientGUICommon.SaneListCtrl( self._ipfs_shares_panel, -1, [ ( 'multihash', 110 ), ( 'num files', 70 ), ( 'total size', 70 ), ( 'note', 200 ) ], delete_key_callback = self.UnpinIPFSDirectories, activation_callback = self.EditIPFSNotes )
self._ipfs_open_search = wx.Button( self._ipfs_shares_panel, label = 'open share in new page' )
self._ipfs_open_search.Bind( wx.EVT_BUTTON, self.EventIPFSOpenSearch )
@ -3946,13 +3971,8 @@ class ReviewServices( ReviewPanel ):
self._booru_shares.RemoveAllSelected()
def EventBooruDelete( self, event ):
self.DeleteBoorus()
def EditBoorus( self ):
def EventBooruEdit( self, event ):
writes = []
for ( name, text, timeout, ( num_hashes, hashes, share_key ) ) in self._booru_shares.GetSelectedClientData():
@ -3981,6 +4001,40 @@ class ReviewServices( ReviewPanel ):
def EditIPFSNotes( self ):
for ( multihash, num_files, total_size, note ) in self._ipfs_shares.GetSelectedClientData():
with ClientGUIDialogs.DialogTextEntry( self, 'Set a note for ' + multihash + '.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
hashes = self._controller.Read( 'service_directory', self._service_key, multihash )
note = dlg.GetValue()
content_update_row = ( hashes, multihash, note )
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_DIRECTORIES, HC.CONTENT_UPDATE_ADD, content_update_row ) ]
HydrusGlobals.client_controller.WriteSynchronous( 'content_updates', { self._service_key : content_updates } )
self._DisplayService()
def EventBooruDelete( self, event ):
self.DeleteBoorus()
def EventBooruEdit( self, event ):
self.EditBoorus()
def EventBooruOpenSearch( self, event ):
for ( name, text, timeout, ( num_hashes, hashes, share_key ) ) in self._booru_shares.GetSelectedClientData():
@ -4181,26 +4235,7 @@ class ReviewServices( ReviewPanel ):
def EventIPFSSetNote( self, event ):
for ( multihash, num_files, total_size, note ) in self._ipfs_shares.GetSelectedClientData():
with ClientGUIDialogs.DialogTextEntry( self, 'Set a note for ' + multihash + '.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
hashes = self._controller.Read( 'service_directory', self._service_key, multihash )
note = dlg.GetValue()
content_update_row = ( hashes, multihash, note )
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_DIRECTORIES, HC.CONTENT_UPDATE_ADD, content_update_row ) ]
HydrusGlobals.client_controller.WriteSynchronous( 'content_updates', { self._service_key : content_updates } )
self._DisplayService()
self.EditIPFSNotes()
def EventIPFSUnpin( self, event ):

View File

@ -470,7 +470,7 @@ class ShowKeys( Frame ):
elif key_type == 'access': title = 'Access Keys'
# give it no parent, so this doesn't close when the dialog is closed!
Frame.__init__( self, None, HydrusGlobals.client_controller.PrepStringForDisplay( title ) )
Frame.__init__( self, None, HydrusGlobals.client_controller.PrepStringForDisplay( title ), float_on_parent = False )
self._key_type = key_type
self._keys = keys

View File

@ -61,42 +61,23 @@ def GeneratePerceptualHash( path ):
if depth == 4:
# create a white greyscale canvas
white = numpy.ones( ( x, y ) ) * 255
# create weight and transform numpy_image to greyscale
numpy_alpha = numpy_image[ :, :, 3 ]
numpy_alpha_float = numpy_alpha / 255.0
numpy_image_bgr = numpy_image[ :, :, :3 ]
numpy_image_gray = cv2.cvtColor( numpy_image_bgr, cv2.COLOR_BGR2GRAY )
numpy_image_gray_bare = cv2.cvtColor( numpy_image_bgr, cv2.COLOR_BGR2GRAY )
numpy_image_result = numpy.empty( ( y, x ), numpy.float32 )
# create a white greyscale canvas
# paste greyscale onto the white
white = numpy.ones( ( y, x ) ) * 255.0
# can't think of a better way to do this!
# cv2.addWeighted only takes a scalar for weight!
for i in range( y ):
for j in range( x ):
opacity = float( numpy_alpha[ i, j ] ) / 255.0
grey_part = numpy_image_gray[ i, j ] * opacity
white_part = 255 * ( 1 - opacity )
pixel = grey_part + white_part
numpy_image_result[ i, j ] = pixel
# paste the grayscale image onto the white canvas using: pixel * alpha + white * ( 1 - alpha )
numpy_image_gray = numpy_image_result
# use 255 for white weight, alpha for image weight
numpy_image_gray = numpy.uint8( ( numpy_image_gray_bare * numpy_alpha_float ) + ( white * ( numpy.ones( ( y, x ) ) - numpy_alpha_float ) ) )
else:

View File

@ -212,7 +212,9 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
gallery.GetFile( temp_path, url, report_hooks = [ self._file_download_hook ] )
( status, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', temp_path, import_file_options = self._import_file_options, url = url )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
( status, hash ) = client_files_manager.ImportFile( temp_path, import_file_options = self._import_file_options, url = url )
finally:
@ -488,6 +490,7 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
with self._lock:
self._current_query = None
self._gallery_paused = False
@ -681,7 +684,20 @@ class HDDImport( HydrusSerialisable.SerialisableBase ):
try:
( status, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', path, import_file_options = self._import_file_options )
( os_file_handle, temp_path ) = HydrusPaths.GetTempPath()
try:
HydrusPaths.MirrorFile( path, temp_path )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
( status, hash ) = client_files_manager.ImportFile( temp_path, import_file_options = self._import_file_options )
finally:
HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
self._paths_cache.UpdateSeedStatus( path, status )
@ -1044,6 +1060,8 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
all_paths = ClientFiles.GetAllPaths( raw_paths )
all_paths = HydrusPaths.FilterFreePaths( all_paths )
for path in all_paths:
if path.endswith( '.txt' ):
@ -1074,7 +1092,20 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
if mime in self._mimes:
( status, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', path, import_file_options = self._import_file_options )
( os_file_handle, temp_path ) = HydrusPaths.GetTempPath()
try:
HydrusPaths.MirrorFile( path, temp_path )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
( status, hash ) = client_files_manager.ImportFile( temp_path, import_file_options = self._import_file_options )
finally:
HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
self._path_cache.UpdateSeedStatus( path, status )
@ -1315,7 +1346,9 @@ class PageOfImagesImport( HydrusSerialisable.SerialisableBase ):
HydrusGlobals.client_controller.DoHTTP( HC.GET, file_url, report_hooks = report_hooks, temp_path = temp_path )
( status, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', temp_path, import_file_options = self._import_file_options, url = file_url )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
( status, hash ) = client_files_manager.ImportFile( temp_path, import_file_options = self._import_file_options, url = file_url )
finally:
@ -2152,7 +2185,9 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
job_key.SetVariable( 'popup_text_1', x_out_of_y + 'importing file' )
( status, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', temp_path, import_file_options = self._import_file_options, url = url )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
( status, hash ) = client_files_manager.ImportFile( temp_path, import_file_options = self._import_file_options, url = url )
if status == CC.STATUS_SUCCESSFUL:
@ -2623,7 +2658,9 @@ class ThreadWatcherImport( HydrusSerialisable.SerialisableBase ):
HydrusGlobals.client_controller.DoHTTP( HC.GET, file_url, report_hooks = report_hooks, temp_path = temp_path )
( status, hash ) = HydrusGlobals.client_controller.WriteSynchronous( 'import_file', temp_path, import_file_options = self._import_file_options, url = file_url )
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
( status, hash ) = client_files_manager.ImportFile( temp_path, import_file_options = self._import_file_options, url = file_url )
finally:

View File

@ -572,7 +572,7 @@ class HydrusBitmap( object ):
image = wx.ImageFromBitmap( bitmap )
wx.CallAfter( bitmap.Destroy )
bitmap.Destroy()
return image

View File

@ -7,6 +7,7 @@ import HydrusData
import HydrusGlobals
import HydrusThreading
import os
import wx
class JobKey( object ):
@ -21,6 +22,7 @@ class JobKey( object ):
self._stop_time = stop_time
self._deleted = threading.Event()
self._deletion_time = None
self._begun = threading.Event()
self._done = threading.Event()
self._cancelled = threading.Event()
@ -75,11 +77,26 @@ class JobKey( object ):
if not self._deleted.is_set():
if self._deletion_time is not None:
if HydrusData.TimeHasPassed( self._deletion_time ):
self.Finish()
self._deleted.set()
def Begin( self ): self._begun.set()
def CanBegin( self ):
self._CheckCancelTests()
if self.IsCancelled():
return False
@ -100,11 +117,16 @@ class JobKey( object ):
self.Finish()
def Delete( self ):
def Delete( self, seconds = None ):
self.Finish()
self._deleted.set()
if seconds is None:
self._deletion_time = HydrusData.GetNow()
else:
self._deletion_time = HydrusData.GetNow() + seconds
def DeleteVariable( self, name ):
@ -133,11 +155,15 @@ class JobKey( object ):
def IsBegun( self ):
self._CheckCancelTests()
return self._begun.is_set()
def IsCancellable( self ):
self._CheckCancelTests()
return self._cancellable and not self.IsDone()
@ -157,14 +183,31 @@ class JobKey( object ):
def IsDone( self ):
self._CheckCancelTests()
return HydrusThreading.IsThreadShuttingDown() or self._done.is_set()
def IsPausable( self ): return self._pausable and not self.IsDone()
def IsPausable( self ):
self._CheckCancelTests()
return self._pausable and not self.IsDone()
def IsPaused( self ): return self._paused.is_set() and not self.IsDone()
def IsPaused( self ):
self._CheckCancelTests()
return self._paused.is_set() and not self.IsDone()
def IsWorking( self ): return self.IsBegun() and not self.IsDone()
def IsWorking( self ):
self._CheckCancelTests()
return self.IsBegun() and not self.IsDone()
def PausePlay( self ):

View File

@ -48,7 +48,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 219
SOFTWARE_VERSION = 220
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -114,8 +114,6 @@ class HydrusController( object ):
result = self._db.Read( action, HC.HIGH_PRIORITY, *args, **kwargs )
time.sleep( 0.00001 )
return result
@ -138,8 +136,6 @@ class HydrusController( object ):
result = self._db.Write( action, priority, synchronous, *args, **kwargs )
time.sleep( 0.00001 )
return result

View File

@ -180,6 +180,26 @@ def DeletePath( path ):
def FilterFreePaths( paths ):
free_paths = []
for path in paths:
try:
os.rename( path, path ) # rename a path to itself
free_paths.append( path )
except OSError as e: # 'already in use by another process'
HydrusData.Print( path + ' ' + str( e ) )
return free_paths
def GetDevice( path ):
path = path.lower()

View File

@ -174,11 +174,19 @@ def Hydrusffmpeg_parse_infos(filename, print_infos=False):
if 'start:' in line:
m = re.search( '(start\\: )' + '-?[0-9]\\.[0-9]*', line )
m = re.search( '(start\\: )' + '-?[0-9]+\\.[0-9]*', line )
start_offset = float( line[ m.start() + 7 : m.end() ] )
else: start_offset = 0
if abs( start_offset ) > 1.0: # once had a file with start offset of 957499 seconds jej
start_offset = 0
else:
start_offset = 0
match = re.search("[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9]", line)
hms = map(float, line[match.start()+1:match.end()].split(':'))

View File

@ -2301,65 +2301,6 @@ class DB( HydrusDB.HydrusDB ):
HydrusData.Print( 'The server is updating to version ' + str( version + 1 ) )
if version == 161:
for filename in os.listdir( HC.SERVER_UPDATES_DIR ):
path = os.path.join( HC.SERVER_UPDATES_DIR, filename )
with open( path, 'rb' ) as f:
compressed_inefficient_string = f.read()
try:
inefficient_string = lz4.loads( compressed_inefficient_string )
( dump_type, dump_version, dump ) = json.loads( inefficient_string )
if not isinstance( dump, ( unicode, str ) ):
continue
serialisable_info = json.loads( dump )
except:
continue
better_string = json.dumps( ( dump_type, dump_version, serialisable_info ) )
compressed_better_string = lz4.dumps( better_string )
with open( path, 'wb' ) as f:
f.write( compressed_better_string )
if version == 169:
bad_tag_ids = set()
for ( tag_id, tag ) in self._c.execute( 'SELECT tag_id, tag FROM tags;' ):
try:
HydrusTags.CheckTagNotEmpty( tag )
except HydrusExceptions.SizeException:
bad_tag_ids.add( tag_id )
self._c.executemany( 'DELETE FROM mappings WHERE tag_id = ?;', ( ( tag_id, ) for tag_id in bad_tag_ids ) )
if version == 179:
HydrusData.Print( 'moving updates about' )