Version 375

This commit is contained in:
Hydrus Network Developer 2019-11-27 19:11:46 -06:00
parent 4d4f39984e
commit d18410121e
35 changed files with 2105 additions and 1404 deletions

View File

@ -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>

View File

@ -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 )

View File

@ -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 ):

View File

@ -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, ) )

View File

@ -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 )

View File

@ -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 ) )

View File

@ -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

View File

@ -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 )

File diff suppressed because it is too large Load Diff

View File

@ -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 )

211
include/ClientGUIAsync.py Normal file
View File

@ -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 )

View File

@ -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:

View File

@ -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 )

View File

@ -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 )

View File

@ -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 )

View File

@ -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 ):

View File

@ -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

View File

@ -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 )

View File

@ -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()

View File

@ -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 )

View File

@ -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 ):

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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 ):

View File

@ -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 ):

View File

@ -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:

View File

@ -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

View File

@ -67,7 +67,7 @@ options = {}
# Misc
NETWORK_VERSION = 18
SOFTWARE_VERSION = 374
SOFTWARE_VERSION = 375
CLIENT_API_VERSION = 11
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -370,7 +370,7 @@ def SetEnvTempDir( path ):
if tmp_name in os.environ:
os.environ[ tmp_name ] = path
os.putenv( tmp_name, path )

View File

@ -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 )

View File

@ -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 ):

View File

@ -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

View File

@ -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