Version 375
This commit is contained in:
parent
4d4f39984e
commit
d18410121e
|
@ -8,6 +8,46 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 375</h3></li>
|
||||
<ul>
|
||||
<li>qt:</li>
|
||||
<li>disabled the failed legacy high dpi scaling mode experiment (which was scaling up thumbnails and media in an ugly way) and returned to font-size-based natural ui scaling as set by the OS. a couple of non-font things like bitmap buttons and various layout margins are too small on >100% UI scale, and the splash screen is borked again, but it looks clear again. I'll keep working on this</li>
|
||||
<li>fixed the custom taglist at >100% UI scale, which was spacing its tags at the wrong text height. this should survive changing ui scale while the program is open and environments with multiple monitors at different ui scale</li>
|
||||
<li>re-fixed a critical old media-viewer-close-on-video memory leak from wx code to qt code. this was also a cause for some child ffmpeg processes not being terminated</li>
|
||||
<li>fixed the media viewer not redrawing correctly when the media size completely exceeds the canvas window size</li>
|
||||
<li>fixed the loading of the shortcut edit panel when the shortcut set a tag</li>
|
||||
<li>fixed some url class edit path component ui</li>
|
||||
<li>fixed and cleaned up some 'safe window size/position' calculations that were missing out the total frame geometry, meaning some dialogs were not moving up and left enough to show entirely on screen, and dialogs with parent-dimension gravity were not calculating initial size accurately</li>
|
||||
<li>fixed focusing on the already-open manage tags text input when you hit 'manage tags' on a canvas with a manage tags dialog already open</li>
|
||||
<li>fixed the html formula rule edit ui actually rendering html tag labels, lmao</li>
|
||||
<li>updated boot-password entry to use the normal hydrus text entry dialog, and fixed a hydrus password cancel not setting a 'clean' exit for the next boot</li>
|
||||
<li>fixed page layout splitter sash positions not resetting nicely from the menu command</li>
|
||||
<li>fixed keyboard delete in the manage urls dialog</li>
|
||||
<li>popup message titles are now in bold</li>
|
||||
<li>popup message titles should now multiline correctly and fill available width</li>
|
||||
<li>the popup messages manager should now set its min/fixed width more sensibly</li>
|
||||
<li>subscription popups now will be wider if space is available</li>
|
||||
<li>wrote a new class to manage better asynchronous updates for future Qt ui presentation</li>
|
||||
<li>the file, pages, and pending menubar menus, which all require a db hit to generate, now operate on this new update class. all three should update faster when able and more politely and smoothly wait when the db is busy</li>
|
||||
<li>reduced some accidental blocking in an old ui-update routine that kicked in when it was running hard</li>
|
||||
<li>if the media_viewer frame type is set not to remember its 'last size', it will now instantiate with a small min size</li>
|
||||
<li>when pasting new queries into a sub, if there are more than 5 or 50 that are already in or new, they will be rendered in a more compact way in order to stop the notification dialog growing too tall</li>
|
||||
<li>improved stability of page update, splash screen update, and perhaps pubsub update</li>
|
||||
<li>.</li>
|
||||
<li>new file maintenance jobs:</li>
|
||||
<li>added a new 'check for missing files' file maintenance job, where if the file is missing and has urls, those urls will be queued up in a new url downloader for redownload. the file record is not removed, preserving archive/inbox and import time</li>
|
||||
<li>added a new 'check for invalid files' file maintenance job that does the same deal as above with an additional expensive byte-for-byte content check if the file is not missing</li>
|
||||
<li>added a new 'check for invalid files' file maintenance job that only cares about invalidity--if the file is present and invalid, it is moved out but the file record is not removed</li>
|
||||
<li>.</li>
|
||||
<li>the rest:</li>
|
||||
<li>network jobs that receive low-bandwidth error codes from the server now use a separate wait routine (previously, they piggybacked on the connection fail retry system). they have a separate cog-menu action to override these waits</li>
|
||||
<li>the time delay multiple for connection errors and serverside bandwidth problems are now editable under options->connection. old default was 10 seconds base, now 15 and 60 seconds respectively</li>
|
||||
<li>updated the danbooru login script</li>
|
||||
<li>improved the precision of the thumbnail size estimate in database migration</li>
|
||||
<li>the alphabetisation of a url class's GET paramaters on normalise is now optional. it is a new checkbox on the url class edit panel</li>
|
||||
<li>when a default object fails to load from a png path, a simple error is now written to the log</li>
|
||||
<li>misc cleanup</li>
|
||||
</ul>
|
||||
<li><h3>version 374</h3></li>
|
||||
<ul>
|
||||
<li>qt environment/build:</li>
|
||||
|
|
|
@ -1808,6 +1808,8 @@ class ThumbnailCache( object ):
|
|||
self._waterfall_queue_quick = set()
|
||||
self._waterfall_queue = []
|
||||
|
||||
self._waterfall_queue_empty_event = threading.Event()
|
||||
|
||||
self._delayed_regeneration_queue_quick = set()
|
||||
self._delayed_regeneration_queue = []
|
||||
|
||||
|
@ -2101,6 +2103,15 @@ class ThumbnailCache( object ):
|
|||
# we pop off the end, so reverse
|
||||
self._waterfall_queue.sort( key = sort_waterfall, reverse = True )
|
||||
|
||||
if len( self._waterfall_queue ) == 0:
|
||||
|
||||
self._waterfall_queue_empty_event.set()
|
||||
|
||||
else:
|
||||
|
||||
self._waterfall_queue_empty_event.clear()
|
||||
|
||||
|
||||
def sort_regen( item ):
|
||||
|
||||
media_result = item
|
||||
|
@ -2189,11 +2200,21 @@ class ThumbnailCache( object ):
|
|||
|
||||
|
||||
|
||||
def DoingWork( self ):
|
||||
def WaitUntilFree( self ):
|
||||
|
||||
with self._lock:
|
||||
while True:
|
||||
|
||||
return len( self._waterfall_queue ) > 0
|
||||
if HG.view_shutdown:
|
||||
|
||||
raise HydrusExceptions.ShutdownException( 'Application shutting down!' )
|
||||
|
||||
|
||||
queue_is_empty = self._waterfall_queue_empty_event.wait( 1 )
|
||||
|
||||
if queue_is_empty:
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2324,6 +2345,11 @@ class ThumbnailCache( object ):
|
|||
|
||||
result = self._waterfall_queue.pop()
|
||||
|
||||
if len( self._waterfall_queue ) == 0:
|
||||
|
||||
self._waterfall_queue_empty_event.set()
|
||||
|
||||
|
||||
self._waterfall_queue_quick.discard( result )
|
||||
|
||||
|
||||
|
|
|
@ -215,7 +215,14 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
if win is not None and not QP.isValid( win ):
|
||||
|
||||
raise HydrusExceptions.QtDeadWindowException('Parent Window was destroyed before Qt command was called!')
|
||||
if HG.view_shutdown:
|
||||
|
||||
raise HydrusExceptions.ShutdownException( 'Application is shutting down!' )
|
||||
|
||||
else:
|
||||
|
||||
raise HydrusExceptions.QtDeadWindowException('Parent Window was destroyed before Qt command was called!')
|
||||
|
||||
|
||||
|
||||
result = func( *args, **kwargs )
|
||||
|
@ -854,7 +861,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
while True:
|
||||
|
||||
with QP.PasswordEntryDialog( self._splash, 'Enter your password:', 'Enter password' ) as dlg:
|
||||
with ClientGUIDialogs.DialogTextEntry( self._splash, 'Enter your password.', allow_blank = True, password_entry = True ) as dlg:
|
||||
|
||||
if dlg.exec() == QW.QDialog.Accepted:
|
||||
|
||||
|
@ -1208,11 +1215,6 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
QP.MonkeyPatchMissingMethods()
|
||||
|
||||
if hasattr( QC.Qt, 'AA_EnableHighDpiScaling' ):
|
||||
|
||||
QW.QApplication.setAttribute( QC.Qt.AA_EnableHighDpiScaling, True )
|
||||
|
||||
|
||||
self.app = App( sys.argv )
|
||||
|
||||
HydrusData.Print( 'booting controller\u2026' )
|
||||
|
@ -1545,6 +1547,8 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
HydrusData.Print( e )
|
||||
|
||||
HydrusData.CleanRunningFile( self.db_dir, 'client' )
|
||||
|
||||
QP.CallAfter( QW.QApplication.exit, 0 )
|
||||
|
||||
except Exception as e:
|
||||
|
@ -1682,21 +1686,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
def WaitUntilThumbnailsFree( self ):
|
||||
|
||||
while True:
|
||||
|
||||
if HG.view_shutdown:
|
||||
|
||||
raise HydrusExceptions.ShutdownException( 'Application shutting down!' )
|
||||
|
||||
elif not self._caches[ 'thumbnail' ].DoingWork():
|
||||
|
||||
return
|
||||
|
||||
else:
|
||||
|
||||
time.sleep( 0.00001 )
|
||||
|
||||
|
||||
self._caches[ 'thumbnail' ].WaitUntilFree()
|
||||
|
||||
|
||||
def Write( self, action, *args, **kwargs ):
|
||||
|
|
|
@ -13163,6 +13163,32 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
if version == 374:
|
||||
|
||||
try:
|
||||
|
||||
login_manager = self._GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_LOGIN_MANAGER )
|
||||
|
||||
login_manager.Initialise()
|
||||
|
||||
#
|
||||
|
||||
login_manager.OverwriteDefaultLoginScripts( [ 'danbooru login' ] )
|
||||
|
||||
#
|
||||
|
||||
self._SetJSONDump( login_manager )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
HydrusData.PrintException( e )
|
||||
|
||||
message = 'Trying to update some login scripts failed! Please let hydrus dev know!'
|
||||
|
||||
self.pub_initial_message( message )
|
||||
|
||||
|
||||
|
||||
self._controller.pub( 'splash_set_title_text', 'updated db to v' + str( version + 1 ) )
|
||||
|
||||
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
|
||||
|
|
|
@ -102,8 +102,6 @@ def DAEMONMaintainTrash( controller ):
|
|||
|
||||
service_keys_to_content_updates = { CC.TRASH_SERVICE_KEY : [ content_update ] }
|
||||
|
||||
controller.WaitUntilModelFree()
|
||||
|
||||
controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
service_info = controller.Read( 'service_info', CC.TRASH_SERVICE_KEY )
|
||||
|
@ -129,8 +127,6 @@ def DAEMONMaintainTrash( controller ):
|
|||
|
||||
service_keys_to_content_updates = { CC.TRASH_SERVICE_KEY : [ content_update ] }
|
||||
|
||||
controller.WaitUntilModelFree()
|
||||
|
||||
controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
hashes = controller.Read( 'trash_hashes', limit = 10, minimum_age = max_age )
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from . import ClientConstants as CC
|
||||
from . import ClientData
|
||||
from . import HydrusConstants as HC
|
||||
from . import HydrusData
|
||||
from . import HydrusGlobals as HG
|
||||
from . import HydrusNetworking
|
||||
from . import HydrusSerialisable
|
||||
|
@ -469,9 +470,9 @@ def GetDefaultObjectsFromPNGs( dir_path, allowed_object_types ):
|
|||
|
||||
|
||||
|
||||
except:
|
||||
except Exception as e:
|
||||
|
||||
pass
|
||||
HydrusData.Print( 'Object at location "{}" failed to load: {}'.format( path, e ) )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -463,8 +463,6 @@ class QuickDownloadManager( object ):
|
|||
|
||||
file_repository.Request( HC.GET, 'file', { 'hash' : hash }, temp_path = temp_path )
|
||||
|
||||
self._controller.WaitUntilModelFree()
|
||||
|
||||
exclude_deleted = False # this is the important part here
|
||||
do_not_check_known_urls_before_importing = False
|
||||
do_not_check_hashes_before_importing = False
|
||||
|
|
|
@ -31,6 +31,9 @@ REGENERATE_FILE_DATA_JOB_FIX_PERMISSIONS = 7
|
|||
REGENERATE_FILE_DATA_JOB_CHECK_SIMILAR_FILES_MEMBERSHIP = 8
|
||||
REGENERATE_FILE_DATA_JOB_SIMILAR_FILES_METADATA = 9
|
||||
REGENERATE_FILE_DATA_JOB_FILE_MODIFIED_TIMESTAMP = 10
|
||||
REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_URL = 11
|
||||
REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL = 12
|
||||
REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_SILENT_DELETE = 13
|
||||
|
||||
regen_file_enum_to_str_lookup = {}
|
||||
|
||||
|
@ -39,8 +42,11 @@ regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_FORCE_THUMBNAIL ] = 'reg
|
|||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_REFIT_THUMBNAIL ] = 'regenerate thumbnail if incorrect size'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_OTHER_HASHES ] = 'regenerate non-standard hashes'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_DELETE_NEIGHBOUR_DUPES ] = 'delete duplicate neighbours with incorrect file extension'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE ] = 'check if file is present in file system'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA ] = 'check full file data integrity'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE ] = 'if file is missing, remove record'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_URL ] = 'if file is missing and has url, try to redownload'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA ] = 'if file is missing/incorrect, move file out and remove record'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL ] = 'if file is missing/incorrect and has url, move file out and try to redownload'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_SILENT_DELETE ] = 'if file is incorrect, move file out'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_FIX_PERMISSIONS ] = 'fix file read/write permissions'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_CHECK_SIMILAR_FILES_MEMBERSHIP ] = 'check for membership in the similar files search system'
|
||||
regen_file_enum_to_str_lookup[ REGENERATE_FILE_DATA_JOB_SIMILAR_FILES_METADATA ] = 'regenerate similar files metadata'
|
||||
|
@ -53,8 +59,11 @@ regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_FORCE_THUMBNAIL
|
|||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_REFIT_THUMBNAIL ] = 'This looks for the existing thumbnail, and if it is not the correct resolution or is missing, will regenerate a new one for the source file.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_OTHER_HASHES ] = 'This regenerates hydrus\'s store of md5, sha1, and sha512 supplementary hashes, which it can use for various external (usually website) lookups.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_DELETE_NEIGHBOUR_DUPES ] = 'Sometimes, a file metadata regeneration will mean a new filetype and thus a new file extension. If the existing, incorrectly named file is in use, it must be copied rather than renamed, and so there is a spare duplicate left over after the operation. This jobs cleans up the duplicate at a later time.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE ] = 'This checks to see if the file is present in the file system as expected. If it is not, the internal file record in the database is removed, just as if the file were deleted. Use this if you have manually deleted or otherwise lost a number of files from your file structure and need hydrus to re-sync with what it has. Missing files will have their known URLs exported to your database directory if you wish to attempt to re-download them.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA ] = 'This does the same check as the \'present\' job, and if the file is where it is expected, it ensures its file content, byte-for-byte, is correct. This is a heavy job, so be wary. Files that are incorrect will be exported to your database directory along with their known URLs.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE ] = 'This checks to see if the file is present in the file system as expected. If it is not, the internal file record in the database is removed, just as if the file were deleted. Use this if you have manually deleted or otherwise lost a number of files from your file structure and need hydrus to re-sync with what it actually has. Missing files will have their known URLs exported to your database directory.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_URL ] = 'This checks to see if the file is present in the file system as expected. If it is not, and it has known post/file urls, the URLs will be automatically added to a new URL downloader. Missing files will also have their known URLs exported to your database directory.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA ] = 'This does the same check as the \'file is missing\' job, and if the file is where it is expected, it ensures its file content, byte-for-byte, is correct. This is a heavy job, so be wary. If the file is incorrect, it will be exported to your database directory along with their known URLs, and the file record deleted.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL ] = 'This does the same check as the \'file is missing\' job, and if the file is where it is expected, it ensures its file content, byte-for-byte, is correct. This is a heavy job, so be wary. If the file is incorrect _and_ is has known post/file urls, the URLs will be automatically added to a new URL downloader. Incorrect files will also have their known URLs exported to your database directory.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_SILENT_DELETE ] = 'If the file is where it is expected, this ensures its file content, byte-for-byte, is correct. This is a heavy job, so be wary. If the file is incorrect, it will be exported to your database directory along with its known URLs. The client\'s file record will not be deleted. This is useful if you have a valid backup and need to clear out invalid files from your live db so you can fill in gaps from your backup with a program like FreeFileSync.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_FIX_PERMISSIONS ] = 'This ensures that files in the file system are readable and writeable. For Linux/macOS users, it specifically sets 644. If you wish to run this job on Linux/macOS, ensure you are first the file owner of all your files.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_CHECK_SIMILAR_FILES_MEMBERSHIP ] = 'This checks to see if files should be in the similar files system, and if they are falsely in or falsely out, it will remove their record or queue them up for a search as appropriate. It is useful to repair database damage.'
|
||||
regen_file_enum_to_description_lookup[ REGENERATE_FILE_DATA_JOB_SIMILAR_FILES_METADATA ] = 'This forces a regeneration of the file\'s similar-files \'phashes\'. It is not useful unless you know there is missing data to repair.'
|
||||
|
@ -70,7 +79,10 @@ regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_REFIT_THUMBNAIL ]
|
|||
regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_OTHER_HASHES ] = 100
|
||||
regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_DELETE_NEIGHBOUR_DUPES ] = 25
|
||||
regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE ] = 5
|
||||
regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_URL ] = 50
|
||||
regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA ] = 100
|
||||
regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL ] = 100
|
||||
regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_SILENT_DELETE ] = 100
|
||||
regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_FIX_PERMISSIONS ] = 25
|
||||
regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_CHECK_SIMILAR_FILES_MEMBERSHIP ] = 50
|
||||
regen_file_enum_to_job_weight_lookup[ REGENERATE_FILE_DATA_JOB_SIMILAR_FILES_METADATA ] = 100
|
||||
|
@ -84,13 +96,16 @@ regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_REFIT_THUMBNAIL ] =
|
|||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_OTHER_HASHES ] = []
|
||||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_DELETE_NEIGHBOUR_DUPES ] = []
|
||||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE ] = []
|
||||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_URL ] = []
|
||||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA ] = [ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE ]
|
||||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL ] = [ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_URL ]
|
||||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_SILENT_DELETE ] = []
|
||||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_FIX_PERMISSIONS ] = []
|
||||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_CHECK_SIMILAR_FILES_MEMBERSHIP ] = []
|
||||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_SIMILAR_FILES_METADATA ] = [ REGENERATE_FILE_DATA_JOB_CHECK_SIMILAR_FILES_MEMBERSHIP ]
|
||||
regen_file_enum_to_overruled_jobs[ REGENERATE_FILE_DATA_JOB_FILE_MODIFIED_TIMESTAMP ] = []
|
||||
|
||||
ALL_REGEN_JOBS_IN_PREFERRED_ORDER = [ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA, REGENERATE_FILE_DATA_JOB_FILE_METADATA, REGENERATE_FILE_DATA_JOB_REFIT_THUMBNAIL, REGENERATE_FILE_DATA_JOB_FORCE_THUMBNAIL, REGENERATE_FILE_DATA_JOB_SIMILAR_FILES_METADATA, REGENERATE_FILE_DATA_JOB_CHECK_SIMILAR_FILES_MEMBERSHIP, REGENERATE_FILE_DATA_JOB_FIX_PERMISSIONS, REGENERATE_FILE_DATA_JOB_FILE_MODIFIED_TIMESTAMP, REGENERATE_FILE_DATA_JOB_OTHER_HASHES, REGENERATE_FILE_DATA_JOB_DELETE_NEIGHBOUR_DUPES ]
|
||||
ALL_REGEN_JOBS_IN_PREFERRED_ORDER = [ REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_URL, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_SILENT_DELETE, REGENERATE_FILE_DATA_JOB_FILE_METADATA, REGENERATE_FILE_DATA_JOB_REFIT_THUMBNAIL, REGENERATE_FILE_DATA_JOB_FORCE_THUMBNAIL, REGENERATE_FILE_DATA_JOB_SIMILAR_FILES_METADATA, REGENERATE_FILE_DATA_JOB_CHECK_SIMILAR_FILES_MEMBERSHIP, REGENERATE_FILE_DATA_JOB_FIX_PERMISSIONS, REGENERATE_FILE_DATA_JOB_FILE_MODIFIED_TIMESTAMP, REGENERATE_FILE_DATA_JOB_OTHER_HASHES, REGENERATE_FILE_DATA_JOB_DELETE_NEIGHBOUR_DUPES ]
|
||||
|
||||
def GetAllFilePaths( raw_paths, do_human_sort = True ):
|
||||
|
||||
|
@ -1306,8 +1321,8 @@ class FilesMaintenanceManager( object ):
|
|||
|
||||
self._controller = controller
|
||||
|
||||
self._pubbed_message_about_missing_files = False
|
||||
self._pubbed_message_about_damaged_files = False
|
||||
self._pubbed_message_about_bad_file_record_delete = False
|
||||
self._pubbed_message_about_invalid_file_export = False
|
||||
|
||||
self._work_tracker = HydrusNetworking.BandwidthTracker()
|
||||
|
||||
|
@ -1377,7 +1392,7 @@ class FilesMaintenanceManager( object ):
|
|||
HydrusData.DebugPrint( 'Missing file: {}!'.format( hash.hex() ) )
|
||||
|
||||
|
||||
if not file_is_missing and job_type == REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA:
|
||||
if not file_is_missing and job_type in ( REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_SILENT_DELETE ):
|
||||
|
||||
actual_hash = HydrusFileHandling.GetHashFromPath( path )
|
||||
|
||||
|
@ -1387,23 +1402,6 @@ class FilesMaintenanceManager( object ):
|
|||
|
||||
HydrusData.DebugPrint( 'Invalid file: {} actually had hash {}!'.format( hash.hex(), actual_hash.hex() ) )
|
||||
|
||||
HydrusPaths.MakeSureDirectoryExists( error_dir )
|
||||
|
||||
dest_path = os.path.join( error_dir, os.path.basename( path ) )
|
||||
|
||||
HydrusPaths.MergeFile( path, dest_path )
|
||||
|
||||
if not self._pubbed_message_about_damaged_files:
|
||||
|
||||
self._pubbed_message_about_damaged_files = True
|
||||
|
||||
message = 'During file maintenance, a file was found to be invalid. It has been moved to {}.'.format( error_dir )
|
||||
message += os.linesep * 2
|
||||
message += 'More files may be invalid, but this message will not appear again during this boot.'
|
||||
|
||||
HydrusData.ShowText( message )
|
||||
|
||||
|
||||
|
||||
|
||||
file_was_bad = file_is_missing or file_is_invalid
|
||||
|
@ -1435,24 +1433,93 @@ class FilesMaintenanceManager( object ):
|
|||
|
||||
|
||||
|
||||
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, ( hash, ), reason = 'Record deleted during File Integrity check.' )
|
||||
useful_urls = []
|
||||
|
||||
for service_key in [ CC.LOCAL_FILE_SERVICE_KEY, CC.LOCAL_UPDATE_SERVICE_KEY, CC.TRASH_SERVICE_KEY, CC.COMBINED_LOCAL_FILE_SERVICE_KEY ]:
|
||||
for url in urls:
|
||||
|
||||
service_keys_to_content_updates = { CC.TRASH_SERVICE_KEY : [ content_update ] }
|
||||
add_it = False
|
||||
|
||||
self._controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
|
||||
url_class = HG.client_controller.network_engine.domain_manager.GetURLClass( url )
|
||||
|
||||
if url_class is None:
|
||||
|
||||
add_it = True
|
||||
|
||||
else:
|
||||
|
||||
if url_class.GetURLType() in ( HC.URL_TYPE_FILE, HC.URL_TYPE_POST ):
|
||||
|
||||
add_it = True
|
||||
|
||||
|
||||
|
||||
if add_it:
|
||||
|
||||
useful_urls.append( url )
|
||||
|
||||
|
||||
|
||||
if not self._pubbed_message_about_missing_files:
|
||||
delete_record = job_type in ( REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA )
|
||||
try_redownload = job_type in ( REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_URL, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL ) and len( useful_urls ) > 0
|
||||
do_export = file_is_invalid and ( job_type in ( REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_SILENT_DELETE ) or ( job_type == REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL and try_redownload ) )
|
||||
|
||||
if do_export:
|
||||
|
||||
self._pubbed_message_about_missing_files = True
|
||||
HydrusPaths.MakeSureDirectoryExists( error_dir )
|
||||
|
||||
message = 'During file maintenance, a file was found to be missing or invalid. Its record has been removed from the database. More information has been been written to the log, and any known URLs for the file have been written to {}.'.format( error_dir )
|
||||
message += os.linesep * 2
|
||||
message += 'More files may be missing or invalid, but this message will not appear again during this boot.'
|
||||
dest_path = os.path.join( error_dir, os.path.basename( path ) )
|
||||
|
||||
HydrusData.ShowText( message )
|
||||
HydrusPaths.MergeFile( path, dest_path )
|
||||
|
||||
if not self._pubbed_message_about_invalid_file_export:
|
||||
|
||||
self._pubbed_message_about_invalid_file_export = True
|
||||
|
||||
message = 'During file maintenance, a file was found to be invalid. It and any known URLs have been moved to "{}".'.format( error_dir )
|
||||
message += os.linesep * 2
|
||||
message += 'More files may be invalid, but this message will not appear again during this boot.'
|
||||
|
||||
HydrusData.ShowText( message )
|
||||
|
||||
|
||||
|
||||
if delete_record:
|
||||
|
||||
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, ( hash, ), reason = 'Record deleted during File Integrity check.' )
|
||||
|
||||
for service_key in [ CC.LOCAL_FILE_SERVICE_KEY, CC.LOCAL_UPDATE_SERVICE_KEY, CC.TRASH_SERVICE_KEY, CC.COMBINED_LOCAL_FILE_SERVICE_KEY ]:
|
||||
|
||||
service_keys_to_content_updates = { CC.TRASH_SERVICE_KEY : [ content_update ] }
|
||||
|
||||
self._controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
|
||||
if not self._pubbed_message_about_bad_file_record_delete:
|
||||
|
||||
self._pubbed_message_about_bad_file_record_delete = True
|
||||
|
||||
message = 'During file maintenance, a file was found to be missing or invalid. Its record has been removed from the database. Any known URLs for the file have been written to "{}".'.format( error_dir )
|
||||
message += os.linesep * 2
|
||||
message += 'More file records may have been removed, but this message will not appear again during this boot.'
|
||||
|
||||
HydrusData.ShowText( message )
|
||||
|
||||
|
||||
|
||||
if try_redownload:
|
||||
|
||||
def qt_add_url( url ):
|
||||
|
||||
if QP.isValid( HG.client_controller.gui ):
|
||||
|
||||
HG.client_controller.gui.ImportURL( url, 'missing files redownloader' )
|
||||
|
||||
|
||||
|
||||
for url in useful_urls:
|
||||
|
||||
QP.CallAfter( qt_add_url, url )
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1528,7 +1595,7 @@ class FilesMaintenanceManager( object ):
|
|||
|
||||
except HydrusExceptions.MimeException:
|
||||
|
||||
self._CheckFileIntegrity( media_result, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA )
|
||||
self._CheckFileIntegrity( media_result, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL )
|
||||
|
||||
return None
|
||||
|
||||
|
@ -1752,7 +1819,7 @@ class FilesMaintenanceManager( object ):
|
|||
|
||||
self._FixFilePermissions( media_result )
|
||||
|
||||
elif job_type in ( REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA ):
|
||||
elif job_type in ( REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_PRESENCE_URL, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_URL, REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_SILENT_DELETE ):
|
||||
|
||||
file_was_bad = self._CheckFileIntegrity( media_result, job_type )
|
||||
|
||||
|
|
2317
include/ClientGUI.py
2317
include/ClientGUI.py
File diff suppressed because it is too large
Load Diff
|
@ -501,6 +501,7 @@ class AutoCompleteDropdown( QW.QWidget ):
|
|||
|
||||
|
||||
self._text_ctrl.textChanged.connect( self.EventText )
|
||||
|
||||
self._text_ctrl_widget_event_filter.EVT_KEY_DOWN( self.keyPressFilter )
|
||||
|
||||
self._text_ctrl_widget_event_filter.EVT_MOUSEWHEEL( self.EventMouseWheel )
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
from . import ClientData
|
||||
from . import ClientConstants as CC
|
||||
from . import HydrusConstants as HC
|
||||
from . import HydrusData
|
||||
from . import HydrusExceptions
|
||||
from . import HydrusGlobals as HG
|
||||
from . import HydrusText
|
||||
import threading
|
||||
import traceback
|
||||
from qtpy import QtCore as QC
|
||||
from qtpy import QtWidgets as QW
|
||||
from qtpy import QtGui as QG
|
||||
from . import QtPorting as QP
|
||||
|
||||
class AsyncQtUpdater( object ):
|
||||
|
||||
def __init__( self, win ):
|
||||
|
||||
# ultimate improvement here is to move to QObject/QThread and do the notifications through signals and slots (which will disconnect on object deletion)
|
||||
|
||||
self._win = win
|
||||
|
||||
self._calllater_waiting = False
|
||||
self._work_needs_to_restart = False
|
||||
self._is_working = False
|
||||
|
||||
self._lock = threading.Lock()
|
||||
|
||||
|
||||
def _getResult( self ):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def _publishLoading( self ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def _publishResult( self, result ):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def _doWork( self ):
|
||||
|
||||
def deliver_result( result ):
|
||||
|
||||
if not self._win or not QP.isValid( self._win ):
|
||||
|
||||
self._win = None
|
||||
|
||||
return
|
||||
|
||||
|
||||
self._publishResult( result )
|
||||
|
||||
|
||||
with self._lock:
|
||||
|
||||
self._calllater_waiting = False
|
||||
self._work_needs_to_restart = False
|
||||
self._is_working = True
|
||||
|
||||
|
||||
try:
|
||||
|
||||
result = self._getResult()
|
||||
|
||||
try:
|
||||
|
||||
HG.client_controller.CallBlockingToQt( self._win, deliver_result, result )
|
||||
|
||||
except ( HydrusExceptions.QtDeadWindowException, HydrusExceptions.ShutdownException ):
|
||||
|
||||
self._win = None
|
||||
|
||||
return
|
||||
|
||||
|
||||
finally:
|
||||
|
||||
with self._lock:
|
||||
|
||||
self._is_working = False
|
||||
|
||||
if self._work_needs_to_restart and not self._calllater_waiting:
|
||||
|
||||
QP.CallAfter( self.update )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _startWork( self ):
|
||||
|
||||
HG.client_controller.CallToThread( self._doWork )
|
||||
|
||||
|
||||
def update( self ):
|
||||
|
||||
if not self._win or not QP.isValid( self._win ):
|
||||
|
||||
self._win = None
|
||||
|
||||
return
|
||||
|
||||
|
||||
with self._lock:
|
||||
|
||||
if self._is_working:
|
||||
|
||||
self._work_needs_to_restart = True
|
||||
|
||||
elif not self._calllater_waiting:
|
||||
|
||||
self._publishLoading()
|
||||
|
||||
self._calllater_waiting = True
|
||||
|
||||
self._startWork()
|
||||
|
||||
|
||||
|
||||
|
||||
class FastThreadToGUIUpdater( object ):
|
||||
|
||||
def __init__( self, win, func ):
|
||||
|
||||
self._win = win
|
||||
self._func = func
|
||||
|
||||
self._lock = threading.Lock()
|
||||
|
||||
self._args = None
|
||||
self._kwargs = None
|
||||
|
||||
self._callafter_waiting = False
|
||||
self._work_needs_to_restart = False
|
||||
self._is_working = False
|
||||
|
||||
|
||||
def QtDoIt( self ):
|
||||
|
||||
if not self._win or not QP.isValid( self._win ):
|
||||
|
||||
self._win = None
|
||||
|
||||
return
|
||||
|
||||
|
||||
with self._lock:
|
||||
|
||||
self._callafter_waiting = False
|
||||
self._work_needs_to_restart = False
|
||||
self._is_working = True
|
||||
|
||||
args = self._args
|
||||
kwargs = self._kwargs
|
||||
|
||||
|
||||
try:
|
||||
|
||||
self._func( *args, **kwargs )
|
||||
|
||||
except HydrusExceptions.ShutdownException:
|
||||
|
||||
pass
|
||||
|
||||
finally:
|
||||
|
||||
with self._lock:
|
||||
|
||||
self._is_working = False
|
||||
|
||||
if self._work_needs_to_restart and not self._callafter_waiting:
|
||||
|
||||
self._callafter_waiting = True
|
||||
|
||||
QP.CallAfter( self.QtDoIt )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# the point here is that we can spam this a hundred times a second, updating the args and kwargs, and Qt will catch up to it when it can
|
||||
# if Qt feels like running fast, it'll update at 60fps
|
||||
# if not, we won't get bungled up with 10,000+ pubsub events in the event queue
|
||||
def Update( self, *args, **kwargs ):
|
||||
|
||||
if HG.view_shutdown:
|
||||
|
||||
return
|
||||
|
||||
|
||||
with self._lock:
|
||||
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
|
||||
if self._is_working:
|
||||
|
||||
self._work_needs_to_restart = True
|
||||
|
||||
elif not self._callafter_waiting:
|
||||
|
||||
QP.CallAfter( self.QtDoIt )
|
||||
|
||||
|
||||
|
||||
|
|
@ -926,11 +926,11 @@ class CanvasFrame( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
self.destroyed.connect( HG.client_controller.gui.MaintainCanvasFrameReferences )
|
||||
|
||||
|
||||
def Close( self ):
|
||||
def close( self ):
|
||||
|
||||
self._canvas_window.CleanBeforeDestroy()
|
||||
|
||||
self.close()
|
||||
ClientGUITopLevelWindows.FrameThatResizes.close( self )
|
||||
|
||||
|
||||
def FullscreenSwitch( self ):
|
||||
|
@ -997,7 +997,7 @@ class CanvasFrame( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
vbox = QP.VBoxLayout( margin = 0 )
|
||||
|
||||
QP.AddToLayout( vbox, self._canvas_window, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
||||
QP.AddToLayout( vbox, self._canvas_window )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
@ -1024,6 +1024,8 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
||||
self.setSizePolicy( QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding )
|
||||
|
||||
self._file_service_key = CC.LOCAL_FILE_SERVICE_KEY
|
||||
|
||||
self._current_media_start_time = HydrusData.GetNow()
|
||||
|
@ -1471,7 +1473,11 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
if isinstance( panel, ClientGUITags.ManageTagsPanel ):
|
||||
|
||||
panel.setFocus( QC.Qt.OtherFocusReason )
|
||||
child.activateWindow()
|
||||
|
||||
command = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'set_search_focus' )
|
||||
|
||||
panel.ProcessApplicationCommand( command )
|
||||
|
||||
return
|
||||
|
||||
|
@ -1753,6 +1759,9 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
HG.client_controller.pub( 'canvas_new_zoom', self._canvas_key, self._current_zoom )
|
||||
|
||||
# due to the foolish 'giganto window' system for large zooms, some auto-update stuff doesn't work right if the convas rect is contained by the media rect, so do a refresh here
|
||||
self._DrawCurrentMedia()
|
||||
|
||||
self.update()
|
||||
|
||||
|
||||
|
@ -2270,6 +2279,11 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
|
||||
|
||||
def minimumSizeHint( self ):
|
||||
|
||||
return QC.QSize( 120, 120 )
|
||||
|
||||
|
||||
def ZoomIn( self, canvas_key ):
|
||||
|
||||
if canvas_key == self._canvas_key:
|
||||
|
@ -2757,7 +2771,7 @@ class CanvasWithHovers( CanvasWithDetails ):
|
|||
|
||||
self._widget_event_filter.EVT_MOTION( self.EventMouseMove )
|
||||
|
||||
HG.client_controller.sub( self, 'Close', 'canvas_close' )
|
||||
HG.client_controller.sub( self, 'CloseFromHover', 'canvas_close' )
|
||||
HG.client_controller.sub( self, 'FullscreenSwitch', 'canvas_fullscreen_switch' )
|
||||
|
||||
|
||||
|
@ -2773,7 +2787,7 @@ class CanvasWithHovers( CanvasWithDetails ):
|
|||
raise NotImplementedError()
|
||||
|
||||
|
||||
def Close( self, canvas_key ):
|
||||
def CloseFromHover( self, canvas_key ):
|
||||
|
||||
if canvas_key == self._canvas_key:
|
||||
|
||||
|
|
|
@ -397,6 +397,9 @@ class BetterStaticText( QP.EllipsizedLabel ):
|
|||
|
||||
QP.EllipsizedLabel.__init__( self, parent, ellipsize_end = ellipsize_end )
|
||||
|
||||
# otherwise by default html in 'this is a <hr> parsing step' stuff renders fully lmaoooo
|
||||
self.setTextFormat( QC.Qt.PlainText )
|
||||
|
||||
self._tooltip_label = tooltip_label
|
||||
|
||||
if 'ellipsize_end' in kwargs and kwargs[ 'ellipsize_end' ]:
|
||||
|
@ -669,6 +672,8 @@ class CheckboxManagerOptions( CheckboxManager ):
|
|||
|
||||
class ChoiceSort( QW.QWidget ):
|
||||
|
||||
sortChanged = QC.Signal( ClientMedia.MediaSort )
|
||||
|
||||
def __init__( self, parent, management_controller = None ):
|
||||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
@ -744,6 +749,8 @@ class ChoiceSort( QW.QWidget ):
|
|||
|
||||
media_sort = self._GetCurrentSort()
|
||||
|
||||
self.sortChanged.emit( media_sort )
|
||||
|
||||
if self._management_controller is not None:
|
||||
|
||||
self._management_controller.SetVariable( 'media_sort', media_sort )
|
||||
|
@ -2592,73 +2599,3 @@ class TextAndGauge( QW.QWidget ):
|
|||
self._gauge.SetValue( value )
|
||||
|
||||
|
||||
class ThreadToGUIUpdater( object ):
|
||||
|
||||
def __init__( self, win, func ):
|
||||
|
||||
self._win = win
|
||||
self._func = func
|
||||
|
||||
self._lock = threading.Lock()
|
||||
self._dirty_count = 0
|
||||
|
||||
self._args = None
|
||||
self._kwargs = None
|
||||
|
||||
self._doing_it = False
|
||||
|
||||
|
||||
def QtDoIt( self ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
if not self._win or not QP.isValid( self._win ):
|
||||
|
||||
self._win = None
|
||||
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
|
||||
self._func( *self._args, **self._kwargs )
|
||||
|
||||
except HydrusExceptions.ShutdownException:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
self._dirty_count = 0
|
||||
self._doing_it = False
|
||||
|
||||
|
||||
|
||||
# the point here is that we can spam this a hundred times a second, updating the args and kwargs, and Qt will catch up to it when it can
|
||||
# if Qt feels like running fast, it'll update at 60fps
|
||||
# if not, we won't get bungled up with 10,000+ pubsub events in the event queue
|
||||
def Update( self, *args, **kwargs ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
|
||||
if not self._doing_it and not HG.view_shutdown:
|
||||
|
||||
QP.CallAfter( self.QtDoIt )
|
||||
|
||||
self._doing_it = True
|
||||
|
||||
|
||||
self._dirty_count += 1
|
||||
|
||||
take_a_break = self._dirty_count % 1000 == 0
|
||||
|
||||
|
||||
# just in case we are choking the Qt thread, let's give it a break every now and then
|
||||
if take_a_break:
|
||||
|
||||
time.sleep( 0.25 )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1169,14 +1169,22 @@ class NetworkJobControl( QW.QFrame ):
|
|||
if self._network_job is not None:
|
||||
|
||||
if self._network_job.CurrentlyWaitingOnConnectionError():
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'reattempt connection now', 'Stop waiting on a connection error and reattempt the job now.', self._network_job.OverrideConnectionErrorWait )
|
||||
|
||||
|
||||
if self._network_job.CurrentlyWaitingOnServersideBandwidth():
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'reattempt request now (server reports low bandwidth)', 'Stop waiting on a serverside bandwidth delay and reattempt the job now.', self._network_job.OverrideServersideBandwidthWait )
|
||||
|
||||
|
||||
if self._network_job.ObeysBandwidth():
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'override bandwidth rules for this job', 'Tell the current job to ignore existing bandwidth rules and go ahead anyway.', self._network_job.OverrideBandwidth )
|
||||
|
||||
|
||||
if not self._network_job.TokensOK():
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'override gallery slot requirements for this job', 'Force-allow this download to proceed, ignoring the normal gallery wait times.', self._network_job.OverrideToken )
|
||||
|
||||
|
||||
|
|
|
@ -1156,7 +1156,7 @@ class DialogSelectImageboard( Dialog ):
|
|||
|
||||
class DialogTextEntry( Dialog ):
|
||||
|
||||
def __init__( self, parent, message, default = '', placeholder = None, allow_blank = False, suggestions = None, max_chars = None ):
|
||||
def __init__( self, parent, message, default = '', placeholder = None, allow_blank = False, suggestions = None, max_chars = None, password_entry = False ):
|
||||
|
||||
if suggestions is None:
|
||||
|
||||
|
@ -1180,6 +1180,11 @@ class DialogTextEntry( Dialog ):
|
|||
self._text.textChanged.connect( self.EventText )
|
||||
self._text.installEventFilter( ClientGUICommon.TextCatchEnterEventFilter( self._text, self.EnterText ) )
|
||||
|
||||
if password_entry:
|
||||
|
||||
self._text.setEchoMode( QW.QLineEdit.Password )
|
||||
|
||||
|
||||
if self._max_chars is not None:
|
||||
|
||||
self._text.setMaxLength( self._max_chars )
|
||||
|
|
|
@ -849,15 +849,17 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
self._last_view_start = None
|
||||
|
||||
self._text_y = QW.QApplication.fontMetrics().height()
|
||||
|
||||
self._num_rows_per_page = 0
|
||||
|
||||
self.verticalScrollBar().setSingleStep( self._text_y )
|
||||
self.setFont( QW.QApplication.font() )
|
||||
|
||||
text_height = self.fontMetrics().height()
|
||||
|
||||
self.verticalScrollBar().setSingleStep( text_height )
|
||||
|
||||
( min_width, min_height ) = ClientGUIFunctions.ConvertTextToPixels( self, ( 16, height_num_chars ) )
|
||||
|
||||
QP.SetMinClientSize( self, (min_width,min_height) )
|
||||
QP.SetMinClientSize( self, ( min_width, min_height ) )
|
||||
|
||||
self._widget_event_filter = QP.WidgetEventFilter( self.widget() )
|
||||
|
||||
|
@ -879,7 +881,9 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
size_hint = QW.QScrollArea.sizeHint( self )
|
||||
|
||||
size_hint.setHeight( 9 * self._text_y + self.height() - self.viewport().height() )
|
||||
text_height = self.fontMetrics().height()
|
||||
|
||||
size_hint.setHeight( 9 * text_height + self.height() - self.viewport().height() )
|
||||
|
||||
return size_hint
|
||||
|
||||
|
@ -961,7 +965,9 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
y = mouse_event.pos().y()
|
||||
|
||||
row_index = y // self._text_y
|
||||
text_height = self.fontMetrics().height()
|
||||
|
||||
row_index = y // text_height
|
||||
|
||||
if row_index >= len( self._ordered_terms ):
|
||||
|
||||
|
@ -1171,7 +1177,9 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
if self._last_hit_index is not None:
|
||||
|
||||
y = self._text_y * self._last_hit_index
|
||||
text_height = self.fontMetrics().height()
|
||||
|
||||
y = text_height * self._last_hit_index
|
||||
|
||||
visible_rect = QP.ScrollAreaVisibleRect( self )
|
||||
|
||||
|
@ -1183,9 +1191,9 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
self.ensureVisible( 0, y, 0, 0 )
|
||||
|
||||
elif y > visible_rect_y + visible_rect_height - self._text_y:
|
||||
elif y > visible_rect_y + visible_rect_height - text_height:
|
||||
|
||||
self.ensureVisible( 0, y + self._text_y , 0, 0 )
|
||||
self.ensureVisible( 0, y + text_height , 0, 0 )
|
||||
|
||||
|
||||
|
||||
|
@ -1208,6 +1216,8 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
def _Redraw( self, painter ):
|
||||
|
||||
text_height = self.fontMetrics().height()
|
||||
|
||||
visible_rect = QP.ScrollAreaVisibleRect( self )
|
||||
|
||||
visible_rect_y = visible_rect.y()
|
||||
|
@ -1215,19 +1225,17 @@ class ListBox( QW.QScrollArea ):
|
|||
visible_rect_width = visible_rect.width()
|
||||
visible_rect_height = visible_rect.height()
|
||||
|
||||
first_visible_index = visible_rect_y // self._text_y
|
||||
first_visible_index = visible_rect_y // text_height
|
||||
|
||||
last_visible_index = ( visible_rect_y + visible_rect_height ) // self._text_y
|
||||
last_visible_index = ( visible_rect_y + visible_rect_height ) // text_height
|
||||
|
||||
if ( visible_rect_y + visible_rect_height ) % self._text_y != 0:
|
||||
if ( visible_rect_y + visible_rect_height ) % text_height != 0:
|
||||
|
||||
last_visible_index += 1
|
||||
|
||||
|
||||
last_visible_index = min( last_visible_index, len( self._ordered_terms ) - 1 )
|
||||
|
||||
painter.setFont( QW.QApplication.font() )
|
||||
|
||||
painter.setBackground( QG.QBrush( self._background_colour ) )
|
||||
|
||||
painter.eraseRect( painter.viewport() )
|
||||
|
@ -1261,14 +1269,14 @@ class ListBox( QW.QScrollArea ):
|
|||
background_colour_x = x_start
|
||||
|
||||
|
||||
painter.drawRect( background_colour_x, current_index*self._text_y, visible_rect_width, self._text_y )
|
||||
painter.drawRect( background_colour_x, current_index * text_height, visible_rect_width, text_height )
|
||||
|
||||
text_colour = self._background_colour
|
||||
|
||||
|
||||
painter.setPen( QG.QPen( text_colour ) )
|
||||
|
||||
( x, y ) = ( x_start, current_index * self._text_y )
|
||||
( x, y ) = ( x_start, current_index * text_height )
|
||||
|
||||
( text_width, text_height ) = painter.fontMetrics().size( QC.Qt.TextSingleLine, text ).toTuple()
|
||||
|
||||
|
@ -1334,7 +1342,9 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
( my_x, my_y ) = self.widget().size().toTuple()
|
||||
|
||||
ideal_virtual_size = QC.QSize( my_x, self._text_y * len( self._ordered_terms ) )
|
||||
text_height = self.fontMetrics().height()
|
||||
|
||||
ideal_virtual_size = QC.QSize( my_x, text_height * len( self._ordered_terms ) )
|
||||
|
||||
if ideal_virtual_size != self.widget().size():
|
||||
|
||||
|
@ -1479,11 +1489,15 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
def resizeEvent( self, event ):
|
||||
|
||||
text_height = self.fontMetrics().height()
|
||||
|
||||
visible_rect = QP.ScrollAreaVisibleRect( self )
|
||||
|
||||
self.verticalScrollBar().setSingleStep( text_height )
|
||||
|
||||
visible_rect_height = visible_rect.height()
|
||||
|
||||
self._num_rows_per_page = visible_rect_height // self._text_y
|
||||
self._num_rows_per_page = visible_rect_height // text_height
|
||||
|
||||
self._SetVirtualSize()
|
||||
|
||||
|
@ -1504,7 +1518,9 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
def GetIdealHeight( self ):
|
||||
|
||||
return self._text_y * len( self._ordered_terms ) + 20
|
||||
text_height = self.fontMetrics().height()
|
||||
|
||||
return text_height * len( self._ordered_terms ) + 20
|
||||
|
||||
|
||||
def HasValues( self ):
|
||||
|
|
|
@ -766,6 +766,14 @@ class ManagementPanel( QW.QScrollArea ):
|
|||
pass
|
||||
|
||||
|
||||
def SetSort( self, page_key, media_sort ):
|
||||
|
||||
if page_key == self._page_key:
|
||||
|
||||
self._management_controller.SetVariable( 'media_sort', media_sort )
|
||||
|
||||
|
||||
|
||||
def Start( self ):
|
||||
|
||||
pass
|
||||
|
|
|
@ -2947,7 +2947,10 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
|
||||
|
||||
HG.client_controller.GetCache( 'thumbnail' ).Waterfall( self._page_key, thumbnails_to_render_later )
|
||||
if len( thumbnails_to_render_later ) > 0:
|
||||
|
||||
HG.client_controller.GetCache( 'thumbnail' ).Waterfall( self._page_key, thumbnails_to_render_later )
|
||||
|
||||
|
||||
|
||||
def _FadeThumbnails( self, thumbnails ):
|
||||
|
@ -3205,7 +3208,9 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
self._UpdateScrollBars() # would lead to infinite recursion if called from a resize event
|
||||
|
||||
|
||||
|
||||
return ( virtual_width, virtual_height )
|
||||
|
||||
|
||||
return ( 0, 0 )
|
||||
|
||||
|
@ -3419,13 +3424,16 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
thumbnails = MediaPanel.AddMediaResults( self, page_key, media_results )
|
||||
|
||||
self._RecalculateVirtualSize()
|
||||
|
||||
HG.client_controller.GetCache( 'thumbnail' ).Waterfall( self._page_key, thumbnails )
|
||||
|
||||
if len( self._selected_media ) == 0:
|
||||
if len( thumbnails ) > 0:
|
||||
|
||||
self._PublishSelectionIncrement( thumbnails )
|
||||
self._RecalculateVirtualSize()
|
||||
|
||||
HG.client_controller.GetCache( 'thumbnail' ).Waterfall( self._page_key, thumbnails )
|
||||
|
||||
if len( self._selected_media ) == 0:
|
||||
|
||||
self._PublishSelectionIncrement( thumbnails )
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -769,23 +769,9 @@ class Page( QW.QSplitter ):
|
|||
|
||||
def SetSplitterPositions( self, hpos, vpos ):
|
||||
|
||||
if QP.SplitterVisibleCount( self._search_preview_split ) > 1:
|
||||
|
||||
self._search_preview_split.widget( 0 ).resize( self._search_preview_split.widget( 0 ).width(), vpos )
|
||||
|
||||
else:
|
||||
|
||||
QP.SplitHorizontally( self._search_preview_split, self._management_panel, self._preview_panel, vpos )
|
||||
|
||||
QP.SplitHorizontally( self._search_preview_split, self._management_panel, self._preview_panel, vpos )
|
||||
|
||||
if QP.SplitterVisibleCount( self ) > 1:
|
||||
|
||||
self.widget( 0 ).resize( hpos, self.widget( 0 ).height() )
|
||||
|
||||
else:
|
||||
|
||||
QP.SplitVertically( self, self._search_preview_split, self._media_panel, hpos )
|
||||
|
||||
QP.SplitVertically( self, self._search_preview_split, self._media_panel, hpos )
|
||||
|
||||
if HC.options[ 'hide_preview' ]:
|
||||
|
||||
|
@ -2446,7 +2432,11 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
|
|||
insertion_index = min( insertion_index, self.count() )
|
||||
|
||||
self.insertTab( insertion_index, page, page_name )
|
||||
if select_page: self.setCurrentIndex( insertion_index )
|
||||
|
||||
if select_page:
|
||||
|
||||
self.setCurrentIndex( insertion_index )
|
||||
|
||||
|
||||
self.LayoutPages()
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from . import ClientAPI
|
||||
from . import ClientConstants as CC
|
||||
from . import ClientGUIAPI
|
||||
from . import ClientGUIAsync
|
||||
from . import ClientGUICommon
|
||||
from . import ClientGUIDialogs
|
||||
from . import ClientGUIDialogsQuick
|
||||
|
@ -393,7 +394,7 @@ class ReviewServicePanel( QW.QWidget ):
|
|||
|
||||
self._service = service
|
||||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
self._name_and_type = ClientGUICommon.BetterStaticText( self )
|
||||
|
||||
|
@ -442,7 +443,7 @@ class ReviewServicePanel( QW.QWidget ):
|
|||
|
||||
self._service = service
|
||||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
self._service_status = ClientGUICommon.BetterStaticText( self )
|
||||
|
||||
|
@ -755,7 +756,7 @@ class ReviewServicePanel( QW.QWidget ):
|
|||
|
||||
self._service = service
|
||||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
self._file_info_st = ClientGUICommon.BetterStaticText( self )
|
||||
|
||||
|
@ -828,7 +829,7 @@ class ReviewServicePanel( QW.QWidget ):
|
|||
|
||||
self._service = service
|
||||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
self._address = ClientGUICommon.BetterStaticText( self )
|
||||
self._functional = ClientGUICommon.BetterStaticText( self )
|
||||
|
@ -909,7 +910,7 @@ class ReviewServicePanel( QW.QWidget ):
|
|||
|
||||
self._service = service
|
||||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
self._title_and_expires_st = ClientGUICommon.BetterStaticText( self )
|
||||
self._status_st = ClientGUICommon.BetterStaticText( self )
|
||||
|
@ -1104,7 +1105,7 @@ class ReviewServicePanel( QW.QWidget ):
|
|||
|
||||
self._service = service
|
||||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
self._content_panel = QW.QWidget( self )
|
||||
|
||||
|
@ -1497,7 +1498,7 @@ class ReviewServicePanel( QW.QWidget ):
|
|||
|
||||
self._service = service
|
||||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
interaction_panel = IPFSDaemonStatusAndInteractionPanel( self, self.GetService )
|
||||
|
||||
|
@ -1725,7 +1726,7 @@ class ReviewServicePanel( QW.QWidget ):
|
|||
|
||||
self._share_key_info = {}
|
||||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
self._service_status = ClientGUICommon.BetterStaticText( self )
|
||||
|
||||
|
@ -1983,7 +1984,7 @@ class ReviewServicePanel( QW.QWidget ):
|
|||
|
||||
self._service = service
|
||||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
self._rating_info_st = ClientGUICommon.BetterStaticText( self )
|
||||
|
||||
|
@ -2073,7 +2074,7 @@ class ReviewServicePanel( QW.QWidget ):
|
|||
|
||||
self._service = service
|
||||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
self._tag_info_st = ClientGUICommon.BetterStaticText( self )
|
||||
|
||||
|
|
|
@ -53,21 +53,31 @@ class PopupMessage( PopupWindow ):
|
|||
|
||||
PopupWindow.__init__( self, parent, manager )
|
||||
|
||||
self.setSizePolicy( QW.QSizePolicy.MinimumExpanding, QW.QSizePolicy.Fixed )
|
||||
|
||||
self._job_key = job_key
|
||||
|
||||
vbox = QP.VBoxLayout()
|
||||
|
||||
self._title = ClientGUICommon.BetterStaticText( self )
|
||||
self._title.setAlignment( QC.Qt.AlignHCenter | QC.Qt.AlignVCenter )
|
||||
self.setSizePolicy( QW.QSizePolicy.MinimumExpanding, QW.QSizePolicy.Preferred )
|
||||
|
||||
font = self._title.font()
|
||||
font.setBold( True )
|
||||
self._title.setFont( font )
|
||||
|
||||
popup_message_character_width = HG.client_controller.new_options.GetInteger( 'popup_message_character_width' )
|
||||
|
||||
wrap_width = ClientGUIFunctions.ConvertTextToPixelWidth( self._title, popup_message_character_width )
|
||||
popup_char_width = ClientGUIFunctions.ConvertTextToPixelWidth( self._title, popup_message_character_width )
|
||||
|
||||
if HG.client_controller.new_options.GetBoolean( 'popup_message_force_min_width' ):
|
||||
|
||||
QP.SetMinClientSize( self, ( wrap_width, -1 ) )
|
||||
#QP.SetMinClientSize( self, ( wrap_width, -1 ) )
|
||||
self.setFixedWidth( popup_char_width )
|
||||
|
||||
else:
|
||||
|
||||
self.setMaximumWidth( popup_char_width )
|
||||
|
||||
|
||||
self._title.setWordWrap( True )
|
||||
|
@ -84,6 +94,7 @@ class PopupMessage( PopupWindow ):
|
|||
self._gauge_1 = ClientGUICommon.Gauge( self )
|
||||
self._gauge_1_ev = QP.WidgetEventFilter( self._gauge_1 )
|
||||
self._gauge_1_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
||||
self._gauge_1.setMinimumWidth( int( popup_char_width * 0.9 ) )
|
||||
self._gauge_1.hide()
|
||||
|
||||
self._text_2 = ClientGUICommon.BetterStaticText( self )
|
||||
|
@ -95,6 +106,7 @@ class PopupMessage( PopupWindow ):
|
|||
self._gauge_2 = ClientGUICommon.Gauge( self )
|
||||
self._gauge_2_ev = QP.WidgetEventFilter( self._gauge_2 )
|
||||
self._gauge_2_ev.EVT_RIGHT_DOWN( self.EventDismiss )
|
||||
self._gauge_2.setMinimumWidth( int( popup_char_width * 0.9 ) )
|
||||
self._gauge_2.hide()
|
||||
|
||||
self._text_yes_no = ClientGUICommon.BetterStaticText( self )
|
||||
|
@ -157,12 +169,12 @@ class PopupMessage( PopupWindow ):
|
|||
QP.AddToLayout( yes_no_hbox, self._yes, CC.FLAGS_VCENTER )
|
||||
QP.AddToLayout( yes_no_hbox, self._no, CC.FLAGS_VCENTER )
|
||||
|
||||
QP.AddToLayout( vbox, self._title, alignment = QC.Qt.AlignHCenter )
|
||||
QP.AddToLayout( vbox, self._text_1 )
|
||||
QP.AddToLayout( vbox, self._gauge_1 )
|
||||
QP.AddToLayout( vbox, self._text_2 )
|
||||
QP.AddToLayout( vbox, self._gauge_2 )
|
||||
QP.AddToLayout( vbox, self._text_yes_no )
|
||||
QP.AddToLayout( vbox, self._title, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._text_1, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._gauge_1, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._text_2, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._gauge_2, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._text_yes_no, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, yes_no_hbox )
|
||||
QP.AddToLayout( vbox, self._network_job_ctrl )
|
||||
QP.AddToLayout( vbox, self._copy_to_clipboard_button )
|
||||
|
@ -538,6 +550,7 @@ class PopupMessageManager( QW.QWidget ):
|
|||
self._message_panel = QW.QWidget( self )
|
||||
|
||||
self._message_vbox = QP.VBoxLayout( margin = 0 )
|
||||
|
||||
vbox.setSizeConstraint( QW.QLayout.SetFixedSize )
|
||||
|
||||
self._message_panel.setLayout( self._message_vbox )
|
||||
|
@ -570,7 +583,7 @@ class PopupMessageManager( QW.QWidget ):
|
|||
|
||||
job_key.SetVariable( 'popup_text_1', 'initialising popup message manager\u2026' )
|
||||
|
||||
self._update_job = HG.client_controller.CallRepeatingQtSafe(self, 0.25, 0.5, self.REPEATINGUpdate)
|
||||
self._update_job = HG.client_controller.CallRepeatingQtSafe( self, 0.25, 0.5, self.REPEATINGUpdate )
|
||||
|
||||
HG.client_controller.CallLaterQtSafe(self, 0.5, self.AddMessage, job_key)
|
||||
|
||||
|
@ -1082,7 +1095,8 @@ class PopupMessageDialogPanel( QW.QWidget ):
|
|||
|
||||
self._message_pubbed = False
|
||||
|
||||
self._update_job = HG.client_controller.CallRepeatingQtSafe(self, 0.25, 0.5, self.REPEATINGUpdate)
|
||||
self._update_job = HG.client_controller.CallRepeatingQtSafe( self, 0.25, 0.5, self.REPEATINGUpdate )
|
||||
|
||||
|
||||
def CanCancel( self ):
|
||||
|
||||
|
|
|
@ -4144,20 +4144,52 @@ But if 2 is--and is also perhaps accompanied by many 'could not parse' errors--t
|
|||
|
||||
if len( already_existing_query_texts ) > 0:
|
||||
|
||||
message = 'The queries:'
|
||||
message += os.linesep * 2
|
||||
message += os.linesep.join( already_existing_query_texts )
|
||||
message += os.linesep * 2
|
||||
message += 'Were already in the subscription, so they need not be added.'
|
||||
if len( already_existing_query_texts ) > 50:
|
||||
|
||||
message = '{} queries were already in the subscription, so they need not be added.'.format( HydrusData.ToHumanInt( len( already_existing_query_texts ) ) )
|
||||
|
||||
else:
|
||||
|
||||
if len( already_existing_query_texts ) > 5:
|
||||
|
||||
aeqt_separator = ', '
|
||||
|
||||
else:
|
||||
|
||||
aeqt_separator = os.linesep
|
||||
|
||||
|
||||
message = 'The queries:'
|
||||
message += os.linesep * 2
|
||||
message += aeqt_separator.join( already_existing_query_texts )
|
||||
message += os.linesep * 2
|
||||
message += 'Were already in the subscription, so they need not be added.'
|
||||
|
||||
|
||||
if len( new_query_texts ) > 0:
|
||||
|
||||
message += os.linesep * 2
|
||||
message += 'The queries:'
|
||||
message += os.linesep * 2
|
||||
message += os.linesep.join( new_query_texts )
|
||||
message += os.linesep * 2
|
||||
message += 'Were new and will be added.'
|
||||
if len( new_query_texts ) > 50:
|
||||
|
||||
message = '{} queries were new and will be added.'.format( HydrusData.ToHumanInt( len( new_query_texts ) ) )
|
||||
|
||||
else:
|
||||
|
||||
if len( new_query_texts ) > 5:
|
||||
|
||||
nqt_separator = ', '
|
||||
|
||||
else:
|
||||
|
||||
nqt_separator = os.linesep
|
||||
|
||||
|
||||
message += os.linesep * 2
|
||||
message += 'The queries:'
|
||||
message += os.linesep * 2
|
||||
message += nqt_separator.join( new_query_texts )
|
||||
message += os.linesep * 2
|
||||
message += 'Were new and will be added.'
|
||||
|
||||
|
||||
|
||||
QW.QMessageBox.information( self, 'Information', message )
|
||||
|
@ -6204,7 +6236,7 @@ class TagSummaryGeneratorButton( ClientGUICommon.BetterButton ):
|
|||
|
||||
class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
|
||||
|
||||
def __init__( self, parent, url_class ):
|
||||
def __init__( self, parent, url_class: ClientNetworkingDomain.URLClass ):
|
||||
|
||||
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
|
||||
|
||||
|
@ -6228,6 +6260,14 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
self._netloc = QW.QLineEdit( self )
|
||||
|
||||
self._alphabetise_get_parameters = QW.QCheckBox( self )
|
||||
|
||||
tt = 'Normally, to ensure the same URLs are merged, hydrus will alphabetise GET parameters as part of the normalisation process.'
|
||||
tt += os.linesep * 2
|
||||
tt += 'Almost all servers support GET params in any order. One or two do not. Uncheck this if you know there is a problem.'
|
||||
|
||||
self._alphabetise_get_parameters.setToolTip( tt )
|
||||
|
||||
self._match_subdomains = QW.QCheckBox( self )
|
||||
|
||||
tt = 'Should this class apply to subdomains as well?'
|
||||
|
@ -6309,7 +6349,7 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
self._normalised_url.setToolTip( tt )
|
||||
|
||||
( url_type, preferred_scheme, netloc, match_subdomains, keep_matched_subdomains, path_components, parameters, api_lookup_converter, send_referral_url, referral_url_converter, can_produce_multiple_files, should_be_associated_with_files, example_url ) = url_class.ToTuple()
|
||||
( url_type, preferred_scheme, netloc, path_components, parameters, api_lookup_converter, send_referral_url, referral_url_converter, example_url ) = url_class.ToTuple()
|
||||
|
||||
self._send_referral_url = ClientGUICommon.BetterChoice( self )
|
||||
|
||||
|
@ -6355,6 +6395,9 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
self._netloc.setText( netloc )
|
||||
|
||||
( match_subdomains, keep_matched_subdomains, alphabetise_get_parameters, can_produce_multiple_files, should_be_associated_with_files ) = url_class.GetURLBooleans()
|
||||
|
||||
self._alphabetise_get_parameters.setChecked( alphabetise_get_parameters )
|
||||
self._match_subdomains.setChecked( match_subdomains )
|
||||
self._keep_matched_subdomains.setChecked( keep_matched_subdomains )
|
||||
self._can_produce_multiple_files.setChecked( can_produce_multiple_files )
|
||||
|
@ -6410,6 +6453,7 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
rows.append( ( 'url type: ', self._url_type ) )
|
||||
rows.append( ( 'preferred scheme: ', self._preferred_scheme ) )
|
||||
rows.append( ( 'network location: ', self._netloc ) )
|
||||
rows.append( ( 'alphabetise GET parameters?: ', self._alphabetise_get_parameters ) )
|
||||
rows.append( ( 'match subdomains?: ', self._match_subdomains ) )
|
||||
rows.append( ( 'keep matched subdomains?: ', self._keep_matched_subdomains ) )
|
||||
rows.append( ( 'can produce multiple files: ', self._can_produce_multiple_files ) )
|
||||
|
@ -6445,6 +6489,7 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
self._preferred_scheme.currentIndexChanged.connect( self._UpdateControls )
|
||||
self._netloc.textChanged.connect( self._UpdateControls )
|
||||
self._alphabetise_get_parameters.clicked.connect( self._UpdateControls )
|
||||
self._match_subdomains.clicked.connect( self._UpdateControls )
|
||||
self._keep_matched_subdomains.clicked.connect( self._UpdateControls )
|
||||
self._can_produce_multiple_files.clicked.connect( self._UpdateControls )
|
||||
|
@ -6736,10 +6781,6 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
url_type = self._url_type.GetValue()
|
||||
preferred_scheme = self._preferred_scheme.GetValue()
|
||||
netloc = self._netloc.text()
|
||||
match_subdomains = self._match_subdomains.isChecked()
|
||||
keep_matched_subdomains = self._keep_matched_subdomains.isChecked()
|
||||
can_produce_multiple_files = self._can_produce_multiple_files.isChecked()
|
||||
should_be_associated_with_files = self._should_be_associated_with_files.isChecked()
|
||||
path_components = self._path_components.GetData()
|
||||
parameters = dict( self._parameters.GetData() )
|
||||
api_lookup_converter = self._api_lookup_converter.GetValue()
|
||||
|
@ -6751,7 +6792,15 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
example_url = self._example_url.text()
|
||||
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_class_key = url_class_key, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, match_subdomains = match_subdomains, keep_matched_subdomains = keep_matched_subdomains, path_components = path_components, parameters = parameters, api_lookup_converter = api_lookup_converter, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, can_produce_multiple_files = can_produce_multiple_files, should_be_associated_with_files = should_be_associated_with_files, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_class_key = url_class_key, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, path_components = path_components, parameters = parameters, api_lookup_converter = api_lookup_converter, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
|
||||
match_subdomains = self._match_subdomains.isChecked()
|
||||
keep_matched_subdomains = self._keep_matched_subdomains.isChecked()
|
||||
alphabetise_get_parameters = self._alphabetise_get_parameters.isChecked()
|
||||
can_produce_multiple_files = self._can_produce_multiple_files.isChecked()
|
||||
should_be_associated_with_files = self._should_be_associated_with_files.isChecked()
|
||||
|
||||
url_class.SetURLBooleans( match_subdomains, keep_matched_subdomains, alphabetise_get_parameters, can_produce_multiple_files, should_be_associated_with_files )
|
||||
|
||||
return url_class
|
||||
|
||||
|
|
|
@ -1709,11 +1709,17 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
self._verify_regular_https = QW.QCheckBox( general )
|
||||
|
||||
self._network_timeout = QP.MakeQSpinBox( general, min=3, max=300 )
|
||||
self._network_timeout = QP.MakeQSpinBox( general, min = 3, max = 600 )
|
||||
self._network_timeout.setToolTip( 'If a network connection cannot be made in this duration or, if once started, it experiences uninterrupted inactivity for six times this duration, it will be abandoned.' )
|
||||
|
||||
self._max_network_jobs = QP.MakeQSpinBox( general, min=1, max=30 )
|
||||
self._max_network_jobs_per_domain = QP.MakeQSpinBox( general, min=1, max=5 )
|
||||
self._connection_error_wait_time = QP.MakeQSpinBox( general, min = 3, max = 1800 )
|
||||
self._connection_error_wait_time.setToolTip( 'If a network connection times out as above, it will wait increasing multiples of this base time before retrying.' )
|
||||
|
||||
self._serverside_bandwidth_wait_time = QP.MakeQSpinBox( general, min = 3, max = 1800 )
|
||||
self._serverside_bandwidth_wait_time.setToolTip( 'If a server returns a failure status code indicating it is short on bandwidth, the network job will wait increasing multiples of this base time before retrying.' )
|
||||
|
||||
self._max_network_jobs = QP.MakeQSpinBox( general, min = 1, max = 30 )
|
||||
self._max_network_jobs_per_domain = QP.MakeQSpinBox( general, min = 1, max = 5 )
|
||||
|
||||
#
|
||||
|
||||
|
@ -1732,6 +1738,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._https_proxy.SetValue( self._new_options.GetNoneableString( 'https_proxy' ) )
|
||||
|
||||
self._network_timeout.setValue( self._new_options.GetInteger( 'network_timeout' ) )
|
||||
self._connection_error_wait_time.setValue( self._new_options.GetInteger( 'connection_error_wait_time' ) )
|
||||
self._serverside_bandwidth_wait_time.setValue( self._new_options.GetInteger( 'serverside_bandwidth_wait_time' ) )
|
||||
|
||||
self._max_network_jobs.setValue( self._new_options.GetInteger( 'max_network_jobs' ) )
|
||||
self._max_network_jobs_per_domain.setValue( self._new_options.GetInteger( 'max_network_jobs_per_domain' ) )
|
||||
|
@ -1741,6 +1749,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
rows = []
|
||||
|
||||
rows.append( ( 'network timeout (seconds): ', self._network_timeout ) )
|
||||
rows.append( ( 'connection error retry wait (seconds): ', self._connection_error_wait_time ) )
|
||||
rows.append( ( 'serverside bandwidth retry wait (seconds): ', self._serverside_bandwidth_wait_time ) )
|
||||
rows.append( ( 'max number of simultaneous active network jobs: ', self._max_network_jobs ) )
|
||||
rows.append( ( 'max number of simultaneous active network jobs per domain: ', self._max_network_jobs_per_domain ) )
|
||||
rows.append( ( 'BUGFIX: verify regular https traffic:', self._verify_regular_https ) )
|
||||
|
@ -1792,8 +1802,11 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._new_options.SetNoneableString( 'https_proxy', self._https_proxy.GetValue() )
|
||||
|
||||
self._new_options.SetInteger( 'network_timeout', self._network_timeout.value() )
|
||||
self._new_options.SetInteger( 'connection_error_wait_time', self._connection_error_wait_time.value() )
|
||||
self._new_options.SetInteger( 'serverside_bandwidth_wait_time', self._serverside_bandwidth_wait_time.value() )
|
||||
self._new_options.SetInteger( 'max_network_jobs', self._max_network_jobs.value() )
|
||||
self._new_options.SetInteger( 'max_network_jobs_per_domain', self._max_network_jobs_per_domain.value() )
|
||||
|
||||
|
||||
|
||||
class _DownloadingPanel( QW.QWidget ):
|
||||
|
@ -5087,6 +5100,7 @@ class ManageShortcutsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._tag_panel = ClientGUICommon.StaticBox( self._content_panel, 'tag service actions' )
|
||||
|
||||
self._tag_service_keys = QW.QComboBox( self._tag_panel )
|
||||
|
||||
self._tag_value = QW.QLineEdit()
|
||||
self._tag_value.setReadOnly( True )
|
||||
|
||||
|
@ -5190,7 +5204,7 @@ class ManageShortcutsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
QP.SetStringSelection( self._tag_service_keys, service_name )
|
||||
|
||||
self._tag_value.setValue( value )
|
||||
self._tag_value.setText( value )
|
||||
|
||||
self._SetTag()
|
||||
|
||||
|
@ -5843,7 +5857,7 @@ class ManageURLsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
if key in CC.DELETE_KEYS:
|
||||
|
||||
urls = [ QP.GetClientData( self._urls_listbox, selection ) for selection in list( self._urls_listbox.selectedIndexes() ) ]
|
||||
urls = [ QP.GetClientData( self._urls_listbox, selection.row() ) for selection in list( self._urls_listbox.selectedIndexes() ) ]
|
||||
|
||||
for url in urls:
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ from . import ClientDragDrop
|
|||
from . import ClientExporting
|
||||
from . import ClientFiles
|
||||
from . import ClientGUIACDropdown
|
||||
from . import ClientGUIAsync
|
||||
from . import ClientGUICharts
|
||||
from . import ClientGUICommon
|
||||
from . import ClientGUIControls
|
||||
|
@ -76,6 +77,7 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ):
|
|||
service_info = HG.client_controller.Read( 'service_info', CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
|
||||
|
||||
self._all_local_files_total_size = service_info[ HC.SERVICE_INFO_TOTAL_SIZE ]
|
||||
self._all_local_files_total_num = service_info[ HC.SERVICE_INFO_NUM_FILES ]
|
||||
|
||||
menu_items = []
|
||||
|
||||
|
@ -300,10 +302,8 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ):
|
|||
|
||||
def _ConvertLocationToListCtrlTuples( self, location ):
|
||||
|
||||
thumbnail_ratio_estimate = self._GetThumbnailRatioEstimate()
|
||||
|
||||
f_space = self._all_local_files_total_size
|
||||
t_space = self._all_local_files_total_size * thumbnail_ratio_estimate
|
||||
t_space = self._GetThumbnailSizeEstimate()
|
||||
|
||||
# current
|
||||
|
||||
|
@ -514,18 +514,19 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ):
|
|||
return all_locations
|
||||
|
||||
|
||||
def _GetThumbnailRatioEstimate( self ):
|
||||
def _GetThumbnailSizeEstimate( self ):
|
||||
|
||||
( t_width, t_height ) = HG.client_controller.options[ 'thumbnail_dimensions' ]
|
||||
|
||||
normal_num_pixels = 200 * 200
|
||||
normal_ratio = 0.016
|
||||
typical_thumb_num_pixels = 320 * 240
|
||||
typical_thumb_size = 36 * 1024
|
||||
|
||||
current_num_pixels = t_width * t_height
|
||||
our_thumb_num_pixels = t_width * t_height
|
||||
our_thumb_size_estimate = typical_thumb_size * ( our_thumb_num_pixels / typical_thumb_num_pixels )
|
||||
|
||||
estimate_ratio = ( current_num_pixels / normal_num_pixels ) * normal_ratio
|
||||
our_total_thumb_size_estimate = self._all_local_files_total_num * our_thumb_size_estimate
|
||||
|
||||
return estimate_ratio
|
||||
return our_total_thumb_size_estimate
|
||||
|
||||
|
||||
def _IncreaseWeight( self ):
|
||||
|
@ -761,10 +762,8 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ):
|
|||
self._current_db_path_st.setText( 'database (about '+HydrusData.ToHumanBytes(approx_total_db_size)+'): '+self._controller.GetDBDir() )
|
||||
self._current_install_path_st.setText( 'install: '+HC.BASE_DIR )
|
||||
|
||||
thumbnail_ratio_estimate = self._GetThumbnailRatioEstimate()
|
||||
|
||||
approx_total_client_files = self._all_local_files_total_size
|
||||
approx_total_thumbnails = self._all_local_files_total_size * thumbnail_ratio_estimate
|
||||
approx_total_thumbnails = self._GetThumbnailSizeEstimate()
|
||||
|
||||
label = 'media is ' + HydrusData.ToHumanBytes( approx_total_client_files ) + ', thumbnails are estimated at ' + HydrusData.ToHumanBytes( approx_total_thumbnails ) + ':'
|
||||
|
||||
|
@ -1976,7 +1975,7 @@ class ReviewDownloaderImport( ClientGUIScrolledPanels.ReviewPanel ):
|
|||
QP.AddToLayout( vbox, help_hbox, CC.FLAGS_BUTTON_SIZER )
|
||||
QP.AddToLayout( vbox, st, CC.FLAGS_CENTER )
|
||||
QP.AddToLayout( vbox, win, CC.FLAGS_CENTER )
|
||||
QP.AddToLayout( vbox, ClientGUICommon.WrapInText(self._select_from_list,self,'select objects from list'), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, ClientGUICommon.WrapInText( self._select_from_list, self, 'select objects from list' ), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
|
||||
self.widget().setLayout( vbox )
|
||||
|
||||
|
@ -2960,7 +2959,7 @@ class ReviewLocalFileImports( ClientGUIScrolledPanels.ReviewPanel ):
|
|||
self._pause_event = threading.Event()
|
||||
self._cancel_event = threading.Event()
|
||||
|
||||
self._progress_updater = ClientGUICommon.ThreadToGUIUpdater( self._progress, self._progress.SetValue )
|
||||
self._progress_updater = ClientGUIAsync.FastThreadToGUIUpdater( self._progress, self._progress.SetValue )
|
||||
|
||||
if len( paths ) > 0:
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ def GetSafePosition( position ):
|
|||
else:
|
||||
|
||||
display_index = None
|
||||
|
||||
|
||||
if display_index is None:
|
||||
|
||||
|
@ -56,6 +57,8 @@ def GetSafeSize( tlw, min_size, gravity ):
|
|||
|
||||
( min_width, min_height ) = min_size
|
||||
|
||||
frame_padding = tlw.frameGeometry().size() - tlw.size()
|
||||
|
||||
parent = tlw.parentWidget()
|
||||
|
||||
if parent is None:
|
||||
|
@ -65,7 +68,21 @@ def GetSafeSize( tlw, min_size, gravity ):
|
|||
|
||||
else:
|
||||
|
||||
( parent_window_width, parent_window_height ) = parent.window().size().toTuple()
|
||||
parent_window = parent.window()
|
||||
|
||||
# when we initialise, we might not have a frame yet because we haven't done show() yet
|
||||
# so borrow parent's
|
||||
if frame_padding.isEmpty():
|
||||
|
||||
frame_padding = parent_window.frameGeometry().size() - parent_window.size()
|
||||
|
||||
|
||||
parent_frame_size = parent_window.frameGeometry().size()
|
||||
|
||||
parent_available_size = parent_frame_size - frame_padding
|
||||
|
||||
parent_available_width = parent_available_size.width()
|
||||
parent_available_height = parent_available_size.height()
|
||||
|
||||
( width_gravity, height_gravity ) = gravity
|
||||
|
||||
|
@ -75,7 +92,7 @@ def GetSafeSize( tlw, min_size, gravity ):
|
|||
|
||||
else:
|
||||
|
||||
max_width = parent_window_width - 2 * CHILD_POSITION_PADDING
|
||||
max_width = parent_available_width - ( 2 * CHILD_POSITION_PADDING )
|
||||
|
||||
width = int( width_gravity * max_width )
|
||||
|
||||
|
@ -86,7 +103,7 @@ def GetSafeSize( tlw, min_size, gravity ):
|
|||
|
||||
else:
|
||||
|
||||
max_height = parent_window_height - 2 * CHILD_POSITION_PADDING
|
||||
max_height = parent_available_height - ( 2 * CHILD_POSITION_PADDING )
|
||||
|
||||
height = int( height_gravity * max_height )
|
||||
|
||||
|
@ -94,8 +111,10 @@ def GetSafeSize( tlw, min_size, gravity ):
|
|||
|
||||
display_size = GetDisplaySize( tlw )
|
||||
|
||||
width = min( display_size.width(), width )
|
||||
height = min( display_size.height(), height )
|
||||
display_available_size = display_size - frame_padding
|
||||
|
||||
width = min( display_available_size.width() - 2 * CHILD_POSITION_PADDING, width )
|
||||
height = min( display_available_size.height() - 2 * CHILD_POSITION_PADDING, height )
|
||||
|
||||
return ( width, height )
|
||||
|
||||
|
@ -226,14 +245,14 @@ def SetInitialTLWSizeAndPosition( tlw, frame_key ):
|
|||
|
||||
if isinstance( parent, QW.QWidget ):
|
||||
|
||||
parent_tlp = parent.window()
|
||||
parent_tlw = parent.window()
|
||||
|
||||
else:
|
||||
|
||||
parent_tlp = parent
|
||||
parent_tlw = parent
|
||||
|
||||
|
||||
( parent_x, parent_y ) = parent_tlp.pos().toTuple()
|
||||
( parent_x, parent_y ) = parent_tlw.pos().toTuple()
|
||||
|
||||
tlw.move( QP.TupleToQPoint( ( parent_x + CHILD_POSITION_PADDING, parent_y + CHILD_POSITION_PADDING ) ) )
|
||||
|
||||
|
@ -271,17 +290,19 @@ def SetInitialTLWSizeAndPosition( tlw, frame_key ):
|
|||
|
||||
def SlideOffScreenTLWUpAndLeft( tlw ):
|
||||
|
||||
( tlw_width, tlw_height ) = tlw.size().toTuple()
|
||||
( tlw_x, tlw_y ) = tlw.pos().toTuple()
|
||||
tlw_frame_rect = tlw.frameGeometry()
|
||||
|
||||
tlw_right = tlw_x + tlw_width
|
||||
tlw_bottom = tlw_y + tlw_height
|
||||
tlw_top_left = tlw_frame_rect.topLeft()
|
||||
tlw_bottom_right = tlw_frame_rect.bottomRight()
|
||||
|
||||
tlw_right = tlw_bottom_right.x()
|
||||
tlw_bottom = tlw_bottom_right.y()
|
||||
|
||||
display_size = GetDisplaySize( tlw )
|
||||
display_pos = GetDisplayPosition( tlw )
|
||||
|
||||
display_right = display_pos.x() + display_size.width()
|
||||
display_bottom = display_pos.y() + display_size.height()
|
||||
display_right = display_pos.x() + display_size.width() - CHILD_POSITION_PADDING
|
||||
display_bottom = display_pos.y() + display_size.height() - CHILD_POSITION_PADDING
|
||||
|
||||
move_x = tlw_right > display_right
|
||||
move_y = tlw_bottom > display_bottom
|
||||
|
@ -291,7 +312,11 @@ def SlideOffScreenTLWUpAndLeft( tlw ):
|
|||
delta_x = min( display_right - tlw_right, 0 )
|
||||
delta_y = min( display_bottom - tlw_bottom, 0 )
|
||||
|
||||
tlw.move( QP.TupleToQPoint( (tlw_x+delta_x,tlw_y+delta_y) ) )
|
||||
delta_point = QC.QPoint( delta_x, delta_y )
|
||||
|
||||
safe_pos = tlw_top_left + delta_point
|
||||
|
||||
tlw.move( safe_pos )
|
||||
|
||||
|
||||
class NewDialog( QP.Dialog ):
|
||||
|
|
|
@ -17,7 +17,9 @@ import urllib.parse
|
|||
|
||||
def AlphabetiseQueryText( query_text ):
|
||||
|
||||
return ConvertQueryDictToText( ConvertQueryTextToDict( query_text ) )
|
||||
( query_dict, param_order ) = ConvertQueryTextToDict( query_text )
|
||||
|
||||
return ConvertQueryDictToText( query_dict )
|
||||
|
||||
def ConvertDomainIntoAllApplicableDomains( domain, discard_www = True ):
|
||||
|
||||
|
@ -91,14 +93,21 @@ def ConvertHTTPToHTTPS( url ):
|
|||
raise Exception( 'Given a url that did not have a scheme!' )
|
||||
|
||||
|
||||
def ConvertQueryDictToText( query_dict ):
|
||||
def ConvertQueryDictToText( query_dict, param_order = None ):
|
||||
|
||||
# we now do everything with requests, which does all the unicode -> %20 business naturally, phew
|
||||
# we still want to call str explicitly to coerce integers and so on that'll slip in here and there
|
||||
|
||||
param_pairs = list( query_dict.items() )
|
||||
|
||||
param_pairs.sort()
|
||||
if param_order is None:
|
||||
|
||||
param_pairs = list( query_dict.items() )
|
||||
|
||||
param_pairs.sort()
|
||||
|
||||
else:
|
||||
|
||||
param_pairs = [ ( key, query_dict[ key ] ) for key in param_order if key in query_dict ]
|
||||
|
||||
|
||||
query_text = '&'.join( ( str( key ) + '=' + str( value ) for ( key, value ) in param_pairs ) )
|
||||
|
||||
|
@ -117,6 +126,8 @@ def ConvertQueryTextToDict( query_text ):
|
|||
# except these chars, which screw with GET arg syntax when unquoted
|
||||
bad_chars = [ '&', '=', '/', '?' ]
|
||||
|
||||
param_order = []
|
||||
|
||||
query_dict = {}
|
||||
|
||||
pairs = query_text.split( '&' )
|
||||
|
@ -169,11 +180,13 @@ def ConvertQueryTextToDict( query_text ):
|
|||
pass
|
||||
|
||||
|
||||
param_order.append( key )
|
||||
|
||||
query_dict[ key ] = value
|
||||
|
||||
|
||||
|
||||
return query_dict
|
||||
return ( query_dict, param_order )
|
||||
|
||||
def ConvertURLClassesIntoAPIPairs( url_classes ):
|
||||
|
||||
|
@ -2576,9 +2589,9 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_URL_CLASS
|
||||
SERIALISABLE_NAME = 'URL Class'
|
||||
SERIALISABLE_VERSION = 7
|
||||
SERIALISABLE_VERSION = 8
|
||||
|
||||
def __init__( self, name, url_class_key = None, url_type = None, preferred_scheme = 'https', netloc = 'hostname.com', match_subdomains = False, keep_matched_subdomains = False, path_components = None, parameters = None, api_lookup_converter = None, send_referral_url = SEND_REFERRAL_URL_ONLY_IF_PROVIDED, referral_url_converter = None, can_produce_multiple_files = False, should_be_associated_with_files = True, gallery_index_type = None, gallery_index_identifier = None, gallery_index_delta = 1, example_url = 'https://hostname.com/post/page.php?id=123456&s=view' ):
|
||||
def __init__( self, name, url_class_key = None, url_type = None, preferred_scheme = 'https', netloc = 'hostname.com', path_components = None, parameters = None, api_lookup_converter = None, send_referral_url = SEND_REFERRAL_URL_ONLY_IF_PROVIDED, referral_url_converter = None, gallery_index_type = None, gallery_index_identifier = None, gallery_index_delta = 1, example_url = 'https://hostname.com/post/page.php?id=123456&s=view' ):
|
||||
|
||||
if url_class_key is None:
|
||||
|
||||
|
@ -2628,10 +2641,11 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
self._preferred_scheme = preferred_scheme
|
||||
self._netloc = netloc
|
||||
|
||||
self._match_subdomains = match_subdomains
|
||||
self._keep_matched_subdomains = keep_matched_subdomains
|
||||
self._can_produce_multiple_files = can_produce_multiple_files
|
||||
self._should_be_associated_with_files = should_be_associated_with_files
|
||||
self._match_subdomains = False
|
||||
self._keep_matched_subdomains = False
|
||||
self._alphabetise_get_parameters = True
|
||||
self._can_produce_multiple_files = False
|
||||
self._should_be_associated_with_files = True
|
||||
|
||||
self._path_components = path_components
|
||||
self._parameters = parameters
|
||||
|
@ -2717,14 +2731,14 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
def _ClipAndFleshOutQuery( self, query, allow_clip = True ):
|
||||
|
||||
query_dict = ConvertQueryTextToDict( query )
|
||||
( query_dict, param_order ) = ConvertQueryTextToDict( query )
|
||||
|
||||
if allow_clip:
|
||||
|
||||
query_dict = { key : value for ( key, value ) in list(query_dict.items()) if key in self._parameters }
|
||||
query_dict = { key : value for ( key, value ) in query_dict.items() if key in self._parameters }
|
||||
|
||||
|
||||
for ( key, ( string_match, default ) ) in list(self._parameters.items()):
|
||||
for ( key, ( string_match, default ) ) in self._parameters.items():
|
||||
|
||||
if key not in query_dict:
|
||||
|
||||
|
@ -2739,7 +2753,12 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
|
||||
|
||||
query = ConvertQueryDictToText( query_dict )
|
||||
if self._alphabetise_get_parameters:
|
||||
|
||||
param_order = None
|
||||
|
||||
|
||||
query = ConvertQueryDictToText( query_dict, param_order = param_order )
|
||||
|
||||
return query
|
||||
|
||||
|
@ -2752,12 +2771,12 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
serialisable_api_lookup_converter = self._api_lookup_converter.GetSerialisableTuple()
|
||||
serialisable_referral_url_converter = self._referral_url_converter.GetSerialisableTuple()
|
||||
|
||||
return ( serialisable_url_class_key, self._url_type, self._preferred_scheme, self._netloc, self._match_subdomains, self._keep_matched_subdomains, serialisable_path_components, serialisable_parameters, serialisable_api_lookup_converter, self._send_referral_url, serialisable_referral_url_converter, self._can_produce_multiple_files, self._should_be_associated_with_files, self._gallery_index_type, self._gallery_index_identifier, self._gallery_index_delta, self._example_url )
|
||||
return ( serialisable_url_class_key, self._url_type, self._preferred_scheme, self._netloc, self._match_subdomains, self._keep_matched_subdomains, self._alphabetise_get_parameters, serialisable_path_components, serialisable_parameters, serialisable_api_lookup_converter, self._send_referral_url, serialisable_referral_url_converter, self._can_produce_multiple_files, self._should_be_associated_with_files, self._gallery_index_type, self._gallery_index_identifier, self._gallery_index_delta, self._example_url )
|
||||
|
||||
|
||||
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
||||
|
||||
( serialisable_url_class_key, self._url_type, self._preferred_scheme, self._netloc, self._match_subdomains, self._keep_matched_subdomains, serialisable_path_components, serialisable_parameters, serialisable_api_lookup_converter, self._send_referral_url, serialisable_referral_url_converter, self._can_produce_multiple_files, self._should_be_associated_with_files, self._gallery_index_type, self._gallery_index_identifier, self._gallery_index_delta, self._example_url ) = serialisable_info
|
||||
( serialisable_url_class_key, self._url_type, self._preferred_scheme, self._netloc, self._match_subdomains, self._keep_matched_subdomains, self._alphabetise_get_parameters, serialisable_path_components, serialisable_parameters, serialisable_api_lookup_converter, self._send_referral_url, serialisable_referral_url_converter, self._can_produce_multiple_files, self._should_be_associated_with_files, self._gallery_index_type, self._gallery_index_identifier, self._gallery_index_delta, self._example_url ) = serialisable_info
|
||||
|
||||
self._url_class_key = bytes.fromhex( serialisable_url_class_key )
|
||||
self._path_components = [ ( HydrusSerialisable.CreateFromSerialisableTuple( serialisable_string_match ), default ) for ( serialisable_string_match, default ) in serialisable_path_components ]
|
||||
|
@ -2859,6 +2878,22 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
return ( 7, new_serialisable_info )
|
||||
|
||||
|
||||
if version == 7:
|
||||
|
||||
( serialisable_url_class_key, url_type, preferred_scheme, netloc, match_subdomains, keep_matched_subdomains, serialisable_path_components, serialisable_parameters, serialisable_api_lookup_converter, send_referral_url, serialisable_referrel_url_converter, can_produce_multiple_files, should_be_associated_with_files, gallery_index_type, gallery_index_identifier, gallery_index_delta, example_url ) = old_serialisable_info
|
||||
|
||||
alphabetise_get_parameters = True
|
||||
|
||||
new_serialisable_info = ( serialisable_url_class_key, url_type, preferred_scheme, netloc, match_subdomains, keep_matched_subdomains, alphabetise_get_parameters, serialisable_path_components, serialisable_parameters, serialisable_api_lookup_converter, send_referral_url, serialisable_referrel_url_converter, can_produce_multiple_files, should_be_associated_with_files, gallery_index_type, gallery_index_identifier, gallery_index_delta, example_url )
|
||||
|
||||
return ( 8, new_serialisable_info )
|
||||
|
||||
|
||||
|
||||
def AlphabetiseGetParameters( self ):
|
||||
|
||||
return self._alphabetise_get_parameters
|
||||
|
||||
|
||||
def CanGenerateNextGalleryPage( self ):
|
||||
|
||||
|
@ -2969,7 +3004,7 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
page_index_name = self._gallery_index_identifier
|
||||
|
||||
query_dict = ConvertQueryTextToDict( query )
|
||||
( query_dict, param_order ) = ConvertQueryTextToDict( query )
|
||||
|
||||
if page_index_name not in query_dict:
|
||||
|
||||
|
@ -2989,7 +3024,12 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
query_dict[ page_index_name ] = page_index + self._gallery_index_delta
|
||||
|
||||
query = ConvertQueryDictToText( query_dict )
|
||||
if self._alphabetise_get_parameters:
|
||||
|
||||
param_order = None
|
||||
|
||||
|
||||
query = ConvertQueryDictToText( query_dict, param_order = param_order )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -3043,6 +3083,11 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
return 'URL Class "' + self._name + '" - ' + ConvertURLIntoDomain( self.GetExampleURL() )
|
||||
|
||||
|
||||
def GetURLBooleans( self ):
|
||||
|
||||
return ( self._match_subdomains, self._keep_matched_subdomains, self._alphabetise_get_parameters, self._can_produce_multiple_files, self._should_be_associated_with_files )
|
||||
|
||||
|
||||
def GetURLType( self ):
|
||||
|
||||
return self._url_type
|
||||
|
@ -3132,6 +3177,15 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
self._url_class_key = match_key
|
||||
|
||||
|
||||
def SetURLBooleans( self, match_subdomains, keep_matched_subdomains, alphabetise_get_parameters, can_produce_multiple_files, should_be_associated_with_files ):
|
||||
|
||||
self._match_subdomains = match_subdomains
|
||||
self._keep_matched_subdomains = keep_matched_subdomains
|
||||
self._alphabetise_get_parameters = alphabetise_get_parameters
|
||||
self._can_produce_multiple_files = can_produce_multiple_files
|
||||
self._should_be_associated_with_files = should_be_associated_with_files
|
||||
|
||||
|
||||
def ShouldAssociateWithFiles( self ):
|
||||
|
||||
return self._should_be_associated_with_files
|
||||
|
@ -3186,7 +3240,7 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
|
||||
|
||||
url_parameters = ConvertQueryTextToDict( p.query )
|
||||
( url_parameters, param_order ) = ConvertQueryTextToDict( p.query )
|
||||
|
||||
for ( key, ( string_match, default ) ) in list(self._parameters.items()):
|
||||
|
||||
|
@ -3217,7 +3271,7 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
def ToTuple( self ):
|
||||
|
||||
return ( self._url_type, self._preferred_scheme, self._netloc, self._match_subdomains, self._keep_matched_subdomains, self._path_components, self._parameters, self._api_lookup_converter, self._send_referral_url, self._referral_url_converter, self._can_produce_multiple_files, self._should_be_associated_with_files, self._example_url )
|
||||
return ( self._url_type, self._preferred_scheme, self._netloc, self._path_components, self._parameters, self._api_lookup_converter, self._send_referral_url, self._referral_url_converter, self._example_url )
|
||||
|
||||
|
||||
def UsesAPIURL( self ):
|
||||
|
|
|
@ -136,6 +136,7 @@ class NetworkJob( object ):
|
|||
self._bandwidth_tracker = HydrusNetworking.BandwidthTracker()
|
||||
|
||||
self._connection_error_wake_time = 0
|
||||
self._serverside_bandwidth_wake_time = 0
|
||||
|
||||
self._wake_time = 0
|
||||
|
||||
|
@ -507,7 +508,9 @@ class NetworkJob( object ):
|
|||
|
||||
def _WaitOnConnectionError( self, status_text ):
|
||||
|
||||
self._connection_error_wake_time = HydrusData.GetNow() + ( ( self._current_connection_attempt_number - 1 ) * 10 )
|
||||
connection_error_wait_time = HG.client_controller.new_options.GetInteger( 'connection_error_wait_time' )
|
||||
|
||||
self._connection_error_wake_time = HydrusData.GetNow() + ( ( self._current_connection_attempt_number - 1 ) * connection_error_wait_time )
|
||||
|
||||
while not HydrusData.TimeHasPassed( self._connection_error_wake_time ) and not self._IsCancelled():
|
||||
|
||||
|
@ -528,6 +531,26 @@ class NetworkJob( object ):
|
|||
|
||||
|
||||
|
||||
def _WaitOnServersideBandwidth( self, status_text ):
|
||||
|
||||
# 429 or 509 response from server. basically means 'I'm under big load mate'
|
||||
# a future version of this could def talk to domain manager and add a temp delay so other network jobs can be informed
|
||||
|
||||
serverside_bandwidth_wait_time = HG.client_controller.new_options.GetInteger( 'serverside_bandwidth_wait_time' )
|
||||
|
||||
self._serverside_bandwidth_wake_time = HydrusData.GetNow() + ( ( self._current_connection_attempt_number - 1 ) * serverside_bandwidth_wait_time )
|
||||
|
||||
while not HydrusData.TimeHasPassed( self._serverside_bandwidth_wake_time ) and not self._IsCancelled():
|
||||
|
||||
with self._lock:
|
||||
|
||||
self._status_text = status_text + ' - retrying in {}'.format( HydrusData.TimestampToPrettyTimeDelta( self._serverside_bandwidth_wake_time ) )
|
||||
|
||||
|
||||
time.sleep( 1 )
|
||||
|
||||
|
||||
|
||||
def AddAdditionalHeader( self, key, value ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -655,6 +678,14 @@ class NetworkJob( object ):
|
|||
|
||||
|
||||
|
||||
def CurrentlyWaitingOnServersideBandwidth( self ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
return not HydrusData.TimeHasPassed( self._serverside_bandwidth_wake_time )
|
||||
|
||||
|
||||
|
||||
def GenerateLoginProcess( self ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -902,6 +933,14 @@ class NetworkJob( object ):
|
|||
|
||||
|
||||
|
||||
def OverrideServersideBandwidthWait( self ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
self._serverside_bandwidth_wake_time = 0
|
||||
|
||||
|
||||
|
||||
def OverrideToken( self ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -1075,10 +1114,10 @@ class NetworkJob( object ):
|
|||
|
||||
if not self._CanReattemptRequest():
|
||||
|
||||
raise HydrusExceptions.NetworkException( 'Ran out of bandwidth: ' + str( e ) )
|
||||
raise HydrusExceptions.NetworkException( 'Server reported very limited bandwidth: ' + str( e ) )
|
||||
|
||||
|
||||
self._WaitOnConnectionError( 'server reported limited bandwidth' )
|
||||
self._WaitOnServersideBandwidth( 'server reported limited bandwidth' )
|
||||
|
||||
except HydrusExceptions.ShouldReattemptNetworkException as e:
|
||||
|
||||
|
|
|
@ -203,6 +203,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
self._dictionary[ 'integers' ][ 'page_file_count_display' ] = CC.PAGE_FILE_COUNT_DISPLAY_ALL
|
||||
|
||||
self._dictionary[ 'integers' ][ 'network_timeout' ] = 10
|
||||
self._dictionary[ 'integers' ][ 'connection_error_wait_time' ] = 15
|
||||
self._dictionary[ 'integers' ][ 'serverside_bandwidth_wait_time' ] = 60
|
||||
|
||||
self._dictionary[ 'integers' ][ 'thumbnail_visibility_scroll_percent' ] = 75
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 18
|
||||
SOFTWARE_VERSION = 374
|
||||
SOFTWARE_VERSION = 375
|
||||
CLIENT_API_VERSION = 11
|
||||
|
||||
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
|
|
@ -370,7 +370,7 @@ def SetEnvTempDir( path ):
|
|||
|
||||
if tmp_name in os.environ:
|
||||
|
||||
os.environ[ tmp_name ] = path
|
||||
os.putenv( tmp_name, path )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -27,9 +27,11 @@ class HydrusPubSub( object ):
|
|||
self._topics_to_method_names = {}
|
||||
|
||||
|
||||
def _GetCallables( self, topic ):
|
||||
def _GetCallableTuples( self, topic ):
|
||||
|
||||
callables = []
|
||||
# this now does the obj as well so we have a strong direct ref to it throughout procesing
|
||||
|
||||
callable_tuples = []
|
||||
|
||||
if topic in self._topics_to_objects:
|
||||
|
||||
|
@ -52,7 +54,7 @@ class HydrusPubSub( object ):
|
|||
|
||||
callable = getattr( obj, method_name )
|
||||
|
||||
callables.append( callable )
|
||||
callable_tuples.append( ( obj, callable ) )
|
||||
|
||||
|
||||
|
||||
|
@ -63,7 +65,7 @@ class HydrusPubSub( object ):
|
|||
|
||||
|
||||
|
||||
return callables
|
||||
return callable_tuples
|
||||
|
||||
|
||||
def DoingWork( self ):
|
||||
|
@ -84,7 +86,7 @@ class HydrusPubSub( object ):
|
|||
|
||||
try:
|
||||
|
||||
callables = []
|
||||
callable_tuples = []
|
||||
|
||||
with self._lock:
|
||||
|
||||
|
@ -104,25 +106,25 @@ class HydrusPubSub( object ):
|
|||
|
||||
# do all this _outside_ the lock, lol
|
||||
|
||||
callables = self._GetCallables( topic )
|
||||
callable_tuples = self._GetCallableTuples( topic )
|
||||
|
||||
# don't want to report the showtext we just send here!
|
||||
not_a_report = topic != 'message'
|
||||
|
||||
if HG.pubsub_report_mode and not_a_report:
|
||||
|
||||
HydrusData.ShowText( ( topic, args, kwargs, callables ) )
|
||||
HydrusData.ShowText( ( topic, args, kwargs, callable_tuples ) )
|
||||
|
||||
|
||||
if HG.pubsub_profile_mode and not_a_report:
|
||||
|
||||
summary = 'Profiling ' + HydrusData.ToHumanInt( len( callables ) ) + ' x ' + topic
|
||||
summary = 'Profiling ' + HydrusData.ToHumanInt( len( callable_tuples ) ) + ' x ' + topic
|
||||
|
||||
HydrusData.ShowText( summary )
|
||||
|
||||
per_summary = 'Profiling ' + topic
|
||||
|
||||
for callable in callables:
|
||||
for ( obj, callable ) in callable_tuples:
|
||||
|
||||
try:
|
||||
|
||||
|
@ -136,7 +138,7 @@ class HydrusPubSub( object ):
|
|||
|
||||
else:
|
||||
|
||||
for callable in callables:
|
||||
for ( obj, callable ) in callable_tuples:
|
||||
|
||||
try:
|
||||
|
||||
|
@ -175,10 +177,10 @@ class HydrusPubSub( object ):
|
|||
|
||||
with self._lock:
|
||||
|
||||
callables = self._GetCallables( topic )
|
||||
callable_tuples = self._GetCallableTuples( topic )
|
||||
|
||||
|
||||
for callable in callables:
|
||||
for ( obj, callable ) in callable_tuples:
|
||||
|
||||
callable( *args, **kwargs )
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ if not 'QT_API' in os.environ:
|
|||
|
||||
import PySide2
|
||||
|
||||
os.environ[ 'QT_API' ] = 'pyside2'
|
||||
os.putenv( 'QT_API', 'pyside2' )
|
||||
|
||||
except ImportError:
|
||||
|
||||
|
@ -875,7 +875,7 @@ class GridLayout( QW.QGridLayout ):
|
|||
|
||||
self.setContentsMargins( val, val, val, val )
|
||||
|
||||
|
||||
|
||||
def AddToLayout( layout, item, flag = None, alignment = None, sizePolicy = None ):
|
||||
|
||||
if isinstance( layout, GridLayout ):
|
||||
|
@ -2059,12 +2059,12 @@ class PasswordEntryDialog( Dialog ):
|
|||
|
||||
self.layout().addLayout( entry_layout )
|
||||
self.layout().addLayout( button_layout )
|
||||
|
||||
|
||||
|
||||
def GetValue( self ):
|
||||
|
||||
return self._password.text()
|
||||
|
||||
|
||||
|
||||
class DirDialog( QW.QFileDialog ):
|
||||
|
||||
|
|
|
@ -870,7 +870,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
# some
|
||||
|
||||
url = 'http://safebooru.org/index.php?page=post&s=view&id=2753608'
|
||||
url = 'http://safebooru.org/index.php?s=view&page=post&id=2753608'
|
||||
normalised_url = 'https://safebooru.org/index.php?id=2753608&page=post&s=view'
|
||||
|
||||
hash = os.urandom( 32 )
|
||||
|
@ -899,6 +899,9 @@ class TestClientAPI( unittest.TestCase ):
|
|||
expected_answer[ 'normalised_url' ] = normalised_url
|
||||
expected_answer[ 'url_file_statuses' ] = json_url_file_statuses
|
||||
|
||||
HydrusData.Print( d )
|
||||
HydrusData.Print( expected_answer )
|
||||
|
||||
self.assertEqual( d, expected_answer )
|
||||
|
||||
# get url info
|
||||
|
|
|
@ -229,8 +229,12 @@ class TestNetworkingDomain( unittest.TestCase ):
|
|||
url_type = HC.URL_TYPE_POST
|
||||
preferred_scheme = 'https'
|
||||
netloc = 'testbooru.cx'
|
||||
|
||||
alphabetise_get_parameters = True
|
||||
match_subdomains = False
|
||||
keep_matched_subdomains = False
|
||||
can_produce_multiple_files = False
|
||||
should_be_associated_with_files = True
|
||||
|
||||
path_components = []
|
||||
|
||||
|
@ -244,8 +248,6 @@ class TestNetworkingDomain( unittest.TestCase ):
|
|||
|
||||
send_referral_url = ClientNetworkingDomain.SEND_REFERRAL_URL_ONLY_IF_PROVIDED
|
||||
referral_url_converter = None
|
||||
can_produce_multiple_files = False
|
||||
should_be_associated_with_files = True
|
||||
gallery_index_type = None
|
||||
gallery_index_identifier = None
|
||||
gallery_index_delta = 1
|
||||
|
@ -259,7 +261,9 @@ class TestNetworkingDomain( unittest.TestCase ):
|
|||
unnormalised_good_url_2 = 'https://testbooru.cx/post/page.php?s=view&id=123456'
|
||||
bad_url = 'https://wew.lad/123456'
|
||||
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, match_subdomains = match_subdomains, keep_matched_subdomains = keep_matched_subdomains, path_components = path_components, parameters = parameters, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, can_produce_multiple_files = can_produce_multiple_files, should_be_associated_with_files = should_be_associated_with_files, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, path_components = path_components, parameters = parameters, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
|
||||
url_class.SetURLBooleans( match_subdomains, keep_matched_subdomains, alphabetise_get_parameters, can_produce_multiple_files, should_be_associated_with_files )
|
||||
|
||||
self.assertEqual( url_class.Matches( example_url ), True )
|
||||
self.assertEqual( url_class.Matches( bad_url ), False )
|
||||
|
@ -272,9 +276,23 @@ class TestNetworkingDomain( unittest.TestCase ):
|
|||
|
||||
#
|
||||
|
||||
alphabetise_get_parameters = False
|
||||
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, path_components = path_components, parameters = parameters, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
|
||||
url_class.SetURLBooleans( match_subdomains, keep_matched_subdomains, alphabetise_get_parameters, can_produce_multiple_files, should_be_associated_with_files )
|
||||
|
||||
self.assertEqual( url_class.Normalise( unnormalised_good_url_2 ), unnormalised_good_url_2 )
|
||||
|
||||
alphabetise_get_parameters = True
|
||||
|
||||
#
|
||||
|
||||
send_referral_url = ClientNetworkingDomain.SEND_REFERRAL_URL_NEVER
|
||||
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, match_subdomains = match_subdomains, keep_matched_subdomains = keep_matched_subdomains, path_components = path_components, parameters = parameters, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, can_produce_multiple_files = can_produce_multiple_files, should_be_associated_with_files = should_be_associated_with_files, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, path_components = path_components, parameters = parameters, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
|
||||
url_class.SetURLBooleans( match_subdomains, keep_matched_subdomains, alphabetise_get_parameters, can_produce_multiple_files, should_be_associated_with_files )
|
||||
|
||||
self.assertEqual( url_class.GetReferralURL( good_url, referral_url ), None )
|
||||
self.assertEqual( url_class.GetReferralURL( good_url, None ), None )
|
||||
|
@ -291,7 +309,9 @@ class TestNetworkingDomain( unittest.TestCase ):
|
|||
|
||||
send_referral_url = ClientNetworkingDomain.SEND_REFERRAL_URL_CONVERTER_IF_NONE_PROVIDED
|
||||
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, match_subdomains = match_subdomains, keep_matched_subdomains = keep_matched_subdomains, path_components = path_components, parameters = parameters, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, can_produce_multiple_files = can_produce_multiple_files, should_be_associated_with_files = should_be_associated_with_files, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, path_components = path_components, parameters = parameters, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
|
||||
url_class.SetURLBooleans( match_subdomains, keep_matched_subdomains, alphabetise_get_parameters, can_produce_multiple_files, should_be_associated_with_files )
|
||||
|
||||
self.assertEqual( url_class.GetReferralURL( good_url, referral_url ), referral_url )
|
||||
self.assertEqual( url_class.GetReferralURL( good_url, None ), converted_referral_url )
|
||||
|
@ -300,7 +320,9 @@ class TestNetworkingDomain( unittest.TestCase ):
|
|||
|
||||
send_referral_url = ClientNetworkingDomain.SEND_REFERRAL_URL_ONLY_CONVERTER
|
||||
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, match_subdomains = match_subdomains, keep_matched_subdomains = keep_matched_subdomains, path_components = path_components, parameters = parameters, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, can_produce_multiple_files = can_produce_multiple_files, should_be_associated_with_files = should_be_associated_with_files, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
url_class = ClientNetworkingDomain.URLClass( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, path_components = path_components, parameters = parameters, send_referral_url = send_referral_url, referral_url_converter = referral_url_converter, gallery_index_type = gallery_index_type, gallery_index_identifier = gallery_index_identifier, gallery_index_delta = gallery_index_delta, example_url = example_url )
|
||||
|
||||
url_class.SetURLBooleans( match_subdomains, keep_matched_subdomains, alphabetise_get_parameters, can_produce_multiple_files, should_be_associated_with_files )
|
||||
|
||||
self.assertEqual( url_class.GetReferralURL( good_url, referral_url ), converted_referral_url )
|
||||
self.assertEqual( url_class.GetReferralURL( good_url, None ), converted_referral_url )
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.9 KiB |
Loading…
Reference in New Issue