changelog
-
+
version 457
+ - smoother menubar updates: +
- improved the way the menubar menus update. rather than generating a whole new (e.g. 'pages') menu and replacing the existing out of date one, now there is a static menu skeleton that has subsections or labels updated in place. this means fewer objects changing, less flicker/jank, and should allow you to upload pending even if you have, say, a bunch of subscriptions running +
- . +
- misc: +
- thanks to a user's help, the filetype parser now detects pngs (this mostly happens during import) much faster! the problem previously was determining if a png is actually an apng--figuring out if they are truly apngs is now done with very fast file header scanning, rather than the previous method that booted ffmpeg. this brings filetype parse time for pngs down from 50-150ms to 1-2ms +
- getting apng metadata is also now faster. num_frames is now pulled from the file header, it no longer has to be manually counted by ffmpeg +
- clicking the session weight item in the 'pages' menu now gives you more detailed info on your session weight, including on currently closed pages in the undo list +
- stripped out a lot of ancient wx-era safety code that stops the client from doing certain UI work while it is minimised or minimised to tray. also brushed up some ugly update routines for menus refresh and modal message presentation that could lead to a pile-up of updates as soon as the client was unminimised, causing lag or freezes. with luck, the client should be better about restoring itself from minimised to system tray. if you minimise to tray, feedback on how this works out for you would be appreciated +
- when a network job stalls with the 'this domain had some errors recently' message, the cog menu on the widget now allows you to 'scrub domain errors' and try again immediately +
- if your search has system:limit, then any tag search you type in the autocomplete will now search the database, not your thumbnails. previously, the hack to enable this behaviour was to flip 'searching immediately' off. let's see if this new behaviour is ultimately confusing/annoying, I am mixed on it and think this subtle search option needs more thought and UI to make it more obvious and user friendly +
- if you have autocomplete tag search typed, and results from thumbnails displayed, and you flip 'searching immediately' off, the search will now automatically update and give you full database numbers immediately +
- . +
- help: +
- I moved 'searching with wildcards' from the advanced help to the 'more getting started with files' help here: https://hydrusnetwork.github.io/hydrus/help/getting_started_more_files.html +
- I also wrote a more detailed description of what the autocomplete dropdown buttons do in that page +
- I also wrote a brief description of how a system:limit query will try to clip according to the current file sort, getting the n 'biggest files' and so on +
- . +
- boring code cleanup: +
- cleaned some network job widget update calls +
- improved some misc autocomplete search status tracking +
- improved some account object permission checking and tests. accounts now never say they have permissions (e.g. if you click the 'see account permissions' button on review services) if they are banned or expired +
- file and pages menus now uses the new update routine +
- pending menu now uses the new update routine, with an emphasis on anti-jitter so you can interact while it is updating +
- database, network, service, and undo menu now use newer async update code and also use the new update routine +
- cleaned up help and tags menu init code +
- the signal that causes the pending menu to update is now only sent on tag changes if the tag service is a repository (previously, local-only updates were janking this for no reason) +
- the pending menu now updates its sibling/parent numbers when repository processing causes a clever row change to stuff you have pending +
- also, some menubar items that only show when in advanced mode now update their visibility when advanced mode is flipped on or off +
- misc menubar code cleanup and improvements +
version 456
- misc: diff --git a/help/getting_started_more_files.html b/help/getting_started_more_files.html index c553969d..35f6c883 100644 --- a/help/getting_started_more_files.html +++ b/help/getting_started_more_files.html @@ -7,6 +7,47 @@
-
+
favourite searches star
+Once you get experience with the client, have a play with this. Rather than leaving common search pages open, save them in here and load them up as needed. You will keep your client lightweight and save time.
+
+ -
+
include current/pending tags
+Turn these on and off to control whether tag search predicates apply to tags the exist, or limit just to those pending to be uploaded to a tag repository. Just searching 'pending' tags is useful if you want to scan what you have pending to go up to the PTR--just turn off 'current' tags and search 'system:num tags > 0'.
+
+ -
+
searching immediately
+This controls whether a change to the search tags will instantly run the new search and get new results. Turning this off is helpful if you want to add, remove, or replace several heavy search terms in a row without getting UI lag.
+
+ -
+
OR
+You only see this if you have 'advanced mode' on. It is an experimental module. Have a play with it--it lets you enter some pretty complicated tags!
+
+ -
+
file/tag domains
+By default, you will search in 'my files' and 'all known tags' domain. This is the intersection of your local media files (on your hard disk) and the union of all known tag searches. If you search for 'character:samus aran', then you will get file results from your 'my files' domain that have 'character:samus aran' in any tag service. For most purposes, this search domain is fine, but as you use the client more, you may want to access different search domains.
+For instance, if you change the file domain to 'trash', then you will instead get files that are in your trash. Setting the tag domain to 'my tags' will ignore other tag services (e.g. the PTR) for all tag search predicates, so a 'system:num_tags' or a 'character:samus aran' will only look 'my tags'.
+Turning on 'advanced mode' gives access to more search domains. Some of them are subtly complicated and only useful for clever jobs--most of the time, you still want 'my files' and 'all known tags'.
+
+
-
+
searching with wildcards
+The autocomplete tag dropdown supports wildcard searching with '*'.
+ +The '*' will match any number of characters. Every normal autocomplete search has a secret '*' on the end that you don't see, which is how full words get matched from you only typing in a few letters.
+This is useful when you can only remember part of a word, or can't spell part of it. You can put '*' characters anywhere, but you should experiment to get used to the exact way these searches work. Some results can be surprising!
+ +You can select the special predicate inserted at the top of your autocomplete results (the highlighted '*gelion' and '*va*ge*' above). It will return all files that match that wildcard, i.e. every file for every other tag in the dropdown list.
+This is particularly useful if you have a number of files with commonly structured over-informationed tags, like this:
+ +In this case, selecting the 'title:cool pic*' predicate will return all three images in the same search, where you can conveniently give them some more-easily searched tags like 'series:cool pic' and 'page:1', 'page:2', 'page:3'.
+more searching
+Let's look at the tag autocomplete dropdown again:
+ +-
+
sorting with system limit
+If you add system:limit to a search, the client will consider what that page's file sort currently is. If it is simple enough--something like file size or import time--then it will sort your results before they come back and clip the limit according to that sort, getting the n 'largest file size' or 'newest imports' and so on. This can be a great way to set up a lightweight filtering page for 'the 256 biggest videos in my inbox'.
+If you change the sort, hydrus will not refresh the search, it'll just re-sort the n files you have. Hit F5 to refresh the search with a new sort.
+Not all sorts are supported. Anything complicated like tag sort will result in a random sample instead.
exporting and uploading
There are many ways to export files from the client:
-
diff --git a/hydrus/client/ClientSearch.py b/hydrus/client/ClientSearch.py
index beac5280..d9a41732 100644
--- a/hydrus/client/ClientSearch.py
+++ b/hydrus/client/ClientSearch.py
@@ -787,6 +787,11 @@ class FileSystemPredicates( object ):
return self._has_system_everything
+ def HasSystemLimit( self ):
+
+ return self._limit is not None
+
+
def MustBeArchive( self ): return self._archive
def MustBeInbox( self ): return self._inbox
diff --git a/hydrus/client/db/ClientDB.py b/hydrus/client/db/ClientDB.py
index d0de3ec5..2a1e1c75 100644
--- a/hydrus/client/db/ClientDB.py
+++ b/hydrus/client/db/ClientDB.py
@@ -12250,7 +12250,14 @@ class DB( HydrusDB.HydrusDB ):
self.modules_files_storage.RescindPendFiles( service_id, hash_ids )
- notify_new_pending = True
+ if service_key == CC.COMBINED_LOCAL_FILE_SERVICE_KEY:
+
+ notify_new_downloads = True
+
+ else:
+
+ notify_new_pending = True
+
elif action == HC.CONTENT_UPDATE_RESCIND_PETITION:
@@ -12439,6 +12446,11 @@ class DB( HydrusDB.HydrusDB ):
changed_parent_tag_ids.update( ( child_tag_id, parent_tag_id ) )
+ if service_type == HC.TAG_REPOSITORY:
+
+ notify_new_pending = True
+
+
elif action in ( HC.CONTENT_UPDATE_PEND, HC.CONTENT_UPDATE_PETITION ):
( child_tag, parent_tag ) = row
@@ -12471,7 +12483,10 @@ class DB( HydrusDB.HydrusDB ):
changed_parent_tag_ids.update( ( child_tag_id, parent_tag_id ) )
- notify_new_pending = True
+ if service_type == HC.TAG_REPOSITORY:
+
+ notify_new_pending = True
+
elif action in ( HC.CONTENT_UPDATE_RESCIND_PEND, HC.CONTENT_UPDATE_RESCIND_PETITION ):
@@ -12501,7 +12516,10 @@ class DB( HydrusDB.HydrusDB ):
changed_parent_tag_ids.update( ( child_tag_id, parent_tag_id ) )
- notify_new_pending = True
+ if service_type == HC.TAG_REPOSITORY:
+
+ notify_new_pending = True
+
notify_new_parents = True
@@ -12536,6 +12554,11 @@ class DB( HydrusDB.HydrusDB ):
changed_sibling_tag_ids.update( ( bad_tag_id, good_tag_id ) )
+ if service_type == HC.TAG_REPOSITORY:
+
+ notify_new_pending = True
+
+
elif action in ( HC.CONTENT_UPDATE_PEND, HC.CONTENT_UPDATE_PETITION ):
( bad_tag, good_tag ) = row
@@ -12568,7 +12591,10 @@ class DB( HydrusDB.HydrusDB ):
changed_sibling_tag_ids.update( ( bad_tag_id, good_tag_id ) )
- notify_new_pending = True
+ if service_type == HC.TAG_REPOSITORY:
+
+ notify_new_pending = True
+
elif action in ( HC.CONTENT_UPDATE_RESCIND_PEND, HC.CONTENT_UPDATE_RESCIND_PETITION ):
@@ -12598,7 +12624,10 @@ class DB( HydrusDB.HydrusDB ):
changed_sibling_tag_ids.update( ( bad_tag_id, good_tag_id ) )
- notify_new_pending = True
+ if service_type == HC.TAG_REPOSITORY:
+
+ notify_new_pending = True
+
notify_new_siblings = True
@@ -12698,7 +12727,10 @@ class DB( HydrusDB.HydrusDB ):
self._UpdateMappings( service_id, mappings_ids = ultimate_mappings_ids, deleted_mappings_ids = ultimate_deleted_mappings_ids, pending_mappings_ids = ultimate_pending_mappings_ids, pending_rescinded_mappings_ids = ultimate_pending_rescinded_mappings_ids, petitioned_mappings_ids = ultimate_petitioned_mappings_ids, petitioned_rescinded_mappings_ids = ultimate_petitioned_rescinded_mappings_ids )
- notify_new_pending = True
+ if service_type == HC.TAG_REPOSITORY:
+
+ notify_new_pending = True
+
if len( changed_sibling_tag_ids ) > 0:
diff --git a/hydrus/client/gui/ClientGUI.py b/hydrus/client/gui/ClientGUI.py
index abd1bf9b..a8e6cc40 100644
--- a/hydrus/client/gui/ClientGUI.py
+++ b/hydrus/client/gui/ClientGUI.py
@@ -427,7 +427,6 @@ def THREADUploadPending( service_key ):
HG.client_controller.Write( 'delete_service_info', service_key, types_to_delete )
- HG.currently_uploading_pending = False
HG.client_controller.pub( 'notify_new_pending' )
@@ -490,6 +489,8 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
self._message_manager = ClientGUIPopupMessages.PopupMessageManager( self )
+ self._pending_modal_job_keys = set()
+
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_ICONIZE( self.EventIconize )
@@ -503,6 +504,7 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
self._controller.sub( self, 'DoFileStorageRebalance', 'do_file_storage_rebalance' )
self._controller.sub( self, 'NewPageImportHDD', 'new_hdd_import' )
self._controller.sub( self, 'NewPageQuery', 'new_page_query' )
+ self._controller.sub( self, 'NotifyAdvancedMode', 'notify_advanced_mode' )
self._controller.sub( self, 'NotifyClosedPage', 'notify_closed_page' )
self._controller.sub( self, 'NotifyDeletedPage', 'notify_deleted_page' )
self._controller.sub( self, 'NotifyNewExportFolders', 'notify_new_export_folders' )
@@ -2080,6 +2082,99 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
+ def _InitialiseMenubar( self ):
+
+ self._menubar = QW.QMenuBar( self )
+
+ self._menubar.setNativeMenuBar( False )
+
+ self._menu_updater_database = self._InitialiseMenubarGetMenuUpdaterDatabase()
+ self._menu_updater_file = self._InitialiseMenubarGetMenuUpdaterFile()
+ self._menu_updater_network = self._InitialiseMenubarGetMenuUpdaterNetwork()
+ self._menu_updater_pages = self._InitialiseMenubarGetMenuUpdaterPages()
+ self._menu_updater_pending = self._InitialiseMenubarGetMenuUpdaterPending()
+ self._menu_updater_services = self._InitialiseMenubarGetMenuUpdaterServices()
+ self._menu_updater_undo = self._InitialiseMenubarGetMenuUpdaterUndo()
+
+ self._boned_updater = self._InitialiseMenubarGetBonesUpdater()
+
+ self.setMenuBar( self._menubar )
+
+ for name in MENU_ORDER:
+
+ if name == 'database':
+
+ ( menu, label ) = self._InitialiseMenuInfoDatabase()
+
+ self.ReplaceMenu( name, menu, label )
+
+ self._menu_updater_database.update()
+
+ elif name == 'file':
+
+ ( menu, label ) = self._InitialiseMenuInfoFile()
+
+ self.ReplaceMenu( name, menu, label )
+
+ self._menu_updater_file.update()
+
+ elif name == 'help':
+
+ ( menu, label ) = self._InitialiseMenuInfoHelp()
+
+ self.ReplaceMenu( name, menu, label )
+
+ elif name == 'network':
+
+ ( menu, label ) = self._InitialiseMenuInfoNetwork()
+
+ self.ReplaceMenu( name, menu, label )
+
+ self._menu_updater_network.update()
+
+ elif name == 'pages':
+
+ ( menu, label ) = self._InitialiseMenuInfoPages()
+
+ self.ReplaceMenu( name, menu, label )
+
+ self._menu_updater_pages.update()
+
+ elif name == 'pending':
+
+ self._pending_service_keys_to_submenus = {}
+
+ self._menubar_pending_submenu = QW.QMenu( self )
+
+ self.ReplaceMenu( name, self._menubar_pending_submenu, '&pending' )
+
+ self._menu_updater_pending.update()
+
+ elif name == 'services':
+
+ ( menu, label ) = self._InitialiseMenuInfoServices()
+
+ self.ReplaceMenu( name, menu, label )
+
+ self._menu_updater_services.update()
+
+ elif name == 'tags':
+
+ ( menu, label ) = self._InitialiseMenuInfoTags()
+
+ self.ReplaceMenu( name, menu, label )
+
+ elif name == 'undo':
+
+ ( self._menubar_undo_submenu, label ) = self._InitialiseMenuInfoUndo()
+
+ self.ReplaceMenu( name, self._menubar_undo_submenu, label )
+
+ self._menu_updater_undo.update()
+
+
+
+
def _InitialiseMenubarGetBonesUpdater( self ):
def loading_callable():
@@ -2116,11 +2211,45 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
- def _InitialiseMenubarGetFileMenuUpdater( self ):
+ def _InitialiseMenubarGetMenuUpdaterDatabase( self ):
def loading_callable():
- self.DisableMenu( 'file' )
+ pass
+
+
+ def work_callable():
+
+ all_locations_are_default = HG.client_controller.client_files_manager.AllLocationsAreDefault()
+
+ return all_locations_are_default
+
+
+ def publish_callable( result ):
+
+ all_locations_are_default = result
+
+ backup_path = self._new_options.GetNoneableString( 'backup_path' )
+
+ self._menubar_database_set_up_backup_path.setVisible( all_locations_are_default and backup_path is None )
+
+ self._menubar_database_update_backup.setVisible( all_locations_are_default and backup_path is not None )
+ self._menubar_database_change_backup_path.setVisible( all_locations_are_default and backup_path is not None )
+
+ self._menubar_database_restore_backup.setVisible( all_locations_are_default )
+
+ self._menubar_database_multiple_location_label.setVisible( not all_locations_are_default )
+
+
+ return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
+
+
+ def _InitialiseMenubarGetMenuUpdaterFile( self ):
+
+ def loading_callable():
+
+ self._menubar_file_import_submenu.setEnabled( False )
+ self._menubar_file_export_submenu.setEnabled( False )
def work_callable():
@@ -2135,19 +2264,91 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
( import_folder_names, export_folder_names ) = result
- ( menu, label ) = self.GenerateMenuInfoFile( import_folder_names, export_folder_names )
+ self._menubar_file_import_submenu.setEnabled( True )
- self.ReplaceMenu( 'file', menu, label )
+ self._menubar_file_import_submenu.clear()
+
+ self._menubar_file_import_submenu.menuAction().setVisible( len( import_folder_names ) > 0 )
+
+ if len( import_folder_names ) > 0:
+
+ if len( import_folder_names ) > 1:
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_file_import_submenu, 'check all', 'Check all import folders.', self._CheckImportFolder )
+
+ ClientGUIMenus.AppendSeparator( self._menubar_file_import_submenu )
+
+
+ for name in import_folder_names:
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_file_import_submenu, name, 'Check this import folder now.', self._CheckImportFolder, name )
+
+
+
+ self._menubar_file_export_submenu.setEnabled( True )
+
+ self._menubar_file_export_submenu.clear()
+
+ self._menubar_file_export_submenu.menuAction().setVisible( len( export_folder_names ) > 0 )
+
+ if len( export_folder_names ) > 0:
+
+ if len( export_folder_names ) > 1:
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_file_export_submenu, 'run all', 'Run all export folders.', self._RunExportFolder )
+
+ ClientGUIMenus.AppendSeparator( self._menubar_file_export_submenu )
+
+
+ for name in export_folder_names:
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_file_export_submenu, name, 'Run this export folder now.', self._RunExportFolder, name )
+
+
+
+ simple_non_windows = not HC.PLATFORM_WINDOWS and not HG.client_controller.new_options.GetBoolean( 'advanced_mode' )
+
+ windows_or_advanced_non_windows = not simple_non_windows
+
+ self._menubar_file_minimise_to_system_tray.setVisible( ClientGUISystemTray.SystemTrayAvailable() and windows_or_advanced_non_windows )
return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
- def _InitialiseMenubarGetPagesMenuUpdater( self ):
+ def _InitialiseMenubarGetMenuUpdaterNetwork( self ):
def loading_callable():
- self.DisableMenu( 'pages' )
+ pass
+
+
+ def work_callable():
+
+ return 1
+
+
+ def publish_callable( result ):
+
+ advanced_mode = self._controller.new_options.GetBoolean( 'advanced_mode' )
+
+ self._menubar_network_nudge_subs.setVisible( advanced_mode )
+
+ self._menubar_network_all_traffic_paused.setChecked( HG.client_controller.new_options.GetBoolean( 'pause_all_new_network_traffic' ) )
+
+ self._menubar_network_subscriptions_paused.setChecked( HC.options[ 'pause_subs_sync' ] )
+
+
+ return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
+
+
+ def _InitialiseMenubarGetMenuUpdaterPages( self ):
+
+ def loading_callable():
+
+ self._menubar_pages_sessions_submenu.setEnabled( False )
+ self._menubar_pages_search_submenu.setEnabled( False )
+ self._menubar_pages_petition_submenu.setEnabled( False )
def work_callable():
@@ -2168,21 +2369,169 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
def publish_callable( result ):
+ (
+ total_active_page_count,
+ total_active_num_hashes,
+ total_active_num_seeds,
+ total_closed_page_count,
+ total_closed_num_hashes,
+ total_closed_num_seeds
+ ) = self.GetTotalPageCounts()
+
+ total_active_weight = ClientGUIPages.ConvertNumHashesAndSeedsToWeight( total_active_num_hashes, total_active_num_seeds )
+
+ if total_active_weight > 10000000 and self._controller.new_options.GetBoolean( 'show_session_size_warnings' ) and not self._have_shown_session_size_warning:
+
+ self._have_shown_session_size_warning = True
+
+ HydrusData.ShowText( 'Your session weight is {}, which is pretty big! To keep your UI lag-free, please try to close some pages or clear some finished downloaders!'.format( HydrusData.ToHumanInt( total_active_weight ) ) )
+
+
+ ClientGUIMenus.SetMenuItemLabel( self._menubar_pages_page_count, '{} pages open'.format( HydrusData.ToHumanInt( total_active_page_count ) ) )
+
+ ClientGUIMenus.SetMenuItemLabel( self._menubar_pages_session_weight, 'total session weight: {}'.format( HydrusData.ToHumanInt( total_active_weight ) ) )
+
+ #
+
( gui_session_names, gui_session_names_to_backup_timestamps ) = result
- ( menu, label ) = self.GenerateMenuInfoPages( gui_session_names, gui_session_names_to_backup_timestamps )
+ gui_session_names = sorted( gui_session_names )
- self.ReplaceMenu( 'pages', menu, label )
+ self._menubar_pages_sessions_submenu.setEnabled( True )
+
+ self._menubar_pages_sessions_submenu.clear()
+
+ if len( gui_session_names ) > 0:
+
+ load = QW.QMenu( self._menubar_pages_sessions_submenu )
+
+ for name in gui_session_names:
+
+ ClientGUIMenus.AppendMenuItem( load, name, 'Close all other pages and load this session.', self._notebook.LoadGUISession, name )
+
+
+ ClientGUIMenus.AppendMenu( self._menubar_pages_sessions_submenu, load, 'clear and load' )
+
+ append = QW.QMenu( self._menubar_pages_sessions_submenu )
+
+ for name in gui_session_names:
+
+ ClientGUIMenus.AppendMenuItem( append, name, 'Append this session to whatever pages are already open.', self._notebook.AppendGUISessionFreshest, name )
+
+
+ ClientGUIMenus.AppendMenu( self._menubar_pages_sessions_submenu, append, 'append' )
+
+ if len( gui_session_names_to_backup_timestamps ) > 0:
+
+ append_backup = QW.QMenu( self._menubar_pages_sessions_submenu )
+
+ rows = sorted( gui_session_names_to_backup_timestamps.items() )
+
+ for ( name, timestamps ) in rows:
+
+ submenu = QW.QMenu( append_backup )
+
+ for timestamp in timestamps:
+
+ ClientGUIMenus.AppendMenuItem( submenu, HydrusData.ConvertTimestampToPrettyTime( timestamp ), 'Append this backup session to whatever pages are already open.', self._notebook.AppendGUISessionBackup, name, timestamp )
+
+
+ ClientGUIMenus.AppendMenu( append_backup, submenu, name )
+
+
+ ClientGUIMenus.AppendMenu( self._menubar_pages_sessions_submenu, append_backup, 'append session backup' )
+
+
+
+ save = QW.QMenu( self._menubar_pages_sessions_submenu )
+
+ for name in gui_session_names:
+
+ if name in ClientGUISession.RESERVED_SESSION_NAMES:
+
+ continue
+
+
+ ClientGUIMenus.AppendMenuItem( save, name, 'Save the existing open pages as a session.', self.ProposeSaveGUISession, name )
+
+
+ ClientGUIMenus.AppendMenuItem( save, 'as new session', 'Save the existing open pages as a session.', self.ProposeSaveGUISession )
+
+ ClientGUIMenus.AppendMenu( self._menubar_pages_sessions_submenu, save, 'save' )
+
+ if len( set( gui_session_names ).difference( ClientGUISession.RESERVED_SESSION_NAMES ) ) > 0:
+
+ delete = QW.QMenu( self._menubar_pages_sessions_submenu )
+
+ for name in gui_session_names:
+
+ if name in ClientGUISession.RESERVED_SESSION_NAMES:
+
+ continue
+
+
+ ClientGUIMenus.AppendMenuItem( delete, name, 'Delete this session.', self._DeleteGUISession, name )
+
+
+ ClientGUIMenus.AppendMenu( self._menubar_pages_sessions_submenu, delete, 'delete' )
+
+
+ #
+
+ self._menubar_pages_search_submenu.setEnabled( True )
+
+ self._menubar_pages_search_submenu.clear()
+
+ services = self._controller.services_manager.GetServices()
+
+ local_file_services = [ service for service in services if service.GetServiceType() == HC.LOCAL_FILE_DOMAIN and service.GetServiceKey() != CC.LOCAL_UPDATE_SERVICE_KEY ]
+
+ for service in local_file_services:
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_pages_search_submenu, service.GetName(), 'Open a new search tab.', self._notebook.NewPageQuery, service.GetServiceKey(), on_deepest_notebook = True )
+
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_pages_search_submenu, 'trash', 'Open a new search tab for your recently deleted files.', self._notebook.NewPageQuery, CC.TRASH_SERVICE_KEY, on_deepest_notebook = True )
+
+ repositories = [ service for service in services if service.GetServiceType() in HC.REPOSITORIES ]
+
+ file_repositories = [ service for service in repositories if service.GetServiceType() == HC.FILE_REPOSITORY ]
+
+ for service in file_repositories:
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_pages_search_submenu, service.GetName(), 'Open a new search tab for ' + service.GetName() + '.', self._notebook.NewPageQuery, service.GetServiceKey(), on_deepest_notebook = True )
+
+
+ petition_permissions = [ ( content_type, HC.PERMISSION_ACTION_MODERATE ) for content_type in HC.SERVICE_TYPES_TO_CONTENT_TYPES ]
+
+ petition_resolvable_repositories = [ repository for repository in repositories if True in ( repository.HasPermission( content_type, action ) for ( content_type, action ) in petition_permissions ) ]
+
+ self._menubar_pages_petition_submenu.setEnabled( True )
+
+ self._menubar_pages_petition_submenu.clear()
+
+ self._menubar_pages_petition_submenu.menuAction().setVisible( len( petition_resolvable_repositories ) > 0 )
+
+ for service in petition_resolvable_repositories:
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_pages_petition_submenu, service.GetName(), 'Open a new petition page for ' + service.GetName() + '.', self._notebook.NewPagePetitions, service.GetServiceKey(), on_deepest_notebook = True )
+
+
+ self._menubar_pages_download_popup_submenu.setEnabled( True )
+
+ has_ipfs = len( [ service for service in services if service.GetServiceType() == HC.IPFS ] )
+
+ self._menubar_pages_download_popup_submenu.menuAction().setVisible( has_ipfs )
return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
- def _InitialiseMenubarGetPendingMenuUpdater( self ):
+ def _InitialiseMenubarGetMenuUpdaterPending( self ):
def loading_callable():
- self.DisableMenu( 'pending' )
+ pass
def work_callable():
@@ -2196,52 +2545,953 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
nums_pending = result
- ( menu_or_none, label ) = self.GenerateMenuInfoPending( nums_pending )
+ total_num_pending = 0
- self.ReplaceMenu( 'pending', menu_or_none, label )
+ for service_key in nums_pending.keys():
+
+ if service_key not in self._pending_service_keys_to_submenus:
+
+ service = self._controller.services_manager.GetService( service_key )
+
+ name = service.GetName()
+
+ submenu = QW.QMenu( self._menubar_pending_submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'commit', 'Upload {}\'s pending content.'.format( name ), self._UploadPending, service_key )
+ ClientGUIMenus.AppendMenuItem( submenu, 'forget', 'Clear {}\'s pending content.'.format( name ), self._DeletePending, service_key )
+
+ ClientGUIMenus.SetMenuTitle( submenu, name )
+
+ insert_before_action = None
+
+ for action in self._menubar_pending_submenu.actions():
+
+ if action.text() > name:
+
+ insert_before_action = action
+
+ break
+
+
+
+ if insert_before_action is None:
+
+ self._menubar_pending_submenu.addMenu( submenu )
+
+ else:
+
+ self._menubar_pending_submenu.insertMenu( insert_before_action, submenu )
+
+
+ self._pending_service_keys_to_submenus[ service_key ] = submenu
+
+
+
+ for ( service_key, submenu ) in self._pending_service_keys_to_submenus.items():
+
+ num_pending = 0
+ num_petitioned = 0
+
+ if service_key in nums_pending:
+
+ info = nums_pending[ service_key ]
+
+ service = self._controller.services_manager.GetService( service_key )
+
+ service_type = service.GetServiceType()
+ name = service.GetName()
+
+ if service_type == HC.TAG_REPOSITORY:
+
+ pending_phrase = 'tag data to upload'
+ petitioned_phrase = 'tag data to petition'
+
+ elif service_type == HC.FILE_REPOSITORY:
+
+ pending_phrase = 'files to upload'
+ petitioned_phrase = 'files to petition'
+
+ elif service_type == HC.IPFS:
+
+ pending_phrase = 'files to pin'
+ petitioned_phrase = 'files to unpin'
+
+
+ if service_type == HC.TAG_REPOSITORY:
+
+ num_pending = info[ HC.SERVICE_INFO_NUM_PENDING_MAPPINGS ] + info[ HC.SERVICE_INFO_NUM_PENDING_TAG_SIBLINGS ] + info[ HC.SERVICE_INFO_NUM_PENDING_TAG_PARENTS ]
+ num_petitioned = info[ HC.SERVICE_INFO_NUM_PETITIONED_MAPPINGS ] + info[ HC.SERVICE_INFO_NUM_PETITIONED_TAG_SIBLINGS ] + info[ HC.SERVICE_INFO_NUM_PETITIONED_TAG_PARENTS ]
+
+ elif service_type in ( HC.FILE_REPOSITORY, HC.IPFS ):
+
+ num_pending = info[ HC.SERVICE_INFO_NUM_PENDING_FILES ]
+ num_petitioned = info[ HC.SERVICE_INFO_NUM_PETITIONED_FILES ]
+
+
+ if num_pending + num_petitioned > 0:
+
+ submessages = []
+
+ if num_pending > 0:
+
+ submessages.append( '{} {}'.format( HydrusData.ToHumanInt( num_pending ), pending_phrase ) )
+
+
+ if num_petitioned > 0:
+
+ submessages.append( '{} {}'.format( HydrusData.ToHumanInt( num_petitioned ), petitioned_phrase ) )
+
+
+ title = '{}: {}'.format( name, ', '.join( submessages ) )
+
+ ClientGUIMenus.SetMenuTitle( submenu, title )
+
+
+
+ submenu.menuAction().setVisible( num_pending + num_petitioned > 0 )
+
+ total_num_pending += num_pending + num_petitioned
+
+
+ ClientGUIMenus.SetMenuTitle( self._menubar_pending_submenu, 'pending ({})'.format( HydrusData.ToHumanInt( total_num_pending ) ) )
+
+ self._menubar_pending_submenu.menuAction().setVisible( total_num_pending > 0 )
return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
- def _InitialiseMenubar( self ):
+ def _InitialiseMenubarGetMenuUpdaterServices( self ):
- self._menubar = QW.QMenuBar( self )
-
- self._menubar.setNativeMenuBar( False )
-
- self._menu_updater = ClientGUIAsync.FastThreadToGUIUpdater( self._menubar, self.RefreshMenu )
- self._dirty_menus = set()
-
- self._menu_updater_file = self._InitialiseMenubarGetFileMenuUpdater()
- self._menu_updater_pages = self._InitialiseMenubarGetPagesMenuUpdater()
- self._menu_updater_pending = self._InitialiseMenubarGetPendingMenuUpdater()
-
- self._boned_updater = self._InitialiseMenubarGetBonesUpdater()
-
- self.setMenuBar( self._menubar )
-
- for name in MENU_ORDER:
+ def loading_callable():
- if name == 'file':
+ self._menubar_services_admin_submenu.setEnabled( False )
+
+
+ def work_callable():
+
+ return 1
+
+
+ def publish_callable( result ):
+
+ self._menubar_services_admin_submenu.setEnabled( True )
+
+ self._menubar_services_admin_submenu.clear()
+
+ repository_admin_permissions = [ ( HC.CONTENT_TYPE_ACCOUNTS, HC.PERMISSION_ACTION_CREATE ), ( HC.CONTENT_TYPE_ACCOUNTS, HC.PERMISSION_ACTION_MODERATE ), ( HC.CONTENT_TYPE_ACCOUNT_TYPES, HC.PERMISSION_ACTION_MODERATE ), ( HC.CONTENT_TYPE_OPTIONS, HC.PERMISSION_ACTION_MODERATE ) ]
+
+ repositories = self._controller.services_manager.GetServices( HC.REPOSITORIES )
+ admin_repositories = [ service for service in repositories if True in ( service.HasPermission( content_type, action ) for ( content_type, action ) in repository_admin_permissions ) ]
+
+ servers_admin = self._controller.services_manager.GetServices( ( HC.SERVER_ADMIN, ) )
+ server_admins = [ service for service in servers_admin if service.HasPermission( HC.CONTENT_TYPE_SERVICES, HC.PERMISSION_ACTION_MODERATE ) ]
+
+ admin_services = admin_repositories + server_admins
+
+ if len( admin_services ) > 0:
- self._menu_updater_file.update()
-
- elif name == 'pages':
-
- self._menu_updater_pages.update()
-
- elif name == 'pending':
-
- self._menu_updater_pending.update()
-
- else:
-
- ( menu_or_none, label ) = self.GenerateMenuInfo( name )
-
- self.ReplaceMenu( name, menu_or_none, label )
+ for service in admin_services:
+
+ submenu = QW.QMenu( self._menubar_services_admin_submenu )
+
+ service_key = service.GetServiceKey()
+
+ service_type = service.GetServiceType()
+
+ can_create_accounts = service.HasPermission( HC.CONTENT_TYPE_ACCOUNTS, HC.PERMISSION_ACTION_CREATE )
+ can_overrule_accounts = service.HasPermission( HC.CONTENT_TYPE_ACCOUNTS, HC.PERMISSION_ACTION_MODERATE )
+ can_overrule_account_types = service.HasPermission( HC.CONTENT_TYPE_ACCOUNT_TYPES, HC.PERMISSION_ACTION_MODERATE )
+ can_overrule_services = service.HasPermission( HC.CONTENT_TYPE_SERVICES, HC.PERMISSION_ACTION_MODERATE )
+ can_overrule_options = service.HasPermission( HC.CONTENT_TYPE_OPTIONS, HC.PERMISSION_ACTION_MODERATE )
+
+ if can_overrule_accounts:
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'review all accounts', 'See all accounts.', self._STARTReviewAllAccounts, service_key )
+ ClientGUIMenus.AppendMenuItem( submenu, 'modify an account', 'Modify a specific account\'s type and expiration.', self._ModifyAccount, service_key )
+
+
+ if can_overrule_accounts and service_type == HC.FILE_REPOSITORY:
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'get an uploader\'s ip address', 'Fetch the ip address that uploaded a specific file, if the service knows it.', self._FetchIP, service_key )
+
+
+ if can_create_accounts:
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'create new accounts', 'Create new accounts for this service.', self._GenerateNewAccounts, service_key )
+
+
+ if can_overrule_account_types:
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage account types', 'Add, edit and delete account types for this service.', self._STARTManageAccountTypes, service_key )
+
+
+ if can_overrule_options and service_type in HC.REPOSITORIES:
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'change update period', 'Change the update period for this service.', self._ManageServiceOptionsUpdatePeriod, service_key )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'change anonymisation period', 'Change the account history nullification period for this service.', self._ManageServiceOptionsNullificationPeriod, service_key )
+
+
+ if can_overrule_services and service_type == HC.SERVER_ADMIN:
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage services', 'Add, edit, and delete this server\'s services.', self._ManageServer, service_key )
+ ClientGUIMenus.AppendSeparator( submenu )
+ ClientGUIMenus.AppendMenuItem( submenu, 'backup server', 'Command the server to temporarily pause and back up its database.', self._BackupServer, service_key )
+ ClientGUIMenus.AppendSeparator( submenu )
+ ClientGUIMenus.AppendMenuItem( submenu, 'vacuum server', 'Command the server to temporarily pause and vacuum its database.', self._VacuumServer, service_key )
+ ClientGUIMenus.AppendSeparator( submenu )
+ ClientGUIMenus.AppendMenuItem( submenu, 'server/db lock: on', 'Command the server to lock itself and disconnect its db.', self._LockServer, service_key, True )
+ ClientGUIMenus.AppendMenuItem( submenu, 'server/db lock: test', 'See if the server is currently busy.', self._TestServerBusy, service_key )
+ ClientGUIMenus.AppendMenuItem( submenu, 'server/db lock: off', 'Command the server to unlock itself and resume its db.', self._LockServer, service_key, False )
+
+
+ ClientGUIMenus.AppendMenu( self._menubar_services_admin_submenu, submenu, service.GetName() )
+
+ self._menubar_services_admin_submenu.menuAction().setVisible( len( admin_services ) > 0 )
+
+
+ return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
+
+
+ def _InitialiseMenubarGetMenuUpdaterUndo( self ):
+
+ def loading_callable():
+
+ self._menubar_undo_closed_pages_submenu.setEnabled( False )
+
+
+ def work_callable():
+
+ return 1
+
+
+ def publish_callable( result ):
+
+ have_closed_pages = len( self._closed_pages ) > 0
+
+ undo_manager = self._controller.GetManager( 'undo' )
+
+ ( undo_string, redo_string ) = undo_manager.GetUndoRedoStrings()
+
+ have_undo_stuff = undo_string is not None or redo_string is not None
+
+ if have_closed_pages or have_undo_stuff:
+
+ self._menubar_undo_undo.setVisible( undo_string is not None )
+
+ if undo_string is not None:
+
+ ClientGUIMenus.SetMenuItemLabel( self._menubar_undo_undo, undo_string )
+
+
+ self._menubar_undo_redo.setVisible( redo_string is not None )
+
+ if redo_string is not None:
+
+ ClientGUIMenus.SetMenuItemLabel( self._menubar_undo_redo, redo_string )
+
+
+ self._menubar_undo_closed_pages_submenu.setEnabled( True )
+
+ self._menubar_undo_closed_pages_submenu.clear()
+
+ self._menubar_undo_closed_pages_submenu.menuAction().setVisible( have_closed_pages )
+
+ if have_closed_pages:
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_undo_closed_pages_submenu, 'clear all', 'Remove all closed pages from memory.', self.AskToDeleteAllClosedPages )
+
+ self._menubar_undo_closed_pages_submenu.addSeparator()
+
+ args = []
+
+ for ( i, ( time_closed, page ) ) in enumerate( self._closed_pages ):
+
+ name = page.GetName()
+
+ args.append( ( i, name + ' - ' + page.GetPrettyStatus() ) )
+
+
+ args.reverse() # so that recently closed are at the top
+
+ for ( index, name ) in args:
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_undo_closed_pages_submenu, name, 'Restore this page.', self._UnclosePage, index )
+
+
+
+
+ self._menubar_undo_submenu.menuAction().setVisible( have_closed_pages or have_undo_stuff )
+
+
+ return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
+
+
+ def _InitialiseMenuInfoDatabase( self ):
+
+ menu = QW.QMenu( self )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'set a password', 'Set a simple password for the database so only you can open it in the client.', self._SetPassword )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ self._menubar_database_set_up_backup_path = ClientGUIMenus.AppendMenuItem( menu, 'set up a database backup location', 'Choose a path to back the database up to.', self._SetupBackupPath )
+ self._menubar_database_update_backup = ClientGUIMenus.AppendMenuItem( menu, 'update database backup', 'Back the database up to an external location.', self._BackupDatabase )
+ self._menubar_database_change_backup_path = ClientGUIMenus.AppendMenuItem( menu, 'change database backup location', 'Choose a path to back the database up to.', self._SetupBackupPath )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ self._menubar_database_restore_backup = ClientGUIMenus.AppendMenuItem( menu, 'restore from a database backup', 'Restore the database from an external location.', self._controller.RestoreDatabase )
+
+ message = 'Your database is stored across multiple locations, which disables my internal backup routine. To back up, please use a third-party program that will work better than anything I can write.'
+ message += os.linesep * 2
+ message += 'Please check the help for more info on how best to backup manually.'
+
+ self._menubar_database_multiple_location_label = ClientGUIMenus.AppendMenuItem( menu, 'database is stored in multiple locations', 'The database is migrated.', HydrusData.ShowText, message )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'migrate database', 'Review and manage the locations your database is stored.', self._MigrateDatabase )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ file_maintenance_menu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( file_maintenance_menu, 'manage scheduled jobs', 'Review outstanding jobs, and schedule new ones.', self._ReviewFileMaintenance )
+ ClientGUIMenus.AppendSeparator( file_maintenance_menu )
+
+ check_manager = ClientGUICommon.CheckboxManagerOptions( 'file_maintenance_during_idle' )
+
+ current_value = check_manager.GetCurrentValue()
+ func = check_manager.Invert
+
+ ClientGUIMenus.AppendMenuCheckItem( file_maintenance_menu, 'work file jobs during idle time', 'Control whether file maintenance can work during idle time.', current_value, func )
+
+ check_manager = ClientGUICommon.CheckboxManagerOptions( 'file_maintenance_during_active' )
+
+ current_value = check_manager.GetCurrentValue()
+ func = check_manager.Invert
+
+ ClientGUIMenus.AppendMenuCheckItem( file_maintenance_menu, 'work file jobs during normal time', 'Control whether file maintenance can work during normal time.', current_value, func )
+
+ ClientGUIMenus.AppendMenu( menu, file_maintenance_menu, 'file maintenance' )
+
+ maintenance_submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'analyze', 'Optimise slow queries by running statistical analyses on the database.', self._AnalyzeDatabase )
+ ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'review vacuum data', 'See whether it is worth rebuilding the database to reformat tables and recover disk space.', self._ReviewVacuumData )
+
+ ClientGUIMenus.AppendSeparator( maintenance_submenu )
+
+ ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'clear orphan files', 'Clear out surplus files that have found their way into the file structure.', self._ClearOrphanFiles )
+ ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'clear orphan file records', 'Clear out surplus file records that have not been deleted correctly.', self._ClearOrphanFileRecords )
+
+ ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'clear orphan tables', 'Clear out surplus db tables that have not been deleted correctly.', self._ClearOrphanTables )
+
+ ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'clear orphan hashed serialisables', 'Clear non-needed cached hashed serialisable objects.', self._ClearOrphanHashedSerialisables )
+
+ ClientGUIMenus.AppendMenu( menu, maintenance_submenu, 'db maintenance' )
+
+ check_submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( check_submenu, 'database integrity', 'Have the database examine all its records for internal consistency.', self._CheckDBIntegrity )
+ ClientGUIMenus.AppendMenuItem( check_submenu, 'repopulate truncated mappings tables', 'Use the mappings cache to try to repair a previously damaged mappings file.', self._RepopulateMappingsTables )
+ ClientGUIMenus.AppendMenuItem( check_submenu, 'fix logically inconsistent mappings', 'Remove tags that are occupying two mutually exclusive states.', self._FixLogicallyInconsistentMappings )
+ ClientGUIMenus.AppendMenuItem( check_submenu, 'fix invalid tags', 'Scan the database for invalid tags.', self._RepairInvalidTags )
+
+ ClientGUIMenus.AppendMenu( menu, check_submenu, 'check and repair' )
+
+ regen_submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'total pending count, in the pending menu', 'Regenerate the pending count up top.', self._DeleteServiceInfo, only_pending = True )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag storage mappings cache (all, with deferred siblings & parents calculation)', 'Delete and recreate the tag mappings cache, fixing bad tags or miscounts.', self._RegenerateTagMappingsCache )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag storage mappings cache (just pending tags, instant calculation)', 'Delete and recreate the tag pending mappings cache, fixing bad tags or miscounts.', self._RegenerateTagPendingMappingsCache )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag display mappings cache (all, deferred siblings & parents calculation)', 'Delete and recreate the tag display mappings cache, fixing bad tags or miscounts.', self._RegenerateTagDisplayMappingsCache )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag display mappings cache (just pending tags, instant calculation)', 'Delete and recreate the tag display pending mappings cache, fixing bad tags or miscounts.', self._RegenerateTagDisplayPendingMappingsCache )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag siblings lookup cache', 'Delete and recreate the tag siblings cache.', self._RegenerateTagSiblingsLookupCache )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag parents lookup cache', 'Delete and recreate the tag siblings cache.', self._RegenerateTagParentsLookupCache )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag text search cache', 'Delete and regenerate the cache hydrus uses for fast tag search.', self._RegenerateTagCache )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag text search cache (subtags repopulation)', 'Repopulate the subtags for the cache hydrus uses for fast tag search.', self._RepopulateTagCacheMissingSubtags )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag text search cache (searchable subtag maps)', 'Regenerate the searchable subtag maps.', self._RegenerateTagCacheSearchableSubtagsMaps )
+
+ ClientGUIMenus.AppendSeparator( regen_submenu )
+
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'local hash cache', 'Repopulate the cache hydrus uses for fast hash lookup for local files.', self._RegenerateLocalHashCache )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'local tag cache', 'Repopulate the cache hydrus uses for fast tag lookup for local files.', self._RegenerateLocalTagCache )
+
+ ClientGUIMenus.AppendSeparator( regen_submenu )
+
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'clear service info cache', 'Delete all cached service info like total number of mappings or files, in case it has become desynchronised. Some parts of the gui may be laggy immediately after this as these numbers are recalculated.', self._DeleteServiceInfo )
+ ClientGUIMenus.AppendMenuItem( regen_submenu, 'similar files search tree', 'Delete and recreate the similar files search tree.', self._RegenerateSimilarFilesTree )
+
+ ClientGUIMenus.AppendMenu( menu, regen_submenu, 'regenerate' )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ file_viewing_submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( file_viewing_submenu, 'clear all file viewing statistics', 'Delete all file viewing records from the database.', self._ClearFileViewingStats )
+ ClientGUIMenus.AppendMenuItem( file_viewing_submenu, 'cull file viewing statistics based on current min/max values', 'Cull your file viewing statistics based on minimum and maximum permitted time deltas.', self._CullFileViewingStats )
+
+ ClientGUIMenus.AppendMenu( menu, file_viewing_submenu, 'file viewing statistics' )
+
+ return ( menu, '&database' )
+
+
+ def _InitialiseMenuInfoFile( self ):
+
+ menu = QW.QMenu( self )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'import files', 'Add new files to the database.', self._ImportFiles )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ #
+
+ i_and_e_submenu = QW.QMenu( menu )
+
+ submenu = QW.QMenu( i_and_e_submenu )
+
+ ClientGUIMenus.AppendMenuCheckItem( submenu, 'import folders', 'Pause the client\'s import folders.', HC.options['pause_import_folders_sync'], self._PausePlaySync, 'import_folders' )
+ ClientGUIMenus.AppendMenuCheckItem( submenu, 'export folders', 'Pause the client\'s export folders.', HC.options['pause_export_folders_sync'], self._PausePlaySync, 'export_folders' )
+
+ ClientGUIMenus.AppendMenu( i_and_e_submenu, submenu, 'pause' )
+
+ ClientGUIMenus.AppendSeparator( i_and_e_submenu )
+
+ self._menubar_file_import_submenu = QW.QMenu( i_and_e_submenu )
+
+ ClientGUIMenus.AppendMenu( i_and_e_submenu, self._menubar_file_import_submenu, 'check import folder now' )
+
+ self._menubar_file_export_submenu = QW.QMenu( i_and_e_submenu )
+
+ ClientGUIMenus.AppendMenu( i_and_e_submenu, self._menubar_file_export_submenu, 'run export folder now' )
+
+ ClientGUIMenus.AppendSeparator( i_and_e_submenu )
+
+ ClientGUIMenus.AppendMenuItem( i_and_e_submenu, 'manage import folders', 'Manage folders from which the client can automatically import.', self._ManageImportFolders )
+ ClientGUIMenus.AppendMenuItem( i_and_e_submenu, 'manage export folders', 'Manage folders to which the client can automatically export.', self._ManageExportFolders )
+
+ ClientGUIMenus.AppendMenu( menu, i_and_e_submenu, 'import and export folders' )
+
+ #
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ open = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( open, 'installation directory', 'Open the installation directory for this client.', self._OpenInstallFolder )
+ ClientGUIMenus.AppendMenuItem( open, 'database directory', 'Open the database directory for this instance of the client.', self._OpenDBFolder )
+ ClientGUIMenus.AppendMenuItem( open, 'quick export directory', 'Open the export directory so you can easily access the files you have exported.', self._OpenExportFolder )
+
+ ClientGUIMenus.AppendMenu( menu, open, 'open' )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'options', 'Change how the client operates.', self._ManageOptions )
+ ClientGUIMenus.AppendMenuItem( menu, 'shortcuts', 'Edit the shortcuts your client responds to.', ClientGUIShortcutControls.ManageShortcuts, self )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ label = 'minimise to system tray'
+
+ if not HC.PLATFORM_WINDOWS:
+
+ label += ' (may be buggy/crashy!)'
+
+
+ self._menubar_file_minimise_to_system_tray = ClientGUIMenus.AppendMenuItem( menu, label, 'Hide the client to an icon on your system tray.', self._FlipShowHideWholeUI )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ we_borked_linux_pyinstaller = HC.PLATFORM_LINUX and not HC.RUNNING_FROM_SOURCE
+
+ if not we_borked_linux_pyinstaller:
+
+ ClientGUIMenus.AppendMenuItem( menu, 'restart', 'Shut the client down and then start it up again.', self.TryToExit, restart = True )
+
+
+ ClientGUIMenus.AppendMenuItem( menu, 'exit and force shutdown maintenance', 'Shut the client down and force any outstanding shutdown maintenance to run.', self.TryToExit, force_shutdown_maintenance = True )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'exit', 'Shut the client down.', self.TryToExit )
+
+ return ( menu, '&file' )
+
+
+ def _InitialiseMenuInfoHelp( self ):
+
+ menu = QW.QMenu( self )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'help and getting started guide', 'Open hydrus\'s local help in your web browser.', ClientPaths.LaunchPathInWebBrowser, os.path.join( HC.HELP_DIR, 'index.html' ) )
+
+ links = QW.QMenu( menu )
+
+ site = ClientGUIMenus.AppendMenuBitmapItem( links, 'site', 'Open hydrus\'s website, which is mostly a mirror of the local help.', CC.global_pixmaps().file_repository, ClientPaths.LaunchURLInWebBrowser, 'https://hydrusnetwork.github.io/hydrus/' )
+ site = ClientGUIMenus.AppendMenuBitmapItem( links, 'github repository', 'Open the hydrus github repository.', CC.global_pixmaps().github, ClientPaths.LaunchURLInWebBrowser, 'https://github.com/hydrusnetwork/hydrus' )
+ site = ClientGUIMenus.AppendMenuBitmapItem( links, 'latest build', 'Open the latest build on the hydrus github repository.', CC.global_pixmaps().github, ClientPaths.LaunchURLInWebBrowser, 'https://github.com/hydrusnetwork/hydrus/releases/latest' )
+ site = ClientGUIMenus.AppendMenuBitmapItem( links, 'issue tracker', 'Open the github issue tracker, which is run by users.', CC.global_pixmaps().github, ClientPaths.LaunchURLInWebBrowser, 'https://github.com/hydrusnetwork/hydrus/issues' )
+ site = ClientGUIMenus.AppendMenuBitmapItem( links, '8chan.moe /t/ (Hydrus Network General)', 'Open the 8chan.moe /t/ board, where a Hydrus Network General should exist with release posts and other status updates.', CC.global_pixmaps().eight_chan, ClientPaths.LaunchURLInWebBrowser, 'https://8chan.moe/t/catalog.html' )
+ site = ClientGUIMenus.AppendMenuItem( links, 'Endchan board bunker', 'Open hydrus dev\'s Endchan board, the bunker for the case when 8chan.moe is unavailable. Try .org if .net is unavailable.', ClientPaths.LaunchURLInWebBrowser, 'https://endchan.net/hydrus/index.html' )
+ site = ClientGUIMenus.AppendMenuBitmapItem( links, 'twitter', 'Open hydrus dev\'s twitter, where he makes general progress updates and emergency notifications.', CC.global_pixmaps().twitter, ClientPaths.LaunchURLInWebBrowser, 'https://twitter.com/hydrusnetwork' )
+ site = ClientGUIMenus.AppendMenuBitmapItem( links, 'tumblr', 'Open hydrus dev\'s tumblr, where he makes release posts and other status updates.', CC.global_pixmaps().tumblr, ClientPaths.LaunchURLInWebBrowser, 'https://hydrus.tumblr.com/' )
+ site = ClientGUIMenus.AppendMenuBitmapItem( links, 'discord', 'Open a discord channel where many hydrus users congregate. Hydrus dev visits regularly.', CC.global_pixmaps().discord, ClientPaths.LaunchURLInWebBrowser, 'https://discord.gg/wPHPCUZ' )
+ site = ClientGUIMenus.AppendMenuBitmapItem( links, 'patreon', 'Open hydrus dev\'s patreon, which lets you support development.', CC.global_pixmaps().patreon, ClientPaths.LaunchURLInWebBrowser, 'https://www.patreon.com/hydrus_dev' )
+
+ ClientGUIMenus.AppendMenu( menu, links, 'links' )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'changelog', 'Open hydrus\'s local changelog in your web browser.', ClientPaths.LaunchPathInWebBrowser, os.path.join( HC.HELP_DIR, 'changelog.html' ) )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'add the public tag repository', 'This will add the public tag repository to your client.', self._AutoRepoSetup )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'how boned am I?', 'Check for a summary of your ride so far.', self._HowBonedAmI )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ currently_darkmode = self._new_options.GetString( 'current_colourset' ) == 'darkmode'
+
+ ClientGUIMenus.AppendMenuCheckItem( menu, 'darkmode', 'Set the \'darkmode\' colourset on and off.', currently_darkmode, self.FlipDarkmode )
+
+ check_manager = ClientGUICommon.CheckboxManagerOptions( 'advanced_mode' )
+
+ current_value = check_manager.GetCurrentValue()
+ func = check_manager.Invert
+
+ ClientGUIMenus.AppendMenuCheckItem( menu, 'advanced mode', 'Turn on advanced menu options and buttons.', current_value, func )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ debug = QW.QMenu( menu )
+
+ debug_modes = QW.QMenu( debug )
+
+ ClientGUIMenus.AppendMenuCheckItem( debug_modes, 'force idle mode', 'Make the client consider itself idle and fire all maintenance routines right now. This may hang the gui for a while.', HG.force_idle_mode, self._SwitchBoolean, 'force_idle_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( debug_modes, 'no page limit mode', 'Let the user create as many pages as they want with no warnings or prohibitions.', HG.no_page_limit_mode, self._SwitchBoolean, 'no_page_limit_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( debug_modes, 'thumbnail debug mode', 'Show some thumbnail debug info.', HG.thumbnail_debug_mode, self._SwitchBoolean, 'thumbnail_debug_mode' )
+ ClientGUIMenus.AppendMenuItem( debug_modes, 'simulate a wake from sleep', 'Tell the controller to pretend that it just woke up from sleep.', self._controller.SimulateWakeFromSleepEvent )
+
+ ClientGUIMenus.AppendMenu( debug, debug_modes, 'debug modes' )
+
+ profiling = QW.QMenu( debug )
+
+ profile_mode_message = 'If something is running slow, you can turn on profile mode to have hydrus gather information on how long many jobs take to run.'
+ profile_mode_message += os.linesep * 2
+ profile_mode_message += 'Turn the mode on, do the slow thing for a bit, and then turn it off. In your database directory will be a new profile log, which is really helpful for hydrus dev to figure out what is running slow for you and how to fix it.'
+ profile_mode_message += os.linesep * 2
+ profile_mode_message += 'A new Query Planner mode also makes very detailed database analysis. This is an alternate profiling mode hydev is testing.'
+ profile_mode_message += os.linesep * 2
+ profile_mode_message += 'More information is available in the help, under \'reducing program lag\'.'
+
+ ClientGUIMenus.AppendMenuItem( profiling, 'what is this?', 'Show profile info.', QW.QMessageBox.information, self, 'Profile modes', profile_mode_message )
+ ClientGUIMenus.AppendMenuCheckItem( profiling, 'profile mode', 'Run detailed \'profiles\'.', HG.profile_mode, self._SwitchBoolean, 'profile_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( profiling, 'query planner mode', 'Run detailed \'query plans\'.', HG.query_planner_mode, self._SwitchBoolean, 'query_planner_mode' )
+
+ ClientGUIMenus.AppendMenu( debug, profiling, 'profiling' )
+
+ report_modes = QW.QMenu( debug )
+
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'cache report mode', 'Have the image and thumb caches report their operation.', HG.cache_report_mode, self._SwitchBoolean, 'cache_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'callto report mode', 'Report whenever the thread pool is given a task.', HG.callto_report_mode, self._SwitchBoolean, 'callto_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'canvas tile borders mode', 'Draw tile borders.', HG.canvas_tile_outline_mode, self._SwitchBoolean, 'canvas_tile_outline_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'daemon report mode', 'Have the daemons report whenever they fire their jobs.', HG.daemon_report_mode, self._SwitchBoolean, 'daemon_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'db report mode', 'Have the db report query information, where supported.', HG.db_report_mode, self._SwitchBoolean, 'db_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'file import report mode', 'Have the db and file manager report file import progress.', HG.file_import_report_mode, self._SwitchBoolean, 'file_import_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'file report mode', 'Have the file manager report file request information, where supported.', HG.file_report_mode, self._SwitchBoolean, 'file_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'gui report mode', 'Have the gui report inside information, where supported.', HG.gui_report_mode, self._SwitchBoolean, 'gui_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'hover window report mode', 'Have the hover windows report their show/hide logic.', HG.hover_window_report_mode, self._SwitchBoolean, 'hover_window_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'media load report mode', 'Have the client report media load information, where supported.', HG.media_load_report_mode, self._SwitchBoolean, 'media_load_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'mpv report mode', 'Have the client report significant mpv debug information.', HG.mpv_report_mode, self._SwitchBoolean, 'mpv_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'network report mode', 'Have the network engine report new jobs.', HG.network_report_mode, self._SwitchBoolean, 'network_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'pubsub report mode', 'Report info about every pubsub processed.', HG.pubsub_report_mode, self._SwitchBoolean, 'pubsub_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'similar files metadata generation report mode', 'Have the phash generation routine report its progress.', HG.phash_generation_report_mode, self._SwitchBoolean, 'phash_generation_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'shortcut report mode', 'Have the new shortcut system report what shortcuts it catches and whether it matches an action.', HG.shortcut_report_mode, self._SwitchBoolean, 'shortcut_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'subprocess report mode', 'Report whenever an external process is called.', HG.subprocess_report_mode, self._SwitchBoolean, 'subprocess_report_mode' )
+ ClientGUIMenus.AppendMenuCheckItem( report_modes, 'subscription report mode', 'Have the subscription system report what it is doing.', HG.subscription_report_mode, self._SwitchBoolean, 'subscription_report_mode' )
+
+ ClientGUIMenus.AppendMenu( debug, report_modes, 'report modes' )
+
+ gui_actions = QW.QMenu( debug )
+
+ default_local_file_service_key = HG.client_controller.services_manager.GetDefaultLocalFileServiceKey()
+
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'make some popups', 'Throw some varied popups at the message manager, just to check it is working.', self._DebugMakeSomePopups )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'make a long text popup', 'Make a popup with text that will grow in size.', self._DebugLongTextPopup )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'make a popup in five seconds', 'Throw a delayed popup at the message manager, giving you time to minimise or otherwise alter the client before it arrives.', self._controller.CallLater, 5, HydrusData.ShowText, 'This is a delayed popup message.' )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'make a modal popup in five seconds', 'Throw up a delayed modal popup to test with. It will stay alive for five seconds.', self._DebugMakeDelayedModalPopup, True )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'make a non-cancellable modal popup in five seconds', 'Throw up a delayed modal popup to test with. It will stay alive for five seconds.', self._DebugMakeDelayedModalPopup, False )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'make a new page in five seconds', 'Throw a delayed page at the main notebook, giving you time to minimise or otherwise alter the client before it arrives.', self._controller.CallLater, 5, self._controller.pub, 'new_page_query', default_local_file_service_key )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'refresh pages menu in five seconds', 'Delayed refresh the pages menu, giving you time to minimise or otherwise alter the client before it arrives.', self._controller.CallLater, 5, self._menu_updater_pages.update )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'publish some sub files in five seconds', 'Publish some files like a subscription would.', self._controller.CallLater, 5, lambda: HG.client_controller.pub( 'imported_files_to_page', [ HydrusData.GenerateKey() for i in range( 5 ) ], 'example sub files' ) )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'make a parentless text ctrl dialog', 'Make a parentless text control in a dialog to test some character event catching.', self._DebugMakeParentlessTextCtrl )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'reset multi-column list settings to default', 'Reset all multi-column list widths and other display settings to default.', self._DebugResetColumnListManager )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'force a main gui layout now', 'Tell the gui to relayout--useful to test some gui bootup layout issues.', self.adjustSize )
+ ClientGUIMenus.AppendMenuItem( gui_actions, 'save \'last session\' gui session', 'Make an immediate save of the \'last session\' gui session. Mostly for testing crashes, where last session is not saved correctly.', self.ProposeSaveGUISession, CC.LAST_SESSION_SESSION_NAME )
+
+ ClientGUIMenus.AppendMenu( debug, gui_actions, 'gui actions' )
+
+ data_actions = QW.QMenu( debug )
+
+ ClientGUIMenus.AppendMenuCheckItem( data_actions, 'db ui-hang relief mode', 'Have UI-synchronised database jobs process pending Qt events while they wait.', HG.db_ui_hang_relief_mode, self._SwitchBoolean, 'db_ui_hang_relief_mode' )
+ ClientGUIMenus.AppendMenuItem( data_actions, 'review threads', 'Show current threads and what they are doing.', self._ReviewThreads )
+ ClientGUIMenus.AppendMenuItem( data_actions, 'show scheduled jobs', 'Print some information about the currently scheduled jobs log.', self._DebugShowScheduledJobs )
+ ClientGUIMenus.AppendMenuItem( data_actions, 'subscription manager snapshot', 'Have the subscription system show what it is doing.', self._controller.subscriptions_manager.ShowSnapshot )
+ ClientGUIMenus.AppendMenuItem( data_actions, 'flush log', 'Command the log to write any buffered contents to hard drive.', HydrusData.DebugPrint, 'Flushing log' )
+ ClientGUIMenus.AppendMenuItem( data_actions, 'enable truncated image loading', 'Enable the truncated image loading to test out broken jpegs.', self._EnableLoadTruncatedImages )
+ ClientGUIMenus.AppendSeparator( data_actions )
+ ClientGUIMenus.AppendMenuItem( data_actions, 'simulate program quit signal', 'Kill the program via a QApplication quit.', QW.QApplication.instance().quit )
+
+ ClientGUIMenus.AppendMenu( debug, data_actions, 'data actions' )
+
+ memory_actions = QW.QMenu( debug )
+
+ ClientGUIMenus.AppendMenuItem( memory_actions, 'run fast memory maintenance', 'Tell all the fast caches to maintain themselves.', self._controller.MaintainMemoryFast )
+ ClientGUIMenus.AppendMenuItem( memory_actions, 'run slow memory maintenance', 'Tell all the slow caches to maintain themselves.', self._controller.MaintainMemorySlow )
+ ClientGUIMenus.AppendMenuItem( memory_actions, 'clear image rendering cache', 'Tell the image rendering system to forget all current images. This will often free up a bunch of memory immediately.', self._controller.ClearCaches )
+ ClientGUIMenus.AppendMenuItem( memory_actions, 'clear thumbnail cache', 'Tell the thumbnail cache to forget everything and redraw all current thumbs.', self._controller.pub, 'reset_thumbnail_cache' )
+ ClientGUIMenus.AppendMenuItem( memory_actions, 'print garbage', 'Print some information about the python garbage to the log.', self._DebugPrintGarbage )
+ ClientGUIMenus.AppendMenuItem( memory_actions, 'take garbage snapshot', 'Capture current garbage object counts.', self._DebugTakeGarbageSnapshot )
+ ClientGUIMenus.AppendMenuItem( memory_actions, 'show garbage snapshot changes', 'Show object count differences from the last snapshot.', self._DebugShowGarbageDifferences )
+
+ ClientGUIMenus.AppendMenu( debug, memory_actions, 'memory actions' )
+
+ network_actions = QW.QMenu( debug )
+
+ ClientGUIMenus.AppendMenuItem( network_actions, 'fetch a url', 'Fetch a URL using the network engine as per normal.', self._DebugFetchAURL )
+
+ ClientGUIMenus.AppendMenu( debug, network_actions, 'network actions' )
+
+ tests = QW.QMenu( debug )
+
+ ClientGUIMenus.AppendMenuItem( tests, 'run the ui test', 'Run hydrus_dev\'s weekly UI Test. Guaranteed to work and not mess up your session, ha ha.', self._RunUITest )
+ ClientGUIMenus.AppendMenuItem( tests, 'run the client api test', 'Run hydrus_dev\'s weekly Client API Test. Guaranteed to work and not mess up your session, ha ha.', self._RunClientAPITest )
+ ClientGUIMenus.AppendMenuItem( tests, 'run the server test', 'This will try to boot the server in your install folder and initialise it. This is mostly here for testing purposes.', self._RunServerTest )
+
+ ClientGUIMenus.AppendMenu( debug, tests, 'tests, do not touch' )
+
+ ClientGUIMenus.AppendMenu( menu, debug, 'debug' )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'about Qt', 'See information about the Qt framework.', QW.QMessageBox.aboutQt, self )
+ ClientGUIMenus.AppendMenuItem( menu, 'about', 'See this client\'s version and other information.', self._AboutWindow )
+
+ return ( menu, '&help' )
+
+
+ def _InitialiseMenuInfoNetwork( self ):
+
+ menu = QW.QMenu( self )
+
+ submenu = QW.QMenu( menu )
+
+ pause_all_new_network_traffic = self._controller.new_options.GetBoolean( 'pause_all_new_network_traffic' )
+
+ self._menubar_network_all_traffic_paused = ClientGUIMenus.AppendMenuCheckItem( submenu, 'all new network traffic', 'Stop any new network jobs from sending data.', pause_all_new_network_traffic, self.FlipNetworkTrafficPaused )
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ self._menubar_network_subscriptions_paused = ClientGUIMenus.AppendMenuCheckItem( submenu, 'subscriptions', 'Pause the client\'s synchronisation with website subscriptions.', HC.options[ 'pause_subs_sync' ], self.FlipSubscriptionsPaused )
+
+ self._menubar_network_nudge_subs = ClientGUIMenus.AppendMenuItem( submenu, 'nudge subscriptions awake', 'Tell the subs daemon to wake up, just in case any subs are due.', self._controller.subscriptions_manager.Wake )
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuCheckItem( submenu, 'paged file import queues', 'Pause all file import queues.', self._controller.new_options.GetBoolean( 'pause_all_file_queues' ), self._controller.new_options.FlipBoolean, 'pause_all_file_queues' )
+ ClientGUIMenus.AppendMenuCheckItem( submenu, 'gallery searches', 'Pause all gallery imports\' searching.', self._controller.new_options.GetBoolean( 'pause_all_gallery_searches' ), self._controller.new_options.FlipBoolean, 'pause_all_gallery_searches' )
+ ClientGUIMenus.AppendMenuCheckItem( submenu, 'watcher checkers', 'Pause all watchers\' checking.', self._controller.new_options.GetBoolean( 'pause_all_watcher_checkers' ), self._controller.new_options.FlipBoolean, 'pause_all_watcher_checkers' )
+
+ ClientGUIMenus.AppendMenu( menu, submenu, 'pause' )
+
+ #
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'manage subscriptions', 'Change the queries you want the client to regularly import from.', self._ManageSubscriptions )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'review bandwidth usage and edit rules', 'See where you are consuming data.', self._ReviewBandwidth )
+ ClientGUIMenus.AppendMenuItem( submenu, 'review current network jobs', 'Review the jobs currently running in the network engine.', self._ReviewNetworkJobs )
+ ClientGUIMenus.AppendMenuItem( submenu, 'review session cookies', 'Review and edit which cookies you have for which network contexts.', self._ReviewNetworkSessions )
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage http headers', 'Configure how the client talks to the network.', self._ManageNetworkHeaders )
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage upnp', 'If your router supports it, see and edit your current UPnP NAT traversal mappings.', self._ManageUPnP )
+
+ ClientGUIMenus.AppendMenu( menu, submenu, 'data' )
+
+ #
+
+ submenu = QW.QMenu( menu )
+
+ if not ClientParsing.HTML5LIB_IS_OK:
+
+ message = 'The client was unable to import html5lib on boot. This is an important parsing library that performs better than the usual backup, lxml. Without it, some downloaders will not work well and you will miss tags and files.'
+ message += os.linesep * 2
+ message += 'You are likely running from source, so I recommend you close the client, run \'pip install html5lib\' (or whatever is appropriate for your environment) and try again. You can double-check what imported ok under help->about.'
+
+ ClientGUIMenus.AppendMenuItem( submenu, '*** html5lib not found! ***', 'Your client does not have an important library.', QW.QMessageBox.warning, self, 'Warning', message )
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'import downloaders', 'Import new download capability through encoded pngs from other users.', self._ImportDownloaders )
+ ClientGUIMenus.AppendMenuItem( submenu, 'export downloaders', 'Export downloader components to easy-import pngs.', self._ExportDownloader )
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage default tag import options', 'Change the default tag import options for each of your linked url matches.', self._ManageDefaultTagImportOptions )
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage downloader and url display', 'Configure how downloader objects present across the client.', self._ManageDownloaderDisplay )
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ clipboard_menu = QW.QMenu( submenu )
+
+ ClientGUIMenus.AppendMenuCheckItem( clipboard_menu, 'watcher urls', 'Automatically import watcher URLs that enter the clipboard just as if you drag-and-dropped them onto the ui.', self._controller.new_options.GetBoolean( 'watch_clipboard_for_watcher_urls' ), self._FlipClipboardWatcher, 'watch_clipboard_for_watcher_urls' )
+ ClientGUIMenus.AppendMenuCheckItem( clipboard_menu, 'other recognised urls', 'Automatically import recognised URLs that enter the clipboard just as if you drag-and-dropped them onto the ui.', self._controller.new_options.GetBoolean( 'watch_clipboard_for_other_recognised_urls' ), self._FlipClipboardWatcher, 'watch_clipboard_for_other_recognised_urls' )
+
+ ClientGUIMenus.AppendMenu( submenu, clipboard_menu, 'watch clipboard for urls' )
+
+ ClientGUIMenus.AppendMenu( menu, submenu, 'downloaders' )
+
+ #
+
+ submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage url class links', 'Configure how URLs present across the client.', self._ManageURLClassLinks )
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage gallery url generators', 'Manage the client\'s GUGs, which convert search terms into URLs.', self._ManageGUGs )
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage url classes', 'Configure which URLs the client can recognise.', self._ManageURLClasses )
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage parsers', 'Manage the client\'s parsers, which convert URL content into hydrus metadata.', self._ManageParsers )
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'SEMI-LEGACY: manage file lookup scripts', 'Manage how the client parses different types of web content.', self._ManageParsingScripts )
+
+ ClientGUIMenus.AppendMenu( menu, submenu, 'downloader components' )
+
+ #
+
+ submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage logins', 'Edit which domains you wish to log in to.', self._ManageLogins )
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'manage login scripts', 'Manage the client\'s login scripts, which define how to log in to different sites.', self._ManageLoginScripts )
+
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'DEBUG: do tumblr GDPR click-through', 'Do a manual click-through for the tumblr GDPR page.', self._controller.CallLater, 0.0, self._controller.network_engine.login_manager.LoginTumblrGDPR )
+
+ ClientGUIMenus.AppendMenu( menu, submenu, 'logins' )
+
+ #
+
+ return ( menu, '&network' )
+
+
+ def _InitialiseMenuInfoPages( self ):
+
+ menu = QW.QMenu( self )
+
+ self._menubar_pages_page_count = ClientGUIMenus.AppendMenuLabel( menu, 'initialising', 'You have this many pages open.' )
+
+ self._menubar_pages_session_weight = ClientGUIMenus.AppendMenuItem( menu, 'initialising', 'Your session is this heavy.', self._ShowPageWeightInfo )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'refresh', 'If the current page has a search, refresh it.', self._Refresh )
+
+ splitter_menu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( splitter_menu, 'show/hide', 'Show or hide the panels on the left.', self._ShowHideSplitters )
+
+ ClientGUIMenus.AppendSeparator( splitter_menu )
+
+ ClientGUIMenus.AppendMenuCheckItem( splitter_menu, 'save current page\'s sash positions on client exit', 'Set whether sash position should be saved over on client exit.', self._new_options.GetBoolean( 'saving_sash_positions_on_exit' ), self._new_options.FlipBoolean, 'saving_sash_positions_on_exit' )
+
+ ClientGUIMenus.AppendSeparator( splitter_menu )
+
+ ClientGUIMenus.AppendMenuItem( splitter_menu, 'save current page\'s sash positions now', 'Save the current page\'s sash positions.', self._SaveSplitterPositions )
+
+ ClientGUIMenus.AppendSeparator( splitter_menu )
+
+ ClientGUIMenus.AppendMenuItem( splitter_menu, 'restore all pages\' sash positions to saved value', 'Restore the current sash positions for all pages to the values that are saved.', self._RestoreSplitterPositions )
+
+ ClientGUIMenus.AppendMenu( menu, splitter_menu, 'management and preview panels' )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ self._menubar_pages_sessions_submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenu( menu, self._menubar_pages_sessions_submenu, 'sessions' )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'pick a new page', 'Choose a new page to open.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_PAGE ) )
+
+ #
+
+ self._menubar_pages_search_submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenu( menu, self._menubar_pages_search_submenu, 'new search page' )
+
+ #
+
+ self._menubar_pages_petition_submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenu( menu, self._menubar_pages_petition_submenu, 'new petition page' )
+
+ #
+
+ download_menu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( download_menu, 'url download', 'Open a new tab to download some separate urls.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_URL_DOWNLOADER_PAGE ) )
+ ClientGUIMenus.AppendMenuItem( download_menu, 'watcher', 'Open a new tab to watch threads or other updating locations.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_WATCHER_DOWNLOADER_PAGE ) )
+ ClientGUIMenus.AppendMenuItem( download_menu, 'gallery', 'Open a new tab to download from gallery sites.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_GALLERY_DOWNLOADER_PAGE ) )
+ ClientGUIMenus.AppendMenuItem( download_menu, 'simple downloader', 'Open a new tab to download files from generic galleries or threads.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_SIMPLE_DOWNLOADER_PAGE ) )
+
+ ClientGUIMenus.AppendMenu( menu, download_menu, 'new download page' )
+
+ #
+
+ self._menubar_pages_download_popup_submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( self._menubar_pages_download_popup_submenu, 'an ipfs multihash', 'Enter an IPFS multihash and attempt to import whatever is returned.', self._StartIPFSDownload )
+
+ ClientGUIMenus.AppendMenu( menu, self._menubar_pages_download_popup_submenu, 'new download popup' )
+
+ #
+
+ special_menu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( special_menu, 'page of pages', 'Open a new tab that can hold more tabs.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_PAGE_OF_PAGES ) )
+ ClientGUIMenus.AppendMenuItem( special_menu, 'duplicates processing', 'Open a new tab to discover and filter duplicate files.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_DUPLICATE_FILTER_PAGE ) )
+
+ ClientGUIMenus.AppendMenu( menu, special_menu, 'new special page' )
+
+ #
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ special_command_menu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( special_command_menu, 'clear all multiwatcher highlights', 'Command all multiwatcher pages to clear their highlighted watchers.', HG.client_controller.pub, 'clear_multiwatcher_highlights' )
+
+ ClientGUIMenus.AppendMenu( menu, special_command_menu, 'special commands' )
+
+ #
+
+ return ( menu, '&pages' )
+
+
+ def _InitialiseMenuInfoServices( self ):
+
+ menu = QW.QMenu( self )
+
+ submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuCheckItem( submenu, 'all repository synchronisation', 'Pause the client\'s synchronisation with hydrus repositories.', HC.options['pause_repo_sync'], self._PausePlaySync, 'repo' )
+
+ ClientGUIMenus.AppendMenu( menu, submenu, 'pause' )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'review services', 'Look at the services your client connects to.', self._ReviewServices )
+ ClientGUIMenus.AppendMenuItem( menu, 'manage services', 'Edit the services your client connects to.', self._ManageServices )
+
+ self._menubar_services_admin_submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenu( menu, self._menubar_services_admin_submenu, 'administrate services' )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'import repository update files', 'Add repository update files to the database.', self._ImportUpdateFiles )
+
+ return ( menu, '&services' )
+
+
+ def _InitialiseMenuInfoTags( self ):
+
+ menu = QW.QMenu( self )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'migrate tags', 'Migrate tags from one place to another.', self._MigrateTags )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'manage tag display and search', 'Set which tags you want to see from which services.', self._ManageTagDisplay )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'manage tag siblings', 'Set certain tags to be automatically replaced with other tags.', self._ManageTagSiblings )
+ ClientGUIMenus.AppendMenuItem( menu, 'manage tag parents', 'Set certain tags to be automatically added with other tags.', self._ManageTagParents )
+
+ ClientGUIMenus.AppendMenuItem( menu, 'manage where tag siblings and parents apply', 'Set which services\' siblings and parents apply where.', self._ManageTagDisplayApplication )
+
+ #
+
+ tag_display_maintenance_menu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenuItem( tag_display_maintenance_menu, 'review tag sibling/parent maintenance', 'See how siblings and parents are currently applied.', self._ReviewTagDisplayMaintenance )
+ ClientGUIMenus.AppendSeparator( tag_display_maintenance_menu )
+
+ check_manager = ClientGUICommon.CheckboxManagerOptions( 'tag_display_maintenance_during_idle' )
+
+ current_value = check_manager.GetCurrentValue()
+ func = check_manager.Invert
+
+ ClientGUIMenus.AppendMenuCheckItem( tag_display_maintenance_menu, 'sync tag display during idle time', 'Control whether tag display maintenance can work during idle time.', current_value, func )
+
+ check_manager = ClientGUICommon.CheckboxManagerOptions( 'tag_display_maintenance_during_active' )
+
+ current_value = check_manager.GetCurrentValue()
+ func = check_manager.Invert
+
+ ClientGUIMenus.AppendMenuCheckItem( tag_display_maintenance_menu, 'sync tag display during normal time', 'Control whether tag display maintenance can work during normal time.', current_value, func )
+
+ ClientGUIMenus.AppendMenu( menu, tag_display_maintenance_menu, 'sibling/parent sync' )
+
+ #
+
+ return ( menu, '&tags' )
+
+
+
+ def _InitialiseMenuInfoUndo( self ):
+
+ menu = QW.QMenu( self )
+
+ self._menubar_undo_undo = ClientGUIMenus.AppendMenuItem( menu, 'initialising', 'Undo last operation.', self._controller.pub, 'undo' )
+
+ self._menubar_undo_redo = ClientGUIMenus.AppendMenuItem( menu, 'initialising', 'Redo last operation.', self._controller.pub, 'redo' )
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ self._menubar_undo_closed_pages_submenu = QW.QMenu( menu )
+
+ ClientGUIMenus.AppendMenu( menu, self._menubar_undo_closed_pages_submenu, 'closed pages' )
+
+ return ( menu, '&undo' )
def _InitialiseSession( self ):
@@ -3367,9 +4617,7 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
dlg.exec()
- self.DirtyMenu( 'database' )
-
- self._menu_updater.Update()
+ self._menu_updater_database.update()
def _MigrateTags( self ):
@@ -3473,11 +4721,6 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
def _RefreshStatusBar( self ):
- if not QP.isValid( self ) or not self._notebook or not self._statusbar or self._CurrentlyMinimisedOrHidden():
-
- return
-
-
page = self._notebook.GetCurrentMediaPage()
if page is None:
@@ -4808,6 +6051,41 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
+ def _ShowPageWeightInfo( self ):
+
+ (
+ total_active_page_count,
+ total_active_num_hashes,
+ total_active_num_seeds,
+ total_closed_page_count,
+ total_closed_num_hashes,
+ total_closed_num_seeds
+ ) = self.GetTotalPageCounts()
+
+ total_active_num_hashes_weight = ClientGUIPages.ConvertNumHashesToWeight( total_active_num_hashes )
+ total_active_num_seeds_weight = ClientGUIPages.ConvertNumSeedsToWeight( total_active_num_seeds )
+
+ total_closed_num_hashes_weight = ClientGUIPages.ConvertNumHashesToWeight( total_closed_num_hashes )
+ total_closed_num_seeds_weight = ClientGUIPages.ConvertNumSeedsToWeight( total_closed_num_seeds )
+
+ message = 'Session weight is a simple representation of your pages combined memory and CPU load. A file counts as 1, and a URL counts as 20.'
+ message += os.linesep * 2
+ message += 'Try to keep the total below 10 million! It is also generally better to spread it around--have five download pages each of 500k weight rather than one page with 2.5M.'
+ message += os.linesep * 2
+ message += 'Your {} open pages\' total is: {}'.format( total_active_page_count, HydrusData.ToHumanInt( total_active_num_hashes_weight + total_active_num_seeds_weight ) )
+ message += os.linesep * 2
+ message += 'Specifically, your file weight is {} and URL weight is {}.'.format( HydrusData.ToHumanInt( total_active_num_hashes_weight ), HydrusData.ToHumanInt( total_active_num_seeds_weight ) )
+ message += os.linesep * 2
+ message += 'For extra info, your {} closed pages (in the undo list) have total weight {}, being file weight {} and URL weight {}.'.format(
+ total_closed_page_count,
+ HydrusData.ToHumanInt( total_closed_num_hashes_weight + total_closed_num_seeds_weight ),
+ HydrusData.ToHumanInt( total_closed_num_hashes_weight ),
+ HydrusData.ToHumanInt( total_closed_num_seeds_weight )
+ )
+
+ QW.QMessageBox.information( self, 'Information', message )
+
+
def _StartIPFSDownload( self ):
ipfs_services = self._controller.services_manager.GetServices( ( HC.IPFS, ), randomised = True )
@@ -5036,7 +6314,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._controller.pub( 'notify_page_unclosed', page )
- self._controller.pub( 'notify_new_undo' )
+ self._menu_updater_undo.update()
self._controller.pub( 'notify_new_pages' )
@@ -5071,8 +6349,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
return
- HG.currently_uploading_pending = True
-
self._controller.CallToThread( THREADUploadPending, service_key )
@@ -5217,7 +6493,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
if self._CurrentlyMinimisedOrHidden() or dialog_is_open or not ClientGUIFunctions.TLWOrChildIsActive( self ):
- self._controller.CallLaterQtSafe( self, 0.5, 'modal message wait loop', self.AddModalMessage, job_key )
+ self._pending_modal_job_keys.add( job_key )
else:
@@ -5334,7 +6610,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._DestroyPages( deletee_pages )
- self._controller.pub( 'notify_new_undo' )
+ self._menu_updater_undo.update()
@@ -5366,32 +6642,12 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
if len( old_closed_pages ) != len( self._closed_pages ):
- self._controller.pub( 'notify_new_undo' )
+ self._menu_updater_undo.update()
self._DestroyPages( deletee_pages )
- def DirtyMenu( self, name ):
-
- if name not in self._dirty_menus:
-
- self.DisableMenu( name )
-
- self._dirty_menus.add( name )
-
-
-
- def DisableMenu( self, name ):
-
- menu_index = self._FindMenuBarIndex( name )
-
- if menu_index != -1:
-
- self._menubar.actions()[ menu_index ].setEnabled( False )
-
-
-
def DoFileStorageRebalance( self, job_key: ClientThreading.JobKey ):
self._controller.CallToThread( self._controller.client_files_manager.Rebalance, job_key )
@@ -5421,11 +6677,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._FlipShowHideWholeUI()
- else:
-
- QP.CallAfter( self.RefreshMenu )
- QP.CallAfter( self.RefreshStatusBar )
-
def EventMove( self, event ):
@@ -5538,9 +6789,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._UpdateSystemTrayIcon()
- self.DirtyMenu( 'network' )
-
- self._menu_updater.Update()
+ self._menu_updater_network.update()
def FlipSubscriptionsPaused( self ):
@@ -5553,1062 +6802,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._UpdateSystemTrayIcon()
- self.DirtyMenu( 'network' )
-
- self._menu_updater.Update()
-
-
- def GenerateMenuInfo( self, name ):
-
- def undo():
-
- have_closed_pages = len( self._closed_pages ) > 0
-
- undo_manager = self._controller.GetManager( 'undo' )
-
- ( undo_string, redo_string ) = undo_manager.GetUndoRedoStrings()
-
- have_undo_stuff = undo_string is not None or redo_string is not None
-
- if have_closed_pages or have_undo_stuff:
-
- menu = QW.QMenu( self )
-
- if undo_string is not None:
-
- ClientGUIMenus.AppendMenuItem( menu, undo_string, 'Undo last operation.', self._controller.pub, 'undo' )
-
-
- if redo_string is not None:
-
- ClientGUIMenus.AppendMenuItem( menu, redo_string, 'Redo last operation.', self._controller.pub, 'redo' )
-
-
- if have_closed_pages:
-
- ClientGUIMenus.AppendSeparator( menu )
-
- undo_pages = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( undo_pages, 'clear all', 'Remove all closed pages from memory.', self.AskToDeleteAllClosedPages )
-
- undo_pages.addSeparator()
-
- args = []
-
- for ( i, ( time_closed, page ) ) in enumerate( self._closed_pages ):
-
- name = page.GetName()
-
- args.append( ( i, name + ' - ' + page.GetPrettyStatus() ) )
-
-
- args.reverse() # so that recently closed are at the top
-
- for ( index, name ) in args:
-
- ClientGUIMenus.AppendMenuItem( undo_pages, name, 'Restore this page.', self._UnclosePage, index )
-
-
- ClientGUIMenus.AppendMenu( menu, undo_pages, 'closed pages' )
-
-
- else:
-
- menu = None
-
-
- return ( menu, '&undo' )
-
-
- def database():
-
- menu = QW.QMenu( self )
-
- ClientGUIMenus.AppendMenuItem( menu, 'set a password', 'Set a simple password for the database so only you can open it in the client.', self._SetPassword )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- if HG.client_controller.client_files_manager.AllLocationsAreDefault():
-
- backup_path = self._new_options.GetNoneableString( 'backup_path' )
-
- if backup_path is None:
-
- ClientGUIMenus.AppendMenuItem( menu, 'set up a database backup location', 'Choose a path to back the database up to.', self._SetupBackupPath )
-
- else:
-
- ClientGUIMenus.AppendMenuItem( menu, 'update database backup', 'Back the database up to an external location.', self._BackupDatabase )
- ClientGUIMenus.AppendMenuItem( menu, 'change database backup location', 'Choose a path to back the database up to.', self._SetupBackupPath )
-
-
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'restore from a database backup', 'Restore the database from an external location.', self._controller.RestoreDatabase )
-
- else:
-
- message = 'Your database is stored across multiple locations, which disables my internal backup routine. To back up, please use a third-party program that will work better than anything I can write.'
- message += os.linesep * 2
- message += 'Please check the help for more info on how best to backup manually.'
-
- ClientGUIMenus.AppendMenuItem( menu, 'database is stored in multiple locations', 'The database is migrated.', HydrusData.ShowText, message )
-
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'migrate database', 'Review and manage the locations your database is stored.', self._MigrateDatabase )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- file_maintenance_menu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( file_maintenance_menu, 'manage scheduled jobs', 'Review outstanding jobs, and schedule new ones.', self._ReviewFileMaintenance )
- ClientGUIMenus.AppendSeparator( file_maintenance_menu )
-
- check_manager = ClientGUICommon.CheckboxManagerOptions( 'file_maintenance_during_idle' )
-
- current_value = check_manager.GetCurrentValue()
- func = check_manager.Invert
-
- ClientGUIMenus.AppendMenuCheckItem( file_maintenance_menu, 'work file jobs during idle time', 'Control whether file maintenance can work during idle time.', current_value, func )
-
- check_manager = ClientGUICommon.CheckboxManagerOptions( 'file_maintenance_during_active' )
-
- current_value = check_manager.GetCurrentValue()
- func = check_manager.Invert
-
- ClientGUIMenus.AppendMenuCheckItem( file_maintenance_menu, 'work file jobs during normal time', 'Control whether file maintenance can work during normal time.', current_value, func )
-
- ClientGUIMenus.AppendMenu( menu, file_maintenance_menu, 'file maintenance' )
-
- maintenance_submenu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'analyze', 'Optimise slow queries by running statistical analyses on the database.', self._AnalyzeDatabase )
- ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'review vacuum data', 'See whether it is worth rebuilding the database to reformat tables and recover disk space.', self._ReviewVacuumData )
-
- ClientGUIMenus.AppendSeparator( maintenance_submenu )
-
- ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'clear orphan files', 'Clear out surplus files that have found their way into the file structure.', self._ClearOrphanFiles )
- ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'clear orphan file records', 'Clear out surplus file records that have not been deleted correctly.', self._ClearOrphanFileRecords )
-
- if self._controller.new_options.GetBoolean( 'advanced_mode' ):
-
- ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'clear orphan tables', 'Clear out surplus db tables that have not been deleted correctly.', self._ClearOrphanTables )
-
-
- ClientGUIMenus.AppendMenuItem( maintenance_submenu, 'clear orphan hashed serialisables', 'Clear non-needed cached hashed serialisable objects.', self._ClearOrphanHashedSerialisables )
-
- ClientGUIMenus.AppendMenu( menu, maintenance_submenu, 'db maintenance' )
-
- check_submenu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( check_submenu, 'database integrity', 'Have the database examine all its records for internal consistency.', self._CheckDBIntegrity )
- ClientGUIMenus.AppendMenuItem( check_submenu, 'repopulate truncated mappings tables', 'Use the mappings cache to try to repair a previously damaged mappings file.', self._RepopulateMappingsTables )
- ClientGUIMenus.AppendMenuItem( check_submenu, 'fix logically inconsistent mappings', 'Remove tags that are occupying two mutually exclusive states.', self._FixLogicallyInconsistentMappings )
- ClientGUIMenus.AppendMenuItem( check_submenu, 'fix invalid tags', 'Scan the database for invalid tags.', self._RepairInvalidTags )
-
- ClientGUIMenus.AppendMenu( menu, check_submenu, 'check and repair' )
-
- regen_submenu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'total pending count, in the pending menu', 'Regenerate the pending count up top.', self._DeleteServiceInfo, only_pending = True )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag storage mappings cache (all, with deferred siblings & parents calculation)', 'Delete and recreate the tag mappings cache, fixing bad tags or miscounts.', self._RegenerateTagMappingsCache )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag storage mappings cache (just pending tags, instant calculation)', 'Delete and recreate the tag pending mappings cache, fixing bad tags or miscounts.', self._RegenerateTagPendingMappingsCache )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag display mappings cache (all, deferred siblings & parents calculation)', 'Delete and recreate the tag display mappings cache, fixing bad tags or miscounts.', self._RegenerateTagDisplayMappingsCache )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag display mappings cache (just pending tags, instant calculation)', 'Delete and recreate the tag display pending mappings cache, fixing bad tags or miscounts.', self._RegenerateTagDisplayPendingMappingsCache )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag siblings lookup cache', 'Delete and recreate the tag siblings cache.', self._RegenerateTagSiblingsLookupCache )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag parents lookup cache', 'Delete and recreate the tag siblings cache.', self._RegenerateTagParentsLookupCache )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag text search cache', 'Delete and regenerate the cache hydrus uses for fast tag search.', self._RegenerateTagCache )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag text search cache (subtags repopulation)', 'Repopulate the subtags for the cache hydrus uses for fast tag search.', self._RepopulateTagCacheMissingSubtags )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'tag text search cache (searchable subtag maps)', 'Regenerate the searchable subtag maps.', self._RegenerateTagCacheSearchableSubtagsMaps )
-
- ClientGUIMenus.AppendSeparator( regen_submenu )
-
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'local hash cache', 'Repopulate the cache hydrus uses for fast hash lookup for local files.', self._RegenerateLocalHashCache )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'local tag cache', 'Repopulate the cache hydrus uses for fast tag lookup for local files.', self._RegenerateLocalTagCache )
-
- ClientGUIMenus.AppendSeparator( regen_submenu )
-
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'clear service info cache', 'Delete all cached service info like total number of mappings or files, in case it has become desynchronised. Some parts of the gui may be laggy immediately after this as these numbers are recalculated.', self._DeleteServiceInfo )
- ClientGUIMenus.AppendMenuItem( regen_submenu, 'similar files search tree', 'Delete and recreate the similar files search tree.', self._RegenerateSimilarFilesTree )
-
- ClientGUIMenus.AppendMenu( menu, regen_submenu, 'regenerate' )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- file_viewing_submenu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( file_viewing_submenu, 'clear all file viewing statistics', 'Delete all file viewing records from the database.', self._ClearFileViewingStats )
- ClientGUIMenus.AppendMenuItem( file_viewing_submenu, 'cull file viewing statistics based on current min/max values', 'Cull your file viewing statistics based on minimum and maximum permitted time deltas.', self._CullFileViewingStats )
-
- ClientGUIMenus.AppendMenu( menu, file_viewing_submenu, 'file viewing statistics' )
-
- return ( menu, '&database' )
-
-
- def network():
-
- menu = QW.QMenu( self )
-
- submenu = QW.QMenu( menu )
-
- pause_all_new_network_traffic = self._controller.new_options.GetBoolean( 'pause_all_new_network_traffic' )
-
- ClientGUIMenus.AppendMenuCheckItem( submenu, 'all new network traffic', 'Stop any new network jobs from sending data.', pause_all_new_network_traffic, self.FlipNetworkTrafficPaused )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuCheckItem( submenu, 'subscriptions', 'Pause the client\'s synchronisation with website subscriptions.', HC.options[ 'pause_subs_sync' ], self.FlipSubscriptionsPaused )
-
- if self._controller.new_options.GetBoolean( 'advanced_mode' ):
-
- ClientGUIMenus.AppendMenuItem( submenu, 'nudge subscriptions awake', 'Tell the subs daemon to wake up, just in case any subs are due.', self._controller.subscriptions_manager.Wake )
-
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuCheckItem( submenu, 'paged file import queues', 'Pause all file import queues.', self._controller.new_options.GetBoolean( 'pause_all_file_queues' ), self._controller.new_options.FlipBoolean, 'pause_all_file_queues' )
- ClientGUIMenus.AppendMenuCheckItem( submenu, 'gallery searches', 'Pause all gallery imports\' searching.', self._controller.new_options.GetBoolean( 'pause_all_gallery_searches' ), self._controller.new_options.FlipBoolean, 'pause_all_gallery_searches' )
- ClientGUIMenus.AppendMenuCheckItem( submenu, 'watcher checkers', 'Pause all watchers\' checking.', self._controller.new_options.GetBoolean( 'pause_all_watcher_checkers' ), self._controller.new_options.FlipBoolean, 'pause_all_watcher_checkers' )
-
- ClientGUIMenus.AppendMenu( menu, submenu, 'pause' )
-
- #
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'manage subscriptions', 'Change the queries you want the client to regularly import from.', self._ManageSubscriptions )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- submenu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'review bandwidth usage and edit rules', 'See where you are consuming data.', self._ReviewBandwidth )
- ClientGUIMenus.AppendMenuItem( submenu, 'review current network jobs', 'Review the jobs currently running in the network engine.', self._ReviewNetworkJobs )
- ClientGUIMenus.AppendMenuItem( submenu, 'review session cookies', 'Review and edit which cookies you have for which network contexts.', self._ReviewNetworkSessions )
- ClientGUIMenus.AppendMenuItem( submenu, 'manage http headers', 'Configure how the client talks to the network.', self._ManageNetworkHeaders )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'manage upnp', 'If your router supports it, see and edit your current UPnP NAT traversal mappings.', self._ManageUPnP )
-
- ClientGUIMenus.AppendMenu( menu, submenu, 'data' )
-
- #
-
- submenu = QW.QMenu( menu )
-
- if not ClientParsing.HTML5LIB_IS_OK:
-
- message = 'The client was unable to import html5lib on boot. This is an important parsing library that performs better than the usual backup, lxml. Without it, some downloaders will not work well and you will miss tags and files.'
- message += os.linesep * 2
- message += 'You are likely running from source, so I recommend you close the client, run \'pip install html5lib\' (or whatever is appropriate for your environment) and try again. You can double-check what imported ok under help->about.'
-
- ClientGUIMenus.AppendMenuItem( submenu, '*** html5lib not found! ***', 'Your client does not have an important library.', QW.QMessageBox.warning, self, 'Warning', message )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
-
- ClientGUIMenus.AppendMenuItem( submenu, 'import downloaders', 'Import new download capability through encoded pngs from other users.', self._ImportDownloaders )
- ClientGUIMenus.AppendMenuItem( submenu, 'export downloaders', 'Export downloader components to easy-import pngs.', self._ExportDownloader )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'manage default tag import options', 'Change the default tag import options for each of your linked url matches.', self._ManageDefaultTagImportOptions )
- ClientGUIMenus.AppendMenuItem( submenu, 'manage downloader and url display', 'Configure how downloader objects present across the client.', self._ManageDownloaderDisplay )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- clipboard_menu = QW.QMenu( submenu )
-
- ClientGUIMenus.AppendMenuCheckItem( clipboard_menu, 'watcher urls', 'Automatically import watcher URLs that enter the clipboard just as if you drag-and-dropped them onto the ui.', self._controller.new_options.GetBoolean( 'watch_clipboard_for_watcher_urls' ), self._FlipClipboardWatcher, 'watch_clipboard_for_watcher_urls' )
- ClientGUIMenus.AppendMenuCheckItem( clipboard_menu, 'other recognised urls', 'Automatically import recognised URLs that enter the clipboard just as if you drag-and-dropped them onto the ui.', self._controller.new_options.GetBoolean( 'watch_clipboard_for_other_recognised_urls' ), self._FlipClipboardWatcher, 'watch_clipboard_for_other_recognised_urls' )
-
- ClientGUIMenus.AppendMenu( submenu, clipboard_menu, 'watch clipboard for urls' )
-
- ClientGUIMenus.AppendMenu( menu, submenu, 'downloaders' )
-
- #
-
- submenu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'manage url class links', 'Configure how URLs present across the client.', self._ManageURLClassLinks )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'manage gallery url generators', 'Manage the client\'s GUGs, which convert search terms into URLs.', self._ManageGUGs )
- ClientGUIMenus.AppendMenuItem( submenu, 'manage url classes', 'Configure which URLs the client can recognise.', self._ManageURLClasses )
- ClientGUIMenus.AppendMenuItem( submenu, 'manage parsers', 'Manage the client\'s parsers, which convert URL content into hydrus metadata.', self._ManageParsers )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'SEMI-LEGACY: manage file lookup scripts', 'Manage how the client parses different types of web content.', self._ManageParsingScripts )
-
- ClientGUIMenus.AppendMenu( menu, submenu, 'downloader components' )
-
- #
-
- submenu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'manage logins', 'Edit which domains you wish to log in to.', self._ManageLogins )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'manage login scripts', 'Manage the client\'s login scripts, which define how to log in to different sites.', self._ManageLoginScripts )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'DEBUG: do tumblr GDPR click-through', 'Do a manual click-through for the tumblr GDPR page.', self._controller.CallLater, 0.0, self._controller.network_engine.login_manager.LoginTumblrGDPR )
-
- ClientGUIMenus.AppendMenu( menu, submenu, 'logins' )
-
- #
-
- return ( menu, '&network' )
-
-
- def services():
-
- menu = QW.QMenu( self )
-
- tag_services = self._controller.services_manager.GetServices( ( HC.TAG_REPOSITORY, ) )
- file_services = self._controller.services_manager.GetServices( ( HC.FILE_REPOSITORY, ) )
-
- submenu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuCheckItem( submenu, 'all repository synchronisation', 'Pause the client\'s synchronisation with hydrus repositories.', HC.options['pause_repo_sync'], self._PausePlaySync, 'repo' )
-
- ClientGUIMenus.AppendMenu( menu, submenu, 'pause' )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'review services', 'Look at the services your client connects to.', self._ReviewServices )
- ClientGUIMenus.AppendMenuItem( menu, 'manage services', 'Edit the services your client connects to.', self._ManageServices )
-
- repository_admin_permissions = [ ( HC.CONTENT_TYPE_ACCOUNTS, HC.PERMISSION_ACTION_CREATE ), ( HC.CONTENT_TYPE_ACCOUNTS, HC.PERMISSION_ACTION_MODERATE ), ( HC.CONTENT_TYPE_ACCOUNT_TYPES, HC.PERMISSION_ACTION_MODERATE ), ( HC.CONTENT_TYPE_OPTIONS, HC.PERMISSION_ACTION_MODERATE ) ]
-
- repositories = self._controller.services_manager.GetServices( HC.REPOSITORIES )
- admin_repositories = [ service for service in repositories if True in ( service.HasPermission( content_type, action ) for ( content_type, action ) in repository_admin_permissions ) ]
-
- servers_admin = self._controller.services_manager.GetServices( ( HC.SERVER_ADMIN, ) )
- server_admins = [ service for service in servers_admin if service.HasPermission( HC.CONTENT_TYPE_SERVICES, HC.PERMISSION_ACTION_MODERATE ) ]
-
- admin_services = admin_repositories + server_admins
-
- if len( admin_services ) > 0:
-
- admin_menu = QW.QMenu( menu )
-
- for service in admin_services:
-
- submenu = QW.QMenu( admin_menu )
-
- service_key = service.GetServiceKey()
-
- service_type = service.GetServiceType()
-
- can_create_accounts = service.HasPermission( HC.CONTENT_TYPE_ACCOUNTS, HC.PERMISSION_ACTION_CREATE )
- can_overrule_accounts = service.HasPermission( HC.CONTENT_TYPE_ACCOUNTS, HC.PERMISSION_ACTION_MODERATE )
- can_overrule_account_types = service.HasPermission( HC.CONTENT_TYPE_ACCOUNT_TYPES, HC.PERMISSION_ACTION_MODERATE )
- can_overrule_services = service.HasPermission( HC.CONTENT_TYPE_SERVICES, HC.PERMISSION_ACTION_MODERATE )
- can_overrule_options = service.HasPermission( HC.CONTENT_TYPE_OPTIONS, HC.PERMISSION_ACTION_MODERATE )
-
- if can_overrule_accounts:
-
- ClientGUIMenus.AppendMenuItem( submenu, 'review all accounts', 'See all accounts.', self._STARTReviewAllAccounts, service_key )
- ClientGUIMenus.AppendMenuItem( submenu, 'modify an account', 'Modify a specific account\'s type and expiration.', self._ModifyAccount, service_key )
-
-
- if can_overrule_accounts and service_type == HC.FILE_REPOSITORY:
-
- ClientGUIMenus.AppendMenuItem( submenu, 'get an uploader\'s ip address', 'Fetch the ip address that uploaded a specific file, if the service knows it.', self._FetchIP, service_key )
-
-
- if can_create_accounts:
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'create new accounts', 'Create new accounts for this service.', self._GenerateNewAccounts, service_key )
-
-
- if can_overrule_account_types:
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'manage account types', 'Add, edit and delete account types for this service.', self._STARTManageAccountTypes, service_key )
-
-
- if can_overrule_options and service_type in HC.REPOSITORIES:
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'change update period', 'Change the update period for this service.', self._ManageServiceOptionsUpdatePeriod, service_key )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'change anonymisation period', 'Change the account history nullification period for this service.', self._ManageServiceOptionsNullificationPeriod, service_key )
-
-
- if can_overrule_services and service_type == HC.SERVER_ADMIN:
-
- ClientGUIMenus.AppendSeparator( submenu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'manage services', 'Add, edit, and delete this server\'s services.', self._ManageServer, service_key )
- ClientGUIMenus.AppendSeparator( submenu )
- ClientGUIMenus.AppendMenuItem( submenu, 'backup server', 'Command the server to temporarily pause and back up its database.', self._BackupServer, service_key )
- ClientGUIMenus.AppendSeparator( submenu )
- ClientGUIMenus.AppendMenuItem( submenu, 'vacuum server', 'Command the server to temporarily pause and vacuum its database.', self._VacuumServer, service_key )
- ClientGUIMenus.AppendSeparator( submenu )
- ClientGUIMenus.AppendMenuItem( submenu, 'server/db lock: on', 'Command the server to lock itself and disconnect its db.', self._LockServer, service_key, True )
- ClientGUIMenus.AppendMenuItem( submenu, 'server/db lock: test', 'See if the server is currently busy.', self._TestServerBusy, service_key )
- ClientGUIMenus.AppendMenuItem( submenu, 'server/db lock: off', 'Command the server to unlock itself and resume its db.', self._LockServer, service_key, False )
-
-
- ClientGUIMenus.AppendMenu( admin_menu, submenu, service.GetName() )
-
-
- ClientGUIMenus.AppendMenu( menu, admin_menu, 'administrate services' )
-
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'import repository update files', 'Add repository update files to the database.', self._ImportUpdateFiles )
-
- return ( menu, '&services' )
-
-
- def tags():
-
- menu = QW.QMenu( self )
-
- ClientGUIMenus.AppendMenuItem( menu, 'migrate tags', 'Migrate tags from one place to another.', self._MigrateTags )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'manage tag display and search', 'Set which tags you want to see from which services.', self._ManageTagDisplay )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'manage tag siblings', 'Set certain tags to be automatically replaced with other tags.', self._ManageTagSiblings )
- ClientGUIMenus.AppendMenuItem( menu, 'manage tag parents', 'Set certain tags to be automatically added with other tags.', self._ManageTagParents )
-
- ClientGUIMenus.AppendMenuItem( menu, 'manage where tag siblings and parents apply', 'Set which services\' siblings and parents apply where.', self._ManageTagDisplayApplication )
-
- #
-
- tag_display_maintenance_menu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( tag_display_maintenance_menu, 'review tag sibling/parent maintenance', 'See how siblings and parents are currently applied.', self._ReviewTagDisplayMaintenance )
- ClientGUIMenus.AppendSeparator( tag_display_maintenance_menu )
-
- check_manager = ClientGUICommon.CheckboxManagerOptions( 'tag_display_maintenance_during_idle' )
-
- current_value = check_manager.GetCurrentValue()
- func = check_manager.Invert
-
- ClientGUIMenus.AppendMenuCheckItem( tag_display_maintenance_menu, 'sync tag display during idle time', 'Control whether tag display maintenance can work during idle time.', current_value, func )
-
- check_manager = ClientGUICommon.CheckboxManagerOptions( 'tag_display_maintenance_during_active' )
-
- current_value = check_manager.GetCurrentValue()
- func = check_manager.Invert
-
- ClientGUIMenus.AppendMenuCheckItem( tag_display_maintenance_menu, 'sync tag display during normal time', 'Control whether tag display maintenance can work during normal time.', current_value, func )
-
- ClientGUIMenus.AppendMenu( menu, tag_display_maintenance_menu, 'sibling/parent sync' )
-
- #
-
- return ( menu, '&tags' )
-
-
- def help():
-
- menu = QW.QMenu( self )
-
- ClientGUIMenus.AppendMenuItem( menu, 'help and getting started guide', 'Open hydrus\'s local help in your web browser.', ClientPaths.LaunchPathInWebBrowser, os.path.join( HC.HELP_DIR, 'index.html' ) )
-
- links = QW.QMenu( menu )
-
- site = ClientGUIMenus.AppendMenuBitmapItem( links, 'site', 'Open hydrus\'s website, which is mostly a mirror of the local help.', CC.global_pixmaps().file_repository, ClientPaths.LaunchURLInWebBrowser, 'https://hydrusnetwork.github.io/hydrus/' )
- site = ClientGUIMenus.AppendMenuBitmapItem( links, 'github repository', 'Open the hydrus github repository.', CC.global_pixmaps().github, ClientPaths.LaunchURLInWebBrowser, 'https://github.com/hydrusnetwork/hydrus' )
- site = ClientGUIMenus.AppendMenuBitmapItem( links, 'latest build', 'Open the latest build on the hydrus github repository.', CC.global_pixmaps().github, ClientPaths.LaunchURLInWebBrowser, 'https://github.com/hydrusnetwork/hydrus/releases/latest' )
- site = ClientGUIMenus.AppendMenuBitmapItem( links, 'issue tracker', 'Open the github issue tracker, which is run by users.', CC.global_pixmaps().github, ClientPaths.LaunchURLInWebBrowser, 'https://github.com/hydrusnetwork/hydrus/issues' )
- site = ClientGUIMenus.AppendMenuBitmapItem( links, '8chan.moe /t/ (Hydrus Network General)', 'Open the 8chan.moe /t/ board, where a Hydrus Network General should exist with release posts and other status updates.', CC.global_pixmaps().eight_chan, ClientPaths.LaunchURLInWebBrowser, 'https://8chan.moe/t/catalog.html' )
- site = ClientGUIMenus.AppendMenuItem( links, 'Endchan board bunker', 'Open hydrus dev\'s Endchan board, the bunker for the case when 8chan.moe is unavailable. Try .org if .net is unavailable.', ClientPaths.LaunchURLInWebBrowser, 'https://endchan.net/hydrus/index.html' )
- site = ClientGUIMenus.AppendMenuBitmapItem( links, 'twitter', 'Open hydrus dev\'s twitter, where he makes general progress updates and emergency notifications.', CC.global_pixmaps().twitter, ClientPaths.LaunchURLInWebBrowser, 'https://twitter.com/hydrusnetwork' )
- site = ClientGUIMenus.AppendMenuBitmapItem( links, 'tumblr', 'Open hydrus dev\'s tumblr, where he makes release posts and other status updates.', CC.global_pixmaps().tumblr, ClientPaths.LaunchURLInWebBrowser, 'https://hydrus.tumblr.com/' )
- site = ClientGUIMenus.AppendMenuBitmapItem( links, 'discord', 'Open a discord channel where many hydrus users congregate. Hydrus dev visits regularly.', CC.global_pixmaps().discord, ClientPaths.LaunchURLInWebBrowser, 'https://discord.gg/wPHPCUZ' )
- site = ClientGUIMenus.AppendMenuBitmapItem( links, 'patreon', 'Open hydrus dev\'s patreon, which lets you support development.', CC.global_pixmaps().patreon, ClientPaths.LaunchURLInWebBrowser, 'https://www.patreon.com/hydrus_dev' )
-
- ClientGUIMenus.AppendMenu( menu, links, 'links' )
-
- ClientGUIMenus.AppendMenuItem( menu, 'changelog', 'Open hydrus\'s local changelog in your web browser.', ClientPaths.LaunchPathInWebBrowser, os.path.join( HC.HELP_DIR, 'changelog.html' ) )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'add the public tag repository', 'This will add the public tag repository to your client.', self._AutoRepoSetup )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'how boned am I?', 'Check for a summary of your ride so far.', self._HowBonedAmI )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- currently_darkmode = self._new_options.GetString( 'current_colourset' ) == 'darkmode'
-
- ClientGUIMenus.AppendMenuCheckItem( menu, 'darkmode', 'Set the \'darkmode\' colourset on and off.', currently_darkmode, self.FlipDarkmode )
-
- check_manager = ClientGUICommon.CheckboxManagerOptions( 'advanced_mode' )
-
- current_value = check_manager.GetCurrentValue()
- func = check_manager.Invert
-
- ClientGUIMenus.AppendMenuCheckItem( menu, 'advanced mode', 'Turn on advanced menu options and buttons.', current_value, func )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- debug = QW.QMenu( menu )
-
- debug_modes = QW.QMenu( debug )
-
- ClientGUIMenus.AppendMenuCheckItem( debug_modes, 'force idle mode', 'Make the client consider itself idle and fire all maintenance routines right now. This may hang the gui for a while.', HG.force_idle_mode, self._SwitchBoolean, 'force_idle_mode' )
- ClientGUIMenus.AppendMenuCheckItem( debug_modes, 'no page limit mode', 'Let the user create as many pages as they want with no warnings or prohibitions.', HG.no_page_limit_mode, self._SwitchBoolean, 'no_page_limit_mode' )
- ClientGUIMenus.AppendMenuCheckItem( debug_modes, 'thumbnail debug mode', 'Show some thumbnail debug info.', HG.thumbnail_debug_mode, self._SwitchBoolean, 'thumbnail_debug_mode' )
- ClientGUIMenus.AppendMenuItem( debug_modes, 'simulate a wake from sleep', 'Tell the controller to pretend that it just woke up from sleep.', self._controller.SimulateWakeFromSleepEvent )
-
- ClientGUIMenus.AppendMenu( debug, debug_modes, 'debug modes' )
-
- profiling = QW.QMenu( debug )
-
- profile_mode_message = 'If something is running slow, you can turn on profile mode to have hydrus gather information on how long many jobs take to run.'
- profile_mode_message += os.linesep * 2
- profile_mode_message += 'Turn the mode on, do the slow thing for a bit, and then turn it off. In your database directory will be a new profile log, which is really helpful for hydrus dev to figure out what is running slow for you and how to fix it.'
- profile_mode_message += os.linesep * 2
- profile_mode_message += 'A new Query Planner mode also makes very detailed database analysis. This is an alternate profiling mode hydev is testing.'
- profile_mode_message += os.linesep * 2
- profile_mode_message += 'More information is available in the help, under \'reducing program lag\'.'
-
- ClientGUIMenus.AppendMenuItem( profiling, 'what is this?', 'Show profile info.', QW.QMessageBox.information, self, 'Profile modes', profile_mode_message )
- ClientGUIMenus.AppendMenuCheckItem( profiling, 'profile mode', 'Run detailed \'profiles\'.', HG.profile_mode, self._SwitchBoolean, 'profile_mode' )
- ClientGUIMenus.AppendMenuCheckItem( profiling, 'query planner mode', 'Run detailed \'query plans\'.', HG.query_planner_mode, self._SwitchBoolean, 'query_planner_mode' )
-
- ClientGUIMenus.AppendMenu( debug, profiling, 'profiling' )
-
- report_modes = QW.QMenu( debug )
-
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'cache report mode', 'Have the image and thumb caches report their operation.', HG.cache_report_mode, self._SwitchBoolean, 'cache_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'callto report mode', 'Report whenever the thread pool is given a task.', HG.callto_report_mode, self._SwitchBoolean, 'callto_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'canvas tile borders mode', 'Draw tile borders.', HG.canvas_tile_outline_mode, self._SwitchBoolean, 'canvas_tile_outline_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'daemon report mode', 'Have the daemons report whenever they fire their jobs.', HG.daemon_report_mode, self._SwitchBoolean, 'daemon_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'db report mode', 'Have the db report query information, where supported.', HG.db_report_mode, self._SwitchBoolean, 'db_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'file import report mode', 'Have the db and file manager report file import progress.', HG.file_import_report_mode, self._SwitchBoolean, 'file_import_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'file report mode', 'Have the file manager report file request information, where supported.', HG.file_report_mode, self._SwitchBoolean, 'file_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'gui report mode', 'Have the gui report inside information, where supported.', HG.gui_report_mode, self._SwitchBoolean, 'gui_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'hover window report mode', 'Have the hover windows report their show/hide logic.', HG.hover_window_report_mode, self._SwitchBoolean, 'hover_window_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'media load report mode', 'Have the client report media load information, where supported.', HG.media_load_report_mode, self._SwitchBoolean, 'media_load_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'mpv report mode', 'Have the client report significant mpv debug information.', HG.mpv_report_mode, self._SwitchBoolean, 'mpv_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'network report mode', 'Have the network engine report new jobs.', HG.network_report_mode, self._SwitchBoolean, 'network_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'pubsub report mode', 'Report info about every pubsub processed.', HG.pubsub_report_mode, self._SwitchBoolean, 'pubsub_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'similar files metadata generation report mode', 'Have the phash generation routine report its progress.', HG.phash_generation_report_mode, self._SwitchBoolean, 'phash_generation_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'shortcut report mode', 'Have the new shortcut system report what shortcuts it catches and whether it matches an action.', HG.shortcut_report_mode, self._SwitchBoolean, 'shortcut_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'subprocess report mode', 'Report whenever an external process is called.', HG.subprocess_report_mode, self._SwitchBoolean, 'subprocess_report_mode' )
- ClientGUIMenus.AppendMenuCheckItem( report_modes, 'subscription report mode', 'Have the subscription system report what it is doing.', HG.subscription_report_mode, self._SwitchBoolean, 'subscription_report_mode' )
-
- ClientGUIMenus.AppendMenu( debug, report_modes, 'report modes' )
-
- gui_actions = QW.QMenu( debug )
-
- default_local_file_service_key = HG.client_controller.services_manager.GetDefaultLocalFileServiceKey()
-
- ClientGUIMenus.AppendMenuItem( gui_actions, 'make some popups', 'Throw some varied popups at the message manager, just to check it is working.', self._DebugMakeSomePopups )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'make a long text popup', 'Make a popup with text that will grow in size.', self._DebugLongTextPopup )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'make a popup in five seconds', 'Throw a delayed popup at the message manager, giving you time to minimise or otherwise alter the client before it arrives.', self._controller.CallLater, 5, HydrusData.ShowText, 'This is a delayed popup message.' )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'make a modal popup in five seconds', 'Throw up a delayed modal popup to test with. It will stay alive for five seconds.', self._DebugMakeDelayedModalPopup, True )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'make a non-cancellable modal popup in five seconds', 'Throw up a delayed modal popup to test with. It will stay alive for five seconds.', self._DebugMakeDelayedModalPopup, False )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'make a new page in five seconds', 'Throw a delayed page at the main notebook, giving you time to minimise or otherwise alter the client before it arrives.', self._controller.CallLater, 5, self._controller.pub, 'new_page_query', default_local_file_service_key )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'refresh pages menu in five seconds', 'Delayed refresh the pages menu, giving you time to minimise or otherwise alter the client before it arrives.', self._controller.CallLater, 5, self._menu_updater_pages.update )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'publish some sub files in five seconds', 'Publish some files like a subscription would.', self._controller.CallLater, 5, lambda: HG.client_controller.pub( 'imported_files_to_page', [ HydrusData.GenerateKey() for i in range( 5 ) ], 'example sub files' ) )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'make a parentless text ctrl dialog', 'Make a parentless text control in a dialog to test some character event catching.', self._DebugMakeParentlessTextCtrl )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'reset multi-column list settings to default', 'Reset all multi-column list widths and other display settings to default.', self._DebugResetColumnListManager )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'force a main gui layout now', 'Tell the gui to relayout--useful to test some gui bootup layout issues.', self.adjustSize )
- ClientGUIMenus.AppendMenuItem( gui_actions, 'save \'last session\' gui session', 'Make an immediate save of the \'last session\' gui session. Mostly for testing crashes, where last session is not saved correctly.', self.ProposeSaveGUISession, CC.LAST_SESSION_SESSION_NAME )
-
- ClientGUIMenus.AppendMenu( debug, gui_actions, 'gui actions' )
-
- data_actions = QW.QMenu( debug )
-
- ClientGUIMenus.AppendMenuCheckItem( data_actions, 'db ui-hang relief mode', 'Have UI-synchronised database jobs process pending Qt events while they wait.', HG.db_ui_hang_relief_mode, self._SwitchBoolean, 'db_ui_hang_relief_mode' )
- ClientGUIMenus.AppendMenuItem( data_actions, 'review threads', 'Show current threads and what they are doing.', self._ReviewThreads )
- ClientGUIMenus.AppendMenuItem( data_actions, 'show scheduled jobs', 'Print some information about the currently scheduled jobs log.', self._DebugShowScheduledJobs )
- ClientGUIMenus.AppendMenuItem( data_actions, 'subscription manager snapshot', 'Have the subscription system show what it is doing.', self._controller.subscriptions_manager.ShowSnapshot )
- ClientGUIMenus.AppendMenuItem( data_actions, 'flush log', 'Command the log to write any buffered contents to hard drive.', HydrusData.DebugPrint, 'Flushing log' )
- ClientGUIMenus.AppendMenuItem( data_actions, 'enable truncated image loading', 'Enable the truncated image loading to test out broken jpegs.', self._EnableLoadTruncatedImages )
- ClientGUIMenus.AppendSeparator( data_actions )
- ClientGUIMenus.AppendMenuItem( data_actions, 'simulate program quit signal', 'Kill the program via a QApplication quit.', QW.QApplication.instance().quit )
-
- ClientGUIMenus.AppendMenu( debug, data_actions, 'data actions' )
-
- memory_actions = QW.QMenu( debug )
-
- ClientGUIMenus.AppendMenuItem( memory_actions, 'run fast memory maintenance', 'Tell all the fast caches to maintain themselves.', self._controller.MaintainMemoryFast )
- ClientGUIMenus.AppendMenuItem( memory_actions, 'run slow memory maintenance', 'Tell all the slow caches to maintain themselves.', self._controller.MaintainMemorySlow )
- ClientGUIMenus.AppendMenuItem( memory_actions, 'clear image rendering cache', 'Tell the image rendering system to forget all current images. This will often free up a bunch of memory immediately.', self._controller.ClearCaches )
- ClientGUIMenus.AppendMenuItem( memory_actions, 'clear thumbnail cache', 'Tell the thumbnail cache to forget everything and redraw all current thumbs.', self._controller.pub, 'reset_thumbnail_cache' )
- ClientGUIMenus.AppendMenuItem( memory_actions, 'print garbage', 'Print some information about the python garbage to the log.', self._DebugPrintGarbage )
- ClientGUIMenus.AppendMenuItem( memory_actions, 'take garbage snapshot', 'Capture current garbage object counts.', self._DebugTakeGarbageSnapshot )
- ClientGUIMenus.AppendMenuItem( memory_actions, 'show garbage snapshot changes', 'Show object count differences from the last snapshot.', self._DebugShowGarbageDifferences )
-
- ClientGUIMenus.AppendMenu( debug, memory_actions, 'memory actions' )
-
- network_actions = QW.QMenu( debug )
-
- ClientGUIMenus.AppendMenuItem( network_actions, 'fetch a url', 'Fetch a URL using the network engine as per normal.', self._DebugFetchAURL )
-
- ClientGUIMenus.AppendMenu( debug, network_actions, 'network actions' )
-
- tests = QW.QMenu( debug )
-
- ClientGUIMenus.AppendMenuItem( tests, 'run the ui test', 'Run hydrus_dev\'s weekly UI Test. Guaranteed to work and not mess up your session, ha ha.', self._RunUITest )
- ClientGUIMenus.AppendMenuItem( tests, 'run the client api test', 'Run hydrus_dev\'s weekly Client API Test. Guaranteed to work and not mess up your session, ha ha.', self._RunClientAPITest )
- ClientGUIMenus.AppendMenuItem( tests, 'run the server test', 'This will try to boot the server in your install folder and initialise it. This is mostly here for testing purposes.', self._RunServerTest )
-
- ClientGUIMenus.AppendMenu( debug, tests, 'tests, do not touch' )
-
- ClientGUIMenus.AppendMenu( menu, debug, 'debug' )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'about Qt', 'See information about the Qt framework.', QW.QMessageBox.aboutQt, self )
- ClientGUIMenus.AppendMenuItem( menu, 'about', 'See this client\'s version and other information.', self._AboutWindow )
-
- return ( menu, '&help' )
-
-
- if name == 'undo': result = undo()
- elif name == 'database': result = database()
- elif name == 'network': result = network()
- elif name == 'services': result = services()
- elif name == 'tags': result = tags()
- elif name == 'help': result = help()
-
- # hackery dackery doo
- ( menu_or_none, label ) = result
-
- return ( menu_or_none, label )
-
-
- def GenerateMenuInfoFile( self, import_folder_names, export_folder_names ):
-
- menu = QW.QMenu( self )
-
- ClientGUIMenus.AppendMenuItem( menu, 'import files', 'Add new files to the database.', self._ImportFiles )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- #
-
- i_and_e_submenu = QW.QMenu( menu )
-
- submenu = QW.QMenu( i_and_e_submenu )
-
- ClientGUIMenus.AppendMenuCheckItem( submenu, 'import folders', 'Pause the client\'s import folders.', HC.options['pause_import_folders_sync'], self._PausePlaySync, 'import_folders' )
- ClientGUIMenus.AppendMenuCheckItem( submenu, 'export folders', 'Pause the client\'s export folders.', HC.options['pause_export_folders_sync'], self._PausePlaySync, 'export_folders' )
-
- ClientGUIMenus.AppendMenu( i_and_e_submenu, submenu, 'pause' )
-
- ClientGUIMenus.AppendSeparator( i_and_e_submenu )
-
- if len( import_folder_names ) > 0:
-
- submenu = QW.QMenu( i_and_e_submenu )
-
- if len( import_folder_names ) > 1:
-
- ClientGUIMenus.AppendMenuItem( submenu, 'check all', 'Check all import folders.', self._CheckImportFolder )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
-
- for name in import_folder_names:
-
- ClientGUIMenus.AppendMenuItem( submenu, name, 'Check this import folder now.', self._CheckImportFolder, name )
-
-
- ClientGUIMenus.AppendMenu( i_and_e_submenu, submenu, 'check import folder now' )
-
-
- if len( export_folder_names ) > 0:
-
- submenu = QW.QMenu( i_and_e_submenu )
-
- if len( export_folder_names ) > 1:
-
- ClientGUIMenus.AppendMenuItem( submenu, 'run all', 'Run all export folders.', self._RunExportFolder )
-
- ClientGUIMenus.AppendSeparator( submenu )
-
-
- for name in export_folder_names:
-
- ClientGUIMenus.AppendMenuItem( submenu, name, 'Run this export folder now.', self._RunExportFolder, name )
-
-
- ClientGUIMenus.AppendMenu( i_and_e_submenu, submenu, 'run export folder now' )
-
-
- ClientGUIMenus.AppendSeparator( i_and_e_submenu )
-
- ClientGUIMenus.AppendMenuItem( i_and_e_submenu, 'manage import folders', 'Manage folders from which the client can automatically import.', self._ManageImportFolders )
- ClientGUIMenus.AppendMenuItem( i_and_e_submenu, 'manage export folders', 'Manage folders to which the client can automatically export.', self._ManageExportFolders )
-
- ClientGUIMenus.AppendMenu( menu, i_and_e_submenu, 'import and export folders' )
-
- #
-
- ClientGUIMenus.AppendSeparator( menu )
-
- open = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( open, 'installation directory', 'Open the installation directory for this client.', self._OpenInstallFolder )
- ClientGUIMenus.AppendMenuItem( open, 'database directory', 'Open the database directory for this instance of the client.', self._OpenDBFolder )
- ClientGUIMenus.AppendMenuItem( open, 'quick export directory', 'Open the export directory so you can easily access the files you have exported.', self._OpenExportFolder )
-
- ClientGUIMenus.AppendMenu( menu, open, 'open' )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'options', 'Change how the client operates.', self._ManageOptions )
- ClientGUIMenus.AppendMenuItem( menu, 'shortcuts', 'Edit the shortcuts your client responds to.', ClientGUIShortcutControls.ManageShortcuts, self )
-
- if ClientGUISystemTray.SystemTrayAvailable() and not ( not HC.PLATFORM_WINDOWS and not HG.client_controller.new_options.GetBoolean( 'advanced_mode' ) ):
-
- ClientGUIMenus.AppendSeparator( menu )
-
- label = 'minimise to system tray'
-
- if not HC.PLATFORM_WINDOWS:
-
- label += ' (may be buggy/crashy!)'
-
-
- ClientGUIMenus.AppendMenuItem( menu, label, 'Hide the client to an icon on your system tray.', self._FlipShowHideWholeUI )
-
-
- ClientGUIMenus.AppendSeparator( menu )
-
- we_borked_linux_pyinstaller = HC.PLATFORM_LINUX and not HC.RUNNING_FROM_SOURCE
-
- if not we_borked_linux_pyinstaller:
-
- ClientGUIMenus.AppendMenuItem( menu, 'restart', 'Shut the client down and then start it up again.', self.TryToExit, restart = True )
-
-
- ClientGUIMenus.AppendMenuItem( menu, 'exit and force shutdown maintenance', 'Shut the client down and force any outstanding shutdown maintenance to run.', self.TryToExit, force_shutdown_maintenance = True )
-
- ClientGUIMenus.AppendMenuItem( menu, 'exit', 'Shut the client down.', self.TryToExit )
-
- return ( menu, '&file' )
-
-
- def GenerateMenuInfoPages( self, gui_session_names, gui_session_names_to_backup_timestamps ):
-
- menu = QW.QMenu( self )
-
- ( total_active_page_count, total_closed_page_count, total_active_weight, total_closed_weight ) = self.GetTotalPageCounts()
-
- if total_active_weight > 10000000 and self._controller.new_options.GetBoolean( 'show_session_size_warnings' ) and not self._have_shown_session_size_warning:
-
- self._have_shown_session_size_warning = True
-
- HydrusData.ShowText( 'Your session weight is {}, which is pretty big! To keep your UI lag-free, please try to close some pages or clear some finished downloaders!'.format( HydrusData.ToHumanInt( total_active_weight ) ) )
-
-
- ClientGUIMenus.AppendMenuLabel( menu, '{} pages open'.format( HydrusData.ToHumanInt( total_active_page_count ) ), 'You have this many pages open.' )
-
- message = '''For session weight, a file counts as 1, and a URL counts as 20!
-
-Try to keep this below 10 million!'''
-
- ClientGUIMenus.AppendMenuItem( menu, 'total session weight: {}'.format( HydrusData.ToHumanInt( total_active_weight ) ), 'Your session is this heavy.', QW.QMessageBox.information, self, 'Information', message )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'refresh', 'If the current page has a search, refresh it.', self._Refresh )
-
- splitter_menu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( splitter_menu, 'show/hide', 'Show or hide the panels on the left.', self._ShowHideSplitters )
-
- ClientGUIMenus.AppendSeparator( splitter_menu )
-
- ClientGUIMenus.AppendMenuCheckItem( splitter_menu, 'save current page\'s sash positions on client exit', 'Set whether sash position should be saved over on client exit.', self._new_options.GetBoolean( 'saving_sash_positions_on_exit' ), self._new_options.FlipBoolean, 'saving_sash_positions_on_exit' )
-
- ClientGUIMenus.AppendSeparator( splitter_menu )
-
- ClientGUIMenus.AppendMenuItem( splitter_menu, 'save current page\'s sash positions now', 'Save the current page\'s sash positions.', self._SaveSplitterPositions )
-
- ClientGUIMenus.AppendSeparator( splitter_menu )
-
- ClientGUIMenus.AppendMenuItem( splitter_menu, 'restore all pages\' sash positions to saved value', 'Restore the current sash positions for all pages to the values that are saved.', self._RestoreSplitterPositions )
-
- ClientGUIMenus.AppendMenu( menu, splitter_menu, 'management and preview panels' )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- sessions = QW.QMenu( menu )
-
- gui_session_names = sorted( gui_session_names )
-
- if len( gui_session_names ) > 0:
-
- load = QW.QMenu( sessions )
-
- for name in gui_session_names:
-
- ClientGUIMenus.AppendMenuItem( load, name, 'Close all other pages and load this session.', self._notebook.LoadGUISession, name )
-
-
- ClientGUIMenus.AppendMenu( sessions, load, 'clear and load' )
-
- append = QW.QMenu( sessions )
-
- for name in gui_session_names:
-
- ClientGUIMenus.AppendMenuItem( append, name, 'Append this session to whatever pages are already open.', self._notebook.AppendGUISessionFreshest, name )
-
-
- ClientGUIMenus.AppendMenu( sessions, append, 'append' )
-
- if len( gui_session_names_to_backup_timestamps ) > 0:
-
- append_backup = QW.QMenu( sessions )
-
- rows = sorted( gui_session_names_to_backup_timestamps.items() )
-
- for ( name, timestamps ) in rows:
-
- submenu = QW.QMenu( append_backup )
-
- for timestamp in timestamps:
-
- ClientGUIMenus.AppendMenuItem( submenu, HydrusData.ConvertTimestampToPrettyTime( timestamp ), 'Append this backup session to whatever pages are already open.', self._notebook.AppendGUISessionBackup, name, timestamp )
-
-
- ClientGUIMenus.AppendMenu( append_backup, submenu, name )
-
-
- ClientGUIMenus.AppendMenu( sessions, append_backup, 'append session backup' )
-
-
-
- save = QW.QMenu( sessions )
-
- for name in gui_session_names:
-
- if name in ClientGUISession.RESERVED_SESSION_NAMES:
-
- continue
-
-
- ClientGUIMenus.AppendMenuItem( save, name, 'Save the existing open pages as a session.', self.ProposeSaveGUISession, name )
-
-
- ClientGUIMenus.AppendMenuItem( save, 'as new session', 'Save the existing open pages as a session.', self.ProposeSaveGUISession )
-
- ClientGUIMenus.AppendMenu( sessions, save, 'save' )
-
- if len( set( gui_session_names ).difference( ClientGUISession.RESERVED_SESSION_NAMES ) ) > 0:
-
- delete = QW.QMenu( sessions )
-
- for name in gui_session_names:
-
- if name in ClientGUISession.RESERVED_SESSION_NAMES:
-
- continue
-
-
- ClientGUIMenus.AppendMenuItem( delete, name, 'Delete this session.', self._DeleteGUISession, name )
-
-
- ClientGUIMenus.AppendMenu( sessions, delete, 'delete' )
-
-
- ClientGUIMenus.AppendMenu( menu, sessions, 'sessions' )
-
- ClientGUIMenus.AppendSeparator( menu )
-
- ClientGUIMenus.AppendMenuItem( menu, 'pick a new page', 'Choose a new page to open.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_PAGE ) )
-
- #
-
- search_menu = QW.QMenu( menu )
-
- services = self._controller.services_manager.GetServices()
-
- petition_permissions = [ ( content_type, HC.PERMISSION_ACTION_MODERATE ) for content_type in HC.SERVICE_TYPES_TO_CONTENT_TYPES ]
-
- repositories = [ service for service in services if service.GetServiceType() in HC.REPOSITORIES ]
-
- file_repositories = [ service for service in repositories if service.GetServiceType() == HC.FILE_REPOSITORY ]
-
- petition_resolvable_repositories = [ repository for repository in repositories if True in ( repository.HasPermission( content_type, action ) for ( content_type, action ) in petition_permissions ) ]
-
- local_file_services = [ service for service in services if service.GetServiceType() == HC.LOCAL_FILE_DOMAIN and service.GetServiceKey() != CC.LOCAL_UPDATE_SERVICE_KEY ]
-
- for service in local_file_services:
-
- ClientGUIMenus.AppendMenuItem( search_menu, service.GetName(), 'Open a new search tab.', self._notebook.NewPageQuery, service.GetServiceKey(), on_deepest_notebook = True )
-
-
- ClientGUIMenus.AppendMenuItem( search_menu, 'trash', 'Open a new search tab for your recently deleted files.', self._notebook.NewPageQuery, CC.TRASH_SERVICE_KEY, on_deepest_notebook = True )
-
- for service in file_repositories:
-
- ClientGUIMenus.AppendMenuItem( search_menu, service.GetName(), 'Open a new search tab for ' + service.GetName() + '.', self._notebook.NewPageQuery, service.GetServiceKey(), on_deepest_notebook = True )
-
-
- ClientGUIMenus.AppendMenu( menu, search_menu, 'new search page' )
-
- #
-
- if len( petition_resolvable_repositories ) > 0:
-
- petition_menu = QW.QMenu( menu )
-
- for service in petition_resolvable_repositories:
-
- ClientGUIMenus.AppendMenuItem( petition_menu, service.GetName(), 'Open a new petition page for ' + service.GetName() + '.', self._notebook.NewPagePetitions, service.GetServiceKey(), on_deepest_notebook = True )
-
-
- ClientGUIMenus.AppendMenu( menu, petition_menu, 'new petition page' )
-
-
- #
-
- download_menu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( download_menu, 'url download', 'Open a new tab to download some separate urls.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_URL_DOWNLOADER_PAGE ) )
- ClientGUIMenus.AppendMenuItem( download_menu, 'watcher', 'Open a new tab to watch threads or other updating locations.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_WATCHER_DOWNLOADER_PAGE ) )
- ClientGUIMenus.AppendMenuItem( download_menu, 'gallery', 'Open a new tab to download from gallery sites.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_GALLERY_DOWNLOADER_PAGE ) )
- ClientGUIMenus.AppendMenuItem( download_menu, 'simple downloader', 'Open a new tab to download files from generic galleries or threads.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_SIMPLE_DOWNLOADER_PAGE ) )
-
- ClientGUIMenus.AppendMenu( menu, download_menu, 'new download page' )
-
- #
-
- has_ipfs = len( [ service for service in services if service.GetServiceType() == HC.IPFS ] )
-
- if has_ipfs:
-
- download_popup_menu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( download_popup_menu, 'an ipfs multihash', 'Enter an IPFS multihash and attempt to import whatever is returned.', self._StartIPFSDownload )
-
- ClientGUIMenus.AppendMenu( menu, download_popup_menu, 'new download popup' )
-
-
- #
-
- special_menu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( special_menu, 'page of pages', 'Open a new tab that can hold more tabs.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_PAGE_OF_PAGES ) )
- ClientGUIMenus.AppendMenuItem( special_menu, 'duplicates processing', 'Open a new tab to discover and filter duplicate files.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_DUPLICATE_FILTER_PAGE ) )
-
- ClientGUIMenus.AppendMenu( menu, special_menu, 'new special page' )
-
- #
-
- ClientGUIMenus.AppendSeparator( menu )
-
- special_command_menu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( special_command_menu, 'clear all multiwatcher highlights', 'Command all multiwatcher pages to clear their highlighted watchers.', HG.client_controller.pub, 'clear_multiwatcher_highlights' )
-
- ClientGUIMenus.AppendMenu( menu, special_command_menu, 'special commands' )
-
- #
-
- return ( menu, '&pages' )
-
-
- def GenerateMenuInfoPending( self, nums_pending ):
-
- total_num_pending = 0
-
- menu = None
-
- can_do_a_menu = not HG.currently_uploading_pending
-
- for ( service_key, info ) in nums_pending.items():
-
- service = self._controller.services_manager.GetService( service_key )
-
- service_type = service.GetServiceType()
- name = service.GetName()
-
- if service_type == HC.TAG_REPOSITORY:
-
- pending_phrase = 'tag data to upload'
- petitioned_phrase = 'tag data to petition'
-
- elif service_type == HC.FILE_REPOSITORY:
-
- pending_phrase = 'files to upload'
- petitioned_phrase = 'files to petition'
-
- elif service_type == HC.IPFS:
-
- pending_phrase = 'files to pin'
- petitioned_phrase = 'files to unpin'
-
-
- if service_type == HC.TAG_REPOSITORY:
-
- num_pending = info[ HC.SERVICE_INFO_NUM_PENDING_MAPPINGS ] + info[ HC.SERVICE_INFO_NUM_PENDING_TAG_SIBLINGS ] + info[ HC.SERVICE_INFO_NUM_PENDING_TAG_PARENTS ]
- num_petitioned = info[ HC.SERVICE_INFO_NUM_PETITIONED_MAPPINGS ] + info[ HC.SERVICE_INFO_NUM_PETITIONED_TAG_SIBLINGS ] + info[ HC.SERVICE_INFO_NUM_PETITIONED_TAG_PARENTS ]
-
- elif service_type in ( HC.FILE_REPOSITORY, HC.IPFS ):
-
- num_pending = info[ HC.SERVICE_INFO_NUM_PENDING_FILES ]
- num_petitioned = info[ HC.SERVICE_INFO_NUM_PETITIONED_FILES ]
-
-
- if can_do_a_menu and num_pending + num_petitioned > 0:
-
- if menu is None:
-
- menu = QW.QMenu( self )
-
-
- submenu = QW.QMenu( menu )
-
- ClientGUIMenus.AppendMenuItem( submenu, 'commit', 'Upload ' + name + '\'s pending content.', self._UploadPending, service_key )
- ClientGUIMenus.AppendMenuItem( submenu, 'forget', 'Clear ' + name + '\'s pending content.', self._DeletePending, service_key )
-
- submessages = []
-
- if num_pending > 0:
-
- submessages.append( HydrusData.ToHumanInt( num_pending ) + ' ' + pending_phrase )
-
-
- if num_petitioned > 0:
-
- submessages.append( HydrusData.ToHumanInt( num_petitioned ) + ' ' + petitioned_phrase )
-
-
- message = name + ': ' + ', '.join( submessages )
-
- ClientGUIMenus.AppendMenu( menu, submenu, message )
-
-
- total_num_pending += num_pending + num_petitioned
-
-
- return ( menu, '&pending ({})'.format( HydrusData.ToHumanInt( total_num_pending ) ) )
+ self._menu_updater_network.update()
def GetCurrentPage( self ):
@@ -6665,10 +6859,27 @@ Try to keep this below 10 million!'''
total_closed_page_count = len( self._closed_pages )
- total_active_weight = self._notebook.GetTotalWeight()
- total_closed_weight = sum( ( page.GetTotalWeight() for ( time_closed, page ) in self._closed_pages ) )
+ ( total_active_num_hashes, total_active_num_seeds ) = self._notebook.GetTotalNumHashesAndSeeds()
- return ( total_active_page_count, total_closed_page_count, total_active_weight, total_closed_weight )
+ total_closed_num_hashes = 0
+ total_closed_num_seeds = 0
+
+ for ( time_closed, page ) in self._closed_pages:
+
+ ( num_hashes, num_seeds ) = page.GetTotalNumHashesAndSeeds()
+
+ total_closed_num_hashes += num_hashes
+ total_closed_num_seeds += num_seeds
+
+
+ return (
+ total_active_page_count,
+ total_active_num_hashes,
+ total_active_num_seeds,
+ total_closed_page_count,
+ total_closed_num_hashes,
+ total_closed_num_seeds
+ )
def HideToSystemTray( self ):
@@ -6810,6 +7021,12 @@ Try to keep this below 10 million!'''
+ def NotifyAdvancedMode( self ):
+
+ self._menu_updater_network.update()
+ self._menu_updater_file.update()
+
+
def NotifyClosedPage( self, page ):
if self._clipboard_watcher_destination_page_urls == page:
@@ -6829,6 +7046,7 @@ Try to keep this below 10 million!'''
self._controller.ClosePageKeys( page.GetPageKeys() )
self._menu_updater_pages.update()
+ self._menu_updater_undo.update()
def NotifyDeletedPage( self, page ):
@@ -6837,15 +7055,11 @@ Try to keep this below 10 million!'''
self._menu_updater_pages.update()
- self._menu_updater.Update()
-
def NotifyNewExportFolders( self ):
self._menu_updater_file.update()
- self._menu_updater.Update()
-
def NotifyNewImportFolders( self ):
@@ -6854,11 +7068,8 @@ Try to keep this below 10 million!'''
def NotifyNewOptions( self ):
- self.DirtyMenu( 'database' )
- self.DirtyMenu( 'services' )
- self.DirtyMenu( 'help' )
-
- self._menu_updater.Update()
+ self._menu_updater_database.update()
+ self._menu_updater_services.update()
def NotifyNewPages( self ):
@@ -6874,21 +7085,13 @@ Try to keep this below 10 million!'''
def NotifyNewPermissions( self ):
self._menu_updater_pages.update()
-
- self.DirtyMenu( 'services' )
- self.DirtyMenu( 'network' )
-
- self._menu_updater.Update()
+ self._menu_updater_services.update()
def NotifyNewServices( self ):
self._menu_updater_pages.update()
-
- self.DirtyMenu( 'services' )
- self.DirtyMenu( 'network' )
-
- self._menu_updater.Update()
+ self._menu_updater_services.update()
def NotifyNewSessions( self ):
@@ -6898,9 +7101,7 @@ Try to keep this below 10 million!'''
def NotifyNewUndo( self ):
- self.DirtyMenu( 'undo' )
-
- self._menu_updater.Update()
+ self._menu_updater_undo.update()
def PresentImportedFilesToPage( self, hashes, page_name ):
@@ -7051,10 +7252,6 @@ Try to keep this below 10 million!'''
self._SwitchBoolean( 'force_idle_mode' )
- self.DirtyMenu( 'help' )
-
- self._menu_updater.Update()
-
else:
command_processed = False
@@ -7143,41 +7340,6 @@ Try to keep this below 10 million!'''
self._controller.CallToThread( self._controller.SaveGUISession, session )
- def RefreshMenu( self ):
-
- if not QP.isValid( self ) or not self or self._CurrentlyMinimisedOrHidden():
-
- return
-
-
- db_going_to_hang_if_we_hit_it = HG.client_controller.DBCurrentlyDoingJob()
- menu_open = CGC.core().MenuIsOpen()
-
- if db_going_to_hang_if_we_hit_it or menu_open:
-
- self._controller.CallLaterQtSafe( self, 0.5, 'menu refresh wait loop', self.RefreshMenu )
-
- return
-
-
- if len( self._dirty_menus ) > 0:
-
- name = self._dirty_menus.pop()
-
- if name not in ( 'file', 'pages', 'pending' ):
-
- ( menu_or_none, label ) = self.GenerateMenuInfo( name )
-
- self.ReplaceMenu( name, menu_or_none, label )
-
-
-
- if len( self._dirty_menus ) > 0:
-
- self._controller.CallLaterQtSafe( self, 0.5, 'refresh menu', self.RefreshMenu )
-
-
-
def RefreshStatusBar( self ):
self._RefreshStatusBar()
@@ -7221,11 +7383,6 @@ Try to keep this below 10 million!'''
def REPEATINGBandwidth( self ):
- if not QP.isValid( self ) or self._CurrentlyMinimisedOrHidden():
-
- return
-
-
global_tracker = self._controller.network_engine.bandwidth_manager.GetMySessionTracker()
boot_time = self._controller.GetBootTime()
@@ -7320,11 +7477,6 @@ Try to keep this below 10 million!'''
def REPEATINGPageUpdate( self ):
- if not QP.isValid( self ) or self._CurrentlyMinimisedOrHidden():
-
- return
-
-
page = self.GetCurrentPage()
if page is not None:
@@ -7341,14 +7493,17 @@ Try to keep this below 10 million!'''
+ if len( self._pending_modal_job_keys ) > 0:
+
+ # another safety thing. normally modal lads are shown immediately, no problem, but sometimes they can be delayed
+ job_key = self._pending_modal_job_keys.pop()
+
+ self._controller.pub( 'modal_message', job_key )
+
+
def REPEATINGUIUpdate( self ):
- if self._currently_minimised_to_system_tray:
-
- return
-
-
for window in list( self._ui_update_windows ):
if not QP.isValid( window ):
@@ -7367,11 +7522,6 @@ Try to keep this below 10 million!'''
continue
- if tlw == self and self._CurrentlyMinimisedOrHidden():
-
- continue
-
-
try:
if HG.profile_mode:
@@ -7411,6 +7561,8 @@ Try to keep this below 10 million!'''
def ReplaceMenu( self, name, menu_or_none, label ):
+ # this is now way more complicated than I generally need, but I'll hang on to it for the moment
+
if menu_or_none is not None:
menu_or_none.menuAction().setProperty( 'hydrus_menubar_name', name )
@@ -7460,37 +7612,26 @@ Try to keep this below 10 million!'''
else:
- if name == 'pending' and HG.currently_uploading_pending:
+ old_action = self._menubar.actions()[ old_menu_index ]
+
+ old_menu = old_action.menu()
+
+ if menu_or_none is not None:
- self._menubar.actions()[ old_menu_index ].setText( label )
+ menu = menu_or_none
- if menu_or_none is not None:
-
- ClientGUIMenus.DestroyMenu( menu_or_none )
-
+ menu.setParent( self )
+
+ self._menubar.insertMenu( old_action, menu )
+
+ self._menubar.removeAction( old_action )
else:
- old_action = self._menubar.actions()[ old_menu_index ]
+ self._menubar.removeAction( old_action )
- old_menu = old_action.menu()
-
- if menu_or_none is not None:
-
- menu = menu_or_none
-
- menu.setParent( self )
-
- self._menubar.insertMenu( old_action, menu )
-
- self._menubar.removeAction( old_action )
-
- else:
-
- self._menubar.removeAction( old_action )
-
-
- ClientGUIMenus.DestroyMenu( old_menu )
+
+ ClientGUIMenus.DestroyMenu( old_menu )
diff --git a/hydrus/client/gui/ClientGUIImport.py b/hydrus/client/gui/ClientGUIImport.py
index a002aee7..286473ea 100644
--- a/hydrus/client/gui/ClientGUIImport.py
+++ b/hydrus/client/gui/ClientGUIImport.py
@@ -1876,9 +1876,23 @@ class GalleryImportPanel( ClientGUICommon.StaticBox ):
( file_network_job, gallery_network_job ) = self._gallery_import.GetNetworkJobs()
- self._file_download_control.SetNetworkJob( file_network_job )
+ if file_network_job is None:
+
+ self._file_download_control.ClearNetworkJob()
+
+ else:
+
+ self._file_download_control.SetNetworkJob( file_network_job )
+
- self._gallery_download_control.SetNetworkJob( gallery_network_job )
+ if gallery_network_job is None:
+
+ self._gallery_download_control.ClearNetworkJob()
+
+ else:
+
+ self._gallery_download_control.SetNetworkJob( gallery_network_job )
+
@@ -2513,9 +2527,23 @@ class WatcherReviewPanel( ClientGUICommon.StaticBox ):
( file_network_job, checker_network_job ) = self._watcher.GetNetworkJobs()
- self._file_download_control.SetNetworkJob( file_network_job )
+ if file_network_job is None:
+
+ self._file_download_control.ClearNetworkJob()
+
+ else:
+
+ self._file_download_control.SetNetworkJob( file_network_job )
+
- self._checker_download_control.SetNetworkJob( checker_network_job )
+ if checker_network_job is None:
+
+ self._checker_download_control.ClearNetworkJob()
+
+ else:
+
+ self._checker_download_control.SetNetworkJob( checker_network_job )
+
diff --git a/hydrus/client/gui/ClientGUILogin.py b/hydrus/client/gui/ClientGUILogin.py
index b163afa8..e17d6f0c 100644
--- a/hydrus/client/gui/ClientGUILogin.py
+++ b/hydrus/client/gui/ClientGUILogin.py
@@ -1131,7 +1131,7 @@ class EditLoginsPanel( ClientGUIScrolledPanels.EditPanel ):
return domains_to_login_info
-def GenerateTestNetworkJobPresentationContextFactory( window, network_job_control ):
+def GenerateTestNetworkJobPresentationContextFactory( window: QW.QWidget, network_job_control: ClientGUINetworkJobControl.NetworkJobControl ):
def network_job_presentation_context_factory( network_job ):
@@ -1142,7 +1142,14 @@ def GenerateTestNetworkJobPresentationContextFactory( window, network_job_contro
return
- network_job_control.SetNetworkJob( nj )
+ if nj is None:
+
+ network_job_control.ClearNetworkJob()
+
+ else:
+
+ network_job_control.SetNetworkJob( nj )
+
def enter_call():
diff --git a/hydrus/client/gui/ClientGUIMenus.py b/hydrus/client/gui/ClientGUIMenus.py
index ef3affe4..e9938a3d 100644
--- a/hydrus/client/gui/ClientGUIMenus.py
+++ b/hydrus/client/gui/ClientGUIMenus.py
@@ -180,7 +180,7 @@ def GetEventCallable( callable, *args, **kwargs ):
return event_callable
-def SanitiseLabel( label ):
+def SanitiseLabel( label: str ) -> str:
if label == '':
@@ -189,3 +189,15 @@ def SanitiseLabel( label ):
return label.replace( '&', '&&' )
+def SetMenuItemLabel( menu_item: QW.QAction, label: str ):
+
+ label = SanitiseLabel( label )
+
+ menu_item.setText( label )
+
+def SetMenuTitle( menu: QW.QMenu, label: str ):
+
+ label = SanitiseLabel( label )
+
+ menu.setTitle( label )
+
diff --git a/hydrus/client/gui/ClientGUIPopupMessages.py b/hydrus/client/gui/ClientGUIPopupMessages.py
index 1ff5efc3..c96cef47 100644
--- a/hydrus/client/gui/ClientGUIPopupMessages.py
+++ b/hydrus/client/gui/ClientGUIPopupMessages.py
@@ -687,7 +687,10 @@ class PopupMessageManager( QW.QWidget ):
def _DoDebugHide( self ):
- if not QP.isValid( self ): return
+ if not QP.isValid( self ):
+
+ return
+
parent = self.parentWidget()
@@ -1113,6 +1116,7 @@ class PopupMessageManager( QW.QWidget ):
raise
+
# This was originally a reviewpanel subclass which is a scroll area subclass, but having it in a scroll area didn't work out with dynamically updating size as the widget contents change.
class PopupMessageDialogPanel( QW.QWidget ):
diff --git a/hydrus/client/gui/networking/ClientGUINetworkJobControl.py b/hydrus/client/gui/networking/ClientGUINetworkJobControl.py
index 01e560b6..3870b069 100644
--- a/hydrus/client/gui/networking/ClientGUINetworkJobControl.py
+++ b/hydrus/client/gui/networking/ClientGUINetworkJobControl.py
@@ -175,6 +175,11 @@ class NetworkJobControl( QW.QFrame ):
ClientGUIMenus.AppendMenuItem( menu, 'reattempt connection now', 'Stop waiting on a connection error and reattempt the job now.', self._network_job.OverrideConnectionErrorWait )
+ if not self._network_job.DomainOK():
+
+ ClientGUIMenus.AppendMenuItem( menu, 'scrub domain errors', 'Clear recent domain errors and allow this job to go now.', self._network_job.ScrubDomainErrors )
+
+
if self._network_job.CurrentlyWaitingOnServersideBandwidth():
ClientGUIMenus.AppendMenuItem( menu, 'reattempt request now (server reports low bandwidth)', 'Stop waiting on a serverside bandwidth delay and reattempt the job now.', self._network_job.OverrideServersideBandwidthWait )
@@ -330,7 +335,16 @@ class NetworkJobControl( QW.QFrame ):
def ClearNetworkJob( self ):
- self.SetNetworkJob( None )
+ if self._network_job is not None:
+
+ self._network_job = None
+
+ self._gauge.setToolTip( '' )
+
+ self._Update()
+
+ HG.client_controller.gui.UnregisterUIUpdateWindow( self )
+
def FlipAutoOverrideBandwidth( self ):
@@ -338,33 +352,17 @@ class NetworkJobControl( QW.QFrame ):
self._auto_override_bandwidth_rules = not self._auto_override_bandwidth_rules
- def SetNetworkJob( self, network_job: typing.Optional[ ClientNetworkingJobs.NetworkJob ] ):
+ def SetNetworkJob( self, network_job: ClientNetworkingJobs.NetworkJob ):
- if network_job is None:
+ if self._network_job != network_job:
- if self._network_job is not None:
-
- self._network_job = None
-
- self._gauge.setToolTip( '' )
-
- self._Update()
-
- HG.client_controller.gui.UnregisterUIUpdateWindow( self )
-
+ self._network_job = network_job
- else:
+ self._gauge.setToolTip( self._network_job.GetURL() )
- if self._network_job != network_job:
-
- self._network_job = network_job
-
- self._gauge.setToolTip( self._network_job.GetURL() )
-
- self._Update()
-
- HG.client_controller.gui.RegisterUIUpdateWindow( self )
-
+ self._Update()
+
+ HG.client_controller.gui.RegisterUIUpdateWindow( self )
diff --git a/hydrus/client/gui/pages/ClientGUIManagement.py b/hydrus/client/gui/pages/ClientGUIManagement.py
index 146f7702..6b4d836a 100644
--- a/hydrus/client/gui/pages/ClientGUIManagement.py
+++ b/hydrus/client/gui/pages/ClientGUIManagement.py
@@ -3700,9 +3700,23 @@ class ManagementPanelImporterSimpleDownloader( ManagementPanelImporter ):
( file_network_job, page_network_job ) = self._simple_downloader_import.GetNetworkJobs()
- self._file_download_control.SetNetworkJob( file_network_job )
+ if file_network_job is None:
+
+ self._file_download_control.ClearNetworkJob()
+
+ else:
+
+ self._file_download_control.SetNetworkJob( file_network_job )
+
- self._page_download_control.SetNetworkJob( page_network_job )
+ if page_network_job is None:
+
+ self._page_download_control.ClearNetworkJob()
+
+ else:
+
+ self._page_download_control.SetNetworkJob( page_network_job )
+
def CheckAbleToClose( self ):
@@ -3923,9 +3937,23 @@ class ManagementPanelImporterURLs( ManagementPanelImporter ):
( file_network_job, gallery_network_job ) = self._urls_import.GetNetworkJobs()
- self._file_download_control.SetNetworkJob( file_network_job )
+ if file_network_job is None:
+
+ self._file_download_control.ClearNetworkJob()
+
+ else:
+
+ self._file_download_control.SetNetworkJob( file_network_job )
+
- self._gallery_download_control.SetNetworkJob( gallery_network_job )
+ if gallery_network_job is None:
+
+ self._gallery_download_control.ClearNetworkJob()
+
+ else:
+
+ self._gallery_download_control.SetNetworkJob( gallery_network_job )
+
def CheckAbleToClose( self ):
diff --git a/hydrus/client/gui/pages/ClientGUIPages.py b/hydrus/client/gui/pages/ClientGUIPages.py
index 09561ef7..767dc6b6 100644
--- a/hydrus/client/gui/pages/ClientGUIPages.py
+++ b/hydrus/client/gui/pages/ClientGUIPages.py
@@ -31,6 +31,18 @@ from hydrus.client.gui.pages import ClientGUIResults
from hydrus.client.gui.pages import ClientGUISession
from hydrus.client.gui.pages import ClientGUISessionLegacy # to get serialisable data types loaded
+def ConvertNumHashesToWeight( num_hashes: int ) -> int:
+
+ return num_hashes
+
+def ConvertNumHashesAndSeedsToWeight( num_hashes: int, num_seeds: int ) -> int:
+
+ return ConvertNumHashesToWeight( num_hashes ) + ConvertNumSeedsToWeight( num_seeds )
+
+def ConvertNumSeedsToWeight( num_seeds: int ) -> int:
+
+ return num_seeds * 20
+
class DialogPageChooser( ClientGUIDialogs.Dialog ):
def __init__( self, parent, controller ):
@@ -784,12 +796,19 @@ class Page( QW.QSplitter ):
return ( hpos, vpos )
- def GetTotalWeight( self ):
+ def GetTotalNumHashesAndSeeds( self ):
num_hashes = len( self.GetHashes() )
num_seeds = self._management_controller.GetNumSeeds()
- return num_hashes + ( num_seeds * 20 )
+ return ( num_hashes, num_seeds )
+
+
+ def GetTotalWeight( self ) -> int:
+
+ ( num_hashes, num_seeds ) = self.GetTotalNumHashesAndSeeds()
+
+ return ConvertNumHashesAndSeedsToWeight( num_hashes, num_seeds )
def IsCurrentSessionPageDirty( self ):
@@ -1241,7 +1260,6 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
else:
self._controller.pub( 'notify_closed_page', page )
- self._controller.pub( 'notify_new_undo' )
return True
@@ -2492,7 +2510,23 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
- def GetTotalWeight( self ):
+ def GetTotalNumHashesAndSeeds( self ) -> int:
+
+ total_num_hashes = 0
+ total_num_seeds = 0
+
+ for page in self._GetPages():
+
+ ( num_hashes, num_seeds ) = page.GetTotalNumHashesAndSeeds()
+
+ total_num_hashes += num_hashes
+ total_num_seeds += num_seeds
+
+
+ return ( total_num_hashes, total_num_seeds )
+
+
+ def GetTotalWeight( self ) -> int:
total_weight = sum( ( page.GetTotalWeight() for page in self._GetPages() ) )
@@ -2874,7 +2908,14 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
WARNING_TOTAL_PAGES = self._controller.new_options.GetInteger( 'total_pages_warning' )
MAX_TOTAL_PAGES = 500
- ( total_active_page_count, total_closed_page_count, total_active_weight, total_closed_weight ) = self._controller.gui.GetTotalPageCounts()
+ (
+ total_active_page_count,
+ total_active_num_hashes,
+ total_active_num_seeds,
+ total_closed_page_count,
+ total_closed_num_hashes,
+ total_closed_num_seeds
+ ) = self._controller.gui.GetTotalPageCounts()
if total_active_page_count + total_closed_page_count >= WARNING_TOTAL_PAGES:
diff --git a/hydrus/client/gui/search/ClientGUIACDropdown.py b/hydrus/client/gui/search/ClientGUIACDropdown.py
index 8417e239..d0c7e85e 100644
--- a/hydrus/client/gui/search/ClientGUIACDropdown.py
+++ b/hydrus/client/gui/search/ClientGUIACDropdown.py
@@ -165,7 +165,7 @@ def ReadFetch(
fetch_from_db = True
- if synchronised and qt_media_callable is not None:
+ if synchronised and qt_media_callable is not None and not file_search_context.GetSystemPredicates().HasSystemLimit():
try:
@@ -1783,7 +1783,9 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
def _SignalNewSearchState( self ):
- file_search_context = self.GetFileSearchContext()
+ self._file_search_context.SetPredicates( self._predicates_listbox.GetPredicates() )
+
+ file_search_context = self._file_search_context.Duplicate()
self.searchChanged.emit( file_search_context )
@@ -2040,6 +2042,8 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
HG.client_controller.CallLaterQtSafe( self, 0.2, 'set stub predicates', self.SetStubPredicates, job_key, stub_predicates, parsed_autocomplete_text )
+ fsc = self.GetFileSearchContext()
+
if self._under_construction_or_predicate is None:
under_construction_or_predicate = None
@@ -2049,7 +2053,7 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
under_construction_or_predicate = self._under_construction_or_predicate.Duplicate()
- HG.client_controller.CallToThread( ReadFetch, self, job_key, self.SetFetchedResults, parsed_autocomplete_text, self._media_callable, self._file_search_context.Duplicate(), self._search_pause_play.IsOn(), self._include_unusual_predicate_types, self._results_cache, under_construction_or_predicate, self._force_system_everything )
+ HG.client_controller.CallToThread( ReadFetch, self, job_key, self.SetFetchedResults, parsed_autocomplete_text, self._media_callable, fsc, self._search_pause_play.IsOn(), self._include_unusual_predicate_types, self._results_cache, under_construction_or_predicate, self._force_system_everything )
def _ShouldTakeResponsibilityForEnter( self ):
@@ -2195,11 +2199,11 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
self._file_search_context = file_search_context.Duplicate()
+ self._predicates_listbox.SetPredicates( self._file_search_context.GetPredicates() )
+
self._ChangeFileService( self._file_search_context.GetFileServiceKey() )
self._ChangeTagService( self._file_search_context.GetTagSearchContext().service_key )
- self._predicates_listbox.SetPredicates( self._file_search_context.GetPredicates() )
-
self._SignalNewSearchState()
@@ -2231,6 +2235,12 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
self._RestoreTextCtrlFocus()
+ if not self._search_pause_play.IsOn() and not self._file_search_context.GetSystemPredicates().HasSystemLimit():
+
+ # update if user goes from sync to non-sync
+ self._SetListDirty()
+
+
def PausePlaySearch( self ):
diff --git a/hydrus/client/gui/widgets/ClientGUICommon.py b/hydrus/client/gui/widgets/ClientGUICommon.py
index fe7af752..46f0c17b 100644
--- a/hydrus/client/gui/widgets/ClientGUICommon.py
+++ b/hydrus/client/gui/widgets/ClientGUICommon.py
@@ -714,6 +714,11 @@ class CheckboxManagerOptions( CheckboxManager ):
new_options.InvertBoolean( self._boolean_name )
+ if self._boolean_name == 'advanced_mode':
+
+ HG.client_controller.pub( 'notify_advanced_mode' )
+
+
HG.client_controller.pub( 'checkbox_manager_inverted' )
HG.client_controller.pub( 'notify_new_menu_option' )
diff --git a/hydrus/client/networking/ClientNetworkingDomain.py b/hydrus/client/networking/ClientNetworkingDomain.py
index 3c898109..65e64c79 100644
--- a/hydrus/client/networking/ClientNetworkingDomain.py
+++ b/hydrus/client/networking/ClientNetworkingDomain.py
@@ -2016,6 +2016,26 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
+ def ScrubDomainErrors( self, url ):
+
+ with self._lock:
+
+ try:
+
+ domain = ConvertURLIntoSecondLevelDomain( url )
+
+ except:
+
+ return
+
+
+ if domain in self._second_level_domains_to_network_infrastructure_errors:
+
+ del self._second_level_domains_to_network_infrastructure_errors[ domain ]
+
+
+
+
def SetClean( self ):
with self._lock:
diff --git a/hydrus/client/networking/ClientNetworkingJobs.py b/hydrus/client/networking/ClientNetworkingJobs.py
index ecd9f650..e8da807b 100644
--- a/hydrus/client/networking/ClientNetworkingJobs.py
+++ b/hydrus/client/networking/ClientNetworkingJobs.py
@@ -1098,6 +1098,16 @@ class NetworkJob( object ):
+ def ScrubDomainErrors( self ):
+
+ with self._lock:
+
+ self.engine.domain_manager.ScrubDomainErrors( self._url )
+
+ self._wake_time_float = 0.0
+
+
+
def SetError( self, e, error ):
with self._lock:
diff --git a/hydrus/core/HydrusConstants.py b/hydrus/core/HydrusConstants.py
index 460adc82..84d78c2c 100644
--- a/hydrus/core/HydrusConstants.py
+++ b/hydrus/core/HydrusConstants.py
@@ -81,7 +81,7 @@ options = {}
# Misc
NETWORK_VERSION = 20
-SOFTWARE_VERSION = 456
+SOFTWARE_VERSION = 457
CLIENT_API_VERSION = 20
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
diff --git a/hydrus/core/HydrusFileHandling.py b/hydrus/core/HydrusFileHandling.py
index c35b7851..57359901 100644
--- a/hydrus/core/HydrusFileHandling.py
+++ b/hydrus/core/HydrusFileHandling.py
@@ -1,5 +1,6 @@
import hashlib
import os
+import struct
from hydrus.core import HydrusAudioHandling
from hydrus.core import HydrusConstants as HC
@@ -311,7 +312,7 @@ def GetMime( path, ok_to_look_for_hydrus_updates = False ):
elif mime == HC.UNDETERMINED_PNG:
- if HydrusVideoHandling.HasVideoStream( path ):
+ if IsPNGAnimated( bit_to_check ):
return HC.IMAGE_APNG
@@ -379,3 +380,22 @@ def GetMime( path, ok_to_look_for_hydrus_updates = False ):
return HC.APPLICATION_UNKNOWN
+def IsPNGAnimated( file_header_bytes ):
+
+ if file_header_bytes[ 37: ].startswith( b'acTL' ):
+
+ # this is an animated png
+
+ # acTL chunk in an animated png is 4 bytes of num frames, then 4 bytes of num times to loop
+ # https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk
+
+ num_frames = HydrusVideoHandling.GetAPNGNumFrames( file_header_bytes )
+
+ if num_frames > 1:
+
+ return True
+
+
+
+ return False
+
diff --git a/hydrus/core/HydrusGlobals.py b/hydrus/core/HydrusGlobals.py
index 4fa37679..7a332f46 100644
--- a/hydrus/core/HydrusGlobals.py
+++ b/hydrus/core/HydrusGlobals.py
@@ -68,7 +68,6 @@ mpv_report_mode = False
force_idle_mode = False
no_page_limit_mode = False
thumbnail_debug_mode = False
-currently_uploading_pending = False
do_idle_shutdown_work = False
shutdown_complete = False
diff --git a/hydrus/core/HydrusVideoHandling.py b/hydrus/core/HydrusVideoHandling.py
index ed7f1f0c..a94228bc 100644
--- a/hydrus/core/HydrusVideoHandling.py
+++ b/hydrus/core/HydrusVideoHandling.py
@@ -1,6 +1,7 @@
import numpy
import os
import re
+import struct
import subprocess
from hydrus.core import HydrusAudioHandling
@@ -42,6 +43,12 @@ def CheckFFMPEGError( lines ):
raise HydrusExceptions.DamagedOrUnusualFileException( 'FFMPEG could not parse.' )
+def GetAPNGNumFrames( file_header_bytes ):
+
+ ( num_frames, ) = struct.unpack( '>I', file_header_bytes[ 41 : 45 ] )
+
+ return num_frames
+
def GetFFMPEGVersion():
cmd = [ FFMPEG_PATH, '-version' ]
@@ -217,6 +224,34 @@ def GetFFMPEGInfoLines( path, count_frames_manually = False, only_first_second =
return lines
+def GetFFMPEGAPNGProperties( path ):
+
+ with open( path, 'rb' ) as f:
+
+ file_header_bytes = f.read( 256 )
+
+
+ num_frames = GetAPNGNumFrames( file_header_bytes )
+
+ lines = GetFFMPEGInfoLines( path )
+
+ resolution = ParseFFMPEGVideoResolution( lines )
+
+ ( fps, confident_fps ) = ParseFFMPEGFPS( lines )
+
+ if not confident_fps:
+
+ fps = 24
+
+
+ duration = num_frames / fps
+
+ duration_in_ms = int( duration * 1000 )
+
+ has_audio = False
+
+ return ( resolution, duration_in_ms, num_frames, has_audio )
+
def GetFFMPEGVideoProperties( path, force_count_frames_manually = False ):
lines_for_first_second = GetFFMPEGInfoLines( path, count_frames_manually = True, only_first_second = True )
diff --git a/hydrus/core/networking/HydrusNetwork.py b/hydrus/core/networking/HydrusNetwork.py
index b20206fb..b67ce796 100644
--- a/hydrus/core/networking/HydrusNetwork.py
+++ b/hydrus/core/networking/HydrusNetwork.py
@@ -172,6 +172,22 @@ class Account( object ):
return self.__repr__()
+ def _CheckBanned( self ):
+
+ if self._IsBanned():
+
+ raise HydrusExceptions.InsufficientCredentialsException( 'This account is banned: ' + self._GetBannedString() )
+
+
+
+ def _CheckExpired( self ):
+
+ if self._IsExpired():
+
+ raise HydrusExceptions.InsufficientCredentialsException( 'This account is expired: ' + self._GetExpiresString() )
+
+
+
def _CheckFunctional( self ):
if self._created == 0:
@@ -179,20 +195,15 @@ class Account( object ):
raise HydrusExceptions.ConflictException( 'account is unsynced' )
- if self._account_type.HasPermission( HC.CONTENT_TYPE_SERVICES, HC.PERMISSION_ACTION_MODERATE ):
+ if self._IsAdmin():
- return # admins can do anything
+ # admins can do anything
+ return
- if self._IsBanned():
-
- raise HydrusExceptions.InsufficientCredentialsException( 'This account is banned: ' + self._GetBannedString() )
-
+ self._CheckBanned()
- if self._IsExpired():
-
- raise HydrusExceptions.InsufficientCredentialsException( 'This account is expired: ' + self._GetExpiresString() )
-
+ self._CheckExpired()
if not self._account_type.BandwidthOK( self._bandwidth_tracker ):
@@ -219,6 +230,11 @@ class Account( object ):
return HydrusData.ConvertTimestampToPrettyExpires( self._expires )
+ def _IsAdmin( self ):
+
+ return self._account_type.HasPermission( HC.CONTENT_TYPE_SERVICES, HC.PERMISSION_ACTION_MODERATE )
+
+
def _IsBanned( self ):
if self._banned_info is None:
@@ -299,6 +315,15 @@ class Account( object ):
with self._lock:
+ if self._IsAdmin():
+
+ return
+
+
+ self._CheckBanned()
+
+ self._CheckExpired()
+
if not self._account_type.HasPermission( content_type, action ):
raise HydrusExceptions.InsufficientCredentialsException( 'You do not have permission to do that.' )
@@ -441,6 +466,16 @@ class Account( object ):
with self._lock:
+ if self._IsAdmin():
+
+ return True
+
+
+ if self._IsBanned() or self._IsExpired():
+
+ return False
+
+
return self._account_type.HasPermission( content_type, action )
diff --git a/hydrus/server/ServerDB.py b/hydrus/server/ServerDB.py
index e08e6517..0a1b3de6 100644
--- a/hydrus/server/ServerDB.py
+++ b/hydrus/server/ServerDB.py
@@ -3017,7 +3017,7 @@ class DB( HydrusDB.HydrusDB ):
- def _RepositoryProcessClientToServerUpdate( self, service_key, account, client_to_server_update, timestamp ):
+ def _RepositoryProcessClientToServerUpdate( self, service_key: bytes, account: HydrusNetwork.Account, client_to_server_update: HydrusNetwork.ClientToServerUpdate, timestamp: int ):
service_id = self._GetServiceId( service_key )
diff --git a/hydrus/test/TestServerDB.py b/hydrus/test/TestServerDB.py
index f12ac401..e56a3ebc 100644
--- a/hydrus/test/TestServerDB.py
+++ b/hydrus/test/TestServerDB.py
@@ -65,7 +65,7 @@ class TestServerDB( unittest.TestCase ):
#
- r_keys = self._read( 'registration_keys', self._tag_service_key, self._tag_service_account, 5, self._deletee_user_account_type.GetAccountTypeKey(), 86400 * 365 )
+ r_keys = self._read( 'registration_keys', self._tag_service_key, self._tag_service_account, 5, self._deletee_user_account_type.GetAccountTypeKey(), HydrusData.GetNow() + 86400 * 365 )
access_keys = [ self._read( 'access_key', self._tag_service_key, r_key ) for r_key in r_keys ]
@@ -97,7 +97,7 @@ class TestServerDB( unittest.TestCase ):
#
- r_keys = self._read( 'registration_keys', self._tag_service_key, self._tag_service_account, 5, self._regular_user_account_type.GetAccountTypeKey(), 86400 * 365 )
+ r_keys = self._read( 'registration_keys', self._tag_service_key, self._tag_service_account, 5, self._regular_user_account_type.GetAccountTypeKey(), HydrusData.GetNow() + 86400 * 365 )
self.assertEqual( len( r_keys ), 5 )