diff --git a/README.md b/README.md
index 586a79e8..1cf99e23 100755
--- a/README.md
+++ b/README.md
@@ -6,7 +6,8 @@ I am continually working on the software and try to put out a new release every
This github repository is currently a weekly sync with my home dev environment, where I work on hydrus by myself. **Feel free to fork and do whatever you like with my code, but please do not make pull requests.** The [issue tracker here on Github](https://github.com/hydrusnetwork/hydrus/issues) is active and run by blessed volunteer users. I am not active here on Github, and I have difficulty keeping up with social media in general, but I welcome feedback of any sort and will eventually catch up with and reply to email, the 8chan or Endchan, tumblr, twitter, or the discord.
-The client can do quite a lot! Please check out the help inside the release or [here](https://hydrusnetwork.github.io/hydrus/help), which includes a comprehensive getting started guide.
+The client can do quite a lot! Please check out the help inside the release or [here](https://hydrusnetwork.github.io/hydrus/help), which includes a comprehensive getting started guide.
+
A rudimentary documentation for the [container](https://github.com/hydrusnetwork/hydrus/pkgs/container/hydrus) setup can be found [here](https://github.com/hydrusnetwork/hydrus/blob/master/static/build_files/docker/README.md).
* [homepage](https://hydrusnetwork.github.io/hydrus/)
diff --git a/help/changelog.html b/help/changelog.html
index f01a0f00..9740f275 100755
--- a/help/changelog.html
+++ b/help/changelog.html
@@ -8,6 +8,27 @@
+
+
+ - misc:
+ - my 'simple' shortcut commands can now store additional variables. to start things off, I have finally added 'seek video' shortcut commands that have back/forwards and second+millisecond values. these should work on the native video viewer and mpv, audio and video. existing users will have to add their own (do it to the 'media viewers - all' set), but new users will get ctrl+left/right for 2.5s back and 5s forwards as the new defaults. let me know if you have any trouble!
+ - the maximum number tracked by 'tag as number' system predicate is expanded from -99999999->99999999 to -2^63 -> 2^63 - 1. tag caches will be regenerated on update to store these, it will take a few minutes. the input ui for the system predicate is temporarily limited to -/+2^31, but I'll expand it
+ - subscriptions now have a checkbox for 'do not worry about subscription gaps'. if you have a subscription that gets files randomly, or gets an intentionally small sample, this will disable the 'hey, there was a gap, click here to fill it in' popup messages
+ - you can now set negative values for the duplicate score weights in options->duplicates
+ - also added a weight for the 'nice ratio' duplicate comparison
+ - vastly improved the cancel speed for searches in the realm of 'get the files that have any xxx tag', be that all tags or a namespace wildcard, and also some important search setup for various 'all known files' search pages. if you have ever tried to search the PTR raw and run into a three minute uncancellable initial setup lag, it should be gone now!
+ - when you right-click on files in a specific local file domain (e.g. trash or my files), the 'view this file's dupes' number check is now run on 'all local files' as well, and if the numbers differ, a second menu is shown for all local files. this should make it easier to chase dupes of trashed files that are still untrashed while also allowing a trash-only search
+ - fixed a critical bug in repository mapping processing that was not adding mappings to certain caches for files imported before the repo was added, and/or the new repository 'per content type' processing reset. this mostly manifested as these files not showing up in search results despite the tag being there. there is more work to do here, so it is top priority next week, and likely some maintenance to regen the bad caches
+ - .
+ - boring rewrites for multiple local file services:
+ - many users have asked for the ability, when multiple local file domains are available, to search multiple file domains at once. I spent time this week doing background work to support this, and a related concept of searching 'deleted' files, which is a current gap in the program and not always covered by 'all known files'. nothing significant changes, but almost all the file search code now works on n file domains rather than 1, but for now n=1 lmao
+ - made a new 'database search context' object to handle a virtualised but still simple and fast file 'location view' at the database level
+ - the primary file search call is converted to use this object. references to a single file service are replaced with the view or its components
+ - all duplicate file search code is moved to the new location search context
+ - searching by 'system:import time' now works over multiple domains, although it is a little muddled. in future, import time predicate will have an optional specific file service and do 'import time' vs 'deleted time'
+ - 'system:local' and 'system:not local' is adapted so it can still work fast with multiple file domains, sped up worst case 'local' time, and a new optimisation lets it run fast for 'deleted from local files' too
+ - sorting search files by import time is now only supported if the search domain is just one domain
+
- stupid anti-virus thing:
diff --git a/hydrus/client/ClientApplicationCommand.py b/hydrus/client/ClientApplicationCommand.py
index efd3ef01..19818b28 100644
--- a/hydrus/client/ClientApplicationCommand.py
+++ b/hydrus/client/ClientApplicationCommand.py
@@ -1,4 +1,5 @@
from hydrus.core import HydrusConstants as HC
+from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusSerialisable
@@ -137,6 +138,7 @@ SIMPLE_AUTOCOMPLETE_IF_EMPTY_PAGE_LEFT = 130
SIMPLE_AUTOCOMPLETE_IF_EMPTY_PAGE_RIGHT = 131
SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_PREVIOUS = 132
SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_NEXT = 133
+SIMPLE_MEDIA_SEEK_DELTA = 134
simple_enum_to_str_lookup = {
SIMPLE_ARCHIVE_DELETE_FILTER_BACK : 'archive/delete filter: back',
@@ -272,7 +274,8 @@ simple_enum_to_str_lookup = {
SIMPLE_AUTOCOMPLETE_IF_EMPTY_PAGE_LEFT : 'if input & results list are empty, move to left one service page',
SIMPLE_AUTOCOMPLETE_IF_EMPTY_PAGE_RIGHT : 'if input & results list are empty, move to right one service page',
SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_PREVIOUS : 'if input & results list are empty and in media viewer manage tags dialog, move to previous media',
- SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_NEXT : 'if input & results list are empty and in media viewer manage tags dialog, move to previous media'
+ SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_NEXT : 'if input & results list are empty and in media viewer manage tags dialog, move to previous media',
+ SIMPLE_MEDIA_SEEK_DELTA : 'seek media'
}
legacy_simple_str_to_enum_lookup = {
@@ -389,7 +392,7 @@ class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_APPLICATION_COMMAND
SERIALISABLE_NAME = 'Application Command'
- SERIALISABLE_VERSION = 3
+ SERIALISABLE_VERSION = 4
def __init__( self, command_type = None, data = None ):
@@ -397,10 +400,7 @@ class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
command_type = APPLICATION_COMMAND_TYPE_SIMPLE
-
- if data is None:
-
- data = SIMPLE_ARCHIVE_FILE
+ data = ( SIMPLE_ARCHIVE_FILE, None )
HydrusSerialisable.SerialisableBase.__init__( self )
@@ -409,6 +409,21 @@ class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
self._data = data
+ def __eq__( self, other ):
+
+ if isinstance( other, ApplicationCommand ):
+
+ return self.__hash__() == other.__hash__()
+
+
+ return NotImplemented
+
+
+ def __hash__( self ):
+
+ return ( self._command_type, self._data ).__hash__()
+
+
def __repr__( self ):
return self.ToString()
@@ -436,7 +451,14 @@ class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
if self._command_type == APPLICATION_COMMAND_TYPE_SIMPLE:
- self._data = serialisable_data
+ ( simple_action, simple_data ) = serialisable_data
+
+ if isinstance( simple_data, list ):
+
+ simple_data = tuple( simple_data )
+
+
+ self._data = ( simple_action, simple_data )
elif self._command_type == APPLICATION_COMMAND_TYPE_CONTENT:
@@ -490,15 +512,48 @@ class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
return ( 3, new_serialisable_info )
+ if version == 3:
+
+ ( command_type, serialisable_data ) = old_serialisable_info
+
+ if command_type == APPLICATION_COMMAND_TYPE_SIMPLE:
+
+ serialisable_data = ( serialisable_data, None )
+
+
+ new_serialisable_info = ( command_type, serialisable_data )
+
+ return ( 4, new_serialisable_info )
+
+
def GetCommandType( self ):
return self._command_type
- def GetData( self ):
+ def GetSimpleAction( self ) -> int:
- return self._data
+ if self._command_type != APPLICATION_COMMAND_TYPE_SIMPLE:
+
+ raise Exception( 'Not a simple command!' )
+
+
+ ( simple_action, simple_data ) = self._data
+
+ return simple_action
+
+
+ def GetSimpleData( self ):
+
+ if self._command_type != APPLICATION_COMMAND_TYPE_SIMPLE:
+
+ raise Exception( 'Not a simple command!' )
+
+
+ ( simple_action, simple_data ) = self._data
+
+ return simple_data
def GetContentAction( self ):
@@ -551,7 +606,22 @@ class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
if self._command_type == APPLICATION_COMMAND_TYPE_SIMPLE:
- return simple_enum_to_str_lookup[ self._data ]
+ action = self.GetSimpleAction()
+
+ s = simple_enum_to_str_lookup[ action ]
+
+ if action == SIMPLE_MEDIA_SEEK_DELTA:
+
+ ( direction, ms ) = self.GetSimpleData()
+
+ direction_s = 'back' if direction == -1 else 'forwards'
+
+ ms_s = HydrusData.TimeDeltaToPrettyTimeDelta( ms / 1000 )
+
+ s = '{} ({} {})'.format( s, direction_s, ms_s )
+
+
+ return s
elif self._command_type == APPLICATION_COMMAND_TYPE_CONTENT:
@@ -616,5 +686,11 @@ class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
+ @staticmethod
+ def STATICCreateSimpleCommand( simple_action, simple_data = None ) -> "ApplicationCommand":
+
+ return ApplicationCommand( APPLICATION_COMMAND_TYPE_SIMPLE, ( simple_action, simple_data ) )
+
+
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_APPLICATION_COMMAND ] = ApplicationCommand
diff --git a/hydrus/client/ClientDefaults.py b/hydrus/client/ClientDefaults.py
index acbb03eb..54dbe87e 100644
--- a/hydrus/client/ClientDefaults.py
+++ b/hydrus/client/ClientDefaults.py
@@ -286,41 +286,41 @@ def GetDefaultShortcuts():
global_shortcuts = ClientGUIShortcuts.ShortcutSet( 'global' )
- global_shortcuts.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'G' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_GLOBAL_AUDIO_MUTE_FLIP ) )
+ global_shortcuts.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'G' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_GLOBAL_AUDIO_MUTE_FLIP ) )
shortcuts.append( global_shortcuts )
archive_delete_filter = ClientGUIShortcuts.ShortcutSet( 'archive_delete_filter' )
- archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP ) )
- archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_DOUBLE_CLICK, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP ) )
- archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_DELETE ) )
- archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_MIDDLE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK ) )
+ archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP ) )
+ archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_DOUBLE_CLICK, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP ) )
+ archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_DELETE ) )
+ archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_MIDDLE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK ) )
- archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_SPACE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP ) )
- archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F7, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP ) )
- archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_DELETE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_DELETE ) )
- archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_BACKSPACE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK ) )
- archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_SKIP ) )
+ archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_SPACE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP ) )
+ archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F7, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP ) )
+ archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_DELETE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_DELETE ) )
+ archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_BACKSPACE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK ) )
+ archive_delete_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_SKIP ) )
shortcuts.append( archive_delete_filter )
duplicate_filter = ClientGUIShortcuts.ShortcutSet( 'duplicate_filter' )
- duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER ) )
- duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_DOUBLE_CLICK, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER ) )
- duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_ALTERNATES ) )
- duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_MIDDLE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_BACK ) )
+ duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER ) )
+ duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_DOUBLE_CLICK, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER ) )
+ duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_ALTERNATES ) )
+ duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_MIDDLE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_BACK ) )
- duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_SPACE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER ) )
- duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_SKIP ) )
+ duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_SPACE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER ) )
+ duplicate_filter.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_SKIP ) )
shortcuts.append( duplicate_filter )
media = ClientGUIShortcuts.ShortcutSet( 'media' )
- delete_command = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DELETE_FILE )
- undelete_command = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_UNDELETE_FILE )
+ delete_command = CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DELETE_FILE )
+ undelete_command = CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_UNDELETE_FILE )
for delete_key in ClientGUIShortcuts.DELETE_KEYS_HYDRUS:
@@ -339,119 +339,122 @@ def GetDefaultShortcuts():
- media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F4, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_MANAGE_FILE_RATINGS ) )
- media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F3, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_MANAGE_FILE_TAGS ) )
+ media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F4, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_MANAGE_FILE_RATINGS ) )
+ media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F3, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_MANAGE_FILE_TAGS ) )
- media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F7, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_FILE ) )
- media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F7, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_INBOX_FILE ) )
+ media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F7, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_FILE ) )
+ media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F7, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_INBOX_FILE ) )
- media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'E' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM ) )
+ media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'E' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM ) )
- media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'R' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_REMOVE_FILE_FROM_VIEW ) )
+ media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'R' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_REMOVE_FILE_FROM_VIEW ) )
- media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F12, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_LAUNCH_THE_ARCHIVE_DELETE_FILTER ) )
+ media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F12, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_LAUNCH_THE_ARCHIVE_DELETE_FILTER ) )
- media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'C' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_COPY_FILE ) )
+ media.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'C' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_COPY_FILE ) )
shortcuts.append( media )
tags_autocomplete = ClientGUIShortcuts.ShortcutSet( 'tags_autocomplete' )
- tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_SPACE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_AUTOCOMPLETE_FORCE_FETCH ) )
- tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_INSERT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_AUTOCOMPLETE_IME_MODE ) )
+ tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_SPACE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_AUTOCOMPLETE_FORCE_FETCH ) )
+ tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_INSERT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_AUTOCOMPLETE_IME_MODE ) )
- tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_TAB_LEFT ) )
- tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_TAB_RIGHT ) )
- tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_PAGE_LEFT ) )
- tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_PAGE_RIGHT ) )
- tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_PAGE_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_PREVIOUS ) )
- tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_PAGE_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_NEXT ) )
+ tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_TAB_LEFT ) )
+ tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_TAB_RIGHT ) )
+ tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_PAGE_LEFT ) )
+ tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_PAGE_RIGHT ) )
+ tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_PAGE_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_PREVIOUS ) )
+ tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_PAGE_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_NEXT ) )
- tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'I' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SYNCHRONISED_WAIT_SWITCH ) )
+ tags_autocomplete.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'I' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SYNCHRONISED_WAIT_SWITCH ) )
shortcuts.append( tags_autocomplete )
main_gui = ClientGUIShortcuts.ShortcutSet( 'main_gui' )
- main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F5, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_REFRESH ) )
- main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F9, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_PAGE ) )
+ main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F5, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_REFRESH ) )
+ main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_F9, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_PAGE ) )
- main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'M' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SET_MEDIA_FOCUS ) )
- main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'R' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL, ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SHOW_HIDE_SPLITTERS ) )
- main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'S' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SET_SEARCH_FOCUS ) )
- main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'T' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_PAGE ) )
- main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'U' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_UNCLOSE_PAGE ) )
- main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'W' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_CLOSE_PAGE ) )
- main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'Y' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_REDO ) )
- main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'Z' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_UNDO ) )
+ main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'M' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SET_MEDIA_FOCUS ) )
+ main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'R' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL, ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SHOW_HIDE_SPLITTERS ) )
+ main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'S' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SET_SEARCH_FOCUS ) )
+ main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'T' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_PAGE ) )
+ main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'U' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_UNCLOSE_PAGE ) )
+ main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'W' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_CLOSE_PAGE ) )
+ main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'Y' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_REDO ) )
+ main_gui.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'Z' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_UNDO ) )
shortcuts.append( main_gui )
media_viewer_browser = ClientGUIShortcuts.ShortcutSet( 'media_viewer_browser' )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_PREVIOUS ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_PREVIOUS ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_PAGE_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_PREVIOUS ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_PREVIOUS ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_PREVIOUS ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_PAGE_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_PREVIOUS ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_RELEASE, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SHOW_MENU ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_RELEASE, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SHOW_MENU ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_SCROLL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_PREVIOUS ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_SCROLL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_PREVIOUS ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_NEXT ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_NEXT ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_PAGE_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_NEXT ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_NEXT ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_NEXT ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_PAGE_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_NEXT ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_SCROLL_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_NEXT ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_SCROLL_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_NEXT ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_HOME, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_FIRST ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_HOME, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_FIRST ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_END, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_LAST ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_END, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_LAST ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_DOUBLE_CLICK, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
- media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_MIDDLE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_DOUBLE_CLICK, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
+ media_viewer_browser.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_MIDDLE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
shortcuts.append( media_viewer_browser )
media_viewer = ClientGUIShortcuts.ShortcutSet( 'media_viewer' )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_SPACE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_PAUSE_PLAY_MEDIA ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_SPACE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_PAUSE_PLAY_MEDIA ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'B' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_MOVE_ANIMATION_TO_PREVIOUS_FRAME ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'N' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_MOVE_ANIMATION_TO_NEXT_FRAME ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_MEDIA_SEEK_DELTA, simple_data = ( -1, 2500 ) ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_MEDIA_SEEK_DELTA, simple_data = ( 1, 5000 ) ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'F' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SWITCH_BETWEEN_FULLSCREEN_BORDERLESS_AND_REGULAR_FRAMED_WINDOW ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'B' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_MOVE_ANIMATION_TO_PREVIOUS_FRAME ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'N' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_MOVE_ANIMATION_TO_NEXT_FRAME ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'Z' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SWITCH_BETWEEN_100_PERCENT_AND_CANVAS_ZOOM ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( '+' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ZOOM_IN ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( '-' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ZOOM_OUT ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'F' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SWITCH_BETWEEN_FULLSCREEN_BORDERLESS_AND_REGULAR_FRAMED_WINDOW ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_SCROLL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ZOOM_IN ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_SCROLL_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ZOOM_OUT ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'Z' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SWITCH_BETWEEN_100_PERCENT_AND_CANVAS_ZOOM ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( '+' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ZOOM_IN ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( '-' ), ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ZOOM_OUT ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_PAN_UP ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_PAN_DOWN ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_PAN_LEFT ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_PAN_RIGHT ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_SCROLL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ZOOM_IN ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_SCROLL_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_CTRL ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ZOOM_OUT ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_ENTER, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_ENTER, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_KEYPAD ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RETURN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RETURN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_KEYPAD ] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
- media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_ESCAPE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_UP, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_PAN_UP ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_DOWN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_PAN_DOWN ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_PAN_LEFT ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RIGHT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_SHIFT ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_PAN_RIGHT ) )
+
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_ENTER, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_ENTER, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_KEYPAD ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RETURN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_RETURN, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [ ClientGUIShortcuts.SHORTCUT_MODIFIER_KEYPAD ] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
+ media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_KEYBOARD_SPECIAL, ClientGUIShortcuts.SHORTCUT_KEY_SPECIAL_ESCAPE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_CLOSE_MEDIA_VIEWER ) )
shortcuts.append( media_viewer )
media_viewer_video_audio_player = ClientGUIShortcuts.ShortcutSet( 'media_viewer_media_window' )
- media_viewer_video_audio_player.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_PAUSE_PLAY_MEDIA ) )
+ media_viewer_video_audio_player.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_PAUSE_PLAY_MEDIA ) )
shortcuts.append( media_viewer_video_audio_player )
preview_video_audio_player = ClientGUIShortcuts.ShortcutSet( 'preview_media_window' )
- preview_video_audio_player.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_PAUSE_PLAY_MEDIA ) )
- preview_video_audio_player.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_DOUBLE_CLICK, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_LAUNCH_MEDIA_VIEWER ) )
- preview_video_audio_player.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_MIDDLE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_LAUNCH_MEDIA_VIEWER ) )
+ preview_video_audio_player.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_PAUSE_PLAY_MEDIA ) )
+ preview_video_audio_player.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_LEFT, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_DOUBLE_CLICK, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_LAUNCH_MEDIA_VIEWER ) )
+ preview_video_audio_player.SetCommand( ClientGUIShortcuts.Shortcut( ClientGUIShortcuts.SHORTCUT_TYPE_MOUSE, ClientGUIShortcuts.SHORTCUT_MOUSE_MIDDLE, ClientGUIShortcuts.SHORTCUT_PRESS_TYPE_PRESS, [] ), CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_LAUNCH_MEDIA_VIEWER ) )
shortcuts.append( preview_video_audio_player )
diff --git a/hydrus/client/ClientOptions.py b/hydrus/client/ClientOptions.py
index 9250e29f..f331055b 100644
--- a/hydrus/client/ClientOptions.py
+++ b/hydrus/client/ClientOptions.py
@@ -346,6 +346,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'integers' ][ 'duplicate_comparison_score_much_higher_resolution' ] = 50
self._dictionary[ 'integers' ][ 'duplicate_comparison_score_more_tags' ] = 8
self._dictionary[ 'integers' ][ 'duplicate_comparison_score_older' ] = 4
+ self._dictionary[ 'integers' ][ 'duplicate_comparison_score_nicer_ratio' ] = 10
self._dictionary[ 'integers' ][ 'image_tile_cache_size' ] = 1024 * 1024 * 256
diff --git a/hydrus/client/ClientRendering.py b/hydrus/client/ClientRendering.py
index 9ddee207..e768134b 100644
--- a/hydrus/client/ClientRendering.py
+++ b/hydrus/client/ClientRendering.py
@@ -775,6 +775,39 @@ class RasterContainerVideo( RasterContainer ):
return self._times_to_play_gif
+ def GetFrameIndex( self, timestamp_ms ):
+
+ if self._media.GetMime() == HC.IMAGE_GIF:
+
+ so_far = 0
+
+ for ( frame_index, duration_ms ) in enumerate( self._durations ):
+
+ so_far += duration_ms
+
+ if so_far > timestamp_ms:
+
+ result = frame_index
+
+ if FrameIndexOutOfRange( result, 0, self.GetNumFrames() - 1 ):
+
+ return 0
+
+ else:
+
+ return result
+
+
+
+
+ return 0
+
+ else:
+
+ return timestamp_ms // self._average_frame_duration
+
+
+
def GetTimestampMS( self, frame_index ):
if self._media.GetMime() == HC.IMAGE_GIF:
diff --git a/hydrus/client/ClientSearch.py b/hydrus/client/ClientSearch.py
index ba55e3eb..6a461355 100644
--- a/hydrus/client/ClientSearch.py
+++ b/hydrus/client/ClientSearch.py
@@ -951,9 +951,9 @@ class FileSearchContext( HydrusSerialisable.SerialisableBase ):
def GetFileServiceKey( self ):
- if len( self._location_search_context.current_service_keys ) > 0:
+ if self._location_search_context.SearchesAnything():
- return self._location_search_context.current_service_keys[0]
+ return self._location_search_context.GetFileServiceKey()
else:
@@ -1080,14 +1080,33 @@ class LocationSearchContext( HydrusSerialisable.SerialisableBase ):
self.deleted_service_keys = services_manager.FilterValidServiceKeys( self.deleted_service_keys )
+ def GetFileServiceKey( self ):
+
+ if not self.IsOneDomain():
+
+ raise Exception( 'Location context was asked for specific file domain, but it did not have a single domain' )
+
+
+ if len( self.current_service_keys ) > 0:
+
+ ( service_key, ) = self.current_service_keys
+
+ else:
+
+ ( service_key, ) = self.deleted_service_keys
+
+
+ return service_key
+
+
def IsAllKnownFiles( self ):
- return len( self.deleted_service_keys ) == 0 and len( self.current_service_keys ) == 1 and CC.COMBINED_FILE_SERVICE_KEY in self.current_service_keys
+ return self.IsOneDomain() and CC.COMBINED_FILE_SERVICE_KEY in self.current_service_keys
def IsAllLocalFiles( self ):
- return len( self.deleted_service_keys ) == 0 and len( self.current_service_keys ) == 1 and CC.COMBINED_LOCAL_FILE_SERVICE_KEY in self.current_service_keys
+ return self.IsOneDomain() and CC.COMBINED_LOCAL_FILE_SERVICE_KEY in self.current_service_keys
def IsOneDomain( self ):
@@ -1100,6 +1119,16 @@ class LocationSearchContext( HydrusSerialisable.SerialisableBase ):
return len( self.current_service_keys ) + len( self.deleted_service_keys ) > 0
+ def SearchesCurrent( self ):
+
+ return len( self.current_service_keys ) > 0
+
+
+ def SearchesDeleted( self ):
+
+ return len( self.deleted_service_keys ) > 0
+
+
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_LOCATION_SEARCH_CONTEXT ] = LocationSearchContext
class TagSearchContext( HydrusSerialisable.SerialisableBase ):
@@ -1160,6 +1189,11 @@ class TagSearchContext( HydrusSerialisable.SerialisableBase ):
+ def IsAllKnownTags( self ):
+
+ return self.service_key == CC.COMBINED_TAG_SERVICE_KEY
+
+
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_TAG_SEARCH_CONTEXT ] = TagSearchContext
class FavouriteSearchManager( HydrusSerialisable.SerialisableBase ):
diff --git a/hydrus/client/db/ClientDB.py b/hydrus/client/db/ClientDB.py
index a25a8e6c..347151da 100644
--- a/hydrus/client/db/ClientDB.py
+++ b/hydrus/client/db/ClientDB.py
@@ -144,9 +144,9 @@ from hydrus.client.networking import ClientNetworkingBandwidthLegacy
#▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓██▓▓▓▒▒▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓ ▒░▓░ ░░ ▒▓▒▒▒▒▒▒
#▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░▒▒░░▓▓▒▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ░▒▒▒▒ ▓████▒ ▒▒▒▒▒▒▒▒
-# Sqlite can handle -( 2 ** 63 ) -> ( 2 ** 63 ) - 1, but the user won't be searching that distance, so np
-MIN_CACHED_INTEGER = -99999999
-MAX_CACHED_INTEGER = 99999999
+# Sqlite can handle -( 2 ** 63 ) -> ( 2 ** 63 ) - 1
+MIN_CACHED_INTEGER = - ( 2 ** 63 )
+MAX_CACHED_INTEGER = ( 2 ** 63 ) - 1
def BlockingSafeShowMessage( message ):
@@ -1253,10 +1253,10 @@ class DB( HydrusDB.HydrusDB ):
self._CacheTagsAddTags( file_service_id, tag_service_id, new_tag_ids )
-
- if len( new_local_tag_ids ) > 0:
-
- self.modules_tags_local_cache.AddTagIdsToCache( new_local_tag_ids )
+ if len( new_local_tag_ids ) > 0:
+
+ self.modules_tags_local_cache.AddTagIdsToCache( new_local_tag_ids )
+
@@ -1331,12 +1331,11 @@ class DB( HydrusDB.HydrusDB ):
self._CacheTagsDeleteTags( file_service_id, tag_service_id, deleted_tag_ids )
-
- if len( deleted_local_tag_ids ) > 0:
+ if len( deleted_local_tag_ids ) > 0:
+
+ self._CacheLocalTagIdsPotentialDelete( deleted_local_tag_ids )
+
- self._CacheLocalTagIdsPotentialDelete( deleted_local_tag_ids )
-
-
def _CacheMappingsUpdateACCounts( self, tag_display_type, file_service_id, tag_service_id, ac_cache_changes ):
@@ -2261,6 +2260,7 @@ class DB( HydrusDB.HydrusDB ):
with self._MakeTemporaryIntegerTable( block_of_hash_ids, 'hash_id' ) as temp_hash_id_table_name:
self._CacheSpecificMappingsAddFiles( file_service_id, tag_service_id, block_of_hash_ids, temp_hash_id_table_name )
+
self.modules_db_maintenance.AnalyzeTable( cache_files_table_name )
@@ -2274,18 +2274,15 @@ class DB( HydrusDB.HydrusDB ):
def _CacheSpecificMappingsGetFilteredHashesGenerator( self, file_service_ids, tag_service_id, hash_ids ):
- # we have a fast 'does this file exist on this specific domain' cache, so let's filter hashes by that
-
file_service_ids_to_valid_hash_ids = collections.defaultdict( set )
with self._MakeTemporaryIntegerTable( hash_ids, 'hash_id' ) as temp_table_name:
for file_service_id in file_service_ids:
- cache_files_table_name = GenerateSpecificFilesTableName( file_service_id, tag_service_id )
+ table_join = self.modules_files_storage.GetCurrentTableJoinPhrase( file_service_id, temp_table_name )
- # temp hashes to files
- valid_hash_ids = self._STS( self._Execute( 'SELECT hash_id FROM {} CROSS JOIN {} USING ( hash_id );'.format( temp_table_name, cache_files_table_name ) ) )
+ valid_hash_ids = self._STS( self._Execute( 'SELECT hash_id FROM {};'.format( table_join ) ) )
file_service_ids_to_valid_hash_ids[ file_service_id ] = valid_hash_ids
@@ -2296,8 +2293,6 @@ class DB( HydrusDB.HydrusDB ):
def _CacheSpecificMappingsGetFilteredMappingsGenerator( self, file_service_ids, tag_service_id, mappings_ids ):
- # we have a fast 'does this file exist on this specific domain' cache, so let's filter mappings by that
-
all_hash_ids = set( itertools.chain.from_iterable( ( hash_ids for ( tag_id, hash_ids ) in mappings_ids ) ) )
file_service_ids_to_valid_hash_ids = collections.defaultdict( set )
@@ -2306,10 +2301,9 @@ class DB( HydrusDB.HydrusDB ):
for file_service_id in file_service_ids:
- cache_files_table_name = GenerateSpecificFilesTableName( file_service_id, tag_service_id )
+ table_join = self.modules_files_storage.GetCurrentTableJoinPhrase( file_service_id, temp_table_name )
- # temp hashes to files
- valid_hash_ids = self._STS( self._Execute( 'SELECT hash_id FROM {} CROSS JOIN {} USING ( hash_id );'.format( temp_table_name, cache_files_table_name ) ) )
+ valid_hash_ids = self._STS( self._Execute( 'SELECT hash_id FROM {};'.format( table_join ) ) )
file_service_ids_to_valid_hash_ids[ file_service_id ] = valid_hash_ids
@@ -4790,9 +4784,9 @@ class DB( HydrusDB.HydrusDB ):
return media_ids
- def _DuplicatesGetBestKingId( self, media_id, file_service_id, allowed_hash_ids = None, preferred_hash_ids = None ):
+ def _DuplicatesGetBestKingId( self, media_id, db_location_search_context: ClientDBFilesStorage.DBLocationSearchContext, allowed_hash_ids = None, preferred_hash_ids = None ):
- media_hash_ids = self._DuplicatesGetDuplicateHashIds( media_id, file_service_id = file_service_id )
+ media_hash_ids = self._DuplicatesGetDuplicateHashIds( media_id, db_location_search_context = db_location_search_context )
if allowed_hash_ids is not None:
@@ -4829,31 +4823,29 @@ class DB( HydrusDB.HydrusDB ):
return None
- def _DuplicatesGetDuplicateHashIds( self, media_id, file_service_id = None ):
+ def _DuplicatesGetDuplicateHashIds( self, media_id, db_location_search_context: ClientDBFilesStorage.DBLocationSearchContext = None ):
- if file_service_id is None or file_service_id == self.modules_services.combined_file_service_id:
+ table_join = 'duplicate_file_members'
+
+ if db_location_search_context is not None:
- hash_ids = self._STS( self._Execute( 'SELECT hash_id FROM duplicate_file_members WHERE media_id = ?;', ( media_id, ) ) )
-
- else:
-
- table_join = self.modules_files_storage.GetCurrentTableJoinPhrase( file_service_id, 'duplicate_file_members' )
-
- hash_ids = self._STS( self._Execute( 'SELECT hash_id FROM {} WHERE media_id = ?;'.format( table_join ), ( media_id, ) ) )
+ table_join = db_location_search_context.GetTableJoinLimitedByFileDomain( table_join )
+ hash_ids = self._STS( self._Execute( 'SELECT hash_id FROM {} WHERE media_id = ?;'.format( table_join ), ( media_id, ) ) )
+
return hash_ids
- def _DuplicatesGetDuplicatesHashIds( self, media_ids, file_service_id = None ):
+ def _DuplicatesGetDuplicatesHashIds( self, media_ids, db_location_search_context: ClientDBFilesStorage.DBLocationSearchContext = None ):
with self._MakeTemporaryIntegerTable( media_ids, 'media_id' ) as temp_media_ids_table_name:
table_join = '{} CROSS JOIN {} USING ( media_id )'.format( temp_media_ids_table_name, 'duplicate_file_members' )
- if file_service_id is not None and file_service_id != self.modules_services.combined_file_service_id:
+ if db_location_search_context is not None:
- table_join = self.modules_files_storage.GetCurrentTableJoinPhrase( file_service_id, table_join )
+ table_join = db_location_search_context.GetTableJoinLimitedByFileDomain( table_join )
hash_ids = self._STS( self._Execute( 'SELECT hash_id FROM {};'.format( table_join ) ) )
@@ -4891,7 +4883,11 @@ class DB( HydrusDB.HydrusDB ):
if media_id is not None:
- table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( file_service_key )
+ location_search_context = ClientSearch.LocationSearchContext( current_service_keys = [ file_service_key ] )
+
+ db_location_search_context = self.modules_files_storage.GetDBLocationSearchContext( location_search_context )
+
+ table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( db_location_search_context )
( num_potentials, ) = self._Execute( 'SELECT COUNT( * ) FROM ( SELECT DISTINCT smaller_media_id, larger_media_id FROM {} WHERE smaller_media_id = ? OR larger_media_id = ? );'.format( table_join ), ( media_id, media_id, ) ).fetchone()
@@ -4904,9 +4900,7 @@ class DB( HydrusDB.HydrusDB ):
result_dict[ 'is_king' ] = king_hash_id == hash_id
- file_service_id = self.modules_services.GetServiceId( file_service_key )
-
- media_hash_ids = self._DuplicatesGetDuplicateHashIds( media_id, file_service_id = file_service_id )
+ media_hash_ids = self._DuplicatesGetDuplicateHashIds( media_id, db_location_search_context = db_location_search_context )
num_other_dupe_members = len( media_hash_ids ) - 1
@@ -4925,7 +4919,7 @@ class DB( HydrusDB.HydrusDB ):
for alt_media_id in alt_media_ids:
- alt_hash_ids = self._DuplicatesGetDuplicateHashIds( alt_media_id, file_service_id = file_service_id )
+ alt_hash_ids = self._DuplicatesGetDuplicateHashIds( alt_media_id, db_location_search_context = db_location_search_context )
if len( alt_hash_ids ) > 0:
@@ -4953,7 +4947,7 @@ class DB( HydrusDB.HydrusDB ):
for fp_media_id in fp_media_ids:
- fp_hash_ids = self._DuplicatesGetDuplicateHashIds( fp_media_id, file_service_id = file_service_id )
+ fp_hash_ids = self._DuplicatesGetDuplicateHashIds( fp_media_id, db_location_search_context = db_location_search_context )
if len( fp_hash_ids ) > 0:
@@ -4973,7 +4967,9 @@ class DB( HydrusDB.HydrusDB ):
hash_id = self.modules_hashes_local_cache.GetHashId( hash )
- file_service_id = self.modules_services.GetServiceId( file_service_key )
+ location_search_context = ClientSearch.LocationSearchContext( current_service_keys = [ file_service_key ] )
+
+ db_location_search_context = self.modules_files_storage.GetDBLocationSearchContext( location_search_context )
dupe_hash_ids = set()
@@ -5000,7 +4996,7 @@ class DB( HydrusDB.HydrusDB ):
for false_positive_media_id in false_positive_media_ids:
- best_king_hash_id = self._DuplicatesGetBestKingId( false_positive_media_id, file_service_id, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
+ best_king_hash_id = self._DuplicatesGetBestKingId( false_positive_media_id, db_location_search_context, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
if best_king_hash_id is not None:
@@ -5026,7 +5022,7 @@ class DB( HydrusDB.HydrusDB ):
for alternates_media_id in alternates_media_ids:
- best_king_hash_id = self._DuplicatesGetBestKingId( alternates_media_id, file_service_id, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
+ best_king_hash_id = self._DuplicatesGetBestKingId( alternates_media_id, db_location_search_context, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
if best_king_hash_id is not None:
@@ -5042,7 +5038,7 @@ class DB( HydrusDB.HydrusDB ):
if media_id is not None:
- media_hash_ids = self._DuplicatesGetDuplicateHashIds( media_id, file_service_id = file_service_id )
+ media_hash_ids = self._DuplicatesGetDuplicateHashIds( media_id, db_location_search_context = db_location_search_context )
if allowed_hash_ids is not None:
@@ -5058,7 +5054,7 @@ class DB( HydrusDB.HydrusDB ):
if media_id is not None:
- best_king_hash_id = self._DuplicatesGetBestKingId( media_id, file_service_id, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
+ best_king_hash_id = self._DuplicatesGetBestKingId( media_id, db_location_search_context, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
if best_king_hash_id is not None:
@@ -5072,7 +5068,7 @@ class DB( HydrusDB.HydrusDB ):
if media_id is not None:
- table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( file_service_key )
+ table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( db_location_search_context )
for ( smaller_media_id, larger_media_id ) in self._Execute( 'SELECT smaller_media_id, larger_media_id FROM {} WHERE smaller_media_id = ? OR larger_media_id = ?;'.format( table_join ), ( media_id, media_id ) ).fetchall():
@@ -5085,7 +5081,7 @@ class DB( HydrusDB.HydrusDB ):
potential_media_id = larger_media_id
- best_king_hash_id = self._DuplicatesGetBestKingId( potential_media_id, file_service_id, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
+ best_king_hash_id = self._DuplicatesGetBestKingId( potential_media_id, db_location_search_context, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
if best_king_hash_id is not None:
@@ -5106,7 +5102,7 @@ class DB( HydrusDB.HydrusDB ):
return dupe_hashes
- def _DuplicatesGetHashIdsFromDuplicateCountPredicate( self, file_service_key, operator, num_relationships, dupe_type ):
+ def _DuplicatesGetHashIdsFromDuplicateCountPredicate( self, db_location_search_context: ClientDBFilesStorage.DBLocationSearchContext, operator, num_relationships, dupe_type ):
# doesn't work for '= 0' or '< 1'
@@ -5146,8 +5142,6 @@ class DB( HydrusDB.HydrusDB ):
if dupe_type == HC.DUPLICATE_FALSE_POSITIVE:
- file_service_id = self.modules_services.GetServiceId( file_service_key )
-
alternates_group_ids_to_valid_for_file_domain = {}
alternates_group_ids_to_false_positives = collections.defaultdict( list )
@@ -5173,7 +5167,7 @@ class DB( HydrusDB.HydrusDB ):
for fp_media_id in fp_media_ids:
- fp_hash_ids = self._DuplicatesGetDuplicateHashIds( fp_media_id, file_service_id = file_service_id )
+ fp_hash_ids = self._DuplicatesGetDuplicateHashIds( fp_media_id, db_location_search_context = db_location_search_context )
if len( fp_hash_ids ) > 0:
@@ -5196,14 +5190,12 @@ class DB( HydrusDB.HydrusDB ):
media_ids = self._DuplicatesGetAlternateMediaIds( alternates_group_id )
- hash_ids = self._DuplicatesGetDuplicatesHashIds( media_ids, file_service_id = file_service_id )
+ hash_ids = self._DuplicatesGetDuplicatesHashIds( media_ids, db_location_search_context = db_location_search_context )
elif dupe_type == HC.DUPLICATE_ALTERNATE:
- file_service_id = self.modules_services.GetServiceId( file_service_key )
-
query = 'SELECT alternates_group_id, COUNT( * ) FROM alternate_file_group_members GROUP BY alternates_group_id;'
results = self._Execute( query ).fetchall()
@@ -5218,7 +5210,7 @@ class DB( HydrusDB.HydrusDB ):
for media_id in media_ids:
- media_id_hash_ids = self._DuplicatesGetDuplicateHashIds( media_id, file_service_id = file_service_id )
+ media_id_hash_ids = self._DuplicatesGetDuplicateHashIds( media_id, db_location_search_context = db_location_search_context )
if len( media_id_hash_ids ) == 0:
@@ -5239,16 +5231,7 @@ class DB( HydrusDB.HydrusDB ):
elif dupe_type == HC.DUPLICATE_MEMBER:
- file_service_id = self.modules_services.GetServiceId( file_service_key )
-
- if file_service_id == self.modules_services.combined_file_service_id:
-
- table_join = 'duplicate_file_members'
-
- else:
-
- table_join = self.modules_files_storage.GetCurrentTableJoinPhrase( file_service_id, 'duplicate_file_members' )
-
+ table_join = db_location_search_context.GetTableJoinLimitedByFileDomain( 'duplicate_file_members' )
query = 'SELECT media_id, COUNT( * ) FROM {} GROUP BY media_id;'.format( table_join )
@@ -5264,17 +5247,15 @@ class DB( HydrusDB.HydrusDB ):
- hash_ids = self._DuplicatesGetDuplicatesHashIds( media_ids, file_service_id = file_service_id )
+ hash_ids = self._DuplicatesGetDuplicatesHashIds( media_ids, db_location_search_context = db_location_search_context )
elif dupe_type == HC.DUPLICATE_POTENTIAL:
- table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( file_service_key )
+ table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( db_location_search_context )
smaller_query = 'SELECT smaller_media_id, COUNT( * ) FROM ( SELECT DISTINCT smaller_media_id, larger_media_id FROM {} ) GROUP BY smaller_media_id;'.format( table_join )
larger_query = 'SELECT larger_media_id, COUNT( * ) FROM ( SELECT DISTINCT smaller_media_id, larger_media_id FROM {} ) GROUP BY larger_media_id;'.format( table_join )
- file_service_id = self.modules_services.GetServiceId( file_service_key )
-
media_ids_to_counts = collections.Counter()
for ( media_id, count ) in self._Execute( smaller_query ):
@@ -5289,7 +5270,7 @@ class DB( HydrusDB.HydrusDB ):
media_ids = [ media_id for ( media_id, count ) in media_ids_to_counts.items() if filter_func( count ) ]
- hash_ids = self._DuplicatesGetDuplicatesHashIds( media_ids, file_service_id = file_service_id )
+ hash_ids = self._DuplicatesGetDuplicatesHashIds( media_ids, db_location_search_context = db_location_search_context )
return hash_ids
@@ -5327,30 +5308,23 @@ class DB( HydrusDB.HydrusDB ):
return media_id
- def _DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( self, file_service_key ):
+ def _DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( self, db_location_search_context: ClientDBFilesStorage.DBLocationSearchContext ):
- if file_service_key == CC.COMBINED_FILE_SERVICE_KEY:
+ if db_location_search_context.location_search_context.IsAllKnownFiles():
table_join = 'potential_duplicate_pairs'
else:
- service_id = self.modules_services.GetServiceId( file_service_key )
+ files_table_name = db_location_search_context.files_table_name
- current_files_table_name = ClientDBFilesStorage.GenerateFilesTableName( service_id, HC.CONTENT_STATUS_CURRENT )
-
- table_join = 'potential_duplicate_pairs, duplicate_file_members AS duplicate_file_members_smaller, {} AS current_files_smaller, duplicate_file_members AS duplicate_file_members_larger, {} AS current_files_larger ON ( smaller_media_id = duplicate_file_members_smaller.media_id AND duplicate_file_members_smaller.hash_id = current_files_smaller.hash_id AND larger_media_id = duplicate_file_members_larger.media_id AND duplicate_file_members_larger.hash_id = current_files_larger.hash_id )'.format( current_files_table_name, current_files_table_name )
+ table_join = 'potential_duplicate_pairs, duplicate_file_members AS duplicate_file_members_smaller, {} AS current_files_smaller, duplicate_file_members AS duplicate_file_members_larger, {} AS current_files_larger ON ( smaller_media_id = duplicate_file_members_smaller.media_id AND duplicate_file_members_smaller.hash_id = current_files_smaller.hash_id AND larger_media_id = duplicate_file_members_larger.media_id AND duplicate_file_members_larger.hash_id = current_files_larger.hash_id )'.format( files_table_name, files_table_name )
return table_join
- def _DuplicatesGetPotentialDuplicatePairsTableJoinOnSearchResults( self, file_service_key, results_table_name, both_files_match ):
-
- if file_service_key == CC.COMBINED_FILE_SERVICE_KEY:
-
- file_service_key = CC.COMBINED_LOCAL_FILE_SERVICE_KEY
-
+ def _DuplicatesGetPotentialDuplicatePairsTableJoinOnSearchResults( self, db_location_search_context: ClientDBFilesStorage.DBLocationSearchContext, results_table_name, both_files_match ):
if both_files_match:
@@ -5358,21 +5332,26 @@ class DB( HydrusDB.HydrusDB ):
else:
- service_id = self.modules_services.GetServiceId( file_service_key )
-
- current_files_table_name = ClientDBFilesStorage.GenerateFilesTableName( service_id, HC.CONTENT_STATUS_CURRENT )
-
- table_join = 'potential_duplicate_pairs, duplicate_file_members AS duplicate_file_members_smaller, duplicate_file_members AS duplicate_file_members_larger, {} AS results_table_for_this_query, {} AS current_files_for_this_query ON ( ( smaller_media_id = duplicate_file_members_smaller.media_id AND duplicate_file_members_smaller.hash_id = results_table_for_this_query.hash_id AND larger_media_id = duplicate_file_members_larger.media_id AND duplicate_file_members_larger.hash_id = current_files_for_this_query.hash_id ) OR ( smaller_media_id = duplicate_file_members_smaller.media_id AND duplicate_file_members_smaller.hash_id = current_files_for_this_query.hash_id AND larger_media_id = duplicate_file_members_larger.media_id AND duplicate_file_members_larger.hash_id = results_table_for_this_query.hash_id ) )'.format( results_table_name, current_files_table_name )
+ if db_location_search_context.location_search_context.IsAllKnownFiles():
+
+ table_join = 'potential_duplicate_pairs, duplicate_file_members AS duplicate_file_members_smaller, duplicate_file_members AS duplicate_file_members_larger, {} AS results_table_for_this_query ON ( ( smaller_media_id = duplicate_file_members_smaller.media_id AND duplicate_file_members_smaller.hash_id = results_table_for_this_query.hash_id AND larger_media_id = duplicate_file_members_larger.media_id ) OR ( smaller_media_id = duplicate_file_members_smaller.media_id AND larger_media_id = duplicate_file_members_larger.media_id AND duplicate_file_members_larger.hash_id = results_table_for_this_query.hash_id ) )'.format( results_table_name )
+
+ else:
+
+ files_table_name = db_location_search_context.files_table_name
+
+ table_join = 'potential_duplicate_pairs, duplicate_file_members AS duplicate_file_members_smaller, duplicate_file_members AS duplicate_file_members_larger, {} AS results_table_for_this_query, {} AS current_files_for_this_query ON ( ( smaller_media_id = duplicate_file_members_smaller.media_id AND duplicate_file_members_smaller.hash_id = results_table_for_this_query.hash_id AND larger_media_id = duplicate_file_members_larger.media_id AND duplicate_file_members_larger.hash_id = current_files_for_this_query.hash_id ) OR ( smaller_media_id = duplicate_file_members_smaller.media_id AND duplicate_file_members_smaller.hash_id = current_files_for_this_query.hash_id AND larger_media_id = duplicate_file_members_larger.media_id AND duplicate_file_members_larger.hash_id = results_table_for_this_query.hash_id ) )'.format( results_table_name, files_table_name )
+
return table_join
- def _DuplicatesGetRandomPotentialDuplicateHashes( self, file_search_context, both_files_match ):
+ def _DuplicatesGetRandomPotentialDuplicateHashes( self, file_search_context: ClientSearch.FileSearchContext, both_files_match ):
file_service_key = file_search_context.GetFileServiceKey()
- file_service_id = self.modules_services.GetServiceId( file_service_key )
+ db_location_search_context = self.modules_files_storage.GetDBLocationSearchContext( file_search_context.GetLocationSearchContext() )
is_complicated_search = False
@@ -5385,7 +5364,7 @@ class DB( HydrusDB.HydrusDB ):
if file_search_context.IsJustSystemEverything() or file_search_context.HasNoPredicates():
- table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( file_service_key )
+ table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( db_location_search_context )
else:
@@ -5406,7 +5385,7 @@ class DB( HydrusDB.HydrusDB ):
self._AnalyzeTempTable( temp_table_name )
- table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnSearchResults( file_service_key, temp_table_name, both_files_match )
+ table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnSearchResults( db_location_search_context, temp_table_name, both_files_match )
potential_media_ids = set()
@@ -5433,7 +5412,7 @@ class DB( HydrusDB.HydrusDB ):
for potential_media_id in potential_media_ids:
- best_king_hash_id = self._DuplicatesGetBestKingId( potential_media_id, file_service_id, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
+ best_king_hash_id = self._DuplicatesGetBestKingId( potential_media_id, db_location_search_context, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
if best_king_hash_id is not None:
@@ -5463,16 +5442,14 @@ class DB( HydrusDB.HydrusDB ):
return self._DuplicatesGetFileHashesByDuplicateType( file_service_key, hash, HC.DUPLICATE_POTENTIAL, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
- def _DuplicatesGetPotentialDuplicatePairsForFiltering( self, file_search_context, both_files_match ):
+ def _DuplicatesGetPotentialDuplicatePairsForFiltering( self, file_search_context: ClientSearch.FileSearchContext, both_files_match ):
# we need to batch non-intersecting decisions here to keep it simple at the gui-level
# we also want to maximise per-decision value
# now we will fetch some unknown pairs
- file_service_key = file_search_context.GetFileServiceKey()
-
- file_service_id = self.modules_services.GetServiceId( file_service_key )
+ db_location_search_context = self.modules_files_storage.GetDBLocationSearchContext( file_search_context.GetLocationSearchContext() )
with self._MakeTemporaryIntegerTable( [], 'hash_id' ) as temp_table_name:
@@ -5481,7 +5458,7 @@ class DB( HydrusDB.HydrusDB ):
if file_search_context.IsJustSystemEverything() or file_search_context.HasNoPredicates():
- table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( file_service_key )
+ table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( db_location_search_context )
else:
@@ -5500,7 +5477,7 @@ class DB( HydrusDB.HydrusDB ):
self._AnalyzeTempTable( temp_table_name )
- table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnSearchResults( file_service_key, temp_table_name, both_files_match )
+ table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnSearchResults( db_location_search_context, temp_table_name, both_files_match )
# distinct important here for the search results table join
@@ -5594,7 +5571,7 @@ class DB( HydrusDB.HydrusDB ):
for media_id in seen_media_ids:
- best_king_hash_id = self._DuplicatesGetBestKingId( media_id, file_service_id, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
+ best_king_hash_id = self._DuplicatesGetBestKingId( media_id, db_location_search_context, allowed_hash_ids = allowed_hash_ids, preferred_hash_ids = preferred_hash_ids )
if best_king_hash_id is not None:
@@ -5615,13 +5592,13 @@ class DB( HydrusDB.HydrusDB ):
def _DuplicatesGetPotentialDuplicatesCount( self, file_search_context, both_files_match ):
- file_service_key = file_search_context.GetFileServiceKey()
+ db_location_search_context = self.modules_files_storage.GetDBLocationSearchContext( file_search_context.GetLocationSearchContext() )
with self._MakeTemporaryIntegerTable( [], 'hash_id' ) as temp_table_name:
if file_search_context.IsJustSystemEverything() or file_search_context.HasNoPredicates():
- table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( file_service_key )
+ table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( db_location_search_context )
else:
@@ -5631,7 +5608,7 @@ class DB( HydrusDB.HydrusDB ):
self._AnalyzeTempTable( temp_table_name )
- table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnSearchResults( file_service_key, temp_table_name, both_files_match )
+ table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnSearchResults( db_location_search_context, temp_table_name, both_files_match )
# distinct important here for the search results table join
@@ -7145,7 +7122,11 @@ class DB( HydrusDB.HydrusDB ):
total_alternate_files = sum( ( count for ( alternates_group_id, count ) in self._Execute( 'SELECT alternates_group_id, COUNT( * ) FROM alternate_file_group_members GROUP BY alternates_group_id;' ) if count > 1 ) )
total_duplicate_files = sum( ( count for ( media_id, count ) in self._Execute( 'SELECT media_id, COUNT( * ) FROM duplicate_file_members GROUP BY media_id;' ) if count > 1 ) )
- table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
+ location_search_context = ClientSearch.LocationSearchContext( current_service_keys = [ CC.COMBINED_LOCAL_FILE_SERVICE_KEY ] )
+
+ db_location_search_context = self.modules_files_storage.GetDBLocationSearchContext( location_search_context )
+
+ table_join = self._DuplicatesGetPotentialDuplicatePairsTableJoinOnFileService( db_location_search_context )
( total_potential_pairs, ) = self._Execute( 'SELECT COUNT( * ) FROM ( SELECT DISTINCT smaller_media_id, larger_media_id FROM {} );'.format( table_join ) ).fetchone()
@@ -7654,7 +7635,7 @@ class DB( HydrusDB.HydrusDB ):
query_hash_ids = set( query_hash_ids )
- have_cross_referenced_file_service = False
+ have_cross_referenced_file_locations = False
self._controller.ResetIdleTimer()
@@ -7710,6 +7691,8 @@ class DB( HydrusDB.HydrusDB ):
deleted_file_service_ids.add( deleted_file_service_id )
+ db_location_search_context = self.modules_files_storage.GetDBLocationSearchContext( location_search_context )
+
file_service_id = self.modules_services.GetServiceId( file_service_key )
try:
@@ -7723,10 +7706,6 @@ class DB( HydrusDB.HydrusDB ):
return set()
- file_service = self.modules_services.GetService( file_service_id )
-
- file_service_type = file_service.GetServiceType()
-
tags_to_include = file_search_context.GetTagsToInclude()
tags_to_exclude = file_search_context.GetTagsToExclude()
@@ -7971,7 +7950,7 @@ class DB( HydrusDB.HydrusDB ):
query_hash_ids = do_or_preds( or_predicates, query_hash_ids )
- have_cross_referenced_file_service = True
+ have_cross_referenced_file_locations = True
done_or_predicates = True
@@ -8002,6 +7981,9 @@ class DB( HydrusDB.HydrusDB ):
if need_file_domain_cross_reference:
+ # in future we will hang an explicit service off this predicate and specify import/deleted time
+ # for now we'll wangle a compromise and just check all, and if domain is deleted, then search deletion time
+
import_timestamp_predicates = []
if 'min_import_timestamp' in simple_preds: import_timestamp_predicates.append( 'timestamp >= ' + str( simple_preds[ 'min_import_timestamp' ] ) )
@@ -8011,13 +7993,20 @@ class DB( HydrusDB.HydrusDB ):
pred_string = ' AND '.join( import_timestamp_predicates )
- current_files_table_name = ClientDBFilesStorage.GenerateFilesTableName( file_service_id, HC.CONTENT_STATUS_CURRENT )
+ table_names = []
+ table_names.extend( ( ClientDBFilesStorage.GenerateFilesTableName( self.modules_services.GetServiceId( service_key ), HC.CONTENT_STATUS_CURRENT ) for service_key in location_search_context.current_service_keys ) )
+ table_names.extend( ( ClientDBFilesStorage.GenerateFilesTableName( self.modules_services.GetServiceId( service_key ), HC.CONTENT_STATUS_DELETED ) for service_key in location_search_context.deleted_service_keys ) )
- import_timestamp_hash_ids = self._STS( self._Execute( 'SELECT hash_id FROM {} WHERE {};'.format( current_files_table_name, pred_string ) ) )
+ import_timestamp_hash_ids = set()
+
+ for table_name in table_names:
+
+ import_timestamp_hash_ids.update( self._Execute( 'SELECT hash_id FROM {} WHERE {};'.format( table_name, pred_string ) ) )
+
query_hash_ids = intersection_update_qhi( query_hash_ids, import_timestamp_hash_ids )
- have_cross_referenced_file_service = True
+ have_cross_referenced_file_locations = True
@@ -8139,11 +8128,11 @@ class DB( HydrusDB.HydrusDB ):
else:
- dupe_hash_ids = self._DuplicatesGetHashIdsFromDuplicateCountPredicate( file_service_key, operator, num_relationships, dupe_type )
+ dupe_hash_ids = self._DuplicatesGetHashIdsFromDuplicateCountPredicate( db_location_search_context, operator, num_relationships, dupe_type )
query_hash_ids = intersection_update_qhi( query_hash_ids, dupe_hash_ids )
- have_cross_referenced_file_service = True
+ have_cross_referenced_file_locations = True
@@ -8203,7 +8192,7 @@ class DB( HydrusDB.HydrusDB ):
query_hash_ids = intersection_update_qhi( query_hash_ids, tag_query_hash_ids )
- have_cross_referenced_file_service = True
+ have_cross_referenced_file_locations = True
if query_hash_ids == set():
@@ -8229,7 +8218,7 @@ class DB( HydrusDB.HydrusDB ):
query_hash_ids = intersection_update_qhi( query_hash_ids, namespace_query_hash_ids )
- have_cross_referenced_file_service = True
+ have_cross_referenced_file_locations = True
if query_hash_ids == set():
@@ -8255,7 +8244,7 @@ class DB( HydrusDB.HydrusDB ):
query_hash_ids = intersection_update_qhi( query_hash_ids, wildcard_query_hash_ids )
- have_cross_referenced_file_service = True
+ have_cross_referenced_file_locations = True
if query_hash_ids == set():
@@ -8271,7 +8260,7 @@ class DB( HydrusDB.HydrusDB ):
query_hash_ids = do_or_preds( or_predicates, query_hash_ids )
- have_cross_referenced_file_service = True
+ have_cross_referenced_file_locations = True
done_or_predicates = True
@@ -8281,7 +8270,7 @@ class DB( HydrusDB.HydrusDB ):
done_files_info_predicates = False
we_need_some_results = query_hash_ids is None
- we_need_to_cross_reference = need_file_domain_cross_reference and not have_cross_referenced_file_service
+ we_need_to_cross_reference = need_file_domain_cross_reference and not have_cross_referenced_file_locations
if we_need_some_results or we_need_to_cross_reference:
@@ -8291,22 +8280,22 @@ class DB( HydrusDB.HydrusDB ):
else:
- current_files_table_name = ClientDBFilesStorage.GenerateFilesTableName( file_service_id, HC.CONTENT_STATUS_CURRENT )
-
if len( files_info_predicates ) == 0:
files_info_predicates.insert( 0, '1=1' )
+ files_table_name = db_location_search_context.files_table_name
+
if query_hash_ids is None:
- query_hash_ids = intersection_update_qhi( query_hash_ids, self._STS( self._Execute( 'SELECT hash_id FROM {} NATURAL JOIN files_info WHERE {};'.format( current_files_table_name, ' AND '.join( files_info_predicates ) ) ) ) )
+ query_hash_ids = intersection_update_qhi( query_hash_ids, self._STS( self._Execute( 'SELECT hash_id FROM {} NATURAL JOIN files_info WHERE {};'.format( files_table_name, ' AND '.join( files_info_predicates ) ) ) ) )
else:
if is_inbox and len( query_hash_ids ) == len( self.modules_files_metadata_basic.inbox_hash_ids ):
- query_hash_ids = intersection_update_qhi( query_hash_ids, self._STS( self._Execute( 'SELECT hash_id FROM {} NATURAL JOIN {} NATURAL JOIN files_info WHERE {};'.format( 'file_inbox', current_files_table_name, ' AND '.join( files_info_predicates ) ) ) ) )
+ query_hash_ids = intersection_update_qhi( query_hash_ids, self._STS( self._Execute( 'SELECT hash_id FROM {} NATURAL JOIN {} NATURAL JOIN files_info WHERE {};'.format( 'file_inbox', files_table_name, ' AND '.join( files_info_predicates ) ) ) ) )
else:
@@ -8314,12 +8303,12 @@ class DB( HydrusDB.HydrusDB ):
self._AnalyzeTempTable( temp_table_name )
- query_hash_ids = intersection_update_qhi( query_hash_ids, self._STS( self._Execute( 'SELECT hash_id FROM {} NATURAL JOIN {} NATURAL JOIN files_info WHERE {};'.format( temp_table_name, current_files_table_name, ' AND '.join( files_info_predicates ) ) ) ) )
+ query_hash_ids = intersection_update_qhi( query_hash_ids, self._STS( self._Execute( 'SELECT hash_id FROM {} NATURAL JOIN {} NATURAL JOIN files_info WHERE {};'.format( temp_table_name, files_table_name, ' AND '.join( files_info_predicates ) ) ) ) )
- have_cross_referenced_file_service = True
+ have_cross_referenced_file_locations = True
done_files_info_predicates = True
@@ -8504,17 +8493,17 @@ class DB( HydrusDB.HydrusDB ):
if only_do_zero:
- nonzero_hash_ids = self._DuplicatesGetHashIdsFromDuplicateCountPredicate( file_service_key, '>', 0, dupe_type )
+ nonzero_hash_ids = self._DuplicatesGetHashIdsFromDuplicateCountPredicate( db_location_search_context, '>', 0, dupe_type )
query_hash_ids.difference_update( nonzero_hash_ids )
elif include_zero:
- nonzero_hash_ids = self._DuplicatesGetHashIdsFromDuplicateCountPredicate( file_service_key, '>', 0, dupe_type )
+ nonzero_hash_ids = self._DuplicatesGetHashIdsFromDuplicateCountPredicate( db_location_search_context, '>', 0, dupe_type )
zero_hash_ids = query_hash_ids.difference( nonzero_hash_ids )
- accurate_except_zero_hash_ids = self._DuplicatesGetHashIdsFromDuplicateCountPredicate( file_service_key, operator, num_relationships, dupe_type )
+ accurate_except_zero_hash_ids = self._DuplicatesGetHashIdsFromDuplicateCountPredicate( db_location_search_context, operator, num_relationships, dupe_type )
hash_ids = zero_hash_ids.union( accurate_except_zero_hash_ids )
@@ -8620,26 +8609,38 @@ class DB( HydrusDB.HydrusDB ):
#
+ file_location_is_all_local = self.modules_services.LocationSearchContextIsCoveredByCombinedLocalFiles( location_search_context )
+ file_location_is_all_non_local = location_search_context.IsOneDomain() and CC.LOCAL_FILE_SERVICE_KEY in location_search_context.deleted_service_keys
+
must_be_local = system_predicates.MustBeLocal() or system_predicates.MustBeArchive()
must_not_be_local = system_predicates.MustNotBeLocal()
- if file_service_type in HC.LOCAL_FILE_SERVICES:
+ if file_location_is_all_local:
+
+ # if must be all local, we are great already
if must_not_be_local:
query_hash_ids = set()
- elif must_be_local or must_not_be_local:
-
- local_hash_ids = self.modules_files_storage.GetCurrentHashIdsList( self.modules_services.combined_local_file_service_id )
+ elif file_location_is_all_non_local:
if must_be_local:
- query_hash_ids = intersection_update_qhi( query_hash_ids, local_hash_ids )
+ query_hash_ids = set()
+
+
+ elif must_be_local or must_not_be_local:
+
+ if must_be_local:
+
+ query_hash_ids = self.modules_files_storage.FilterCurrentHashIds( self.modules_services.combined_local_file_service_id, query_hash_ids )
elif must_not_be_local:
+ local_hash_ids = self.modules_files_storage.GetCurrentHashIdsList( self.modules_services.combined_local_file_service_id )
+
query_hash_ids.difference_update( local_hash_ids )
@@ -8752,7 +8753,7 @@ class DB( HydrusDB.HydrusDB ):
self._AnalyzeTempTable( temp_table_name )
- good_hash_ids = self._GetHashIdsThatHaveTagAsNum( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, namespace, num, '>', hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name, job_key = job_key )
+ ( good_hash_ids, was_file_location_cross_referenced ) = self._GetHashIdsThatHaveTagAsNumComplexLocation( ClientTags.TAG_DISPLAY_ACTUAL, location_search_context, tag_search_context, namespace, num, '>', hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name, job_key = job_key )
query_hash_ids = intersection_update_qhi( query_hash_ids, good_hash_ids )
@@ -8766,7 +8767,7 @@ class DB( HydrusDB.HydrusDB ):
self._AnalyzeTempTable( temp_table_name )
- good_hash_ids = self._GetHashIdsThatHaveTagAsNum( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, namespace, num, '<', hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name, job_key = job_key )
+ ( good_hash_ids, was_file_location_cross_referenced ) = self._GetHashIdsThatHaveTagAsNumComplexLocation( ClientTags.TAG_DISPLAY_ACTUAL, location_search_context, tag_search_context, namespace, num, '<', hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name, job_key = job_key )
query_hash_ids = intersection_update_qhi( query_hash_ids, good_hash_ids )
@@ -8794,9 +8795,9 @@ class DB( HydrusDB.HydrusDB ):
did_sort = False
- if sort_by is not None and file_service_id != self.modules_services.combined_file_service_id:
+ if sort_by is not None and not location_search_context.IsAllKnownFiles():
- ( did_sort, query_hash_ids ) = self._TryToSortHashIds( file_service_id, query_hash_ids, sort_by )
+ ( did_sort, query_hash_ids ) = self._TryToSortHashIds( location_search_context, query_hash_ids, sort_by )
#
@@ -9162,11 +9163,20 @@ class DB( HydrusDB.HydrusDB ):
+ cancelled_hook = None
+
+ if job_key is not None:
+
+ cancelled_hook = job_key.IsCancelled
+
+
nonzero_tag_hash_ids = set()
for query in queries:
- nonzero_tag_hash_ids.update( self._STI( self._Execute( query ) ) )
+ cursor = self._Execute( query )
+
+ nonzero_tag_hash_ids.update( self._STI( HydrusDB.ReadFromCancellableCursor( cursor, 10240, cancelled_hook ) ) )
if job_key is not None and job_key.IsCancelled():
@@ -9178,7 +9188,77 @@ class DB( HydrusDB.HydrusDB ):
return nonzero_tag_hash_ids
- def _GetHashIdsThatHaveTagAsNum( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, namespace, num, operator, hash_ids = None, hash_ids_table_name = None, job_key = None ):
+ def _ConvertLocationAndTagContextToCoveringCachePairs( self, location_search_context: ClientSearch.LocationSearchContext, tag_search_context: ClientSearch.TagSearchContext ):
+
+ # a way to support deleted efficiently is to have deleted file tag caches
+ # but that is so silly, given how rare deleted file tag searches will be, that we should just swallow 'all known files' searches
+
+ tag_search_contexts = [ tag_search_context ]
+
+ if location_search_context.SearchesCurrent() and not location_search_context.SearchesDeleted():
+
+ file_location_needs_cross_referencing = not location_search_context.IsAllKnownFiles()
+
+ file_service_keys = list( location_search_context.current_service_keys )
+
+ else:
+
+ file_location_needs_cross_referencing = False
+
+ file_service_keys = [ CC.COMBINED_FILE_SERVICE_KEY ]
+
+ if tag_search_context.IsAllKnownTags() == CC.COMBINED_TAG_SERVICE_KEY:
+
+ tag_search_contexts = []
+
+ for tag_service in self.modules_services.GetServices( HC.REAL_TAG_SERVICES ):
+
+ tsc = tag_search_context.Duplicate()
+
+ tsc.service_key = tag_service.GetServiceKey()
+
+ tag_search_contexts.append( tsc )
+
+
+
+
+ return ( list( itertools.product( file_service_keys, tag_search_contexts ) ), file_location_needs_cross_referencing )
+
+
+ def _GetHashIdsThatHaveTagAsNumComplexLocation( self, tag_display_type: int, location_search_context: ClientSearch.LocationSearchContext, tag_search_context: ClientSearch.TagSearchContext, namespace, num, operator, hash_ids = None, hash_ids_table_name = None, job_key = None ):
+
+ if not location_search_context.SearchesAnything():
+
+ return set()
+
+
+ ( file_and_tag_domain_pairs, file_location_is_cross_referenced ) = self._ConvertLocationAndTagContextToCoveringCachePairs( location_search_context, tag_search_context )
+
+ if not file_location_is_cross_referenced and hash_ids_table_name is not None:
+
+ file_location_is_cross_referenced = True
+
+
+ results = set()
+
+ for ( file_service_key, tag_search_context ) in file_and_tag_domain_pairs:
+
+ some_results = self._GetHashIdsThatHaveTagAsNumSimpleLocation( tag_display_type, file_service_key, tag_search_context, namespace, num, operator, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name, job_key = job_key )
+
+ if len( results ) == 0:
+
+ results = some_results
+
+ else:
+
+ results.update( some_results )
+
+
+
+ return ( results, file_location_is_cross_referenced )
+
+
+ def _GetHashIdsThatHaveTagAsNumSimpleLocation( self, tag_display_type: int, file_service_key: bytes, tag_search_context: ClientSearch.TagSearchContext, namespace, num, operator, hash_ids = None, hash_ids_table_name = None, job_key = None ):
file_service_id = self.modules_services.GetServiceId( file_service_key )
tag_service_id = self.modules_services.GetServiceId( tag_search_context.service_key )
@@ -14752,7 +14832,7 @@ class DB( HydrusDB.HydrusDB ):
self._ExecuteMany( 'INSERT INTO service_directory_file_map ( service_id, directory_id, hash_id ) VALUES ( ?, ?, ? );', ( ( service_id, directory_id, hash_id ) for hash_id in hash_ids ) )
- def _TryToSortHashIds( self, file_service_id, hash_ids, sort_by: ClientMedia.MediaSort ):
+ def _TryToSortHashIds( self, location_search_context: ClientSearch.LocationSearchContext, hash_ids, sort_by: ClientMedia.MediaSort ):
did_sort = False
@@ -14784,9 +14864,16 @@ class DB( HydrusDB.HydrusDB ):
if sort_data == CC.SORT_FILES_BY_IMPORT_TIME:
- current_files_table_name = ClientDBFilesStorage.GenerateFilesTableName( file_service_id, HC.CONTENT_STATUS_CURRENT )
-
- query = 'SELECT hash_id, timestamp FROM {} WHERE hash_id = ?;'.format( current_files_table_name )
+ if location_search_context.IsOneDomain():
+
+ file_service_key = location_search_context.GetFileServiceKey()
+
+ file_service_id = self.modules_services.GetServiceId( file_service_key )
+
+ current_files_table_name = ClientDBFilesStorage.GenerateFilesTableName( file_service_id, HC.CONTENT_STATUS_CURRENT )
+
+ query = 'SELECT hash_id, timestamp FROM {} WHERE hash_id = ?;'.format( current_files_table_name )
+
elif sort_data == CC.SORT_FILES_BY_FILESIZE:
@@ -15591,7 +15678,7 @@ class DB( HydrusDB.HydrusDB ):
for shortcut in shortcuts:
- tags_autocomplete.SetCommand( shortcut, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SYNCHRONISED_WAIT_SWITCH ) )
+ tags_autocomplete.SetCommand( shortcut, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SYNCHRONISED_WAIT_SWITCH ) )
main_gui.DeleteShortcut( shortcut )
@@ -16457,6 +16544,68 @@ class DB( HydrusDB.HydrusDB ):
+ if version == 451:
+
+ self.modules_services.combined_file_service_id = self.modules_services.GetServiceId( CC.COMBINED_FILE_SERVICE_KEY )
+
+ file_service_ids = list( self.modules_services.GetServiceIds( HC.TAG_CACHE_SPECIFIC_FILE_SERVICES ) )
+ file_service_ids.append( self.modules_services.combined_file_service_id )
+
+ tag_service_ids = self.modules_services.GetServiceIds( HC.REAL_TAG_SERVICES )
+
+ for ( file_service_id, tag_service_id ) in itertools.product( file_service_ids, tag_service_ids ):
+
+ if file_service_id == self.modules_services.combined_file_service_id:
+
+ self._controller.frame_splash_status.SetText( 'working on combined tags cache - {}'.format( tag_service_id ) )
+
+ else:
+
+ self._controller.frame_splash_status.SetText( 'working on specific tags cache - {} {}'.format( file_service_id, tag_service_id ) )
+
+
+ tags_table_name = self._CacheTagsGetTagsTableName( file_service_id, tag_service_id )
+ integer_subtags_table_name = self._CacheTagsGetIntegerSubtagsTableName( file_service_id, tag_service_id )
+
+ query = 'SELECT subtag_id FROM {};'.format( tags_table_name )
+
+ BLOCK_SIZE = 10000
+
+ for ( group_of_subtag_ids, num_done, num_to_do ) in HydrusDB.ReadLargeIdQueryInSeparateChunks( self._c, query, BLOCK_SIZE ):
+
+ message = HydrusData.ConvertValueRangeToPrettyString( num_done, num_to_do )
+
+ self._controller.frame_splash_status.SetSubtext( message )
+
+ with self._MakeTemporaryIntegerTable( group_of_subtag_ids, 'subtag_id' ) as temp_subtag_ids_table_name:
+
+ # temp subtag_ids to subtags
+ subtag_ids_and_subtags = self._Execute( 'SELECT subtag_id, subtag FROM {} CROSS JOIN subtags USING ( subtag_id );'.format( temp_subtag_ids_table_name ) ).fetchall()
+
+ for ( subtag_id, subtag ) in subtag_ids_and_subtags:
+
+ if subtag.isdecimal():
+
+ try:
+
+ integer_subtag = int( subtag )
+
+ if CanCacheInteger( integer_subtag ):
+
+ self._Execute( 'INSERT OR IGNORE INTO {} ( subtag_id, integer_subtag ) VALUES ( ?, ? );'.format( integer_subtags_table_name ), ( subtag_id, integer_subtag ) )
+
+
+ except ValueError:
+
+ pass
+
+
+
+
+
+
+
+
self._controller.frame_splash_status.SetTitleText( 'updated db to v{}'.format( HydrusData.ToHumanInt( version + 1 ) ) )
self._Execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
diff --git a/hydrus/client/db/ClientDBFilesStorage.py b/hydrus/client/db/ClientDBFilesStorage.py
index 920b893d..c3699f3d 100644
--- a/hydrus/client/db/ClientDBFilesStorage.py
+++ b/hydrus/client/db/ClientDBFilesStorage.py
@@ -7,6 +7,8 @@ from hydrus.core import HydrusData
from hydrus.core import HydrusDB
from hydrus.core import HydrusDBModule
+from hydrus.client import ClientConstants as CC
+from hydrus.client import ClientSearch
from hydrus.client.db import ClientDBMaster
from hydrus.client.db import ClientDBServices
@@ -45,6 +47,44 @@ def GenerateFilesTableName( service_id: int, status: int ) -> str:
return petitioned_files_table_name
+class DBLocationSearchContext( object ):
+
+ def __init__( self, location_search_context: ClientSearch.LocationSearchContext ):
+
+ self.location_search_context = location_search_context
+
+ self.files_table_name = None
+
+
+ def GetLocationSearchContext( self ) -> ClientSearch.LocationSearchContext:
+
+ return self.location_search_context
+
+
+ def GetFileIteratorTableJoin( self, table_phrase: str ):
+
+ if self.location_search_context.IsAllKnownFiles():
+
+ return table_phrase
+
+ else:
+
+ return '{} CROSS JOIN {} USING ( hash_id )'.format( self.files_table_name, table_phrase )
+
+
+
+ def GetTableJoinLimitedByFileDomain( self, table_phrase: str ):
+
+ if self.location_search_context.IsAllKnownFiles():
+
+ return table_phrase
+
+ else:
+
+ return '{} CROSS JOIN {} USING ( hash_id )'.format( table_phrase, self.files_table_name )
+
+
+
class ClientDBFilesStorage( HydrusDBModule.HydrusDBModule ):
def __init__( self, cursor: sqlite3.Cursor, modules_services: ClientDBServices.ClientDBMasterServices, modules_texts: ClientDBMaster.ClientDBMasterTexts ):
@@ -54,6 +94,8 @@ class ClientDBFilesStorage( HydrusDBModule.HydrusDBModule ):
HydrusDBModule.HydrusDBModule.__init__( self, 'client files storage', cursor )
+ self.temp_file_storage_table_name = None
+
def _GetInitialIndexGenerationTuples( self ):
@@ -450,6 +492,73 @@ class ClientDBFilesStorage( HydrusDBModule.HydrusDBModule ):
return ( is_deleted, timestamp, file_deletion_reason )
+ def GetDBLocationSearchContext( self, location_search_context: ClientSearch.LocationSearchContext ):
+
+ if not location_search_context.SearchesAnything():
+
+ location_search_context = ClientSearch.LocationSearchContext( current_service_keys = [ CC.COMBINED_FILE_SERVICE_KEY ] )
+
+
+ db_location_search_context = DBLocationSearchContext( location_search_context )
+
+ if location_search_context.IsAllKnownFiles():
+
+ # no table set, obviously
+
+ return db_location_search_context
+
+
+ table_names = []
+
+ for current_service_key in location_search_context.current_service_keys:
+
+ service_id = self.modules_services.GetServiceId( current_service_key )
+
+ table_names.append( GenerateFilesTableName( service_id, HC.CONTENT_STATUS_CURRENT ) )
+
+
+ for deleted_service_key in location_search_context.deleted_service_keys:
+
+ service_id = self.modules_services.GetServiceId( deleted_service_key )
+
+ table_names.append( GenerateFilesTableName( service_id, HC.CONTENT_STATUS_DELETED ) )
+
+
+ if len( table_names ) == 1:
+
+ table_name = table_names[0]
+
+ db_location_search_context.files_table_name = table_name
+
+ else:
+
+ # while I could make a VIEW of the UNION SELECT, we'll populate an indexed single column table to help query planner later on
+ # we're hardcoding the name to this class for now, so a limit of one db_location_search_context at a time _for now_
+ # we make change this in future to use wrapper temp int tables, we'll see
+
+ # maybe I should stick this guy in 'temp' to live through db connection resets, but we'll see I guess. it is generally ephemeral, not going to linger through weird vacuum maintenance or anything right?
+
+ if self.temp_file_storage_table_name is None:
+
+ self.temp_file_storage_table_name = 'mem.temp_file_storage_hash_id'
+
+ self._Execute( 'CREATE TABLE IF NOT EXISTS {} ( hash_id INTEGER PRIMARY KEY );'.format( self.temp_file_storage_table_name ) )
+
+ else:
+
+ self._Execute( 'DELETE FROM {};'.format( self.temp_file_storage_table_name ) )
+
+
+ select_query = ' UNION '.join( ( 'SELECT hash_id FROM {}'.format( table_name ) for table_name in table_names ) )
+
+ self._Execute( 'INSERT OR IGNORE INTO {} ( hash_id ) SELECT hash_id FROM {};'.format( self.temp_file_storage_table_name, select_query ) )
+
+ db_location_search_context.files_table_name = self.temp_file_storage_table_name
+
+
+ return db_location_search_context
+
+
def GetExpectedTableNames( self ) -> typing.Collection[ str ]:
expected_table_names = [
diff --git a/hydrus/client/db/ClientDBServices.py b/hydrus/client/db/ClientDBServices.py
index 077d329f..b0344e26 100644
--- a/hydrus/client/db/ClientDBServices.py
+++ b/hydrus/client/db/ClientDBServices.py
@@ -8,6 +8,7 @@ from hydrus.core import HydrusExceptions
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
+from hydrus.client import ClientSearch
from hydrus.client import ClientServices
class ClientDBMasterServices( HydrusDBModule.HydrusDBModule ):
@@ -180,6 +181,27 @@ class ClientDBMasterServices( HydrusDBModule.HydrusDBModule ):
return []
+ def LocationSearchContextIsCoveredByCombinedLocalFiles( self, location_search_context: ClientSearch.LocationSearchContext ):
+
+ if location_search_context.SearchesDeleted():
+
+ return False
+
+
+ file_location_is_all_local = False
+
+ service_ids = { self.GetServiceId( service_key ) for service_key in location_search_context.current_service_keys }
+
+ service_types = { self.GetService( service_id ).GetServiceType() for service_id in service_ids }
+
+ if False not in ( service_type in HC.LOCAL_FILE_SERVICES for service_type in service_types ):
+
+ file_location_is_all_local = True
+
+
+ return file_location_is_all_local
+
+
def UpdateService( self, service: ClientServices.Service ):
( service_key, service_type, name, dictionary ) = service.ToTuple()
diff --git a/hydrus/client/gui/ClientGUI.py b/hydrus/client/gui/ClientGUI.py
index 787edeea..8c7e3be0 100644
--- a/hydrus/client/gui/ClientGUI.py
+++ b/hydrus/client/gui/ClientGUI.py
@@ -4282,7 +4282,7 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
t += 0.25
- HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_PAGE_OF_PAGES ) )
+ HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_PAGE_OF_PAGES ) )
t += 0.25
@@ -4290,23 +4290,23 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
t += 0.25
- HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_DUPLICATE_FILTER_PAGE ) )
+ HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_DUPLICATE_FILTER_PAGE ) )
t += 0.25
- HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_GALLERY_DOWNLOADER_PAGE ) )
+ HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_GALLERY_DOWNLOADER_PAGE ) )
t += 0.25
- HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_SIMPLE_DOWNLOADER_PAGE ) )
+ HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_SIMPLE_DOWNLOADER_PAGE ) )
t += 0.25
- HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_URL_DOWNLOADER_PAGE ) )
+ HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_URL_DOWNLOADER_PAGE ) )
t += 0.25
- HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_WATCHER_DOWNLOADER_PAGE ) )
+ HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_WATCHER_DOWNLOADER_PAGE ) )
t += 0.25
@@ -4362,11 +4362,11 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
t += 0.5
- HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SET_MEDIA_FOCUS ) )
+ HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SET_MEDIA_FOCUS ) )
t += 0.5
- HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SET_SEARCH_FOCUS ) )
+ HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SET_SEARCH_FOCUS ) )
t += 0.5
@@ -6391,7 +6391,7 @@ Try to keep this below 10 million!'''
ClientGUIMenus.AppendSeparator( menu )
- ClientGUIMenus.AppendMenuItem( menu, 'pick a new page', 'Choose a new page to open.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_PAGE ) )
+ ClientGUIMenus.AppendMenuItem( menu, 'pick a new page', 'Choose a new page to open.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_PAGE ) )
#
@@ -6441,10 +6441,10 @@ Try to keep this below 10 million!'''
download_menu = QW.QMenu( menu )
- ClientGUIMenus.AppendMenuItem( download_menu, 'url download', 'Open a new tab to download some separate urls.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, 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( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_WATCHER_DOWNLOADER_PAGE ) )
- ClientGUIMenus.AppendMenuItem( download_menu, 'gallery', 'Open a new tab to download from gallery sites.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, 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( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_SIMPLE_DOWNLOADER_PAGE ) )
+ 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' )
@@ -6465,8 +6465,8 @@ Try to keep this below 10 million!'''
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( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, 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( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_NEW_DUPLICATE_FILTER_PAGE ) )
+ 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' )
@@ -6863,11 +6863,9 @@ Try to keep this below 10 million!'''
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_EXIT_APPLICATION:
diff --git a/hydrus/client/gui/ClientGUIApplicationCommand.py b/hydrus/client/gui/ClientGUIApplicationCommand.py
index db90355c..c08aa7df 100644
--- a/hydrus/client/gui/ClientGUIApplicationCommand.py
+++ b/hydrus/client/gui/ClientGUIApplicationCommand.py
@@ -356,14 +356,58 @@ class SimpleSubPanel( QW.QWidget ):
self._simple_actions.addItem( display_string, data )
+ self._seek_panel = QW.QWidget( self )
+
+ choices = [
+ ( 'back', -1 ),
+ ( 'forwards', 1 )
+ ]
+
+ self._seek_direction = ClientGUICommon.BetterRadioBox( self._seek_panel, choices = choices )
+
+ self._seek_duration_s = QP.MakeQSpinBox( self._seek_panel, max=3599, width = 60 )
+ self._seek_duration_ms = QP.MakeQSpinBox( self._seek_panel, max=999, width = 60 )
+
+ #
+
+ self._seek_duration_s.setValue( 5 )
+ self._seek_duration_ms.setValue( 0 )
+
+ self._seek_duration_s.value() * 1000 + self._seek_duration_ms.value()
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, self._seek_direction, CC.FLAGS_CENTER )
+ QP.AddToLayout( hbox, self._seek_duration_s, CC.FLAGS_CENTER )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self._seek_panel, label = 's' ), CC.FLAGS_CENTER )
+ QP.AddToLayout( hbox, self._seek_duration_ms, CC.FLAGS_CENTER )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self._seek_panel, label = 'ms' ), CC.FLAGS_CENTER )
+ hbox.addStretch( 1 )
+
+ self._seek_panel.setLayout( hbox )
+
#
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, self._simple_actions, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
+ QP.AddToLayout( vbox, self._seek_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.setLayout( vbox )
+ self._simple_actions.currentIndexChanged.connect( self._UpdateControls )
+
+ self._UpdateControls()
+
+
+ def _UpdateControls( self ):
+
+ action = self._simple_actions.GetValue()
+
+ self._seek_panel.setVisible( action == CAC.SIMPLE_MEDIA_SEEK_DELTA )
+
def GetValue( self ):
@@ -375,13 +419,45 @@ class SimpleSubPanel( QW.QWidget ):
else:
- return CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, action )
+ if action == CAC.SIMPLE_MEDIA_SEEK_DELTA:
+
+ direction = self._seek_direction.GetValue()
+
+ s = self._seek_duration_s.value()
+ ms = self._seek_duration_ms.value() + ( 1000 * s )
+
+ simple_data = ( direction, ms )
+
+ else:
+
+ simple_data = None
+
+
+ return CAC.ApplicationCommand.STATICCreateSimpleCommand( action, simple_data = simple_data )
- def SetValue( self, value ):
+ def SetValue( self, command: CAC.ApplicationCommand ):
- self._simple_actions.SetValue( value )
+ action = command.GetSimpleAction()
+
+ self._simple_actions.SetValue( action )
+
+ if action == CAC.SIMPLE_MEDIA_SEEK_DELTA:
+
+ ( direction, ms ) = command.GetSimpleData()
+
+ self._seek_direction.SetValue( direction )
+
+ s = ms // 1000
+
+ ms = ms % 1000
+
+ self._seek_duration_s.setValue( s )
+ self._seek_duration_ms.setValue( ms )
+
+
+ self._UpdateControls()
class TagSubPanel( QW.QWidget ):
@@ -533,13 +609,9 @@ class ApplicationCommandWidget( ClientGUIScrolledPanels.EditPanel ):
#
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
-
- self._simple_sub_panel.SetValue( action )
+ self._simple_sub_panel.SetValue( command )
self._panel_choice.SetValue( self.PANEL_SIMPLE )
diff --git a/hydrus/client/gui/ClientGUIDialogsManage.py b/hydrus/client/gui/ClientGUIDialogsManage.py
index bff02553..cede8733 100644
--- a/hydrus/client/gui/ClientGUIDialogsManage.py
+++ b/hydrus/client/gui/ClientGUIDialogsManage.py
@@ -116,11 +116,9 @@ class DialogManageRatings( ClientGUIDialogs.Dialog ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_MANAGE_FILE_RATINGS:
diff --git a/hydrus/client/gui/ClientGUIMPV.py b/hydrus/client/gui/ClientGUIMPV.py
index c1a4ed55..bc09538d 100644
--- a/hydrus/client/gui/ClientGUIMPV.py
+++ b/hydrus/client/gui/ClientGUIMPV.py
@@ -348,11 +348,9 @@ class mpvWidget( QW.QWidget ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_PAUSE_MEDIA:
@@ -362,6 +360,12 @@ class mpvWidget( QW.QWidget ):
self.PausePlay()
+ elif action == CAC.SIMPLE_MEDIA_SEEK_DELTA:
+
+ ( direction, duration_ms ) = command.GetSimpleData()
+
+ self.SeekDelta( direction, duration_ms )
+
elif action == CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM:
if self._media is not None:
@@ -390,22 +394,6 @@ class mpvWidget( QW.QWidget ):
return command_processed
- def SetCanvasType( self, canvas_type ):
-
- self._canvas_type = canvas_type
-
- if self._canvas_type == ClientGUICommon.CANVAS_MEDIA_VIEWER:
-
- shortcut_set = 'media_viewer_media_window'
-
- else:
-
- shortcut_set = 'preview_media_window'
-
-
- self._my_shortcut_handler.SetShortcuts( [ shortcut_set ] )
-
-
def Seek( self, time_index_ms ):
if not self._file_is_loaded:
@@ -433,6 +421,41 @@ class mpvWidget( QW.QWidget ):
+ def SeekDelta( self, direction, duration_ms ):
+
+ if not self._file_is_loaded:
+
+ return
+
+
+ current_timestamp_s = self._player.time_pos
+
+ new_timestamp_ms = max( 0, ( current_timestamp_s * 1000 ) + ( direction * duration_ms ) )
+
+ if new_timestamp_ms > self._media.GetDuration():
+
+ new_timestamp_ms = 0
+
+
+ self.Seek( new_timestamp_ms )
+
+
+ def SetCanvasType( self, canvas_type ):
+
+ self._canvas_type = canvas_type
+
+ if self._canvas_type == ClientGUICommon.CANVAS_MEDIA_VIEWER:
+
+ shortcut_set = 'media_viewer_media_window'
+
+ else:
+
+ shortcut_set = 'preview_media_window'
+
+
+ self._my_shortcut_handler.SetShortcuts( [ shortcut_set ] )
+
+
def SetMedia( self, media, start_paused = False ):
if media == self._media:
diff --git a/hydrus/client/gui/ClientGUIScrolledPanels.py b/hydrus/client/gui/ClientGUIScrolledPanels.py
index 40ede648..ca4cacfd 100644
--- a/hydrus/client/gui/ClientGUIScrolledPanels.py
+++ b/hydrus/client/gui/ClientGUIScrolledPanels.py
@@ -208,11 +208,9 @@ class EditSingleCtrlPanel( EditPanel ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action in self._ok_on_these_commands:
diff --git a/hydrus/client/gui/ClientGUIScrolledPanelsEdit.py b/hydrus/client/gui/ClientGUIScrolledPanelsEdit.py
index 499a3494..230dc79d 100644
--- a/hydrus/client/gui/ClientGUIScrolledPanelsEdit.py
+++ b/hydrus/client/gui/ClientGUIScrolledPanelsEdit.py
@@ -1493,11 +1493,9 @@ class EditFileNotesPanel( ClientGUIScrolledPanels.EditPanel ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_MANAGE_FILE_NOTES:
diff --git a/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py b/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py
index 7640857c..554fd948 100644
--- a/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py
+++ b/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py
@@ -656,14 +656,17 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
weights_panel = ClientGUICommon.StaticBox( self, 'duplicate filter comparison score weights' )
- self._duplicate_comparison_score_higher_jpeg_quality = QP.MakeQSpinBox( weights_panel, min=0, max=100 )
- self._duplicate_comparison_score_much_higher_jpeg_quality = QP.MakeQSpinBox( weights_panel, min=0, max=100 )
- self._duplicate_comparison_score_higher_filesize = QP.MakeQSpinBox( weights_panel, min=0, max=100 )
- self._duplicate_comparison_score_much_higher_filesize = QP.MakeQSpinBox( weights_panel, min=0, max=100 )
- self._duplicate_comparison_score_higher_resolution = QP.MakeQSpinBox( weights_panel, min=0, max=100 )
- self._duplicate_comparison_score_much_higher_resolution = QP.MakeQSpinBox( weights_panel, min=0, max=100 )
- self._duplicate_comparison_score_more_tags = QP.MakeQSpinBox( weights_panel, min=0, max=100 )
- self._duplicate_comparison_score_older = QP.MakeQSpinBox( weights_panel, min=0, max=100 )
+ self._duplicate_comparison_score_higher_jpeg_quality = QP.MakeQSpinBox( weights_panel, min=-100, max=100 )
+ self._duplicate_comparison_score_much_higher_jpeg_quality = QP.MakeQSpinBox( weights_panel, min=-100, max=100 )
+ self._duplicate_comparison_score_higher_filesize = QP.MakeQSpinBox( weights_panel, min=-100, max=100 )
+ self._duplicate_comparison_score_much_higher_filesize = QP.MakeQSpinBox( weights_panel, min=-100, max=100 )
+ self._duplicate_comparison_score_higher_resolution = QP.MakeQSpinBox( weights_panel, min=-100, max=100 )
+ self._duplicate_comparison_score_much_higher_resolution = QP.MakeQSpinBox( weights_panel, min=-100, max=100 )
+ self._duplicate_comparison_score_more_tags = QP.MakeQSpinBox( weights_panel, min=-100, max=100 )
+ self._duplicate_comparison_score_older = QP.MakeQSpinBox( weights_panel, min=-100, max=100 )
+ self._duplicate_comparison_score_nicer_ratio = QP.MakeQSpinBox( weights_panel, min=-100, max=100 )
+
+ self._duplicate_comparison_score_nicer_ratio.setToolTip( 'For instance, 16:9 vs 640:357.')
self._duplicate_filter_max_batch_size = QP.MakeQSpinBox( self, min = 10, max = 1024 )
@@ -677,6 +680,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._duplicate_comparison_score_much_higher_resolution.setValue( self._new_options.GetInteger( 'duplicate_comparison_score_much_higher_resolution' ) )
self._duplicate_comparison_score_more_tags.setValue( self._new_options.GetInteger( 'duplicate_comparison_score_more_tags' ) )
self._duplicate_comparison_score_older.setValue( self._new_options.GetInteger( 'duplicate_comparison_score_older' ) )
+ self._duplicate_comparison_score_nicer_ratio.setValue( self._new_options.GetInteger( 'duplicate_comparison_score_nicer_ratio' ) )
self._duplicate_filter_max_batch_size.setValue( self._new_options.GetInteger( 'duplicate_filter_max_batch_size' ) )
@@ -692,10 +696,13 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
rows.append( ( 'Score for file with significantly higher resolution (as num pixels):', self._duplicate_comparison_score_much_higher_resolution ) )
rows.append( ( 'Score for file with more tags:', self._duplicate_comparison_score_more_tags ) )
rows.append( ( 'Score for file with non-trivially earlier import time:', self._duplicate_comparison_score_older ) )
+ rows.append( ( 'Score for file with \'nicer\' resolution ratio:', self._duplicate_comparison_score_nicer_ratio ) )
gridbox = ClientGUICommon.WrapInGrid( weights_panel, rows )
label = 'When processing potential duplicate pairs in the duplicate filter, the client tries to present the \'best\' file first. It judges the two files on a variety of potential differences, each with a score. The file with the greatest total score is presented first. Here you can tinker with these scores.'
+ label += os.linesep * 2
+ label += 'I recommend you leave all these as positive numbers, but if you wish, you can set a negative number to reduce the score.'
st = ClientGUICommon.BetterStaticText( weights_panel, label )
st.setWordWrap( True )
@@ -731,6 +738,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._new_options.SetInteger( 'duplicate_comparison_score_much_higher_resolution', self._duplicate_comparison_score_much_higher_resolution.value() )
self._new_options.SetInteger( 'duplicate_comparison_score_more_tags', self._duplicate_comparison_score_more_tags.value() )
self._new_options.SetInteger( 'duplicate_comparison_score_older', self._duplicate_comparison_score_older.value() )
+ self._new_options.SetInteger( 'duplicate_comparison_score_nicer_ratio', self._duplicate_comparison_score_nicer_ratio.value() )
self._new_options.SetInteger( 'duplicate_filter_max_batch_size', self._duplicate_filter_max_batch_size.value() )
@@ -3996,11 +4004,9 @@ class ManageURLsPanel( ClientGUIScrolledPanels.ManagePanel ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_MANAGE_FILE_URLS:
diff --git a/hydrus/client/gui/ClientGUIShortcuts.py b/hydrus/client/gui/ClientGUIShortcuts.py
index c66f8346..0dc3ea21 100644
--- a/hydrus/client/gui/ClientGUIShortcuts.py
+++ b/hydrus/client/gui/ClientGUIShortcuts.py
@@ -221,14 +221,14 @@ SHORTCUTS_RESERVED_NAMES = [ 'global', 'archive_delete_filter', 'duplicate_filte
SHORTCUTS_GLOBAL_ACTIONS = [ CAC.SIMPLE_GLOBAL_AUDIO_MUTE, CAC.SIMPLE_GLOBAL_AUDIO_UNMUTE, CAC.SIMPLE_GLOBAL_AUDIO_MUTE_FLIP, CAC.SIMPLE_EXIT_APPLICATION, CAC.SIMPLE_EXIT_APPLICATION_FORCE_MAINTENANCE, CAC.SIMPLE_RESTART_APPLICATION, CAC.SIMPLE_HIDE_TO_SYSTEM_TRAY ]
SHORTCUTS_MEDIA_ACTIONS = [ CAC.SIMPLE_MANAGE_FILE_TAGS, CAC.SIMPLE_MANAGE_FILE_RATINGS, CAC.SIMPLE_MANAGE_FILE_URLS, CAC.SIMPLE_MANAGE_FILE_NOTES, CAC.SIMPLE_ARCHIVE_FILE, CAC.SIMPLE_INBOX_FILE, CAC.SIMPLE_DELETE_FILE, CAC.SIMPLE_UNDELETE_FILE, CAC.SIMPLE_EXPORT_FILES, CAC.SIMPLE_EXPORT_FILES_QUICK_AUTO_EXPORT, CAC.SIMPLE_REMOVE_FILE_FROM_VIEW, CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM, CAC.SIMPLE_OPEN_SELECTION_IN_NEW_PAGE, CAC.SIMPLE_LAUNCH_THE_ARCHIVE_DELETE_FILTER, CAC.SIMPLE_COPY_BMP, CAC.SIMPLE_COPY_BMP_OR_FILE_IF_NOT_BMPABLE, CAC.SIMPLE_COPY_FILE, CAC.SIMPLE_COPY_PATH, CAC.SIMPLE_COPY_SHA256_HASH, CAC.SIMPLE_COPY_MD5_HASH, CAC.SIMPLE_COPY_SHA1_HASH, CAC.SIMPLE_COPY_SHA512_HASH, CAC.SIMPLE_GET_SIMILAR_TO_EXACT, CAC.SIMPLE_GET_SIMILAR_TO_VERY_SIMILAR, CAC.SIMPLE_GET_SIMILAR_TO_SIMILAR, CAC.SIMPLE_GET_SIMILAR_TO_SPECULATIVE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_ALTERNATE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_ALTERNATE_COLLECTIONS, CAC.SIMPLE_DUPLICATE_MEDIA_SET_CUSTOM, CAC.SIMPLE_DUPLICATE_MEDIA_SET_FOCUSED_BETTER, CAC.SIMPLE_DUPLICATE_MEDIA_SET_FOCUSED_KING, CAC.SIMPLE_DUPLICATE_MEDIA_SET_SAME_QUALITY, CAC.SIMPLE_OPEN_KNOWN_URL ]
-SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ CAC.SIMPLE_PAUSE_MEDIA, CAC.SIMPLE_PAUSE_PLAY_MEDIA, CAC.SIMPLE_MOVE_ANIMATION_TO_PREVIOUS_FRAME, CAC.SIMPLE_MOVE_ANIMATION_TO_NEXT_FRAME, CAC.SIMPLE_SWITCH_BETWEEN_FULLSCREEN_BORDERLESS_AND_REGULAR_FRAMED_WINDOW, CAC.SIMPLE_PAN_UP, CAC.SIMPLE_PAN_DOWN, CAC.SIMPLE_PAN_LEFT, CAC.SIMPLE_PAN_RIGHT, CAC.SIMPLE_PAN_TOP_EDGE, CAC.SIMPLE_PAN_BOTTOM_EDGE, CAC.SIMPLE_PAN_LEFT_EDGE, CAC.SIMPLE_PAN_RIGHT_EDGE, CAC.SIMPLE_PAN_VERTICAL_CENTER, CAC.SIMPLE_PAN_HORIZONTAL_CENTER, CAC.SIMPLE_ZOOM_IN, CAC.SIMPLE_ZOOM_OUT, CAC.SIMPLE_SWITCH_BETWEEN_100_PERCENT_AND_CANVAS_ZOOM, CAC.SIMPLE_FLIP_DARKMODE, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
+SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ CAC.SIMPLE_PAUSE_MEDIA, CAC.SIMPLE_PAUSE_PLAY_MEDIA, CAC.SIMPLE_MEDIA_SEEK_DELTA, CAC.SIMPLE_MOVE_ANIMATION_TO_PREVIOUS_FRAME, CAC.SIMPLE_MOVE_ANIMATION_TO_NEXT_FRAME, CAC.SIMPLE_SWITCH_BETWEEN_FULLSCREEN_BORDERLESS_AND_REGULAR_FRAMED_WINDOW, CAC.SIMPLE_PAN_UP, CAC.SIMPLE_PAN_DOWN, CAC.SIMPLE_PAN_LEFT, CAC.SIMPLE_PAN_RIGHT, CAC.SIMPLE_PAN_TOP_EDGE, CAC.SIMPLE_PAN_BOTTOM_EDGE, CAC.SIMPLE_PAN_LEFT_EDGE, CAC.SIMPLE_PAN_RIGHT_EDGE, CAC.SIMPLE_PAN_VERTICAL_CENTER, CAC.SIMPLE_PAN_HORIZONTAL_CENTER, CAC.SIMPLE_ZOOM_IN, CAC.SIMPLE_ZOOM_OUT, CAC.SIMPLE_SWITCH_BETWEEN_100_PERCENT_AND_CANVAS_ZOOM, CAC.SIMPLE_FLIP_DARKMODE, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS = [ CAC.SIMPLE_VIEW_NEXT, CAC.SIMPLE_VIEW_FIRST, CAC.SIMPLE_VIEW_LAST, CAC.SIMPLE_VIEW_PREVIOUS, CAC.SIMPLE_PAUSE_PLAY_SLIDESHOW, CAC.SIMPLE_SHOW_MENU, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
SHORTCUTS_MAIN_GUI_ACTIONS = [ CAC.SIMPLE_REFRESH, CAC.SIMPLE_REFRESH_ALL_PAGES, CAC.SIMPLE_REFRESH_PAGE_OF_PAGES_PAGES, CAC.SIMPLE_NEW_PAGE, CAC.SIMPLE_NEW_PAGE_OF_PAGES, CAC.SIMPLE_NEW_DUPLICATE_FILTER_PAGE, CAC.SIMPLE_NEW_GALLERY_DOWNLOADER_PAGE, CAC.SIMPLE_NEW_URL_DOWNLOADER_PAGE, CAC.SIMPLE_NEW_SIMPLE_DOWNLOADER_PAGE, CAC.SIMPLE_NEW_WATCHER_DOWNLOADER_PAGE, CAC.SIMPLE_SET_MEDIA_FOCUS, CAC.SIMPLE_SHOW_HIDE_SPLITTERS, CAC.SIMPLE_SET_SEARCH_FOCUS, CAC.SIMPLE_UNCLOSE_PAGE, CAC.SIMPLE_CLOSE_PAGE, CAC.SIMPLE_REDO, CAC.SIMPLE_UNDO, CAC.SIMPLE_FLIP_DARKMODE, CAC.SIMPLE_RUN_ALL_EXPORT_FOLDERS, CAC.SIMPLE_CHECK_ALL_IMPORT_FOLDERS, CAC.SIMPLE_FLIP_DEBUG_FORCE_IDLE_MODE_DO_NOT_SET_THIS, CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_FAVOURITE_TAGS, CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_RELATED_TAGS, CAC.SIMPLE_REFRESH_RELATED_TAGS, CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_FILE_LOOKUP_SCRIPT_TAGS, CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_RECENT_TAGS, CAC.SIMPLE_FOCUS_MEDIA_VIEWER, CAC.SIMPLE_MOVE_PAGES_SELECTION_LEFT, CAC.SIMPLE_MOVE_PAGES_SELECTION_RIGHT, CAC.SIMPLE_MOVE_PAGES_SELECTION_HOME, CAC.SIMPLE_MOVE_PAGES_SELECTION_END ]
SHORTCUTS_TAGS_AUTOCOMPLETE_ACTIONS = [ CAC.SIMPLE_SYNCHRONISED_WAIT_SWITCH, CAC.SIMPLE_AUTOCOMPLETE_FORCE_FETCH, CAC.SIMPLE_AUTOCOMPLETE_IME_MODE ]
SHORTCUTS_DUPLICATE_FILTER_ACTIONS = [ CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER, CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_BUT_KEEP_BOTH, CAC.SIMPLE_DUPLICATE_FILTER_EXACTLY_THE_SAME, CAC.SIMPLE_DUPLICATE_FILTER_ALTERNATES, CAC.SIMPLE_DUPLICATE_FILTER_FALSE_POSITIVE, CAC.SIMPLE_DUPLICATE_FILTER_CUSTOM_ACTION, CAC.SIMPLE_DUPLICATE_FILTER_SKIP, CAC.SIMPLE_DUPLICATE_FILTER_BACK, CAC.SIMPLE_CLOSE_MEDIA_VIEWER, CAC.SIMPLE_VIEW_NEXT ]
SHORTCUTS_ARCHIVE_DELETE_FILTER_ACTIONS = [ CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_DELETE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_SKIP, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
-SHORTCUTS_MEDIA_VIEWER_VIDEO_AUDIO_PLAYER_ACTIONS = [ CAC.SIMPLE_PAUSE_MEDIA, CAC.SIMPLE_PAUSE_PLAY_MEDIA, CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
-SHORTCUTS_PREVIEW_VIDEO_AUDIO_PLAYER_ACTIONS = [ CAC.SIMPLE_PAUSE_MEDIA, CAC.SIMPLE_PAUSE_PLAY_MEDIA, CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM, CAC.SIMPLE_LAUNCH_MEDIA_VIEWER ]
+SHORTCUTS_MEDIA_VIEWER_VIDEO_AUDIO_PLAYER_ACTIONS = [ CAC.SIMPLE_PAUSE_MEDIA, CAC.SIMPLE_PAUSE_PLAY_MEDIA, CAC.SIMPLE_MEDIA_SEEK_DELTA, CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM, CAC.SIMPLE_CLOSE_MEDIA_VIEWER ]
+SHORTCUTS_PREVIEW_VIDEO_AUDIO_PLAYER_ACTIONS = [ CAC.SIMPLE_PAUSE_MEDIA, CAC.SIMPLE_PAUSE_PLAY_MEDIA, CAC.SIMPLE_MEDIA_SEEK_DELTA, CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM, CAC.SIMPLE_LAUNCH_MEDIA_VIEWER ]
simple_shortcut_name_to_action_lookup = {}
@@ -965,7 +965,7 @@ class ShortcutSet( HydrusSerialisable.SerialisableBaseNamed ):
if serialisable_service_key is None:
- command = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, data )
+ command = CAC.ApplicationCommand.STATICCreateSimpleCommand( data )
else:
@@ -1029,13 +1029,13 @@ class ShortcutSet( HydrusSerialisable.SerialisableBaseNamed ):
- def GetShortcuts( self, simple_command: int ):
+ def GetShortcuts( self, simple_action: int ):
shortcuts = []
for ( shortcut, command ) in self._shortcuts_to_commands.items():
- if command.IsSimpleCommand() and command.GetData() == simple_command:
+ if command.IsSimpleCommand() and command.GetSimpleAction() == simple_action:
shortcuts.append( shortcut )
diff --git a/hydrus/client/gui/ClientGUISubscriptions.py b/hydrus/client/gui/ClientGUISubscriptions.py
index e6a3fa0a..658011c0 100644
--- a/hydrus/client/gui/ClientGUISubscriptions.py
+++ b/hydrus/client/gui/ClientGUISubscriptions.py
@@ -148,6 +148,7 @@ class EditSubscriptionPanel( ClientGUIScrolledPanels.EditPanel ):
#
( name, gug_key_and_name, query_headers, checker_options, initial_file_limit, periodic_file_limit, paused, file_import_options, tag_import_options, self._no_work_until, self._no_work_until_reason ) = subscription.ToTuple()
+ this_is_a_random_sample_sub = subscription.ThisIsARandomSampleSubscription()
self._query_panel = ClientGUICommon.StaticBox( self, 'site and queries' )
@@ -202,6 +203,9 @@ class EditSubscriptionPanel( ClientGUIScrolledPanels.EditPanel ):
self._periodic_file_limit = QP.MakeQSpinBox( self._file_limits_panel, min=1, max=limits_max )
self._periodic_file_limit.setToolTip( 'Normal syncs will add no more than this many URLs, stopping early if they find several URLs the query has seen before.' )
+ self._this_is_a_random_sample_sub = QW.QCheckBox( self._file_limits_panel )
+ self._this_is_a_random_sample_sub.setToolTip( 'If you check this, you will not get warnings if the normal file limit is hit. Useful if you have a randomly sorted gallery, or you just want a recurring small sample of files.' )
+
self._checker_options = ClientGUIImport.CheckerOptionsButton( self._file_limits_panel, checker_options, update_callable = self._CheckerOptionsUpdated )
self._file_presentation_panel = ClientGUICommon.StaticBox( self, 'presentation' )
@@ -245,6 +249,7 @@ class EditSubscriptionPanel( ClientGUIScrolledPanels.EditPanel ):
self._initial_file_limit.setValue( initial_file_limit )
self._periodic_file_limit.setValue( periodic_file_limit )
+ self._this_is_a_random_sample_sub.setChecked( this_is_a_random_sample_sub )
( show_a_popup_while_working, publish_files_to_popup_button, publish_files_to_page, publish_label_override, merge_query_publish_events ) = subscription.GetPresentationOptions()
@@ -267,6 +272,7 @@ class EditSubscriptionPanel( ClientGUIScrolledPanels.EditPanel ):
rows.append( ( 'on first check, get at most this many files: ', self._initial_file_limit ) )
rows.append( ( 'on normal checks, get at most this many newer files: ', self._periodic_file_limit ) )
+ rows.append( ( 'do not worry about subscription gaps: ', self._this_is_a_random_sample_sub ) )
gridbox = ClientGUICommon.WrapInGrid( self._file_limits_panel, rows )
@@ -1077,11 +1083,11 @@ class EditSubscriptionPanel( ClientGUIScrolledPanels.EditPanel ):
file_import_options = self._file_import_options.GetValue()
tag_import_options = self._tag_import_options.GetValue()
- query_headers = self._query_headers.GetData()
-
subscription.SetTuple( gug_key_and_name, checker_options, initial_file_limit, periodic_file_limit, paused, file_import_options, tag_import_options, self._no_work_until )
- subscription.SetQueryHeaders( query_headers )
+ subscription.SetThisIsARandomSampleSubscription( self._this_is_a_random_sample_sub.isChecked() )
+
+ subscription.SetQueryHeaders( self._query_headers.GetData() )
show_a_popup_while_working = self._show_a_popup_while_working.isChecked()
publish_files_to_popup_button = self._publish_files_to_popup_button.isChecked()
diff --git a/hydrus/client/gui/ClientGUITags.py b/hydrus/client/gui/ClientGUITags.py
index b249dc0c..72122f47 100644
--- a/hydrus/client/gui/ClientGUITags.py
+++ b/hydrus/client/gui/ClientGUITags.py
@@ -1992,11 +1992,9 @@ class ManageTagsPanel( ClientGUIScrolledPanels.ManagePanel ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_MANAGE_FILE_TAGS:
@@ -2692,11 +2690,9 @@ class ManageTagsPanel( ClientGUIScrolledPanels.ManagePanel ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_SET_SEARCH_FOCUS:
diff --git a/hydrus/client/gui/canvas/ClientGUICanvas.py b/hydrus/client/gui/canvas/ClientGUICanvas.py
index c0147473..bf496c13 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvas.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvas.py
@@ -784,7 +784,7 @@ class Canvas( QW.QWidget ):
child.activateWindow()
- command = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SET_SEARCH_FOCUS )
+ command = CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SET_SEARCH_FOCUS )
panel.ProcessApplicationCommand( command )
@@ -1036,6 +1036,16 @@ class Canvas( QW.QWidget ):
HG.client_controller.file_viewing_stats_manager.FinishViewing( viewtype, hash, viewtime_delta )
+ def _SeekDeltaCurrentMedia( self, direction, duration_ms ):
+
+ if self._current_media is None:
+
+ return
+
+
+ self._media_container.SeekDelta( direction, duration_ms )
+
+
def _ShowMediaInNewPage( self ):
if self._current_media is None:
@@ -1492,11 +1502,9 @@ class Canvas( QW.QWidget ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_MANAGE_FILE_RATINGS:
@@ -1603,6 +1611,12 @@ class Canvas( QW.QWidget ):
self._PausePlayCurrentMedia()
+ elif action == CAC.SIMPLE_MEDIA_SEEK_DELTA:
+
+ ( direction, ms ) = command.GetSimpleData()
+
+ self._SeekDeltaCurrentMedia( direction, ms )
+
elif action == CAC.SIMPLE_MOVE_ANIMATION_TO_PREVIOUS_FRAME:
self._media_container.GotoPreviousOrNextFrame( -1 )
@@ -2552,11 +2566,9 @@ class CanvasWithHovers( CanvasWithDetails ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_CLOSE_MEDIA_VIEWER:
@@ -3278,11 +3290,9 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER:
@@ -3893,11 +3903,9 @@ class CanvasMediaListFilterArchiveDelete( CanvasMediaList ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action in ( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_KEEP, CAC.SIMPLE_ARCHIVE_FILE ):
@@ -4005,11 +4013,9 @@ class CanvasMediaListNavigable( CanvasMediaList ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_REMOVE_FILE_FROM_VIEW:
@@ -4231,11 +4237,9 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_PAUSE_PLAY_SLIDESHOW:
diff --git a/hydrus/client/gui/canvas/ClientGUICanvasFrame.py b/hydrus/client/gui/canvas/ClientGUICanvasFrame.py
index 78cfc701..60606e30 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvasFrame.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvasFrame.py
@@ -90,11 +90,9 @@ class CanvasFrame( ClientGUITopLevelWindows.FrameThatResizesWithHovers ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_EXIT_APPLICATION:
diff --git a/hydrus/client/gui/canvas/ClientGUICanvasHoverFrames.py b/hydrus/client/gui/canvas/ClientGUICanvasHoverFrames.py
index b5a56cba..10c17d3b 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvasHoverFrames.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvasHoverFrames.py
@@ -544,15 +544,15 @@ class CanvasHoverFrameRightDuplicates( CanvasHoverFrame ):
close_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().stop, HG.client_controller.pub, 'canvas_close', self._canvas_key )
close_button.setToolTip( 'close filter' )
- self._back_a_pair = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().first, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_BACK ), self._canvas_key )
+ self._back_a_pair = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().first, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_BACK ), self._canvas_key )
self._back_a_pair.SetToolTipWithShortcuts( 'go back a pair', CAC.SIMPLE_DUPLICATE_FILTER_BACK )
self._index_text = ClientGUICommon.BetterStaticText( self, 'index' )
- self._next_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().pair, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_NEXT ), self._canvas_key )
+ self._next_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().pair, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_NEXT ), self._canvas_key )
self._next_button.SetToolTipWithShortcuts( 'next', CAC.SIMPLE_VIEW_NEXT )
- self._skip_a_pair = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().last, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_SKIP ), self._canvas_key )
+ self._skip_a_pair = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().last, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_SKIP ), self._canvas_key )
self._skip_a_pair.SetToolTipWithShortcuts( 'show a different pair', CAC.SIMPLE_DUPLICATE_FILTER_SKIP )
command_button_vbox = QP.VBoxLayout()
@@ -561,17 +561,17 @@ class CanvasHoverFrameRightDuplicates( CanvasHoverFrame ):
dupe_commands = []
- dupe_commands.append( ( 'this is better, and delete the other', 'Set that the current file you are looking at is better than the other in the pair, and set the other file to be deleted.', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER ) ) )
- dupe_commands.append( ( 'this is better, but keep both', 'Set that the current file you are looking at is better than the other in the pair, but keep both files.', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_BUT_KEEP_BOTH ) ) )
- dupe_commands.append( ( 'they are the same quality', 'Set that the two files are duplicates of very similar quality.', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_EXACTLY_THE_SAME ) ) )
+ dupe_commands.append( ( 'this is better, and delete the other', 'Set that the current file you are looking at is better than the other in the pair, and set the other file to be deleted.', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_AND_DELETE_OTHER ) ) )
+ dupe_commands.append( ( 'this is better, but keep both', 'Set that the current file you are looking at is better than the other in the pair, but keep both files.', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_THIS_IS_BETTER_BUT_KEEP_BOTH ) ) )
+ dupe_commands.append( ( 'they are the same quality', 'Set that the two files are duplicates of very similar quality.', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_EXACTLY_THE_SAME ) ) )
dupe_boxes.append( ( 'they are duplicates', dupe_commands ) )
dupe_commands = []
- dupe_commands.append( ( 'they are related alternates', 'Set that the files are not duplicates, but that one is derived from the other or that they are both descendants of a common ancestor.', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_ALTERNATES ) ) )
- dupe_commands.append( ( 'they are not related', 'Set that the files are not duplicates or otherwise related--that this potential pair is a false positive match.', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_FALSE_POSITIVE ) ) )
- dupe_commands.append( ( 'custom action', 'Choose one of the other actions but customise the merge and delete options for this specific decision.', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_CUSTOM_ACTION ) ) )
+ dupe_commands.append( ( 'they are related alternates', 'Set that the files are not duplicates, but that one is derived from the other or that they are both descendants of a common ancestor.', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_ALTERNATES ) ) )
+ dupe_commands.append( ( 'they are not related', 'Set that the files are not duplicates or otherwise related--that this potential pair is a false positive match.', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_FALSE_POSITIVE ) ) )
+ dupe_commands.append( ( 'custom action', 'Choose one of the other actions but customise the merge and delete options for this specific decision.', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_CUSTOM_ACTION ) ) )
dupe_boxes.append( ( 'other', dupe_commands ) )
@@ -583,7 +583,7 @@ class CanvasHoverFrameRightDuplicates( CanvasHoverFrame ):
command_button = ClientGUICommon.BetterButton( button_panel, label, HG.client_controller.pub, 'canvas_application_command', command, self._canvas_key )
- command_button.SetToolTipWithShortcuts( tooltip, command.GetData() )
+ command_button.SetToolTipWithShortcuts( tooltip, command.GetSimpleAction() )
button_panel.Add( command_button, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -827,11 +827,11 @@ class CanvasHoverFrameTop( CanvasHoverFrame ):
if self._current_media.HasInbox():
- command = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_FILE )
+ command = CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_FILE )
else:
- command = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_INBOX_FILE )
+ command = CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_INBOX_FILE )
HG.client_controller.pub( 'canvas_application_command', command, self._canvas_key )
@@ -894,13 +894,13 @@ class CanvasHoverFrameTop( CanvasHoverFrame ):
self._zoom_text = ClientGUICommon.BetterStaticText( self, 'zoom' )
- zoom_in = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().zoom_in, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ZOOM_IN_VIEWER_CENTER ), self._canvas_key )
+ zoom_in = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().zoom_in, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ZOOM_IN_VIEWER_CENTER ), self._canvas_key )
zoom_in.SetToolTipWithShortcuts( 'zoom in', CAC.SIMPLE_ZOOM_IN )
- zoom_out = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().zoom_out, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ZOOM_OUT_VIEWER_CENTER ), self._canvas_key )
+ zoom_out = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().zoom_out, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ZOOM_OUT_VIEWER_CENTER ), self._canvas_key )
zoom_out.SetToolTipWithShortcuts( 'zoom out', CAC.SIMPLE_ZOOM_OUT )
- zoom_switch = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().zoom_switch, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_SWITCH_BETWEEN_100_PERCENT_AND_CANVAS_ZOOM_VIEWER_CENTER ), self._canvas_key )
+ zoom_switch = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().zoom_switch, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SWITCH_BETWEEN_100_PERCENT_AND_CANVAS_ZOOM_VIEWER_CENTER ), self._canvas_key )
zoom_switch.SetToolTipWithShortcuts( 'zoom switch', CAC.SIMPLE_SWITCH_BETWEEN_100_PERCENT_AND_CANVAS_ZOOM )
self._volume_control = ClientGUIMediaControls.VolumeControl( self, ClientGUICommon.CANVAS_MEDIA_VIEWER )
@@ -921,7 +921,7 @@ class CanvasHoverFrameTop( CanvasHoverFrame ):
fullscreen_switch.hide()
- open_externally = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().open_externally, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM ), self._canvas_key )
+ open_externally = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().open_externally, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM ), self._canvas_key )
open_externally.SetToolTipWithShortcuts( 'open externally', CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM )
drag_button = QW.QPushButton( self )
@@ -1086,7 +1086,7 @@ class CanvasHoverFrameTop( CanvasHoverFrame ):
if result != QC.Qt.IgnoreAction:
- HG.client_controller.pub( 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_PAUSE_MEDIA ), self._canvas_key )
+ HG.client_controller.pub( 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_PAUSE_MEDIA ), self._canvas_key )
@@ -1172,19 +1172,19 @@ class CanvasHoverFrameTopArchiveDeleteFilter( CanvasHoverFrameTop ):
def _Archive( self ):
- HG.client_controller.pub( 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_FILE ), self._canvas_key )
+ HG.client_controller.pub( 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_FILE ), self._canvas_key )
def _PopulateLeftButtons( self ):
- self._back_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().previous, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK ), self._canvas_key )
+ self._back_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().previous, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK ), self._canvas_key )
self._back_button.SetToolTipWithShortcuts( 'back', CAC.SIMPLE_ARCHIVE_DELETE_FILTER_BACK )
QP.AddToLayout( self._top_hbox, self._back_button, CC.FLAGS_CENTER_PERPENDICULAR )
CanvasHoverFrameTop._PopulateLeftButtons( self )
- self._skip_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().next_bmp, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_DELETE_FILTER_SKIP ), self._canvas_key )
+ self._skip_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().next_bmp, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_DELETE_FILTER_SKIP ), self._canvas_key )
self._skip_button.SetToolTipWithShortcuts( 'skip', CAC.SIMPLE_ARCHIVE_DELETE_FILTER_SKIP )
QP.AddToLayout( self._top_hbox, self._skip_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1200,12 +1200,12 @@ class CanvasHoverFrameTopNavigable( CanvasHoverFrameTop ):
def _PopulateLeftButtons( self ):
- self._previous_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().previous, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_PREVIOUS ), self._canvas_key )
+ self._previous_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().previous, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_PREVIOUS ), self._canvas_key )
self._previous_button.SetToolTipWithShortcuts( 'previous', CAC.SIMPLE_VIEW_PREVIOUS )
self._index_text = ClientGUICommon.BetterStaticText( self, 'index' )
- self._next_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().next_bmp, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_NEXT ), self._canvas_key )
+ self._next_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().next_bmp, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_NEXT ), self._canvas_key )
self._next_button.SetToolTipWithShortcuts( 'next', CAC.SIMPLE_VIEW_NEXT )
QP.AddToLayout( self._top_hbox, self._previous_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1217,14 +1217,14 @@ class CanvasHoverFrameTopDuplicatesFilter( CanvasHoverFrameTopNavigable ):
def _PopulateLeftButtons( self ):
- self._first_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().first, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_BACK ), self._canvas_key )
+ self._first_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().first, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_BACK ), self._canvas_key )
self._first_button.SetToolTipWithShortcuts( 'go back a pair', CAC.SIMPLE_DUPLICATE_FILTER_BACK )
QP.AddToLayout( self._top_hbox, self._first_button, CC.FLAGS_CENTER_PERPENDICULAR )
CanvasHoverFrameTopNavigable._PopulateLeftButtons( self )
- self._last_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().last, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_FILTER_SKIP ), self._canvas_key )
+ self._last_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().last, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_FILTER_SKIP ), self._canvas_key )
self._last_button.SetToolTipWithShortcuts( 'show a different pair', CAC.SIMPLE_DUPLICATE_FILTER_SKIP )
QP.AddToLayout( self._top_hbox, self._last_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1234,14 +1234,14 @@ class CanvasHoverFrameTopNavigableList( CanvasHoverFrameTopNavigable ):
def _PopulateLeftButtons( self ):
- self._first_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().first, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_FIRST ), self._canvas_key )
+ self._first_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().first, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_FIRST ), self._canvas_key )
self._first_button.SetToolTipWithShortcuts( 'first', CAC.SIMPLE_VIEW_FIRST )
QP.AddToLayout( self._top_hbox, self._first_button, CC.FLAGS_CENTER_PERPENDICULAR )
CanvasHoverFrameTopNavigable._PopulateLeftButtons( self )
- self._last_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().last, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_VIEW_LAST ), self._canvas_key )
+ self._last_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().last, HG.client_controller.pub, 'canvas_application_command', CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_VIEW_LAST ), self._canvas_key )
self._last_button.SetToolTipWithShortcuts( 'last', CAC.SIMPLE_VIEW_LAST )
QP.AddToLayout( self._top_hbox, self._last_button, CC.FLAGS_CENTER_PERPENDICULAR )
@@ -1333,11 +1333,11 @@ class CanvasHoverFrameTopRight( CanvasHoverFrame ):
if self._current_media.HasInbox():
- command = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_FILE )
+ command = CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_FILE )
else:
- command = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_INBOX_FILE )
+ command = CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_INBOX_FILE )
HG.client_controller.pub( 'canvas_application_command', command, self._canvas_key )
diff --git a/hydrus/client/gui/canvas/ClientGUICanvasMedia.py b/hydrus/client/gui/canvas/ClientGUICanvasMedia.py
index 8498cf8d..8b1e5ec5 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvasMedia.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvasMedia.py
@@ -211,7 +211,7 @@ class Animation( QW.QWidget ):
return ( self._current_frame_index, self._current_timestamp_ms, self._paused, buffer_indices )
- def GotoFrame( self, frame_index ):
+ def GotoFrame( self, frame_index, pause_afterwards = True ):
if self._video_container is not None and self._video_container.IsInitialised():
@@ -227,7 +227,30 @@ class Animation( QW.QWidget ):
self._current_frame_drawn = False
- self._paused = True
+ if pause_afterwards:
+
+ self._paused = True
+
+
+
+
+ def GotoTimestamp( self, timestamp_ms, round_direction, pause_afterwards = True ):
+
+ if self._video_container is not None and self._video_container.IsInitialised():
+
+ frame_index = self._video_container.GetFrameIndex( timestamp_ms )
+
+ if frame_index == self._current_frame_index:
+
+ frame_index += round_direction
+
+
+ if frame_index > self._media.GetNumFrames() - 1:
+
+ frame_index = 0
+
+
+ self.GotoFrame( frame_index, pause_afterwards = pause_afterwards )
@@ -279,11 +302,9 @@ class Animation( QW.QWidget ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_PAUSE_MEDIA:
@@ -293,6 +314,12 @@ class Animation( QW.QWidget ):
self.PausePlay()
+ elif action == CAC.SIMPLE_MEDIA_SEEK_DELTA:
+
+ ( direction, duration_ms ) = command.GetSimpleData()
+
+ self.SeekDelta( direction, duration_ms )
+
elif action == CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM:
if self._media is not None:
@@ -379,6 +406,16 @@ class Animation( QW.QWidget ):
+ def SeekDelta( self, direction, duration_ms ):
+
+ if self._video_container is not None and self._video_container.IsInitialised():
+
+ new_ts = self._current_timestamp_ms + ( direction * duration_ms )
+
+ self.GotoTimestamp( new_ts, direction, pause_afterwards = False )
+
+
+
def StopForSlideshow( self, value ):
self._stop_for_slideshow = value
@@ -1335,6 +1372,17 @@ class MediaContainer( QW.QWidget ):
+ def SeekDelta( self, direction, duration_ms ):
+
+ if self._media is not None:
+
+ if isinstance( self._media_window, ( Animation, ClientGUIMPV.mpvWidget ) ):
+
+ self._media_window.SeekDelta( direction, duration_ms )
+
+
+
+
def SetEmbedButton( self ):
self._HideAnimationBar()
@@ -1869,11 +1917,9 @@ class StaticImage( QW.QWidget ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM:
diff --git a/hydrus/client/gui/pages/ClientGUIResults.py b/hydrus/client/gui/pages/ClientGUIResults.py
index 3396cdc9..ce3c75db 100644
--- a/hydrus/client/gui/pages/ClientGUIResults.py
+++ b/hydrus/client/gui/pages/ClientGUIResults.py
@@ -1681,13 +1681,13 @@ class MediaPanel( ClientMedia.ListeningMediaList, QW.QScrollArea ):
- def _ShowDuplicatesInNewPage( self, hash, duplicate_type ):
+ def _ShowDuplicatesInNewPage( self, file_service_key, hash, duplicate_type ):
- hashes = HG.client_controller.Read( 'file_duplicate_hashes', self._file_service_key, hash, duplicate_type )
+ hashes = HG.client_controller.Read( 'file_duplicate_hashes', file_service_key, hash, duplicate_type )
if hashes is not None and len( hashes ) > 0:
- HG.client_controller.pub( 'new_page_query', self._file_service_key, initial_hashes = hashes )
+ HG.client_controller.pub( 'new_page_query', file_service_key, initial_hashes = hashes )
@@ -1843,11 +1843,9 @@ class MediaPanel( ClientMedia.ListeningMediaList, QW.QScrollArea ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_COPY_BMP:
@@ -3703,6 +3701,15 @@ class MediaPanelThumbnails( MediaPanel ):
file_duplicate_info = HG.client_controller.Read( 'file_duplicate_info', self._file_service_key, focused_hash )
+ if HG.client_controller.services_manager.GetService( self._file_service_key ).GetServiceType() in ( HC.LOCAL_FILE_DOMAIN, HC.LOCAL_FILE_TRASH_DOMAIN ):
+
+ all_local_files_file_duplicate_info = HG.client_controller.Read( 'file_duplicate_info', CC.COMBINED_LOCAL_FILE_SERVICE_KEY, focused_hash )
+
+ else:
+
+ all_local_files_file_duplicate_info = None
+
+
focus_is_in_duplicate_group = False
focus_is_in_alternate_group = False
@@ -3716,21 +3723,33 @@ class MediaPanelThumbnails( MediaPanel ):
else:
- file_duplicate_types_to_counts = file_duplicate_info[ 'counts' ]
+ view_duplicate_relations_jobs = []
- if len( file_duplicate_types_to_counts ) > 0:
+ if len( file_duplicate_info[ 'counts' ] ) > 0:
+
+ view_duplicate_relations_jobs.append( ( self._file_service_key, file_duplicate_info ) )
+
+
+ if all_local_files_file_duplicate_info is not None and len( all_local_files_file_duplicate_info[ 'counts' ] ) > 0 and all_local_files_file_duplicate_info != file_duplicate_info:
+
+ view_duplicate_relations_jobs.append( ( CC.COMBINED_LOCAL_FILE_SERVICE_KEY, all_local_files_file_duplicate_info ) )
+
+
+ for ( duplicates_file_service_key, this_file_duplicate_info ) in view_duplicate_relations_jobs:
+
+ file_duplicate_types_to_counts = this_file_duplicate_info[ 'counts' ]
duplicates_view_menu = QW.QMenu( duplicates_menu )
if HC.DUPLICATE_MEMBER in file_duplicate_types_to_counts:
- if file_duplicate_info[ 'is_king' ]:
+ if this_file_duplicate_info[ 'is_king' ]:
ClientGUIMenus.AppendMenuLabel( duplicates_view_menu, 'this is the best quality file of its group' )
else:
- ClientGUIMenus.AppendMenuItem( duplicates_view_menu, 'show the best quality file of this file\'s group', 'Load up a new search with this file\'s best quality duplicate.', self._ShowDuplicatesInNewPage, focused_hash, HC.DUPLICATE_KING )
+ ClientGUIMenus.AppendMenuItem( duplicates_view_menu, 'show the best quality file of this file\'s group', 'Load up a new search with this file\'s best quality duplicate.', self._ShowDuplicatesInNewPage, duplicates_file_service_key, focused_hash, HC.DUPLICATE_KING )
ClientGUIMenus.AppendSeparator( duplicates_view_menu )
@@ -3746,7 +3765,7 @@ class MediaPanelThumbnails( MediaPanel ):
label = HydrusData.ToHumanInt( count ) + ' ' + HC.duplicate_type_string_lookup[ duplicate_type ]
- ClientGUIMenus.AppendMenuItem( duplicates_view_menu, label, 'Show these duplicates in a new page.', self._ShowDuplicatesInNewPage, focused_hash, duplicate_type )
+ ClientGUIMenus.AppendMenuItem( duplicates_view_menu, label, 'Show these duplicates in a new page.', self._ShowDuplicatesInNewPage, duplicates_file_service_key, focused_hash, duplicate_type )
if duplicate_type == HC.DUPLICATE_MEMBER:
@@ -3768,7 +3787,14 @@ class MediaPanelThumbnails( MediaPanel ):
- ClientGUIMenus.AppendMenu( duplicates_menu, duplicates_view_menu, 'view this file\'s relations' )
+ label = 'view this file\'s relations'
+
+ if duplicates_file_service_key != self._file_service_key:
+
+ label = '{} ({})'.format( label, HG.client_controller.services_manager.GetName( duplicates_file_service_key ) )
+
+
+ ClientGUIMenus.AppendMenu( duplicates_menu, duplicates_view_menu, label )
@@ -3790,7 +3816,7 @@ class MediaPanelThumbnails( MediaPanel ):
if not focus_is_definitely_king:
- ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set this file as the best quality of its group', 'Set the focused media to be the King of its group.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_FOCUSED_KING ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set this file as the best quality of its group', 'Set the focused media to be the King of its group.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_FOCUSED_KING ) )
@@ -3800,25 +3826,25 @@ class MediaPanelThumbnails( MediaPanel ):
label = 'set this file as better than the ' + HydrusData.ToHumanInt( num_selected - 1 ) + ' other selected'
- ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, label, 'Set the focused media to be better than the other selected files.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_FOCUSED_BETTER ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, label, 'Set the focused media to be better than the other selected files.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_FOCUSED_BETTER ) )
num_pairs = num_selected * ( num_selected - 1 ) / 2 # com // ations -- n!/2(n-2)!
num_pairs_text = HydrusData.ToHumanInt( num_pairs ) + ' pairs'
- ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set all selected as same quality duplicates', 'Set all the selected files as same quality duplicates.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_SAME_QUALITY ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set all selected as same quality duplicates', 'Set all the selected files as same quality duplicates.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_SAME_QUALITY ) )
ClientGUIMenus.AppendSeparator( duplicates_action_submenu )
- ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set all selected as alternates', 'Set all the selected files as alternates.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_ALTERNATE ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set all selected as alternates', 'Set all the selected files as alternates.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_ALTERNATE ) )
- ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set a relationship with custom metadata merge options', 'Choose which duplicates status to set to this selection and customise non-default duplicate metadata merge options.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_CUSTOM ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set a relationship with custom metadata merge options', 'Choose which duplicates status to set to this selection and customise non-default duplicate metadata merge options.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_CUSTOM ) )
if collections_selected:
ClientGUIMenus.AppendSeparator( duplicates_action_submenu )
- ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set selected collections as groups of alternates', 'Set files in the selection which are collected together as alternates.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_ALTERNATE_COLLECTIONS ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set selected collections as groups of alternates', 'Set files in the selection which are collected together as alternates.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_ALTERNATE_COLLECTIONS ) )
#
@@ -3843,7 +3869,7 @@ class MediaPanelThumbnails( MediaPanel ):
ClientGUIMenus.AppendSeparator( duplicates_action_submenu )
- ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set all possible pair combinations as \'potential\' duplicates for the duplicates filter.', 'Queue all these files up in the duplicates filter.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_SET_POTENTIAL ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, 'set all possible pair combinations as \'potential\' duplicates for the duplicates filter.', 'Queue all these files up in the duplicates filter.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_POTENTIAL ) )
if dissolution_actions_available:
@@ -3854,34 +3880,34 @@ class MediaPanelThumbnails( MediaPanel ):
if focus_can_be_searched:
- ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'schedule this file to be searched for potentials again', 'Queue this file for another potentials search. Will not remove any existing potentials.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_RESET_FOCUSED_POTENTIAL_SEARCH ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'schedule this file to be searched for potentials again', 'Queue this file for another potentials search. Will not remove any existing potentials.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_RESET_FOCUSED_POTENTIAL_SEARCH ) )
if focus_has_potentials:
- ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'remove this file\'s potential relationships', 'Clear out this file\'s potential relationships.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_FOCUSED_POTENTIALS ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'remove this file\'s potential relationships', 'Clear out this file\'s potential relationships.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_FOCUSED_POTENTIALS ) )
if focus_is_in_duplicate_group:
if not focus_is_definitely_king:
- ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'remove this file from its duplicate group', 'Extract this file from its duplicate group and reset its search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_FOCUSED_FROM_DUPLICATE_GROUP ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'remove this file from its duplicate group', 'Extract this file from its duplicate group and reset its search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_FOCUSED_FROM_DUPLICATE_GROUP ) )
- ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'dissolve this file\'s duplicate group completely', 'Completely eliminate this file\'s duplicate group and reset all files\' search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_FOCUSED_DUPLICATE_GROUP ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'dissolve this file\'s duplicate group completely', 'Completely eliminate this file\'s duplicate group and reset all files\' search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_FOCUSED_DUPLICATE_GROUP ) )
if focus_is_in_alternate_group:
- ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'remove this file from its alternate group', 'Extract this file\'s duplicate group from its alternate group and reset the duplicate group\'s search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_FOCUSED_FROM_ALTERNATE_GROUP ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'remove this file from its alternate group', 'Extract this file\'s duplicate group from its alternate group and reset the duplicate group\'s search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_FOCUSED_FROM_ALTERNATE_GROUP ) )
- ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'dissolve this file\'s alternate group completely', 'Completely eliminate this file\'s alternate group and all duplicate group members. This resets search status for all involved files.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_FOCUSED_ALTERNATE_GROUP ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'dissolve this file\'s alternate group completely', 'Completely eliminate this file\'s alternate group and all duplicate group members. This resets search status for all involved files.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_FOCUSED_ALTERNATE_GROUP ) )
if focus_has_fps:
- ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'delete all false-positive relationships this file\'s alternate group has with other groups', 'Clear out all false-positive relationships this file\'s alternates group has with other groups and resets search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_CLEAR_FOCUSED_FALSE_POSITIVES ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_single_dissolution_menu, 'delete all false-positive relationships this file\'s alternate group has with other groups', 'Clear out all false-positive relationships this file\'s alternates group has with other groups and resets search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_CLEAR_FOCUSED_FALSE_POSITIVES ) )
ClientGUIMenus.AppendMenu( duplicates_action_submenu, duplicates_single_dissolution_menu, 'remove/reset for this file' )
@@ -3893,11 +3919,11 @@ class MediaPanelThumbnails( MediaPanel ):
duplicates_multiple_dissolution_menu = QW.QMenu( duplicates_action_submenu )
- ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'schedule these files to be searched for potentials again', 'Queue these files for another potentials search. Will not remove any existing potentials.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_RESET_POTENTIAL_SEARCH ) )
- ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'remove these files\' potential relationships', 'Clear out these files\' potential relationships.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_POTENTIALS ) )
- ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'dissolve these files\' duplicate groups completely', 'Completely eliminate these files\' duplicate groups and reset all files\' search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_DUPLICATE_GROUP ) )
- ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'dissolve these files\' alternate groups completely', 'Completely eliminate these files\' alternate groups and all duplicate group members. This resets search status for all involved files.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_ALTERNATE_GROUP ) )
- ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'delete all false-positive relationships these files\' alternate groups have with other groups', 'Clear out all false-positive relationships these files\' alternates groups has with other groups and resets search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_DUPLICATE_MEDIA_CLEAR_FALSE_POSITIVES ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'schedule these files to be searched for potentials again', 'Queue these files for another potentials search. Will not remove any existing potentials.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_RESET_POTENTIAL_SEARCH ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'remove these files\' potential relationships', 'Clear out these files\' potential relationships.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_REMOVE_POTENTIALS ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'dissolve these files\' duplicate groups completely', 'Completely eliminate these files\' duplicate groups and reset all files\' search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_DUPLICATE_GROUP ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'dissolve these files\' alternate groups completely', 'Completely eliminate these files\' alternate groups and all duplicate group members. This resets search status for all involved files.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_DISSOLVE_ALTERNATE_GROUP ) )
+ ClientGUIMenus.AppendMenuItem( duplicates_multiple_dissolution_menu, 'delete all false-positive relationships these files\' alternate groups have with other groups', 'Clear out all false-positive relationships these files\' alternates groups has with other groups and resets search status.', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_CLEAR_FALSE_POSITIVES ) )
ClientGUIMenus.AppendMenu( duplicates_action_submenu, duplicates_multiple_dissolution_menu, 'remove/reset for all selected' )
diff --git a/hydrus/client/gui/search/ClientGUIACDropdown.py b/hydrus/client/gui/search/ClientGUIACDropdown.py
index 2be4a5f9..093837df 100644
--- a/hydrus/client/gui/search/ClientGUIACDropdown.py
+++ b/hydrus/client/gui/search/ClientGUIACDropdown.py
@@ -1263,11 +1263,9 @@ class AutoCompleteDropdown( QW.QWidget ):
command_processed = True
- data = command.GetData()
-
if command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_AUTOCOMPLETE_IME_MODE:
@@ -2148,11 +2146,9 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
command_processed = True
- data = command.GetData()
-
if self._can_intercept_unusual_key_events and command.IsSimpleCommand():
- action = data
+ action = command.GetSimpleAction()
if action == CAC.SIMPLE_SYNCHRONISED_WAIT_SWITCH:
diff --git a/hydrus/client/gui/search/ClientGUIPredicatesSingle.py b/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
index 0d5e5fbc..5c3eecff 100644
--- a/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
+++ b/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
@@ -1800,7 +1800,7 @@ class PanelPredicateSystemTagAsNumber( PanelPredicateSystemSingle ):
self._sign = QP.RadioBox( self, choices = choices )
- self._num = QP.MakeQSpinBox( self, min=-99999999, max=99999999 )
+ self._num = QP.MakeQSpinBox( self, min=-(2**31), max=(2**31)-1 )
#
diff --git a/hydrus/client/importing/ClientImportSubscriptions.py b/hydrus/client/importing/ClientImportSubscriptions.py
index cb4eb7bb..1d76b4b1 100644
--- a/hydrus/client/importing/ClientImportSubscriptions.py
+++ b/hydrus/client/importing/ClientImportSubscriptions.py
@@ -28,7 +28,7 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION
SERIALISABLE_NAME = 'Subscription'
- SERIALISABLE_VERSION = 1
+ SERIALISABLE_VERSION = 2
def __init__( self, name, gug_key_and_name = None ):
@@ -57,6 +57,9 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
self._periodic_file_limit = 100
+
+ self._this_is_a_random_sample_sub = False
+
self._paused = False
self._file_import_options = new_options.GetDefaultFileImportOptions( 'quiet' )
@@ -168,6 +171,7 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
serialisable_checker_options,
self._initial_file_limit,
self._periodic_file_limit,
+ self._this_is_a_random_sample_sub,
self._paused,
serialisable_file_import_options,
serialisable_tag_import_options,
@@ -189,6 +193,7 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
serialisable_checker_options,
self._initial_file_limit,
self._periodic_file_limit,
+ self._this_is_a_random_sample_sub,
self._paused,
serialisable_file_import_options,
serialisable_tag_import_options,
@@ -233,6 +238,557 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
HG.client_controller.pub( 'message', job_key )
+ def _SyncQueries( self, job_key ):
+
+ self._have_made_an_initial_sync_bandwidth_notification = False
+
+ gug = HG.client_controller.network_engine.domain_manager.GetGUG( self._gug_key_and_name )
+
+ if gug is None:
+
+ self._paused = True
+
+ HydrusData.ShowText( 'The subscription "{}" could not find a Gallery URL Generator for "{}"! The sub has paused!'.format( self._name, self._gug_key_and_name[1] ) )
+
+ return
+
+
+ try:
+
+ gug.CheckFunctional()
+
+ except HydrusExceptions.ParseException as e:
+
+ self._paused = True
+
+ message = 'The subscription "{}"\'s Gallery URL Generator, "{}" seems not to be functional! The sub has paused! The given reason was:'.format( self._name, self._gug_key_and_name[1] )
+ message += os.linesep * 2
+ message += str( e )
+
+ HydrusData.ShowText( message )
+
+ return
+
+
+ self._gug_key_and_name = gug.GetGUGKeyAndName() # just a refresher, to keep up with any changes
+
+ query_headers = self._GetQueryHeadersForProcessing()
+
+ query_headers = [ query_header for query_header in query_headers if query_header.IsSyncDue() ]
+
+ num_queries = len( query_headers )
+
+ for ( i, query_header ) in enumerate( query_headers ):
+
+ status_prefix = 'synchronising'
+
+ query_name = query_header.GetHumanName()
+
+ if query_name != self._name:
+
+ status_prefix += ' "' + query_name + '"'
+
+
+ status_prefix += ' (' + HydrusData.ConvertValueRangeToPrettyString( i + 1, num_queries ) + ')'
+
+ try:
+
+ query_log_container = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER, query_header.GetQueryLogContainerName() )
+
+ except HydrusExceptions.DBException as e:
+
+ if isinstance( e.db_e, HydrusExceptions.DataMissing ):
+
+ self._DealWithMissingQueryLogContainerError( query_header )
+
+ break
+
+ else:
+
+ raise
+
+
+
+ try:
+
+ self._SyncQuery( job_key, gug, query_header, query_log_container, status_prefix )
+
+ except HydrusExceptions.CancelledException:
+
+ break
+
+ finally:
+
+ HG.client_controller.WriteSynchronous( 'serialisable', query_log_container )
+
+
+
+
+ def _SyncQueriesCanDoWork( self ):
+
+ result = True in ( query_header.IsSyncDue() for query_header in self._query_headers )
+
+ if HG.subscription_report_mode:
+
+ HydrusData.ShowText( 'Subscription "{}" checking if any sync work due: {}'.format( self._name, result ) )
+
+
+ return result
+
+
+ def _SyncQuery(
+ self,
+ job_key: ClientThreading.JobKey,
+ gug: ClientNetworkingDomain.GalleryURLGenerator, # not actually correct for an ngug, but _whatever_
+ query_header: ClientImportSubscriptionQuery.SubscriptionQueryHeader,
+ query_log_container: ClientImportSubscriptionQuery.SubscriptionQueryLogContainer,
+ status_prefix: str
+ ):
+
+ query_text = query_header.GetQueryText()
+ query_name = query_header.GetHumanName()
+
+ file_seed_cache = query_log_container.GetFileSeedCache()
+ gallery_seed_log = query_log_container.GetGallerySeedLog()
+
+ this_is_initial_sync = query_header.IsInitialSync()
+ num_master_file_seeds_at_start = file_seed_cache.GetApproxNumMasterFileSeeds()
+ total_new_urls_for_this_sync = 0
+ total_already_in_urls_for_this_sync = 0
+
+ gallery_urls_seen_this_sync = set()
+
+ if this_is_initial_sync:
+
+ file_limit_for_this_sync = self._initial_file_limit
+
+ else:
+
+ file_limit_for_this_sync = self._periodic_file_limit
+
+
+ file_seeds_to_add_in_this_sync = set()
+ file_seeds_to_add_in_this_sync_ordered = []
+
+ stop_reason = 'unknown stop reason'
+
+ job_key.SetVariable( 'popup_text_1', status_prefix )
+
+ initial_search_urls = gug.GenerateGalleryURLs( query_text )
+
+ if len( initial_search_urls ) == 0:
+
+ self._paused = True
+
+ HydrusData.ShowText( 'The subscription "' + self._name + '"\'s Gallery URL Generator, "' + self._gug_key_and_name[1] + '" did not generate any URLs! The sub has paused!' )
+
+ raise HydrusExceptions.CancelledException( 'Bad GUG.' )
+
+
+ gallery_seeds = [ ClientImportGallerySeeds.GallerySeed( url, can_generate_more_pages = True ) for url in initial_search_urls ]
+
+ gallery_seed_log.AddGallerySeeds( gallery_seeds )
+
+ try:
+
+ while gallery_seed_log.WorkToDo():
+
+ p1 = not self._CanDoWorkNow()
+ ( login_ok, login_reason ) = query_header.GalleryLoginOK( HG.client_controller.network_engine, self._name )
+
+ if p1 or not login_ok:
+
+ if not login_ok:
+
+ if not self._paused:
+
+ message = 'Query "{}" for subscription "{}" seemed to have an invalid login. The reason was:'.format( query_header.GetHumanName(), self._name )
+ message += os.linesep * 2
+ message += login_reason
+ message += os.linesep * 2
+ message += 'The subscription has paused. Please see if you can fix the problem and then unpause. Hydrus dev would like feedback on this process.'
+
+ HydrusData.ShowText( message )
+
+ self._DelayWork( 300, login_reason )
+
+ self._paused = True
+
+
+
+ raise HydrusExceptions.CancelledException( 'A problem, so stopping.' )
+
+
+ if job_key.IsCancelled():
+
+ stop_reason = 'gallery parsing cancelled, likely by user'
+
+ self._DelayWork( 600, stop_reason )
+
+ raise HydrusExceptions.CancelledException( 'User cancelled.' )
+
+
+ gallery_seed = gallery_seed_log.GetNextGallerySeed( CC.STATUS_UNKNOWN )
+
+ if gallery_seed is None:
+
+ stop_reason = 'thought there was a page to check, but apparently there was not!'
+
+ break
+
+
+ def status_hook( text ):
+
+ if len( text ) > 0:
+
+ text = text.splitlines()[0]
+
+
+ job_key.SetVariable( 'popup_text_1', status_prefix + ': ' + text )
+
+
+ def title_hook( text ):
+
+ pass
+
+
+ def file_seeds_callable( gallery_page_of_file_seeds ):
+
+ num_urls_added = 0
+ num_urls_already_in_file_seed_cache = 0
+ can_search_for_more_files = True
+ stop_reason = 'unknown stop reason'
+ current_contiguous_num_urls_already_in_file_seed_cache = 0
+ num_file_seeds_in_this_page = len( gallery_page_of_file_seeds )
+
+ for file_seed in gallery_page_of_file_seeds:
+
+ if file_seed in file_seeds_to_add_in_this_sync:
+
+ # this catches the occasional overflow when a new file is uploaded while gallery parsing is going on
+ # we don't want to count these 'seen before this run' urls in the 'caught up to last time' count
+
+ continue
+
+
+ # When are we caught up? This is not a trivial problem. Tags are not always added when files are uploaded, so the order we find files is not completely reliable.
+ # Ideally, we want to search a _bit_ deeper than the first already-seen.
+ # And since we have a page of urls here and now, there is no point breaking early if there might be some new ones at the end.
+ # Current rule is "We are caught up if the final X contiguous files are 'already in'". X is 5 for now.
+
+ if file_seed_cache.HasFileSeed( file_seed ):
+
+ num_urls_already_in_file_seed_cache += 1
+ current_contiguous_num_urls_already_in_file_seed_cache += 1
+
+ if current_contiguous_num_urls_already_in_file_seed_cache >= 100:
+
+ can_search_for_more_files = False
+ stop_reason = 'saw 100 previously seen urls in a row, so assuming this is a large gallery'
+
+ break
+
+
+ else:
+
+ num_urls_added += 1
+ current_contiguous_num_urls_already_in_file_seed_cache = 0
+
+ file_seeds_to_add_in_this_sync.add( file_seed )
+ file_seeds_to_add_in_this_sync_ordered.append( file_seed )
+
+
+ if file_limit_for_this_sync is not None:
+
+ if total_new_urls_for_this_sync + num_urls_added >= file_limit_for_this_sync:
+
+ # we have found enough new files this sync, so should stop adding files and new gallery pages
+
+ if this_is_initial_sync:
+
+ stop_reason = 'hit initial file limit'
+
+ else:
+
+ if total_already_in_urls_for_this_sync + num_urls_already_in_file_seed_cache > 0:
+
+ # this sync produced some knowns, so it is likely we have stepped through a mix of old and tagged-late new files
+ # this is no reason to go crying to the user
+
+ stop_reason = 'hit periodic file limit after seeing several already-seen files'
+
+ else:
+
+ # this page had all entirely new files
+
+ if self._this_is_a_random_sample_sub:
+
+ stop_reason = 'hit periodic file limit'
+
+ else:
+
+ self._ShowHitPeriodicFileLimitMessage( query_name, query_text, file_limit_for_this_sync )
+
+ stop_reason = 'hit periodic file limit without seeing any already-seen files!'
+
+
+
+
+ can_search_for_more_files = False
+
+ break
+
+
+
+ if not this_is_initial_sync:
+
+ # ok, there are a couple of situations where we don't want to go steamroll past a certain point:
+
+ # if the user set 5 initial file limit but 100 periodic limit, then on the first few syncs, we'll want to notice that situation and not steamroll through that first five (or ~seven on third sync)
+ # if 'X' is new and get, 'A' is already in, and '-' is new and don't get, the page should be:
+ # XXXAAAAA----------------------------------
+
+ # the pixiv situation, where a single gallery page may have hundreds of results (and/or multi-file results that will pad out the file cache with more items)
+ # super large gallery pages interfere with the compaction system, adding results that were removed again and making WE_HIT_OLD_GROUND_THRESHOLD test not work correct
+ # similar to above:
+ # XXXXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ # AAAAAAAAAAAAAAAAAAAA-----------------------------------
+ # -------------------------------------------------------
+ # ----------------------
+
+ # I had specific logic targeting both these cases, but in making those, I make the num_master_file_seeds_at_start, which is actually the better thing to test
+ # If the sub has seen basically everything it started with, we are by definition caught up and should stop immediately!
+
+ excuse_the_odd_deleted_file_coefficient = 0.95
+
+ # we found all the 'A's
+ we_have_seen_everything_we_already_got = total_already_in_urls_for_this_sync + num_urls_already_in_file_seed_cache >= num_master_file_seeds_at_start * excuse_the_odd_deleted_file_coefficient
+
+ if we_have_seen_everything_we_already_got:
+
+ stop_reason = 'saw everything I had previously (probably large gallery page or small recent initial sync), so assuming I caught up'
+
+ can_search_for_more_files = False
+
+ break
+
+
+
+
+ WE_HIT_OLD_GROUND_THRESHOLD = 5
+
+ if can_search_for_more_files:
+
+ if current_contiguous_num_urls_already_in_file_seed_cache >= WE_HIT_OLD_GROUND_THRESHOLD:
+
+ # this gallery page has caught up to before, so it should not spawn any more gallery pages
+
+ can_search_for_more_files = False
+ stop_reason = 'saw {} contiguous previously seen urls at end of page, so assuming we caught up'.format( HydrusData.ToHumanInt( current_contiguous_num_urls_already_in_file_seed_cache ) )
+
+
+ if num_urls_added == 0:
+
+ can_search_for_more_files = False
+ stop_reason = 'no new urls found'
+
+
+
+ return ( num_urls_added, num_urls_already_in_file_seed_cache, can_search_for_more_files, stop_reason )
+
+
+ job_key.SetVariable( 'popup_text_1', status_prefix + ': found ' + HydrusData.ToHumanInt( total_new_urls_for_this_sync ) + ' new urls, checking next page' )
+
+ try:
+
+ ( num_urls_added, num_urls_already_in_file_seed_cache, num_urls_total, result_404, added_new_gallery_pages, stop_reason ) = gallery_seed.WorkOnURL( 'subscription', gallery_seed_log, file_seeds_callable, status_hook, title_hook, query_header.GenerateNetworkJobFactory( self._name ), ClientImporting.GenerateMultiplePopupNetworkJobPresentationContextFactory( job_key ), self._file_import_options, gallery_urls_seen_before = gallery_urls_seen_this_sync )
+
+ except HydrusExceptions.CancelledException as e:
+
+ stop_reason = 'gallery network job cancelled, likely by user'
+
+ self._DelayWork( 600, stop_reason )
+
+ raise HydrusExceptions.CancelledException( 'User cancelled.' )
+
+ except Exception as e:
+
+ stop_reason = str( e )
+
+ raise
+
+
+ total_new_urls_for_this_sync += num_urls_added
+ total_already_in_urls_for_this_sync += num_urls_already_in_file_seed_cache
+
+ if file_limit_for_this_sync is not None and total_new_urls_for_this_sync >= file_limit_for_this_sync:
+
+ # we have found enough new files this sync, so stop and cancel any outstanding gallery urls
+
+ if this_is_initial_sync:
+
+ stop_reason = 'hit initial file limit'
+
+ else:
+
+ stop_reason = 'hit periodic file limit'
+
+
+ break
+
+
+
+ finally:
+
+ # now clean up any lingering gallery seeds
+
+ while gallery_seed_log.WorkToDo():
+
+ gallery_seed = gallery_seed_log.GetNextGallerySeed( CC.STATUS_UNKNOWN )
+
+ if gallery_seed is None:
+
+ break
+
+
+ gallery_seed.SetStatus( CC.STATUS_VETOED, note = stop_reason )
+
+
+
+ file_seeds_to_add_in_this_sync_ordered.reverse()
+
+ # 'first' urls are now at the end, so the file_seed_cache should stay roughly in oldest->newest order
+
+ file_seed_cache.AddFileSeeds( file_seeds_to_add_in_this_sync_ordered )
+
+ query_header.RegisterSyncComplete( self._checker_options, query_log_container )
+
+ #
+
+ if query_header.IsDead():
+
+ if this_is_initial_sync:
+
+ HydrusData.ShowText( 'The query "{}" for subscription "{}" did not find any files on its first sync! Could the query text have a typo, like a missing underscore?'.format( query_name, self._name ) )
+
+ else:
+
+ death_file_velocity = self._checker_options.GetDeathFileVelocity()
+
+ ( death_files_found, death_time_delta ) = death_file_velocity
+
+ HydrusData.ShowText( 'The query "{}" for subscription "{}" found fewer than {} files in the last {}, so it appears to be dead!'.format( query_name, self._name, HydrusData.ToHumanInt( death_files_found ), HydrusData.TimeDeltaToPrettyTimeDelta( death_time_delta ) ) )
+
+
+ else:
+
+ if this_is_initial_sync:
+
+ if not query_header.FileBandwidthOK( HG.client_controller.network_engine.bandwidth_manager, self._name ) and not self._have_made_an_initial_sync_bandwidth_notification:
+
+ HydrusData.ShowText( 'FYI: The query "{}" for subscription "{}" performed its initial sync ok, but it is short on bandwidth right now, so no files will be downloaded yet. The subscription will catch up in future as bandwidth becomes available. You can review the estimated time until bandwidth is available under the manage subscriptions dialog. If more queries are performing initial syncs in this run, they may be the same.'.format( query_name, self._name ) )
+
+ self._have_made_an_initial_sync_bandwidth_notification = True
+
+
+
+
+
+ def _SyncQueryLogContainersCanDoWork( self ):
+
+ result = True in ( query_header.WantsToResyncWithLogContainer() for query_header in self._query_headers )
+
+ if HG.subscription_report_mode:
+
+ HydrusData.ShowText( 'Subscription "{}" checking if any log containers need to be resynced: {}'.format( self._name, result ) )
+
+
+ return result
+
+
+ def _SyncQueryLogContainers( self ):
+
+ query_headers_to_do = [ query_header for query_header in self._query_headers if query_header.WantsToResyncWithLogContainer() ]
+
+ for query_header in self._query_headers:
+
+ if not query_header.WantsToResyncWithLogContainer():
+
+ continue
+
+
+ try:
+
+ query_log_container = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER, query_header.GetQueryLogContainerName() )
+
+ except HydrusExceptions.DBException as e:
+
+ if isinstance( e.db_e, HydrusExceptions.DataMissing ):
+
+ self._DealWithMissingQueryLogContainerError( query_header )
+
+ break
+
+ else:
+
+ raise
+
+
+
+ query_header.SyncToQueryLogContainer( self._checker_options, query_log_container )
+
+ # don't need to save the container back, we made no changes
+
+
+
+ def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
+
+ if version == 1:
+
+ (
+ serialisable_gug_key_and_name,
+ serialisable_query_headers,
+ serialisable_checker_options,
+ initial_file_limit,
+ periodic_file_limit,
+ paused,
+ serialisable_file_import_options,
+ serialisable_tag_import_options,
+ no_work_until,
+ no_work_until_reason,
+ show_a_popup_while_working,
+ publish_files_to_popup_button,
+ publish_files_to_page,
+ publish_label_override,
+ merge_query_publish_events
+ ) = old_serialisable_info
+
+ this_is_a_random_sample_sub = False
+
+ new_serialisable_info = (
+ serialisable_gug_key_and_name,
+ serialisable_query_headers,
+ serialisable_checker_options,
+ initial_file_limit,
+ periodic_file_limit,
+ this_is_a_random_sample_sub,
+ paused,
+ serialisable_file_import_options,
+ serialisable_tag_import_options,
+ no_work_until,
+ no_work_until_reason,
+ show_a_popup_while_working,
+ publish_files_to_popup_button,
+ publish_files_to_page,
+ publish_label_override,
+ merge_query_publish_events
+ )
+
+ return ( 2, new_serialisable_info )
+
+
+
def _WorkOnQueriesFiles( self, job_key ):
self._file_error_count = 0
@@ -566,503 +1122,6 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
- def _SyncQueries( self, job_key ):
-
- self._have_made_an_initial_sync_bandwidth_notification = False
-
- gug = HG.client_controller.network_engine.domain_manager.GetGUG( self._gug_key_and_name )
-
- if gug is None:
-
- self._paused = True
-
- HydrusData.ShowText( 'The subscription "{}" could not find a Gallery URL Generator for "{}"! The sub has paused!'.format( self._name, self._gug_key_and_name[1] ) )
-
- return
-
-
- try:
-
- gug.CheckFunctional()
-
- except HydrusExceptions.ParseException as e:
-
- self._paused = True
-
- message = 'The subscription "{}"\'s Gallery URL Generator, "{}" seems not to be functional! The sub has paused! The given reason was:'.format( self._name, self._gug_key_and_name[1] )
- message += os.linesep * 2
- message += str( e )
-
- HydrusData.ShowText( message )
-
- return
-
-
- self._gug_key_and_name = gug.GetGUGKeyAndName() # just a refresher, to keep up with any changes
-
- query_headers = self._GetQueryHeadersForProcessing()
-
- query_headers = [ query_header for query_header in query_headers if query_header.IsSyncDue() ]
-
- num_queries = len( query_headers )
-
- for ( i, query_header ) in enumerate( query_headers ):
-
- status_prefix = 'synchronising'
-
- query_name = query_header.GetHumanName()
-
- if query_name != self._name:
-
- status_prefix += ' "' + query_name + '"'
-
-
- status_prefix += ' (' + HydrusData.ConvertValueRangeToPrettyString( i + 1, num_queries ) + ')'
-
- try:
-
- query_log_container = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER, query_header.GetQueryLogContainerName() )
-
- except HydrusExceptions.DBException as e:
-
- if isinstance( e.db_e, HydrusExceptions.DataMissing ):
-
- self._DealWithMissingQueryLogContainerError( query_header )
-
- break
-
- else:
-
- raise
-
-
-
- try:
-
- self._SyncQuery( job_key, gug, query_header, query_log_container, status_prefix )
-
- except HydrusExceptions.CancelledException:
-
- break
-
- finally:
-
- HG.client_controller.WriteSynchronous( 'serialisable', query_log_container )
-
-
-
-
- def _SyncQueriesCanDoWork( self ):
-
- result = True in ( query_header.IsSyncDue() for query_header in self._query_headers )
-
- if HG.subscription_report_mode:
-
- HydrusData.ShowText( 'Subscription "{}" checking if any sync work due: {}'.format( self._name, result ) )
-
-
- return result
-
-
- def _SyncQuery(
- self,
- job_key: ClientThreading.JobKey,
- gug: ClientNetworkingDomain.GalleryURLGenerator, # not actually correct for an ngug, but _whatever_
- query_header: ClientImportSubscriptionQuery.SubscriptionQueryHeader,
- query_log_container: ClientImportSubscriptionQuery.SubscriptionQueryLogContainer,
- status_prefix: str
- ):
-
- query_text = query_header.GetQueryText()
- query_name = query_header.GetHumanName()
-
- file_seed_cache = query_log_container.GetFileSeedCache()
- gallery_seed_log = query_log_container.GetGallerySeedLog()
-
- this_is_initial_sync = query_header.IsInitialSync()
- num_master_file_seeds_at_start = file_seed_cache.GetApproxNumMasterFileSeeds()
- total_new_urls_for_this_sync = 0
- total_already_in_urls_for_this_sync = 0
-
- gallery_urls_seen_this_sync = set()
-
- if this_is_initial_sync:
-
- file_limit_for_this_sync = self._initial_file_limit
-
- else:
-
- file_limit_for_this_sync = self._periodic_file_limit
-
-
- file_seeds_to_add_in_this_sync = set()
- file_seeds_to_add_in_this_sync_ordered = []
-
- stop_reason = 'unknown stop reason'
-
- job_key.SetVariable( 'popup_text_1', status_prefix )
-
- initial_search_urls = gug.GenerateGalleryURLs( query_text )
-
- if len( initial_search_urls ) == 0:
-
- self._paused = True
-
- HydrusData.ShowText( 'The subscription "' + self._name + '"\'s Gallery URL Generator, "' + self._gug_key_and_name[1] + '" did not generate any URLs! The sub has paused!' )
-
- raise HydrusExceptions.CancelledException( 'Bad GUG.' )
-
-
- gallery_seeds = [ ClientImportGallerySeeds.GallerySeed( url, can_generate_more_pages = True ) for url in initial_search_urls ]
-
- gallery_seed_log.AddGallerySeeds( gallery_seeds )
-
- try:
-
- while gallery_seed_log.WorkToDo():
-
- p1 = not self._CanDoWorkNow()
- ( login_ok, login_reason ) = query_header.GalleryLoginOK( HG.client_controller.network_engine, self._name )
-
- if p1 or not login_ok:
-
- if not login_ok:
-
- if not self._paused:
-
- message = 'Query "{}" for subscription "{}" seemed to have an invalid login. The reason was:'.format( query_header.GetHumanName(), self._name )
- message += os.linesep * 2
- message += login_reason
- message += os.linesep * 2
- message += 'The subscription has paused. Please see if you can fix the problem and then unpause. Hydrus dev would like feedback on this process.'
-
- HydrusData.ShowText( message )
-
- self._DelayWork( 300, login_reason )
-
- self._paused = True
-
-
-
- raise HydrusExceptions.CancelledException( 'A problem, so stopping.' )
-
-
- if job_key.IsCancelled():
-
- stop_reason = 'gallery parsing cancelled, likely by user'
-
- self._DelayWork( 600, stop_reason )
-
- raise HydrusExceptions.CancelledException( 'User cancelled.' )
-
-
- gallery_seed = gallery_seed_log.GetNextGallerySeed( CC.STATUS_UNKNOWN )
-
- if gallery_seed is None:
-
- stop_reason = 'thought there was a page to check, but apparently there was not!'
-
- break
-
-
- def status_hook( text ):
-
- if len( text ) > 0:
-
- text = text.splitlines()[0]
-
-
- job_key.SetVariable( 'popup_text_1', status_prefix + ': ' + text )
-
-
- def title_hook( text ):
-
- pass
-
-
- def file_seeds_callable( gallery_page_of_file_seeds ):
-
- num_urls_added = 0
- num_urls_already_in_file_seed_cache = 0
- can_search_for_more_files = True
- stop_reason = 'unknown stop reason'
- current_contiguous_num_urls_already_in_file_seed_cache = 0
- num_file_seeds_in_this_page = len( gallery_page_of_file_seeds )
-
- for file_seed in gallery_page_of_file_seeds:
-
- if file_seed in file_seeds_to_add_in_this_sync:
-
- # this catches the occasional overflow when a new file is uploaded while gallery parsing is going on
- # we don't want to count these 'seen before this run' urls in the 'caught up to last time' count
-
- continue
-
-
- # When are we caught up? This is not a trivial problem. Tags are not always added when files are uploaded, so the order we find files is not completely reliable.
- # Ideally, we want to search a _bit_ deeper than the first already-seen.
- # And since we have a page of urls here and now, there is no point breaking early if there might be some new ones at the end.
- # Current rule is "We are caught up if the final X contiguous files are 'already in'". X is 5 for now.
-
- if file_seed_cache.HasFileSeed( file_seed ):
-
- num_urls_already_in_file_seed_cache += 1
- current_contiguous_num_urls_already_in_file_seed_cache += 1
-
- if current_contiguous_num_urls_already_in_file_seed_cache >= 100:
-
- can_search_for_more_files = False
- stop_reason = 'saw 100 previously seen urls in a row, so assuming this is a large gallery'
-
- break
-
-
- else:
-
- num_urls_added += 1
- current_contiguous_num_urls_already_in_file_seed_cache = 0
-
- file_seeds_to_add_in_this_sync.add( file_seed )
- file_seeds_to_add_in_this_sync_ordered.append( file_seed )
-
-
- if file_limit_for_this_sync is not None:
-
- if total_new_urls_for_this_sync + num_urls_added >= file_limit_for_this_sync:
-
- # we have found enough new files this sync, so should stop adding files and new gallery pages
-
- if this_is_initial_sync:
-
- stop_reason = 'hit initial file limit'
-
- else:
-
- if total_already_in_urls_for_this_sync + num_urls_already_in_file_seed_cache > 0:
-
- # this sync produced some knowns, so it is likely we have stepped through a mix of old and tagged-late new files
- # this is no reason to go crying to the user
-
- stop_reason = 'hit periodic file limit after seeing several already-seen files'
-
- else:
-
- # this page had all entirely new files
-
- self._ShowHitPeriodicFileLimitMessage( query_name, query_text, file_limit_for_this_sync )
-
- stop_reason = 'hit periodic file limit without seeing any already-seen files!'
-
-
-
- can_search_for_more_files = False
-
- break
-
-
-
- if not this_is_initial_sync:
-
- # ok, there are a couple of situations where we don't want to go steamroll past a certain point:
-
- # if the user set 5 initial file limit but 100 periodic limit, then on the first few syncs, we'll want to notice that situation and not steamroll through that first five (or ~seven on third sync)
- # if 'X' is new and get, 'A' is already in, and '-' is new and don't get, the page should be:
- # XXXAAAAA----------------------------------
-
- # the pixiv situation, where a single gallery page may have hundreds of results (and/or multi-file results that will pad out the file cache with more items)
- # super large gallery pages interfere with the compaction system, adding results that were removed again and making WE_HIT_OLD_GROUND_THRESHOLD test not work correct
- # similar to above:
- # XXXXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- # AAAAAAAAAAAAAAAAAAAA-----------------------------------
- # -------------------------------------------------------
- # ----------------------
-
- # I had specific logic targeting both these cases, but in making those, I make the num_master_file_seeds_at_start, which is actually the better thing to test
- # If the sub has seen basically everything it started with, we are by definition caught up and should stop immediately!
-
- excuse_the_odd_deleted_file_coefficient = 0.95
-
- # we found all the 'A's
- we_have_seen_everything_we_already_got = total_already_in_urls_for_this_sync + num_urls_already_in_file_seed_cache >= num_master_file_seeds_at_start * excuse_the_odd_deleted_file_coefficient
-
- if we_have_seen_everything_we_already_got:
-
- stop_reason = 'saw everything I had previously (probably large gallery page or small recent initial sync), so assuming I caught up'
-
- can_search_for_more_files = False
-
- break
-
-
-
-
- WE_HIT_OLD_GROUND_THRESHOLD = 5
-
- if can_search_for_more_files:
-
- if current_contiguous_num_urls_already_in_file_seed_cache >= WE_HIT_OLD_GROUND_THRESHOLD:
-
- # this gallery page has caught up to before, so it should not spawn any more gallery pages
-
- can_search_for_more_files = False
- stop_reason = 'saw {} contiguous previously seen urls at end of page, so assuming we caught up'.format( HydrusData.ToHumanInt( current_contiguous_num_urls_already_in_file_seed_cache ) )
-
-
- if num_urls_added == 0:
-
- can_search_for_more_files = False
- stop_reason = 'no new urls found'
-
-
-
- return ( num_urls_added, num_urls_already_in_file_seed_cache, can_search_for_more_files, stop_reason )
-
-
- job_key.SetVariable( 'popup_text_1', status_prefix + ': found ' + HydrusData.ToHumanInt( total_new_urls_for_this_sync ) + ' new urls, checking next page' )
-
- try:
-
- ( num_urls_added, num_urls_already_in_file_seed_cache, num_urls_total, result_404, added_new_gallery_pages, stop_reason ) = gallery_seed.WorkOnURL( 'subscription', gallery_seed_log, file_seeds_callable, status_hook, title_hook, query_header.GenerateNetworkJobFactory( self._name ), ClientImporting.GenerateMultiplePopupNetworkJobPresentationContextFactory( job_key ), self._file_import_options, gallery_urls_seen_before = gallery_urls_seen_this_sync )
-
- except HydrusExceptions.CancelledException as e:
-
- stop_reason = 'gallery network job cancelled, likely by user'
-
- self._DelayWork( 600, stop_reason )
-
- raise HydrusExceptions.CancelledException( 'User cancelled.' )
-
- except Exception as e:
-
- stop_reason = str( e )
-
- raise
-
-
- total_new_urls_for_this_sync += num_urls_added
- total_already_in_urls_for_this_sync += num_urls_already_in_file_seed_cache
-
- if file_limit_for_this_sync is not None and total_new_urls_for_this_sync >= file_limit_for_this_sync:
-
- # we have found enough new files this sync, so stop and cancel any outstanding gallery urls
-
- if this_is_initial_sync:
-
- stop_reason = 'hit initial file limit'
-
- else:
-
- stop_reason = 'hit periodic file limit'
-
-
- break
-
-
-
- finally:
-
- # now clean up any lingering gallery seeds
-
- while gallery_seed_log.WorkToDo():
-
- gallery_seed = gallery_seed_log.GetNextGallerySeed( CC.STATUS_UNKNOWN )
-
- if gallery_seed is None:
-
- break
-
-
- gallery_seed.SetStatus( CC.STATUS_VETOED, note = stop_reason )
-
-
-
- file_seeds_to_add_in_this_sync_ordered.reverse()
-
- # 'first' urls are now at the end, so the file_seed_cache should stay roughly in oldest->newest order
-
- file_seed_cache.AddFileSeeds( file_seeds_to_add_in_this_sync_ordered )
-
- query_header.RegisterSyncComplete( self._checker_options, query_log_container )
-
- #
-
- if query_header.IsDead():
-
- if this_is_initial_sync:
-
- HydrusData.ShowText( 'The query "{}" for subscription "{}" did not find any files on its first sync! Could the query text have a typo, like a missing underscore?'.format( query_name, self._name ) )
-
- else:
-
- death_file_velocity = self._checker_options.GetDeathFileVelocity()
-
- ( death_files_found, death_time_delta ) = death_file_velocity
-
- HydrusData.ShowText( 'The query "{}" for subscription "{}" found fewer than {} files in the last {}, so it appears to be dead!'.format( query_name, self._name, HydrusData.ToHumanInt( death_files_found ), HydrusData.TimeDeltaToPrettyTimeDelta( death_time_delta ) ) )
-
-
- else:
-
- if this_is_initial_sync:
-
- if not query_header.FileBandwidthOK( HG.client_controller.network_engine.bandwidth_manager, self._name ) and not self._have_made_an_initial_sync_bandwidth_notification:
-
- HydrusData.ShowText( 'FYI: The query "{}" for subscription "{}" performed its initial sync ok, but it is short on bandwidth right now, so no files will be downloaded yet. The subscription will catch up in future as bandwidth becomes available. You can review the estimated time until bandwidth is available under the manage subscriptions dialog. If more queries are performing initial syncs in this run, they may be the same.'.format( query_name, self._name ) )
-
- self._have_made_an_initial_sync_bandwidth_notification = True
-
-
-
-
-
- def _SyncQueryLogContainersCanDoWork( self ):
-
- result = True in ( query_header.WantsToResyncWithLogContainer() for query_header in self._query_headers )
-
- if HG.subscription_report_mode:
-
- HydrusData.ShowText( 'Subscription "{}" checking if any log containers need to be resynced: {}'.format( self._name, result ) )
-
-
- return result
-
-
- def _SyncQueryLogContainers( self ):
-
- query_headers_to_do = [ query_header for query_header in self._query_headers if query_header.WantsToResyncWithLogContainer() ]
-
- for query_header in self._query_headers:
-
- if not query_header.WantsToResyncWithLogContainer():
-
- continue
-
-
- try:
-
- query_log_container = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER, query_header.GetQueryLogContainerName() )
-
- except HydrusExceptions.DBException as e:
-
- if isinstance( e.db_e, HydrusExceptions.DataMissing ):
-
- self._DealWithMissingQueryLogContainerError( query_header )
-
- break
-
- else:
-
- raise
-
-
-
- query_header.SyncToQueryLogContainer( self._checker_options, query_log_container )
-
- # don't need to save the container back, we made no changes
-
-
-
def CanCheckNow( self ):
return True in ( query_header.CanCheckNow() for query_header in self._query_headers )
@@ -1433,6 +1492,11 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
self._tag_import_options = tag_import_options.Duplicate()
+ def SetThisIsARandomSampleSubscription( self, value: bool ):
+
+ self._this_is_a_random_sample_sub = value
+
+
def SetTuple( self, gug_key_and_name, checker_options: ClientImportOptions.CheckerOptions, initial_file_limit, periodic_file_limit, paused, file_import_options: FileImportOptions.FileImportOptions, tag_import_options: TagImportOptions.TagImportOptions, no_work_until ):
self._gug_key_and_name = gug_key_and_name
@@ -1539,6 +1603,11 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
+ def ThisIsARandomSampleSubscription( self ) -> bool:
+
+ return self._this_is_a_random_sample_sub
+
+
def ToTuple( self ):
return ( self._name, self._gug_key_and_name, self._query_headers, self._checker_options, self._initial_file_limit, self._periodic_file_limit, self._paused, self._file_import_options, self._tag_import_options, self._no_work_until, self._no_work_until_reason )
diff --git a/hydrus/client/media/ClientMedia.py b/hydrus/client/media/ClientMedia.py
index 5a9a72fa..8905c1b8 100644
--- a/hydrus/client/media/ClientMedia.py
+++ b/hydrus/client/media/ClientMedia.py
@@ -87,6 +87,7 @@ def GetDuplicateComparisonStatements( shown_media, comparison_media ):
duplicate_comparison_score_much_higher_resolution = new_options.GetInteger( 'duplicate_comparison_score_much_higher_resolution' )
duplicate_comparison_score_more_tags = new_options.GetInteger( 'duplicate_comparison_score_more_tags' )
duplicate_comparison_score_older = new_options.GetInteger( 'duplicate_comparison_score_older' )
+ duplicate_comparison_score_nicer_ratio = new_options.GetInteger( 'duplicate_comparison_score_nicer_ratio' )
#
@@ -302,12 +303,12 @@ def GetDuplicateComparisonStatements( shown_media, comparison_media ):
elif s_nice:
operator = '>'
- score = 10
+ score = duplicate_comparison_score_nicer_ratio
elif c_nice:
operator = '<'
- score = -10
+ score = -duplicate_comparison_score_nicer_ratio
if s_string == c_string:
diff --git a/hydrus/core/HydrusConstants.py b/hydrus/core/HydrusConstants.py
index 31625f44..82b03ae7 100644
--- a/hydrus/core/HydrusConstants.py
+++ b/hydrus/core/HydrusConstants.py
@@ -81,7 +81,7 @@ options = {}
# Misc
NETWORK_VERSION = 20
-SOFTWARE_VERSION = 451
+SOFTWARE_VERSION = 452
CLIENT_API_VERSION = 19
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
diff --git a/hydrus/core/HydrusController.py b/hydrus/core/HydrusController.py
index 503ee3d6..840375ba 100644
--- a/hydrus/core/HydrusController.py
+++ b/hydrus/core/HydrusController.py
@@ -626,7 +626,7 @@ class HydrusController( object ):
self._MaintainCallToThreads()
- def PrintProfile( self, summary, profile_text ):
+ def PrintProfile( self, summary, profile_text = None ):
pretty_timestamp = time.strftime( '%Y-%m-%d %H-%M-%S', time.localtime( HG.profile_start_time ) )
@@ -639,8 +639,12 @@ class HydrusController( object ):
prefix = time.strftime( '%Y/%m/%d %H:%M:%S: ' )
f.write( prefix + summary )
- f.write( os.linesep * 2 )
- f.write( profile_text )
+
+ if profile_text is not None:
+
+ f.write( os.linesep * 2 )
+ f.write( profile_text )
+
diff --git a/hydrus/core/HydrusData.py b/hydrus/core/HydrusData.py
index f1d538d7..0a068df3 100644
--- a/hydrus/core/HydrusData.py
+++ b/hydrus/core/HydrusData.py
@@ -1213,7 +1213,7 @@ def Profile( summary, code, g, l, min_duration_ms = 20, show_summary = False ):
output.seek( 0 )
- details = output.read()
+ profile_text = output.read()
with HG.profile_counter_lock:
@@ -1225,7 +1225,7 @@ def Profile( summary, code, g, l, min_duration_ms = 20, show_summary = False ):
ShowText( summary )
- HG.controller.PrintProfile( summary, details )
+ HG.controller.PrintProfile( summary, profile_text = profile_text )
else:
@@ -1236,7 +1236,7 @@ def Profile( summary, code, g, l, min_duration_ms = 20, show_summary = False ):
if show_summary:
- HG.controller.PrintProfile( summary, details )
+ HG.controller.PrintProfile( summary )
diff --git a/hydrus/test/TestClientDB.py b/hydrus/test/TestClientDB.py
index b8fb3dda..b105dc66 100644
--- a/hydrus/test/TestClientDB.py
+++ b/hydrus/test/TestClientDB.py
@@ -1891,7 +1891,7 @@ class TestClientDB( unittest.TestCase ):
for ( shortcut, command ) in shortcuts:
- self.assertEqual( result.GetCommand( shortcut ).GetData(), command.GetData() )
+ self.assertEqual( tuple( result.GetCommand( shortcut )._data ), tuple( command._data ) )
#
diff --git a/hydrus/test/TestHydrusSerialisable.py b/hydrus/test/TestHydrusSerialisable.py
index 8bc77fe7..704abaa7 100644
--- a/hydrus/test/TestHydrusSerialisable.py
+++ b/hydrus/test/TestHydrusSerialisable.py
@@ -136,12 +136,14 @@ class TestSerialisables( unittest.TestCase ):
self.assertEqual( obj.GetCommandType(), dupe_obj.GetCommandType() )
- self.assertEqual( obj.GetData(), dupe_obj.GetData() )
+ self.assertSequenceEqual( tuple( obj._data ), tuple( dupe_obj._data ) )
acs = []
- acs.append( ( CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_FILE ), 'archive file' ) )
+ acs.append( ( CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_FILE ), 'archive file' ) )
+ acs.append( ( CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_MEDIA_SEEK_DELTA, simple_data = ( -1, 2500 ) ), 'seek media (back 2.5 seconds)' ) )
+ acs.append( ( CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_MEDIA_SEEK_DELTA, simple_data = ( 1, 800 ) ), 'seek media (forwards 800 milliseconds)' ) )
acs.append( ( CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_CONTENT, ( HydrusData.GenerateKey(), HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_FLIP, 'test' ) ), 'flip on/off mappings "test" for unknown service!' ) )
acs.append( ( CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_CONTENT, ( CC.DEFAULT_LOCAL_TAG_SERVICE_KEY, HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_FLIP, 'test' ) ), 'flip on/off mappings "test" for my tags' ) )
acs.append( ( CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_CONTENT, ( HydrusData.GenerateKey(), HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_SET, 0.4 ) ), 'set ratings uncertain rating, "0.4" for unknown service!' ) )
@@ -430,7 +432,7 @@ class TestSerialisables( unittest.TestCase ):
for ( shortcut, command ) in obj:
- self.assertEqual( dupe_obj.GetCommand( shortcut ).GetData(), command.GetData() )
+ self.assertEqual( dupe_obj.GetCommand( shortcut ), command )
@@ -441,7 +443,7 @@ class TestSerialisables( unittest.TestCase ):
self._dump_and_load_and_test( shortcuts, test )
- command_1 = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_SIMPLE, CAC.SIMPLE_ARCHIVE_FILE )
+ command_1 = CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_ARCHIVE_FILE )
command_2 = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_CONTENT, ( HydrusData.GenerateKey(), HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_FLIP, 'test' ) )
command_3 = CAC.ApplicationCommand( CAC.APPLICATION_COMMAND_TYPE_CONTENT, ( CC.DEFAULT_LOCAL_TAG_SERVICE_KEY, HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_FLIP, 'test' ) )
@@ -467,11 +469,11 @@ class TestSerialisables( unittest.TestCase ):
self._dump_and_load_and_test( shortcut_set, test )
- self.assertEqual( shortcut_set.GetCommand( k_shortcut_1 ).GetData(), command_1.GetData() )
+ self.assertEqual( tuple( shortcut_set.GetCommand( k_shortcut_1 )._data ), tuple( command_1._data ) )
shortcut_set.SetCommand( k_shortcut_1, command_3 )
- self.assertEqual( shortcut_set.GetCommand( k_shortcut_1 ).GetData(), command_3.GetData() )
+ self.assertEqual( tuple( shortcut_set.GetCommand( k_shortcut_1 )._data ), tuple( command_3._data ) )
def test_SERIALISABLE_TYPE_SUBSCRIPTION( self ):