Version 353

This commit is contained in:
Hydrus Network Developer 2019-05-22 17:35:06 -05:00
parent 93e8578e99
commit 4dcdc1e324
41 changed files with 4149 additions and 3285 deletions

View File

@ -8,6 +8,52 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 353</h3></li>
<ul>
<li>duplicate filter:</li>
<li>duplicate action options no longer handle file deletion</li>
<li>renamed 'not duplicates' across the program to 'not related' or 'false positive'</li>
<li>'alternates' and 'not related/false positive' duplicate actions no longer have duplicate action options. no merge content update now occurs on these actions</li>
<li>the duplicate filter hover panel now splits 'this is better' decisions into two buttons--whether to delete or keep the worse file</li>
<li>when selecting 'custom action' in the duplicate filter hover panel, it now asks if you would like to delete the current file, the other file, or both</li>
<li>the 'duplicate_filter_this_is_better' shortcut action will be auto-updated to 'duplicate_filter_this_is_better_and_delete_other'. an alternate 'duplicate_filter_this_is_better_but_keep_both' is now also available</li>
<li>the 'duplicate_filter_not_dupes' shortcut action will be auto-updated to 'duplicate_filter_false_positive'</li>
<li>separated the buttons on the duplicate filter hover panel to more carefully split 'yes, files are duplicates' vs other decisions</li>
<li>in prep for the duplicate db overhaul, refactored all PHash search code and Duplicate management code apart</li>
<li>misc other prep work for duplicate db overhaul</li>
<li>.</li>
<li>file maintenance:</li>
<li>wrote a new unified manager to handle various long-term file maintenance tasks like regenerating file metadata and thumbnails</li>
<li>options to govern how this manager can run are now in options->maintenance and processing. you can enable it for idle and shutdown maintenance time and give it a throttle to limit how fast it will work on files, defaulting to 200 per day</li>
<li>unified the previous db-level attempts at file maintenance to the new system, which supports async job queueing, and moving regen code up to the new manager, out of the db lock</li>
<li>unified a variety of file and thumbnail regen code to work through the new simpler and saner path</li>
<li>the right-click->regen thumbnail commands now run through the new manager and no longer need a modal popup. you can keep browsing while they work. they will also not hang the ui as the old system could on big jobs</li>
<li>when right-click->regenning on more than 50 thumbnails, you now get a dialog asking if you want to do the job now or put it off later</li>
<li>file maintenance tasks can now run in shutdown time! you will get previews of the jobs with file counts and status progress reports on the shutdown splash</li>
<li>cleaned up some file extension renaming and dupe-removing code</li>
<li>in future, I will move the current file integrity check to this new system and have some ui to prompt and set up other big jobs, like fixing various historical misparsing issues</li>
<li>thumbnail resizing during thumbnail fade that resizes down is now more efficient</li>
<li>moved the ClientFilesManager to ClientFiles.py</li>
<li>.</li>
<li>the rest:</li>
<li>the 'manage upnp' dialog now moves the duplicated external ip display from the column up to the status text at the top. it fetches the ip after the initial mappings fetch is done. this ip is no longer affected by the external host override option</li>
<li>cleaned up options->connection page and removed the now defunct external host override option</li>
<li>the manage services page for the local booru now has optional override for scheme, host, and port for the 'copy external url' function</li>
<li>fixed an issue with the recent 'collect by' session saving where a restored session that needed a collect was not sorted</li>
<li>fixed an issue with collections being sorted by approx bitrate</li>
<li>added a new checkbox to options->sort/collect to set it so the default sort updates every time you choose a new sort anywhere</li>
<li>fixed an issue with 'remove trashed files from view', which was incorrectly removing on 'all local files' pages</li>
<li>the 'all local files' file domain, which is frequently confusing to new users, is now no longer an option for new file pages or the autocomplete file domain if the user is not in advanced mode</li>
<li>the client now searches for versions of urls both with and without a final '/' character when looking up file url import status at the db level and in import lists. system:known_url is unfortunately still an inefficient mess</li>
<li>improved how the server code deals with some connectionLost errors</li>
<li>cleaned up and unified some older dialog button code</li>
<li>fixed a problem in manage tag siblings when petitioning existing pairs and then cancelling when asked for a reason</li>
<li>fixed a miscount issue when uploading pending tags while many new tags are coming in. progress would sometimes be -754/1,234, ha ha</li>
<li>db maintenance, repository sync, and file maintenance processing will all now wake on a force idle mode call</li>
<li>deleted some old code</li>
<li>misc fixes and cleanup</li>
<li>some misc gui layout fixes</li>
</ul>
<li><h3>version 352</h3></li>
<ul>
<li>the client now supports importing .ico files! (.cur should be supported too)</li>

File diff suppressed because it is too large Load Diff

View File

@ -349,11 +349,11 @@ SHORTCUTS_RESERVED_NAMES = [ 'archive_delete_filter', 'duplicate_filter', 'media
# shortcut commands
SHORTCUTS_MEDIA_ACTIONS = [ 'manage_file_tags', 'manage_file_ratings', 'manage_file_urls', 'manage_file_notes', 'archive_file', 'inbox_file', 'delete_file', 'export_files', 'export_files_quick_auto_export', 'remove_file_from_view', 'open_file_in_external_program', 'open_selection_in_new_page', 'launch_the_archive_delete_filter', 'copy_bmp', 'copy_file', 'copy_path', 'copy_sha256_hash', 'get_similar_to_exact', 'get_similar_to_very_similar', 'get_similar_to_similar', 'get_similar_to_speculative', 'duplicate_media_remove_relationships', 'duplicate_media_reset_to_potential', 'duplicate_media_set_alternate', 'duplicate_media_set_alternate_collections', 'duplicate_media_set_custom', 'duplicate_media_set_focused_better', 'duplicate_media_set_not_duplicate', 'duplicate_media_set_same_quality', 'open_known_url' ]
SHORTCUTS_MEDIA_ACTIONS = [ 'manage_file_tags', 'manage_file_ratings', 'manage_file_urls', 'manage_file_notes', 'archive_file', 'inbox_file', 'delete_file', 'export_files', 'export_files_quick_auto_export', 'remove_file_from_view', 'open_file_in_external_program', 'open_selection_in_new_page', 'launch_the_archive_delete_filter', 'copy_bmp', 'copy_file', 'copy_path', 'copy_sha256_hash', 'get_similar_to_exact', 'get_similar_to_very_similar', 'get_similar_to_similar', 'get_similar_to_speculative', 'duplicate_media_remove_relationships', 'duplicate_media_reset_to_potential', 'duplicate_media_set_alternate', 'duplicate_media_set_alternate_collections', 'duplicate_media_set_custom', 'duplicate_media_set_focused_better', 'duplicate_media_set_false_positive', 'duplicate_media_set_same_quality', 'open_known_url' ]
SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ 'move_animation_to_previous_frame', 'move_animation_to_next_frame', 'switch_between_fullscreen_borderless_and_regular_framed_window', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'zoom_in', 'zoom_out', 'switch_between_100_percent_and_canvas_zoom', 'flip_darkmode' ]
SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS = [ 'view_next', 'view_first', 'view_last', 'view_previous' ]
SHORTCUTS_MAIN_GUI_ACTIONS = [ 'refresh', 'new_page', 'new_page_of_pages', 'new_duplicate_filter_page', 'new_gallery_downloader_page', 'new_url_downloader_page', 'new_simple_downloader_page', 'new_watcher_downloader_page', 'synchronised_wait_switch', 'set_media_focus', 'show_hide_splitters', 'set_search_focus', 'unclose_page', 'close_page', 'redo', 'undo', 'flip_darkmode', 'check_all_import_folders', 'flip_debug_force_idle_mode_do_not_set_this' ]
SHORTCUTS_DUPLICATE_FILTER_ACTIONS = [ 'duplicate_filter_this_is_better', 'duplicate_filter_exactly_the_same', 'duplicate_filter_alternates', 'duplicate_filter_not_dupes', 'duplicate_filter_custom_action', 'duplicate_filter_skip', 'duplicate_filter_back' ]
SHORTCUTS_DUPLICATE_FILTER_ACTIONS = [ 'duplicate_filter_this_is_better_and_delete_other', 'duplicate_filter_this_is_better_but_keep_both', 'duplicate_filter_exactly_the_same', 'duplicate_filter_alternates', 'duplicate_filter_false_positive', 'duplicate_filter_custom_action', 'duplicate_filter_skip', 'duplicate_filter_back' ]
SHORTCUTS_ARCHIVE_DELETE_FILTER_ACTIONS = [ 'archive_delete_filter_keep', 'archive_delete_filter_delete', 'archive_delete_filter_skip', 'archive_delete_filter_back' ]
simple_shortcut_name_to_action_lookup = {}

View File

@ -16,6 +16,7 @@ from . import ClientCaches
from . import ClientData
from . import ClientDaemons
from . import ClientDefaults
from . import ClientFiles
from . import ClientGUICommon
from . import ClientGUIMenus
from . import ClientNetworking
@ -428,6 +429,11 @@ class Controller( HydrusController.HydrusController ):
if self.new_options.GetBoolean( 'file_maintenance_on_shutdown' ):
self.files_maintenance_manager.DoMaintenance( only_when_idle = False, stop_time = stop_time )
self.Write( 'last_shutdown_work_time', HydrusData.GetNow() )
@ -565,6 +571,11 @@ class Controller( HydrusController.HydrusController ):
if self.new_options.GetBoolean( 'file_maintenance_on_shutdown' ):
work_to_do.extend( self.files_maintenance_manager.GetIdleShutdownWorkDue() )
return work_to_do
@ -585,7 +596,7 @@ class Controller( HydrusController.HydrusController ):
if dlg.ShowModal() == wx.ID_OK:
self.client_files_manager = ClientCaches.ClientFilesManager( self )
self.client_files_manager = ClientFiles.ClientFilesManager( self )
missing_locations = self.client_files_manager.GetMissing()
@ -598,7 +609,9 @@ class Controller( HydrusController.HydrusController ):
return missing_locations
self.client_files_manager = ClientCaches.ClientFilesManager( self )
self.client_files_manager = ClientFiles.ClientFilesManager( self )
self.files_maintenance_manager = ClientFiles.FilesMaintenanceManager( self )
missing_locations = self.client_files_manager.GetMissing()
@ -811,7 +824,7 @@ class Controller( HydrusController.HydrusController ):
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'DownloadFiles', ClientDaemons.DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) ) )
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'SynchroniseSubscriptions', ClientDaemons.DAEMONSynchroniseSubscriptions, ( 'notify_restart_subs_sync_daemon', 'notify_new_subscriptions' ), period = 4 * 3600, init_wait = 60, pre_call_wait = 3 ) )
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'MaintainTrash', ClientDaemons.DAEMONMaintainTrash, init_wait = 120 ) )
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'SynchroniseRepositories', ClientDaemons.DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ), period = 4 * 3600, pre_call_wait = 1 ) )
self._daemons.append( HydrusThreading.DAEMONForegroundWorker( self, 'SynchroniseRepositories', ClientDaemons.DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions', 'wake_idle_workers' ), period = 4 * 3600, pre_call_wait = 1 ) )
job = self.CallRepeating( 5.0, 180.0, ClientDaemons.DAEMONCheckImportFolders )
@ -820,6 +833,11 @@ class Controller( HydrusController.HydrusController ):
job.ShouldDelayOnWakeup( True )
self._daemon_jobs[ 'import_folders' ] = job
job = self.CallRepeating( 60.0, 300.0, self.files_maintenance_manager.DoMaintenance )
job.ShouldDelayOnWakeup( True )
job.WakeOnPubSub( 'wake_idle_workers' )
self._daemon_jobs[ 'maintain_files' ] = job
job = self.CallRepeating( 5.0, 180.0, ClientDaemons.DAEMONCheckExportFolders )
job.WakeOnPubSub( 'notify_restart_export_folders_daemon' )
job.WakeOnPubSub( 'notify_new_export_folders' )
@ -906,12 +924,7 @@ class Controller( HydrusController.HydrusController ):
search_stop_time = HydrusData.GetNow() + 60
self.WriteSynchronous( 'maintain_similar_files_duplicate_pairs', search_distance, stop_time = search_stop_time, abandon_if_other_work_to_do = True )
if stop_time is None or not HydrusData.TimeHasPassed( stop_time ):
self.WriteSynchronous( 'maintain_file_reparsing', stop_time = stop_time )
self.WriteSynchronous( 'maintain_similar_files_search_for_potential_duplicates', search_distance, stop_time = search_stop_time, abandon_if_other_work_to_do = True )
if stop_time is None or not HydrusData.TimeHasPassed( stop_time ):

File diff suppressed because it is too large Load Diff

View File

@ -503,7 +503,7 @@ class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_APPLICATION_COMMAND
SERIALISABLE_NAME = 'Application Command'
SERIALISABLE_VERSION = 1
SERIALISABLE_VERSION = 2
def __init__( self, command_type = None, data = None ):
@ -560,6 +560,30 @@ class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
( command_type, serialisable_data ) = old_serialisable_info
if command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
if serialisable_data == 'duplicate_filter_this_is_better':
serialisable_data = 'duplicate_filter_this_is_better_and_delete_other'
elif serialisable_data == 'duplicate_filter_not_dupes':
serialisable_data = 'duplicate_filter_false_positive'
new_serialisable_info = ( command_type, serialisable_data )
return ( 2, new_serialisable_info )
def GetCommandType( self ):
return self._command_type

View File

@ -37,7 +37,6 @@ def GetClientDefaultOptions():
options[ 'trash_max_size' ] = 2048
options[ 'remove_trashed_files' ] = False
options[ 'remove_filtered_files' ] = False
options[ 'external_host' ] = None
options[ 'gallery_file_limit' ] = 2000
options[ 'always_embed_autocompletes' ] = HC.PLATFORM_LINUX or HC.PLATFORM_OSX
options[ 'confirm_trash' ] = True
@ -335,11 +334,11 @@ def GetDefaultShortcuts():
duplicate_filter = ClientGUIShortcuts.Shortcuts( 'duplicate_filter' )
duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better' ) )
duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better_and_delete_other' ) )
duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_RIGHT, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_alternates' ) )
duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_MIDDLE, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_back' ) )
duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_SPACE, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better' ) )
duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_SPACE, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better_and_delete_other' ) )
duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_UP, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_skip' ) )
duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_NUMPAD_UP, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_skip' ) )

View File

@ -10,9 +10,9 @@ class DuplicateActionOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_DUPLICATE_ACTION_OPTIONS
SERIALISABLE_NAME = 'Duplicate Action Options'
SERIALISABLE_VERSION = 3
SERIALISABLE_VERSION = 4
def __init__( self, tag_service_actions = None, rating_service_actions = None, delete_second_file = False, sync_archive = False, delete_both_files = False, sync_urls_action = None ):
def __init__( self, tag_service_actions = None, rating_service_actions = None, sync_archive = False, sync_urls_action = None ):
if tag_service_actions is None:
@ -28,9 +28,7 @@ class DuplicateActionOptions( HydrusSerialisable.SerialisableBase ):
self._tag_service_actions = tag_service_actions
self._rating_service_actions = rating_service_actions
self._delete_second_file = delete_second_file
self._sync_archive = sync_archive
self._delete_both_files = delete_both_files
self._sync_urls_action = sync_urls_action
@ -47,12 +45,12 @@ class DuplicateActionOptions( HydrusSerialisable.SerialisableBase ):
serialisable_tag_service_actions = [ ( service_key.hex(), action, tag_filter.GetSerialisableTuple() ) for ( service_key, action, tag_filter ) in self._tag_service_actions ]
serialisable_rating_service_actions = [ ( service_key.hex(), action ) for ( service_key, action ) in self._rating_service_actions ]
return ( serialisable_tag_service_actions, serialisable_rating_service_actions, self._delete_second_file, self._sync_archive, self._delete_both_files, self._sync_urls_action )
return ( serialisable_tag_service_actions, serialisable_rating_service_actions, self._sync_archive, self._sync_urls_action )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( serialisable_tag_service_actions, serialisable_rating_service_actions, self._delete_second_file, self._sync_archive, self._delete_both_files, self._sync_urls_action ) = serialisable_info
( serialisable_tag_service_actions, serialisable_rating_service_actions, self._sync_archive, self._sync_urls_action ) = serialisable_info
self._tag_service_actions = [ ( bytes.fromhex( serialisable_service_key ), action, HydrusSerialisable.CreateFromSerialisableTuple( serialisable_tag_filter ) ) for ( serialisable_service_key, action, serialisable_tag_filter ) in serialisable_tag_service_actions ]
self._rating_service_actions = [ ( bytes.fromhex( serialisable_service_key ), action ) for ( serialisable_service_key, action ) in serialisable_rating_service_actions ]
@ -104,42 +102,30 @@ class DuplicateActionOptions( HydrusSerialisable.SerialisableBase ):
return ( 3, new_serialisable_info )
def GetDeletedHashes( self, first_media, second_media ):
first_hashes = first_media.GetHashes()
second_hashes = second_media.GetHashes()
if self._delete_second_file:
if version == 3:
return second_hashes
( serialisable_tag_service_actions, serialisable_rating_service_actions, delete_second_file, sync_archive, delete_both_files, sync_urls_action ) = old_serialisable_info
elif self._delete_both_files:
new_serialisable_info = ( serialisable_tag_service_actions, serialisable_rating_service_actions, sync_archive, sync_urls_action )
return first_hashes.union( second_hashes )
else:
return set()
return ( 4, new_serialisable_info )
def SetTuple( self, tag_service_actions, rating_service_actions, delete_second_file, sync_archive, delete_both_files, sync_urls_action ):
def SetTuple( self, tag_service_actions, rating_service_actions, sync_archive, sync_urls_action ):
self._tag_service_actions = tag_service_actions
self._rating_service_actions = rating_service_actions
self._delete_second_file = delete_second_file
self._sync_archive = sync_archive
self._delete_both_files = delete_both_files
self._sync_urls_action = sync_urls_action
def ToTuple( self ):
return ( self._tag_service_actions, self._rating_service_actions, self._delete_second_file, self._sync_archive, self._delete_both_files, self._sync_urls_action )
return ( self._tag_service_actions, self._rating_service_actions, self._sync_archive, self._sync_urls_action )
def ProcessPairIntoContentUpdates( self, first_media, second_media, file_deletion_reason = None ):
def ProcessPairIntoContentUpdates( self, first_media, second_media, delete_first = False, delete_second = False, delete_both = False, file_deletion_reason = None ):
if file_deletion_reason is None:
@ -341,20 +327,17 @@ class DuplicateActionOptions( HydrusSerialisable.SerialisableBase ):
deletee_media = []
if self._delete_second_file or self._delete_both_files:
if delete_first or delete_second or delete_both:
if self._delete_both_files:
if delete_first or delete_both:
deletee_media.append( first_media )
file_deletion_reason += ': both files deleted'
else:
file_deletion_reason += ': \'worse\' file deleted'
deletee_media.append( second_media )
if delete_second or delete_both:
deletee_media.append( second_media )
for media in deletee_media:

File diff suppressed because it is too large Load Diff

View File

@ -100,10 +100,14 @@ def THREADUploadPending( service_key ):
info = nums_pending[ service_key ]
remaining_num_pending = sum( info.values() )
done_num_pending = initial_num_pending - remaining_num_pending
job_key.SetVariable( 'popup_text_1', 'uploading to ' + service_name + ': ' + HydrusData.ConvertValueRangeToPrettyString( done_num_pending, initial_num_pending ) )
job_key.SetVariable( 'popup_gauge_1', ( done_num_pending, initial_num_pending ) )
# sometimes more come in while we are pending, -754/1,234 ha ha
num_to_do = max( initial_num_pending, remaining_num_pending )
done_num_pending = num_to_do - remaining_num_pending
job_key.SetVariable( 'popup_text_1', 'uploading to ' + service_name + ': ' + HydrusData.ConvertValueRangeToPrettyString( done_num_pending, num_to_do ) )
job_key.SetVariable( 'popup_gauge_1', ( done_num_pending, num_to_do ) )
while job_key.IsPaused() or job_key.IsCancelled():
@ -4047,7 +4051,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
HG.force_idle_mode = not HG.force_idle_mode
self._controller.pub( 'wake_daemons' )
self._controller.pub( 'wake_idle_workers' )
self.SetStatusBarDirty()
elif name == 'no_page_limit_mode':

View File

@ -1155,7 +1155,12 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
services.append( services_manager.GetService( CC.LOCAL_FILE_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.TRASH_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
if HG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
services.append( services_manager.GetService( CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
services.extend( services_manager.GetServices( ( HC.FILE_REPOSITORY, ) ) )
advanced_mode = HG.client_controller.new_options.GetBoolean( 'advanced_mode' )

View File

@ -5,6 +5,7 @@ from . import HydrusGlobals as HG
from . import ClientCaches
from . import ClientConstants as CC
from . import ClientData
from . import ClientDuplicates
from . import ClientGUICommon
from . import ClientGUIDialogs
from . import ClientGUIDialogsManage
@ -3170,9 +3171,9 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
pair_info = []
for ( hash_pair, duplicate_type, first_media, second_media, duplicate_action_options, was_auto_skipped ) in self._processed_pairs:
for ( hash_pair, duplicate_type, first_media, second_media, service_keys_to_content_updates, was_auto_skipped ) in self._processed_pairs:
if duplicate_type == HC.DUPLICATE_UNKNOWN:
if duplicate_type == HC.DUPLICATE_UNKNOWN or was_auto_skipped:
continue # it was a 'skip' decision
@ -3180,19 +3181,6 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
first_hash = first_media.GetHash()
second_hash = second_media.GetHash()
if duplicate_type in ( HC.DUPLICATE_BETTER, HC.DUPLICATE_WORSE, HC.DUPLICATE_LARGER_BETTER, HC.DUPLICATE_SMALLER_BETTER, HC.DUPLICATE_BETTER_OR_WORSE ):
file_deletion_reason = 'better/worse'
else:
file_deletion_reason = HC.duplicate_type_string_lookup[ duplicate_type ]
file_deletion_reason = 'Deleted in Duplicate Filter ({}).'.format( file_deletion_reason )
service_keys_to_content_updates = duplicate_action_options.ProcessPairIntoContentUpdates( first_media, second_media, file_deletion_reason = file_deletion_reason )
pair_info.append( ( duplicate_type, first_hash, second_hash, service_keys_to_content_updates ) )
@ -3205,9 +3193,9 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
self._hashes_due_to_be_deleted_in_this_batch = set()
def _CurrentMediaIsBetter( self ):
def _CurrentMediaIsBetter( self, delete_second = True ):
self._ProcessPair( HC.DUPLICATE_BETTER )
self._ProcessPair( HC.DUPLICATE_BETTER, delete_second = delete_second )
def _Delete( self, media = None, reason = None, file_service_key = None ):
@ -3270,7 +3258,7 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
return
duplicate_types = [ HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_QUALITY, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_NOT_DUPLICATE ]
duplicate_types = [ HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_QUALITY, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_FALSE_POSITIVE ]
choice_tuples = [ ( HC.duplicate_type_string_lookup[ duplicate_type ], duplicate_type ) for duplicate_type in duplicate_types ]
@ -3285,22 +3273,74 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
new_options = HG.client_controller.new_options
duplicate_action_options = new_options.GetDuplicateActionOptions( duplicate_type )
if duplicate_type in [ HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_QUALITY ]:
duplicate_action_options = new_options.GetDuplicateActionOptions( duplicate_type )
with ClientGUITopLevelWindows.DialogEdit( self, 'edit duplicate merge options' ) as dlg_2:
panel = ClientGUIScrolledPanelsEdit.EditDuplicateActionOptionsPanel( dlg_2, duplicate_type, duplicate_action_options, for_custom_action = True )
dlg_2.SetPanel( panel )
if dlg_2.ShowModal() == wx.ID_OK:
duplicate_action_options = panel.GetValue()
else:
return
else:
duplicate_action_options = None
with ClientGUITopLevelWindows.DialogEdit( self, 'edit duplicate merge options' ) as dlg_2:
text = 'Delete any of the files?'
yes_tuples = []
yes_tuples.append( ( 'delete this one', 'delete_first' ) )
yes_tuples.append( ( 'delete the other', 'delete_second' ) )
yes_tuples.append( ( 'delete both', 'delete_both' ) )
delete_first = False
delete_second = False
delete_both = False
with ClientGUIDialogs.DialogYesYesNo( self, text, yes_tuples = yes_tuples, no_label = 'forget it' ) as dlg:
panel = ClientGUIScrolledPanelsEdit.EditDuplicateActionOptionsPanel( dlg_2, duplicate_type, duplicate_action_options, for_custom_action = True )
dlg_2.SetPanel( panel )
if dlg_2.ShowModal() == wx.ID_OK:
if dlg.ShowModal() == wx.ID_YES:
duplicate_action_options = panel.GetValue()
value = dlg.GetValue()
self._ProcessPair( duplicate_type, duplicate_action_options )
if value == 'delete_first':
delete_first = True
elif value == 'delete_second':
delete_second = True
elif value == 'delete_both':
delete_both = True
else:
return
else:
return
self._ProcessPair( duplicate_type, delete_first = delete_first, delete_second = delete_second, delete_both = delete_both, duplicate_action_options = duplicate_action_options )
def _DrawBackgroundDetails( self, dc ):
@ -3384,7 +3424,7 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
def _GetNumCommittableDecisions( self ):
return len( [ 1 for ( hash_pair, duplicate_type, first_media, second_media, duplicate_action_options, was_auto_skipped ) in self._processed_pairs if duplicate_type != HC.DUPLICATE_UNKNOWN ] )
return len( [ 1 for ( hash_pair, duplicate_type, first_media, second_media, service_keys_to_content_updates, was_auto_skipped ) in self._processed_pairs if duplicate_type != HC.DUPLICATE_UNKNOWN ] )
def _GoBack( self ):
@ -3393,13 +3433,13 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
self._unprocessed_pairs.append( self._current_pair )
( hash_pair, duplicate_type, first_media, second_media, duplicate_action_options, was_auto_skipped ) = self._processed_pairs.pop()
( hash_pair, duplicate_type, first_media, second_media, service_keys_to_content_updates, was_auto_skipped ) = self._processed_pairs.pop()
self._unprocessed_pairs.append( hash_pair )
while was_auto_skipped:
( hash_pair, duplicate_type, first_media, second_media, duplicate_action_options, was_auto_skipped ) = self._processed_pairs.pop()
( hash_pair, duplicate_type, first_media, second_media, service_keys_to_content_updates, was_auto_skipped ) = self._processed_pairs.pop()
self._unprocessed_pairs.append( hash_pair )
@ -3415,9 +3455,9 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
self._ProcessPair( HC.DUPLICATE_ALTERNATE )
def _MediaAreNotDupes( self ):
def _MediaAreFalsePositive( self ):
self._ProcessPair( HC.DUPLICATE_NOT_DUPLICATE )
self._ProcessPair( HC.DUPLICATE_FALSE_POSITIVE )
def _MediaAreTheSame( self ):
@ -3425,7 +3465,7 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
self._ProcessPair( HC.DUPLICATE_SAME_QUALITY )
def _ProcessPair( self, duplicate_type, duplicate_action_options = None ):
def _ProcessPair( self, duplicate_type, delete_first = False, delete_second = False, delete_both = False, duplicate_action_options = None ):
if self._current_media is None:
@ -3434,20 +3474,64 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
if duplicate_action_options is None:
new_options = HG.client_controller.new_options
duplicate_action_options = new_options.GetDuplicateActionOptions( duplicate_type )
if duplicate_type in [ HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_QUALITY ]:
new_options = HG.client_controller.new_options
duplicate_action_options = new_options.GetDuplicateActionOptions( duplicate_type )
else:
duplicate_action_options = ClientDuplicates.DuplicateActionOptions( [], [] )
other_media = self._media_list.GetNext( self._current_media )
deleted_hashes = duplicate_action_options.GetDeletedHashes( self._current_media, other_media )
self._hashes_due_to_be_deleted_in_this_batch.update( deleted_hashes )
first_media = self._current_media
second_media = self._media_list.GetNext( first_media )
was_auto_skipped = False
self._processed_pairs.append( ( self._current_pair, duplicate_type, self._current_media, other_media, duplicate_action_options, was_auto_skipped ) )
if delete_first or delete_second or delete_both:
if delete_first or delete_both:
self._hashes_due_to_be_deleted_in_this_batch.update( first_media.GetHashes() )
if delete_second or delete_both:
self._hashes_due_to_be_deleted_in_this_batch.update( second_media.GetHashes() )
if duplicate_type in ( HC.DUPLICATE_BETTER, HC.DUPLICATE_WORSE, HC.DUPLICATE_LARGER_BETTER, HC.DUPLICATE_SMALLER_BETTER, HC.DUPLICATE_BETTER_OR_WORSE ):
file_deletion_reason = 'better/worse'
if delete_second:
file_deletion_reason += ', worse file deleted'
else:
file_deletion_reason = HC.duplicate_type_string_lookup[ duplicate_type ]
if delete_both:
file_deletion_reason += ', both files deleted'
file_deletion_reason = 'Deleted in Duplicate Filter ({}).'.format( file_deletion_reason )
else:
file_deletion_reason = None
service_keys_to_content_updates = duplicate_action_options.ProcessPairIntoContentUpdates( first_media, second_media, delete_first = delete_first, delete_second = delete_second, delete_both = delete_both, file_deletion_reason = file_deletion_reason )
self._processed_pairs.append( ( self._current_pair, duplicate_type, first_media, second_media, service_keys_to_content_updates, was_auto_skipped ) )
self._ShowNewPair()
@ -3475,13 +3559,13 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
else:
( hash_pair, duplicate_type, first_media, second_media, duplicate_action_options, was_auto_skipped ) = self._processed_pairs.pop()
( hash_pair, duplicate_type, first_media, second_media, service_keys_to_content_updates, was_auto_skipped ) = self._processed_pairs.pop()
self._unprocessed_pairs.append( hash_pair )
while was_auto_skipped:
( hash_pair, duplicate_type, first_media, second_media, duplicate_action_options, was_auto_skipped ) = self._processed_pairs.pop()
( hash_pair, duplicate_type, first_media, second_media, service_keys_to_content_updates, was_auto_skipped ) = self._processed_pairs.pop()
self._unprocessed_pairs.append( hash_pair )
@ -3538,7 +3622,7 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
was_auto_skipped = True
self._processed_pairs.append( ( potential_pair, HC.DUPLICATE_UNKNOWN, None, None, None, was_auto_skipped ) )
self._processed_pairs.append( ( potential_pair, HC.DUPLICATE_UNKNOWN, None, None, {}, was_auto_skipped ) )
if len( self._unprocessed_pairs ) == 0:
@ -3609,7 +3693,7 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
was_auto_skipped = False
self._processed_pairs.append( ( self._current_pair, HC.DUPLICATE_UNKNOWN, None, None, None, was_auto_skipped ) )
self._processed_pairs.append( ( self._current_pair, HC.DUPLICATE_UNKNOWN, None, None, {}, was_auto_skipped ) )
self._ShowNewPair()
@ -3768,9 +3852,13 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
action = data
if action == 'duplicate_filter_this_is_better':
if action == 'duplicate_filter_this_is_better_and_delete_other':
self._CurrentMediaIsBetter()
self._CurrentMediaIsBetter( delete_second = True )
elif action == 'duplicate_filter_this_is_better_but_keep_both':
self._CurrentMediaIsBetter( delete_second = False )
elif action == 'duplicate_filter_exactly_the_same':
@ -3780,9 +3868,9 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
self._MediaAreAlternates()
elif action == 'duplicate_filter_not_dupes':
elif action == 'duplicate_filter_false_positive':
self._MediaAreNotDupes()
self._MediaAreFalsePositive()
elif action == 'duplicate_filter_custom_action':

View File

@ -1531,6 +1531,16 @@ class ChoiceSort( wx.Panel ):
def _UserChoseASort( self ):
if HG.client_controller.new_options.GetBoolean( 'save_page_sort_on_change' ):
media_sort = self._GetCurrentSort()
HG.client_controller.new_options.SetDefaultSort( media_sort )
def ACollectHappened( self, page_key, collect_by ):
if self._management_controller is not None:
@ -1556,11 +1566,15 @@ class ChoiceSort( wx.Panel ):
def EventSortAscChoice( self, event ):
self._UserChoseASort()
self._BroadcastSort()
def EventSortTypeChoice( self, event ):
self._UserChoseASort()
self._UpdateAscLabels( set_default_asc = True )
self._BroadcastSort()

View File

@ -365,7 +365,7 @@ class EditStringConverterPanel( ClientGUIScrolledPanels.EditPanel ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( transformations_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.SetSizer( vbox )
@ -901,7 +901,7 @@ class EditStringMatchPanel( ClientGUIScrolledPanels.EditPanel ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( self._example_string_matches, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )

View File

@ -135,7 +135,6 @@ class Dialog( wx.Dialog ):
self.SetIcon( HG.client_controller.frame_icon )
self.Bind( wx.EVT_BUTTON, self.EventDialogButton )
self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
if parent is not None and position == 'center':
@ -160,14 +159,6 @@ class Dialog( wx.Dialog ):
def EventDialogButton( self, event ):
if self.IsModal():
self.EndModal( event.GetId() )
def SetInitialSize( self, size ):
( width, height ) = size
@ -224,7 +215,10 @@ class DialogChooseNewServiceMethod( Dialog ):
self.EndModal( wx.ID_OK )
def GetRegister( self ): return self._should_register
def GetRegister( self ):
return self._should_register
class DialogCommitInterstitialFiltering( Dialog ):
@ -232,10 +226,10 @@ class DialogCommitInterstitialFiltering( Dialog ):
Dialog.__init__( self, parent, 'commit and continue?', position = 'center' )
self._commit = wx.Button( self, id = wx.ID_YES, label = 'commit and continue' )
self._commit = ClientGUICommon.BetterButton( self, 'commit and continue', self.EndModal, wx.ID_YES )
self._commit.SetForegroundColour( ( 0, 128, 0 ) )
self._back = wx.Button( self, id = wx.ID_CANCEL, label = 'go back' )
self._back = ClientGUICommon.BetterButton( self, 'go back', self.EndModal, wx.ID_NO )
vbox = wx.BoxSizer( wx.VERTICAL )
@ -266,7 +260,6 @@ class DialogFinishFiltering( Dialog ):
self._forget.SetForegroundColour( ( 128, 0, 0 ) )
self._back = ClientGUICommon.BetterButton( self, 'back to filtering', self.EndModal, wx.ID_CANCEL )
self._back.SetId( wx.ID_CANCEL )
hbox = wx.BoxSizer( wx.HORIZONTAL )
@ -303,7 +296,7 @@ class DialogGenerateNewAccounts( Dialog ):
self._lifetime = ClientGUICommon.BetterChoice( self )
self._ok = wx.Button( self, label = 'OK' )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'OK' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
@ -520,18 +513,25 @@ class DialogInputLocalBooruShare( Dialog ):
def EventCopyExternalShareURL( self, event ):
self._service = HG.client_controller.services_manager.GetService( CC.LOCAL_BOORU_SERVICE_KEY )
internal_port = self._service.GetPort()
external_ip = HydrusNATPunch.GetExternalIP() # eventually check for optional host replacement here
external_port = self._service.GetUPnPPort()
if external_port is None:
if internal_port is None:
external_port = self._service.GetPort()
wx.MessageBox( 'The local booru is not currently running!' )
url = 'http://' + external_ip + ':' + str( external_port ) + '/gallery?share_key=' + self._share_key.hex()
try:
url = self._service.GetExternalShareURL( self._share_key )
except Exception as e:
HydrusData.ShowException( e )
wx.MessageBox( 'Unfortunately, could not generate an external URL: {}'.format( e ) )
return
HG.client_controller.pub( 'clipboard', 'text', url )
@ -697,6 +697,8 @@ class FrameInputLocalFiles( wx.Frame ):
self.Show()
self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
HG.client_controller.gui.RegisterUIUpdateWindow( self )
HG.client_controller.CallToThreadLongRunning( self.THREADParseImportablePaths, self._unparsed_paths_queue, self._currently_parsing, self._work_to_do, self._parsed_path_queue, self._progress_updater, self._pause_event, self._cancel_event )
@ -768,6 +770,22 @@ class FrameInputLocalFiles( wx.Frame ):
self.Close()
def EventCharHook( self, event ):
( modifier, key ) = ClientGUIShortcuts.ConvertKeyEventToSimpleTuple( event )
if key == wx.WXK_ESCAPE:
self._TidyUp()
self.Close()
else:
event.Skip()
def EventDeleteAfterSuccessCheck( self, event ):
if self._delete_after_success.GetValue():
@ -1247,84 +1265,6 @@ class DialogInputNamespaceRegex( Dialog ):
return ( namespace, regex )
class DialogInputNewFormField( Dialog ):
def __init__( self, parent, form_field = None ):
Dialog.__init__( self, parent, 'configure form field' )
if form_field is None: ( name, field_type, default, editable ) = ( '', CC.FIELD_TEXT, '', True )
else: ( name, field_type, default, editable ) = form_field
self._name = wx.TextCtrl( self )
self._type = wx.Choice( self )
self._default = wx.TextCtrl( self )
self._editable = wx.CheckBox( self )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'OK' )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._name.SetValue( name )
for temp_type in CC.FIELDS: self._type.Append( CC.field_string_lookup[ temp_type ], temp_type )
self._type.Select( field_type )
self._default.SetValue( default )
self._editable.SetValue( editable )
#
rows = []
rows.append( ( 'name: ', self._name ) )
rows.append( ( 'type: ', self._type ) )
rows.append( ( 'default: ', self._default ) )
rows.append( ( 'editable: ', self._editable ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.Add( self._ok, CC.FLAGS_VCENTER )
b_box.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( b_box, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def GetFormField( self ):
name = self._name.GetValue()
field_type = self._type.GetClientData( self._type.GetSelection() )
default = self._default.GetValue()
editable = self._editable.GetValue()
return ( name, field_type, default, editable )
class DialogInputTags( Dialog ):
def __init__( self, parent, service_key, tags, message = '' ):
@ -1339,7 +1279,7 @@ class DialogInputTags( Dialog ):
self._tag_box = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self, self.EnterTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key, null_entry_callable = self.OK )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'OK' )
self._ok = ClientGUICommon.BetterButton( self, 'OK', self.EndModal, wx.ID_OK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
@ -1424,7 +1364,7 @@ class DialogInputUPnPMapping( Dialog ):
self._description = wx.TextCtrl( self )
self._duration = wx.SpinCtrl( self, min = 0, max = 86400 )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'OK' )
self._ok = ClientGUICommon.BetterButton( self, 'OK', self.EndModal, wx.ID_OK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
@ -1702,10 +1642,10 @@ class DialogSelectFromURLTree( Dialog ):
self._tree = wx.lib.agw.customtreectrl.CustomTreeCtrl( self, agwStyle = agwStyle )
self._ok = wx.Button( self, id = wx.ID_OK )
self._ok = ClientGUICommon.BetterButton( self, 'OK', self.EndModal, wx.ID_OK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
@ -1807,7 +1747,6 @@ class DialogSelectFromURLTree( Dialog ):
return urls
class DialogSelectImageboard( Dialog ):
def __init__( self, parent ):
@ -1896,7 +1835,7 @@ class DialogTextEntry( Dialog ):
self._text.SetMaxLength( self._max_chars )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok = ClientGUICommon.BetterButton( self, 'ok', self.EndModal, wx.ID_OK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
@ -1994,11 +1933,11 @@ class DialogYesNo( Dialog ):
Dialog.__init__( self, parent, title, position = 'center' )
self._yes = wx.Button( self, id = wx.ID_YES )
self._yes = ClientGUICommon.BetterButton( self, yes_label, self.EndModal, wx.ID_YES )
self._yes.SetForegroundColour( ( 0, 128, 0 ) )
self._yes.SetLabelText( yes_label )
self._no = wx.Button( self, id = wx.ID_NO )
self._no = ClientGUICommon.BetterButton( self, no_label, self.EndModal, wx.ID_NO )
self._no.SetForegroundColour( ( 128, 0, 0 ) )
self._no.SetLabelText( no_label )
@ -2052,9 +1991,8 @@ class DialogYesYesNo( Dialog ):
yes_buttons.append( yes_button )
self._no = wx.Button( self, id = wx.ID_NO )
self._no = ClientGUICommon.BetterButton( self, no_label, self.EndModal, wx.ID_NO )
self._no.SetForegroundColour( ( 128, 0, 0 ) )
self._no.SetLabelText( no_label )
#

View File

@ -351,7 +351,7 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
columns = [ ( 'description', -1 ), ( 'internal ip', 17 ), ( 'internal port', 7 ), ( 'external ip', 17 ), ( 'external port', 7 ), ( 'prototcol', 5 ), ( 'lease', 12 ) ]
columns = [ ( 'description', -1 ), ( 'internal ip', 17 ), ( 'internal port', 7 ), ( 'external port', 7 ), ( 'prototcol', 5 ), ( 'lease', 12 ) ]
self._mappings_list_ctrl = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, 'manage_upnp_mappings', 12, 36, columns, self._ConvertDataToListCtrlTuples, delete_key_callback = self._Remove, activation_callback = self._Edit )
@ -387,6 +387,8 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
self._mappings_list_ctrl.Sort( 0 )
self._started_external_ip_fetch = False
self._RefreshMappings()
@ -406,7 +408,7 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
( external_port, protocol, internal_port, description, duration ) = dlg.GetInfo()
for ( existing_description, existing_internal_ip, existing_internal_port, existing_external_ip, existing_external_port, existing_protocol, existing_lease ) in self._mappings:
for ( existing_description, existing_internal_ip, existing_internal_port, existing_external_port, existing_protocol, existing_lease ) in self._mappings:
if external_port == existing_external_port and protocol == existing_protocol:
@ -432,7 +434,7 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
def _ConvertDataToListCtrlTuples( self, mapping ):
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = mapping
( description, internal_ip, internal_port, external_port, protocol, duration ) = mapping
if duration == 0:
@ -443,7 +445,7 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
pretty_duration = HydrusData.TimeDeltaToPrettyTimeDelta( duration )
display_tuple = ( description, internal_ip, str( internal_port ), external_ip, str( external_port ), protocol, pretty_duration )
display_tuple = ( description, internal_ip, str( internal_port ), str( external_port ), protocol, pretty_duration )
sort_tuple = mapping
return ( display_tuple, sort_tuple )
@ -457,7 +459,7 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
for selected_mapping in selected_mappings:
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = selected_mapping
( description, internal_ip, internal_port, external_port, protocol, duration ) = selected_mapping
with ClientGUIDialogs.DialogInputUPnPMapping( self, external_port, protocol, internal_port, description, duration ) as dlg:
@ -486,6 +488,41 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
def _RefreshExternalIP( self ):
def wx_code( external_ip_text ):
if not self:
return
self._status_st.SetLabelText( external_ip_text )
def THREADdo_it():
try:
external_ip = HydrusNATPunch.GetExternalIP()
external_ip_text = 'External IP: {}'.format( external_ip )
except Exception as e:
external_ip_text = 'Error finding external IP: ' + str( e )
return
wx.CallAfter( wx_code, external_ip_text )
self._status_st.SetLabelText( 'Loading external IP\u2026' )
HG.client_controller.CallToThread( THREADdo_it )
def _RefreshMappings( self ):
def wx_code( mappings ):
@ -501,6 +538,13 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
self._status_st.SetLabelText( '' )
if not self._started_external_ip_fetch:
self._started_external_ip_fetch = True
self._RefreshExternalIP()
def THREADdo_it():
@ -520,7 +564,7 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
wx.CallAfter( wx_code, mappings )
self._status_st.SetLabelText( 'Refreshing mappings--please wait...' )
self._status_st.SetLabelText( 'Refreshing mappings--please wait\u2026' )
self._mappings_list_ctrl.SetData( [] )
@ -535,7 +579,7 @@ class DialogManageUPnP( ClientGUIDialogs.Dialog ):
for selected_mapping in selected_mappings:
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = selected_mapping
( description, internal_ip, internal_port, external_port, protocol, duration ) = selected_mapping
HydrusNATPunch.RemoveUPnPMapping( external_port, protocol )

View File

@ -268,8 +268,6 @@ class FullscreenHoverFrameRightDuplicates( FullscreenHoverFrame ):
menu_items.append( ( 'normal', 'edit duplicate action options for \'this is better\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_BETTER ) ) )
menu_items.append( ( 'normal', 'edit duplicate action options for \'same quality\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_SAME_QUALITY ) ) )
menu_items.append( ( 'normal', 'edit duplicate action options for \'alternates\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_ALTERNATE ) ) )
menu_items.append( ( 'normal', 'edit duplicate action options for \'not duplicates\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_NOT_DUPLICATE ) ) )
menu_items.append( ( 'separator', None, None, None ) )
menu_items.append( ( 'normal', 'edit background lighten/darken switch intensity', 'edit how much the background will brighten or darken as you switch between the pair', self._EditBackgroundSwitchIntensity ) )
@ -289,23 +287,40 @@ class FullscreenHoverFrameRightDuplicates( FullscreenHoverFrame ):
self._cog_button = ClientGUICommon.MenuBitmapButton( self, CC.GlobalBMPs.cog, menu_items )
dupe_commands = []
dupe_commands.append( ( 'this is better', 'Set that the current file you are looking at is better than the other in the pair.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better' ) ) )
dupe_commands.append( ( 'same quality', 'Set that the two files are duplicates of very similar quality.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_exactly_the_same' ) ) )
dupe_commands.append( ( 'alternates', 'Set that the files are not duplicates, but that one is derived from the other or that they are both descendants of a common ancestor.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_alternates' ) ) )
dupe_commands.append( ( 'not duplicates', 'Set that the files are not duplicates or otherwise related--that this pair is a false-positive match.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_not_dupes' ) ) )
dupe_commands.append( ( 'custom action', 'Choose one of the other actions but customise the merge and delete options for this specific decision.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_custom_action' ) ) )
command_button_vbox = wx.BoxSizer( wx.VERTICAL )
for ( label, tooltip, command ) in dupe_commands:
dupe_boxes = []
dupe_commands = []
dupe_commands.append( ( 'this is better, and delete the other', 'Set that the current file you are looking at is better than the other in the pair, and set the other file to be deleted.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better_and_delete_other' ) ) )
dupe_commands.append( ( 'this is better, but keep both', 'Set that the current file you are looking at is better than the other in the pair, but keep both files.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better_but_keep_both' ) ) )
dupe_commands.append( ( 'they are the same quality', 'Set that the two files are duplicates of very similar quality.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_exactly_the_same' ) ) )
dupe_boxes.append( ( 'they are duplicates', dupe_commands ) )
dupe_commands = []
dupe_commands.append( ( 'they are related alternates', 'Set that the files are not duplicates, but that one is derived from the other or that they are both descendants of a common ancestor.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_alternates' ) ) )
dupe_commands.append( ( 'they are not related', 'Set that the files are not duplicates or otherwise related--that this potential pair is a false positive match.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_false_positive' ) ) )
dupe_commands.append( ( 'custom action', 'Choose one of the other actions but customise the merge and delete options for this specific decision.', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_custom_action' ) ) )
dupe_boxes.append( ( 'other', dupe_commands ) )
for ( panel_name, dupe_commands ) in dupe_boxes:
command_button = ClientGUICommon.BetterButton( self, label, HG.client_controller.pub, 'canvas_application_command', command, self._canvas_key )
button_panel = ClientGUICommon.StaticBox( self, panel_name )
command_button.SetToolTip( tooltip )
for ( label, tooltip, command ) in dupe_commands:
command_button = ClientGUICommon.BetterButton( button_panel, label, HG.client_controller.pub, 'canvas_application_command', command, self._canvas_key )
command_button.SetToolTip( tooltip )
button_panel.Add( command_button, CC.FLAGS_EXPAND_PERPENDICULAR )
command_button_vbox.Add( command_button, CC.FLAGS_EXPAND_PERPENDICULAR )
command_button_vbox.Add( button_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self._comparison_statements_vbox = wx.BoxSizer( wx.VERTICAL )

View File

@ -2109,7 +2109,7 @@ class EditLoginStepPanel( ClientGUIScrolledPanels.EditPanel ):
vbox = ClientGUICommon.BetterBoxSizer( wx.VERTICAL )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( required_credentials_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( static_args_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( temp_args_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

View File

@ -899,6 +899,11 @@ class ManagementPanel( wx.lib.scrolledpanel.ScrolledPanel ):
def GetSortBy( self ):
return self._sort_by.GetSort()
def _MakeCurrentSelectionTagsBox( self, sizer ):
tags_box = ClientGUICommon.StaticBoxSorterForListBoxTags( self, 'selection tags' )
@ -1044,8 +1049,6 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
menu_items.append( ( 'normal', 'edit duplicate action options for \'this is better\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_BETTER ) ) )
menu_items.append( ( 'normal', 'edit duplicate action options for \'same quality\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_SAME_QUALITY ) ) )
menu_items.append( ( 'normal', 'edit duplicate action options for \'alternates\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_ALTERNATE ) ) )
menu_items.append( ( 'normal', 'edit duplicate action options for \'not duplicates\'', 'edit what content is merged when you filter files', HydrusData.Call( self._EditMergeOptions, HC.DUPLICATE_NOT_DUPLICATE ) ) )
self._edit_merge_options = ClientGUICommon.MenuButton( self._main_right_panel, 'edit default duplicate action options', menu_items )
@ -1073,9 +1076,10 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
random_filtering_panel = ClientGUICommon.StaticBox( self._main_right_panel, 'quick and dirty processing' )
self._show_some_dupes = ClientGUICommon.BetterButton( random_filtering_panel, 'show some random potential pairs', self._ShowSomeDupes )
self._set_random_as_alternates_button = ClientGUICommon.BetterButton( random_filtering_panel, 'set current media as all alternates', self._SetCurrentMediaAs, HC.DUPLICATE_ALTERNATE )
self._set_random_as_same_quality_button = ClientGUICommon.BetterButton( random_filtering_panel, 'set current media as all same quality', self._SetCurrentMediaAs, HC.DUPLICATE_SAME_QUALITY )
self._set_random_as_not_duplicates_button = ClientGUICommon.BetterButton( random_filtering_panel, 'set current media as not duplicates', self._SetCurrentMediaAs, HC.DUPLICATE_NOT_DUPLICATE )
self._set_random_as_same_quality_button = ClientGUICommon.BetterButton( random_filtering_panel, 'set current media as duplicates of the same quality', self._SetCurrentMediaAs, HC.DUPLICATE_SAME_QUALITY )
self._set_random_as_alternates_button = ClientGUICommon.BetterButton( random_filtering_panel, 'set current media as all related alternates', self._SetCurrentMediaAs, HC.DUPLICATE_ALTERNATE )
self._set_random_as_false_positives_button = ClientGUICommon.BetterButton( random_filtering_panel, 'set current media as not related/false positive', self._SetCurrentMediaAs, HC.DUPLICATE_FALSE_POSITIVE )
#
@ -1167,9 +1171,9 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
self._filtering_panel.Add( self._launch_filter, CC.FLAGS_EXPAND_PERPENDICULAR )
random_filtering_panel.Add( self._show_some_dupes, CC.FLAGS_EXPAND_PERPENDICULAR )
random_filtering_panel.Add( self._set_random_as_alternates_button, CC.FLAGS_EXPAND_PERPENDICULAR )
random_filtering_panel.Add( self._set_random_as_same_quality_button, CC.FLAGS_EXPAND_PERPENDICULAR )
random_filtering_panel.Add( self._set_random_as_not_duplicates_button, CC.FLAGS_EXPAND_PERPENDICULAR )
random_filtering_panel.Add( self._set_random_as_alternates_button, CC.FLAGS_EXPAND_PERPENDICULAR )
random_filtering_panel.Add( self._set_random_as_false_positives_button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox = ClientGUICommon.BetterBoxSizer( wx.VERTICAL )
@ -1381,7 +1385,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
search_distance = self._search_distance_spinctrl.GetValue()
self._controller.Write( 'maintain_similar_files_duplicate_pairs', search_distance, job_key = job_key )
self._controller.Write( 'maintain_similar_files_search_for_potential_duplicates', search_distance, job_key = job_key )
self._controller.pub( 'modal_message', job_key )

View File

@ -1627,9 +1627,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledCanvas ):
flat_media = self._GetSelectedFlatMedia()
hashes = { media.GetHash() for media in flat_media }
num_files = len( hashes )
num_files = len( flat_media )
if num_files > 0:
@ -1648,21 +1646,69 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledCanvas ):
text = 'This will regenerate the {} selected files\' thumbnails, but only if they are the wrong size.'.format( HydrusData.ToHumanInt( num_files ) )
text += os.linesep * 2
text += 'It may take some time to finish this job.'
do_it_now = True
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
if num_files > 50:
if dlg.ShowModal() == wx.ID_YES:
text += os.linesep * 2
text += 'You have selected {} files, so this job may take some time. If you would like, you can simply schedule it to happen in idle time.'.format( HydrusData.ToHumanInt( num_files ) )
yes_tuples = []
yes_tuples.append( ( 'do it now', 'now' ) )
yes_tuples.append( ( 'do it later', 'later' ) )
with ClientGUIDialogs.DialogYesYesNo( self, text, yes_tuples = yes_tuples, no_label = 'forget it' ) as dlg:
self._SetFocussedMedia( None )
if dlg.ShowModal() == wx.ID_YES:
value = dlg.GetValue()
if value == 'now':
do_it_now = True
elif value == 'later':
do_it_now = False
else:
return
else:
return
time.sleep( 1 )
else:
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
HG.client_controller.Write( 'regenerate_file_data', job_type, hashes )
if dlg.ShowModal() != wx.ID_YES:
return
if do_it_now:
self._SetFocussedMedia( None )
time.sleep( 0.1 )
HG.client_controller.CallToThread( HG.client_controller.files_maintenance_manager.RunJobImmediately, flat_media, job_type )
else:
hashes = { media.GetHash() for media in flat_media }
HG.client_controller.CallToThread( HG.client_controller.files_maintenance_manager.ScheduleJob, hashes, job_type )
def _RescindDownloadSelected( self ):
@ -1819,11 +1865,21 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledCanvas ):
elif duplicate_action_options is None:
yes_no_text = 'set all pair relationships to ' + HC.duplicate_type_string_lookup[ duplicate_type ] + ' (with default duplicate action/merge options)'
yes_no_text = 'set all pair relationships to ' + HC.duplicate_type_string_lookup[ duplicate_type ]
new_options = HG.client_controller.new_options
duplicate_action_options = new_options.GetDuplicateActionOptions( duplicate_type )
if duplicate_type in [ HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_QUALITY ]:
yes_no_text += ' (with default duplicate action/merge options)'
new_options = HG.client_controller.new_options
duplicate_action_options = new_options.GetDuplicateActionOptions( duplicate_type )
else:
duplicate_action_options = None
else:
@ -1917,7 +1973,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledCanvas ):
def _SetDuplicatesCustom( self ):
duplicate_types = [ HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_QUALITY, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_NOT_DUPLICATE ]
duplicate_types = [ HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_QUALITY ]
choice_tuples = [ ( HC.duplicate_type_string_lookup[ duplicate_type ], duplicate_type ) for duplicate_type in duplicate_types ]
@ -2242,9 +2298,9 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledCanvas ):
self._SetDuplicatesFocusedBetter()
elif action == 'duplicate_media_set_not_duplicate':
elif action == 'duplicate_media_set_false_positive':
self._SetDuplicates( HC.DUPLICATE_NOT_DUPLICATE )
self._SetDuplicates( HC.DUPLICATE_FALSE_POSITIVE )
elif action == 'duplicate_media_set_same_quality':
@ -4117,8 +4173,6 @@ class MediaPanelThumbnails( MediaPanel ):
ClientGUIMenus.AppendMenuItem( self, duplicates_action_submenu, 'set all selected as alternates', 'Set all the selected files as alternates.', self.ProcessApplicationCommand, ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_media_set_alternate' ) )
ClientGUIMenus.AppendMenuItem( self, duplicates_action_submenu, 'set all selected as not duplicates', 'Set all the selected files as not duplicates.', self.ProcessApplicationCommand, ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_media_set_not_duplicate' ) )
ClientGUIMenus.AppendMenuItem( self, duplicates_action_submenu, 'make a custom duplicates action', 'Choose which duplicates status to set to this selection and customise non-default merge options.', self.ProcessApplicationCommand, ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_media_set_custom' ) )
if collections_selected:
@ -4138,7 +4192,7 @@ class MediaPanelThumbnails( MediaPanel ):
duplicates_edit_action_submenu = wx.Menu()
for duplicate_type in ( HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_QUALITY, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_NOT_DUPLICATE ):
for duplicate_type in ( HC.DUPLICATE_BETTER, HC.DUPLICATE_SAME_QUALITY ):
ClientGUIMenus.AppendMenuItem( self, duplicates_edit_action_submenu, 'for ' + HC.duplicate_type_string_lookup[ duplicate_type ], 'Edit what happens when you set this status.', self._EditDuplicateActionOptions, duplicate_type )
@ -4160,7 +4214,7 @@ class MediaPanelThumbnails( MediaPanel ):
duplicates_view_menu = wx.Menu()
for duplicate_type in ( HC.DUPLICATE_BETTER_OR_WORSE, HC.DUPLICATE_BETTER, HC.DUPLICATE_WORSE, HC.DUPLICATE_SAME_QUALITY, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_NOT_DUPLICATE, HC.DUPLICATE_UNKNOWN ):
for duplicate_type in ( HC.DUPLICATE_BETTER_OR_WORSE, HC.DUPLICATE_BETTER, HC.DUPLICATE_WORSE, HC.DUPLICATE_SAME_QUALITY, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_FALSE_POSITIVE, HC.DUPLICATE_UNKNOWN ):
if duplicate_type in file_duplicate_types_to_counts:

View File

@ -218,7 +218,11 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
entries.append( ( 'page_query', CC.LOCAL_FILE_SERVICE_KEY ) )
entries.append( ( 'page_query', CC.TRASH_SERVICE_KEY ) )
entries.append( ( 'page_query', CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
if HG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
entries.append( ( 'page_query', CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
for service in self._services:
@ -417,6 +421,10 @@ class Page( wx.SplitterWindow ):
new_panel.Collect( self._page_key, collect_by )
sort_by = self._management_panel.GetSortBy()
new_panel.Sort( self._page_key, sort_by )
self.ReplaceWindow( self._media_panel, new_panel )

View File

@ -1505,17 +1505,6 @@ class ReviewServicePanel( wx.Panel ):
def _CopyExternalShareURL( self ):
try:
external_ip = HydrusNATPunch.GetExternalIP()
except Exception as e:
wx.MessageBox( str( e ) )
return
internal_port = self._service.GetPort()
if internal_port is None:
@ -1523,18 +1512,22 @@ class ReviewServicePanel( wx.Panel ):
wx.MessageBox( 'The local booru is not currently running!' )
external_port = self._service.GetUPnPPort()
if external_port is None:
external_port = internal_port
urls = []
for share_key in self._booru_shares.GetData( only_selected = True ):
url = 'http://' + external_ip + ':' + str( external_port ) + '/gallery?share_key=' + share_key.hex()
try:
url = self._service.GetExternalShareURL( share_key )
except Exception as e:
HydrusData.ShowException( e )
wx.MessageBox( 'Unfortunately, could not generate an external URL: {}'.format( e ) )
return
urls.append( url )

View File

@ -1504,7 +1504,7 @@ class EditJSONParsingRulePanel( ClientGUIScrolledPanels.EditPanel ):
vbox.Add( self._parse_rule_type, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._string_match, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.SetSizer( vbox )
@ -3070,7 +3070,7 @@ class EditPageParserPanel( ClientGUIScrolledPanels.EditPanel ):
gridbox = ClientGUICommon.WrapInGrid( test_url_fetch_panel, rows )
test_url_fetch_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
test_url_fetch_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
test_url_fetch_panel.Add( self._fetch_example_data, CC.FLAGS_EXPAND_PERPENDICULAR )
test_url_fetch_panel.Add( self._test_network_job_control, CC.FLAGS_EXPAND_PERPENDICULAR )

View File

@ -1041,6 +1041,11 @@ class PopupMessageManager( wx.Frame ):
def EventMove( self, event ):
if not self: # funny runtime error caused this
return
if self._OKToAlterUI():
self._SizeAndPositionAndShow()

View File

@ -319,7 +319,7 @@ class PanelPredicateSystemDuplicateRelationships( PanelPredicateSystem ):
self._num = wx.SpinCtrl( self, min = 0, max = 65535 )
choices = [ ( HC.duplicate_type_string_lookup[ status ], status ) for status in ( HC.DUPLICATE_BETTER_OR_WORSE, HC.DUPLICATE_BETTER, HC.DUPLICATE_WORSE, HC.DUPLICATE_SAME_QUALITY, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_NOT_DUPLICATE, HC.DUPLICATE_UNKNOWN ) ]
choices = [ ( HC.duplicate_type_string_lookup[ status ], status ) for status in ( HC.DUPLICATE_BETTER_OR_WORSE, HC.DUPLICATE_BETTER, HC.DUPLICATE_WORSE, HC.DUPLICATE_SAME_QUALITY, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_FALSE_POSITIVE, HC.DUPLICATE_UNKNOWN ) ]
self._dupe_type = ClientGUICommon.BetterRadioBox( self, choices = choices, style = wx.RA_SPECIFY_ROWS )

View File

@ -271,7 +271,7 @@ class EditCookiePanel( ClientGUIScrolledPanels.EditPanel ):
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( expires_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
@ -363,7 +363,7 @@ class EditDefaultTagImportOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( self._list_ctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
@ -1135,11 +1135,7 @@ class EditDuplicateActionOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
#
self._delete_second_file = wx.CheckBox( self )
self._sync_archive = wx.CheckBox( self )
self._delete_both_files = wx.CheckBox( self )
self._delete_both_files.SetToolTip( 'This is only enabled on custom actions.' )
self._sync_urls_action = ClientGUICommon.BetterChoice( self )
@ -1154,7 +1150,7 @@ class EditDuplicateActionOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
#
( tag_service_options, rating_service_options, delete_second_file, sync_archive, delete_both_files, sync_urls_action ) = duplicate_action_options.ToTuple()
( tag_service_options, rating_service_options, sync_archive, sync_urls_action ) = duplicate_action_options.ToTuple()
services_manager = HG.client_controller.services_manager
@ -1182,18 +1178,11 @@ class EditDuplicateActionOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
self._delete_second_file.SetValue( delete_second_file )
self._sync_archive.SetValue( sync_archive )
self._delete_both_files.SetValue( delete_both_files )
#
if not for_custom_action:
self._delete_both_files.Disable()
if self._duplicate_action in ( HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_NOT_DUPLICATE ) and not for_custom_action:
if self._duplicate_action in ( HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_FALSE_POSITIVE ) and not for_custom_action:
self._sync_archive.Disable()
self._sync_urls_action.Disable()
@ -1205,11 +1194,6 @@ class EditDuplicateActionOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
self._sync_urls_action.SelectClientData( sync_urls_action )
if self._duplicate_action != HC.DUPLICATE_BETTER:
self._delete_second_file.Disable()
#
button_hbox = wx.BoxSizer( wx.HORIZONTAL )
@ -1244,8 +1228,6 @@ class EditDuplicateActionOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
rows = []
rows.append( ( 'delete worse file: ', self._delete_second_file ) )
rows.append( ( 'delete both files: ', self._delete_both_files ) )
rows.append( ( 'if one file is archived, archive the other as well: ', self._sync_archive ) )
rows.append( ( 'sync known urls?: ', self._sync_urls_action ) )
@ -1574,12 +1556,10 @@ class EditDuplicateActionOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
tag_service_actions = self._tag_service_actions.GetClientData()
rating_service_actions = self._rating_service_actions.GetClientData()
delete_second_file = self._delete_second_file.GetValue()
sync_archive = self._sync_archive.GetValue()
delete_both_files = self._delete_both_files.GetValue()
sync_urls_action = self._sync_urls_action.GetChoice()
duplicate_action_options = ClientDuplicates.DuplicateActionOptions( tag_service_actions, rating_service_actions, delete_second_file, sync_archive, delete_both_files, sync_urls_action )
duplicate_action_options = ClientDuplicates.DuplicateActionOptions( tag_service_actions, rating_service_actions, sync_archive, sync_urls_action )
return duplicate_action_options
@ -5205,7 +5185,7 @@ class EditTagImportOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
gridbox = ClientGUICommon.WrapInGrid( downloader_options_panel, rows )
downloader_options_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
downloader_options_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
if not self._show_downloader_options:

View File

@ -1075,6 +1075,17 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
self._upnp = ClientGUICommon.NoneableSpinCtrl( self._client_server_options_panel, 'upnp port', none_phrase = 'do not forward port', max = 65535 )
self._external_scheme_override = ClientGUICommon.NoneableTextCtrl( self._client_server_options_panel, message = 'scheme (http/https) override when copying external links' )
self._external_host_override = ClientGUICommon.NoneableTextCtrl( self._client_server_options_panel, message = 'host override when copying external links' )
self._external_port_override = ClientGUICommon.NoneableTextCtrl( self._client_server_options_panel, message = 'port override when copying external links' )
if service_type != HC.LOCAL_BOORU:
self._external_scheme_override.Hide()
self._external_host_override.Hide()
self._external_port_override.Hide()
self._bandwidth_rules = ClientGUIControls.BandwidthRulesCtrl( self._client_server_options_panel, dictionary[ 'bandwidth_rules' ] )
#
@ -1089,6 +1100,10 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
self._support_cors.SetValue( dictionary[ 'support_cors' ] )
self._log_requests.SetValue( dictionary[ 'log_requests' ] )
self._external_scheme_override.SetValue( dictionary[ 'external_scheme_override' ] )
self._external_host_override.SetValue( dictionary[ 'external_host_override' ] )
self._external_port_override.SetValue( dictionary[ 'external_port_override' ] )
#
self._client_server_options_panel.Add( self._port, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -1096,6 +1111,9 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
self._client_server_options_panel.Add( self._support_cors, CC.FLAGS_EXPAND_PERPENDICULAR )
self._client_server_options_panel.Add( self._log_requests, CC.FLAGS_EXPAND_PERPENDICULAR )
self._client_server_options_panel.Add( self._upnp, CC.FLAGS_EXPAND_PERPENDICULAR )
self._client_server_options_panel.Add( self._external_scheme_override, CC.FLAGS_EXPAND_PERPENDICULAR )
self._client_server_options_panel.Add( self._external_host_override, CC.FLAGS_EXPAND_PERPENDICULAR )
self._client_server_options_panel.Add( self._external_port_override, CC.FLAGS_EXPAND_PERPENDICULAR )
self._client_server_options_panel.Add( self._bandwidth_rules, CC.FLAGS_EXPAND_BOTH_WAYS )
self.Add( self._client_server_options_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
@ -1131,6 +1149,9 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
dictionary_part[ 'allow_non_local_connections' ] = self._allow_non_local_connections.GetValue()
dictionary_part[ 'support_cors' ] = self._support_cors.GetValue()
dictionary_part[ 'log_requests' ] = self._log_requests.GetValue()
dictionary_part[ 'external_scheme_override' ] = self._external_scheme_override.GetValue()
dictionary_part[ 'external_host_override' ] = self._external_host_override.GetValue()
dictionary_part[ 'external_port_override' ] = self._external_port_override.GetValue()
dictionary_part[ 'bandwidth_rules' ] = self._bandwidth_rules.GetValue()
return dictionary_part
@ -1268,7 +1289,7 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( self, rows )
self.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
def GetValue( self ):
@ -1319,7 +1340,7 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( self, rows )
self.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
def GetValue( self ):
@ -1548,14 +1569,13 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._verify_regular_https = wx.CheckBox( general )
self._external_host = wx.TextCtrl( self )
self._external_host.SetToolTip( 'If you have trouble parsing your external ip using UPnP, you can force it to be this.' )
self._network_timeout = wx.SpinCtrl( self, min = 3, max = 300 )
self._network_timeout = wx.SpinCtrl( general, min = 3, max = 300 )
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 = wx.SpinCtrl( self, min = 1, max = 30 )
self._max_network_jobs_per_domain = wx.SpinCtrl( self, min = 1, max = 5 )
self._max_network_jobs = wx.SpinCtrl( general, min = 1, max = 30 )
self._max_network_jobs_per_domain = wx.SpinCtrl( general, min = 1, max = 5 )
#
proxy_panel = ClientGUICommon.StaticBox( self, 'proxy settings' )
@ -1576,15 +1596,13 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
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' ) )
if HC.options[ 'external_host' ] is not None:
self._external_host.SetValue( HC.options[ 'external_host' ] )
#
rows = []
rows.append( ( 'network timeout (seconds): ', self._network_timeout ) )
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 ) )
gridbox = ClientGUICommon.WrapInGrid( general, rows )
@ -1616,19 +1634,9 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
#
rows = []
rows.append( ( 'network timeout (seconds): ', self._network_timeout ) )
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( ( 'external ip/host override: ', self._external_host ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( general, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( proxy_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
@ -1641,15 +1649,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._new_options.SetNoneableString( 'http_proxy', self._http_proxy.GetValue() )
self._new_options.SetNoneableString( 'https_proxy', self._https_proxy.GetValue() )
external_host = self._external_host.GetValue()
if external_host == '':
external_host = None
HC.options[ 'external_host' ] = external_host
self._new_options.SetInteger( 'network_timeout', self._network_timeout.GetValue() )
self._new_options.SetInteger( 'max_network_jobs', self._max_network_jobs.GetValue() )
self._new_options.SetInteger( 'max_network_jobs_per_domain', self._max_network_jobs_per_domain.GetValue() )
@ -2044,7 +2043,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( mime_panel, rows )
mime_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
mime_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
mime_panel.Add( self._mime_launch_listctrl, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( mime_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
@ -2412,7 +2411,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( frame_locations_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
@ -2610,7 +2609,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.SetSizer( vbox )
@ -2702,7 +2701,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._new_options = HG.client_controller.new_options
self._jobs_panel = ClientGUICommon.StaticBox( self, 'when to run high cpu jobs' )
self._maintenance_panel = ClientGUICommon.StaticBox( self, 'maintenance period' )
self._file_maintenance_panel = ClientGUICommon.StaticBox( self, 'file maintenance' )
self._vacuum_panel = ClientGUICommon.StaticBox( self, 'vacuum' )
self._idle_panel = ClientGUICommon.StaticBox( self._jobs_panel, 'idle' )
self._shutdown_panel = ClientGUICommon.StaticBox( self._jobs_panel, 'shutdown' )
@ -2728,11 +2728,28 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._idle_shutdown.Bind( wx.EVT_CHOICE, self.EventIdleShutdown )
self._idle_shutdown_max_minutes = wx.SpinCtrl( self._shutdown_panel, min = 1, max = 1440 )
self._shutdown_work_period = ClientGUITime.TimeDeltaButton( self._shutdown_panel, min = 3600, days = True, hours = True )
self._shutdown_work_period = ClientGUITime.TimeDeltaButton( self._shutdown_panel, min = 60, days = True, hours = True, minutes = True )
#
self._maintenance_vacuum_period_days = ClientGUICommon.NoneableSpinCtrl( self._maintenance_panel, '', min = 28, max = 365, none_phrase = 'do not automatically vacuum' )
self._file_maintenance_during_idle = wx.CheckBox( self._file_maintenance_panel )
self._file_maintenance_on_shutdown = wx.CheckBox( self._file_maintenance_panel )
self._file_maintenance_throttle_enable = wx.CheckBox( self._file_maintenance_panel )
min_unit_value = 10
max_unit_value = 100000
min_time_delta = 3600
self._file_maintenance_throttle_velocity = ClientGUITime.VelocityCtrl( self._file_maintenance_panel, min_unit_value, max_unit_value, min_time_delta, days = True, hours = True, per_phrase = 'every', unit = 'files' )
tt = 'Please note that this throttle is not very rigorous, as file processing history is not currently saved on client restart. If you restart the client, the file manager thinks it has run on 0 files and will be happy to run until the throttle kicks in again.'
self._file_maintenance_throttle_enable.SetToolTip( tt )
self._file_maintenance_throttle_velocity.SetToolTip( tt )
#
self._maintenance_vacuum_period_days = ClientGUICommon.NoneableSpinCtrl( self._vacuum_panel, '', min = 28, max = 365, none_phrase = 'do not automatically vacuum' )
tts = 'Vacuuming is a kind of full defrag of the database\'s internal page table. It can take a long time (1MB/s) on a slow drive and does not need to be done often, so feel free to set this at 90 days+.'
@ -2749,8 +2766,21 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._idle_shutdown_max_minutes.SetValue( HC.options[ 'idle_shutdown_max_minutes' ] )
self._shutdown_work_period.SetValue( self._new_options.GetInteger( 'shutdown_work_period' ) )
self._file_maintenance_during_idle.SetValue( self._new_options.GetBoolean( 'file_maintenance_during_idle' ) )
self._file_maintenance_on_shutdown.SetValue( self._new_options.GetBoolean( 'file_maintenance_on_shutdown' ) )
self._file_maintenance_throttle_enable.SetValue( self._new_options.GetBoolean( 'file_maintenance_throttle_enable' ) )
file_maintenance_throttle_files = self._new_options.GetInteger( 'file_maintenance_throttle_files' )
file_maintenance_throttle_time_delta = self._new_options.GetInteger( 'file_maintenance_throttle_time_delta' )
file_maintenance_throttle_velocity = ( file_maintenance_throttle_files, file_maintenance_throttle_time_delta )
self._file_maintenance_throttle_velocity.SetValue( file_maintenance_throttle_velocity )
self._maintenance_vacuum_period_days.SetValue( self._new_options.GetNoneableInteger( 'maintenance_vacuum_period_days' ) )
self._file_maintenance_throttle_enable.Bind( wx.EVT_CHECKBOX, self.EventFileMaintenanceThrottle )
#
rows = []
@ -2762,7 +2792,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( self._idle_panel, rows )
self._idle_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self._idle_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
@ -2774,7 +2804,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( self._shutdown_panel, rows )
self._shutdown_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self._shutdown_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
@ -2802,23 +2832,42 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
#
message = 'File maintenance jobs include reparsing file metadata and regenerating thumbnails.'
self._file_maintenance_panel.Add( ClientGUICommon.BetterStaticText( self._file_maintenance_panel, label = message ), CC.FLAGS_EXPAND_PERPENDICULAR )
rows = []
rows.append( ( 'Permit file maintenance to run during idle time: ', self._file_maintenance_during_idle ) )
rows.append( ( 'Permit file maintenance to run during shutdown: ', self._file_maintenance_on_shutdown ) )
rows.append( ( 'Throttle file maintenance: ', self._file_maintenance_throttle_enable ) )
rows.append( ( 'Throttle to this value: ', self._file_maintenance_throttle_velocity ) )
gridbox = ClientGUICommon.WrapInGrid( self._file_maintenance_panel, rows )
self._file_maintenance_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
rows = []
rows.append( ( 'Number of days to wait between vacuums: ', self._maintenance_vacuum_period_days ) )
gridbox = ClientGUICommon.WrapInGrid( self._maintenance_panel, rows )
gridbox = ClientGUICommon.WrapInGrid( self._vacuum_panel, rows )
self._maintenance_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._vacuum_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._jobs_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._maintenance_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._file_maintenance_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._vacuum_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
self._EnableDisableFileMaintenanceThrottle()
self._EnableDisableIdleNormal()
self._EnableDisableIdleShutdown()
@ -2831,12 +2880,16 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._idle_mouse_period.Enable()
self._idle_cpu_max.Enable()
self._file_maintenance_during_idle.Enable()
else:
self._idle_period.Disable()
self._idle_mouse_period.Disable()
self._idle_cpu_max.Disable()
self._file_maintenance_during_idle.Disable()
def _EnableDisableIdleShutdown( self ):
@ -2846,11 +2899,27 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._shutdown_work_period.Disable()
self._idle_shutdown_max_minutes.Disable()
self._file_maintenance_on_shutdown.Disable()
else:
self._shutdown_work_period.Enable()
self._idle_shutdown_max_minutes.Enable()
self._file_maintenance_on_shutdown.Enable()
def _EnableDisableFileMaintenanceThrottle( self ):
if self._file_maintenance_throttle_enable.GetValue() == True:
self._file_maintenance_throttle_velocity.Enable()
else:
self._file_maintenance_throttle_velocity.Disable()
def EventIdleNormal( self, event ):
@ -2863,6 +2932,11 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._EnableDisableIdleShutdown()
def EventFileMaintenanceThrottle( self, event ):
self._EnableDisableFileMaintenanceThrottle()
def UpdateOptions( self ):
HC.options[ 'idle_normal' ] = self._idle_normal.GetValue()
@ -2876,6 +2950,17 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._new_options.SetInteger( 'shutdown_work_period', self._shutdown_work_period.GetValue() )
self._new_options.SetBoolean( 'file_maintenance_during_idle', self._file_maintenance_during_idle.GetValue() )
self._new_options.SetBoolean( 'file_maintenance_on_shutdown', self._file_maintenance_on_shutdown.GetValue() )
self._new_options.SetBoolean( 'file_maintenance_throttle_enable', self._file_maintenance_throttle_enable.GetValue() )
file_maintenance_throttle_velocity = self._file_maintenance_throttle_velocity.GetValue()
( file_maintenance_throttle_files, file_maintenance_throttle_time_delta ) = file_maintenance_throttle_velocity
self._new_options.SetInteger( 'file_maintenance_throttle_files', file_maintenance_throttle_files )
self._new_options.SetInteger( 'file_maintenance_throttle_time_delta', file_maintenance_throttle_time_delta )
self._new_options.SetNoneableInteger( 'maintenance_vacuum_period_days', self._maintenance_vacuum_period_days.GetValue() )
@ -2963,7 +3048,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._media_viewer_panel.Add( self._media_viewer_options, CC.FLAGS_EXPAND_BOTH_WAYS )
self._media_viewer_panel.Add( self._media_viewer_edit_button, CC.FLAGS_LONE_BUTTON )
@ -3123,6 +3208,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._fallback_sort = ClientGUICommon.ChoiceSort( self )
self._save_page_sort_on_change = wx.CheckBox( self )
self._default_collect = ClientGUICommon.CheckboxCollect( self )
self._sort_by = wx.ListBox( self )
@ -3162,12 +3249,15 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._sort_by.Append( '-'.join( sort_by ), sort_by )
self._save_page_sort_on_change.SetValue( self._new_options.GetBoolean( 'save_page_sort_on_change' ) )
#
rows = []
rows.append( ( 'Default sort: ', self._default_sort ) )
rows.append( ( 'Secondary sort (when primary gives two equal values): ', self._fallback_sort ) )
rows.append( ( 'Update default sort every time a new sort is manually chosen: ', self._save_page_sort_on_change ) )
rows.append( ( 'Default collect: ', self._default_collect ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
@ -3228,6 +3318,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._new_options.SetDefaultSort( self._default_sort.GetSort() )
self._new_options.SetFallbackSort( self._fallback_sort.GetSort() )
self._new_options.SetBoolean( 'save_page_sort_on_change', self._save_page_sort_on_change.GetValue() )
HC.options[ 'default_collect' ] = self._default_collect.GetChoice()
sort_by_choices = []
@ -3422,7 +3513,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( media_panel, rows )
media_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
media_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( media_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -3444,7 +3535,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( buffer_panel, rows )
buffer_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
buffer_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( buffer_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -3464,7 +3555,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( ac_panel, rows )
ac_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
ac_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( ac_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -3476,7 +3567,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( misc_panel, rows )
misc_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
misc_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( misc_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -3700,7 +3791,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( general_panel, rows )
general_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
general_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( general_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -3818,7 +3909,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( render_panel, rows )
render_panel.Add( render_st, CC.FLAGS_EXPAND_PERPENDICULAR )
render_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
render_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( render_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -4015,7 +4106,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
desc = 'This will search the database for statistically related tags based on what your focused file already has.'
panel_vbox.Add( ClientGUICommon.BetterStaticText( suggested_tags_related_panel, desc ), CC.FLAGS_EXPAND_PERPENDICULAR )
panel_vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
panel_vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
suggested_tags_related_panel.SetSizer( panel_vbox )
@ -4030,7 +4121,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( suggested_tags_file_lookup_script_panel, rows )
panel_vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
panel_vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
suggested_tags_file_lookup_script_panel.SetSizer( panel_vbox )
@ -4061,7 +4152,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
desc = 'The manage tags dialog can provide several kinds of tag suggestions. For simplicity, most are turned off by default.'
suggested_tags_panel.Add( ClientGUICommon.BetterStaticText( suggested_tags_panel, desc ), CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_panel.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
suggested_tags_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
suggested_tags_panel.Add( suggest_tags_panel_notebook, CC.FLAGS_EXPAND_BOTH_WAYS )
#
@ -4202,7 +4293,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.SetSizer( vbox )

View File

@ -437,7 +437,7 @@ class Shortcuts( HydrusSerialisable.SerialisableBaseNamed ):
def __iter__( self ):
for ( shortcut, command ) in list(self._shortcuts_to_commands.items()):
for ( shortcut, command ) in list( self._shortcuts_to_commands.items() ):
yield ( shortcut, command )

View File

@ -2450,7 +2450,10 @@ class ManageTagParents( ClientGUIScrolledPanels.ManagePanel ):
reason = dlg.GetValue()
else: do_it = False
else:
do_it = False
@ -2506,7 +2509,10 @@ class ManageTagParents( ClientGUIScrolledPanels.ManagePanel ):
reason = dlg.GetValue()
else: do_it = False
else:
do_it = False
@ -3356,7 +3362,10 @@ class ManageTagSiblings( ClientGUIScrolledPanels.ManagePanel ):
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ].update( current_pairs )
if do_it:
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ].update( current_pairs )
if len( pending_pairs ) > 0:

View File

@ -38,7 +38,11 @@ class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
#
self._death_file_velocity = VelocityCtrl( self, min_time_delta = 60, days = True, hours = True, minutes = True, per_phrase = 'in', unit = 'files' )
min_unit_value = 0
max_unit_value = 1000
min_time_delta = 60
self._death_file_velocity = VelocityCtrl( self, min_unit_value, max_unit_value, min_time_delta, days = True, hours = True, minutes = True, per_phrase = 'in', unit = 'files' )
self._flat_check_period_checkbox = wx.CheckBox( self )
@ -489,11 +493,11 @@ class TimeDeltaCtrl( wx.Panel ):
class VelocityCtrl( wx.Panel ):
def __init__( self, parent, min_time_delta = 60, days = False, hours = False, minutes = False, seconds = False, per_phrase = 'per', unit = None ):
def __init__( self, parent, min_unit_value, max_unit_value, min_time_delta, days = False, hours = False, minutes = False, seconds = False, per_phrase = 'per', unit = None ):
wx.Panel.__init__( self, parent )
self._num = wx.SpinCtrl( self, min = 0, max = 1000, size = ( 60, -1 ) )
self._num = wx.SpinCtrl( self, min = min_unit_value, max = max_unit_value, size = ( 60, -1 ) )
self._times = TimeDeltaCtrl( self, min = min_time_delta, days = days, hours = hours, minutes = minutes, seconds = seconds )

View File

@ -1226,7 +1226,10 @@ class MediaList( object ):
return False
def HasNoMedia( self ): return len( self._sorted_media ) == 0
def HasNoMedia( self ):
return len( self._sorted_media ) == 0
def ProcessContentUpdates( self, service_keys_to_content_updates ):
@ -1255,11 +1258,11 @@ class MediaList( object ):
deleted_from_trash_and_local_view = service_key == CC.TRASH_SERVICE_KEY and self._file_service_key in local_file_services
trashed_and_non_trash_local_view = HC.options[ 'remove_trashed_files' ] and service_key in non_trash_local_file_services and self._file_service_key in non_trash_local_file_services
trashed_from_our_local_file_domain = HC.options[ 'remove_trashed_files' ] and service_key in local_file_domains and self._file_service_key == service_key
deleted_from_repo_and_repo_view = service_key not in local_file_services and self._file_service_key == service_key
if deleted_from_trash_and_local_view or trashed_and_non_trash_local_view or deleted_from_repo_and_repo_view:
if deleted_from_trash_and_local_view or trashed_from_our_local_file_domain or deleted_from_repo_and_repo_view:
self._RemoveMediaByHashes( hashes )
@ -1474,11 +1477,24 @@ class MediaCollection( MediaList, Media ):
return len( self._hashes )
def GetNumInbox( self ): return sum( ( media.GetNumInbox() for media in self._sorted_media ) )
def GetNumInbox( self ):
return sum( ( media.GetNumInbox() for media in self._sorted_media ) )
def GetNumFrames( self ): return sum( ( media.GetNumFrames() for media in self._sorted_media ) )
def GetNumFrames( self ):
num_frames = ( media.GetNumFrames() for media in self._sorted_media )
return sum( ( nf for nf in num_frames if nf is not None ) )
def GetNumWords( self ): return sum( ( media.GetNumWords() for media in self._sorted_media ) )
def GetNumWords( self ):
num_words = ( media.GetNumWords() for media in self._sorted_media )
return sum( ( nw for nw in num_words if nw is not None ) )
def GetPrettyInfoLines( self ):
@ -1498,7 +1514,17 @@ class MediaCollection( MediaList, Media ):
return self._ratings_manager
def GetResolution( self ): return ( self._width, self._height )
def GetResolution( self ):
if self._width is None:
return ( 0, 0 )
else:
return ( self._width, self._height )
def GetSingletonsTagsManagers( self ):
@ -1709,8 +1735,14 @@ class MediaSingleton( Media ):
( width, height ) = self._media_result.GetResolution()
if width is None: return ( 0, 0 )
else: return ( width, height )
if width is None:
return ( 0, 0 )
else:
return ( width, height )
def GetSize( self ):

View File

@ -313,6 +313,18 @@ def GetSearchURLs( url ):
search_urls.add( r.geturl() )
for url in list( search_urls ):
if url.endswith( '/' ):
search_urls.add( url[:-1] )
else:
search_urls.add( url + '/' )
return search_urls
VALID_DENIED = 0

View File

@ -90,6 +90,13 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'booleans' ][ 'saving_sash_positions_on_exit' ] = True
self._dictionary[ 'booleans' ][ 'file_maintenance_on_shutdown' ] = True
self._dictionary[ 'booleans' ][ 'file_maintenance_during_idle' ] = True
self._dictionary[ 'booleans' ][ 'file_maintenance_throttle_enable' ] = True
self._dictionary[ 'booleans' ][ 'save_page_sort_on_change' ] = False
self._dictionary[ 'booleans' ][ 'pause_all_new_network_traffic' ] = False
self._dictionary[ 'booleans' ][ 'pause_all_file_queues' ] = False
self._dictionary[ 'booleans' ][ 'pause_all_watcher_checkers' ] = False
@ -163,10 +170,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
from . import ClientTags
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_BETTER ] = ClientDuplicates.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE, ClientTags.TagFilter() ) ], [], True, True, sync_urls_action = HC.CONTENT_MERGE_ACTION_COPY )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_SAME_QUALITY ] = ClientDuplicates.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE, ClientTags.TagFilter() ) ], [], False, True, sync_urls_action = HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_ALTERNATE ] = ClientDuplicates.DuplicateActionOptions( [], [], False )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_NOT_DUPLICATE ] = ClientDuplicates.DuplicateActionOptions( [], [], False )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_BETTER ] = ClientDuplicates.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE, ClientTags.TagFilter() ) ], [], sync_archive = True, sync_urls_action = HC.CONTENT_MERGE_ACTION_COPY )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_SAME_QUALITY ] = ClientDuplicates.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE, ClientTags.TagFilter() ) ], [], sync_archive = True, sync_urls_action = HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE )
#
@ -223,6 +228,9 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'integers' ][ 'thumbnail_border' ] = 1
self._dictionary[ 'integers' ][ 'thumbnail_margin' ] = 2
self._dictionary[ 'integers' ][ 'file_maintenance_throttle_files' ] = 200
self._dictionary[ 'integers' ][ 'file_maintenance_throttle_time_delta' ] = 86400
self._dictionary[ 'integers' ][ 'subscription_network_error_delay' ] = 12 * 3600
self._dictionary[ 'integers' ][ 'subscription_other_error_delay' ] = 36 * 3600
self._dictionary[ 'integers' ][ 'downloader_network_error_delay' ] = 90 * 60
@ -693,7 +701,14 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
with self._lock:
return self._dictionary[ 'duplicate_action_options' ][ duplicate_type ]
if duplicate_type in self._dictionary[ 'duplicate_action_options' ]:
return self._dictionary[ 'duplicate_action_options' ][ duplicate_type ]
else:
return ClientDuplicates.DuplicateActionOptions( [], [] )

View File

@ -10,6 +10,7 @@ from . import HydrusConstants as HC
from . import HydrusData
from . import HydrusExceptions
from . import HydrusGlobals as HG
from . import HydrusNATPunch
from . import HydrusNetwork
from . import HydrusNetworking
from . import HydrusSerialisable
@ -65,6 +66,10 @@ def GenerateDefaultServiceDictionary( service_type ):
dictionary[ 'support_cors' ] = False
dictionary[ 'log_requests' ] = False
dictionary[ 'external_scheme_override' ] = None
dictionary[ 'external_host_override' ] = None
dictionary[ 'external_port_override' ] = None
if service_type == HC.LOCAL_BOORU:
allow_non_local_connections = True
@ -339,6 +344,9 @@ class ServiceLocalServerService( Service ):
dictionary[ 'log_requests' ] = self._log_requests
dictionary[ 'bandwidth_tracker' ] = self._bandwidth_tracker
dictionary[ 'bandwidth_rules' ] = self._bandwidth_rules
dictionary[ 'external_scheme_override' ] = self._external_scheme_override
dictionary[ 'external_host_override' ] = self._external_host_override
dictionary[ 'external_port_override' ] = self._external_port_override
return dictionary
@ -354,6 +362,9 @@ class ServiceLocalServerService( Service ):
self._log_requests = dictionary[ 'log_requests' ]
self._bandwidth_tracker = dictionary[ 'bandwidth_tracker' ]
self._bandwidth_rules = dictionary[ 'bandwidth_rules' ]
self._external_scheme_override = dictionary[ 'external_scheme_override' ]
self._external_host_override = dictionary[ 'external_host_override' ]
self._external_port_override = dictionary[ 'external_port_override' ]
# this should support the same serverservice interface so we can just toss it at the regular serverengine and all the bandwidth will work ok
@ -424,7 +435,46 @@ class ServiceLocalServerService( Service ):
class ServiceLocalBooru( ServiceLocalServerService ):
pass
def GetExternalShareURL( self, share_key ):
if self._external_scheme_override is None:
scheme = 'http'
else:
scheme = self._external_scheme_override
if self._external_host_override is None:
host = HydrusNATPunch.GetExternalIP()
else:
host = self._external_host_override
if self._external_port_override is None:
if self._upnp_port is None:
port = self._port
else:
port = self._upnp_port
else:
port = self._external_port_override
url = '{}://{}:{}/gallery?share_key={}'.format( scheme, host, port, share_key.hex() )
return url
class ServiceClientAPI( ServiceLocalServerService ):

View File

@ -67,7 +67,7 @@ options = {}
# Misc
NETWORK_VERSION = 18
SOFTWARE_VERSION = 352
SOFTWARE_VERSION = 353
CLIENT_API_VERSION = 6
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -196,7 +196,7 @@ DEFINITIONS_TYPE_HASHES = 0
DEFINITIONS_TYPE_TAGS = 1
DUPLICATE_UNKNOWN = 0
DUPLICATE_NOT_DUPLICATE = 1
DUPLICATE_FALSE_POSITIVE = 1
DUPLICATE_SAME_QUALITY = 2
DUPLICATE_ALTERNATE = 3
DUPLICATE_BETTER = 4
@ -208,7 +208,7 @@ DUPLICATE_BETTER_OR_WORSE = 8
duplicate_type_string_lookup = {}
duplicate_type_string_lookup[ DUPLICATE_UNKNOWN ] = 'potential duplicates'
duplicate_type_string_lookup[ DUPLICATE_NOT_DUPLICATE ] = 'not duplicates'
duplicate_type_string_lookup[ DUPLICATE_FALSE_POSITIVE ] = 'not related/false positive'
duplicate_type_string_lookup[ DUPLICATE_SAME_QUALITY ] = 'same quality'
duplicate_type_string_lookup[ DUPLICATE_ALTERNATE ] = 'alternates'
duplicate_type_string_lookup[ DUPLICATE_BETTER ] = 'this is better'

View File

@ -493,6 +493,7 @@ class HydrusController( object ):
job = self.CallRepeating( 60.0, 300.0, self.MaintainDB )
job.WakeOnPubSub( 'wake_idle_workers' )
job.ShouldDelayOnWakeup( True )
self._daemon_jobs[ 'maintain_db' ] = job

View File

@ -29,11 +29,6 @@ EXTERNAL_IP[ 'time' ] = 0
def GetExternalIP():
if 'external_host' in HC.options and HC.options[ 'external_host' ] is not None:
return HC.options[ 'external_host' ]
if HydrusData.TimeHasPassed( EXTERNAL_IP[ 'time' ] + ( 3600 * 24 ) ):
cmd = [ upnpc_path, '-l' ]
@ -118,8 +113,6 @@ def AddUPnPMapping( internal_client, internal_port, external_port, protocol, des
def GetUPnPMappings():
external_ip_address = GetExternalIP()
cmd = [ upnpc_path, '-l' ]
sbp_kwargs = HydrusData.GetSubprocessKWArgs( text = True )
@ -203,7 +196,7 @@ def GetUPnPMappings():
lease_time = int( rest_of_line[1:] )
processed_data.append( ( description, internal_client, internal_port, external_ip_address, external_port, protocol, lease_time ) )
processed_data.append( ( description, internal_client, internal_port, external_port, protocol, lease_time ) )
return processed_data
@ -261,7 +254,7 @@ class ServicesUPnPManager( object ):
current_mappings = GetUPnPMappings()
our_mappings = { ( internal_client, internal_port ) : external_port for ( description, internal_client, internal_port, external_ip_address, external_port, protocol, enabled ) in current_mappings }
our_mappings = { ( internal_client, internal_port ) : external_port for ( description, internal_client, internal_port, external_port, protocol, enabled ) in current_mappings }
except:

View File

@ -392,8 +392,7 @@ class HydrusResource( Resource ):
if request.channel is None:
# Connection was lost, it seems.
request.finish()
# no need for request.finish
return
@ -741,10 +740,10 @@ class HydrusResource( Resource ):
d.addErrback( self._errbackHandleEmergencyError, request )
reactor.callLater( 0, d.callback, request )
request.notifyFinish().addErrback( self._errbackDisconnected, d )
reactor.callLater( 0, d.callback, request )
return NOT_DONE_YET
@ -764,10 +763,10 @@ class HydrusResource( Resource ):
d.addErrback( self._errbackHandleEmergencyError, request )
reactor.callLater( 0, d.callback, request )
request.notifyFinish().addErrback( self._errbackDisconnected, d )
reactor.callLater( 0, d.callback, request )
return NOT_DONE_YET
@ -789,10 +788,10 @@ class HydrusResource( Resource ):
d.addErrback( self._errbackHandleEmergencyError, request )
reactor.callLater( 0, d.callback, request )
request.notifyFinish().addErrback( self._errbackDisconnected, d )
reactor.callLater( 0, d.callback, request )
return NOT_DONE_YET

View File

@ -15,6 +15,7 @@ from . import ClientConstants as CC
from . import HydrusGlobals as HG
from . import ClientAPI
from . import ClientDefaults
from . import ClientFiles
from . import ClientNetworking
from . import ClientNetworkingBandwidth
from . import ClientNetworkingDomain
@ -253,7 +254,7 @@ class Controller( object ):
self._managers = {}
self.services_manager = ClientCaches.ServicesManager( self )
self.client_files_manager = ClientCaches.ClientFilesManager( self )
self.client_files_manager = ClientFiles.ClientFilesManager( self )
self.parsing_cache = ClientCaches.ParsingCache()

View File

@ -24,12 +24,10 @@ class TestNATPunch( unittest.TestCase ):
mappings = HydrusNATPunch.GetUPnPMappings()
external_ip_address = mappings[0][3]
mappings_without_lease_times = [ mapping[:-1] for mapping in mappings ]
self.assertIn( ( description_tcp, internal_client, internal_port, external_ip_address, external_port, 'TCP' ), mappings_without_lease_times )
self.assertIn( ( description_udp, internal_client, internal_port, external_ip_address, external_port, 'UDP' ), mappings_without_lease_times )
self.assertIn( ( description_tcp, internal_client, internal_port, external_port, 'TCP' ), mappings_without_lease_times )
self.assertIn( ( description_udp, internal_client, internal_port, external_port, 'UDP' ), mappings_without_lease_times )
HydrusNATPunch.RemoveUPnPMapping( external_port, 'TCP' )
HydrusNATPunch.RemoveUPnPMapping( external_port, 'UDP' )
@ -38,7 +36,7 @@ class TestNATPunch( unittest.TestCase ):
mappings_without_lease_times = [ mapping[:-1] for mapping in mappings ]
self.assertNotIn( ( description_tcp, internal_client, internal_port, external_ip_address, external_port, 'TCP' ), mappings_without_lease_times )
self.assertNotIn( ( description_udp, internal_client, internal_port, external_ip_address, external_port, 'UDP' ), mappings_without_lease_times )
self.assertNotIn( ( description_tcp, internal_client, internal_port, external_port, 'TCP' ), mappings_without_lease_times )
self.assertNotIn( ( description_udp, internal_client, internal_port, external_port, 'UDP' ), mappings_without_lease_times )

View File

@ -159,9 +159,9 @@ class TestSerialisables( unittest.TestCase ):
self.assertEqual( obj.ToTuple(), dupe_obj.ToTuple() )
duplicate_action_options_delete_and_move = ClientDuplicates.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE, ClientTags.TagFilter() ) ], [ ( TC.LOCAL_RATING_LIKE_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE ), ( TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE ) ], True )
duplicate_action_options_copy = ClientDuplicates.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_COPY, ClientTags.TagFilter() ) ], [ ( TC.LOCAL_RATING_LIKE_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_COPY ), ( TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_COPY ) ], False )
duplicate_action_options_merge = ClientDuplicates.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE, ClientTags.TagFilter() ) ], [ ( TC.LOCAL_RATING_LIKE_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ), ( TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ) ], False )
duplicate_action_options_delete_and_move = ClientDuplicates.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE, ClientTags.TagFilter() ) ], [ ( TC.LOCAL_RATING_LIKE_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE ), ( TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE ) ] )
duplicate_action_options_copy = ClientDuplicates.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_COPY, ClientTags.TagFilter() ) ], [ ( TC.LOCAL_RATING_LIKE_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_COPY ), ( TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_COPY ) ] )
duplicate_action_options_merge = ClientDuplicates.DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE, ClientTags.TagFilter() ) ], [ ( TC.LOCAL_RATING_LIKE_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ), ( TC.LOCAL_RATING_NUMERICAL_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ) ] )
inbox = True
size = 40960
@ -277,7 +277,7 @@ class TestSerialisables( unittest.TestCase ):
#
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, local_media_empty, file_deletion_reason = file_deletion_reason )
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, local_media_empty, delete_second = True, file_deletion_reason = file_deletion_reason )
scu = {}
@ -287,7 +287,7 @@ class TestSerialisables( unittest.TestCase ):
#
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, trashed_media_empty, file_deletion_reason = file_deletion_reason )
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, trashed_media_empty, delete_second = True, file_deletion_reason = file_deletion_reason )
scu = {}
@ -297,13 +297,13 @@ class TestSerialisables( unittest.TestCase ):
#
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, deleted_media_empty, file_deletion_reason = file_deletion_reason )
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, deleted_media_empty, delete_second = True, file_deletion_reason = file_deletion_reason )
self.assertEqual( result, {} )
#
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, other_local_media_has_values, file_deletion_reason = file_deletion_reason )
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_has_values, other_local_media_has_values, delete_second = True, file_deletion_reason = file_deletion_reason )
scu = {}
@ -316,7 +316,7 @@ class TestSerialisables( unittest.TestCase ):
#
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_empty, other_local_media_has_values, file_deletion_reason = file_deletion_reason )
result = duplicate_action_options_delete_and_move.ProcessPairIntoContentUpdates( local_media_empty, other_local_media_has_values, delete_second = True, file_deletion_reason = file_deletion_reason )
scu = {}