diff --git a/docs/changelog.md b/docs/changelog.md
index 52bb9275a..6d9b99640 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -7,6 +7,54 @@ title: Changelog
!!! note
This is the new changelog, only the most recent builds. For all versions, see the [old changelog](old_changelog.html).
+## [Version 581](https://github.com/hydrusnetwork/hydrus/releases/tag/v581)
+
+### misc
+
+* thanks to a user, we have a much improved shimmie parser, for both file and gallery urls, that fetches md5 better, improves gallery navigation, stops grabbing bad urls and related tags by accident, and can handle namespaces for those shimmies that use them. for our purposes, this improves r34h and r34@paheal downloaders by default
+* thanks to a user, we have a new 'Dark Blue 1.1' styesheet with some improvements. the recommendation is: check the different scrollbar styling to see if you prefer the old version
+* timedelta widgets now enforce their minimum time on focus-out rather than value change. if it wants at least 20 minutes, you can now type in '5...' in the minutes column without it going nuts. let me know if you discover a way to out-fox the focus-out detection!
+* added a checkbox to file import options to govern whether 'import destinations' and 'archive all imports' apply to 'already in db' files. this turns on/off the logic that I made more reliable last week. default is that they do
+* added 'do sleep check' to _options->system_ to try some things out on systems that often false-positive this check
+* the 'review current network jobs' multi-column list has a new right-click menu to show a bit more debug info about each job: each of its network contexts, how the bandwidth is on each context, if the domain is ok, if it is waiting on a connection error, if it is waiting on serverside bandwidth, if it obey bandwidth, and if its tokens are ok. if you have been working with me on gallery jobs that just sit on 'starting soon', please check it out and let me know what you see. also, 'review current network jobs' is duplicated to the help->debug menu. I forgot where it was, so let's have it in both places
+* on the filename-import tagging panel, the filename and directory checkbox-and-text-edit widgets no longer emit a (sometimes laggy) update signal when typing when the checkbox is unchecked
+
+### janitor stuff
+
+* if you are a repository janitor, right-clicking on any tag shows a new 'admin' menu
+* if you have 'change options' permission, you will see 'block x'/'re-allow x' to let you quickly see if tags are blocked and then edit the repository tag filter respectively
+* if you have 'mappings petition resolution' permission, you can 'purge' the selected tags, which will deleted them from the service entirely. this launches a review window that previews the job and allows adding of more tags using the standard autocomplete interface. when 'fired off', it launches a tag migration job to queue up the full petition/delete upload
+* this new 'purge' window is also available from the normal 'administrate services' menu in the main gui
+* also under the 'administrate services' is a new 'purge tag filter' command, which applies the existing repository tag filter to its own mappings store, retroactively syncing you to it
+
+### tag filters and migration
+
+* I wrote a database routine that quickly converts a hydrus tag filter into the list of tags within a file and tag search context. this tech will have a variety of uses in the genre of 'hey please delete/fetch/check all these tags'
+* to start with, it is now plugged into the tag migration system, so when you set up, say, an 'all known files' tag migration that only looks for a namespace or a bunch of single tags, the 'setup' phase is now massively, massively faster (previously, with something like the PTR, this would be scanning through tens of millions of files for minutes; now it just targets the 50k or whatever using existing tag search tech usually within less than a second)
+* cleaned (KISSed) and reworked the tag filter logic a bit--it can now, underlyingly, handle 'no namespaced tags, except for creator:anything, but still allowing creator:blah'
+* optimised how tag filters do 'apply unnamespaced rules to namespaced tags' (which happens in some blacklists that want to be expansive)
+* improved how the tag filter describes itself in many cases. it should make more grammatical sense and repeat itself less now (e.g. no more 'all tags and namespaced tags and unnamespaced tags' rubbish)
+* improved how some tag filter rules are handled across the program, including fixing some edge-case false-positive namespace-rule detection
+* deleted some ancient and no longer used tag filtering code
+
+### boring multi-column list stuff
+
+* did more 'select, sort, and scroll' code cleanup in my multi-column lists, specifically: manage import folders; manage export folders; the string-to-string dict list; edit ngug; edit downloader display (both gugs and url classes, and with a one-shot show/hide choice on a multi-selection rather than asking for each in turn); the special 'duplicate' command of edit shortcut set; and the string converter conversions list (including better select logic on move up/down)
+* in keeping with the new general policy of 'when you edit a multi-column list, you just edit one row', the various 'edit' buttons under these lists across the program are now generally only enabled when you have one row selected
+* the new 'select, sort, and scroll to new item when a human adds it' tech now _deselects_ the previous selection. let me know if this screws up anywhere (maybe in a hacky multi-add somewhere it'll only select the last added?)
+* the aggravating 'clear the focus of the list on most changes bro' jank seems to be fixed--it was a dumb legacy thing
+* whenever the multi-column list does its new 'scroll-to' action, it now takes focus to better highlight where we are (rather than stay, for instance, leaving focus on the 'add' button you just clicked)
+
+### other boring stuff
+
+* worked a little more on a routine that collapses an arbitrary list of strings to a human-presentable summary and replaced the hardcoded hacky version that presents the 'paste queries' result in the 'edit subscription' panel with it
+* wrote a similar new routine to collapse an arbitrary list of strings to a single-line summary, appropriate for menu labels and such
+* fixed a layout issue in the 'manage downloader display' dialog that caused the 'edit' button on the 'media viewer urls' side to not show, lmaooooooo
+* ephemeral 'watcher' and 'gallery' network contexts now describe themselves with a nicer string
+* decoupled how some service admin stuff works behind the scenes to make it easier to launch this stuff from different UI widgets
+* refactored `ToHumanInt` and the `ToPrettyOrdinalString` guys to a new `HydrusNumbers.py` file
+* fixed some bad Client API documentation for the params in `/get_files/search_files`
+
## [Version 580](https://github.com/hydrusnetwork/hydrus/releases/tag/v580)
### misc
@@ -322,33 +370,3 @@ title: Changelog
* renamed the various simple commands I have replaced in the past few weeks as 'legacy', so we don't accidentally refer to them again in real code
* the unit test for 'dateparser decode' is no longer run if dateparser is not in the environment
* fixed the file metadata parsing unit tests to account for newer ffmpeg, which sees a -10ms different duration on one of the test files, and made the various tests +/-20% lenient to handle this stuff if it comes up again in future
-
-## [Version 571](https://github.com/hydrusnetwork/hydrus/releases/tag/v571)
-
-### clean install
-
-* the recent 'future build' test went well, so I am rolling these updates into the normal release for everyone. on Windows and Linux, the built program is now running Python 3.11, and, on all platforms, updated versions of Qt (UI) and OpenCV (image-processing). there's nothing earth-shattering about these changes, but some things will work better and faster
-* **because of the jump, v570 and v571 have dll conflicts! if you are on Windows or Linux and use the .zip or .tar.zst "Extract" release, you will need to a clean install as here**: https://hydrusnetwork.github.io/hydrus/getting_started_installing.html#clean_installs
-* **if you are a Windows installer/macOS App/source user, you do not need to do a clean install, just update as normal**
-
-### misc
-
-* when you finish an archive/delete filter and there are several domains you could delete from, the 'commit' buttons are now disabled for 1.2 seconds. this catches you from accidentally spamming enter through a surprise complicated decision
-* under _options->files and trash_, you can now say 'when finishing filtering, always delete from all possible domains', which makes the above decision always single domain. hit this if you do want to spam through this and are fine always deleting from everywhere
-* the client will now, by default, attempt to load truncated images. this was previously off until you set it per-session-on in a debug menu, but is now a checkbox under _options->media_. some weird damaged jpegs and pngs should now load, fingers crossed
-* the 'load images with PIL' setting is now default on for new users and no longer IN TESTING
-* every normal single column text list across the program now copies text better if you explicitly hit ctrl+c/ctrl+insert. they now copy all selected rows (rather than just one), and when the display text differs from the underlying data/sort text, you'll now get the sort text (e.g. on manage urls launched on multiple files, you might see 'site.com/123456 (2)', but now, when it copies, that ' (1)' display cruft is omitted). I spammed this to 22 locations and tested 2 so there are definitely no weird string copy bugs anywhere
-* fixed an issue opening/closing manage parsers, url classes, or url class links if you have url classes with invalid example urls or critically missing default values in your storage
-* the server has a new 'restart_services' command, only triggerable by an admin with service modification ability, which tells all the services on all ports to stop and restart. if there's a new ssl cert, they load the new one
-
-### client api
-
-* the 'associate urls' command has a new 'normalise_urls' parameter (default true, which was the behaviour before) to let you force-add un-normalised URLs or URIs or whatever
-* added some unit tests to test this new param
-* client api version is now 64
-
-### help docs
-
-* wrote a new help document, 'help my db disappeared.txt' for the db directory that tells you what to do if you boot one day and suddenly get the 'this looks like the first time you ran this program' popup
-* clarified the Windows 'running from source' help a little around 'git' and added a 'here is the Python version you want' link for Win 7 users
-* gave the install help a very light pass, just fixing and updating a few things here and there. I also warn Linux users that the AUR package may throw errors if Arch updates a Qt library or something before we have had a chance to test it (as we have seen a couple times recently), and I generally suggest AUR people run from source manually if they can
diff --git a/docs/developer_api.md b/docs/developer_api.md
index be2b73057..f1917fee5 100644
--- a/docs/developer_api.md
+++ b/docs/developer_api.md
@@ -539,7 +539,7 @@ Required Headers:
Arguments (in JSON):
:
* [files](#parameters_files)
-* [file domain](#parameters_file_domain) (optional, defaults to 'all my files')
+* [file domain](#parameters_file_domain) (optional, defaults to _all my files_)
* `reason`: (optional, string, the reason attached to the delete action)
```json title="Example request body"
@@ -551,7 +551,7 @@ Arguments (in JSON):
Response:
: 200 and no content.
-If you specify a file service, the file will only be deleted from that location. Only local file domains are allowed (so you can't delete from a file repository or unpin from ipfs yet). It defaults to 'all my files', which will delete from all local services (i.e. force sending to trash). Sending 'all local files' on a file already in the trash will trigger a physical file delete.
+If you specify a file service, the file will only be deleted from that location. Only local file domains are allowed (so you can't delete from a file repository or unpin from ipfs yet). It defaults to _all my files_, which will delete from all local services (i.e. force sending to trash). Sending 'all local files' on a file already in the trash will trigger a physical file delete.
### **POST `/add_files/undelete_files`** { id="add_files_undelete_files" }
@@ -567,7 +567,7 @@ Required Headers:
Arguments (in JSON):
:
* [files](#parameters_files)
-* [file domain](#parameters_file_domain) (optional, defaults to 'all my files')
+* [file domain](#parameters_file_domain) (optional, defaults to _all my files_)
```json title="Example request body"
{
@@ -1064,9 +1064,9 @@ Required Headers: n/a
Arguments:
:
* `search`: (the tag text to search for, enter exactly what you would in the client UI)
- * [file domain](#parameters_file_domain) (optional, defaults to 'all my files')
- * `tag_service_key`: (optional, hexadecimal, the tag domain on which to search, defaults to 'all known tags')
- * `tag_display_type`: (optional, string, to select whether to search raw or sibling-processed tags, defaults to 'storage')
+ * [file domain](#parameters_file_domain) (optional, defaults to _all my files_)
+ * `tag_service_key`: (optional, hexadecimal, the tag domain on which to search, defaults to _all known tags_)
+ * `tag_display_type`: (optional, string, to select whether to search raw or sibling-processed tags, defaults to `storage`)
The `file domain` and `tag_service_key` perform the function of the file and tag domain buttons in the client UI.
@@ -1183,9 +1183,11 @@ This last example is far more complicated than you will usually see. Pend rescin
Note that the enumerated status keys in the service\_keys\_to\_actions\_to_tags structure are strings, not ints (JSON does not support int keys for Objects).
-The `override_previously_deleted_mappings` parameter adjusts your Add/Pend actions. In the client, if a human, in the _manage tags dialog_, tries to add a tag mapping that has been previously deleted, that deleted record will be overwritten. An automatic system like a gallery parser will filter/skip any Add/Pend actions in this case (so that repeat downloads do not overwrite a human user delete, etc..). The Client API acts like a human, by default, overwriting previously deleted mappings. If you want to spam a lot of new mappings but do not want to overwrite previously deletion decisions, then set this to `false`.
+The `override_previously_deleted_mappings` parameter adjusts your Add/Pend actions. In the client, if a human, in the _manage tags dialog_, tries to add a tag mapping that has been previously deleted, that deleted record will be overwritten. An automatic system like a gallery parser will filter/skip any Add/Pend actions in this case (so that repeat downloads do not overwrite a human user delete, etc..). The Client API acts like a human, by default, overwriting previously deleted mappings. If you want to spam a lot of new mappings but do not want to overwrite previously deletion decisions, acting like a downloader, then set this to `false`.
-The `create_new_deleted_mappings` parameter adjusts your Delete/Petition actions. Particularly, whether a delete record should be made _even if the tag does not exist on the file_. There are not many ways to spontaneously create a delete record in the normal hydrus UI, but you as the Client API should think whether this is what you want. Are you saying 'migrate these deleted tag records from A to B'? Then you want this `true`. Are you saying 'I accidentally spammed this tag to the wrong places, so remove all instances of it on any of these files so I can try again'? Then set it `false`.
+The `create_new_deleted_mappings` parameter adjusts your Delete/Petition actions, particularly whether a delete record should be made _even if the tag does not exist on the file_. There are not many ways to spontaneously create a delete record in the normal hydrus UI, but you as the Client API should think whether this is what you want. By default, the Client API will write a delete record whether the tag already exists for the file or not. If you only want to create a delete record (which prohibits the tag being added back again by something like a downloader, as with `override_previously_deleted_mappings`) when the tag already exists on the file, then set this to `false`. Are you saying 'migrate all these deleted tag records from A to B so that none of them are re-added'? Then you want this `true`. Are you saying 'This tag was applied incorrectly to some but perhaps not all of of these files; where it exists, delete it.'? Then set it `false`.
+
+There is currently no way to delete a tag mapping without leaving a delete record (as you can with files). This will probably happen, though, and it'll be a new parameter here.
Response description:
: 200 and no content.
@@ -1421,12 +1423,12 @@ Required Headers: n/a
Arguments (in percent-encoded JSON):
:
* `tags`: (a list of tags you wish to search for)
- * [file domain](#parameters_file_domain) (optional, defaults to 'all my files')
- * `tag_service_key`: (optional, hexadecimal, the tag domain on which to search, defaults to 'all my files')
- * `file_sort_type`: (optional, integer, the results sort method, defaults to 'all known tags')
- * `file_sort_asc`: true or false (optional, the results sort order)
- * `return_file_ids`: true or false (optional, default true, returns file id results)
- * `return_hashes`: true or false (optional, default false, returns hex hash results)
+ * [file domain](#parameters_file_domain) (optional, defaults to _all my files_)
+ * `tag_service_key`: (optional, hexadecimal, the tag domain on which to search, defaults to _all my files_)
+ * `file_sort_type`: (optional, integer, the results sort method, defaults to `2` for `import time`)
+ * `file_sort_asc`: true or false (optional, default `true`, the results sort order)
+ * `return_file_ids`: true or false (optional, default `true`, returns file id results)
+ * `return_hashes`: true or false (optional, default `false`, returns hex hash results)
``` title='Example request for 16 files (system:limit=16) in the inbox with tags "blue eyes", "blonde hair", and "кино"'
/get_files/search_files?tags=%5B%22blue%20eyes%22%2C%20%22blonde%20hair%22%2C%20%22%5Cu043a%5Cu0438%5Cu043d%5Cu043e%22%2C%20%22system%3Ainbox%22%2C%20%22system%3Alimit%3D16%22%5D
@@ -2105,7 +2107,7 @@ Required Headers: n/a
Arguments (in percent-encoded JSON):
:
* [files](#parameters_files)
- * [file domain](#parameters_file_domain) (optional, defaults to 'all my files')
+ * [file domain](#parameters_file_domain) (optional, defaults to _all my files_)
``` title="Example request"
/manage_file_relationships/get_file_relationships?hash=ac940bb9026c430ea9530b4f4f6980a12d9432c2af8d9d39dfc67b05d91df11d
@@ -2166,7 +2168,7 @@ Required Headers: n/a
Arguments (in percent-encoded JSON):
:
- * [file domain](#parameters_file_domain) (optional, defaults to 'all my files')
+ * [file domain](#parameters_file_domain) (optional, defaults to _all my files_)
* `tag_service_key_1`: (optional, default 'all known tags', a hex tag service key)
* `tags_1`: (optional, default system:everything, a list of tags you wish to search for)
* `tag_service_key_2`: (optional, default 'all known tags', a hex tag service key)
@@ -2216,7 +2218,7 @@ Required Headers: n/a
Arguments (in percent-encoded JSON):
:
- * [file domain](#parameters_file_domain) (optional, defaults to 'all my files')
+ * [file domain](#parameters_file_domain) (optional, defaults to _all my files_)
* `tag_service_key_1`: (optional, default 'all known tags', a hex tag service key)
* `tags_1`: (optional, default system:everything, a list of tags you wish to search for)
* `tag_service_key_2`: (optional, default 'all known tags', a hex tag service key)
@@ -2261,7 +2263,7 @@ Required Headers: n/a
Arguments (in percent-encoded JSON):
:
- * [file domain](#parameters_file_domain) (optional, defaults to 'all my files')
+ * [file domain](#parameters_file_domain) (optional, defaults to _all my files_)
* `tag_service_key_1`: (optional, default 'all known tags', a hex tag service key)
* `tags_1`: (optional, default system:everything, a list of tags you wish to search for)
* `tag_service_key_2`: (optional, default 'all known tags', a hex tag service key)
@@ -3350,8 +3352,8 @@ Restricted access:
Arguments (in percent-encoded JSON):
:
* `tags`: (optional, a list of tags you wish to search for)
- * [file domain](#parameters_file_domain) (optional, defaults to 'all my files')
- * `tag_service_key`: (optional, hexadecimal, the tag domain on which to search, defaults to 'all my files')
+ * [file domain](#parameters_file_domain) (optional, defaults to _all my files_)
+ * `tag_service_key`: (optional, hexadecimal, the tag domain on which to search, defaults to _all my files_)
``` title="Example requests"
/manage_database/mr_bones
diff --git a/docs/old_changelog.html b/docs/old_changelog.html
index 447bbf4be..29d3ec936 100644
--- a/docs/old_changelog.html
+++ b/docs/old_changelog.html
@@ -34,6 +34,47 @@
+ -
+
+
+ misc
+ - thanks to a user, we have a much improved shimmie parser, for both file and gallery urls, that fetches md5 better, improves gallery navigation, stops grabbing bad urls and related tags by accident, and can handle namespaces for those shimmies that use them. for our purposes, this improves r34h and r34@paheal downloaders by default
+ - thanks to a user, we have a new 'Dark Blue 1.1' styesheet with some improvements. the recommendation is: check the different scrollbar styling to see if you prefer the old version
+ - timedelta widgets now enforce their minimum time on focus-out rather than value change. if it wants at least 20 minutes, you can now type in '5...' in the minutes column without it going nuts. let me know if you discover a way to out-fox the focus-out detection!
+ - added a checkbox to file import options to govern whether 'import destinations' and 'archive all imports' apply to 'already in db' files. this turns on/off the logic that I made more reliable last week. default is that they do
+ - added 'do sleep check' to _options->system_ to try some things out on systems that often false-positive this check
+ - the 'review current network jobs' multi-column list has a new right-click menu to show a bit more debug info about each job: each of its network contexts, how the bandwidth is on each context, if the domain is ok, if it is waiting on a connection error, if it is waiting on serverside bandwidth, if it obey bandwidth, and if its tokens are ok. if you have been working with me on gallery jobs that just sit on 'starting soon', please check it out and let me know what you see. also, 'review current network jobs' is duplicated to the help->debug menu. I forgot where it was, so let's have it in both places
+ - on the filename-import tagging panel, the filename and directory checkbox-and-text-edit widgets no longer emit a (sometimes laggy) update signal when typing when the checkbox is unchecked
+ janitor stuff
+ - if you are a repository janitor, right-clicking on any tag shows a new 'admin' menu
+ - if you have 'change options' permission, you will see 'block x'/'re-allow x' to let you quickly see if tags are blocked and then edit the repository tag filter respectively
+ - if you have 'mappings petition resolution' permission, you can 'purge' the selected tags, which will deleted them from the service entirely. this launches a review window that previews the job and allows adding of more tags using the standard autocomplete interface. when 'fired off', it launches a tag migration job to queue up the full petition/delete upload
+ - this new 'purge' window is also available from the normal 'administrate services' menu in the main gui
+ - also under the 'administrate services' is a new 'purge tag filter' command, which applies the existing repository tag filter to its own mappings store, retroactively syncing you to it
+ tag filters and migration
+ - I wrote a database routine that quickly converts a hydrus tag filter into the list of tags within a file and tag search context. this tech will have a variety of uses in the genre of 'hey please delete/fetch/check all these tags'
+ - to start with, it is now plugged into the tag migration system, so when you set up, say, an 'all known files' tag migration that only looks for a namespace or a bunch of single tags, the 'setup' phase is now massively, massively faster (previously, with something like the PTR, this would be scanning through tens of millions of files for minutes; now it just targets the 50k or whatever using existing tag search tech usually within less than a second)
+ - cleaned (KISSed) and reworked the tag filter logic a bit--it can now, underlyingly, handle 'no namespaced tags, except for creator:anything, but still allowing creator:blah'
+ - optimised how tag filters do 'apply unnamespaced rules to namespaced tags' (which happens in some blacklists that want to be expansive)
+ - improved how the tag filter describes itself in many cases. it should make more grammatical sense and repeat itself less now (e.g. no more 'all tags and namespaced tags and unnamespaced tags' rubbish)
+ - improved how some tag filter rules are handled across the program, including fixing some edge-case false-positive namespace-rule detection
+ - deleted some ancient and no longer used tag filtering code
+ boring multi-column list stuff
+ - did more 'select, sort, and scroll' code cleanup in my multi-column lists, specifically: manage import folders; manage export folders; the string-to-string dict list; edit ngug; edit downloader display (both gugs and url classes, and with a one-shot show/hide choice on a multi-selection rather than asking for each in turn); the special 'duplicate' command of edit shortcut set; and the string converter conversions list (including better select logic on move up/down)
+ - in keeping with the new general policy of 'when you edit a multi-column list, you just edit one row', the various 'edit' buttons under these lists across the program are now generally only enabled when you have one row selected
+ - the new 'select, sort, and scroll to new item when a human adds it' tech now _deselects_ the previous selection. let me know if this screws up anywhere (maybe in a hacky multi-add somewhere it'll only select the last added?)
+ - the aggravating 'clear the focus of the list on most changes bro' jank seems to be fixed--it was a dumb legacy thing
+ - whenever the multi-column list does its new 'scroll-to' action, it now takes focus to better highlight where we are (rather than stay, for instance, leaving focus on the 'add' button you just clicked)
+ other boring stuff
+ - worked a little more on a routine that collapses an arbitrary list of strings to a human-presentable summary and replaced the hardcoded hacky version that presents the 'paste queries' result in the 'edit subscription' panel with it
+ - wrote a similar new routine to collapse an arbitrary list of strings to a single-line summary, appropriate for menu labels and such
+ - fixed a layout issue in the 'manage downloader display' dialog that caused the 'edit' button on the 'media viewer urls' side to not show, lmaooooooo
+ - ephemeral 'watcher' and 'gallery' network contexts now describe themselves with a nicer string
+ - decoupled how some service admin stuff works behind the scenes to make it easier to launch this stuff from different UI widgets
+ - refactored `ToHumanInt` and the `ToPrettyOrdinalString` guys to a new `HydrusNumbers.py` file
+ - fixed some bad Client API documentation for the params in `/get_files/search_files`
+
+
-
diff --git a/hydrus/client/ClientAPI.py b/hydrus/client/ClientAPI.py
index 5e974d6c2..5a76950ce 100644
--- a/hydrus/client/ClientAPI.py
+++ b/hydrus/client/ClientAPI.py
@@ -3,7 +3,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusTime
@@ -360,7 +360,7 @@ def CheckPermissionToSeeFiles( self, hash_ids: typing.Collection[ int ] ):
if num_files_allowed_to_see != num_files_asked_for:
- error_text = f'You do not seem to have access to all those files! You asked to see {HydrusData.ToHumanInt( num_files_asked_for )} files, but you were only authorised to see {HydrusData.ToHumanInt( num_files_allowed_to_see )} of them!'
+ error_text = f'You do not seem to have access to all those files! You asked to see {HydrusNumbers.ToHumanInt( num_files_asked_for )} files, but you were only authorised to see {HydrusNumbers.ToHumanInt( num_files_allowed_to_see )} of them!'
raise HydrusExceptions.InsufficientCredentialsException( error_text )
diff --git a/hydrus/client/ClientApplicationCommand.py b/hydrus/client/ClientApplicationCommand.py
index 689d18716..47361b7cf 100644
--- a/hydrus/client/ClientApplicationCommand.py
+++ b/hydrus/client/ClientApplicationCommand.py
@@ -3,6 +3,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
@@ -1012,7 +1013,7 @@ def ToString( self ):
elif rearrange_type == REARRANGE_THUMBNAILS_TYPE_FIXED:
- s = f'{s} (to index {HydrusData.ToHumanInt(rearrange_data)})'
+ s = f'{s} (to index {HydrusNumbers.ToHumanInt(rearrange_data)})'
diff --git a/hydrus/client/ClientController.py b/hydrus/client/ClientController.py
index 0584fd751..c4c43db99 100644
--- a/hydrus/client/ClientController.py
+++ b/hydrus/client/ClientController.py
@@ -18,6 +18,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusThreading
from hydrus.core import HydrusTime
@@ -858,7 +859,7 @@ def FlipQueryPlannerMode( self ):
HG.queries_planned = set()
- HydrusData.ShowText( 'Query Planning done: {} queries analyzed'.format( HydrusData.ToHumanInt( HG.query_planner_query_count ) ) )
+ HydrusData.ShowText( 'Query Planning done: {} queries analyzed'.format( HydrusNumbers.ToHumanInt( HG.query_planner_query_count ) ) )
@@ -889,7 +890,7 @@ def FlipProfileMode( self ):
( slow, fast ) = ( HG.profile_slow_count, HG.profile_fast_count )
- HydrusData.ShowText( 'Profiling done: {} slow jobs, {} fast jobs'.format( HydrusData.ToHumanInt( slow ), HydrusData.ToHumanInt( fast ) ) )
+ HydrusData.ShowText( 'Profiling done: {} slow jobs, {} fast jobs'.format( HydrusNumbers.ToHumanInt( slow ), HydrusNumbers.ToHumanInt( fast ) ) )
@@ -2169,6 +2170,18 @@ def ShutdownView( self ):
HydrusController.HydrusController.ShutdownView( self )
+ def SleepCheck( self ) -> None:
+
+ if not self.new_options.GetBoolean( 'do_sleep_check' ):
+
+ self._just_woke_from_sleep = False
+
+ return
+
+
+ HydrusController.HydrusController.SleepCheck( self )
+
+
def SynchroniseAccounts( self ):
if CG.client_controller.new_options.GetBoolean( 'pause_all_new_network_traffic' ):
diff --git a/hydrus/client/ClientDownloading.py b/hydrus/client/ClientDownloading.py
index 9f958e745..e169515ec 100644
--- a/hydrus/client/ClientDownloading.py
+++ b/hydrus/client/ClientDownloading.py
@@ -4,6 +4,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTemp
from hydrus.core import HydrusThreading
@@ -396,7 +397,7 @@ def MainLoop( self ):
if total_successful_hashes_in_this_run > 0:
- job_status.SetStatusText( HydrusData.ToHumanInt( total_successful_hashes_in_this_run ) + ' files downloaded' )
+ job_status.SetStatusText( HydrusNumbers.ToHumanInt( total_successful_hashes_in_this_run ) + ' files downloaded' )
job_status_pub_job.Cancel()
diff --git a/hydrus/client/ClientFiles.py b/hydrus/client/ClientFiles.py
index da83ef3ea..43558b65d 100644
--- a/hydrus/client/ClientFiles.py
+++ b/hydrus/client/ClientFiles.py
@@ -10,6 +10,7 @@
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusThreading
from hydrus.core import HydrusTime
@@ -523,7 +524,7 @@ def _AttemptToHealMissingLocations( self ):
if len( correct_rows ) > 0:
- summaries = sorted( ( '{} folders seem to have moved from {} to {}'.format( HydrusData.ToHumanInt( count ), missing_base_location, correct_base_location ) for ( ( missing_base_location, correct_base_location ), count ) in fixes_counter.items() ) )
+ summaries = sorted( ( '{} folders seem to have moved from {} to {}'.format( HydrusNumbers.ToHumanInt( count ), missing_base_location, correct_base_location ) for ( ( missing_base_location, correct_base_location ), count ) in fixes_counter.items() ) )
summary_message = 'Some client file folders were missing, but they appear to be in other known locations! The folders are:'
summary_message += '\n' * 2
@@ -1316,7 +1317,7 @@ def ClearOrphans( self, move_location = None ):
if num_files_reviewed % 100 == 0:
- status = 'reviewed ' + HydrusData.ToHumanInt( num_files_reviewed ) + ' files, found ' + HydrusData.ToHumanInt( len( orphan_paths ) ) + ' orphans'
+ status = 'reviewed ' + HydrusNumbers.ToHumanInt( num_files_reviewed ) + ' files, found ' + HydrusNumbers.ToHumanInt( len( orphan_paths ) ) + ' orphans'
job_status.SetStatusText( status, level = 2 )
@@ -1325,7 +1326,7 @@ def ClearOrphans( self, move_location = None ):
if num_thumbnails_reviewed % 100 == 0:
- status = 'reviewed ' + HydrusData.ToHumanInt( num_thumbnails_reviewed ) + ' thumbnails, found ' + HydrusData.ToHumanInt( len( orphan_thumbnails ) ) + ' orphans'
+ status = 'reviewed ' + HydrusNumbers.ToHumanInt( num_thumbnails_reviewed ) + ' thumbnails, found ' + HydrusNumbers.ToHumanInt( len( orphan_thumbnails ) ) + ' orphans'
job_status.SetStatusText( status, level = 2 )
@@ -1415,7 +1416,7 @@ def ClearOrphans( self, move_location = None ):
if len( orphan_paths ) > 0:
- status = 'found ' + HydrusData.ToHumanInt( len( orphan_paths ) ) + ' orphan files, now deleting'
+ status = 'found ' + HydrusNumbers.ToHumanInt( len( orphan_paths ) ) + ' orphan files, now deleting'
job_status.SetStatusText( status )
@@ -1442,7 +1443,7 @@ def ClearOrphans( self, move_location = None ):
if len( orphan_thumbnails ) > 0:
- status = 'found ' + HydrusData.ToHumanInt( len( orphan_thumbnails ) ) + ' orphan thumbnails, now deleting'
+ status = 'found ' + HydrusNumbers.ToHumanInt( len( orphan_thumbnails ) ) + ' orphan thumbnails, now deleting'
job_status.SetStatusText( status )
@@ -1474,7 +1475,7 @@ def ClearOrphans( self, move_location = None ):
else:
- final_text = HydrusData.ToHumanInt( len( orphan_paths ) ) + ' orphan files and ' + HydrusData.ToHumanInt( len( orphan_thumbnails ) ) + ' orphan thumbnails cleared!'
+ final_text = HydrusNumbers.ToHumanInt( len( orphan_paths ) ) + ' orphan files and ' + HydrusNumbers.ToHumanInt( len( orphan_thumbnails ) ) + ' orphan thumbnails cleared!'
job_status.SetStatusText( final_text )
@@ -1606,7 +1607,7 @@ def DoDeferredPhysicalDeletes( self ):
self._controller.pub( 'notify_new_physical_file_delete_numbers' )
- HydrusData.Print( 'Physically deleted {} files and {} thumbnails from file storage.'.format( HydrusData.ToHumanInt( num_files_deleted ), HydrusData.ToHumanInt( num_files_deleted ) ) )
+ HydrusData.Print( 'Physically deleted {} files and {} thumbnails from file storage.'.format( HydrusNumbers.ToHumanInt( num_files_deleted ), HydrusNumbers.ToHumanInt( num_files_deleted ) ) )
@@ -2022,12 +2023,12 @@ def add_extra_comments_to_job_status( job_status: ClientThreading.JobStatus ):
if num_thumb_refits is not None:
- extra_comments.append( 'thumbs needing regen: {}'.format( HydrusData.ToHumanInt( num_thumb_refits ) ) )
+ extra_comments.append( 'thumbs needing regen: {}'.format( HydrusNumbers.ToHumanInt( num_thumb_refits ) ) )
if num_bad_files is not None:
- extra_comments.append( 'missing or invalid files: {}'.format( HydrusData.ToHumanInt( num_bad_files ) ) )
+ extra_comments.append( 'missing or invalid files: {}'.format( HydrusNumbers.ToHumanInt( num_bad_files ) ) )
sub_status_message = '\n'.join( extra_comments )
diff --git a/hydrus/client/ClientLocation.py b/hydrus/client/ClientLocation.py
index 122dc8551..a119d3079 100644
--- a/hydrus/client/ClientLocation.py
+++ b/hydrus/client/ClientLocation.py
@@ -3,6 +3,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
@@ -323,7 +324,7 @@ def ToString( self, name_method ):
else:
- service_string = '{} services'.format( HydrusData.ToHumanInt( len( service_keys_to_consider ) ) )
+ service_string = '{} services'.format( HydrusNumbers.ToHumanInt( len( service_keys_to_consider ) ) )
return prefix + service_string
diff --git a/hydrus/client/ClientMigration.py b/hydrus/client/ClientMigration.py
index c8c10ce6c..281f34640 100644
--- a/hydrus/client/ClientMigration.py
+++ b/hydrus/client/ClientMigration.py
@@ -261,6 +261,13 @@ def DoSomeWork( self, source ):
class MigrationDestinationTagServiceMappings( MigrationDestinationTagService ):
+ def __init__( self, controller, tag_service_key, content_action ):
+
+ MigrationDestinationTagService.__init__( self, controller, tag_service_key, content_action )
+
+ self._reason = 'Mass Migration Job'
+
+
def DoSomeWork( self, source ):
time_started_precise = HydrusTime.GetNowPrecise()
@@ -282,7 +289,7 @@ def DoSomeWork( self, source ):
if self._content_action == HC.CONTENT_UPDATE_PETITION:
- reason = 'Mass Migration Job'
+ reason = self._reason
else:
@@ -301,6 +308,12 @@ def DoSomeWork( self, source ):
return GetBasicSpeedStatement( num_done, time_started_precise )
+ def SetReason( self, reason: str ):
+
+ self._reason = reason
+
+
+
class MigrationDestinationTagServicePairs( MigrationDestinationTagService ):
def __init__( self, controller, tag_service_key, content_action, content_type ):
@@ -721,7 +734,7 @@ def Prepare( self ):
# later can spread this out into bunch of small jobs, a start and a continue, based on tag filter subsets
- self._controller.WriteSynchronous( 'migration_start_mappings_job', self._database_temp_job_name, self._location_context, self._tag_service_key, self._hashes, self._content_statuses )
+ self._controller.WriteSynchronous( 'migration_start_mappings_job', self._database_temp_job_name, self._location_context, self._tag_service_key, self._tag_filter, self._hashes, self._content_statuses )
class MigrationSourceTagServicePairs( MigrationSource ):
diff --git a/hydrus/client/ClientOptions.py b/hydrus/client/ClientOptions.py
index 4ad0a49c0..e2637a54c 100644
--- a/hydrus/client/ClientOptions.py
+++ b/hydrus/client/ClientOptions.py
@@ -255,7 +255,8 @@ def _InitialiseDefaults( self ):
'slideshow_always_play_duration_media_once_through' : False,
'enable_truncated_images_pil' : True,
'do_icc_profile_normalisation' : True,
- 'mpv_available_at_start' : ClientGUIMPV.MPV_IS_AVAILABLE
+ 'mpv_available_at_start' : ClientGUIMPV.MPV_IS_AVAILABLE,
+ 'do_sleep_check' : True
}
#
diff --git a/hydrus/client/ClientParsing.py b/hydrus/client/ClientParsing.py
index c9d8e9df8..f8ab80d56 100644
--- a/hydrus/client/ClientParsing.py
+++ b/hydrus/client/ClientParsing.py
@@ -12,6 +12,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
@@ -712,7 +713,7 @@ def RenderJSONParseRule( rule ):
index = parse_rule
- s = 'get the ' + HydrusData.ConvertIndexToPrettyOrdinalString( index ) + ' item (for Objects, keys sorted)'
+ s = 'get the ' + HydrusNumbers.ConvertIndexToPrettyOrdinalString( index ) + ' item (for Objects, keys sorted)'
elif parse_rule_type == JSON_PARSE_RULE_TYPE_DICT_KEY:
@@ -804,7 +805,7 @@ def ParsePretty( self, parsing_context, parsing_text: str, collapse_newlines: bo
pretty_texts = [ MakeParsedTextPretty( text ) for text in texts ]
- pretty_texts = [ '*** ' + HydrusData.ToHumanInt( len( pretty_texts ) ) + ' RESULTS BEGIN ***' ] + pretty_texts + [ '*** RESULTS END ***' ]
+ pretty_texts = [ '*** ' + HydrusNumbers.ToHumanInt( len( pretty_texts ) ) + ' RESULTS BEGIN ***' ] + pretty_texts + [ '*** RESULTS END ***' ]
separator = self._GetParsePrettySeparator()
@@ -968,7 +969,7 @@ def GetSubstitutionPhrase( self ):
def ToPrettyString( self ):
- return 'COMPOUND with ' + HydrusData.ToHumanInt( len( self._formulae ) ) + ' formulae.'
+ return 'COMPOUND with ' + HydrusNumbers.ToHumanInt( len( self._formulae ) ) + ' formulae.'
def ToPrettyMultilineString( self ):
@@ -1406,7 +1407,7 @@ def ParsesSeparatedContent( self ):
def ToPrettyString( self ):
- return 'HTML with ' + HydrusData.ToHumanInt( len( self._tag_rules ) ) + ' tag rules.'
+ return 'HTML with ' + HydrusNumbers.ToHumanInt( len( self._tag_rules ) ) + ' tag rules.'
def ToPrettyMultilineString( self ):
@@ -1656,7 +1657,7 @@ def ToString( self ):
else:
- s += ' the ' + HydrusData.ConvertIndexToPrettyOrdinalString( self._tag_index )
+ s += ' the ' + HydrusNumbers.ConvertIndexToPrettyOrdinalString( self._tag_index )
if self._tag_name is not None:
@@ -1677,11 +1678,11 @@ def ToString( self ):
if self._tag_name is None:
- s += ' ' + HydrusData.ToHumanInt( self._tag_depth ) + ' tag levels'
+ s += ' ' + HydrusNumbers.ToHumanInt( self._tag_depth ) + ' tag levels'
else:
- s += ' to the ' + HydrusData.ConvertIntToPrettyOrdinalString( self._tag_depth ) + ' <' + self._tag_name + '> tag'
+ s += ' to the ' + HydrusNumbers.ConvertIntToPrettyOrdinalString( self._tag_depth ) + ' <' + self._tag_name + '> tag'
@@ -1996,7 +1997,7 @@ def ParsesSeparatedContent( self ):
def ToPrettyString( self ):
- return 'JSON with ' + HydrusData.ToHumanInt( len( self._parse_rules ) ) + ' parse rules.'
+ return 'JSON with ' + HydrusNumbers.ToHumanInt( len( self._parse_rules ) ) + ' parse rules.'
def ToPrettyMultilineString( self ):
@@ -2446,7 +2447,7 @@ def ParsePretty( self, parsing_context, parsing_text: str ):
raise e
- result_lines = [ '*** ' + HydrusData.ToHumanInt( len( results ) ) + ' RESULTS BEGIN ***' ]
+ result_lines = [ '*** ' + HydrusNumbers.ToHumanInt( len( results ) ) + ' RESULTS BEGIN ***' ]
result_lines.extend( results )
@@ -2808,7 +2809,7 @@ def ParsePretty( self, parsing_context, parsing_text ):
result_lines = []
- result_lines.append( '*** ' + HydrusData.ToHumanInt( len( all_parse_results ) ) + ' RESULTS BEGIN ***' + '\n' )
+ result_lines.append( '*** ' + HydrusNumbers.ToHumanInt( len( all_parse_results ) ) + ' RESULTS BEGIN ***' + '\n' )
result_lines.append( pretty_parse_result_text )
@@ -3315,7 +3316,7 @@ def Parse( self, job_status, parsing_text ):
else:
- job_status.SetVariable( 'script_status', 'Found ' + HydrusData.ToHumanInt( len( parse_results ) ) + ' rows.' )
+ job_status.SetVariable( 'script_status', 'Found ' + HydrusNumbers.ToHumanInt( len( parse_results ) ) + ' rows.' )
return parse_results
diff --git a/hydrus/client/ClientSerialisable.py b/hydrus/client/ClientSerialisable.py
index 32e2b7a88..61a8c7c58 100644
--- a/hydrus/client/ClientSerialisable.py
+++ b/hydrus/client/ClientSerialisable.py
@@ -10,7 +10,7 @@
from hydrus.core import HydrusCompression
from hydrus.core import HydrusData
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTemp
@@ -216,7 +216,7 @@ def GetPayloadTypeString( payload_obj ):
type_string_counts[ GetPayloadTypeString( o ) ] += 1
- type_string = ', '.join( ( HydrusData.ToHumanInt( count ) + ' ' + s for ( s, count ) in list(type_string_counts.items()) ) )
+ type_string = ', '.join( ( HydrusNumbers.ToHumanInt( count ) + ' ' + s for ( s, count ) in list(type_string_counts.items()) ) )
return 'A list of ' + type_string
diff --git a/hydrus/client/ClientServices.py b/hydrus/client/ClientServices.py
index 3b5747dac..a08a1e1f1 100644
--- a/hydrus/client/ClientServices.py
+++ b/hydrus/client/ClientServices.py
@@ -13,6 +13,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
@@ -535,7 +536,7 @@ def ConvertNoneableRatingToString( self, rating: typing.Optional[ int ] ):
elif isinstance( rating, int ):
- return HydrusData.ToHumanInt( rating )
+ return HydrusNumbers.ToHumanInt( rating )
return 'unknown'
@@ -545,7 +546,7 @@ def ConvertRatingStateAndRatingToString( self, rating_state: int, rating: float
if rating_state == ClientRatings.SET:
- return HydrusData.ToHumanInt( rating )
+ return HydrusNumbers.ToHumanInt( rating )
elif rating_state == ClientRatings.MIXED:
@@ -1639,9 +1640,9 @@ def _LogFinalRowSpeed( self, precise_timestamp, total_rows, row_name ):
it_took = HydrusTime.GetNowPrecise() - precise_timestamp
- rows_s = HydrusData.ToHumanInt( int( total_rows / it_took ) )
+ rows_s = HydrusNumbers.ToHumanInt( int( total_rows / it_took ) )
- summary = '{} processed {} {} at {} rows/s'.format( self._name, HydrusData.ToHumanInt( total_rows ), row_name, rows_s )
+ summary = '{} processed {} {} at {} rows/s'.format( self._name, HydrusNumbers.ToHumanInt( total_rows ), row_name, rows_s )
HydrusData.Print( summary )
@@ -1650,7 +1651,7 @@ def _ReportOngoingRowSpeed( self, job_status, rows_done, total_rows, precise_tim
it_took = HydrusTime.GetNowPrecise() - precise_timestamp
- rows_s = HydrusData.ToHumanInt( int( rows_done_in_last_packet / it_took ) )
+ rows_s = HydrusNumbers.ToHumanInt( int( rows_done_in_last_packet / it_took ) )
popup_message = '{} {}: processing at {} rows/s'.format( row_name, HydrusData.ConvertValueRangeToPrettyString( rows_done, total_rows ), rows_s )
diff --git a/hydrus/client/ClientStrings.py b/hydrus/client/ClientStrings.py
index 92046faf5..bb27e16f8 100644
--- a/hydrus/client/ClientStrings.py
+++ b/hydrus/client/ClientStrings.py
@@ -10,9 +10,9 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
-from hydrus.core import HydrusText
from hydrus.core import HydrusTime
from hydrus.client import ClientTime
@@ -382,7 +382,7 @@ def ToString( self, simple = False, with_type = False ) -> str:
if simple:
- label = '{} changes'.format( HydrusData.ToHumanInt( num_rules ) )
+ label = '{} changes'.format( HydrusNumbers.ToHumanInt( num_rules ) )
else:
@@ -405,19 +405,19 @@ def ConversionToString( conversion ):
if conversion_type == STRING_CONVERSION_REMOVE_TEXT_FROM_BEGINNING:
- return 'remove the first ' + HydrusData.ToHumanInt( data ) + ' characters'
+ return 'remove the first ' + HydrusNumbers.ToHumanInt( data ) + ' characters'
elif conversion_type == STRING_CONVERSION_REMOVE_TEXT_FROM_END:
- return 'remove the last ' + HydrusData.ToHumanInt( data ) + ' characters'
+ return 'remove the last ' + HydrusNumbers.ToHumanInt( data ) + ' characters'
elif conversion_type == STRING_CONVERSION_CLIP_TEXT_FROM_BEGINNING:
- return 'take the first ' + HydrusData.ToHumanInt( data ) + ' characters'
+ return 'take the first ' + HydrusNumbers.ToHumanInt( data ) + ' characters'
elif conversion_type == STRING_CONVERSION_CLIP_TEXT_FROM_END:
- return 'take the last ' + HydrusData.ToHumanInt( data ) + ' characters'
+ return 'take the last ' + HydrusNumbers.ToHumanInt( data ) + ' characters'
elif conversion_type == STRING_CONVERSION_PREPEND_TEXT:
@@ -431,7 +431,7 @@ def ConversionToString( conversion ):
( population_text, num_chars ) = data
- return f'append with {HydrusData.ToHumanInt( num_chars )} random characters, from "{population_text}"'
+ return f'append with {HydrusNumbers.ToHumanInt( num_chars )} random characters, from "{population_text}"'
elif conversion_type == STRING_CONVERSION_ENCODE:
@@ -705,12 +705,12 @@ def Test( self, text ):
if self._min_chars is not None and text_len < self._min_chars:
- raise HydrusExceptions.StringMatchException( presentation_text + ' had fewer than ' + HydrusData.ToHumanInt( self._min_chars ) + ' characters' )
+ raise HydrusExceptions.StringMatchException( presentation_text + ' had fewer than ' + HydrusNumbers.ToHumanInt( self._min_chars ) + ' characters' )
if self._max_chars is not None and text_len > self._max_chars:
- raise HydrusExceptions.StringMatchException( presentation_text + ' had more than ' + HydrusData.ToHumanInt( self._max_chars ) + ' characters' )
+ raise HydrusExceptions.StringMatchException( presentation_text + ' had more than ' + HydrusNumbers.ToHumanInt( self._max_chars ) + ' characters' )
if self._match_type == STRING_MATCH_FIXED:
@@ -978,7 +978,7 @@ def ToString( self, simple = False, with_type = False ) -> str:
elif self.SelectsOne():
- result = 'selecting the {} string'.format( HydrusData.ConvertIndexToPrettyOrdinalString( self._index_start ) )
+ result = 'selecting the {} string'.format( HydrusNumbers.ConvertIndexToPrettyOrdinalString( self._index_start ) )
elif self._index_start is None and self._index_end is None:
@@ -986,15 +986,15 @@ def ToString( self, simple = False, with_type = False ) -> str:
elif self._index_start is not None and self._index_end is None:
- result = 'selecting the {} string and onwards'.format( HydrusData.ConvertIndexToPrettyOrdinalString( self._index_start ) )
+ result = 'selecting the {} string and onwards'.format( HydrusNumbers.ConvertIndexToPrettyOrdinalString( self._index_start ) )
elif self._index_start is None and self._index_end is not None:
- result = 'selecting up to and including the {} string'.format( HydrusData.ConvertIndexToPrettyOrdinalString( self._index_end - 1 ) )
+ result = 'selecting up to and including the {} string'.format( HydrusNumbers.ConvertIndexToPrettyOrdinalString( self._index_end - 1 ) )
else:
- result = 'selecting the {} string up to and including the {} string'.format( HydrusData.ConvertIndexToPrettyOrdinalString( self._index_start ), HydrusData.ConvertIndexToPrettyOrdinalString( self._index_end - 1 ) )
+ result = 'selecting the {} string up to and including the {} string'.format( HydrusNumbers.ConvertIndexToPrettyOrdinalString( self._index_start ), HydrusNumbers.ConvertIndexToPrettyOrdinalString( self._index_end - 1 ) )
if with_type:
@@ -1259,7 +1259,7 @@ def ToString( self, simple = False, with_type = False ) -> str:
if self._max_splits is not None:
- result = '{}, at most {} times'.format( result, HydrusData.ToHumanInt( self._max_splits ) )
+ result = '{}, at most {} times'.format( result, HydrusNumbers.ToHumanInt( self._max_splits ) )
if with_type:
diff --git a/hydrus/client/db/ClientDB.py b/hydrus/client/db/ClientDB.py
index 755e1eba5..981f5e978 100644
--- a/hydrus/client/db/ClientDB.py
+++ b/hydrus/client/db/ClientDB.py
@@ -18,6 +18,7 @@
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
@@ -176,7 +177,7 @@ def report_content_speed_to_job_status( job_status, rows_done, total_rows, preci
it_took = HydrusTime.GetNowPrecise() - precise_timestamp
- rows_s = HydrusData.ToHumanInt( int( num_rows / it_took ) )
+ rows_s = HydrusNumbers.ToHumanInt( int( num_rows / it_took ) )
popup_message = 'content row ' + HydrusData.ConvertValueRangeToPrettyString( rows_done, total_rows ) + ': processing ' + row_name + ' at ' + rows_s + ' rows/s'
@@ -187,7 +188,7 @@ def report_speed_to_job_status( job_status, precise_timestamp, num_rows, row_nam
it_took = HydrusTime.GetNowPrecise() - precise_timestamp
- rows_s = HydrusData.ToHumanInt( int( num_rows / it_took ) )
+ rows_s = HydrusNumbers.ToHumanInt( int( num_rows / it_took ) )
popup_message = 'processing ' + row_name + ' at ' + rows_s + ' rows/s'
@@ -203,9 +204,9 @@ def report_speed_to_log( precise_timestamp, num_rows, row_name ):
it_took = HydrusTime.GetNowPrecise() - precise_timestamp
- rows_s = HydrusData.ToHumanInt( int( num_rows / it_took ) )
+ rows_s = HydrusNumbers.ToHumanInt( int( num_rows / it_took ) )
- summary = 'processed ' + HydrusData.ToHumanInt( num_rows ) + ' ' + row_name + ' at ' + rows_s + ' rows/s'
+ summary = 'processed ' + HydrusNumbers.ToHumanInt( num_rows ) + ' ' + row_name + ' at ' + rows_s + ' rows/s'
HydrusData.Print( summary )
@@ -1163,7 +1164,7 @@ def _ClearOrphanFileRecords( self ):
# with fingers crossed this magically corrects all sorts of stuff
self._AddFiles( umbrella_master_service_id, import_rows )
- HydrusData.ShowText( 'Found and recovered {} records for files that were safely in specific component services components but not the master "{}". I have opened a new page with these files--they may have been faulty imports or faulty deletes, so you probably need to give them a look.'.format( HydrusData.ToHumanInt( len( in_components_not_in_master ) ), description ) )
+ HydrusData.ShowText( 'Found and recovered {} records for files that were safely in specific component services components but not the master "{}". I have opened a new page with these files--they may have been faulty imports or faulty deletes, so you probably need to give them a look.'.format( HydrusNumbers.ToHumanInt( len( in_components_not_in_master ) ), description ) )
service_key = self.modules_services.GetServiceKey( umbrella_master_service_id )
@@ -1200,7 +1201,7 @@ def _ClearOrphanFileRecords( self ):
self.modules_hashes_local_cache.DropHashIdsFromCache( in_components_not_in_master )
- HydrusData.ShowText( 'Found and deleted {} records for files that were in specific service components but not the master "{}".'.format( HydrusData.ToHumanInt( len( in_components_not_in_master ) ), description ) )
+ HydrusData.ShowText( 'Found and deleted {} records for files that were in specific service components but not the master "{}".'.format( HydrusNumbers.ToHumanInt( len( in_components_not_in_master ) ), description ) )
@@ -1219,7 +1220,7 @@ def _ClearOrphanFileRecords( self ):
self._DeleteFiles( umbrella_master_service_id, in_master_not_in_components )
- HydrusData.ShowText( 'Found and deleted {} records for files that were in the master "{}" but not it its specific service components.'.format( HydrusData.ToHumanInt( len( in_master_not_in_components ) ), description ) )
+ HydrusData.ShowText( 'Found and deleted {} records for files that were in the master "{}" but not it its specific service components.'.format( HydrusNumbers.ToHumanInt( len( in_master_not_in_components ) ), description ) )
@@ -2414,7 +2415,7 @@ def _FixLogicallyInconsistentMappings( self, tag_service_key = None ):
self._controller.pub( 'notify_new_pending' )
- HydrusData.ShowText( 'Found {} bad mappings! They _should_ be deleted, and your pending counts should be updated.'.format( HydrusData.ToHumanInt( total_fixed ) ) )
+ HydrusData.ShowText( 'Found {} bad mappings! They _should_ be deleted, and your pending counts should be updated.'.format( HydrusNumbers.ToHumanInt( total_fixed ) ) )
job_status.DeleteStatusText( 2 )
@@ -3635,7 +3636,7 @@ def _GetMaintenanceDue( self, stop_time ):
if len( names_to_analyze ) > 0:
- jobs_to_do.append( 'analyze ' + HydrusData.ToHumanInt( len( names_to_analyze ) ) + ' table_names' )
+ jobs_to_do.append( 'analyze ' + HydrusNumbers.ToHumanInt( len( names_to_analyze ) ) + ' table_names' )
similar_files_due = self.modules_similar_files.MaintenanceDue()
@@ -4820,7 +4821,7 @@ def _GetTrashHashes( self, limit = None, minimum_age = None ):
else:
- message += 'at most ' + HydrusData.ToHumanInt( limit )
+ message += 'at most ' + HydrusNumbers.ToHumanInt( limit )
message += ' trash files,'
@@ -4830,7 +4831,7 @@ def _GetTrashHashes( self, limit = None, minimum_age = None ):
message += ' with minimum age ' + ClientTime.TimestampToPrettyTimeDelta( timestamp_cutoff, just_now_threshold = 0 ) + ','
- message += ' I found ' + HydrusData.ToHumanInt( len( hash_ids ) ) + '.'
+ message += ' I found ' + HydrusNumbers.ToHumanInt( len( hash_ids ) ) + '.'
HydrusData.ShowText( message )
@@ -5454,67 +5455,159 @@ def _MigrationGetPairs( self, database_temp_job_name, left_tag_filter, right_tag
return data
- def _MigrationStartMappingsJob( self, database_temp_job_name, location_context: ClientLocation.LocationContext, tag_service_key, hashes, content_statuses ):
+ def _MigrationStartMappingsJob(
+ self,
+ database_temp_job_name,
+ location_context: ClientLocation.LocationContext,
+ tag_service_key,
+ tag_filter: HydrusTags.TagFilter,
+ hashes: typing.Collection[ bytes ],
+ content_statuses: typing.Collection[ int ]
+ ):
- self._Execute( 'CREATE TABLE IF NOT EXISTS durable_temp.{} ( hash_id INTEGER PRIMARY KEY );'.format( database_temp_job_name ) )
+ # the overall migration loop loads files and checks if they have the tags. thus:
+ # this guy can deliver files that are in the file domain but which do not have the tags
+ # it must not deliver files outside of the domain that do have the tags!
+
+ tag_service_id = self.modules_services.GetServiceId( tag_service_key )
+
+ self._Execute( f'CREATE TABLE IF NOT EXISTS durable_temp.{database_temp_job_name} ( hash_id INTEGER PRIMARY KEY );' )
if hashes is not None:
- hash_ids = self.modules_hashes_local_cache.GetHashIds( hashes )
+ # hashes
- self._ExecuteMany( 'INSERT INTO {} ( hash_id ) VALUES ( ? );'.format( database_temp_job_name ), ( ( hash_id, ) for hash_id in hash_ids ) )
+ hash_ids = self.modules_hashes_local_cache.GetHashIds( hashes )
- else:
+ self._ExecuteMany( f'INSERT INTO {database_temp_job_name} ( hash_id ) VALUES ( ? );', ( ( hash_id, ) for hash_id in hash_ids ) )
- tag_service_id = self.modules_services.GetServiceId( tag_service_key )
+ elif not tag_filter.AllowsEverything():
- use_hashes_table = False
+ # no hashes but a tag filter
- if location_context.IsAllKnownFiles():
-
- # if our tag service is the biggest, and if it basically accounts for all the hashes we know about, it is much faster to just use the hashes table
+ with self._MakeTemporaryIntegerTable( [], 'tag_id' ) as temp_tag_ids_table_name:
- our_results = self._GetServiceInfo( tag_service_key )
-
- our_num_files = our_results[ HC.SERVICE_INFO_NUM_FILE_HASHES ]
+ select_subqueries = []
- other_services = [ service for service in self.modules_services.GetServices( HC.REAL_TAG_SERVICES ) if service.GetServiceKey() != tag_service_key ]
+ my_search_includes_deleted_tags = HC.CONTENT_STATUS_DELETED in content_statuses
- other_num_files = []
+ statuses_to_table_names = self.modules_mappings_storage.GetFastestStorageMappingTableNamesFromLocationContext( location_context, tag_service_id )
- for other_service in other_services:
+ if location_context.IsAllKnownFiles():
- other_results = self._GetServiceInfo( other_service.GetServiceKey() )
+ self.modules_tag_search.PopulateTableFromTagFilter( self.modules_services.combined_file_service_id, tag_service_id, tag_filter, temp_tag_ids_table_name, my_search_includes_deleted_tags )
- other_num_files.append( other_results[ HC.SERVICE_INFO_NUM_FILE_HASHES ] )
+ for content_status in content_statuses:
+
+ mappings_table_name = statuses_to_table_names[ content_status ]
+
+ select_subquery = f'SELECT DISTINCT hash_id FROM {temp_tag_ids_table_name} CROSS JOIN {mappings_table_name} USING ( tag_id )'
+
+ select_subqueries.append( select_subquery )
+
-
- if len( other_num_files ) == 0:
+ else:
- we_are_big = True
+ # we need to be cross-referenced here
- else:
+ if len( location_context.deleted_service_keys ) > 0:
+
+ self.modules_tag_search.PopulateTableFromTagFilter( self.modules_services.combined_deleted_file_service_id, tag_service_id, tag_filter, temp_tag_ids_table_name, my_search_includes_deleted_tags )
+
- we_are_big = our_num_files >= 0.75 * max( other_num_files )
+ for file_service_key in location_context.current_service_keys:
+
+ self.modules_tag_search.PopulateTableFromTagFilter( self.modules_services.GetServiceId( file_service_key ), tag_service_id, tag_filter, temp_tag_ids_table_name, my_search_includes_deleted_tags )
+
-
- if we_are_big:
+ # we need to return a properly cross-referenced result, but what we have done above may include tags for extra files because of umbrella domains
- local_files_results = self._GetServiceInfo( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
+ db_location_context = self.modules_files_storage.GetDBLocationContext( location_context )
- local_files_num_files = local_files_results[ HC.SERVICE_INFO_NUM_FILES ]
+ statuses_to_table_names = self.modules_mappings_storage.GetFastestStorageMappingTableNamesFromLocationContext( location_context, tag_service_id )
- if local_files_num_files > our_num_files:
+ for content_status in content_statuses:
- # probably a small local tags service, ok to pull from current_mappings
+ mappings_table_name = statuses_to_table_names[ content_status ]
- we_are_big = False
+ if db_location_context.SingleTableIsFast():
+
+ files_table_names = ( db_location_context.GetSingleFilesTableName(), )
+
+ else:
+
+ files_table_names = db_location_context.GetMultipleFilesTableNames()
+
+
+ for files_table_name in files_table_names:
+
+ select_subquery = f'SELECT DISTINCT hash_id FROM {temp_tag_ids_table_name} CROSS JOIN {mappings_table_name} USING ( tag_id ) CROSS JOIN {files_table_name} USING ( hash_id )'
+
+ select_subqueries.append( select_subquery )
+
- if we_are_big:
+ for select_subquery in select_subqueries:
- use_hashes_table = True
+ self._Execute( f'INSERT OR IGNORE INTO {database_temp_job_name} ( hash_id ) {select_subquery};' )
+
+
+
+ else:
+
+ # no hashes, no tag filter, big job
+
+ use_hashes_table = False
+
+ if location_context.IsAllKnownFiles():
+
+ if tag_filter.AllowsEverything():
+
+ # if our tag service is the biggest, and if it basically accounts for all the hashes we know about, it is much faster to just use the hashes table
+
+ our_results = self._GetServiceInfo( tag_service_key )
+
+ our_num_files = our_results[ HC.SERVICE_INFO_NUM_FILE_HASHES ]
+
+ other_services = [ service for service in self.modules_services.GetServices( HC.REAL_TAG_SERVICES ) if service.GetServiceKey() != tag_service_key ]
+
+ other_num_files = []
+
+ for other_service in other_services:
+
+ other_results = self._GetServiceInfo( other_service.GetServiceKey() )
+
+ other_num_files.append( other_results[ HC.SERVICE_INFO_NUM_FILE_HASHES ] )
+
+
+ if len( other_num_files ) == 0:
+
+ we_are_big = True
+
+ else:
+
+ we_are_big = our_num_files >= 0.75 * max( other_num_files )
+
+
+ if we_are_big:
+
+ local_files_results = self._GetServiceInfo( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
+
+ local_files_num_files = local_files_results[ HC.SERVICE_INFO_NUM_FILES ]
+
+ if local_files_num_files > our_num_files:
+
+ # probably a small local tags service, ok to pull from current_mappings
+
+ we_are_big = False
+
+
+
+ if we_are_big:
+
+ use_hashes_table = True
+
@@ -5560,7 +5653,7 @@ def _MigrationStartMappingsJob( self, database_temp_job_name, location_context:
for select_subquery in select_subqueries:
- self._Execute( 'INSERT OR IGNORE INTO {} ( hash_id ) {};'.format( database_temp_job_name, select_subquery ) )
+ self._Execute( f'INSERT OR IGNORE INTO {database_temp_job_name} ( hash_id ) {select_subquery};' )
@@ -5608,7 +5701,7 @@ def _PerceptualHashesSearchForPotentialDuplicates( self, search_distance, mainte
while len( group_of_hash_ids ) > 0:
- text = 'searching potential duplicates: {}'.format( HydrusData.ToHumanInt( num_done ) )
+ text = 'searching potential duplicates: {}'.format( HydrusNumbers.ToHumanInt( num_done ) )
CG.client_controller.frame_splash_status.SetSubtext( text )
@@ -7829,7 +7922,7 @@ def _RepairInvalidTags( self, job_status: typing.Optional[ ClientThreading.JobSt
break
- message = 'Scanning tags: {} - Bad Found: {}'.format( HydrusData.ConvertValueRangeToPrettyString( num_done, num_to_do ), HydrusData.ToHumanInt( bad_tag_count ) )
+ message = 'Scanning tags: {} - Bad Found: {}'.format( HydrusData.ConvertValueRangeToPrettyString( num_done, num_to_do ), HydrusNumbers.ToHumanInt( bad_tag_count ) )
job_status.SetStatusText( message )
@@ -7929,7 +8022,7 @@ def _RepairInvalidTags( self, job_status: typing.Optional[ ClientThreading.JobSt
else:
- message = 'Invalid tag scanning: {} bad tags found and fixed! They have been written to the log.'.format( HydrusData.ToHumanInt( bad_tag_count ) )
+ message = 'Invalid tag scanning: {} bad tags found and fixed! They have been written to the log.'.format( HydrusNumbers.ToHumanInt( bad_tag_count ) )
self._cursor_transaction_wrapper.pub_after_job( 'notify_new_force_refresh_tags_data' )
@@ -7978,7 +8071,7 @@ def _RepopulateMappingsFromCache( self, tag_service_key = None, job_status = Non
message = 'Doing "{}": {}'.format( name, HydrusData.ConvertValueRangeToPrettyString( num_done, num_to_do ) )
message += '\n' * 2
- message += 'Total rows recovered: {}'.format( HydrusData.ToHumanInt( num_rows_recovered ) )
+ message += 'Total rows recovered: {}'.format( HydrusNumbers.ToHumanInt( num_rows_recovered ) )
job_status.SetStatusText( message )
@@ -8010,7 +8103,7 @@ def _RepopulateMappingsFromCache( self, tag_service_key = None, job_status = Non
if job_status is not None:
- job_status.SetStatusText( 'Done! Rows recovered: {}'.format( HydrusData.ToHumanInt( num_rows_recovered ) ) )
+ job_status.SetStatusText( 'Done! Rows recovered: {}'.format( HydrusNumbers.ToHumanInt( num_rows_recovered ) ) )
job_status.Finish()
@@ -8149,14 +8242,14 @@ def _RepopulateTagDisplayMappingsCache( self, tag_service_key = None ):
def _ReportOverupdatedDB( self, version ):
- message = 'This client\'s database is version {}, but the software is version {}! This situation only sometimes works, and when it does not, it can break things! If you are not sure what is going on, or if you accidentally installed an older version of the software to a newer database, force-kill this client in Task Manager right now. Otherwise, ok this dialog box to continue.'.format( HydrusData.ToHumanInt( version ), HydrusData.ToHumanInt( HC.SOFTWARE_VERSION ) )
+ message = 'This client\'s database is version {}, but the software is version {}! This situation only sometimes works, and when it does not, it can break things! If you are not sure what is going on, or if you accidentally installed an older version of the software to a newer database, force-kill this client in Task Manager right now. Otherwise, ok this dialog box to continue.'.format( HydrusNumbers.ToHumanInt( version ), HydrusNumbers.ToHumanInt( HC.SOFTWARE_VERSION ) )
self._controller.BlockingSafeShowMessage( message )
def _ReportUnderupdatedDB( self, version ):
- message = 'This client\'s database is version {}, but the software is significantly later, {}! Trying to update many versions in one go can be dangerous due to bitrot. I suggest you try at most to only do 10 versions at once. If you want to try a big jump anyway, you should make sure you have a backup beforehand so you can roll back to it in case the update makes your db unbootable. If you would rather try smaller updates, or you do not have a backup, force-kill this client in Task Manager right now. Otherwise, ok this dialog box to continue.'.format( HydrusData.ToHumanInt( version ), HydrusData.ToHumanInt( HC.SOFTWARE_VERSION ) )
+ message = 'This client\'s database is version {}, but the software is significantly later, {}! Trying to update many versions in one go can be dangerous due to bitrot. I suggest you try at most to only do 10 versions at once. If you want to try a big jump anyway, you should make sure you have a backup beforehand so you can roll back to it in case the update makes your db unbootable. If you would rather try smaller updates, or you do not have a backup, force-kill this client in Task Manager right now. Otherwise, ok this dialog box to continue.'.format( HydrusNumbers.ToHumanInt( version ), HydrusNumbers.ToHumanInt( HC.SOFTWARE_VERSION ) )
self._controller.BlockingSafeShowMessage( message )
@@ -8386,7 +8479,7 @@ def _ResyncTagMappingsCacheFiles( self, tag_service_key = None ):
problems_found = True
- HydrusData.ShowText( '{} surplus files in {}_{}!'.format( HydrusData.ToHumanInt( len( hash_ids_in_this_cache_but_not_in_file_service ) ), file_service_id, tag_service_id ) )
+ HydrusData.ShowText( '{} surplus files in {}_{}!'.format( HydrusNumbers.ToHumanInt( len( hash_ids_in_this_cache_but_not_in_file_service ) ), file_service_id, tag_service_id ) )
with self._MakeTemporaryIntegerTable( hash_ids_in_this_cache_but_not_in_file_service, 'hash_id' ) as temp_hash_id_table_name:
@@ -8412,7 +8505,7 @@ def _ResyncTagMappingsCacheFiles( self, tag_service_key = None ):
problems_found = True
- HydrusData.ShowText( '{} missing files in {}_{}!'.format( HydrusData.ToHumanInt( len( hash_ids_in_file_service_and_not_in_cache_that_have_tags ) ), file_service_id, tag_service_id ) )
+ HydrusData.ShowText( '{} missing files in {}_{}!'.format( HydrusNumbers.ToHumanInt( len( hash_ids_in_file_service_and_not_in_cache_that_have_tags ) ), file_service_id, tag_service_id ) )
with self._MakeTemporaryIntegerTable( hash_ids_in_file_service_and_not_in_cache_that_have_tags, 'hash_id' ) as temp_hash_id_table_name:
@@ -9801,7 +9894,7 @@ def ask_what_to_do_concatenated_urls():
num_string = HydrusData.ConvertValueRangeToPrettyString( num_done, num_to_do )
- self._controller.frame_splash_status.SetSubtext( f'bad url scan - {num_string} - bad urls: {HydrusData.ToHumanInt(len( bad_url_ids))}' )
+ self._controller.frame_splash_status.SetSubtext( f'bad url scan - {num_string} - bad urls: {HydrusNumbers.ToHumanInt(len( bad_url_ids))}' )
for url_id in chunk_of_url_ids:
@@ -9814,13 +9907,13 @@ def ask_what_to_do_concatenated_urls():
- self._controller.frame_splash_status.SetSubtext( f'bad url scan - done! - bad urls: {HydrusData.ToHumanInt(len( bad_url_ids))}' )
+ self._controller.frame_splash_status.SetSubtext( f'bad url scan - done! - bad urls: {HydrusNumbers.ToHumanInt(len( bad_url_ids))}' )
if len( bad_url_ids ) > 0:
def ask_what_to_do_delete_urls():
- message = f'I found {HydrusData.ToHumanInt(len(bad_url_ids))} bad URLs. I am going to delete them now, ok?'
+ message = f'I found {HydrusNumbers.ToHumanInt(len(bad_url_ids))} bad URLs. I am going to delete them now, ok?'
from hydrus.client.gui import ClientGUIDialogsQuick
@@ -9836,7 +9929,7 @@ def ask_what_to_do_delete_urls():
self._ExecuteMany( 'DELETE FROM urls WHERE url_id = ?;', ( ( url_id, ) for url_id in bad_url_ids ) )
self._ExecuteMany( 'DELETE FROM url_map WHERE url_id = ?;', ( ( url_id, ) for url_id in bad_url_ids ) )
- self.pub_initial_message( f'Deleted {HydrusData.ToHumanInt(len(bad_url_ids))} bad URLs on update!' )
+ self.pub_initial_message( f'Deleted {HydrusNumbers.ToHumanInt(len(bad_url_ids))} bad URLs on update!' )
else:
@@ -10288,7 +10381,43 @@ def ask_what_to_do_zip_docx_scan():
- self._controller.frame_splash_status.SetTitleText( 'updated db to v{}'.format( HydrusData.ToHumanInt( version + 1 ) ) )
+ if version == 580:
+
+ try:
+
+ domain_manager: ClientNetworkingDomain.NetworkDomainManager = self.modules_serialisable.GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER )
+
+ domain_manager.Initialise()
+
+ domain_manager.DeleteParsers( [
+ 'rule34.paheal gallery page parser',
+ 'rule34hentai gallery page parser'
+ ] )
+
+ domain_manager.OverwriteDefaultParsers( [
+ 'shimmie file page parser',
+ 'shimmie gallery page parser'
+ ] )
+
+ #
+
+ domain_manager.TryToLinkURLClassesAndParsers()
+
+ #
+
+ self.modules_serialisable.SetJSONDump( domain_manager )
+
+ except Exception as e:
+
+ HydrusData.PrintException( e )
+
+ message = 'Trying to update some downloaders failed! Please let hydrus dev know!'
+
+ self.pub_initial_message( message )
+
+
+
+ self._controller.frame_splash_status.SetTitleText( 'updated db to v{}'.format( HydrusNumbers.ToHumanInt( version + 1 ) ) )
self._Execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
diff --git a/hydrus/client/db/ClientDBDefinitionsCache.py b/hydrus/client/db/ClientDBDefinitionsCache.py
index 83238452f..7e87dc44a 100644
--- a/hydrus/client/db/ClientDBDefinitionsCache.py
+++ b/hydrus/client/db/ClientDBDefinitionsCache.py
@@ -7,6 +7,7 @@
from hydrus.core import HydrusDBBase
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTags
from hydrus.client import ClientGlobals as CG
@@ -329,7 +330,7 @@ def SyncHashIds( self, all_hash_ids: typing.Collection[ int ], job_status = None
HydrusData.Print( f'Deleted excess desynced local hash_ids: {bad_hash_ids_text}' )
- status_text_info.append( f'{HydrusData.ToHumanInt( len( all_excess_hash_ids ) ) } excess hash records' )
+ status_text_info.append( f'{HydrusNumbers.ToHumanInt( len( all_excess_hash_ids ) ) } excess hash records' )
if len( all_missing_hash_ids ) > 0:
@@ -338,7 +339,7 @@ def SyncHashIds( self, all_hash_ids: typing.Collection[ int ], job_status = None
HydrusData.Print( f'Added missing desynced local hash_ids: {bad_hash_ids_text}' )
- status_text_info.append( f'{HydrusData.ToHumanInt( len( all_missing_hash_ids ) ) } missing hash records' )
+ status_text_info.append( f'{HydrusNumbers.ToHumanInt( len( all_missing_hash_ids ) ) } missing hash records' )
if len( all_incorrect_hash_ids ) > 0:
@@ -347,7 +348,7 @@ def SyncHashIds( self, all_hash_ids: typing.Collection[ int ], job_status = None
HydrusData.Print( f'Fixed incorrect desynced local hash_ids: {bad_hash_ids_text}' )
- status_text_info.append( f'{HydrusData.ToHumanInt( len( all_incorrect_hash_ids ) ) } incorrect hash records' )
+ status_text_info.append( f'{HydrusNumbers.ToHumanInt( len( all_incorrect_hash_ids ) ) } incorrect hash records' )
if len( status_text_info ) > 0:
diff --git a/hydrus/client/db/ClientDBFilesSearch.py b/hydrus/client/db/ClientDBFilesSearch.py
index e3c7ce4c5..93f154b2a 100644
--- a/hydrus/client/db/ClientDBFilesSearch.py
+++ b/hydrus/client/db/ClientDBFilesSearch.py
@@ -493,6 +493,81 @@ def GetHashIdsFromTagIds( self, tag_display_type: int, file_service_key: bytes,
+ return result_hash_ids
+
+
+ def GetHashIdsFromTagIdsTables( self, tag_display_type: int, file_service_key: bytes, tag_context: ClientSearch.TagContext, tag_ids: typing.Collection[ int ], hash_ids = None, hash_ids_table_name = None, job_status = None ):
+
+ do_hash_table_join = False
+
+ if hash_ids_table_name is not None and hash_ids is not None:
+
+ tag_service_id = self.modules_services.GetServiceId( tag_context.service_key )
+ file_service_id = self.modules_services.GetServiceId( file_service_key )
+
+ estimated_count = self.modules_mappings_counts.GetAutocompleteCountEstimate( tag_display_type, tag_service_id, file_service_id, tag_ids, tag_context.include_current_tags, tag_context.include_pending_tags )
+
+ if ClientDBMappingsStorage.DoingAFileJoinTagSearchIsFaster( len( hash_ids ), estimated_count ):
+
+ do_hash_table_join = True
+
+
+
+ result_hash_ids = set()
+
+ table_names = self.modules_tag_search.GetMappingTables( tag_display_type, file_service_key, tag_context )
+
+ cancelled_hook = None
+
+ if job_status is not None:
+
+ cancelled_hook = job_status.IsCancelled
+
+
+ if len( tag_ids ) == 1:
+
+ ( tag_id, ) = tag_ids
+
+ if do_hash_table_join:
+
+ # temp hashes to mappings
+ queries = [ 'SELECT hash_id FROM {} CROSS JOIN {} USING ( hash_id ) WHERE tag_id = ?'.format( hash_ids_table_name, table_name ) for table_name in table_names ]
+
+ else:
+
+ queries = [ 'SELECT hash_id FROM {} WHERE tag_id = ?;'.format( table_name ) for table_name in table_names ]
+
+
+ for query in queries:
+
+ result_hash_ids.update( self._STI( self._ExecuteCancellable( query, ( tag_id, ), cancelled_hook ) ) )
+
+
+ else:
+
+ with self._MakeTemporaryIntegerTable( tag_ids, 'tag_id' ) as temp_tag_ids_table_name:
+
+ if do_hash_table_join:
+
+ # temp hashes to mappings to temp tags
+ # old method, does not do EXISTS efficiently, it makes a list instead and checks that
+ # queries = [ 'SELECT hash_id FROM {} WHERE EXISTS ( SELECT 1 FROM {} CROSS JOIN {} USING ( tag_id ) WHERE {}.hash_id = {}.hash_id );'.format( hash_ids_table_name, table_name, temp_tag_ids_table_name, table_name, hash_ids_table_name ) for table_name in table_names ]
+ # new method, this seems to actually do the correlated scalar subquery, although it does seem to be sqlite voodoo
+ queries = [ 'SELECT hash_id FROM {} WHERE EXISTS ( SELECT 1 FROM {} WHERE {}.hash_id = {}.hash_id AND EXISTS ( SELECT 1 FROM {} WHERE {}.tag_id = {}.tag_id ) );'.format( hash_ids_table_name, table_name, table_name, hash_ids_table_name, temp_tag_ids_table_name, table_name, temp_tag_ids_table_name ) for table_name in table_names ]
+
+ else:
+
+ # temp tags to mappings
+ queries = [ 'SELECT hash_id FROM {} CROSS JOIN {} USING ( tag_id );'.format( temp_tag_ids_table_name, table_name ) for table_name in table_names ]
+
+
+ for query in queries:
+
+ result_hash_ids.update( self._STI( self._ExecuteCancellable( query, (), cancelled_hook ) ) )
+
+
+
+
return result_hash_ids
diff --git a/hydrus/client/db/ClientDBMaintenance.py b/hydrus/client/db/ClientDBMaintenance.py
index 41c528a61..e0530a071 100644
--- a/hydrus/client/db/ClientDBMaintenance.py
+++ b/hydrus/client/db/ClientDBMaintenance.py
@@ -9,6 +9,7 @@
from hydrus.core import HydrusDBBase
from hydrus.core import HydrusDBModule
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTime
from hydrus.client import ClientGlobals as CG
@@ -351,7 +352,7 @@ def CheckDBIntegrity( self ):
CG.client_controller.pub( 'modal_message', job_status )
job_status.SetStatusTitle( prefix_string + 'running' )
- job_status.SetStatusText( 'errors found so far: ' + HydrusData.ToHumanInt( num_errors ) )
+ job_status.SetStatusText( 'errors found so far: ' + HydrusNumbers.ToHumanInt( num_errors ) )
db_names = [ name for ( index, name, path ) in self._Execute( 'PRAGMA database_list;' ) if name not in ( 'mem', 'temp', 'durable_temp' ) ]
@@ -364,7 +365,7 @@ def CheckDBIntegrity( self ):
if should_quit:
job_status.SetStatusTitle( prefix_string + 'cancelled' )
- job_status.SetStatusText( 'errors found: ' + HydrusData.ToHumanInt( num_errors ) )
+ job_status.SetStatusText( 'errors found: ' + HydrusNumbers.ToHumanInt( num_errors ) )
return
@@ -381,14 +382,14 @@ def CheckDBIntegrity( self ):
num_errors += 1
- job_status.SetStatusText( 'errors found so far: ' + HydrusData.ToHumanInt( num_errors ) )
+ job_status.SetStatusText( 'errors found so far: ' + HydrusNumbers.ToHumanInt( num_errors ) )
finally:
job_status.SetStatusTitle( prefix_string + 'completed' )
- job_status.SetStatusText( 'errors found: ' + HydrusData.ToHumanInt( num_errors ) )
+ job_status.SetStatusText( 'errors found: ' + HydrusNumbers.ToHumanInt( num_errors ) )
HydrusData.Print( job_status.ToString() )
diff --git a/hydrus/client/db/ClientDBSimilarFiles.py b/hydrus/client/db/ClientDBSimilarFiles.py
index 1d0afaf6a..abe5208b0 100644
--- a/hydrus/client/db/ClientDBSimilarFiles.py
+++ b/hydrus/client/db/ClientDBSimilarFiles.py
@@ -7,6 +7,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusDBBase
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTime
from hydrus.client import ClientGlobals as CG
@@ -452,7 +453,7 @@ def _RegenerateBranch( self, job_status, perceptual_hash_id ):
# removal of old branch, maintenance schedule, and orphan perceptual_hashes
- job_status.SetStatusText( HydrusData.ToHumanInt( len( unbalanced_nodes ) ) + ' leaves found--now clearing out old branch', 2 )
+ job_status.SetStatusText( HydrusNumbers.ToHumanInt( len( unbalanced_nodes ) ) + ' leaves found--now clearing out old branch', 2 )
unbalanced_perceptual_hash_ids = { p_id for ( p_id, p_h ) in unbalanced_nodes }
@@ -791,7 +792,7 @@ def RegenerateTree( self ):
all_nodes = self._Execute( 'SELECT phash_id, phash FROM shape_perceptual_hashes;' ).fetchall()
- job_status.SetStatusText( HydrusData.ToHumanInt( len( all_nodes ) ) + ' leaves found, now regenerating' )
+ job_status.SetStatusText( HydrusNumbers.ToHumanInt( len( all_nodes ) ) + ' leaves found, now regenerating' )
( root_id, root_perceptual_hash ) = self._PopBestRootNode( all_nodes ) #HydrusData.RandomPop( all_nodes )
@@ -1001,7 +1002,7 @@ def SearchPerceptualHashes( self, search_perceptual_hashes: typing.Collection[ b
if HG.db_report_mode:
- HydrusData.ShowText( 'Similar file search touched {} nodes over {} cycles.'.format( HydrusData.ToHumanInt( total_nodes_searched ), HydrusData.ToHumanInt( num_cycles ) ) )
+ HydrusData.ShowText( 'Similar file search touched {} nodes over {} cycles.'.format( HydrusNumbers.ToHumanInt( total_nodes_searched ), HydrusNumbers.ToHumanInt( num_cycles ) ) )
# so, now we have perceptual_hash_ids and distances. let's map that to actual files.
diff --git a/hydrus/client/db/ClientDBTagSearch.py b/hydrus/client/db/ClientDBTagSearch.py
index b5d5a762a..0689ff915 100644
--- a/hydrus/client/db/ClientDBTagSearch.py
+++ b/hydrus/client/db/ClientDBTagSearch.py
@@ -9,6 +9,7 @@
from hydrus.core import HydrusDBBase
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTags
from hydrus.core import HydrusTime
@@ -1407,6 +1408,106 @@ def HasTag( self, file_service_id, tag_service_id, tag_id ):
return result is not None
+ def PopulateTableFromTagFilter( self, file_service_id: int, tag_service_id: int, tag_filter: HydrusTags.TagFilter, temp_tag_ids_table_name: str, my_search_includes_deleted_tags: bool ):
+
+ if my_search_includes_deleted_tags:
+
+ tags_table_name = 'tags' # lol
+
+ else:
+
+ tags_table_name = self.GetTagsTableName( file_service_id, tag_service_id )
+
+
+ if tag_filter.AllowsEverything():
+
+ self._Execute( f'INSERT OR IGNORE INTO {temp_tag_ids_table_name} ( tag_id ) SELECT tag_id FROM {tags_table_name};' )
+
+ else:
+
+ tag_slices_to_rules = tag_filter.GetTagSlicesToRules()
+
+ # KISS: do 'alls', then namespaces, then tags
+
+ include_all_unnamespaced = '' not in tag_slices_to_rules or ( '' in tag_slices_to_rules and tag_slices_to_rules[ '' ] == HC.FILTER_WHITELIST )
+
+ if include_all_unnamespaced:
+
+ self._Execute( f'INSERT OR IGNORE INTO {temp_tag_ids_table_name} ( tag_id ) SELECT tag_id FROM {tags_table_name} WHERE namespace_id = ?;', ( self.modules_tags.null_namespace_id, ) )
+
+
+ include_all_namespaced = ':' not in tag_slices_to_rules or ( ':' in tag_slices_to_rules and tag_slices_to_rules[ ':' ] == HC.FILTER_WHITELIST )
+
+ if include_all_namespaced:
+
+ self._Execute( f'INSERT OR IGNORE INTO {temp_tag_ids_table_name} ( tag_id ) SELECT tag_id FROM {tags_table_name} WHERE namespace_id != ?;', ( self.modules_tags.null_namespace_id, ) )
+
+
+ #
+
+ for ( tag_slice, rule ) in tag_slices_to_rules.items():
+
+ if tag_slice in ( '', ':' ):
+
+ continue
+
+
+ if HydrusTags.IsNamespaceTagSlice( tag_slice ):
+
+ namespace = tag_slice[:-1]
+
+ namespace_id = self.modules_tags.GetNamespaceId( namespace )
+
+ if rule == HC.FILTER_WHITELIST:
+
+ self._Execute( f'INSERT OR IGNORE INTO {temp_tag_ids_table_name} ( tag_id ) SELECT tag_id FROM {tags_table_name} WHERE namespace_id = ?;', ( namespace_id, ) )
+
+ else:
+
+ self._Execute( f'DELETE FROM {temp_tag_ids_table_name} WHERE tag_id IN ( SELECT tag_id FROM {tags_table_name} WHERE namespaced_id = ? );', ( namespace_id, ) )
+
+
+
+
+ #
+
+ tag_ids_to_add = []
+ tag_ids_to_delete = []
+
+ for ( tag_slice, rule ) in tag_slices_to_rules.items():
+
+ if tag_slice in ( '', ':' ):
+
+ continue
+
+
+ if not HydrusTags.IsNamespaceTagSlice( tag_slice ):
+
+ tag_id = self.modules_tags.GetTagId( tag_slice )
+
+ if rule == HC.FILTER_WHITELIST:
+
+ tag_ids_to_add.append( tag_id )
+
+ else:
+
+ tag_ids_to_delete.append( tag_id )
+
+
+
+
+ if len( tag_ids_to_add ) > 0:
+
+ self._ExecuteMany( f'INSERT OR IGNORE INTO {temp_tag_ids_table_name} ( tag_id ) VALUES ( ? );', ( ( tag_id, ) for tag_id in tag_ids_to_add ) )
+
+
+ if len( tag_ids_to_delete ) > 0:
+
+ self._ExecuteMany( f'DELETE FROM {temp_tag_ids_table_name} WHERE tag_id = ?;', ( ( tag_id, ) for tag_id in tag_ids_to_add ) )
+
+
+
+
def RegenerateSearchableSubtagMap( self, file_service_id, tag_service_id, status_hook = None ):
subtags_fts4_table_name = self.GetSubtagsFTS4TableName( file_service_id, tag_service_id )
@@ -1505,7 +1606,7 @@ def RepopulateMissingSubtags( self, file_service_id, tag_service_id ):
if len( missing_subtag_ids ) > 0:
- HydrusData.ShowText( 'Repopulated {} missing subtags for {}_{}.'.format( HydrusData.ToHumanInt( len( missing_subtag_ids ) ), file_service_id, tag_service_id ) )
+ HydrusData.ShowText( 'Repopulated {} missing subtags for {}_{}.'.format( HydrusNumbers.ToHumanInt( len( missing_subtag_ids ) ), file_service_id, tag_service_id ) )
diff --git a/hydrus/client/duplicates/ClientDuplicates.py b/hydrus/client/duplicates/ClientDuplicates.py
index a1beb9bbf..907d22a69 100644
--- a/hydrus/client/duplicates/ClientDuplicates.py
+++ b/hydrus/client/duplicates/ClientDuplicates.py
@@ -7,6 +7,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusTime
@@ -402,7 +403,7 @@ def GetDuplicateComparisonStatements( shown_media, comparison_media ):
score = -duplicate_comparison_score_more_tags
- statement = '{} tags {} {} tags'.format( HydrusData.ToHumanInt( s_num_tags ), operator, HydrusData.ToHumanInt( c_num_tags ) )
+ statement = '{} tags {} {} tags'.format( HydrusNumbers.ToHumanInt( s_num_tags ), operator, HydrusNumbers.ToHumanInt( c_num_tags ) )
statements_and_scores[ 'num_tags' ] = ( statement, score )
diff --git a/hydrus/client/exporting/ClientExportingFiles.py b/hydrus/client/exporting/ClientExportingFiles.py
index d8ef0db51..c7669dde3 100644
--- a/hydrus/client/exporting/ClientExportingFiles.py
+++ b/hydrus/client/exporting/ClientExportingFiles.py
@@ -8,6 +8,7 @@
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
@@ -648,7 +649,7 @@ def _DoExport( self, job_status: ClientThreading.JobStatus ):
if num_actually_copied > 0:
- HydrusData.Print( 'Export folder ' + self._name + ' exported ' + HydrusData.ToHumanInt( num_actually_copied ) + ' files.' )
+ HydrusData.Print( 'Export folder ' + self._name + ' exported ' + HydrusNumbers.ToHumanInt( num_actually_copied ) + ' files.' )
if self._export_type == HC.EXPORT_FOLDER_TYPE_SYNCHRONISE:
@@ -703,7 +704,7 @@ def _DoExport( self, job_status: ClientThreading.JobStatus ):
if len( deletee_paths ) > 0:
- HydrusData.Print( 'Export folder {} deleted {} files and {} folders.'.format( self._name, HydrusData.ToHumanInt( len( deletee_paths ) ), HydrusData.ToHumanInt( len( deletee_dirs ) ) ) )
+ HydrusData.Print( 'Export folder {} deleted {} files and {} folders.'.format( self._name, HydrusNumbers.ToHumanInt( len( deletee_paths ) ), HydrusNumbers.ToHumanInt( len( deletee_dirs ) ) ) )
diff --git a/hydrus/client/gui/ClientGUI.py b/hydrus/client/gui/ClientGUI.py
index 9e768c5df..5f1053bc7 100644
--- a/hydrus/client/gui/ClientGUI.py
+++ b/hydrus/client/gui/ClientGUI.py
@@ -22,6 +22,7 @@
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusMemory
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusProfiling
from hydrus.core import HydrusSerialisable
@@ -92,6 +93,8 @@
from hydrus.client.gui.parsing import ClientGUIParsing
from hydrus.client.gui.parsing import ClientGUIParsingLegacy
from hydrus.client.gui.services import ClientGUIClientsideServices
+from hydrus.client.gui.services import ClientGUIModalClientsideServiceActions
+from hydrus.client.gui.services import ClientGUIModalServersideServiceActions
from hydrus.client.gui.services import ClientGUIServersideServices
from hydrus.client.gui.widgets import ClientGUICommon
from hydrus.client.interfaces import ClientControllerInterface
@@ -1288,7 +1291,7 @@ def do_it():
else:
- message = '{} orphans cleared!'.format( HydrusData.ToHumanInt( num_done ) )
+ message = '{} orphans cleared!'.format( HydrusNumbers.ToHumanInt( num_done ) )
HydrusData.ShowText( message )
@@ -1430,7 +1433,7 @@ def thread_wait( url ):
def _DebugIsolateMPVWindows( self ):
- HydrusData.ShowText( f'Isolated {HydrusData.ToHumanInt( len( self._persistent_mpv_widgets ) )} MPV widgets.' )
+ HydrusData.ShowText( f'Isolated {HydrusNumbers.ToHumanInt( len( self._persistent_mpv_widgets ) )} MPV widgets.' )
self._isolated_mpv_widgets.extend( self._persistent_mpv_widgets )
@@ -2165,7 +2168,7 @@ def do_it( external_update_dir ):
else:
- job_status.SetStatusText( 'Done with ' + HydrusData.ToHumanInt( num_errors ) + ' errors (written to the log).' )
+ job_status.SetStatusText( 'Done with ' + HydrusNumbers.ToHumanInt( num_errors ) + ' errors (written to the log).' )
finally:
@@ -2874,7 +2877,7 @@ def publish_callable( result ):
if service_key in self._currently_uploading_pending:
- title = '{}: currently uploading {}'.format( name, HydrusData.ToHumanInt( num_pending + num_petitioned ) )
+ title = '{}: currently uploading {}'.format( name, HydrusNumbers.ToHumanInt( num_pending + num_petitioned ) )
else:
@@ -2882,12 +2885,12 @@ def publish_callable( result ):
if num_pending > 0:
- submessages.append( '{} {}'.format( HydrusData.ToHumanInt( num_pending ), pending_phrase ) )
+ submessages.append( '{} {}'.format( HydrusNumbers.ToHumanInt( num_pending ), pending_phrase ) )
if num_petitioned > 0:
- submessages.append( '{} {}'.format( HydrusData.ToHumanInt( num_petitioned ), petitioned_phrase ) )
+ submessages.append( '{} {}'.format( HydrusNumbers.ToHumanInt( num_petitioned ), petitioned_phrase ) )
title = '{}: {}'.format( name, ', '.join( submessages ) )
@@ -2904,7 +2907,7 @@ def publish_callable( result ):
total_num_pending += num_pending + num_petitioned
- ClientGUIMenus.SetMenuTitle( self._menubar_pending_submenu, 'pending ({})'.format( HydrusData.ToHumanInt( total_num_pending ) ) )
+ ClientGUIMenus.SetMenuTitle( self._menubar_pending_submenu, 'pending ({})'.format( HydrusNumbers.ToHumanInt( total_num_pending ) ) )
self._menubar_pending_submenu.menuAction().setEnabled( total_num_pending > 0 )
@@ -2995,7 +2998,11 @@ def publish_callable( result ):
if service_type == HC.TAG_REPOSITORY:
- ClientGUIMenus.AppendMenuItem( submenu, 'edit tag filter' + HC.UNICODE_ELLIPSIS, 'Change the tag filter for this service.', self._ManageServiceOptionsTagFilter, service_key )
+ ClientGUIMenus.AppendSeparator( submenu )
+
+ ClientGUIMenus.AppendMenuItem( submenu, 'edit tag filter' + HC.UNICODE_ELLIPSIS, 'Change the tag filter for this service.', ClientGUIModalServersideServiceActions.ManageServiceOptionsTagFilter, self, service_key )
+ ClientGUIMenus.AppendMenuItem( submenu, 'purge tags' + HC.UNICODE_ELLIPSIS, 'Delete tags completely from the service.', ClientGUIModalClientsideServiceActions.OpenPurgeTagsWindow, self, service_key, [] )
+ ClientGUIMenus.AppendMenuItem( submenu, 'purge tag filter', 'Sync the tag filter to the service, deleting anything that does not pass.', ClientGUIModalClientsideServiceActions.StartPurgeTagFilter, self, service_key )
ClientGUIMenus.AppendSeparator( submenu )
@@ -3514,6 +3521,7 @@ def flip_macos_antiflicker():
network_actions = ClientGUIMenus.GenerateMenu( debug_menu )
+ ClientGUIMenus.AppendMenuItem( network_actions, 'review current network jobs', 'Review the jobs currently running in the network engine.', self._ReviewNetworkJobs )
ClientGUIMenus.AppendMenuItem( network_actions, 'fetch a url', 'Fetch a URL using the network engine as per normal.', self._DebugFetchAURL )
ClientGUIMenus.AppendMenu( debug_menu, network_actions, 'network actions' )
@@ -4729,7 +4737,7 @@ def qt_do_it( subscriptions, missing_query_log_container_names, surplus_query_lo
if len( missing_query_log_container_names ) > 0:
- text = '{} subscription queries had missing database data! This is a serious error!'.format( HydrusData.ToHumanInt( len( missing_query_log_container_names ) ) )
+ text = '{} subscription queries had missing database data! This is a serious error!'.format( HydrusNumbers.ToHumanInt( len( missing_query_log_container_names ) ) )
text += '\n' * 2
text += 'If you continue, the client will now create and save empty file/search logs for those queries, essentially resetting them, but if you know you need to exit and fix your database in a different way, cancel out now.'
text += '\n' * 2
@@ -4769,7 +4777,7 @@ def qt_do_it( subscriptions, missing_query_log_container_names, surplus_query_lo
if len( surplus_query_log_container_names ) > 0:
- text = 'When loading subscription data, the client discovered surplus orphaned subscription data for {} queries! This data is harmless and no longer used. The situation is however unusual, and probably due to an unusual deletion routine or a bug.'.format( HydrusData.ToHumanInt( len( surplus_query_log_container_names ) ) )
+ text = 'When loading subscription data, the client discovered surplus orphaned subscription data for {} queries! This data is harmless and no longer used. The situation is however unusual, and probably due to an unusual deletion routine or a bug.'.format( HydrusNumbers.ToHumanInt( len( surplus_query_log_container_names ) ) )
text += '\n' * 2
text += 'If you continue, this surplus data will backed up to your database directory and then safely deleted from the database itself, but if you recently did manual database editing and know you need to exit and fix your database in a different way, cancel out now.'
text += '\n' * 2
@@ -6680,15 +6688,15 @@ def _ShowPageWeightInfo( self ):
message += '\n' * 2
message += 'Try to keep the total below 10 million! It is also generally better to spread it around--have five download pages each of 500k weight rather than one page with 2.5M.'
message += '\n' * 2
- message += 'Your {} open pages\' total is: {}'.format( total_active_page_count, HydrusData.ToHumanInt( total_active_num_hashes_weight + total_active_num_seeds_weight ) )
+ message += 'Your {} open pages\' total is: {}'.format( total_active_page_count, HydrusNumbers.ToHumanInt( total_active_num_hashes_weight + total_active_num_seeds_weight ) )
message += '\n' * 2
- message += 'Specifically, your file weight is {} and URL weight is {}.'.format( HydrusData.ToHumanInt( total_active_num_hashes_weight ), HydrusData.ToHumanInt( total_active_num_seeds_weight ) )
+ message += 'Specifically, your file weight is {} and URL weight is {}.'.format( HydrusNumbers.ToHumanInt( total_active_num_hashes_weight ), HydrusNumbers.ToHumanInt( total_active_num_seeds_weight ) )
message += '\n' * 2
message += 'For extra info, your {} closed pages (in the undo list) have total weight {}, being file weight {} and URL weight {}.'.format(
total_closed_page_count,
- HydrusData.ToHumanInt( total_closed_num_hashes_weight + total_closed_num_seeds_weight ),
- HydrusData.ToHumanInt( total_closed_num_hashes_weight ),
- HydrusData.ToHumanInt( total_closed_num_seeds_weight )
+ HydrusNumbers.ToHumanInt( total_closed_num_hashes_weight + total_closed_num_seeds_weight ),
+ HydrusNumbers.ToHumanInt( total_closed_num_hashes_weight ),
+ HydrusNumbers.ToHumanInt( total_closed_num_seeds_weight )
)
ClientGUIDialogsMessage.ShowInformation( self, message )
@@ -6941,12 +6949,12 @@ def _UpdateMenuPagesCount( self ):
self._have_shown_session_size_warning = True
- HydrusData.ShowText( 'Your session weight is {}, which is pretty big! To keep your UI lag-free, please try to close some pages or clear some finished downloaders!'.format( HydrusData.ToHumanInt( total_active_weight ) ) )
+ HydrusData.ShowText( 'Your session weight is {}, which is pretty big! To keep your UI lag-free, please try to close some pages or clear some finished downloaders!'.format( HydrusNumbers.ToHumanInt( total_active_weight ) ) )
- ClientGUIMenus.SetMenuItemLabel( self._menubar_pages_page_count, '{} pages open'.format( HydrusData.ToHumanInt( total_active_page_count ) ) )
+ ClientGUIMenus.SetMenuItemLabel( self._menubar_pages_page_count, '{} pages open'.format( HydrusNumbers.ToHumanInt( total_active_page_count ) ) )
- ClientGUIMenus.SetMenuItemLabel( self._menubar_pages_session_weight, 'total session weight: {}'.format( HydrusData.ToHumanInt( total_active_weight ) ) )
+ ClientGUIMenus.SetMenuItemLabel( self._menubar_pages_session_weight, 'total session weight: {}'.format( HydrusNumbers.ToHumanInt( total_active_weight ) ) )
def _UpdateSystemTrayIcon( self, currently_booting = False ):
@@ -7118,7 +7126,7 @@ def AddModalMessage( self, job_status: ClientThreading.JobStatus ):
def AskToDeleteAllClosedPages( self ):
- message = 'Clear the {} closed pages?'.format( HydrusData.ToHumanInt( len( self._closed_pages ) ) )
+ message = 'Clear the {} closed pages?'.format( HydrusNumbers.ToHumanInt( len( self._closed_pages ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -8573,7 +8581,7 @@ def TryToExit( self, restart = False, force_shutdown_maintenance = False ):
if len( work_to_do ) > 0:
- text = 'Is now a good time for the client to do up to ' + HydrusData.ToHumanInt( idle_shutdown_max_minutes ) + ' minutes\' maintenance work? (Will auto-no in 15 seconds)'
+ text = 'Is now a good time for the client to do up to ' + HydrusNumbers.ToHumanInt( idle_shutdown_max_minutes ) + ' minutes\' maintenance work? (Will auto-no in 15 seconds)'
text += '\n' * 2
if CG.client_controller.IsFirstStart():
diff --git a/hydrus/client/gui/ClientGUIDialogsManage.py b/hydrus/client/gui/ClientGUIDialogsManage.py
index 27c977ac2..5abe215cd 100644
--- a/hydrus/client/gui/ClientGUIDialogsManage.py
+++ b/hydrus/client/gui/ClientGUIDialogsManage.py
@@ -8,6 +8,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core.networking import HydrusNATPunch
from hydrus.core import HydrusTime
@@ -41,7 +42,7 @@ def __init__( self, parent, media ):
self._hashes.update( m.GetHashes() )
- ClientGUIDialogs.Dialog.__init__( self, parent, 'manage ratings for ' + HydrusData.ToHumanInt( len( self._hashes ) ) + ' files', position = 'topleft' )
+ ClientGUIDialogs.Dialog.__init__( self, parent, 'manage ratings for ' + HydrusNumbers.ToHumanInt( len( self._hashes ) ) + ' files', position = 'topleft' )
CAC.ApplicationCommandProcessorMixin.__init__( self )
#
diff --git a/hydrus/client/gui/ClientGUIDownloaders.py b/hydrus/client/gui/ClientGUIDownloaders.py
index 84b51c32e..1cef6b6a3 100644
--- a/hydrus/client/gui/ClientGUIDownloaders.py
+++ b/hydrus/client/gui/ClientGUIDownloaders.py
@@ -8,8 +8,9 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
+from hydrus.core import HydrusText
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientDefaults
@@ -41,10 +42,10 @@ def __init__( self, parent: QW.QWidget, network_engine, gugs, gug_keys_to_displa
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._gugs = gugs
- self._gug_keys_to_gugs = { gug.GetGUGKey() : gug for gug in self._gugs }
+ self._gug_keys_to_gugs: typing.Dict[ bytes, ClientNetworkingGUG.GalleryURLGenerator ] = { gug.GetGUGKey() : gug for gug in self._gugs }
self._url_classes = url_classes
- self._url_class_keys_to_url_classes = { url_class.GetClassKey() : url_class for url_class in self._url_classes }
+ self._url_class_keys_to_url_classes: typing.Dict[ bytes, ClientNetworkingURLClass.URLClass ] = { url_class.GetClassKey() : url_class for url_class in self._url_classes }
self._network_engine = network_engine
@@ -118,7 +119,7 @@ def __init__( self, parent: QW.QWidget, network_engine, gugs, gug_keys_to_displa
gridbox = ClientGUICommon.WrapInGrid( media_viewer_urls_panel, rows )
- QP.AddToLayout( vbox, self._url_display_list_ctrl, CC.FLAGS_EXPAND_BOTH_WAYS )
+ QP.AddToLayout( vbox, self._url_display_list_ctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
media_viewer_urls_panel.setLayout( vbox )
@@ -192,76 +193,70 @@ def _ConvertURLDisplayDataToListCtrlTuples( self, data ):
def _EditGUGDisplay( self ):
- edited_datas = []
+ gug_keys_and_displays = self._gug_display_list_ctrl.GetData( only_selected = True )
- for data in self._gug_display_list_ctrl.GetData( only_selected = True ):
+ if len( gug_keys_and_displays ) == 0:
- ( gug_key, display ) = data
+ return
- name = self._gug_keys_to_gugs[ gug_key ].GetName()
+
+ names = [ self._gug_keys_to_gugs[ gug_key_and_display[0] ].GetName() for gug_key_and_display in gug_keys_and_displays ]
+
+ message = f'Show{HydrusText.ConvertManyStringsToNiceInsertableHumanSummary( names )}in the main selector list?'
+
+ ( result, closed_by_user ) = ClientGUIDialogsQuick.GetYesNo( self, message, title = 'Show in the first list?', check_for_cancelled = True )
+
+ if closed_by_user:
- message = 'Show "{}" in the main selector list?'.format( name )
+ return
- result, closed_by_user = ClientGUIDialogsQuick.GetYesNo( self, message, title = 'Show in the first list?', check_for_cancelled = True )
+
+ should_display = result == QW.QDialog.Accepted
+
+ replacement_tuples = []
+
+ for gug_key_and_display in gug_keys_and_displays:
- if not closed_by_user:
-
- display = result == QW.QDialog.Accepted
-
- self._gug_display_list_ctrl.DeleteDatas( ( data, ) )
-
- new_data = ( gug_key, display )
-
- self._gug_display_list_ctrl.AddDatas( ( new_data, ) )
-
- edited_datas.append( new_data )
-
- else:
-
- break
-
+ new_gug_key_and_display = ( gug_key_and_display[0], should_display )
+
+ replacement_tuples.append( ( gug_key_and_display, new_gug_key_and_display ) )
- self._gug_display_list_ctrl.SelectDatas( edited_datas )
-
- self._gug_display_list_ctrl.Sort()
+ self._gug_display_list_ctrl.ReplaceDatas( replacement_tuples, sort_and_scroll = True )
def _EditURLDisplay( self ):
- edited_datas = []
+ url_class_keys_and_displays = self._url_display_list_ctrl.GetData( only_selected = True )
- for data in self._url_display_list_ctrl.GetData( only_selected = True ):
+ if len( url_class_keys_and_displays ) == 0:
- ( url_class_key, display ) = data
+ return
- url_class_name = self._url_class_keys_to_url_classes[ url_class_key ].GetName()
+
+ names = [ self._url_class_keys_to_url_classes[ url_class_key_and_display[0] ].GetName() for url_class_key_and_display in url_class_keys_and_displays ]
+
+ message = f'Show{HydrusText.ConvertManyStringsToNiceInsertableHumanSummary( names )}in the media viewer?'
+
+ ( result, closed_by_user ) = ClientGUIDialogsQuick.GetYesNo( self, message, title = 'Show in the media viewer?', check_for_cancelled = True )
+
+ if closed_by_user:
- message = 'Show ' + url_class_name + ' in the media viewer?'
+ return
- result, closed_by_user = ClientGUIDialogsQuick.GetYesNo( self, message, title = 'Show in the media viewer?', check_for_cancelled = True )
+
+ should_display = result == QW.QDialog.Accepted
+
+ replacement_tuples = []
+
+ for url_class_key_and_display in url_class_keys_and_displays:
- if not closed_by_user:
-
- display = result == QW.QDialog.Accepted
-
- self._url_display_list_ctrl.DeleteDatas( ( data, ) )
-
- new_data = ( url_class_key, display )
-
- self._url_display_list_ctrl.AddDatas( ( new_data, ) )
-
- edited_datas.append( new_data )
-
- else:
-
- break
-
+ new_url_class_key_and_display = ( url_class_key_and_display[0], should_display )
+
+ replacement_tuples.append( ( url_class_key_and_display, new_url_class_key_and_display ) )
- self._url_display_list_ctrl.SelectDatas( edited_datas )
-
- self._url_display_list_ctrl.Sort()
+ self._url_display_list_ctrl.ReplaceDatas( replacement_tuples, sort_and_scroll = True )
def GetValue( self ):
@@ -490,7 +485,7 @@ def _AddGUG( self, gug ):
gug_key_and_name = gug.GetGUGKeyAndName()
- self._gug_list_ctrl.AddDatas( ( gug_key_and_name, ) )
+ self._gug_list_ctrl.AddDatas( ( gug_key_and_name, ), select_sort_and_scroll = True )
def _AddGUGButtonClick( self ):
@@ -590,7 +585,7 @@ def __init__( self, parent: QW.QWidget, gugs ):
self._gug_list_ctrl_panel.SetListCtrl( self._gug_list_ctrl )
self._gug_list_ctrl_panel.AddButton( 'add', self._AddNewGUG )
- self._gug_list_ctrl_panel.AddButton( 'edit', self._EditGUG, enabled_only_on_selection = True )
+ self._gug_list_ctrl_panel.AddButton( 'edit', self._EditGUG, enabled_only_on_single_selection = True )
self._gug_list_ctrl_panel.AddDeleteButton()
self._gug_list_ctrl_panel.AddSeparator()
self._gug_list_ctrl_panel.AddImportExportButtons( ( ClientNetworkingGUG.GalleryURLGenerator, ), self._AddGUG )
@@ -606,7 +601,7 @@ def __init__( self, parent: QW.QWidget, gugs ):
self._ngug_list_ctrl_panel.SetListCtrl( self._ngug_list_ctrl )
self._ngug_list_ctrl_panel.AddButton( 'add', self._AddNewNGUG )
- self._ngug_list_ctrl_panel.AddButton( 'edit', self._EditNGUG, enabled_only_on_selection = True )
+ self._ngug_list_ctrl_panel.AddButton( 'edit', self._EditNGUG, enabled_only_on_single_selection = True )
self._ngug_list_ctrl_panel.AddDeleteButton()
self._ngug_list_ctrl_panel.AddSeparator()
self._ngug_list_ctrl_panel.AddImportExportButtons( ( ClientNetworkingGUG.NestedGalleryURLGenerator, ), self._AddNGUG )
@@ -1349,7 +1344,7 @@ def __init__( self, parent: QW.QWidget, url_class: ClientNetworkingURLClass.URLC
parameters_listctrl_panel.SetListCtrl( self._parameters )
parameters_listctrl_panel.AddButton( 'add', self._AddParameters )
- parameters_listctrl_panel.AddButton( 'edit', self._EditParameters, enabled_only_on_selection = True )
+ parameters_listctrl_panel.AddButton( 'edit', self._EditParameters, enabled_only_on_single_selection = True )
parameters_listctrl_panel.AddDeleteButton()
#
@@ -1965,7 +1960,7 @@ def _UpdateControls( self ):
if True in ( string_match.Matches( n ) for n in ( '0', '1', '10', '100', '42' ) ):
- choices.append( ( HydrusData.ConvertIntToPrettyOrdinalString( index + 1 ) + ' path component', ( ClientNetworkingURLClass.GALLERY_INDEX_TYPE_PATH_COMPONENT, index ) ) )
+ choices.append( ( HydrusNumbers.ConvertIntToPrettyOrdinalString( index + 1 ) + ' path component', ( ClientNetworkingURLClass.GALLERY_INDEX_TYPE_PATH_COMPONENT, index ) ) )
@@ -2306,7 +2301,7 @@ def __init__( self, parent: QW.QWidget, url_classes: typing.Iterable[ ClientNetw
self._list_ctrl_panel.SetListCtrl( self._list_ctrl )
self._list_ctrl_panel.AddButton( 'add', self._Add )
- self._list_ctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ self._list_ctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
self._list_ctrl_panel.AddDeleteButton()
self._list_ctrl_panel.AddSeparator()
self._list_ctrl_panel.AddImportExportButtons( ( ClientNetworkingURLClass.URLClass, ), self._AddURLClass )
@@ -2544,7 +2539,7 @@ def __init__( self, parent: QW.QWidget, network_engine, url_classes, parsers, ur
self._parser_list_ctrl_panel.SetListCtrl( self._parser_list_ctrl )
- self._parser_list_ctrl_panel.AddButton( 'edit', self._EditParser, enabled_only_on_selection = True )
+ self._parser_list_ctrl_panel.AddButton( 'edit', self._EditParser, enabled_only_on_single_selection = True )
self._parser_list_ctrl_panel.AddButton( 'clear', self._ClearParser, enabled_check_func = self._LinksOnCurrentSelection )
self._parser_list_ctrl_panel.AddButton( 'try to fill in gaps based on example urls', self._TryToLinkURLClassesAndParsers, enabled_check_func = self._GapsExist )
diff --git a/hydrus/client/gui/ClientGUIDuplicates.py b/hydrus/client/gui/ClientGUIDuplicates.py
index 27b024f5b..fa24621cc 100644
--- a/hydrus/client/gui/ClientGUIDuplicates.py
+++ b/hydrus/client/gui/ClientGUIDuplicates.py
@@ -3,6 +3,7 @@
from qtpy import QtWidgets as QW
from hydrus.core import HydrusData
+from hydrus.core import HydrusNumbers
from hydrus.client import ClientGlobals as CG
from hydrus.client.gui import ClientGUIDialogsQuick
@@ -19,7 +20,7 @@ def ClearFalsePositives( win, hashes ):
else:
- message = 'Are you sure you want to clear these {} files of their false-positive relations?'.format( HydrusData.ToHumanInt( len( hashes ) ) )
+ message = 'Are you sure you want to clear these {} files of their false-positive relations?'.format( HydrusNumbers.ToHumanInt( len( hashes ) ) )
message += '\n' * 2
message += 'False-positive relations are recorded between alternate groups, so this change will also affect all alternate files to your selection.'
message += '\n' * 2
@@ -45,7 +46,7 @@ def DissolveAlternateGroup( win, hashes ):
else:
- message = 'Are you sure you want to dissolve these {} files\' entire alternates groups?'.format( HydrusData.ToHumanInt( len( hashes ) ) )
+ message = 'Are you sure you want to dissolve these {} files\' entire alternates groups?'.format( HydrusNumbers.ToHumanInt( len( hashes ) ) )
message += '\n' * 2
message += 'This will completely remove all duplicate, alternate, and false-positive relations for all alternate groups of all files selected and set them to come up again in the duplicate filter.'
message += '\n' * 2
@@ -71,7 +72,7 @@ def DissolveDuplicateGroup( win, hashes ):
else:
- message = 'Are you sure you want to dissolve these {} files\' duplicate groups?'.format( HydrusData.ToHumanInt( len( hashes ) ) )
+ message = 'Are you sure you want to dissolve these {} files\' duplicate groups?'.format( HydrusNumbers.ToHumanInt( len( hashes ) ) )
message += '\n' * 2
message += 'This will split all the files\' duplicates groups back into individual files and remove any alternate relations they have. They will all be queued back up in the duplicate filter for reprocessing.'
message += '\n' * 2
@@ -97,7 +98,7 @@ def RemoveFromAlternateGroup( win, hashes ):
else:
- message = 'Are you sure you want to remove these {} files from their alternates groups?'.format( HydrusData.ToHumanInt( len( hashes ) ) )
+ message = 'Are you sure you want to remove these {} files from their alternates groups?'.format( HydrusNumbers.ToHumanInt( len( hashes ) ) )
message += '\n' * 2
message += 'Alternate relationships are stored between duplicate groups, so this will pull any duplicates of these files with them.'
message += '\n' * 2
@@ -123,7 +124,7 @@ def RemoveFromDuplicateGroup( win, hashes ):
else:
- message = 'Are you sure you want to remove these {} files from their duplicate groups?'.format( HydrusData.ToHumanInt( len( hashes ) ) )
+ message = 'Are you sure you want to remove these {} files from their duplicate groups?'.format( HydrusNumbers.ToHumanInt( len( hashes ) ) )
message += '\n' * 2
message += 'The remaining groups will be otherwise unaffected and keep their alternate relationships.'
message += '\n' * 2
@@ -147,7 +148,7 @@ def RemovePotentials( win, hashes ):
else:
- message = 'Are you sure you want to remove all of these {} files\' potentials?'.format( HydrusData.ToHumanInt( len( hashes ) ) )
+ message = 'Are you sure you want to remove all of these {} files\' potentials?'.format( HydrusNumbers.ToHumanInt( len( hashes ) ) )
message += '\n' * 2
message += 'This will mean they (or any of their duplicates) will not appear in the duplicate filter unless new potentials are found with new files. Use this command if the files have accidentally received many false positive potential relationships.'
@@ -169,7 +170,7 @@ def ResetPotentialSearch( win, hashes ):
else:
- message = 'Are you sure you want to search these {} files for potential duplicates again?'.format( HydrusData.ToHumanInt( len( hashes ) ) )
+ message = 'Are you sure you want to search these {} files for potential duplicates again?'.format( HydrusNumbers.ToHumanInt( len( hashes ) ) )
message += '\n' * 2
message += 'This will not remove any existing potential pairs, and will typically not find any new relationships unless an error has occured.'
diff --git a/hydrus/client/gui/ClientGUIFileSeedCache.py b/hydrus/client/gui/ClientGUIFileSeedCache.py
index 65dc498a0..187be232d 100644
--- a/hydrus/client/gui/ClientGUIFileSeedCache.py
+++ b/hydrus/client/gui/ClientGUIFileSeedCache.py
@@ -5,6 +5,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusText
@@ -232,63 +233,63 @@ def PopulateFileSeedCacheMenu( win: QW.QWidget, menu: QW.QMenu, file_seed_cache:
if num_errors > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'retry ' + HydrusData.ToHumanInt( num_errors ) + ' failures', 'Tell this log to reattempt all its error failures.', RetryErrors, win, file_seed_cache )
+ ClientGUIMenus.AppendMenuItem( menu, 'retry ' + HydrusNumbers.ToHumanInt( num_errors ) + ' failures', 'Tell this log to reattempt all its error failures.', RetryErrors, win, file_seed_cache )
if num_vetoed > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'retry ' + HydrusData.ToHumanInt( num_vetoed ) + ' ignored', 'Tell this log to reattempt all its ignored/vetoed results.', RetryIgnored, win, file_seed_cache )
+ ClientGUIMenus.AppendMenuItem( menu, 'retry ' + HydrusNumbers.ToHumanInt( num_vetoed ) + ' ignored', 'Tell this log to reattempt all its ignored/vetoed results.', RetryIgnored, win, file_seed_cache )
ClientGUIMenus.AppendSeparator( menu )
if num_successful > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'successful\' file import items from the queue'.format( HydrusData.ToHumanInt( num_successful ) ), 'Tell this log to clear out successful files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_SUCCESSFUL_AND_NEW, CC.STATUS_SUCCESSFUL_BUT_REDUNDANT, CC.STATUS_SUCCESSFUL_AND_CHILD_FILES ) )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'successful\' file import items from the queue'.format( HydrusNumbers.ToHumanInt( num_successful ) ), 'Tell this log to clear out successful files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_SUCCESSFUL_AND_NEW, CC.STATUS_SUCCESSFUL_BUT_REDUNDANT, CC.STATUS_SUCCESSFUL_AND_CHILD_FILES ) )
if num_already_in > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'already in db\' file import items from the queue'.format( HydrusData.ToHumanInt( num_already_in ) ), 'Tell this log to clear out successful but non-new files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_SUCCESSFUL_BUT_REDUNDANT, ) )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'already in db\' file import items from the queue'.format( HydrusNumbers.ToHumanInt( num_already_in ) ), 'Tell this log to clear out successful but non-new files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_SUCCESSFUL_BUT_REDUNDANT, ) )
if num_deleted > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'previously deleted\' file import items from the queue'.format( HydrusData.ToHumanInt( num_deleted ) ), 'Tell this log to clear out deleted files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_DELETED, ) )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'previously deleted\' file import items from the queue'.format( HydrusNumbers.ToHumanInt( num_deleted ) ), 'Tell this log to clear out deleted files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_DELETED, ) )
if num_errors > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'failed\' file import items from the queue'.format( HydrusData.ToHumanInt( num_errors ) ), 'Tell this log to clear out errored files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_ERROR, ) )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'failed\' file import items from the queue'.format( HydrusNumbers.ToHumanInt( num_errors ) ), 'Tell this log to clear out errored files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_ERROR, ) )
if num_vetoed > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'ignored\' file import items from the queue'.format( HydrusData.ToHumanInt( num_vetoed ) ), 'Tell this log to clear out ignored files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_VETOED, ) )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'ignored\' file import items from the queue'.format( HydrusNumbers.ToHumanInt( num_vetoed ) ), 'Tell this log to clear out ignored files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_VETOED, ) )
if num_skipped > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'skipped\' file import items from the queue'.format( HydrusData.ToHumanInt( num_skipped ) ), 'Tell this log to clear out skipped files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_SKIPPED, ) )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'skipped\' file import items from the queue'.format( HydrusNumbers.ToHumanInt( num_skipped ) ), 'Tell this log to clear out skipped files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_SKIPPED, ) )
if num_unknown > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'unknown\' (i.e. unstarted) file import items from the queue'.format( HydrusData.ToHumanInt( num_unknown ) ), 'Tell this log to clear out any items that have not yet been started (or have been restarted and not yet worked on), reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_UNKNOWN, ) )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'unknown\' (i.e. unstarted) file import items from the queue'.format( HydrusNumbers.ToHumanInt( num_unknown ) ), 'Tell this log to clear out any items that have not yet been started (or have been restarted and not yet worked on), reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_UNKNOWN, ) )
if len( file_seed_cache ) > 0:
ClientGUIMenus.AppendSeparator( menu )
- ClientGUIMenus.AppendMenuItem( menu, f'delete everything ({HydrusData.ToHumanInt( len( file_seed_cache ) )} items) from the queue', 'Tell this log to clear out everything, resetting the queue to empty.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_UNKNOWN, CC.STATUS_SUCCESSFUL_AND_NEW, CC.STATUS_SUCCESSFUL_BUT_REDUNDANT, CC.STATUS_DELETED, CC.STATUS_ERROR, CC.STATUS_VETOED, CC.STATUS_SKIPPED, CC.STATUS_SUCCESSFUL_AND_CHILD_FILES ) )
+ ClientGUIMenus.AppendMenuItem( menu, f'delete everything ({HydrusNumbers.ToHumanInt( len( file_seed_cache ) )} items) from the queue', 'Tell this log to clear out everything, resetting the queue to empty.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_UNKNOWN, CC.STATUS_SUCCESSFUL_AND_NEW, CC.STATUS_SUCCESSFUL_BUT_REDUNDANT, CC.STATUS_DELETED, CC.STATUS_ERROR, CC.STATUS_VETOED, CC.STATUS_SKIPPED, CC.STATUS_SUCCESSFUL_AND_CHILD_FILES ) )
if num_unknown > 0:
ClientGUIMenus.AppendSeparator( menu )
- ClientGUIMenus.AppendMenuItem( menu, 'set {} \'unknown\' (i.e. unstarted) file import items to \'skipped\''.format( HydrusData.ToHumanInt( num_unknown ) ), 'Tell this log to skip any outstanding items in the queue.', file_seed_cache.SetStatusToStatus, CC.STATUS_UNKNOWN, CC.STATUS_SKIPPED )
+ ClientGUIMenus.AppendMenuItem( menu, 'set {} \'unknown\' (i.e. unstarted) file import items to \'skipped\''.format( HydrusNumbers.ToHumanInt( num_unknown ) ), 'Tell this log to skip any outstanding items in the queue.', file_seed_cache.SetStatusToStatus, CC.STATUS_UNKNOWN, CC.STATUS_SKIPPED )
ClientGUIMenus.AppendSeparator( menu )
@@ -366,7 +367,7 @@ def _ConvertFileSeedToListCtrlTuples( self, file_seed: ClientImportFileSeeds.Fil
file_seed_index = self._file_seed_cache.GetFileSeedIndex( file_seed )
- pretty_file_seed_index = HydrusData.ToHumanInt( file_seed_index )
+ pretty_file_seed_index = HydrusNumbers.ToHumanInt( file_seed_index )
except:
@@ -458,7 +459,7 @@ def _DeleteSelected( self ):
if len( file_seeds_to_delete ) > 0:
- message = 'Are you sure you want to delete the {} selected entries?'.format( HydrusData.ToHumanInt( len( file_seeds_to_delete ) ) )
+ message = 'Are you sure you want to delete the {} selected entries?'.format( HydrusNumbers.ToHumanInt( len( file_seeds_to_delete ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
diff --git a/hydrus/client/gui/ClientGUIGallerySeedLog.py b/hydrus/client/gui/ClientGUIGallerySeedLog.py
index ebe48bc69..1a37d2177 100644
--- a/hydrus/client/gui/ClientGUIGallerySeedLog.py
+++ b/hydrus/client/gui/ClientGUIGallerySeedLog.py
@@ -6,6 +6,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusText
from hydrus.client import ClientConstants as CC
@@ -118,7 +119,7 @@ def ImportURLs( win: QW.QWidget, gallery_seed_log: ClientImportGallerySeeds.Gall
num_urls = len( urls )
num_removed = num_urls - len( filtered_urls )
- message = 'Of the ' + HydrusData.ToHumanInt( num_urls ) + ' URLs you mean to add, ' + HydrusData.ToHumanInt( num_removed ) + ' are already in the search log. Would you like to only add new URLs or add everything (which will force a re-check of the duplicates)?'
+ message = 'Of the ' + HydrusNumbers.ToHumanInt( num_urls ) + ' URLs you mean to add, ' + HydrusNumbers.ToHumanInt( num_removed ) + ' are already in the search log. Would you like to only add new URLs or add everything (which will force a re-check of the duplicates)?'
( result, was_cancelled ) = ClientGUIDialogsQuick.GetYesNo( win, message, yes_label = 'only add new urls', no_label = 'add all urls, even duplicates', check_for_cancelled = True )
@@ -194,22 +195,22 @@ def PopulateGallerySeedLogButton( win: QW.QWidget, menu: QW.QMenu, gallery_seed_
if num_successful > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'successful\' gallery log entries from the log'.format( HydrusData.ToHumanInt( num_successful ) ), 'Tell this log to clear out successful records, reducing the size of the queue.', ClearGallerySeeds, win, gallery_seed_log, ( CC.STATUS_SUCCESSFUL_AND_NEW, ), gallery_type_string )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'successful\' gallery log entries from the log'.format( HydrusNumbers.ToHumanInt( num_successful ) ), 'Tell this log to clear out successful records, reducing the size of the queue.', ClearGallerySeeds, win, gallery_seed_log, ( CC.STATUS_SUCCESSFUL_AND_NEW, ), gallery_type_string )
if num_errors > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'failed\' gallery log entries from the log'.format( HydrusData.ToHumanInt( num_errors ) ), 'Tell this log to clear out errored records, reducing the size of the queue.', ClearGallerySeeds, win, gallery_seed_log, ( CC.STATUS_ERROR, ), gallery_type_string )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'failed\' gallery log entries from the log'.format( HydrusNumbers.ToHumanInt( num_errors ) ), 'Tell this log to clear out errored records, reducing the size of the queue.', ClearGallerySeeds, win, gallery_seed_log, ( CC.STATUS_ERROR, ), gallery_type_string )
if num_vetoed > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'ignored\' gallery log entries from the log'.format( HydrusData.ToHumanInt( num_vetoed ) ), 'Tell this log to clear out ignored records, reducing the size of the queue.', ClearGallerySeeds, win, gallery_seed_log, ( CC.STATUS_VETOED, ), gallery_type_string )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'ignored\' gallery log entries from the log'.format( HydrusNumbers.ToHumanInt( num_vetoed ) ), 'Tell this log to clear out ignored records, reducing the size of the queue.', ClearGallerySeeds, win, gallery_seed_log, ( CC.STATUS_VETOED, ), gallery_type_string )
if num_skipped > 0:
- ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'skipped\' gallery log entries from the log'.format( HydrusData.ToHumanInt( num_skipped ) ), 'Tell this log to clear out skipped records, reducing the size of the queue.', ClearGallerySeeds, win, gallery_seed_log, ( CC.STATUS_SKIPPED, ), gallery_type_string )
+ ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'skipped\' gallery log entries from the log'.format( HydrusNumbers.ToHumanInt( num_skipped ) ), 'Tell this log to clear out skipped records, reducing the size of the queue.', ClearGallerySeeds, win, gallery_seed_log, ( CC.STATUS_SKIPPED, ), gallery_type_string )
ClientGUIMenus.AppendSeparator( menu )
@@ -298,7 +299,7 @@ def _ConvertGallerySeedToListCtrlTuples( self, gallery_seed ):
modified = gallery_seed.modified
note = gallery_seed.note
- pretty_gallery_seed_index = HydrusData.ToHumanInt( gallery_seed_index )
+ pretty_gallery_seed_index = HydrusNumbers.ToHumanInt( gallery_seed_index )
pretty_url = ClientNetworkingFunctions.ConvertURLToHumanString( url )
pretty_status = CC.status_string_lookup[ status ] if status != CC.STATUS_UNKNOWN else ''
pretty_added = ClientTime.TimestampToPrettyTimeDelta( added )
@@ -356,7 +357,7 @@ def _DeleteSelected( self ):
if len( gallery_seeds_to_delete ) > 0:
- message = 'Are you sure you want to delete the {} selected entries? This is only useful if you have a really really huge list.'.format( HydrusData.ToHumanInt( len( gallery_seeds_to_delete ) ) )
+ message = 'Are you sure you want to delete the {} selected entries? This is only useful if you have a really really huge list.'.format( HydrusNumbers.ToHumanInt( len( gallery_seeds_to_delete ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
diff --git a/hydrus/client/gui/ClientGUIPopupMessages.py b/hydrus/client/gui/ClientGUIPopupMessages.py
index 1a9cf3439..8b2dad319 100644
--- a/hydrus/client/gui/ClientGUIPopupMessages.py
+++ b/hydrus/client/gui/ClientGUIPopupMessages.py
@@ -11,6 +11,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTime
from hydrus.client import ClientConstants as CC
@@ -564,7 +565,7 @@ def UpdateMessage( self ):
( hashes, attached_files_label ) = result
- text = '{} - show {} files'.format( attached_files_label, HydrusData.ToHumanInt( len( hashes ) ) )
+ text = '{} - show {} files'.format( attached_files_label, HydrusNumbers.ToHumanInt( len( hashes ) ) )
if self._show_files_button.text() != text:
@@ -1523,6 +1524,6 @@ def SetNumMessages( self, num_messages_pending ):
else:
- self._text.setText( HydrusData.ToHumanInt(num_messages_pending)+' messages' )
+ self._text.setText( HydrusNumbers.ToHumanInt(num_messages_pending)+' messages' )
diff --git a/hydrus/client/gui/ClientGUIRatings.py b/hydrus/client/gui/ClientGUIRatings.py
index 658485cd8..a17079500 100644
--- a/hydrus/client/gui/ClientGUIRatings.py
+++ b/hydrus/client/gui/ClientGUIRatings.py
@@ -6,6 +6,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.client import ClientGlobals as CG
from hydrus.client.gui import QtPorting as QP
@@ -71,7 +72,7 @@ def DrawIncDec( painter: QG.QPainter, x, y, service_key, rating_state, rating ):
rating = 0
- text = HydrusData.ToHumanInt( rating )
+ text = HydrusNumbers.ToHumanInt( rating )
original_font = painter.font()
diff --git a/hydrus/client/gui/ClientGUIShortcutControls.py b/hydrus/client/gui/ClientGUIShortcutControls.py
index fc14253f6..3794614b5 100644
--- a/hydrus/client/gui/ClientGUIShortcutControls.py
+++ b/hydrus/client/gui/ClientGUIShortcutControls.py
@@ -7,6 +7,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientApplicationCommand as CAC
@@ -224,6 +225,8 @@ def _SpecialDuplicate( self ):
num_not_added = 0
+ add_rows = []
+
for ( shortcut, command ) in shortcut_set.GetShortcutsAndCommands():
addee_shortcut = shortcut.Duplicate()
@@ -245,13 +248,13 @@ def _SpecialDuplicate( self ):
if addee_shortcut not in all_existing_shortcuts:
- self._shortcuts.AddDatas( [ ( addee_shortcut, command ) ] )
+ add_rows.append( [ ( addee_shortcut, command ) ] )
all_existing_shortcuts.add( addee_shortcut )
- self._shortcuts.Sort()
+ self._shortcuts.AddDatas( add_rows, select_sort_and_scroll = True )
if num_not_added > 0:
@@ -576,7 +579,7 @@ def _GetTuples( self, shortcuts ):
size = len( shortcuts )
- display_tuple = ( pretty_name, HydrusData.ToHumanInt( size ) )
+ display_tuple = ( pretty_name, HydrusNumbers.ToHumanInt( size ) )
sort_tuple = ( sort_name, size )
return ( display_tuple, sort_tuple )
diff --git a/hydrus/client/gui/ClientGUIStringControls.py b/hydrus/client/gui/ClientGUIStringControls.py
index dfe73a016..a06f9e13f 100644
--- a/hydrus/client/gui/ClientGUIStringControls.py
+++ b/hydrus/client/gui/ClientGUIStringControls.py
@@ -213,7 +213,7 @@ def __init__( self, parent, initial_dict: typing.Dict[ ClientStrings.StringMatch
listctrl_panel.SetListCtrl( self._listctrl )
listctrl_panel.AddButton( 'add', self._Add )
- listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
listctrl_panel.AddDeleteButton()
#
@@ -411,7 +411,7 @@ def __init__( self, parent, initial_dict: typing.Dict[ str, str ], min_height =
listctrl_panel.AddButton( 'add', self._Add )
- listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
if allow_add_delete:
@@ -464,7 +464,7 @@ def _Add( self ):
data = ( key, value )
- self._listctrl.AddDatas( ( data, ) )
+ self._listctrl.AddDatas( ( data, ), select_sort_and_scroll = True )
@@ -473,62 +473,56 @@ def _Add( self ):
def _Edit( self ):
- edited_datas = []
+ data = self._listctrl.GetTopSelectedData()
- for data in self._listctrl.GetData( only_selected = True ):
+ if data is None:
- ( key, value ) = data
+ return
- if self._edit_keys:
+
+ ( key, value ) = data
+
+ if self._edit_keys:
+
+ with ClientGUIDialogs.DialogTextEntry( self, 'edit the ' + self._key_name, default = key, allow_blank = False ) as dlg:
- with ClientGUIDialogs.DialogTextEntry( self, 'edit the ' + self._key_name, default = key, allow_blank = False ) as dlg:
+ if dlg.exec() == QW.QDialog.Accepted:
- if dlg.exec() == QW.QDialog.Accepted:
-
- edited_key = dlg.GetValue()
-
- if edited_key != key and edited_key in self._GetExistingKeys():
-
- ClientGUIDialogsMessage.ShowWarning( self, 'That {} already exists!'.format( self._key_name ) )
-
- break
-
+ edited_key = dlg.GetValue()
+
+ if edited_key != key and edited_key in self._GetExistingKeys():
- else:
+ ClientGUIDialogsMessage.ShowWarning( self, 'That {} already exists!'.format( self._key_name ) )
- break
+ return
-
- else:
-
- edited_key = key
-
-
- with ClientGUIDialogs.DialogTextEntry( self, 'edit the ' + self._value_name, default = value, allow_blank = True ) as dlg:
-
- if dlg.exec() == QW.QDialog.Accepted:
-
- edited_value = dlg.GetValue()
-
else:
- break
+ return
- self._listctrl.DeleteDatas( ( data, ) )
+ else:
- edited_data = ( edited_key, edited_value )
+ edited_key = key
- self._listctrl.AddDatas( ( edited_data, ) )
+
+ with ClientGUIDialogs.DialogTextEntry( self, 'edit the ' + self._value_name, default = value, allow_blank = True ) as dlg:
- edited_datas.append( edited_data )
+ if dlg.exec() == QW.QDialog.Accepted:
+
+ edited_value = dlg.GetValue()
+
+ else:
+
+ return
+
- self._listctrl.SelectDatas( edited_datas )
+ edited_data = ( edited_key, edited_value )
- self._listctrl.Sort()
+ self._listctrl.ReplaceData( data, edited_data, sort_and_scroll = True )
def _GetExistingKeys( self ):
@@ -538,9 +532,7 @@ def _GetExistingKeys( self ):
def _SetValue( self, str_to_str_dict: typing.Dict[ str, str ] ):
- self.Clear()
-
- self._listctrl.AddDatas( [ ( str( key ), str( value ) ) for ( key, value ) in str_to_str_dict.items() ] )
+ self._listctrl.SetData( [ ( str( key ), str( value ) ) for ( key, value ) in str_to_str_dict.items() ] )
self._listctrl.Sort()
@@ -579,7 +571,7 @@ def __init__( self, parent, initial_dict: typing.Dict[ str, ClientStrings.String
listctrl_panel.SetListCtrl( self._listctrl )
listctrl_panel.AddButton( 'add', self._Add )
- listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
listctrl_panel.AddDeleteButton()
#
diff --git a/hydrus/client/gui/ClientGUIStringPanels.py b/hydrus/client/gui/ClientGUIStringPanels.py
index 2f16db32a..95de210b4 100644
--- a/hydrus/client/gui/ClientGUIStringPanels.py
+++ b/hydrus/client/gui/ClientGUIStringPanels.py
@@ -7,6 +7,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientGlobals as CG
@@ -235,7 +236,7 @@ def _UpdateResults( self ):
- tab_label = '{} ({})'.format( processing_steps[i].ToString( simple = True ), HydrusData.ToHumanInt( len( results ) ) )
+ tab_label = '{} ({})'.format( processing_steps[i].ToString( simple = True ), HydrusNumbers.ToHumanInt( len( results ) ) )
self._example_results.addTab( results_list, tab_label )
@@ -316,7 +317,7 @@ def __init__( self, parent: QW.QWidget, string_converter: ClientStrings.StringCo
conversions_panel.SetListCtrl( self._conversions )
conversions_panel.AddButton( 'add', self._AddConversion )
- conversions_panel.AddButton( 'edit', self._EditConversion, enabled_only_on_selection = True )
+ conversions_panel.AddButton( 'edit', self._EditConversion, enabled_only_on_single_selection = True )
conversions_panel.AddDeleteButton()
conversions_panel.AddSeparator()
@@ -411,14 +412,12 @@ def _AddConversion( self ):
enumerated_conversion = ( number, conversion_type, data )
- self._conversions.AddDatas( ( enumerated_conversion, ) )
+ self._conversions.AddDatas( ( enumerated_conversion, ), select_sort_and_scroll = True )
self._conversions.UpdateDatas() # need to refresh string after the insertion, so the new row can be included in the parsing calcs
- self._conversions.Sort()
-
def _CanMoveDown( self ):
@@ -458,7 +457,7 @@ def _ConvertConversionToListCtrlTuples( self, conversion ):
( number, conversion_type, data ) = conversion
- pretty_number = HydrusData.ToHumanInt( number )
+ pretty_number = HydrusNumbers.ToHumanInt( number )
pretty_conversion = ClientStrings.StringConverter.ConversionToString( ( conversion_type, data ) )
string_converter = self._GetValue()
@@ -532,60 +531,48 @@ def _DeleteConversion( self ):
def _EditConversion( self ):
- edited_datas = []
+ data_row = self._conversions.GetTopSelectedData()
- selected_data = self._conversions.GetData( only_selected = True )
+ if data_row is None:
+
+ return
+
+
+ ( number, conversion_type, data ) = data_row
- for enumerated_conversion in selected_data:
+ try:
- ( number, conversion_type, data ) = enumerated_conversion
+ string_converter = self._GetValue()
- try:
-
- string_converter = self._GetValue()
-
- example_string_at_this_point = string_converter.Convert( self._example_string.text(), number - 1 )
-
- except:
+ example_string_at_this_point = string_converter.Convert( self._example_string.text(), number - 1 )
+
+ except:
+
+ example_string_at_this_point = self._example_string.text()
+
+
+ with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit conversion', frame_key = 'deeply_nested_dialog' ) as dlg:
+
+ panel = self._ConversionPanel( dlg, conversion_type, data, example_string_at_this_point )
+
+ dlg.SetPanel( panel )
+
+ if dlg.exec() == QW.QDialog.Accepted:
- example_string_at_this_point = self._example_string.text()
+ ( conversion_type, data ) = panel.GetValue()
-
- with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit conversion', frame_key = 'deeply_nested_dialog' ) as dlg:
+ new_default_string_converter = ClientStrings.StringConverter( conversions = [ ( conversion_type, data ) ] )
- panel = self._ConversionPanel( dlg, conversion_type, data, example_string_at_this_point )
+ CG.client_controller.new_options.SetRawSerialisable( 'last_used_string_conversion_step', new_default_string_converter )
- dlg.SetPanel( panel )
+ new_data_row = ( number, conversion_type, data )
- if dlg.exec() == QW.QDialog.Accepted:
-
- self._conversions.DeleteDatas( ( enumerated_conversion, ) )
-
- ( conversion_type, data ) = panel.GetValue()
-
- new_default_string_converter = ClientStrings.StringConverter( conversions = [ ( conversion_type, data ) ] )
-
- CG.client_controller.new_options.SetRawSerialisable( 'last_used_string_conversion_step', new_default_string_converter )
-
- enumerated_conversion = ( number, conversion_type, data )
-
- self._conversions.AddDatas( ( enumerated_conversion, ) )
-
- edited_datas.append( enumerated_conversion )
-
- else:
-
- break
-
+ self._conversions.ReplaceData( data_row, new_data_row, sort_and_scroll = True )
- self._conversions.SelectDatas( edited_datas )
-
self._conversions.UpdateDatas()
- self._conversions.Sort()
-
def _GetConversion( self, desired_number ):
@@ -647,30 +634,18 @@ def _MoveUp( self ):
def _SwapConversions( self, one, two ):
- selected_data = self._conversions.GetData( only_selected = True )
-
- one_selected = one in selected_data
- two_selected = two in selected_data
-
- self._conversions.DeleteDatas( ( one, two ) )
-
( number_1, conversion_type_1, data_1 ) = one
( number_2, conversion_type_2, data_2 ) = two
- one = ( number_2, conversion_type_1, data_1 )
- two = ( number_1, conversion_type_2, data_2 )
+ new_one = ( number_2, conversion_type_1, data_1 )
+ new_two = ( number_1, conversion_type_2, data_2 )
- self._conversions.AddDatas( ( one, two ) )
-
- if one_selected:
-
- self._conversions.SelectDatas( ( one, ) )
-
+ replacement_tuples = [
+ ( one, new_one ),
+ ( two, new_two )
+ ]
- if two_selected:
-
- self._conversions.SelectDatas( ( two, ) )
-
+ self._conversions.ReplaceDatas( replacement_tuples, sort_and_scroll = True )
def EventUpdate( self, text ):
diff --git a/hydrus/client/gui/ClientGUISubscriptions.py b/hydrus/client/gui/ClientGUISubscriptions.py
index 7807bf9bb..104da528e 100644
--- a/hydrus/client/gui/ClientGUISubscriptions.py
+++ b/hydrus/client/gui/ClientGUISubscriptions.py
@@ -10,7 +10,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
@@ -49,12 +49,12 @@ def DoAliveOrDeadCheck( win: QW.QWidget, query_headers: typing.Collection[ Clien
if 0 < num_dead < len( query_headers ):
- message = f'Of the {HydrusData.ToHumanInt(len(query_headers))} selected queries, {HydrusData.ToHumanInt(num_dead)} are DEAD. Which queries do you want to check?'
+ message = f'Of the {HydrusNumbers.ToHumanInt(len(query_headers))} selected queries, {HydrusNumbers.ToHumanInt(num_dead)} are DEAD. Which queries do you want to check?'
choice_tuples = [
( f'all of them', ( True, True ), 'Resuscitate the DEAD queries and check everything.' ),
- ( f'the {HydrusData.ToHumanInt(len(query_headers)-num_dead)} ALIVE', ( True, False ), 'Check the ALIVE queries.' ),
- ( f'the {HydrusData.ToHumanInt(num_dead)} DEAD', ( False, True ), 'Resuscitate the DEAD queries and check them.' )
+ ( f'the {HydrusNumbers.ToHumanInt(len(query_headers)-num_dead)} ALIVE', ( True, False ), 'Check the ALIVE queries.' ),
+ ( f'the {HydrusNumbers.ToHumanInt(num_dead)} DEAD', ( False, True ), 'Resuscitate the DEAD queries and check them.' )
]
try:
@@ -195,7 +195,7 @@ def __init__( self, parent: QW.QWidget, subscription: ClientImportSubscriptions.
queries_panel.AddButton( 'add', self._AddQuery )
queries_panel.AddButton( 'copy queries', self._CopyQueries, enabled_only_on_selection = True )
queries_panel.AddButton( 'paste queries', self._PasteQueries )
- queries_panel.AddButton( 'edit', self._EditQuery, enabled_only_on_selection = True )
+ queries_panel.AddButton( 'edit', self._EditQuery, enabled_only_on_single_selection = True )
queries_panel.AddDeleteButton()
queries_panel.AddSeparator()
queries_panel.AddButton( 'pause/play', self._PausePlay, enabled_only_on_selection = True )
@@ -785,7 +785,7 @@ def _ShowQualityInfo( self, data ):
for ( name, num_inbox, num_archived, num_deleted ) in data:
- data_string = '{}: inbox {} | archive {} | deleted {}'.format( name, HydrusData.ToHumanInt( num_inbox ), HydrusData.ToHumanInt( num_archived ), HydrusData.ToHumanInt( num_deleted ) )
+ data_string = '{}: inbox {} | archive {} | deleted {}'.format( name, HydrusNumbers.ToHumanInt( num_inbox ), HydrusNumbers.ToHumanInt( num_archived ), HydrusNumbers.ToHumanInt( num_deleted ) )
if num_archived + num_deleted > 0:
@@ -922,55 +922,12 @@ def _PasteQueries( self ):
if len( already_existing_query_texts ) > 0:
- if len( already_existing_query_texts ) > 50:
-
- message += '{} queries were already in the subscription.'.format( HydrusData.ToHumanInt( len( already_existing_query_texts ) ) )
-
- else:
-
- if len( already_existing_query_texts ) > 5:
-
- aeqt_separator = ', '
-
- else:
-
- aeqt_separator = '\n'
-
-
- message += 'The queries:'
- message += '\n' * 2
- message += aeqt_separator.join( already_existing_query_texts )
- message += '\n' * 2
- message += 'Were already in the subscription.'
-
+ message += f'The queries{HydrusText.ConvertManyStringsToNiceInsertableHumanSummary(already_existing_query_texts)}were already in the subscription.'
if len( DEAD_query_headers ) > 0:
message += '\n' * 2
-
- if len( DEAD_query_headers ) > 50:
-
- message += '{} DEAD queries were revived.'.format( HydrusData.ToHumanInt( len( DEAD_query_headers ) ) )
-
- else:
-
- DEAD_query_texts = sorted( query_header.GetQueryText() for query_header in DEAD_query_headers )
-
- if len( DEAD_query_texts ) > 5:
-
- aeqt_separator = ', '
-
- else:
-
- aeqt_separator = '\n'
-
-
- message += 'The DEAD queries:'
- message += '\n' * 2
- message += aeqt_separator.join( DEAD_query_texts )
- message += '\n' * 2
- message += 'Were revived.'
-
+ message += f'The DEAD queries{HydrusText.ConvertManyStringsToNiceInsertableHumanSummary(DEAD_query_headers)}were revived.'
@@ -981,27 +938,7 @@ def _PasteQueries( self ):
message += '\n' * 2
- if len( new_query_texts ) > 50:
-
- message += '{} queries were added.'.format( HydrusData.ToHumanInt( len( new_query_texts ) ) )
-
- else:
-
- if len( new_query_texts ) > 5:
-
- nqt_separator = ', '
-
- else:
-
- nqt_separator = '\n'
-
-
- message += 'The queries:'
- message += '\n' * 2
- message += nqt_separator.join( new_query_texts )
- message += '\n' * 2
- message += 'Were added.'
-
+ message += f'The queries{HydrusText.ConvertManyStringsToNiceInsertableHumanSummary(new_query_texts)}were added.'
if len( message ) > 0:
@@ -1398,7 +1335,7 @@ def __init__( self, parent: QW.QWidget, subscriptions: typing.Collection[ Client
self._subscriptions_panel.SetListCtrl( self._subscriptions )
self._subscriptions_panel.AddButton( 'add', self.Add )
- self._subscriptions_panel.AddButton( 'edit', self.Edit, enabled_only_on_selection = True )
+ self._subscriptions_panel.AddButton( 'edit', self.Edit, enabled_only_on_single_selection = True )
self._subscriptions_panel.AddDeleteButton()
self._subscriptions_panel.AddSeparator()
@@ -1518,7 +1455,7 @@ def _AddSubscription( self, unknown_subscription ):
subscription.SetNonDupeName( self._GetExistingNames() )
- self._subscriptions.AddDatas( ( subscription, ) )
+ self._subscriptions.AddDatas( ( subscription, ), select_sort_and_scroll = True )
self._RegenDupeData()
@@ -1669,16 +1606,16 @@ def _ConvertSubscriptionToListCtrlTuples( self, subscription ):
else:
- status_components = [ HydrusData.ToHumanInt( num_ok ) + ' working' ]
+ status_components = [ HydrusNumbers.ToHumanInt( num_ok ) + ' working' ]
if num_paused > 0:
- status_components.append( HydrusData.ToHumanInt( num_paused ) + ' paused' )
+ status_components.append( HydrusNumbers.ToHumanInt( num_paused ) + ' paused' )
if num_dead > 0:
- status_components.append( HydrusData.ToHumanInt( num_dead ) + ' dead' )
+ status_components.append( HydrusNumbers.ToHumanInt( num_dead ) + ' dead' )
pretty_status = ', '.join( status_components )
@@ -1819,7 +1756,7 @@ def _GetSelectedSubsAsExportableContainers( self ):
if len( missing_query_headers ) > 25:
- message = 'Exporting or duplicating the current selection means reading query data for {} queries from the database. This may take just a couple of seconds, or, for hundreds of thousands of cached URLs, it could be a couple of minutes (and a whack of memory). Do not panic, it will get there in the end. Do you want to do the export?'.format( HydrusData.ToHumanInt( len( missing_query_headers ) ) )
+ message = 'Exporting or duplicating the current selection means reading query data for {} queries from the database. This may take just a couple of seconds, or, for hundreds of thousands of cached URLs, it could be a couple of minutes (and a whack of memory). Do not panic, it will get there in the end. Do you want to do the export?'.format( HydrusNumbers.ToHumanInt( len( missing_query_headers ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -2249,7 +2186,7 @@ def DedupeAll( self ):
for ( gug_name, query_texts ) in gug_names_to_dupe_query_texts.items():
- label = '{} ({} duplicates)'.format( gug_name, HydrusData.ToHumanInt( len( query_texts ) ) )
+ label = '{} ({} duplicates)'.format( gug_name, HydrusNumbers.ToHumanInt( len( query_texts ) ) )
tooltip = ', '.join( sorted( query_texts ) )
@@ -2299,7 +2236,7 @@ def DedupeAll( self ):
else:
- message = 'There are {} duplicate query texts for the downloader "{}". Would you like to dedupe them all, or select which to do?'.format( HydrusData.ToHumanInt( len( potential_dupe_query_texts ) ), gug_name )
+ message = 'There are {} duplicate query texts for the downloader "{}". Would you like to dedupe them all, or select which to do?'.format( HydrusNumbers.ToHumanInt( len( potential_dupe_query_texts ) ), gug_name )
yes_tuples = []
@@ -2449,7 +2386,7 @@ def DedupeAll( self ):
if len( query_texts_we_want_to_dedupe_now ) > 0:
- message = 'Dedupe was done. There are still {} queries that can be deduped between other subscriptions. Want to do them now?'.format( HydrusData.ToHumanInt( len( query_texts_we_want_to_dedupe_now ) ) )
+ message = 'Dedupe was done. There are still {} queries that can be deduped between other subscriptions. Want to do them now?'.format( HydrusNumbers.ToHumanInt( len( query_texts_we_want_to_dedupe_now ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -2633,7 +2570,7 @@ def Merge( self ):
primary_sub_name = primary_sub.GetName()
- message = primary_sub_name + ' was able to merge ' + HydrusData.ToHumanInt( len( mergeable_group ) ) + ' other subscriptions. If you wish to change its name, do so here.'
+ message = primary_sub_name + ' was able to merge ' + HydrusNumbers.ToHumanInt( len( mergeable_group ) ) + ' other subscriptions. If you wish to change its name, do so here.'
with ClientGUIDialogs.DialogTextEntry( self, message, default = primary_sub_name ) as dlg:
diff --git a/hydrus/client/gui/ClientGUITagSuggestions.py b/hydrus/client/gui/ClientGUITagSuggestions.py
index ee96a64eb..3a76c48b9 100644
--- a/hydrus/client/gui/ClientGUITagSuggestions.py
+++ b/hydrus/client/gui/ClientGUITagSuggestions.py
@@ -6,8 +6,9 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
+from hydrus.core import HydrusTags
from hydrus.core import HydrusTime
from hydrus.client import ClientApplicationCommand as CAC
@@ -412,12 +413,12 @@ def qt_code( predicates, num_done, num_to_do, num_skipped, total_time_took ):
else:
- tags_s = 'tags ({} skipped)'.format( HydrusData.ToHumanInt( num_skipped ) )
+ tags_s = 'tags ({} skipped)'.format( HydrusNumbers.ToHumanInt( num_skipped ) )
if num_done == num_to_do:
- num_done_s = 'Searched {} {} in '.format( HydrusData.ToHumanInt( num_done ), tags_s )
+ num_done_s = 'Searched {} {} in '.format( HydrusNumbers.ToHumanInt( num_done ), tags_s )
else:
@@ -445,12 +446,9 @@ def qt_code( predicates, num_done, num_to_do, num_skipped, total_time_took ):
for ( tag_slice, weight_percent ) in related_tags_search_tag_slices_weight_percent:
- if tag_slice not in ( ':', '' ):
+ if HydrusTags.IsNamespaceTagSlice( tag_slice ):
- if tag_slice.endswith( ':' ):
-
- tag_slice = tag_slice[ : -1 ]
-
+ tag_slice = tag_slice[ : -1 ]
search_tag_slices_weight_dict[ tag_slice ] = weight_percent / 100
@@ -458,12 +456,9 @@ def qt_code( predicates, num_done, num_to_do, num_skipped, total_time_took ):
for ( tag_slice, weight_percent ) in related_tags_result_tag_slices_weight_percent:
- if tag_slice not in ( ':', '' ):
+ if HydrusTags.IsNamespaceTagSlice( tag_slice ):
- if tag_slice.endswith( ':' ):
-
- tag_slice = tag_slice[ : -1 ]
-
+ tag_slice = tag_slice[ : -1 ]
result_tag_slices_weight_dict[ tag_slice ] = weight_percent / 100
diff --git a/hydrus/client/gui/ClientGUITags.py b/hydrus/client/gui/ClientGUITags.py
index 3f825ccbc..2d93bf358 100644
--- a/hydrus/client/gui/ClientGUITags.py
+++ b/hydrus/client/gui/ClientGUITags.py
@@ -15,6 +15,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
@@ -739,7 +740,7 @@ def __init__( self, parent, tag_filter, only_show_blacklist = False, namespaces
self._redundant_st = ClientGUICommon.BetterStaticText( self, '', ellipsize_end = True )
self._redundant_st.setVisible( False )
- self._current_filter_st = ClientGUICommon.BetterStaticText( self, 'currently keeping: ', ellipsize_end = True )
+ self._current_filter_st = ClientGUICommon.BetterStaticText( self, 'current filter: ', ellipsize_end = True )
self._test_result_st = ClientGUICommon.BetterStaticText( self, self.TEST_RESULT_DEFAULT )
self._test_result_st.setAlignment( QC.Qt.AlignVCenter | QC.Qt.AlignRight )
@@ -1004,7 +1005,7 @@ def _CurrentlyBlocked( self, tag_slice ):
test_slices = { tag_slice }
- elif tag_slice.count( ':' ) == 1 and tag_slice.endswith( ':' ):
+ elif HydrusTags.IsNamespaceTagSlice( tag_slice ):
test_slices = { ':', tag_slice }
@@ -1718,7 +1719,7 @@ def _UpdateStatus( self ):
else:
- pretty_tag_filter = 'currently keeping: {}'.format( tag_filter.ToPermittedString() )
+ pretty_tag_filter = 'current filter: {}'.format( tag_filter.ToPermittedString() )
self._current_filter_st.setText( pretty_tag_filter )
@@ -1825,7 +1826,7 @@ def publish_callable( results ):
c.update( results )
- test_result_text = '{} pass, {} blocked!'.format( HydrusData.ToHumanInt( c[ True ] ), HydrusData.ToHumanInt( c[ False ] ) )
+ test_result_text = '{} pass, {} blocked!'.format( HydrusNumbers.ToHumanInt( c[ True ] ), HydrusNumbers.ToHumanInt( c[ False ] ) )
self._test_result_st.setObjectName( 'HydrusInvalid' )
@@ -2160,7 +2161,7 @@ def _UpdateSuffix( self ):
def _UpdateSummary( self ):
- file_summary = f'{HydrusData.ToHumanInt(len(self._medias))} files'
+ file_summary = f'{HydrusNumbers.ToHumanInt(len(self._medias))} files'
medias_and_tags = self._GetMediaAndTagPairs()
@@ -2217,16 +2218,16 @@ def _UpdateSummary( self ):
else:
- conflict_summary = f'{HydrusData.ToHumanInt( already_count )} files already have these tags.'
+ conflict_summary = f'{HydrusNumbers.ToHumanInt( already_count )} files already have these tags.'
elif already_count == 0:
- conflict_summary = f'{HydrusData.ToHumanInt( disagree_count )} files already have different tags for this namespace. Are you sure you are lined up correct?'
+ conflict_summary = f'{HydrusNumbers.ToHumanInt( disagree_count )} files already have different tags for this namespace. Are you sure you are lined up correct?'
else:
- conflict_summary = f'{HydrusData.ToHumanInt( already_count )} files already have these tags, and {HydrusData.ToHumanInt( disagree_count )} files already have different tags for this namespace. Are you sure you are lined up correct?'
+ conflict_summary = f'{HydrusNumbers.ToHumanInt( already_count )} files already have these tags, and {HydrusNumbers.ToHumanInt( disagree_count )} files already have different tags for this namespace. Are you sure you are lined up correct?'
label = f'For the {file_summary}, you are setting {tag_summary}.'
@@ -2863,11 +2864,11 @@ def _EnterTags( self, tags, only_add = False, only_remove = False, forced_reason
[ ( tag, count ) ] = tag_counts
- text = '{} "{}" for {} files'.format( choice_text_prefix, HydrusText.ElideText( tag, 64 ), HydrusData.ToHumanInt( count ) )
+ text = '{} "{}" for {} files'.format( choice_text_prefix, HydrusText.ElideText( tag, 64 ), HydrusNumbers.ToHumanInt( count ) )
else:
- text = '{} {} tags'.format( choice_text_prefix, HydrusData.ToHumanInt( len( choice_tags ) ) )
+ text = '{} {} tags'.format( choice_text_prefix, HydrusNumbers.ToHumanInt( len( choice_tags ) ) )
data = ( choice_action, choice_tags )
@@ -2883,11 +2884,11 @@ def _EnterTags( self, tags, only_add = False, only_remove = False, forced_reason
t_c = tag_counts
- t_c_lines.extend( ( '{} - {} files'.format( tag, HydrusData.ToHumanInt( count ) ) for ( tag, count ) in t_c ) )
+ t_c_lines.extend( ( '{} - {} files'.format( tag, HydrusNumbers.ToHumanInt( count ) ) for ( tag, count ) in t_c ) )
if len( tag_counts ) > 25:
- t_c_lines.append( 'and {} others'.format( HydrusData.ToHumanInt( len( tag_counts ) - 25 ) ) )
+ t_c_lines.append( 'and {} others'.format( HydrusNumbers.ToHumanInt( len( tag_counts ) - 25 ) ) )
tooltip = '\n'.join( t_c_lines )
@@ -2903,7 +2904,7 @@ def _EnterTags( self, tags, only_add = False, only_remove = False, forced_reason
else:
- message = 'Of the {} files being managed, some have that tag, but not all of them do, so there are different things you can do.'.format( HydrusData.ToHumanInt( len( self._media ) ) )
+ message = 'Of the {} files being managed, some have that tag, but not all of them do, so there are different things you can do.'.format( HydrusNumbers.ToHumanInt( len( self._media ) ) )
( choice_action, tags ) = ClientGUIDialogsQuick.SelectFromListButtons( self, 'What would you like to do?', bdc_choices, message = message )
@@ -2930,7 +2931,7 @@ def _EnterTags( self, tags, only_add = False, only_remove = False, forced_reason
else:
- tag_text = 'the ' + HydrusData.ToHumanInt( len( tags ) ) + ' tags'
+ tag_text = 'the ' + HydrusNumbers.ToHumanInt( len( tags ) ) + ' tags'
message = 'Enter a reason for ' + tag_text + ' to be removed. A janitor will review your petition.'
@@ -3358,7 +3359,7 @@ def RemoveTags( self, tags ):
else:
- message = 'Are you sure you want to remove these ' + HydrusData.ToHumanInt( len( tags ) ) + ' tags?'
+ message = 'Are you sure you want to remove these ' + HydrusNumbers.ToHumanInt( len( tags ) ) + ' tags?'
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -4500,7 +4501,7 @@ def qt_code( original_statuses_to_pairs, current_statuses_to_pairs, service_keys
self._sync_status_st.style().polish( self._sync_status_st )
- self._count_st.setText( 'Starting with '+HydrusData.ToHumanInt(len(original_statuses_to_pairs[HC.CONTENT_STATUS_CURRENT]))+' pairs.' )
+ self._count_st.setText( 'Starting with '+HydrusNumbers.ToHumanInt(len(original_statuses_to_pairs[HC.CONTENT_STATUS_CURRENT]))+' pairs.' )
self._listctrl_panel.setEnabled( True )
self._child_input.setEnabled( True )
@@ -5887,7 +5888,7 @@ def qt_code( original_statuses_to_pairs, current_statuses_to_pairs, service_keys
self._sync_status_st.style().polish( self._sync_status_st )
- self._count_st.setText( 'Starting with '+HydrusData.ToHumanInt(len(original_statuses_to_pairs[HC.CONTENT_STATUS_CURRENT]))+' pairs.' )
+ self._count_st.setText( 'Starting with '+HydrusNumbers.ToHumanInt(len(original_statuses_to_pairs[HC.CONTENT_STATUS_CURRENT]))+' pairs.' )
self._listctrl_panel.setEnabled( True )
@@ -6100,15 +6101,15 @@ def publish_callable( result ):
elif num_parents_to_sync == 0:
- message = '{} siblings to sync.'.format( HydrusData.ToHumanInt( num_siblings_to_sync ) )
+ message = '{} siblings to sync.'.format( HydrusNumbers.ToHumanInt( num_siblings_to_sync ) )
elif num_siblings_to_sync == 0:
- message = '{} parents to sync.'.format( HydrusData.ToHumanInt( num_parents_to_sync ) )
+ message = '{} parents to sync.'.format( HydrusNumbers.ToHumanInt( num_parents_to_sync ) )
else:
- message = '{} siblings and {} parents to sync.'.format( HydrusData.ToHumanInt( num_siblings_to_sync ), HydrusData.ToHumanInt( num_parents_to_sync ) )
+ message = '{} siblings and {} parents to sync.'.format( HydrusNumbers.ToHumanInt( num_siblings_to_sync ), HydrusNumbers.ToHumanInt( num_parents_to_sync ) )
if len( status[ 'waiting_on_tag_repos' ] ) > 0:
@@ -6134,7 +6135,7 @@ def publish_callable( result ):
else:
- message = '{} rules, all synced!'.format( HydrusData.ToHumanInt( num_ideal_rows ) )
+ message = '{} rules, all synced!'.format( HydrusNumbers.ToHumanInt( num_ideal_rows ) )
value = 1
@@ -6149,11 +6150,11 @@ def publish_callable( result ):
if num_ideal_rows == 0:
- message = 'Removing all siblings/parents, {} rules remaining.'.format( HydrusData.ToHumanInt( num_actual_rows ) )
+ message = 'Removing all siblings/parents, {} rules remaining.'.format( HydrusNumbers.ToHumanInt( num_actual_rows ) )
else:
- message = '{} rules applied now, moving to {}.'.format( HydrusData.ToHumanInt( num_actual_rows ), HydrusData.ToHumanInt( num_ideal_rows ) )
+ message = '{} rules applied now, moving to {}.'.format( HydrusNumbers.ToHumanInt( num_actual_rows ), HydrusNumbers.ToHumanInt( num_ideal_rows ) )
if num_actual_rows <= num_ideal_rows:
diff --git a/hydrus/client/gui/canvas/ClientGUICanvas.py b/hydrus/client/gui/canvas/ClientGUICanvas.py
index b045ef43d..fe9c0f3d0 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvas.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvas.py
@@ -8,6 +8,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTags
from hydrus.core import HydrusTime
@@ -1505,7 +1506,7 @@ def ShowMenu( self ):
if num_notes > 0:
- notes_str = '{} ({})'.format( notes_str, HydrusData.ToHumanInt( num_notes ) )
+ notes_str = '{} ({})'.format( notes_str, HydrusNumbers.ToHumanInt( num_notes ) )
ClientGUIMenus.AppendMenuItem( manage_menu, notes_str, 'Manage this file\'s notes.', self._ManageNotes )
@@ -2644,12 +2645,12 @@ def _GetIndexString( self ):
if num_committable > 0:
- components.append( '{} decisions'.format( HydrusData.ToHumanInt( num_committable ) ) )
+ components.append( '{} decisions'.format( HydrusNumbers.ToHumanInt( num_committable ) ) )
if num_deletable > 0:
- components.append( '{} deletes'.format( HydrusData.ToHumanInt( num_deletable ) ) )
+ components.append( '{} deletes'.format( HydrusNumbers.ToHumanInt( num_deletable ) ) )
if len( components ) == 0:
@@ -3017,12 +3018,12 @@ def pair_is_good( pair ):
if num_committable > 0:
- components.append( '{} decisions'.format( HydrusData.ToHumanInt( num_committable ) ) )
+ components.append( '{} decisions'.format( HydrusNumbers.ToHumanInt( num_committable ) ) )
if num_deletable > 0:
- components.append( '{} deletes'.format( HydrusData.ToHumanInt( num_deletable ) ) )
+ components.append( '{} deletes'.format( HydrusNumbers.ToHumanInt( num_deletable ) ) )
label = 'commit {} and continue?'.format( ' and '.join( components ) )
@@ -3316,12 +3317,12 @@ def TryToDoPreClose( self ):
if num_committable > 0:
- components.append( '{} decisions'.format( HydrusData.ToHumanInt( num_committable ) ) )
+ components.append( '{} decisions'.format( HydrusNumbers.ToHumanInt( num_committable ) ) )
if num_deletable > 0:
- components.append( '{} deletes'.format( HydrusData.ToHumanInt( num_deletable ) ) )
+ components.append( '{} deletes'.format( HydrusNumbers.ToHumanInt( num_deletable ) ) )
label = 'commit {}?'.format( ' and '.join( components ) )
@@ -3430,7 +3431,7 @@ def _GetIndexString( self ):
if self._current_media is None:
- index_string = '-/' + HydrusData.ToHumanInt( len( self._sorted_media ) )
+ index_string = '-/' + HydrusNumbers.ToHumanInt( len( self._sorted_media ) )
else:
@@ -3766,7 +3767,7 @@ def TryToDoPreClose( self ):
if len( kept ) > 0:
- kept_label = 'keep {}'.format( HydrusData.ToHumanInt( len( kept ) ) )
+ kept_label = 'keep {}'.format( HydrusNumbers.ToHumanInt( len( kept ) ) )
else:
@@ -3839,7 +3840,7 @@ def TryToDoPreClose( self ):
location_label = location_context.ToString( CG.client_controller.services_manager.GetName )
- delete_label = 'delete {} from {}'.format( HydrusData.ToHumanInt( num_deletable ), location_label )
+ delete_label = 'delete {} from {}'.format( HydrusNumbers.ToHumanInt( num_deletable ), location_label )
deletion_options.append( ( location_context, delete_label ) )
@@ -4626,7 +4627,7 @@ def ShowMenu( self ):
if num_notes > 0:
- notes_str = '{} ({})'.format( notes_str, HydrusData.ToHumanInt( num_notes ) )
+ notes_str = '{} ({})'.format( notes_str, HydrusNumbers.ToHumanInt( num_notes ) )
ClientGUIMenus.AppendMenuItem( manage_menu, notes_str, 'Manage this file\'s notes.', self._ManageNotes )
diff --git a/hydrus/client/gui/exporting/ClientGUIExport.py b/hydrus/client/gui/exporting/ClientGUIExport.py
index fc2759258..79fb6be55 100644
--- a/hydrus/client/gui/exporting/ClientGUIExport.py
+++ b/hydrus/client/gui/exporting/ClientGUIExport.py
@@ -2,6 +2,7 @@
import os
import time
import traceback
+import typing
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
@@ -10,6 +11,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusThreading
from hydrus.core import HydrusTime
@@ -52,7 +54,7 @@ def __init__( self, parent, export_folders ):
self._export_folders_panel.SetListCtrl( self._export_folders )
self._export_folders_panel.AddButton( 'add', self._AddFolder )
- self._export_folders_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ self._export_folders_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
self._export_folders_panel.AddDeleteButton()
#
@@ -134,7 +136,7 @@ def _AddFolder( self ):
export_folder.SetNonDupeName( self._GetExistingNames() )
- self._export_folders.AddDatas( ( export_folder, ) )
+ self._export_folders.AddDatas( ( export_folder, ), select_sort_and_scroll = True )
@@ -184,39 +186,36 @@ def _ConvertExportFolderToListCtrlTuples( self, export_folder: ClientExportingFi
def _Edit( self ):
- export_folders = self._export_folders.GetData( only_selected = True )
+ export_folder: typing.Optional[ ClientExportingFiles.ExportFolder ] = self._export_folders.GetTopSelectedData()
- edited_datas = []
+ if export_folder is None:
+
+ return
+
- for export_folder in export_folders:
+ with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit export folder' ) as dlg:
- with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit export folder' ) as dlg:
-
- panel = EditExportFolderPanel( dlg, export_folder )
+ panel = EditExportFolderPanel( dlg, export_folder )
+
+ dlg.SetPanel( panel )
+
+ if dlg.exec() == QW.QDialog.Accepted:
- dlg.SetPanel( panel )
+ edited_export_folder = panel.GetValue()
- if dlg.exec() == QW.QDialog.Accepted:
-
- edited_export_folder = panel.GetValue()
-
- self._export_folders.DeleteDatas( ( export_folder, ) )
+ if edited_export_folder.GetName() != export_folder.GetName():
- edited_export_folder.SetNonDupeName( self._GetExistingNames() )
+ existing_names = self._GetExistingNames()
- self._export_folders.AddDatas( ( edited_export_folder, ) )
+ existing_names.discard( export_folder.GetName() )
- edited_datas.append( edited_export_folder )
-
- else:
-
- return
+ edited_export_folder.SetNonDupeName( existing_names )
+ self._export_folders.ReplaceData( export_folder, edited_export_folder, sort_and_scroll = True )
+
- self._export_folders.SelectDatas( edited_datas )
-
def _GetExistingNames( self ):
@@ -662,7 +661,7 @@ def _ConvertDataToListCtrlTuples( self, media ):
path = str( e )
- pretty_number = HydrusData.ToHumanInt( number )
+ pretty_number = HydrusNumbers.ToHumanInt( number )
pretty_mime = HC.mime_string_lookup[ mime ]
pretty_path = path
@@ -890,7 +889,7 @@ def do_it( directory, metadata_routers, delete_afterwards, export_symlinks, quit
win = CG.client_controller.gui
- ClientGUIDialogsMessage.ShowCritical( win, 'Problem during file export!', f'Encountered a problem while attempting to export file #{HydrusData.ToHumanInt( number )}:\n\n{traceback.format_exc()}' )
+ ClientGUIDialogsMessage.ShowCritical( win, 'Problem during file export!', f'Encountered a problem while attempting to export file #{HydrusNumbers.ToHumanInt( number )}:\n\n{traceback.format_exc()}' )
break
diff --git a/hydrus/client/gui/importing/ClientGUIImport.py b/hydrus/client/gui/importing/ClientGUIImport.py
index 7e583df94..bff7a628c 100644
--- a/hydrus/client/gui/importing/ClientGUIImport.py
+++ b/hydrus/client/gui/importing/ClientGUIImport.py
@@ -8,7 +8,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
@@ -98,6 +98,61 @@ def SetValue( self, checker_options ):
self._SetValue( checker_options )
+
+class CheckBoxLineEdit( QW.QWidget ):
+
+ valueChanged = QC.Signal()
+
+ def __init__( self, parent, label ):
+
+ QW.QWidget.__init__( self, parent )
+
+ self._checkbox = QW.QCheckBox( self )
+
+ self._lineedit = QW.QLineEdit( self )
+ self._lineedit.setMinimumWidth( 100 )
+
+ self._checkbox.clicked.connect( self.valueChanged )
+ self._lineedit.textEdited.connect( self._LineEditChanged )
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self, label ), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._lineedit, CC.FLAGS_EXPAND_BOTH_WAYS )
+
+ self.setLayout( hbox )
+
+
+ def _LineEditChanged( self ):
+
+ if self.IsChecked():
+
+ self.valueChanged.emit()
+
+
+
+ def GetValue( self ):
+
+ return self._lineedit.text()
+
+
+ def IsChecked( self ):
+
+ return self._checkbox.isChecked()
+
+
+ def SetChecked( self, value ):
+
+ self._checkbox.setChecked( value )
+
+
+ def SetValue( self, text ):
+
+ self._lineedit.setText( text )
+
+
+
class FilenameTaggingOptionsPanel( QW.QWidget ):
movePageLeft = QC.Signal()
@@ -199,7 +254,7 @@ def __init__( self, parent, service_key, filename_tagging_options, present_for_a
quick_namespaces_listctrl_panel.SetListCtrl( self._quick_namespaces_list )
quick_namespaces_listctrl_panel.AddButton( 'add', self.AddQuickNamespace )
- quick_namespaces_listctrl_panel.AddButton( 'edit', self.EditQuickNamespaces, enabled_only_on_selection = True )
+ quick_namespaces_listctrl_panel.AddButton( 'edit', self.EditQuickNamespaces, enabled_only_on_single_selection = True )
quick_namespaces_listctrl_panel.AddDeleteButton()
#
@@ -478,10 +533,7 @@ def __init__( self, parent, service_key, filename_tagging_options, present_for_a
self._checkboxes_panel = ClientGUICommon.StaticBox( self, 'misc' )
- self._filename_namespace = QW.QLineEdit( self._checkboxes_panel )
- self._filename_namespace.setMinimumWidth( 100 )
-
- self._filename_checkbox = QW.QCheckBox( 'add filename? [namespace]', self._checkboxes_panel )
+ self._filename_namespace = CheckBoxLineEdit( self._checkboxes_panel, 'add filename? [namespace]' )
# TODO: Ok, when we move to arbitrary string processing from filenames, and we scrub this, we will want 'easy-add rule' buttons to do this
# When we do, add a thing that adds the nth, including negative n index values. as some users want four deep
@@ -500,12 +552,9 @@ def __init__( self, parent, service_key, filename_tagging_options, present_for_a
for ( index, phrase ) in directory_items:
- dir_checkbox = QW.QCheckBox( 'add '+phrase+' directory? [namespace]', self._checkboxes_panel )
-
- dir_namespace_textctrl = QW.QLineEdit( self._checkboxes_panel )
- dir_namespace_textctrl.setMinimumWidth( 100 )
+ widget = CheckBoxLineEdit( self._checkboxes_panel, f'add {phrase} directory? [namespace]' )
- self._directory_namespace_controls[ index ] = ( dir_checkbox, dir_namespace_textctrl )
+ self._directory_namespace_controls[ index ] = widget
#
@@ -516,18 +565,17 @@ def __init__( self, parent, service_key, filename_tagging_options, present_for_a
( add_filename_boolean, add_filename_namespace ) = add_filename
- self._filename_checkbox.setChecked( add_filename_boolean )
- self._filename_namespace.setText( add_filename_namespace )
+ self._filename_namespace.SetChecked( add_filename_boolean )
+ self._filename_namespace.SetValue( add_filename_namespace )
for ( index, ( dir_boolean, dir_namespace ) ) in directory_dict.items():
- ( dir_checkbox, dir_namespace_textctrl ) = self._directory_namespace_controls[ index ]
+ widget = self._directory_namespace_controls[ index ]
- dir_checkbox.setChecked( dir_boolean )
- dir_namespace_textctrl.setText( dir_namespace )
+ widget.SetChecked( dir_boolean )
+ widget.SetValue( dir_namespace )
- dir_checkbox.clicked.connect( self.tagsChanged )
- dir_namespace_textctrl.textChanged.connect( self.tagsChanged )
+ widget.valueChanged.connect( self.tagsChanged )
#
@@ -540,23 +588,13 @@ def __init__( self, parent, service_key, filename_tagging_options, present_for_a
self._single_tags_panel.Add( self._tag_autocomplete_selection, CC.FLAGS_EXPAND_PERPENDICULAR )
self._single_tags_panel.Add( self._single_tags_paste_button, CC.FLAGS_EXPAND_PERPENDICULAR )
- filename_hbox = QP.HBoxLayout()
-
- QP.AddToLayout( filename_hbox, self._filename_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( filename_hbox, self._filename_namespace, CC.FLAGS_EXPAND_BOTH_WAYS )
-
- self._checkboxes_panel.Add( filename_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
+ self._checkboxes_panel.Add( self._filename_namespace, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
for index in ( 0, 1, 2, -3, -2, -1 ):
- hbox = QP.HBoxLayout()
-
- ( dir_checkbox, dir_namespace_textctrl ) = self._directory_namespace_controls[ index ]
-
- QP.AddToLayout( hbox, dir_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, dir_namespace_textctrl, CC.FLAGS_EXPAND_BOTH_WAYS )
+ widget = self._directory_namespace_controls[ index ]
- self._checkboxes_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
+ self._checkboxes_panel.Add( widget, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._checkboxes_panel.Add( QW.QWidget( self._checkboxes_panel ), CC.FLAGS_EXPAND_BOTH_WAYS )
@@ -574,8 +612,7 @@ def __init__( self, parent, service_key, filename_tagging_options, present_for_a
self._tags.tagsRemoved.connect( self.tagsChanged )
self._single_tags.tagsRemoved.connect( self.SingleTagsRemoved )
- self._filename_namespace.textChanged.connect( self.tagsChanged )
- self._filename_checkbox.clicked.connect( self.tagsChanged )
+ self._filename_namespace.valueChanged.connect( self.tagsChanged )
self._tag_autocomplete_all.tagsPasted.connect( self.EnterTagsOnlyAdd )
self._tag_autocomplete_selection.tagsPasted.connect( self.EnterTagsSingleOnlyAdd )
@@ -769,13 +806,13 @@ def UpdateFilenameTaggingOptions( self, filename_tagging_options ):
tags_for_all = self._tags.GetTags()
- add_filename = ( self._filename_checkbox.isChecked(), self._filename_namespace.text() )
+ add_filename = ( self._filename_namespace.IsChecked(), self._filename_namespace.GetValue() )
directories_dict = {}
- for ( index, ( dir_checkbox, dir_namespace_textctrl ) ) in self._directory_namespace_controls.items():
+ for ( index, widget ) in self._directory_namespace_controls.items():
- directories_dict[ index ] = ( dir_checkbox.isChecked(), dir_namespace_textctrl.text() )
+ directories_dict[ index ] = ( widget.IsChecked(), widget.GetValue() )
filename_tagging_options.SimpleSetTuple( tags_for_all, add_filename, directories_dict )
@@ -955,7 +992,7 @@ def _ConvertDataToListCtrlTuples( self, data ):
tags = self._GetTags( index, path )
- pretty_index = HydrusData.ToHumanInt( index + 1 )
+ pretty_index = HydrusNumbers.ToHumanInt( index + 1 )
pretty_path = path
pretty_tags = ', '.join( tags )
@@ -1063,7 +1100,7 @@ def _ConvertDataToListCtrlTuples( self, data ):
strings = self._GetStrings( path )
- pretty_index = HydrusData.ToHumanInt( index + 1 )
+ pretty_index = HydrusNumbers.ToHumanInt( index + 1 )
pretty_path = path
pretty_strings = ', '.join( strings )
diff --git a/hydrus/client/gui/importing/ClientGUIImportFolders.py b/hydrus/client/gui/importing/ClientGUIImportFolders.py
index a2d53c561..d4961e2b3 100644
--- a/hydrus/client/gui/importing/ClientGUIImportFolders.py
+++ b/hydrus/client/gui/importing/ClientGUIImportFolders.py
@@ -1,4 +1,5 @@
import os
+import typing
from qtpy import QtWidgets as QW
@@ -41,7 +42,7 @@ def __init__( self, parent, import_folders ):
import_folders_panel.SetListCtrl( self._import_folders )
import_folders_panel.AddButton( 'add', self._Add )
- import_folders_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ import_folders_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
import_folders_panel.AddDeleteButton()
#
@@ -85,9 +86,7 @@ def _Add( self ):
import_folder.SetNonDupeName( self._GetExistingNames() )
- self._import_folders.AddDatas( ( import_folder, ) )
-
- self._import_folders.Sort()
+ self._import_folders.AddDatas( ( import_folder, ), select_sort_and_scroll = True )
@@ -122,41 +121,36 @@ def _ConvertImportFolderToListCtrlTuples( self, import_folder ):
def _Edit( self ):
- edited_datas = []
+ import_folder: typing.Optional[ ClientImportLocal.ImportFolder ] = self._import_folders.GetTopSelectedData()
- import_folders = self._import_folders.GetData( only_selected = True )
+ if import_folder is None:
+
+ return
+
- for import_folder in import_folders:
+ with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit import folder' ) as dlg:
- with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit import folder' ) as dlg:
-
- panel = EditImportFolderPanel( dlg, import_folder )
+ panel = EditImportFolderPanel( dlg, import_folder )
+
+ dlg.SetPanel( panel )
+
+ if dlg.exec() == QW.QDialog.Accepted:
- dlg.SetPanel( panel )
+ edited_import_folder = panel.GetValue()
- if dlg.exec() == QW.QDialog.Accepted:
-
- edited_import_folder = panel.GetValue()
-
- self._import_folders.DeleteDatas( ( import_folder, ) )
+ if edited_import_folder.GetName() != import_folder.GetName():
- edited_import_folder.SetNonDupeName( self._GetExistingNames() )
+ existing_names = self._GetExistingNames()
- self._import_folders.AddDatas( ( edited_import_folder, ) )
-
- edited_datas.append( edited_import_folder )
-
- else:
+ existing_names.discard( import_folder.GetName() )
- break
+ edited_import_folder.SetNonDupeName( existing_names )
+ self._import_folders.ReplaceData( import_folder, edited_import_folder, sort_and_scroll = True )
+
- self._import_folders.SelectDatas( edited_datas )
-
- self._import_folders.Sort()
-
def _GetExistingNames( self ):
@@ -167,7 +161,7 @@ def _GetExistingNames( self ):
return names
- def GetValue( self ):
+ def GetValue( self ) -> typing.Collection[ ClientImportLocal.ImportFolder ]:
import_folders = self._import_folders.GetData()
@@ -259,7 +253,7 @@ def create_choice():
filename_tagging_options_panel.SetListCtrl( self._filename_tagging_options )
filename_tagging_options_panel.AddButton( 'add', self._AddFilenameTaggingOptions )
- filename_tagging_options_panel.AddButton( 'edit', self._EditFilenameTaggingOptions, enabled_only_on_selection = True )
+ filename_tagging_options_panel.AddButton( 'edit', self._EditFilenameTaggingOptions, enabled_only_on_single_selection = True )
filename_tagging_options_panel.AddDeleteButton()
metadata_routers = self._import_folder.GetMetadataRouters()
diff --git a/hydrus/client/gui/importing/ClientGUIImportOptions.py b/hydrus/client/gui/importing/ClientGUIImportOptions.py
index 645cbe032..302d534c1 100644
--- a/hydrus/client/gui/importing/ClientGUIImportOptions.py
+++ b/hydrus/client/gui/importing/ClientGUIImportOptions.py
@@ -7,6 +7,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
@@ -175,7 +176,7 @@ def __init__( self, parent: QW.QWidget, file_import_options: FileImportOptions.F
destination_panel = ClientGUICommon.StaticBox( self._specific_options_panel, 'import destinations' )
- self._destination_location_context_st = ClientGUICommon.BetterStaticText( destination_panel, 'If you have more than one local file service, you can send these imports to other/multiple locations. This will still apply if the file is \'already in db\' by ensuring the file is added to any and all of the services if it is not already.' )
+ self._destination_location_context_st = ClientGUICommon.BetterStaticText( destination_panel, 'If you have more than one local file service, you can send these imports to other/multiple locations.' )
destination_location_context = file_import_options.GetDestinationLocationContext()
@@ -190,9 +191,13 @@ def __init__( self, parent: QW.QWidget, file_import_options: FileImportOptions.F
post_import_panel = ClientGUICommon.StaticBox( self._specific_options_panel, 'post-import actions' )
self._auto_archive = QW.QCheckBox( post_import_panel )
- tt = 'Instead of adding imports to the inbox for further processing, this will archive them immediately. You can do this on an import you absolutely know is all good. This will still apply to \'already in db\' results.'
+ tt = 'Instead of adding imports to the inbox for further processing, this will archive them immediately. You can do this on an import you absolutely know is all good.'
self._auto_archive.setToolTip( tt )
+ self._do_content_updates_on_already_in_db_files = QW.QCheckBox( post_import_panel )
+ tt = 'Normally, the "import destinations" and "archive all imports" options apply both to "successful" new files and those "already in db", so a file you already have may be retroactively archived and added to any missing destinations. If you need to only do this on new files, uncheck this.'
+ self._do_content_updates_on_already_in_db_files.setToolTip( ClientGUIFunctions.WrapToolTip( tt ) )
+
self._associate_primary_urls = QW.QCheckBox( post_import_panel )
tt = 'Any URL in the \'chain\' to the file will be linked to it as a \'known url\' unless that URL has a matching URL Class that is set otherwise. Normally, since Gallery URL Classes are by default set not to associate, this means the file will get a visible Post URL and a less prominent direct File URL.'
tt += '\n' * 2
@@ -272,6 +277,7 @@ def __init__( self, parent: QW.QWidget, file_import_options: FileImportOptions.F
rows = []
rows.append( ( 'archive all imports: ', self._auto_archive ) )
+ rows.append( ( 'archive and set import destinations to \'already in db\' files: ', self._do_content_updates_on_already_in_db_files ) )
if show_downloader_options and CG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
@@ -386,6 +392,7 @@ def _SetValue( self, file_import_options: FileImportOptions.FileImportOptions ):
associate_source_urls = file_import_options.ShouldAssociateSourceURLs()
self._auto_archive.setChecked( automatic_archive )
+ self._do_content_updates_on_already_in_db_files.setChecked( file_import_options.GetDoContentUpdatesOnAlreadyInDBFiles() )
self._associate_primary_urls.setChecked( associate_primary_urls )
self._associate_source_urls.setChecked( associate_source_urls )
@@ -533,6 +540,7 @@ def GetValue( self ) -> FileImportOptions.FileImportOptions:
file_import_options.SetAllowedSpecificFiletypes( self._mimes.GetValue() )
file_import_options.SetDestinationLocationContext( destination_location_context )
file_import_options.SetPostImportOptions( automatic_archive, associate_primary_urls, associate_source_urls )
+ file_import_options.SetDoContentUpdatesOnAlreadyInDBFiles( self._do_content_updates_on_already_in_db_files.isChecked() )
file_import_options.SetPresentationImportOptions( presentation_import_options )
@@ -1202,7 +1210,7 @@ def _GetCogIconMenuItems( self ):
def _UpdateAdditionalTagsButtonLabel( self ):
- button_label = HydrusData.ToHumanInt( len( self._additional_tags ) ) + ' additional tags'
+ button_label = HydrusNumbers.ToHumanInt( len( self._additional_tags ) ) + ' additional tags'
self._additional_button.setText( button_label )
@@ -1569,7 +1577,7 @@ def _UpdateTagWhitelistLabel( self ):
else:
- label = 'whitelist of {} tags'.format( HydrusData.ToHumanInt( len( self._tag_whitelist ) ) )
+ label = 'whitelist of {} tags'.format( HydrusNumbers.ToHumanInt( len( self._tag_whitelist ) ) )
self._tag_whitelist_button.setText( label )
diff --git a/hydrus/client/gui/lists/ClientGUIListBoxes.py b/hydrus/client/gui/lists/ClientGUIListBoxes.py
index 2300a5f90..7fded8478 100644
--- a/hydrus/client/gui/lists/ClientGUIListBoxes.py
+++ b/hydrus/client/gui/lists/ClientGUIListBoxes.py
@@ -13,13 +13,16 @@
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
+from hydrus.core import HydrusText
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientGlobals as CG
from hydrus.client import ClientLocation
from hydrus.client import ClientSerialisable
+from hydrus.client import ClientServices
from hydrus.client.gui import ClientGUIAsync
from hydrus.client.gui import ClientGUICore as CGC
from hydrus.client.gui import ClientGUIDialogsMessage
@@ -423,7 +426,7 @@ def _Delete( self ):
from hydrus.client.gui import ClientGUIDialogsQuick
- result = ClientGUIDialogsQuick.GetYesNo( self, 'Remove {} selected?'.format( HydrusData.ToHumanInt( num_selected ) ) )
+ result = ClientGUIDialogsQuick.GetYesNo( self, 'Remove {} selected?'.format( HydrusNumbers.ToHumanInt( num_selected ) ) )
if result != QW.QDialog.Accepted:
@@ -698,7 +701,7 @@ def _ImportObject( self, obj, can_present_messages = True ):
if num_added > 0:
- message = '{} objects added!'.format( HydrusData.ToHumanInt( num_added ) )
+ message = '{} objects added!'.format( HydrusNumbers.ToHumanInt( num_added ) )
ClientGUIDialogsMessage.ShowInformation( self, message )
@@ -948,7 +951,7 @@ def _Delete( self ):
from hydrus.client.gui import ClientGUIDialogsQuick
- result = ClientGUIDialogsQuick.GetYesNo( self, 'Remove {} selected?'.format( HydrusData.ToHumanInt( num_selected ) ) )
+ result = ClientGUIDialogsQuick.GetYesNo( self, 'Remove {} selected?'.format( HydrusNumbers.ToHumanInt( num_selected ) ) )
if result == QW.QDialog.Accepted:
@@ -2647,7 +2650,7 @@ def _ProcessMenuTagEvent( self, command ):
if command == 'hide':
- message = f'Hide{HydrusData.ConvertManyStringsToNiceInsertableHumanSummary( tags )}from here?'
+ message = f'Hide{HydrusText.ConvertManyStringsToNiceInsertableHumanSummary( tags )}from here?'
from hydrus.client.gui import ClientGUIDialogsQuick
@@ -2665,7 +2668,7 @@ def _ProcessMenuTagEvent( self, command ):
namespaces = { namespace for ( namespace, subtag ) in ( HydrusTags.SplitTag( tag ) for tag in tags ) }
nice_namespaces = [ ClientTags.RenderNamespaceForUser( namespace ) for namespace in namespaces ]
- message = f'Hide{HydrusData.ConvertManyStringsToNiceInsertableHumanSummary( nice_namespaces )}tags from here?'
+ message = f'Hide{HydrusText.ConvertManyStringsToNiceInsertableHumanSummary( nice_namespaces )}tags from here?'
from hydrus.client.gui import ClientGUIDialogsQuick
@@ -2886,7 +2889,7 @@ def ShowMenu( self ):
else:
- selection_string = '{} selected'.format( HydrusData.ToHumanInt( len( selected_copyable_tag_strings ) ) )
+ selection_string = '{} selected'.format( HydrusNumbers.ToHumanInt( len( selected_copyable_tag_strings ) ) )
ClientGUIMenus.AppendMenuItem( copy_menu, selection_string, 'Copy the selected tags to your clipboard.', self._ProcessMenuCopyEvent, COPY_SELECTED_TAGS )
@@ -2907,7 +2910,7 @@ def ShowMenu( self ):
else:
- sub_selection_string = '{} selected subtags'.format( HydrusData.ToHumanInt( len( selected_copyable_subtag_strings ) ) )
+ sub_selection_string = '{} selected subtags'.format( HydrusNumbers.ToHumanInt( len( selected_copyable_subtag_strings ) ) )
ClientGUIMenus.AppendMenuItem( copy_menu, sub_selection_string, 'Copy the selected subtags to your clipboard.', self._ProcessMenuCopyEvent, COPY_SELECTED_SUBTAGS )
@@ -3115,7 +3118,7 @@ def group_and_sort_parents_to_service_keys( p_to_s_ks, c_to_s_ks ):
else:
- siblings_menu.setTitle( '{} siblings'.format( HydrusData.ToHumanInt( num_siblings ) ) )
+ siblings_menu.setTitle( '{} siblings'.format( HydrusNumbers.ToHumanInt( num_siblings ) ) )
#
@@ -3158,7 +3161,7 @@ def group_and_sort_parents_to_service_keys( p_to_s_ks, c_to_s_ks ):
else:
- parents_menu.setTitle( '{} parents, {} children'.format( HydrusData.ToHumanInt( num_parents ), HydrusData.ToHumanInt( num_children ) ) )
+ parents_menu.setTitle( '{} parents, {} children'.format( HydrusNumbers.ToHumanInt( num_parents ), HydrusNumbers.ToHumanInt( num_children ) ) )
ClientGUIMenus.AppendSeparator( parents_menu )
@@ -3374,7 +3377,7 @@ def group_and_sort_parents_to_service_keys( p_to_s_ks, c_to_s_ks ):
else:
- namespace_label = f'{HydrusData.ToHumanInt( len( namespaces ) )} selected namespaces from here'
+ namespace_label = f'{HydrusNumbers.ToHumanInt( len( namespaces ) )} selected namespaces from here'
if len( selected_actual_tags ) == 1:
@@ -3385,7 +3388,7 @@ def group_and_sort_parents_to_service_keys( p_to_s_ks, c_to_s_ks ):
else:
- actual_tag_label = f'{HydrusData.ToHumanInt( len( selected_actual_tags ) )} selected tags from here'
+ actual_tag_label = f'{HydrusNumbers.ToHumanInt( len( selected_actual_tags ) )} selected tags from here'
hide_menu = ClientGUIMenus.GenerateMenu( menu )
@@ -3403,7 +3406,7 @@ def add_favourite_tags( tags ):
if len( tags ) > 5:
- message = f'Add{HydrusData.ConvertManyStringsToNiceInsertableHumanSummary( tags )}to the favourites list?'
+ message = f'Add{HydrusText.ConvertManyStringsToNiceInsertableHumanSummary( tags )}to the favourites list?'
from hydrus.client.gui import ClientGUIDialogsQuick
@@ -3426,7 +3429,7 @@ def add_favourite_tags( tags ):
def remove_favourite_tags( tags ):
- message = f'Remove{HydrusData.ConvertManyStringsToNiceInsertableHumanSummary( tags )}from the favourites list?'
+ message = f'Remove{HydrusText.ConvertManyStringsToNiceInsertableHumanSummary( tags )}from the favourites list?'
from hydrus.client.gui import ClientGUIDialogsQuick
@@ -3465,7 +3468,7 @@ def remove_favourite_tags( tags ):
else:
- label = f'Add {HydrusData.ToHumanInt( len( to_add ) )} selected tags to favourites'
+ label = f'Add {HydrusNumbers.ToHumanInt( len( to_add ) )} selected tags to favourites'
description = 'Add these tags to the favourites list.'
@@ -3483,7 +3486,7 @@ def remove_favourite_tags( tags ):
else:
- label = f'Remove {HydrusData.ToHumanInt( len( to_remove ) )} selected tags from favourites'
+ label = f'Remove {HydrusNumbers.ToHumanInt( len( to_remove ) )} selected tags from favourites'
description = 'Add these tags to the favourites list.'
@@ -3524,6 +3527,66 @@ def regen_tags():
ClientGUIMenus.AppendMenu( menu, submenu, 'maintenance' )
+ tag_repos: typing.Collection[ ClientServices.ServiceRepository ] = CG.client_controller.services_manager.GetServices( ( HC.TAG_REPOSITORY, ) )
+
+ we_are_admin = True in ( tag_repo.HasPermission( HC.CONTENT_TYPE_MAPPINGS, HC.PERMISSION_ACTION_MODERATE ) for tag_repo in tag_repos )
+
+ noun = 'tags'
+
+ from hydrus.client.gui.services import ClientGUIModalClientsideServiceActions
+ from hydrus.client.gui.services import ClientGUIModalServersideServiceActions
+
+ if we_are_admin:
+
+ ClientGUIMenus.AppendSeparator( menu )
+
+ for tag_repo in tag_repos:
+
+ if not tag_repo.HasPermission( HC.CONTENT_TYPE_MAPPINGS, HC.PERMISSION_ACTION_MODERATE ):
+
+ continue
+
+
+ service_key = tag_repo.GetServiceKey()
+ service_submenu = ClientGUIMenus.GenerateMenu( menu )
+
+ if tag_repo.HasPermission( HC.CONTENT_TYPE_OPTIONS, HC.PERMISSION_ACTION_MODERATE ):
+
+ try:
+
+ tag_filter = tag_repo.GetTagFilter()
+
+ tags_currently_ok = { tag for tag in selected_actual_tags if tag_filter.TagOK( tag ) }
+ tags_currently_not_ok = { tag for tag in selected_actual_tags if tag not in tags_currently_ok }
+
+ if len( tags_currently_ok ) > 0:
+
+ label = f'block {HydrusText.ConvertManyStringsToNiceInsertableHumanSummarySingleLine(tags_currently_ok,noun)}{HC.UNICODE_ELLIPSIS}'
+
+ ClientGUIMenus.AppendMenuItem( service_submenu, label, 'Change the tag filter for this service.', ClientGUIModalServersideServiceActions.ManageServiceOptionsTagFilter, self, service_key, new_tags_to_block = tags_currently_ok )
+
+
+ if len( tags_currently_not_ok ) > 0:
+
+ label = f're-allow {HydrusText.ConvertManyStringsToNiceInsertableHumanSummarySingleLine(tags_currently_not_ok,noun)}{HC.UNICODE_ELLIPSIS}'
+
+ ClientGUIMenus.AppendMenuItem( service_submenu, label, 'Change the tag filter for this service.', ClientGUIModalServersideServiceActions.ManageServiceOptionsTagFilter, self, service_key, new_tags_to_allow = tags_currently_not_ok )
+
+
+ except:
+
+ ClientGUIMenus.AppendMenuLabel( service_submenu, 'could not fetch service tag filter! maybe your account is unsynced?' )
+
+
+
+ ClientGUIMenus.AppendSeparator( service_submenu )
+
+ ClientGUIMenus.AppendMenuItem( service_submenu, f'delete all {HydrusText.ConvertManyStringsToNiceInsertableHumanSummarySingleLine(selected_actual_tags,noun)}{HC.UNICODE_ELLIPSIS}', 'Delete every instance of this tag from the repository.', ClientGUIModalClientsideServiceActions.OpenPurgeTagsWindow, self, service_key, selected_actual_tags )
+
+ ClientGUIMenus.AppendMenu( menu, service_submenu, 'admin: ' + tag_repo.GetName() )
+
+
+
CGC.core().PopupMenu( self, menu )
diff --git a/hydrus/client/gui/lists/ClientGUIListBoxesData.py b/hydrus/client/gui/lists/ClientGUIListBoxesData.py
index 8d3293821..4d33abd48 100644
--- a/hydrus/client/gui/lists/ClientGUIListBoxesData.py
+++ b/hydrus/client/gui/lists/ClientGUIListBoxesData.py
@@ -2,8 +2,8 @@
import typing
from hydrus.core import HydrusData
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTags
-from hydrus.core import HydrusTime
from hydrus.client.metadata import ClientTags
from hydrus.client.search import ClientSearch
@@ -239,7 +239,7 @@ def _AppendParentsTextWithNamespaces( self, rows_of_texts_with_namespaces, rende
def _AppendParentSuffixTagTextWithNamespace( self, texts_with_namespaces ):
- parents_text = ' ({} parents)'.format( HydrusData.ToHumanInt( len( self._parent_tags ) ) )
+ parents_text = ' ({} parents)'.format( HydrusNumbers.ToHumanInt( len( self._parent_tags ) ) )
texts_with_namespaces.append( ( parents_text, 'namespace', '' ) )
@@ -397,22 +397,22 @@ def GetRowsOfPresentationTextsWithNamespaces( self, render_for_user: bool, sibli
if self._current_count > 0:
- tag_text += ' ({})'.format( HydrusData.ToHumanInt( self._current_count ) )
+ tag_text += ' ({})'.format( HydrusNumbers.ToHumanInt( self._current_count ) )
if self._pending_count > 0:
- tag_text += ' (+{})'.format( HydrusData.ToHumanInt( self._pending_count ) )
+ tag_text += ' (+{})'.format( HydrusNumbers.ToHumanInt( self._pending_count ) )
if self._petitioned_count > 0:
- tag_text += ' (-{})'.format( HydrusData.ToHumanInt( self._petitioned_count ) )
+ tag_text += ' (-{})'.format( HydrusNumbers.ToHumanInt( self._petitioned_count ) )
if self._deleted_count > 0:
- tag_text += ' (X{})'.format( HydrusData.ToHumanInt( self._deleted_count ) )
+ tag_text += ' (X{})'.format( HydrusNumbers.ToHumanInt( self._deleted_count ) )
else:
@@ -576,7 +576,7 @@ def GetRowsOfPresentationTextsWithNamespaces( self, render_for_user: bool, sibli
elif parent_decoration_allowed:
- parents_text = ' ({} parents)'.format( HydrusData.ToHumanInt( len( parent_preds ) ) )
+ parents_text = ' ({} parents)'.format( HydrusNumbers.ToHumanInt( len( parent_preds ) ) )
first_row_of_texts_and_namespaces.append( ( parents_text, 'namespace', '' ) )
diff --git a/hydrus/client/gui/lists/ClientGUIListCtrl.py b/hydrus/client/gui/lists/ClientGUIListCtrl.py
index 6055f281f..0d254494a 100644
--- a/hydrus/client/gui/lists/ClientGUIListCtrl.py
+++ b/hydrus/client/gui/lists/ClientGUIListCtrl.py
@@ -6,7 +6,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
@@ -561,6 +561,8 @@ def AddDatas( self, datas: typing.Iterable[ object ], select_sort_and_scroll = F
if select_sort_and_scroll:
+ self.clearSelection()
+
self.SelectDatas( datas )
self.Sort()
@@ -894,14 +896,14 @@ def ScrollToData( self, data: object ):
self.scrollToItem( item, hint = QW.QAbstractItemView.ScrollHint.PositionAtCenter )
+ self.setFocus( QC.Qt.OtherFocusReason )
+
def SelectDatas( self, datas: typing.Iterable[ object ], deselect_others = False ):
datas = [ QP.ListsToTuples( data ) for data in datas ]
- self.clearFocus()
-
selectee_indices = { self._data_to_indices[ data ] for data in datas if data in self._data_to_indices }
if deselect_others:
@@ -1611,7 +1613,7 @@ def _ImportObject( self, obj, can_present_messages = True ):
if can_present_messages and num_added > 0:
- message = '{} objects added!'.format( HydrusData.ToHumanInt( num_added ) )
+ message = '{} objects added!'.format( HydrusNumbers.ToHumanInt( num_added ) )
ClientGUIDialogsMessage.ShowInformation( self, message )
@@ -1896,7 +1898,7 @@ def ImportFromDragDrop( self, paths ):
from hydrus.client.gui import ClientGUIDialogsQuick
- message = 'Try to import the {} dropped files to this list? I am expecting json or png files.'.format( HydrusData.ToHumanInt( len( paths ) ) )
+ message = 'Try to import the {} dropped files to this list? I am expecting json or png files.'.format( HydrusNumbers.ToHumanInt( len( paths ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
diff --git a/hydrus/client/gui/media/ClientGUIMediaMenus.py b/hydrus/client/gui/media/ClientGUIMediaMenus.py
index a6876003c..5c5bbe04e 100644
--- a/hydrus/client/gui/media/ClientGUIMediaMenus.py
+++ b/hydrus/client/gui/media/ClientGUIMediaMenus.py
@@ -8,6 +8,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusData
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core.files.images import HydrusImageHandling
@@ -124,7 +125,7 @@ def AddDuplicatesMenu( win: QW.QWidget, menu: QW.QMenu, location_context: Client
if count > 0:
- label = 'view {} {}'.format( HydrusData.ToHumanInt( count ), HC.duplicate_type_string_lookup[ duplicate_type ] )
+ label = 'view {} {}'.format( HydrusNumbers.ToHumanInt( count ), HC.duplicate_type_string_lookup[ duplicate_type ] )
ClientGUIMenus.AppendMenuItem( duplicates_menu, label, 'Show these duplicates in a new page.', ClientGUIMediaSimpleActions.ShowDuplicatesInNewPage, job_location_context, focused_hash, duplicate_type )
@@ -178,13 +179,13 @@ def AddDuplicatesMenu( win: QW.QWidget, menu: QW.QMenu, location_context: Client
if multiple_selected:
- label = 'set this file as better than the ' + HydrusData.ToHumanInt( num_selected - 1 ) + ' other selected'
+ label = 'set this file as better than the ' + HydrusNumbers.ToHumanInt( num_selected - 1 ) + ' other selected'
ClientGUIMenus.AppendMenuItem( duplicates_action_submenu, label, 'Set the focused media to be better than the other selected files.', win.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'
+ num_pairs_text = HydrusNumbers.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.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_DUPLICATE_MEDIA_SET_SAME_QUALITY ) )
@@ -538,7 +539,7 @@ def call_generator( u ):
urls = [ url for ( label, url ) in focus_matched_labels_and_urls ]
- label = 'this file\'s ' + HydrusData.ToHumanInt( len( urls ) ) + ' recognised urls'
+ label = 'this file\'s ' + HydrusNumbers.ToHumanInt( len( urls ) ) + ' recognised urls'
ClientGUIMenus.AppendMenuItem( urls_visit_menu, label, 'Open these urls in your web browser.', ClientGUIMediaModalActions.OpenURLs, win, urls )
@@ -551,7 +552,7 @@ def call_generator( u ):
urls = [ url for ( label, url ) in focus_labels_and_urls ]
- label = 'this file\'s ' + HydrusData.ToHumanInt( len( urls ) ) + ' urls'
+ label = 'this file\'s ' + HydrusNumbers.ToHumanInt( len( urls ) ) + ' urls'
ClientGUIMenus.AppendMenuItem( urls_visit_menu, label, 'Open these urls in your web browser.', ClientGUIMediaModalActions.OpenURLs, win, urls )
@@ -915,7 +916,7 @@ def AddShareMenu( win: QW.QWidget, menu: QW.QMenu, focused_media: typing.Optiona
application_command = CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_COPY_FILE_SERVICE_FILENAMES, simple_data = hacky_ipfs_dict )
- ClientGUIMenus.AppendMenuItem( share_menu, f'copy {name} multihashes ({HydrusData.ToHumanInt(ipfs_service_keys_to_num_filenames[ipfs_service_key])} hashes)', 'Copy the selected files\' multihashes to the clipboard.', win.ProcessApplicationCommand, application_command )
+ ClientGUIMenus.AppendMenuItem( share_menu, f'copy {name} multihashes ({HydrusNumbers.ToHumanInt(ipfs_service_keys_to_num_filenames[ipfs_service_key])} hashes)', 'Copy the selected files\' multihashes to the clipboard.', win.ProcessApplicationCommand, application_command )
@@ -933,7 +934,7 @@ def AddShareMenu( win: QW.QWidget, menu: QW.QMenu, focused_media: typing.Optiona
if len( blurhashes ) > 0:
- ClientGUIMenus.AppendMenuItem( copy_hash_menu, f'blurhash ({HydrusData.ToHumanInt(len(blurhashes))} hashes)', 'Copy these files\' blurhashes.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_COPY_FILE_HASHES, simple_data = ( CAC.FILE_COMMAND_TARGET_SELECTED_FILES, 'blurhash' ) ) )
+ ClientGUIMenus.AppendMenuItem( copy_hash_menu, f'blurhash ({HydrusNumbers.ToHumanInt(len(blurhashes))} hashes)', 'Copy these files\' blurhashes.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_COPY_FILE_HASHES, simple_data = ( CAC.FILE_COMMAND_TARGET_SELECTED_FILES, 'blurhash' ) ) )
pixel_hashes = [ media.GetFileInfoManager().pixel_hash for media in selected_media ]
@@ -941,7 +942,7 @@ def AddShareMenu( win: QW.QWidget, menu: QW.QMenu, focused_media: typing.Optiona
if len( pixel_hashes ):
- ClientGUIMenus.AppendMenuItem( copy_hash_menu, f'pixel hashes ({HydrusData.ToHumanInt(len(pixel_hashes))} hashes)', 'Copy these files\' pixel hashes.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_COPY_FILE_HASHES, simple_data = ( CAC.FILE_COMMAND_TARGET_SELECTED_FILES, 'pixel_hash' ) ) )
+ ClientGUIMenus.AppendMenuItem( copy_hash_menu, f'pixel hashes ({HydrusNumbers.ToHumanInt(len(pixel_hashes))} hashes)', 'Copy these files\' pixel hashes.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_COPY_FILE_HASHES, simple_data = ( CAC.FILE_COMMAND_TARGET_SELECTED_FILES, 'pixel_hash' ) ) )
ClientGUIMenus.AppendMenu( share_menu, copy_hash_menu, 'copy hashes' )
@@ -1012,7 +1013,7 @@ def AddShareMenu( win: QW.QWidget, menu: QW.QMenu, focused_media: typing.Optiona
if focused_media is not None:
- hash_id_str = HydrusData.ToHumanInt( focused_media.GetHashId() )
+ hash_id_str = HydrusNumbers.ToHumanInt( focused_media.GetHashId() )
ClientGUIMenus.AppendMenuItem( share_menu, 'copy file id ({})'.format( hash_id_str ), 'Copy this file\'s internal file/hash_id.', win.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_COPY_FILE_ID, simple_data = CAC.FILE_COMMAND_TARGET_FOCUSED_FILE ) )
diff --git a/hydrus/client/gui/media/ClientGUIMediaModalActions.py b/hydrus/client/gui/media/ClientGUIMediaModalActions.py
index 566c5445e..42e1b5cb1 100644
--- a/hydrus/client/gui/media/ClientGUIMediaModalActions.py
+++ b/hydrus/client/gui/media/ClientGUIMediaModalActions.py
@@ -9,6 +9,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusThreading
from hydrus.core import HydrusTime
from hydrus.core.files.images import HydrusImageMetadata
@@ -137,7 +138,7 @@ def ClearDeleteRecord( win, media ):
return
- result = ClientGUIDialogsQuick.GetYesNo( win, 'Clear the deletion record for {} previously deleted files?.'.format( HydrusData.ToHumanInt( len( clearable_media ) ) ) )
+ result = ClientGUIDialogsQuick.GetYesNo( win, 'Clear the deletion record for {} previously deleted files?.'.format( HydrusNumbers.ToHumanInt( len( clearable_media ) ) ) )
if result == QW.QDialog.Accepted:
@@ -208,12 +209,12 @@ def CopyHashesToClipboard( win: QW.QWidget, hash_type: str, medias: typing.Seque
else:
- message = 'Unfortunately, {} of the {} hashes could not be found.'.format( HydrusData.ToHumanInt( num_missing ), hash_type )
+ message = 'Unfortunately, {} of the {} hashes could not be found.'.format( HydrusNumbers.ToHumanInt( num_missing ), hash_type )
if num_remote_medias > 0:
- message += ' {} of the files you wanted are not currently in this client. If they have never visited this client, the lookup is impossible.'.format( HydrusData.ToHumanInt( num_remote_medias ) )
+ message += ' {} of the files you wanted are not currently in this client. If they have never visited this client, the lookup is impossible.'.format( HydrusNumbers.ToHumanInt( num_remote_medias ) )
if num_remote_medias < num_hashes:
@@ -247,7 +248,7 @@ def CopyHashesToClipboard( win: QW.QWidget, hash_type: str, medias: typing.Seque
job_status = ClientThreading.JobStatus()
- job_status.SetStatusText( '{} {} hashes copied'.format( HydrusData.ToHumanInt( len( desired_hashes ) ), hash_type ) )
+ job_status.SetStatusText( '{} {} hashes copied'.format( HydrusNumbers.ToHumanInt( len( desired_hashes ) ), hash_type ) )
CG.client_controller.pub( 'message', job_status )
@@ -268,7 +269,7 @@ def DoClearFileViewingStats( win: QW.QWidget, flat_medias: typing.Collection[ Cl
else:
- insert = 'these {} files'.format( HydrusData.ToHumanInt( len( flat_medias ) ) )
+ insert = 'these {} files'.format( HydrusNumbers.ToHumanInt( len( flat_medias ) ) )
message = 'Clear the file viewing count/duration and \'last viewed time\' for {}?'.format( insert )
@@ -743,7 +744,7 @@ def MoveOrDuplicateLocalFiles( win: QW.QWidget, dest_service_key: bytes, action:
( local_duplicable_to_file_service_keys, local_moveable_from_and_to_file_service_keys ) = ClientGUIMediaSimpleActions.GetLocalFileActionServiceKeys( media )
do_yes_no = do_yes_no = CG.client_controller.new_options.GetBoolean( 'confirm_multiple_local_file_services_copy' )
- yes_no_text = 'Add {} files to {}?'.format( HydrusData.ToHumanInt( len( applicable_media ) ), dest_service_name )
+ yes_no_text = 'Add {} files to {}?'.format( HydrusNumbers.ToHumanInt( len( applicable_media ) ), dest_service_name )
if action == HC.CONTENT_UPDATE_MOVE:
@@ -812,7 +813,7 @@ def MoveOrDuplicateLocalFiles( win: QW.QWidget, dest_service_key: bytes, action:
applicable_media = potential_source_service_keys_to_applicable_media[ source_service_key ]
- yes_no_text = 'Move {} files from {} to {}?'.format( HydrusData.ToHumanInt( len( applicable_media ) ), source_service_name, dest_service_name )
+ yes_no_text = 'Move {} files from {} to {}?'.format( HydrusNumbers.ToHumanInt( len( applicable_media ) ), source_service_name, dest_service_name )
if len( applicable_media ) == 0:
diff --git a/hydrus/client/gui/metadata/ClientGUIEditTimestamps.py b/hydrus/client/gui/metadata/ClientGUIEditTimestamps.py
index 16e9a8c93..d5b09e183 100644
--- a/hydrus/client/gui/metadata/ClientGUIEditTimestamps.py
+++ b/hydrus/client/gui/metadata/ClientGUIEditTimestamps.py
@@ -64,7 +64,7 @@ def __init__( self, parent: QW.QWidget, ordered_medias: typing.List[ ClientMedia
self._domain_modified_list_ctrl_panel.SetListCtrl( self._domain_modified_list_ctrl )
self._domain_modified_list_ctrl_panel.AddButton( 'add', self._AddDomainModifiedTimestamp )
- self._domain_modified_list_ctrl_panel.AddButton( 'edit', self._EditDomainModifiedTimestamp, enabled_only_on_selection = True )
+ self._domain_modified_list_ctrl_panel.AddButton( 'edit', self._EditDomainModifiedTimestamp, enabled_only_on_single_selection = True )
self._domain_modified_list_ctrl_panel.AddDeleteButton()
self._domain_modified_list_ctrl_data_dict = {}
@@ -77,7 +77,7 @@ def __init__( self, parent: QW.QWidget, ordered_medias: typing.List[ ClientMedia
self._file_services_list_ctrl_panel.SetListCtrl( self._file_services_list_ctrl )
- self._file_services_list_ctrl_panel.AddButton( 'edit', self._EditFileServiceTimestamp, enabled_only_on_selection = True )
+ self._file_services_list_ctrl_panel.AddButton( 'edit', self._EditFileServiceTimestamp, enabled_only_on_single_selection = True )
# TODO: An extension here is to add an 'add' button for files that have a _missing_ delete time
# and/or wangle the controls and stuff so a None result is piped along and displays and is settable here
diff --git a/hydrus/client/gui/metadata/ClientGUIMetadataMigration.py b/hydrus/client/gui/metadata/ClientGUIMetadataMigration.py
index f9a3353a9..063727485 100644
--- a/hydrus/client/gui/metadata/ClientGUIMetadataMigration.py
+++ b/hydrus/client/gui/metadata/ClientGUIMetadataMigration.py
@@ -5,6 +5,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusText
from hydrus.client import ClientConstants as CC
@@ -266,7 +267,7 @@ def _RefreshLabel( self ):
else:
- text = '{} sidecar actions'.format( HydrusData.ToHumanInt( len( self._routers ) ) )
+ text = '{} sidecar actions'.format( HydrusNumbers.ToHumanInt( len( self._routers ) ) )
elided_text = HydrusText.ElideText( text, 64 )
diff --git a/hydrus/client/gui/metadata/ClientGUIMigrateTags.py b/hydrus/client/gui/metadata/ClientGUIMigrateTags.py
index 90dceb922..601163d7d 100644
--- a/hydrus/client/gui/metadata/ClientGUIMigrateTags.py
+++ b/hydrus/client/gui/metadata/ClientGUIMigrateTags.py
@@ -5,6 +5,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTags
from hydrus.core import HydrusTagArchive
from hydrus.core import HydrusText
@@ -97,7 +98,7 @@ def __init__( self, parent, service_key, hashes = None ):
if self._hashes is not None:
- self._migration_source_file_filtering_type.addItem( '{} files'.format( HydrusData.ToHumanInt( len( self._hashes ) ) ), self.HASHES_LOCATION )
+ self._migration_source_file_filtering_type.addItem( '{} files'.format( HydrusNumbers.ToHumanInt( len( self._hashes ) ) ), self.HASHES_LOCATION )
self._migration_source_file_filtering_type.SetValue( self.HASHES_LOCATION )
@@ -302,7 +303,7 @@ def _MigrationGo( self ):
location_context = ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_FILE_SERVICE_KEY )
hashes = self._hashes
- extra_info = ' for {} files'.format( HydrusData.ToHumanInt( len( hashes ) ) )
+ extra_info = ' for {} files'.format( HydrusNumbers.ToHumanInt( len( hashes ) ) )
else:
diff --git a/hydrus/client/gui/metadata/ClientGUITime.py b/hydrus/client/gui/metadata/ClientGUITime.py
index be30aa4f6..be2494a71 100644
--- a/hydrus/client/gui/metadata/ClientGUITime.py
+++ b/hydrus/client/gui/metadata/ClientGUITime.py
@@ -8,6 +8,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTime
from hydrus.client import ClientConstants as CC
@@ -539,7 +540,7 @@ def ToString( self ):
elif self._set_count > 1:
- s += f'{HydrusData.ToHumanInt( self._set_count )} files set'
+ s += f'{HydrusNumbers.ToHumanInt( self._set_count )} files set'
if self._min_value == self._max_value:
@@ -559,7 +560,7 @@ def ToString( self ):
else:
- none_prefix = f'{HydrusData.ToHumanInt(self._null_count)} files'
+ none_prefix = f'{HydrusNumbers.ToHumanInt(self._null_count)} files'
if s != '':
@@ -1100,6 +1101,7 @@ def __init__( self, parent, min = 1, days = False, hours = False, minutes = Fals
self._days = ClientGUICommon.BetterSpinBox( self, min=0, max=3653, width = 50 )
self._days.valueChanged.connect( self.EventChange )
+ self._days.installEventFilter( self )
if negative_allowed:
@@ -1114,6 +1116,7 @@ def __init__( self, parent, min = 1, days = False, hours = False, minutes = Fals
self._hours = ClientGUICommon.BetterSpinBox( self, min=0, max=23, width = 45 )
self._hours.valueChanged.connect( self.EventChange )
+ self._hours.installEventFilter( self )
if negative_allowed:
@@ -1128,6 +1131,7 @@ def __init__( self, parent, min = 1, days = False, hours = False, minutes = Fals
self._minutes = ClientGUICommon.BetterSpinBox( self, min=0, max=59, width = 45 )
self._minutes.valueChanged.connect( self.EventChange )
+ self._minutes.installEventFilter( self )
if negative_allowed:
@@ -1142,6 +1146,7 @@ def __init__( self, parent, min = 1, days = False, hours = False, minutes = Fals
self._seconds = ClientGUICommon.BetterSpinBox( self, min=0, max=59, width = 45 )
self._seconds.valueChanged.connect( self.EventChange )
+ self._seconds.installEventFilter( self )
if negative_allowed:
@@ -1156,6 +1161,7 @@ def __init__( self, parent, min = 1, days = False, hours = False, minutes = Fals
self._milliseconds = ClientGUICommon.BetterSpinBox( self, min=0, max=999, width = 65 )
self._milliseconds.valueChanged.connect( self.EventChange )
+ self._milliseconds.installEventFilter( self )
if negative_allowed:
@@ -1175,6 +1181,12 @@ def __init__( self, parent, min = 1, days = False, hours = False, minutes = Fals
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,monthly_label), CC.FLAGS_CENTER_PERPENDICULAR )
+ self.blockSignals( True )
+
+ self.SetValue( self._min )
+
+ self.blockSignals( False )
+
self.setLayout( hbox )
@@ -1208,15 +1220,37 @@ def _UpdateEnables( self ):
- def EventChange( self ):
-
- value = self.GetValue()
+ def eventFilter( self, watched, event ):
- if value is not None and value < self._min:
+ try:
+
+ if event.type() == QC.QEvent.FocusOut:
+
+ if not ClientGUIFunctions.WidgetOrAnyTLWChildHasFocus( self ):
+
+ value = self.GetValue()
+
+ if value is not None and value < self._min:
+
+ self.SetValue( self._min )
+
+
+
+
+ except Exception as e:
+
+ HydrusData.ShowException( e )
- self.SetValue( self._min )
+ return True
+ return False
+
+
+ def EventChange( self ):
+
+ value = self.GetValue()
+
self._UpdateEnables()
self.timeDeltaChanged.emit()
diff --git a/hydrus/client/gui/networking/ClientGUIHydrusNetwork.py b/hydrus/client/gui/networking/ClientGUIHydrusNetwork.py
index f5da0f969..bfb0c58e9 100644
--- a/hydrus/client/gui/networking/ClientGUIHydrusNetwork.py
+++ b/hydrus/client/gui/networking/ClientGUIHydrusNetwork.py
@@ -6,6 +6,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTime
from hydrus.core.networking import HydrusNetwork
from hydrus.core.networking import HydrusNetworking
@@ -171,7 +172,7 @@ def _UpdateAutoCreationHistoryText( self ):
num_created = self._auto_create_history.GetUsage( HC.BANDWIDTH_TYPE_DATA, time_delta )
- text += '{} auto-created in the past {}.'.format( HydrusData.ToHumanInt( num_created ), HydrusTime.TimeDeltaToPrettyTimeDelta( time_delta ) )
+ text += '{} auto-created in the past {}.'.format( HydrusNumbers.ToHumanInt( num_created ), HydrusTime.TimeDeltaToPrettyTimeDelta( time_delta ) )
self._auto_create_history_st.setText( text )
@@ -1038,7 +1039,7 @@ def _AddToExpires( self ):
if num_unchecked > 0:
- ClientGUIDialogsMessage.ShowInformation( self, '{} accounts do not expire, so could not have time added!'.format( HydrusData.ToHumanInt( num_unchecked ) ) )
+ ClientGUIDialogsMessage.ShowInformation( self, '{} accounts do not expire, so could not have time added!'.format( HydrusNumbers.ToHumanInt( num_unchecked ) ) )
subject_accounts = self._account_panel.GetCheckedAccounts()
@@ -1050,7 +1051,7 @@ def _AddToExpires( self ):
return
- message = 'Add {} to expiry for {} accounts?'.format( HydrusTime.TimeDeltaToPrettyTimeDelta( expires_delta ), HydrusData.ToHumanInt( len( subject_accounts ) ) )
+ message = 'Add {} to expiry for {} accounts?'.format( HydrusTime.TimeDeltaToPrettyTimeDelta( expires_delta ), HydrusNumbers.ToHumanInt( len( subject_accounts ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -1097,7 +1098,7 @@ def _DoAccountType( self ):
account_type = self._account_types_choice.GetValue()
- message = 'Set {} accounts to "{}" type?'.format( HydrusData.ToHumanInt( len( subject_account_keys ) ), account_type.GetTitle() )
+ message = 'Set {} accounts to "{}" type?'.format( HydrusNumbers.ToHumanInt( len( subject_account_keys ) ), account_type.GetTitle() )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -1170,7 +1171,7 @@ def _DoBan( self ):
return
- message = 'Ban {} account(s)? All of their pending petitions will be deleted serverside.'.format( HydrusData.ToHumanInt( len( subject_account_keys ) ) )
+ message = 'Ban {} account(s)? All of their pending petitions will be deleted serverside.'.format( HydrusNumbers.ToHumanInt( len( subject_account_keys ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -1227,7 +1228,7 @@ def _DoDeleteAllAccountContent( self ):
subject_account_keys = [ subject_account.GetAccountKey() for subject_account in subject_accounts ]
- message = 'Are you absolutely sure you want to delete all uploads for {} accounts? This will delete everything the user(s) have uploaded since the anonymisation date.'.format( HydrusData.ToHumanInt( len( subject_account_keys ) ) )
+ message = 'Are you absolutely sure you want to delete all uploads for {} accounts? This will delete everything the user(s) have uploaded since the anonymisation date.'.format( HydrusNumbers.ToHumanInt( len( subject_account_keys ) ) )
if self._service.GetServiceType() == HC.TAG_REPOSITORY:
@@ -1341,11 +1342,11 @@ def _DoSetMessage( self ):
if message == '':
- yn_message = 'Clear message for {} accounts?'.format( HydrusData.ToHumanInt( len( subject_accounts ) ) )
+ yn_message = 'Clear message for {} accounts?'.format( HydrusNumbers.ToHumanInt( len( subject_accounts ) ) )
else:
- yn_message = 'Set this message for {} accounts?'.format( HydrusData.ToHumanInt( len( subject_accounts ) ) )
+ yn_message = 'Set this message for {} accounts?'.format( HydrusNumbers.ToHumanInt( len( subject_accounts ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, yn_message )
@@ -1407,7 +1408,7 @@ def _DoUnban( self ):
subject_account_keys = [ subject_account.GetAccountKey() for subject_account in subject_accounts ]
- message = 'Unban {} accounts?'.format( HydrusData.ToHumanInt( len( subject_account_keys ) ) )
+ message = 'Unban {} accounts?'.format( HydrusNumbers.ToHumanInt( len( subject_account_keys ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -1512,7 +1513,7 @@ def _SetExpires( self ):
return
- message = 'Set expiry to {} for {} accounts?'.format( HydrusTime.TimestampToPrettyExpires( expires ), HydrusData.ToHumanInt( len( subject_account_keys_and_new_expires ) ) )
+ message = 'Set expiry to {} for {} accounts?'.format( HydrusTime.TimestampToPrettyExpires( expires ), HydrusNumbers.ToHumanInt( len( subject_account_keys_and_new_expires ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
diff --git a/hydrus/client/gui/networking/ClientGUILogin.py b/hydrus/client/gui/networking/ClientGUILogin.py
index 1986aeb1c..77d04c0e3 100644
--- a/hydrus/client/gui/networking/ClientGUILogin.py
+++ b/hydrus/client/gui/networking/ClientGUILogin.py
@@ -1306,7 +1306,7 @@ def __init__( self, parent, login_script ):
credential_definitions_panel.SetListCtrl( self._credential_definitions )
credential_definitions_panel.AddButton( 'add', self._AddCredentialDefinition )
- credential_definitions_panel.AddButton( 'edit', self._EditCredentialDefinitions, enabled_only_on_selection = True )
+ credential_definitions_panel.AddButton( 'edit', self._EditCredentialDefinitions, enabled_only_on_single_selection = True )
credential_definitions_panel.AddDeleteButton()
#
@@ -1332,7 +1332,7 @@ def __init__( self, parent, login_script ):
example_domains_info_panel.SetListCtrl( self._example_domains_info )
example_domains_info_panel.AddButton( 'add', self._AddExampleDomainsInfo )
- example_domains_info_panel.AddButton( 'edit', self._EditExampleDomainsInfo, enabled_only_on_selection = True )
+ example_domains_info_panel.AddButton( 'edit', self._EditExampleDomainsInfo, enabled_only_on_single_selection = True )
example_domains_info_panel.AddDeleteButton()
#
@@ -1897,7 +1897,7 @@ def __init__( self, parent, login_scripts ):
login_scripts_panel.SetListCtrl( self._login_scripts )
login_scripts_panel.AddButton( 'add', self._Add )
- login_scripts_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ login_scripts_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
login_scripts_panel.AddDeleteButton()
login_scripts_panel.AddSeparator()
login_scripts_panel.AddImportExportButtons( ( ClientNetworkingLogin.LoginScriptDomain, ), self._AddLoginScript )
diff --git a/hydrus/client/gui/networking/ClientGUINetwork.py b/hydrus/client/gui/networking/ClientGUINetwork.py
index f07076781..f41c05d27 100644
--- a/hydrus/client/gui/networking/ClientGUINetwork.py
+++ b/hydrus/client/gui/networking/ClientGUINetwork.py
@@ -8,7 +8,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
from hydrus.core.networking import HydrusNetworking
@@ -21,6 +21,7 @@
from hydrus.client.gui import ClientGUIDialogsMessage
from hydrus.client.gui import ClientGUIDialogsQuick
from hydrus.client.gui import ClientGUIFunctions
+from hydrus.client.gui import ClientGUIMenus
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
@@ -30,8 +31,9 @@
from hydrus.client.gui.widgets import ClientGUIBandwidth
from hydrus.client.gui.widgets import ClientGUICommon
from hydrus.client.networking import ClientNetworking
-from hydrus.client.networking import ClientNetworkingDomain
from hydrus.client.networking import ClientNetworkingContexts
+from hydrus.client.networking import ClientNetworkingDomain
+from hydrus.client.networking import ClientNetworkingJobs
class EditBandwidthRulesPanel( ClientGUIScrolledPanels.EditPanel ):
@@ -319,7 +321,7 @@ def __init__( self, parent: QW.QWidget, network_contexts_to_custom_header_dicts
self._list_ctrl_panel.SetListCtrl( self._list_ctrl )
self._list_ctrl_panel.AddButton( 'add', self._Add )
- self._list_ctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ self._list_ctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
self._list_ctrl_panel.AddDeleteButton()
self._list_ctrl_panel.AddButton( 'duplicate', self._Duplicate, enabled_only_on_selection = True )
@@ -660,7 +662,7 @@ def _ConvertNetworkContextsToListCtrlTuples( self, network_context ):
search_usage = ( search_usage_data, search_usage_requests )
- pretty_search_usage = HydrusData.ToHumanBytes( search_usage_data ) + ' in ' + HydrusData.ToHumanInt( search_usage_requests ) + ' requests'
+ pretty_search_usage = HydrusData.ToHumanBytes( search_usage_data ) + ' in ' + HydrusNumbers.ToHumanInt( search_usage_requests ) + ' requests'
pretty_network_context = network_context.ToString()
@@ -675,8 +677,8 @@ def _ConvertNetworkContextsToListCtrlTuples( self, network_context ):
pretty_current_usage = HydrusData.ToHumanBytes( current_usage ) + '/s'
- pretty_day_usage = HydrusData.ToHumanBytes( day_usage_data ) + ' in ' + HydrusData.ToHumanInt( day_usage_requests ) + ' requests'
- pretty_month_usage = HydrusData.ToHumanBytes( month_usage_data ) + ' in ' + HydrusData.ToHumanInt( month_usage_requests ) + ' requests'
+ pretty_day_usage = HydrusData.ToHumanBytes( day_usage_data ) + ' in ' + HydrusNumbers.ToHumanInt( day_usage_requests ) + ' requests'
+ pretty_month_usage = HydrusData.ToHumanBytes( month_usage_data ) + ' in ' + HydrusNumbers.ToHumanInt( month_usage_requests ) + ' requests'
if network_context == ClientNetworkingContexts.GLOBAL_NETWORK_CONTEXT:
@@ -1006,7 +1008,7 @@ def _Update( self ):
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
- converter = HydrusData.ToHumanInt
+ converter = HydrusNumbers.ToHumanInt
pretty_time_delta_usage = ': ' + converter( time_delta_usage )
@@ -1108,6 +1110,8 @@ def __init__( self, parent, controller ):
self._list_ctrl_panel.SetListCtrl( self._list_ctrl )
+ self._list_ctrl.AddRowsMenuCallable( self._GetListCtrlMenu )
+
# button to stop jobs en-masse
self._list_ctrl_panel.AddButton( 'refresh snapshot', self._RefreshSnapshot )
@@ -1122,6 +1126,10 @@ def __init__( self, parent, controller ):
vbox = QP.VBoxLayout()
+ st = ClientGUICommon.BetterStaticText( self, label = 'right-click a job for more debug info!' )
+ st.setWordWrap( True )
+
+ QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._list_ctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.widget().setLayout( vbox )
@@ -1129,7 +1137,8 @@ def __init__( self, parent, controller ):
def _ConvertDataToListCtrlTuples( self, job_row ):
- ( network_engine_status, job ) = job_row
+ network_engine_status: int = job_row[0]
+ job: ClientNetworkingJobs.NetworkJob = job_row[1]
position = network_engine_status
url = job.GetURL()
@@ -1155,6 +1164,54 @@ def _RefreshSnapshot( self ):
self._list_ctrl.SetData( job_rows )
+ def _GetListCtrlMenu( self ):
+
+ data = self._list_ctrl.GetData( only_selected = True )
+
+ if len( data ) != 1:
+
+ raise HydrusExceptions.DataMissing()
+
+
+ network_job: ClientNetworkingJobs.NetworkJob = data[0][1]
+
+ menu = ClientGUIMenus.GenerateMenu( self )
+
+ submenu = ClientGUIMenus.GenerateMenu( menu )
+
+ network_contexts = network_job.GetNetworkContexts()
+
+ bandwidth_manager = self._controller.network_engine.bandwidth_manager
+
+ for network_context in network_contexts:
+
+ label = network_context.ToString()
+
+ ( waiting_estimate, gumpf ) = bandwidth_manager.GetWaitingEstimateAndContext( [ network_context ] )
+
+ if waiting_estimate > 0:
+
+ label = f'{label} ({HydrusTime.TimeDeltaToPrettyTimeDelta( waiting_estimate )})'
+
+ else:
+
+ label = f'{label} (bandwidth ok)'
+
+
+ ClientGUIMenus.AppendMenuLabel( submenu, label )
+
+
+ ClientGUIMenus.AppendMenu( menu, submenu, 'network contexts' )
+
+ ClientGUIMenus.AppendMenuLabel( menu, f'domain ok: {network_job.DomainOK()}' )
+ ClientGUIMenus.AppendMenuLabel( menu, f'waiting on connection error: {network_job.CurrentlyWaitingOnConnectionError()}' )
+ ClientGUIMenus.AppendMenuLabel( menu, f'waiting on serverside bandwidth: {network_job.CurrentlyWaitingOnServersideBandwidth()}' )
+ ClientGUIMenus.AppendMenuLabel( menu, f'obeys bandwidth: {network_job.ObeysBandwidth()}' )
+ ClientGUIMenus.AppendMenuLabel( menu, f'tokens ok: {network_job.TokensOK()}' )
+
+ return menu
+
+
class ReviewNetworkSessionsPanel( ClientGUIScrolledPanels.ReviewPanel ):
def __init__( self, parent, session_manager ):
@@ -1252,7 +1309,7 @@ def _ConvertNetworkContextToListCtrlTuples( self, network_context ):
pretty_network_context = network_context.ToString()
number_of_cookies = len( session.cookies )
- pretty_number_of_cookies = HydrusData.ToHumanInt( number_of_cookies )
+ pretty_number_of_cookies = HydrusNumbers.ToHumanInt( number_of_cookies )
expires_numbers = [ c.expires for c in session.cookies if c.expires is not None ]
@@ -1334,7 +1391,7 @@ def _ImportCookiesTXTPaths( self, paths ):
- ClientGUIDialogsMessage.ShowInformation( self, f'Added {HydrusData.ToHumanInt(num_added)} cookies!' )
+ ClientGUIDialogsMessage.ShowInformation( self, f'Added {HydrusNumbers.ToHumanInt(num_added)} cookies!' )
self._Update()
@@ -1377,6 +1434,7 @@ def _Update( self ):
self._listctrl.SetData( network_contexts )
+
class ReviewNetworkSessionPanel( ClientGUIScrolledPanels.ReviewPanel ):
def __init__( self, parent, session_manager, network_context ):
@@ -1400,7 +1458,7 @@ def __init__( self, parent, session_manager, network_context ):
listctrl_panel.AddButton( 'add', self._Add )
listctrl_panel.AddButton( 'import cookies.txt (drag and drop also works!)', self._ImportCookiesTXT )
- listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
listctrl_panel.AddDeleteButton()
listctrl_panel.AddSeparator()
listctrl_panel.AddButton( 'refresh', self._Update )
@@ -1588,7 +1646,7 @@ def _ImportCookiesTXTPaths( self, paths ):
- ClientGUIDialogsMessage.ShowInformation( self, f'Added {HydrusData.ToHumanInt(num_added)} cookies!' )
+ ClientGUIDialogsMessage.ShowInformation( self, f'Added {HydrusNumbers.ToHumanInt(num_added)} cookies!' )
self._Update()
diff --git a/hydrus/client/gui/pages/ClientGUIManagementPanels.py b/hydrus/client/gui/pages/ClientGUIManagementPanels.py
index 500e3ec84..4e135b4a4 100644
--- a/hydrus/client/gui/pages/ClientGUIManagementPanels.py
+++ b/hydrus/client/gui/pages/ClientGUIManagementPanels.py
@@ -11,6 +11,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTags
from hydrus.core import HydrusTime
from hydrus.core.networking import HydrusNetwork
@@ -949,7 +950,7 @@ def _UpdateMaintenanceStatus( self ):
total_num_files = sum( searched_distances_to_count.values() )
- self._eligible_files.setText( '{} eligible files in the system.'.format(HydrusData.ToHumanInt(total_num_files)) )
+ self._eligible_files.setText( '{} eligible files in the system.'.format(HydrusNumbers.ToHumanInt(total_num_files)) )
self._max_hamming_distance_for_potential_discovery_button.setEnabled( True )
self._max_hamming_distance_for_potential_discovery_spinctrl.setEnabled( True )
@@ -1009,7 +1010,7 @@ def _UpdatePotentialDuplicatesCount( self, potential_duplicates_count ):
self._potential_duplicates_count = potential_duplicates_count
- self._num_potential_duplicates.setText( '{} potential pairs.'.format( HydrusData.ToHumanInt( potential_duplicates_count ) ) )
+ self._num_potential_duplicates.setText( '{} potential pairs.'.format( HydrusNumbers.ToHumanInt( potential_duplicates_count ) ) )
if self._potential_duplicates_count > 0:
@@ -1847,12 +1848,12 @@ def _RemoveGalleryImports( self ):
- message = 'Remove the ' + HydrusData.ToHumanInt( len( removees ) ) + ' selected queries?'
+ message = 'Remove the ' + HydrusNumbers.ToHumanInt( len( removees ) ) + ' selected queries?'
if num_working > 0:
message += '\n' * 2
- message += HydrusData.ToHumanInt( num_working ) + ' are still working.'
+ message += HydrusNumbers.ToHumanInt( num_working ) + ' are still working.'
if self._highlighted_gallery_import is not None and self._highlighted_gallery_import in removees:
@@ -2225,7 +2226,7 @@ def _UpdateImportStatus( self ):
( num_done, num_total ) = file_seed_cache_status.GetValueRange()
- text_top = '{} queries - {}'.format( HydrusData.ToHumanInt( num_gallery_imports ), HydrusData.ConvertValueRangeToPrettyString( num_done, num_total ) )
+ text_top = '{} queries - {}'.format( HydrusNumbers.ToHumanInt( num_gallery_imports ), HydrusData.ConvertValueRangeToPrettyString( num_done, num_total ) )
text_bottom = file_seed_cache_status.GetStatusText()
@@ -2281,7 +2282,7 @@ def CheckAbleToClose( self ):
if num_working > 0:
- raise HydrusExceptions.VetoException( HydrusData.ToHumanInt( num_working ) + ' queries are still importing.' )
+ raise HydrusExceptions.VetoException( HydrusNumbers.ToHumanInt( num_working ) + ' queries are still importing.' )
@@ -2986,18 +2987,18 @@ def _RemoveWatchers( self ):
- message = 'Remove the ' + HydrusData.ToHumanInt( len( removees ) ) + ' selected watchers?'
+ message = 'Remove the ' + HydrusNumbers.ToHumanInt( len( removees ) ) + ' selected watchers?'
if num_working > 0:
message += '\n' * 2
- message += HydrusData.ToHumanInt( num_working ) + ' are still working.'
+ message += HydrusNumbers.ToHumanInt( num_working ) + ' are still working.'
if num_alive > 0:
message += '\n' * 2
- message += HydrusData.ToHumanInt( num_alive ) + ' are not yet DEAD.'
+ message += HydrusNumbers.ToHumanInt( num_alive ) + ' are not yet DEAD.'
if self._highlighted_watcher is not None and self._highlighted_watcher in removees:
@@ -3352,14 +3353,14 @@ def _UpdateImportStatus( self ):
else:
- num_dead_text = HydrusData.ToHumanInt( num_dead ) + ' DEAD - '
+ num_dead_text = HydrusNumbers.ToHumanInt( num_dead ) + ' DEAD - '
file_seed_cache_status = self._multiple_watcher_import.GetTotalStatus()
( num_done, num_total ) = file_seed_cache_status.GetValueRange()
- text_top = '{} watchers - {}'.format( HydrusData.ToHumanInt( num_watchers ), HydrusData.ConvertValueRangeToPrettyString( num_done, num_total ) )
+ text_top = '{} watchers - {}'.format( HydrusNumbers.ToHumanInt( num_watchers ), HydrusData.ConvertValueRangeToPrettyString( num_done, num_total ) )
text_bottom = file_seed_cache_status.GetStatusText()
@@ -3415,7 +3416,7 @@ def CheckAbleToClose( self ):
if num_working > 0:
- raise HydrusExceptions.VetoException( HydrusData.ToHumanInt( num_working ) + ' watchers are still importing.' )
+ raise HydrusExceptions.VetoException( HydrusNumbers.ToHumanInt( num_working ) + ' watchers are still importing.' )
@@ -3854,7 +3855,7 @@ def EventDelete( self ):
selected_jobs = self._pending_jobs_listbox.GetData( only_selected = True )
- message = 'Delete {} jobs?'.format( HydrusData.ToHumanInt( len( selected_jobs ) ) )
+ message = 'Delete {} jobs?'.format( HydrusNumbers.ToHumanInt( len( selected_jobs ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -4392,7 +4393,7 @@ def _ApproveSelected( self ):
if len( viable_petitions ) > 0:
- text = 'Approve all the content in these {} petitions?'.format( HydrusData.ToHumanInt( len( viable_petitions ) ) )
+ text = 'Approve all the content in these {} petitions?'.format( HydrusNumbers.ToHumanInt( len( viable_petitions ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, text )
@@ -4564,7 +4565,7 @@ def _DenySelected( self ):
if len( viable_petitions ) > 0:
- text = 'Deny all the content in these {} petitions?'.format( HydrusData.ToHumanInt( len( viable_petitions ) ) )
+ text = 'Deny all the content in these {} petitions?'.format( HydrusNumbers.ToHumanInt( len( viable_petitions ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, text )
@@ -4670,7 +4671,7 @@ def _DrawNumPetitions( self ):
( st, button ) = self._petition_types_to_controls[ petition_type ]
- st.setText( '{} petitions'.format( HydrusData.ToHumanInt( count ) ) )
+ st.setText( '{} petitions'.format( HydrusNumbers.ToHumanInt( count ) ) )
button.setEnabled( count > 0 )
@@ -5361,7 +5362,7 @@ def EventContentsRightClick( self, contents ):
text = '\n'.join( copyable_items )
- ClientGUIMenus.AppendMenuItem( menu, 'copy {} tags'.format( HydrusData.ToHumanInt( len( copyable_items ) ) ), 'Copy this tag.', CG.client_controller.pub, 'clipboard', 'text', text )
+ ClientGUIMenus.AppendMenuItem( menu, 'copy {} tags'.format( HydrusNumbers.ToHumanInt( len( copyable_items ) ) ), 'Copy this tag.', CG.client_controller.pub, 'clipboard', 'text', text )
diff --git a/hydrus/client/gui/pages/ClientGUIPages.py b/hydrus/client/gui/pages/ClientGUIPages.py
index 150181bae..166a5802b 100644
--- a/hydrus/client/gui/pages/ClientGUIPages.py
+++ b/hydrus/client/gui/pages/ClientGUIPages.py
@@ -10,6 +10,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
@@ -741,7 +742,7 @@ def GetNameForMenu( self ) -> str:
if num_files > 0:
- name_for_menu = '{} - {} files'.format( name_for_menu, HydrusData.ToHumanInt( num_files ) )
+ name_for_menu = '{} - {} files'.format( name_for_menu, HydrusNumbers.ToHumanInt( num_files ) )
if num_value != num_range:
@@ -1632,7 +1633,7 @@ def _RefreshPageName( self, index ):
if page_file_count_display == CC.PAGE_FILE_COUNT_DISPLAY_ALL or ( page_file_count_display == CC.PAGE_FILE_COUNT_DISPLAY_ONLY_IMPORTERS and page.IsImporter() ):
- num_string += HydrusData.ToHumanInt( num_files )
+ num_string += HydrusNumbers.ToHumanInt( num_files )
if import_page_progress_display:
@@ -1795,7 +1796,7 @@ def _ShowMenu( self, screen_position ):
if CG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
- label = 'page weight: {}'.format( HydrusData.ToHumanInt( page.GetTotalWeight() ) )
+ label = 'page weight: {}'.format( HydrusNumbers.ToHumanInt( page.GetTotalWeight() ) )
ClientGUIMenus.AppendMenuLabel( menu, label, label )
@@ -2382,7 +2383,7 @@ def GetNameForMenu( self ) -> str:
if num_files > 0:
- name_for_menu = '{} - {} files'.format( name_for_menu, HydrusData.ToHumanInt( num_files ) )
+ name_for_menu = '{} - {} files'.format( name_for_menu, HydrusNumbers.ToHumanInt( num_files ) )
if num_value != num_range:
@@ -2601,14 +2602,14 @@ def GetPrettyStatusForStatusBar( self ):
( num_files, ( num_value, num_range ) ) = self.GetNumFileSummary()
- num_string = HydrusData.ToHumanInt( num_files )
+ num_string = HydrusNumbers.ToHumanInt( num_files )
if num_range > 0 and num_value != num_range:
num_string += ', ' + HydrusData.ConvertValueRangeToPrettyString( num_value, num_range )
- return HydrusData.ToHumanInt( self.count() ) + ' pages, ' + num_string + ' files'
+ return HydrusNumbers.ToHumanInt( self.count() ) + ' pages, ' + num_string + ' files'
def GetSerialisablePage( self, only_changed_page_data, about_to_save ):
@@ -2691,7 +2692,7 @@ def GetTestAbleToCloseStatement( self ):
else:
- message = HydrusData.ToHumanInt( c ) + ' pages say:' + reason
+ message = HydrusNumbers.ToHumanInt( c ) + ' pages say:' + reason
message += '\n'
diff --git a/hydrus/client/gui/pages/ClientGUIResults.py b/hydrus/client/gui/pages/ClientGUIResults.py
index e4e32fbcf..bd22fd77e 100644
--- a/hydrus/client/gui/pages/ClientGUIResults.py
+++ b/hydrus/client/gui/pages/ClientGUIResults.py
@@ -13,9 +13,9 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusTime
-from hydrus.core.files.images import HydrusImageHandling
from hydrus.core.networking import HydrusNetwork
from hydrus.client import ClientApplicationCommand as CAC
@@ -225,7 +225,7 @@ def _Archive( self ):
if len( hashes ) > 1:
- message = 'Archive ' + HydrusData.ToHumanInt( len( hashes ) ) + ' files?'
+ message = 'Archive ' + HydrusNumbers.ToHumanInt( len( hashes ) ) + ' files?'
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -463,7 +463,7 @@ def _GetPrettyStatusForStatusBar( self ) -> str:
# if 1 selected, we show the whole mime string, so no need to specify
if num_selected == 1 or selected_files_string == num_files_string:
- selected_files_string = HydrusData.ToHumanInt( num_selected )
+ selected_files_string = HydrusNumbers.ToHumanInt( num_selected )
if num_selected == 1: # 23 files - 1 video selected, file_info
@@ -488,7 +488,7 @@ def _GetPrettyStatusForStatusBar( self ) -> str:
else:
- inbox_phrase = '{} in inbox and {} archived'.format( HydrusData.ToHumanInt( num_inbox ), HydrusData.ToHumanInt( num_selected - num_inbox ) )
+ inbox_phrase = '{} in inbox and {} archived'.format( HydrusNumbers.ToHumanInt( num_inbox ), HydrusNumbers.ToHumanInt( num_selected - num_inbox ) )
pretty_total_size = self._GetPrettyTotalSize( only_selected = True )
@@ -636,7 +636,7 @@ def GetDescriptor( plural, classes, num_collections ):
collections_suffix = 's' if num_collections > 1 else ''
- return 'file{} in {} collection{}'.format( suffix, HydrusData.ToHumanInt( num_collections ), collections_suffix )
+ return 'file{} in {} collection{}'.format( suffix, HydrusNumbers.ToHumanInt( num_collections ), collections_suffix )
else:
@@ -856,7 +856,7 @@ def _Inbox( self ):
if len( hashes ) > 1:
- message = 'Send {} files to inbox?'.format( HydrusData.ToHumanInt( len( hashes ) ) )
+ message = 'Send {} files to inbox?'.format( HydrusNumbers.ToHumanInt( len( hashes ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -982,7 +982,7 @@ def _ManageTags( self ):
num_files = self._GetNumSelected()
- title = 'manage tags for ' + HydrusData.ToHumanInt( num_files ) + ' files'
+ title = 'manage tags for ' + HydrusNumbers.ToHumanInt( num_files ) + ' files'
frame_key = 'manage_tags_dialog'
with ClientGUITopLevelWindowsPanels.DialogManage( self, title, frame_key ) as dlg:
@@ -1145,7 +1145,7 @@ def _PetitionFiles( self, remote_service_key ):
else:
- message = 'Enter a reason for these {} files to be removed from {}.'.format( HydrusData.ToHumanInt( len( hashes ) ), remote_service.GetName() )
+ message = 'Enter a reason for these {} files to be removed from {}.'.format( HydrusNumbers.ToHumanInt( len( hashes ) ), remote_service.GetName() )
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
@@ -1253,17 +1253,17 @@ def _RegenerateFileData( self, job_type ):
if job_type == ClientFiles.REGENERATE_FILE_DATA_JOB_FILE_METADATA:
- message = 'This will reparse the {} selected files\' metadata.'.format( HydrusData.ToHumanInt( num_files ) )
+ message = 'This will reparse the {} selected files\' metadata.'.format( HydrusNumbers.ToHumanInt( num_files ) )
message += '\n' * 2
message += 'If the files were imported before some more recent improvement in the parsing code (such as EXIF rotation or bad video resolution or duration or frame count calculation), this will update them.'
elif job_type == ClientFiles.REGENERATE_FILE_DATA_JOB_FORCE_THUMBNAIL:
- message = 'This will force-regenerate the {} selected files\' thumbnails.'.format( HydrusData.ToHumanInt( num_files ) )
+ message = 'This will force-regenerate the {} selected files\' thumbnails.'.format( HydrusNumbers.ToHumanInt( num_files ) )
elif job_type == ClientFiles.REGENERATE_FILE_DATA_JOB_REFIT_THUMBNAIL:
- message = 'This will regenerate the {} selected files\' thumbnails, but only if they are the wrong size.'.format( HydrusData.ToHumanInt( num_files ) )
+ message = 'This will regenerate the {} selected files\' thumbnails, but only if they are the wrong size.'.format( HydrusNumbers.ToHumanInt( num_files ) )
else:
@@ -1275,7 +1275,7 @@ def _RegenerateFileData( self, job_type ):
if num_files > 50:
message += '\n' * 2
- message += 'You have selected {} files, so this job may take some time. You can run it all now or schedule it to the overall file maintenance queue for later spread-out processing.'.format( HydrusData.ToHumanInt( num_files ) )
+ message += 'You have selected {} files, so this job may take some time. You can run it all now or schedule it to the overall file maintenance queue for later spread-out processing.'.format( HydrusNumbers.ToHumanInt( num_files ) )
yes_tuples = []
@@ -1457,7 +1457,7 @@ def _SetDuplicates( self, duplicate_type, media_pairs = None, media_group = None
flat_media = ClientMedia.FlattenMedia( media_group )
- num_files_str = HydrusData.ToHumanInt( len( flat_media ) )
+ num_files_str = HydrusNumbers.ToHumanInt( len( flat_media ) )
if len( flat_media ) < 2:
@@ -1477,7 +1477,7 @@ def _SetDuplicates( self, duplicate_type, media_pairs = None, media_group = None
else:
- num_files_str = HydrusData.ToHumanInt( len( self._GetSelectedFlatMedia() ) )
+ num_files_str = HydrusNumbers.ToHumanInt( len( self._GetSelectedFlatMedia() ) )
if len( media_pairs ) == 0:
@@ -1492,7 +1492,7 @@ def _SetDuplicates( self, duplicate_type, media_pairs = None, media_group = None
if len( media_pairs ) > 1 and duplicate_type in ( HC.DUPLICATE_FALSE_POSITIVE, HC.DUPLICATE_ALTERNATE ):
- media_pairs_str = HydrusData.ToHumanInt( len( media_pairs ) )
+ media_pairs_str = HydrusNumbers.ToHumanInt( len( media_pairs ) )
message = 'Are you sure you want to {} for the {} selected files? The relationship will be applied between every pair combination in the file selection ({} pairs).'.format( yes_no_text, num_files_str, media_pairs_str )
@@ -1691,7 +1691,7 @@ def _SetDuplicatesFocusedBetter( self, duplicate_content_merge_options = None ):
media_pairs = [ ( better_media, worse_media ) for worse_media in worse_flat_media ]
- message = 'Are you sure you want to set the focused file as better than the {} other files in the selection?'.format( HydrusData.ToHumanInt( len( worse_flat_media ) ) )
+ message = 'Are you sure you want to set the focused file as better than the {} other files in the selection?'.format( HydrusNumbers.ToHumanInt( len( worse_flat_media ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message )
@@ -2572,11 +2572,11 @@ def _GetPrettyStatusForStatusBar( self ):
if self._current is not None:
- s += ' ' + HydrusData.ToHumanInt( self._current )
+ s += ' ' + HydrusNumbers.ToHumanInt( self._current )
if self._max is not None:
- s += ' of ' + HydrusData.ToHumanInt( self._max )
+ s += ' of ' + HydrusNumbers.ToHumanInt( self._max )
@@ -4133,7 +4133,7 @@ def ShowMenu( self, do_not_show_just_return = False ):
if num_notes > 0:
- notes_str = '{} ({})'.format( notes_str, HydrusData.ToHumanInt( num_notes ) )
+ notes_str = '{} ({})'.format( notes_str, HydrusNumbers.ToHumanInt( num_notes ) )
ClientGUIMenus.AppendMenuItem( manage_menu, notes_str, 'Manage notes for the focused file.', self._ManageNotes )
@@ -5106,7 +5106,7 @@ def GetQtImage( self, device_pixel_ratio ) -> QG.QImage:
painter.drawPixmap( icon_x, icon_y, icon )
- num_files_str = HydrusData.ToHumanInt( self.GetNumFiles() )
+ num_files_str = HydrusNumbers.ToHumanInt( self.GetNumFiles() )
( text_size, num_files_str ) = ClientGUIFunctions.GetTextSizeFromPainter( painter, num_files_str )
diff --git a/hydrus/client/gui/panels/ClientGUIScrolledPanels.py b/hydrus/client/gui/panels/ClientGUIScrolledPanels.py
index a9e13d8c4..50305a4a9 100644
--- a/hydrus/client/gui/panels/ClientGUIScrolledPanels.py
+++ b/hydrus/client/gui/panels/ClientGUIScrolledPanels.py
@@ -52,6 +52,7 @@ def eventFilter( self, watched, event ):
return False
+
class ResizingScrolledPanel( QW.QScrollArea ):
diff --git a/hydrus/client/gui/panels/ClientGUIScrolledPanelsEdit.py b/hydrus/client/gui/panels/ClientGUIScrolledPanelsEdit.py
index a1c2b6812..48d34ad1b 100644
--- a/hydrus/client/gui/panels/ClientGUIScrolledPanelsEdit.py
+++ b/hydrus/client/gui/panels/ClientGUIScrolledPanelsEdit.py
@@ -10,6 +10,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
@@ -94,7 +95,7 @@ def __init__(
self._list_ctrl_panel.AddButton( 'copy tags', self._CopyTags, enabled_check_func = self._OnlyOneTIOSelected )
self._list_ctrl_panel.AddButton( 'copy notes', self._CopyNotes, enabled_check_func = self._OnlyOneNIOSelected )
self._list_ctrl_panel.AddButton( 'paste', self._Paste, enabled_only_on_selection = True )
- self._list_ctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ self._list_ctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
self._list_ctrl_panel.AddButton( 'clear tags', self._ClearTags, enabled_check_func = self._AtLeastOneTIOSelected )
self._list_ctrl_panel.AddButton( 'clear notes', self._ClearNotes, enabled_check_func = self._AtLeastOneNIOSelected )
@@ -823,7 +824,7 @@ def _InitialisePermittedActionChoices( self ):
else:
- file_desc = '{} files'.format( HydrusData.ToHumanInt( num_to_delete ) )
+ file_desc = '{} files'.format( HydrusNumbers.ToHumanInt( num_to_delete ) )
if self._num_actionable_local_file_service_domains == 1:
@@ -883,7 +884,7 @@ def _InitialisePermittedActionChoices( self ):
else:
- file_desc = '{} files'.format( HydrusData.ToHumanInt( num_to_delete ) )
+ file_desc = '{} files'.format( HydrusNumbers.ToHumanInt( num_to_delete ) )
if deletee_service.HasPermission( HC.CONTENT_TYPE_FILES, HC.PERMISSION_ACTION_MODERATE ):
@@ -931,7 +932,7 @@ def _InitialisePermittedActionChoices( self ):
else:
- suffix = '{} {}files'.format( HydrusData.ToHumanInt( num_to_delete ), suffix )
+ suffix = '{} {}files'.format( HydrusNumbers.ToHumanInt( num_to_delete ), suffix )
text = 'Permanently delete {}?'.format( suffix )
@@ -972,7 +973,7 @@ def _InitialisePermittedActionChoices( self ):
else:
- text = 'Permanently delete these ' + HydrusData.ToHumanInt( num_to_delete ) + ' files and do not save a deletion record?'
+ text = 'Permanently delete these ' + HydrusNumbers.ToHumanInt( num_to_delete ) + ' files and do not save a deletion record?'
chunks_of_hashes = list( HydrusLists.SplitListIntoChunks( hashes, 64 ) ) # iterator, so list it to use it more than once, jej
@@ -1187,7 +1188,7 @@ def __init__( self, parent: QW.QWidget, duplicate_action, duplicate_content_merg
tag_services_listctrl_panel.SetListCtrl( self._tag_service_actions )
tag_services_listctrl_panel.AddButton( 'add', self._AddTag )
- tag_services_listctrl_panel.AddButton( 'edit', self._EditTag, enabled_only_on_selection = True )
+ tag_services_listctrl_panel.AddButton( 'edit', self._EditTag, enabled_only_on_single_selection = True )
tag_services_listctrl_panel.AddButton( 'delete', self._DeleteTag, enabled_only_on_selection = True )
#
@@ -1204,7 +1205,7 @@ def __init__( self, parent: QW.QWidget, duplicate_action, duplicate_content_merg
if self._duplicate_action == HC.DUPLICATE_BETTER: # because there is only one valid action otherwise
- rating_services_listctrl_panel.AddButton( 'edit', self._EditRating, enabled_only_on_selection = True )
+ rating_services_listctrl_panel.AddButton( 'edit', self._EditRating, enabled_only_on_single_selection = True )
rating_services_listctrl_panel.AddButton( 'delete', self._DeleteRating, enabled_only_on_selection = True )
@@ -1798,7 +1799,7 @@ def __init__( self, parent: QW.QWidget, original_mimes_count: typing.Dict[ int,
count = original_mimes_count[ mime ]
- original_filetype_statements.append( f'{HydrusData.ToHumanInt(count)} {HC.mime_string_lookup[ mime ]}')
+ original_filetype_statements.append( f'{HydrusNumbers.ToHumanInt(count)} {HC.mime_string_lookup[ mime ]}')
@@ -1818,7 +1819,7 @@ def __init__( self, parent: QW.QWidget, original_mimes_count: typing.Dict[ int,
count = forced_mimes_count[ mime ]
- forced_filetype_statements.append( f'{HydrusData.ToHumanInt(count)} {HC.mime_string_lookup[ mime ]}')
+ forced_filetype_statements.append( f'{HydrusNumbers.ToHumanInt(count)} {HC.mime_string_lookup[ mime ]}')
@@ -1830,7 +1831,7 @@ def __init__( self, parent: QW.QWidget, original_mimes_count: typing.Dict[ int,
else:
- forced_filetype_summary = f'{HydrusData.ToHumanInt(total_forced_mimes_count)} are currently being forced, to: {forced_filetype_summary}.'
+ forced_filetype_summary = f'{HydrusNumbers.ToHumanInt(total_forced_mimes_count)} are currently being forced, to: {forced_filetype_summary}.'
@@ -1840,7 +1841,7 @@ def __init__( self, parent: QW.QWidget, original_mimes_count: typing.Dict[ int,
text += '\n\n'
text += 'This will override what hydrus thinks the filetype is for all of these files. Files will be renamed to receive their new file extensions. The original filetype is not forgotten, and this can be undone.'
text += '\n\n'
- text += f'Of the {HydrusData.ToHumanInt( total_file_count )} files, there are {original_filetype_summary}. {forced_filetype_summary}'
+ text += f'Of the {HydrusNumbers.ToHumanInt( total_file_count )} files, there are {original_filetype_summary}. {forced_filetype_summary}'
st = ClientGUICommon.BetterStaticText( self, text )
st.setWordWrap( True )
@@ -2625,7 +2626,7 @@ def __init__( self, parent: QW.QWidget, regex_favourites ):
regex_listctrl_panel.SetListCtrl( self._regexes )
regex_listctrl_panel.AddButton( 'add', self._Add )
- regex_listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ regex_listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
regex_listctrl_panel.AddDeleteButton()
#
diff --git a/hydrus/client/gui/panels/ClientGUIScrolledPanelsManagement.py b/hydrus/client/gui/panels/ClientGUIScrolledPanelsManagement.py
index d3925f692..7a22b5910 100644
--- a/hydrus/client/gui/panels/ClientGUIScrolledPanelsManagement.py
+++ b/hydrus/client/gui/panels/ClientGUIScrolledPanelsManagement.py
@@ -11,11 +11,10 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
-from hydrus.core import HydrusText
from hydrus.core.files.images import HydrusImageHandling
from hydrus.client import ClientApplicationCommand as CAC
@@ -2726,7 +2725,7 @@ def __init__( self, parent ):
media_viewer_list_panel.SetListCtrl( self._filetype_handling_listctrl )
media_viewer_list_panel.AddButton( 'add', self.AddMediaViewerOptions, enabled_check_func = self._CanAddMediaViewOption )
- media_viewer_list_panel.AddButton( 'edit', self.EditMediaViewerOptions, enabled_only_on_selection = True )
+ media_viewer_list_panel.AddButton( 'edit', self.EditMediaViewerOptions, enabled_only_on_single_selection = True )
media_viewer_list_panel.AddDeleteButton( enabled_check_func = self._CanDeleteMediaViewOptions )
#
@@ -3824,7 +3823,7 @@ def EventImageCacheUpdate( self ):
image_cache_estimate = cache_size // estimated_bytes_per_fullscreen
- self._estimated_number_fullscreens.setText( '(about {}-{} images the size of your screen)'.format( HydrusData.ToHumanInt( image_cache_estimate // 2 ), HydrusData.ToHumanInt( image_cache_estimate * 2 ) ) )
+ self._estimated_number_fullscreens.setText( '(about {}-{} images the size of your screen)'.format( HydrusNumbers.ToHumanInt( image_cache_estimate // 2 ), HydrusNumbers.ToHumanInt( image_cache_estimate * 2 ) ) )
num_pixels = cache_size * ( self._image_cache_storage_limit_percentage.value() / 100 ) / 3
@@ -3834,7 +3833,7 @@ def EventImageCacheUpdate( self ):
resolution = ( int( 16 * unit_length ), int( 9 * unit_length ) )
- self._image_cache_storage_limit_percentage_st.setText( '% - {} pixels, or about a {} image'.format( HydrusData.ToHumanInt( num_pixels ), HydrusData.ConvertResolutionToPrettyString( resolution ) ) )
+ self._image_cache_storage_limit_percentage_st.setText( '% - {} pixels, or about a {} image'.format( HydrusNumbers.ToHumanInt( num_pixels ), HydrusData.ConvertResolutionToPrettyString( resolution ) ) )
num_pixels = cache_size * ( self._image_cache_prefetch_limit_percentage.value() / 100 ) / 3
@@ -3844,7 +3843,7 @@ def EventImageCacheUpdate( self ):
resolution = ( int( 16 * unit_length ), int( 9 * unit_length ) )
- self._image_cache_prefetch_limit_percentage_st.setText( '% - {} pixels, or about a {} image'.format( HydrusData.ToHumanInt( num_pixels ), HydrusData.ConvertResolutionToPrettyString( resolution ) ) )
+ self._image_cache_prefetch_limit_percentage_st.setText( '% - {} pixels, or about a {} image'.format( HydrusNumbers.ToHumanInt( num_pixels ), HydrusData.ConvertResolutionToPrettyString( resolution ) ) )
#
@@ -3881,7 +3880,7 @@ def EventImageTilesUpdate( self ):
estimate = value // estimated_bytes_per_fullscreen
- self._estimated_number_image_tiles.setText( '(about {} fullscreens)'.format( HydrusData.ToHumanInt( estimate ) ) )
+ self._estimated_number_image_tiles.setText( '(about {} fullscreens)'.format( HydrusNumbers.ToHumanInt( estimate ) ) )
def EventThumbnailsUpdate( self ):
@@ -3896,7 +3895,7 @@ def EventThumbnailsUpdate( self ):
estimated_thumbs = value // estimated_bytes_per_thumb
- self._estimated_number_thumbnails.setText( '(at '+res_string+', about '+HydrusData.ToHumanInt(estimated_thumbs)+' thumbnails)' )
+ self._estimated_number_thumbnails.setText( '(at '+res_string+', about '+HydrusNumbers.ToHumanInt(estimated_thumbs)+' thumbnails)' )
def EventVideoBufferUpdate( self ):
@@ -3905,7 +3904,7 @@ def EventVideoBufferUpdate( self ):
estimated_720p_frames = int( value // ( 1280 * 720 * 3 ) )
- self._estimated_number_video_frames.setText( '(about '+HydrusData.ToHumanInt(estimated_720p_frames)+' frames of 720p video)' )
+ self._estimated_number_video_frames.setText( '(about '+HydrusNumbers.ToHumanInt(estimated_720p_frames)+' frames of 720p video)' )
def UpdateOptions( self ):
@@ -4077,6 +4076,9 @@ def __init__( self, parent, new_options ):
sleep_panel = ClientGUICommon.StaticBox( self, 'system sleep' )
+ self._do_sleep_check = QW.QCheckBox( sleep_panel )
+ self._do_sleep_check.setToolTip( ClientGUIFunctions.WrapToolTip( 'Hydrus detects sleeps via a hacky method where it simply checks the clock every 15 seconds. If too long a time has passed since the last check, it assumes it has just woken up from sleep. This produces false positives in certain UI-hanging situations, so you may, for debugging purposes, wish to turn it off here. When functioning well, it is useful and you should leave it on!' ) )
+
self._wake_delay_period = ClientGUICommon.BetterSpinBox( sleep_panel, min = 0, max = 60 )
tt = 'It sometimes takes a few seconds for your network adapter to reconnect after a wake. This adds a grace period after a detected wake-from-sleep to allow your OS to sort that out before Hydrus starts making requests.'
@@ -4088,6 +4090,8 @@ def __init__( self, parent, new_options ):
#
+ self._do_sleep_check.setChecked( self._new_options.GetBoolean( 'do_sleep_check' ) )
+
self._wake_delay_period.setValue( self._new_options.GetInteger( 'wake_delay_period' ) )
self._file_system_waits_on_wakeup.setChecked( self._new_options.GetBoolean( 'file_system_waits_on_wakeup' ) )
@@ -4096,6 +4100,7 @@ def __init__( self, parent, new_options ):
rows = []
+ rows.append( ( 'Allow wake-from-system-sleep detection:', self._do_sleep_check ) )
rows.append( ( 'After a wake from system sleep, wait this many seconds before allowing new network access:', self._wake_delay_period ) )
rows.append( ( 'Include the file system in this wait: ', self._file_system_waits_on_wakeup ) )
@@ -4115,6 +4120,7 @@ def __init__( self, parent, new_options ):
def UpdateOptions( self ):
+ self._new_options.SetBoolean( 'do_sleep_check', self._do_sleep_check.isChecked() )
self._new_options.SetInteger( 'wake_delay_period', self._wake_delay_period.value() )
self._new_options.SetBoolean( 'file_system_waits_on_wakeup', self._file_system_waits_on_wakeup.isChecked() )
@@ -4521,21 +4527,21 @@ def _AddNamespaceColour( self ):
namespace = namespace.lower().strip()
- if namespace.endswith( ':' ):
+ if namespace in ( '', ':' ):
- namespace = namespace[:-1]
+ ClientGUIDialogsMessage.ShowWarning( self, 'Sorry, that namespace means unnamespaced/default namespaced, which are already listed.' )
+
+ return
- if namespace != 'system':
+ while namespace.endswith( ':' ):
- namespace = HydrusTags.StripTextOfGumpf( namespace )
+ namespace = namespace[:-1]
- if namespace in ( '', ':' ):
-
- ClientGUIDialogsMessage.ShowWarning( self, 'Sorry, that namespace means unnamespaced/default namespaced, which are already listed.' )
+ if namespace != 'system':
- return
+ namespace = HydrusTags.StripTextOfGumpf( namespace )
existing_namespaces = self._namespace_colours.GetNamespaceColours().keys()
@@ -4982,7 +4988,7 @@ def _ConvertTagSliceAndWeightToListCtrlTuples( self, tag_slice_and_weight ):
pretty_tag_slice = HydrusTags.ConvertTagSliceToPrettyString( tag_slice )
sort_tag_slice = pretty_tag_slice
- pretty_weight = HydrusData.ToHumanInt( weight ) + '%'
+ pretty_weight = HydrusNumbers.ToHumanInt( weight ) + '%'
display_tuple = ( pretty_tag_slice, pretty_weight )
sort_tuple = ( sort_tag_slice, weight )
diff --git a/hydrus/client/gui/panels/ClientGUIScrolledPanelsReview.py b/hydrus/client/gui/panels/ClientGUIScrolledPanelsReview.py
index 720255865..25640d64f 100644
--- a/hydrus/client/gui/panels/ClientGUIScrolledPanelsReview.py
+++ b/hydrus/client/gui/panels/ClientGUIScrolledPanelsReview.py
@@ -16,6 +16,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
@@ -1118,7 +1119,7 @@ def _ImportPayloads( self, payloads ):
if num_misc_objects > 0:
- ClientGUIDialogsMessage.ShowWarning( self, 'I found '+HydrusData.ToHumanInt(num_misc_objects)+' misc objects in that png, but nothing downloader related.' )
+ ClientGUIDialogsMessage.ShowWarning( self, 'I found '+HydrusNumbers.ToHumanInt(num_misc_objects)+' misc objects in that png, but nothing downloader related.' )
return
@@ -1292,7 +1293,7 @@ def _ImportPayloads( self, payloads ):
if len( new_gugs ) + len( new_url_classes ) + len( new_parsers ) + len( new_domain_metadatas ) + len( new_login_scripts ) == 0:
- ClientGUIDialogsMessage.ShowInformation( self, f'All {HydrusData.ToHumanInt( total_num_dupes )} downloader objects in that package appeared to already be in the client, so nothing need be added.' )
+ ClientGUIDialogsMessage.ShowInformation( self, f'All {HydrusNumbers.ToHumanInt( total_num_dupes )} downloader objects in that package appeared to already be in the client, so nothing need be added.' )
return
@@ -1341,7 +1342,7 @@ def _ImportPayloads( self, payloads ):
if len( new_domain_metadatas ) > TOO_MANY_DM:
message += '\n' * 2
- message += 'There are more than ' + HydrusData.ToHumanInt( TOO_MANY_DM ) + ' domain metadata objects. So I do not give you dozens of preview windows, I will only show you these first ' + HydrusData.ToHumanInt( TOO_MANY_DM ) + '.'
+ message += 'There are more than ' + HydrusNumbers.ToHumanInt( TOO_MANY_DM ) + ' domain metadata objects. So I do not give you dozens of preview windows, I will only show you these first ' + HydrusNumbers.ToHumanInt( TOO_MANY_DM ) + '.'
new_domain_metadatas_to_show = new_domain_metadatas[:TOO_MANY_DM]
@@ -1365,7 +1366,7 @@ def _ImportPayloads( self, payloads ):
if len( all_to_add ) > 20:
message += '\n'
- message += '(and ' + HydrusData.ToHumanInt( len( all_to_add ) - 20 ) + ' others)'
+ message += '(and ' + HydrusNumbers.ToHumanInt( len( all_to_add ) - 20 ) + ' others)'
message += '\n' * 2
@@ -1399,11 +1400,11 @@ def _ImportPayloads( self, payloads ):
num_new_gugs = len( new_gugs )
num_aux = len( new_url_classes ) + len( new_parsers ) + len( new_login_scripts ) + len( new_domain_metadatas )
- final_message = 'Successfully added ' + HydrusData.ToHumanInt( num_new_gugs ) + ' new downloaders and ' + HydrusData.ToHumanInt( num_aux ) + ' auxiliary objects.'
+ final_message = 'Successfully added ' + HydrusNumbers.ToHumanInt( num_new_gugs ) + ' new downloaders and ' + HydrusNumbers.ToHumanInt( num_aux ) + ' auxiliary objects.'
if total_num_dupes > 0:
- final_message += ' ' + HydrusData.ToHumanInt( total_num_dupes ) + ' duplicate objects were not added (but some additional metadata may have been merged).'
+ final_message += ' ' + HydrusNumbers.ToHumanInt( total_num_dupes ) + ' duplicate objects were not added (but some additional metadata may have been merged).'
ClientGUIDialogsMessage.ShowInformation( self, final_message )
@@ -1962,7 +1963,7 @@ def _AddJob( self ):
if len( hash_ids ) > 1000:
- message = 'Are you sure you want to schedule "{}" on {} files?'.format( ClientFiles.regen_file_enum_to_str_lookup[ job_type ], HydrusData.ToHumanInt( len( hash_ids ) ) )
+ message = 'Are you sure you want to schedule "{}" on {} files?'.format( ClientFiles.regen_file_enum_to_str_lookup[ job_type ], HydrusNumbers.ToHumanInt( len( hash_ids ) ) )
result = ClientGUIDialogsQuick.GetYesNo( self, message, yes_label = 'do it', no_label = 'forget it' )
@@ -2009,13 +2010,13 @@ def _ConvertJobTypeToListCtrlTuples( self, job_type ):
num_to_do = 0
- pretty_num_to_do = HydrusData.ToHumanInt( num_to_do )
+ pretty_num_to_do = HydrusNumbers.ToHumanInt( num_to_do )
not_due_num_to_do = self._job_types_to_not_due_counts[ job_type ]
if not_due_num_to_do > 0:
- pretty_num_to_do = '{} ({} is not yet due)'.format( pretty_num_to_do, HydrusData.ToHumanInt( not_due_num_to_do ) )
+ pretty_num_to_do = '{} ({} is not yet due)'.format( pretty_num_to_do, HydrusNumbers.ToHumanInt( not_due_num_to_do ) )
display_tuple = ( pretty_job_type, pretty_num_to_do )
@@ -2121,7 +2122,7 @@ def work_callable():
def publish_callable( hash_ids ):
- self._run_search_st.setText( '{} files found'.format( HydrusData.ToHumanInt( len( hash_ids ) ) ) )
+ self._run_search_st.setText( '{} files found'.format( HydrusNumbers.ToHumanInt( len( hash_ids ) ) ) )
self._run_search.setEnabled( True )
@@ -2139,7 +2140,7 @@ def _SeeDescription( self ):
message = ClientFiles.regen_file_enum_to_description_lookup[ job_type ]
message += '\n' * 2
- message += 'This job has weight {}, where a normalised unit of file work has value {}.'.format( HydrusData.ToHumanInt( ClientFiles.regen_file_enum_to_job_weight_lookup[ job_type ] ), HydrusData.ToHumanInt( ClientFiles.NORMALISED_BIG_JOB_WEIGHT ) )
+ message += 'This job has weight {}, where a normalised unit of file work has value {}.'.format( HydrusNumbers.ToHumanInt( ClientFiles.regen_file_enum_to_job_weight_lookup[ job_type ] ), HydrusNumbers.ToHumanInt( ClientFiles.NORMALISED_BIG_JOB_WEIGHT ) )
ClientGUIDialogsMessage.ShowInformation( self, message )
@@ -2207,7 +2208,7 @@ def _SetHashIds( self, hash_ids ):
self._hash_ids = hash_ids
- self._selected_files_st.setText( '{} files selected'.format(HydrusData.ToHumanInt(len(hash_ids))) )
+ self._selected_files_st.setText( '{} files selected'.format(HydrusNumbers.ToHumanInt(len(hash_ids))) )
if len( hash_ids ) == 0:
@@ -2445,10 +2446,10 @@ def _SetDuplicatesPanel( self, boned_stats: dict ):
total_duplicate_files = boned_stats[ 'total_duplicate_files' ]
#total_potential_pairs = boned_stats[ 'total_potential_pairs' ]
- #potentials_label = f'Total duplicate potential pairs: {HydrusData.ToHumanInt( total_potential_pairs )}'
+ #potentials_label = f'Total duplicate potential pairs: {HydrusNumbers.ToHumanInt( total_potential_pairs )}'
potentials_label = f'Total duplicate potential pairs: disabled for now'
- duplicates_label = f'Total files set duplicate: {HydrusData.ToHumanInt( total_duplicate_files )}'
- alternates_label = f'Total duplicate file groups set alternate: {HydrusData.ToHumanInt( total_alternate_files )}'
+ duplicates_label = f'Total files set duplicate: {HydrusNumbers.ToHumanInt( total_duplicate_files )}'
+ alternates_label = f'Total duplicate file groups set alternate: {HydrusNumbers.ToHumanInt( total_alternate_files )}'
self._potentials_st.setText( potentials_label )
self._duplicates_st.setText( duplicates_label )
@@ -2551,7 +2552,7 @@ def _SetFilesPanel( self, boned_stats: dict ):
#
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Total Ever Imported:' ), CC.FLAGS_ON_LEFT )
- QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_supertotal ) ), CC.FLAGS_ON_RIGHT )
+ QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusNumbers.ToHumanInt( num_supertotal ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_supertotal ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT )
@@ -2569,7 +2570,7 @@ def _SetFilesPanel( self, boned_stats: dict ):
#
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Current:' ), CC.FLAGS_ON_LEFT )
- QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_total ) ), CC.FLAGS_ON_RIGHT )
+ QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusNumbers.ToHumanInt( num_total ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( current_num_percent ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_total ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( current_size_percent ) ), CC.FLAGS_ON_RIGHT )
@@ -2578,7 +2579,7 @@ def _SetFilesPanel( self, boned_stats: dict ):
#
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Deleted:' ), CC.FLAGS_ON_LEFT )
- QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_deleted ) ), CC.FLAGS_ON_RIGHT )
+ QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusNumbers.ToHumanInt( num_deleted ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( deleted_num_percent ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_deleted ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( deleted_size_percent ) ), CC.FLAGS_ON_RIGHT )
@@ -2596,7 +2597,7 @@ def _SetFilesPanel( self, boned_stats: dict ):
#
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Inbox:' ), CC.FLAGS_ON_LEFT )
- QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_inbox ) ), CC.FLAGS_ON_RIGHT )
+ QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusNumbers.ToHumanInt( num_inbox ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( inbox_num_percent ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_inbox ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( inbox_size_percent ) ), CC.FLAGS_ON_RIGHT )
@@ -2605,7 +2606,7 @@ def _SetFilesPanel( self, boned_stats: dict ):
#
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Archive:' ), CC.FLAGS_ON_LEFT )
- QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_archive ) ), CC.FLAGS_ON_RIGHT )
+ QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusNumbers.ToHumanInt( num_archive ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( archive_num_percent ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_archive ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( archive_size_percent ) ), CC.FLAGS_ON_RIGHT )
@@ -2634,7 +2635,7 @@ def _SetFilesPanel( self, boned_stats: dict ):
#
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Current:' ), CC.FLAGS_ON_LEFT )
- QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_total ) ), CC.FLAGS_ON_RIGHT )
+ QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusNumbers.ToHumanInt( num_total ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_total ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT )
@@ -2652,7 +2653,7 @@ def _SetFilesPanel( self, boned_stats: dict ):
#
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Inbox:' ), CC.FLAGS_ON_LEFT )
- QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_inbox ) ), CC.FLAGS_ON_RIGHT )
+ QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusNumbers.ToHumanInt( num_inbox ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( inbox_num_percent ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_inbox ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( inbox_size_percent ) ), CC.FLAGS_ON_RIGHT )
@@ -2661,7 +2662,7 @@ def _SetFilesPanel( self, boned_stats: dict ):
#
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Archive:' ), CC.FLAGS_ON_LEFT )
- QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_archive ) ), CC.FLAGS_ON_RIGHT )
+ QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusNumbers.ToHumanInt( num_archive ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( archive_num_percent ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_archive ) ), CC.FLAGS_ON_RIGHT )
QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( archive_size_percent ) ), CC.FLAGS_ON_RIGHT )
@@ -2768,9 +2769,9 @@ def _SetViewsPanel( self, boned_stats: dict ):
( media_views, media_viewtime, preview_views, preview_viewtime ) = total_viewtime
- media_label = 'Total media views: ' + HydrusData.ToHumanInt( media_views ) + ', totalling ' + HydrusTime.TimeDeltaToPrettyTimeDelta( media_viewtime )
+ media_label = 'Total media views: ' + HydrusNumbers.ToHumanInt( media_views ) + ', totalling ' + HydrusTime.TimeDeltaToPrettyTimeDelta( media_viewtime )
- preview_label = 'Total preview views: ' + HydrusData.ToHumanInt( preview_views ) + ', totalling ' + HydrusTime.TimeDeltaToPrettyTimeDelta( preview_viewtime )
+ preview_label = 'Total preview views: ' + HydrusNumbers.ToHumanInt( preview_views ) + ', totalling ' + HydrusTime.TimeDeltaToPrettyTimeDelta( preview_viewtime )
self._media_views_st.setText( media_label )
self._preview_views_st.setText( preview_label )
@@ -2947,7 +2948,7 @@ def _ConvertListCtrlDataToTuple( self, path ):
( index, mime, size ) = self._current_path_data[ path ]
- pretty_index = HydrusData.ToHumanInt( index )
+ pretty_index = HydrusNumbers.ToHumanInt( index )
pretty_mime = HC.mime_string_lookup[ mime ]
pretty_size = HydrusData.ToHumanBytes( size )
@@ -3119,7 +3120,7 @@ def THREADParseImportablePaths( self, unparsed_paths_queue, currently_parsing, w
else:
- message = 'none of the ' + HydrusData.ToHumanInt( total_paths ) + ' files parsed successfully'
+ message = 'none of the ' + HydrusNumbers.ToHumanInt( total_paths ) + ' files parsed successfully'
else:
@@ -3142,27 +3143,27 @@ def THREADParseImportablePaths( self, unparsed_paths_queue, currently_parsing, w
if num_sidecars > 0:
- bad_comments.append( '{} looked like txt/json/xml sidecars'.format( HydrusData.ToHumanInt( num_sidecars ) ) )
+ bad_comments.append( '{} looked like txt/json/xml sidecars'.format( HydrusNumbers.ToHumanInt( num_sidecars ) ) )
if num_empty_files > 0:
- bad_comments.append( HydrusData.ToHumanInt( num_empty_files ) + ' were empty' )
+ bad_comments.append( HydrusNumbers.ToHumanInt( num_empty_files ) + ' were empty' )
if num_missing_files > 0:
- bad_comments.append( HydrusData.ToHumanInt( num_missing_files ) + ' were missing' )
+ bad_comments.append( HydrusNumbers.ToHumanInt( num_missing_files ) + ' were missing' )
if num_unimportable_mime_files > 0:
- bad_comments.append( HydrusData.ToHumanInt( num_unimportable_mime_files ) + ' had unsupported file types' )
+ bad_comments.append( HydrusNumbers.ToHumanInt( num_unimportable_mime_files ) + ' had unsupported file types' )
if num_occupied_files > 0:
- bad_comments.append( HydrusData.ToHumanInt( num_occupied_files ) + ' were inaccessible (maybe in use by another process)' )
+ bad_comments.append( HydrusNumbers.ToHumanInt( num_occupied_files ) + ' were inaccessible (maybe in use by another process)' )
message += ' and '.join( bad_comments )
@@ -3571,7 +3572,7 @@ def _ConvertRowToListCtrlTuples( self, name ):
else:
sort_num_rows = num_rows
- pretty_num_rows = HydrusData.ToHumanInt( sort_num_rows )
+ pretty_num_rows = HydrusNumbers.ToHumanInt( sort_num_rows )
display_tuple = ( pretty_name, pretty_num_rows )
diff --git a/hydrus/client/gui/parsing/ClientGUIParsing.py b/hydrus/client/gui/parsing/ClientGUIParsing.py
index 96f4bed83..1430730e5 100644
--- a/hydrus/client/gui/parsing/ClientGUIParsing.py
+++ b/hydrus/client/gui/parsing/ClientGUIParsing.py
@@ -9,7 +9,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
@@ -253,7 +253,7 @@ def _Export( self ):
if len( export_object ) > 20:
message += '\n'
- message += '(and ' + HydrusData.ToHumanInt( len( export_object ) - 20 ) + ' others)'
+ message += '(and ' + HydrusNumbers.ToHumanInt( len( export_object ) - 20 ) + ' others)'
message += '\n' * 2
@@ -290,7 +290,7 @@ def _Export( self ):
else:
- title += ' - ' + HydrusData.ToHumanInt( num_gugs ) + ' downloaders'
+ title += ' - ' + HydrusNumbers.ToHumanInt( num_gugs ) + ' downloaders'
description = ', '.join( gug_names )
@@ -1037,7 +1037,7 @@ def __init__( self, parent: QW.QWidget, test_data_callable: typing.Callable[ [],
content_parsers_panel.SetListCtrl( self._content_parsers )
content_parsers_panel.AddButton( 'add', self._Add )
- content_parsers_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ content_parsers_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
content_parsers_panel.AddDeleteButton()
content_parsers_panel.AddSeparator()
content_parsers_panel.AddImportExportButtons( ( ClientParsing.ContentParser, ), self._AddContentParser )
@@ -1262,7 +1262,7 @@ def __init__( self, parent, parser: ClientParsing.PageParser, formula = None, te
sub_page_parsers_panel.SetListCtrl( self._sub_page_parsers )
sub_page_parsers_panel.AddButton( 'add', self._AddSubPageParser )
- sub_page_parsers_panel.AddButton( 'edit', self._EditSubPageParser, enabled_only_on_selection = True )
+ sub_page_parsers_panel.AddButton( 'edit', self._EditSubPageParser, enabled_only_on_single_selection = True )
sub_page_parsers_panel.AddDeleteButton()
#
@@ -1659,7 +1659,7 @@ def __init__( self, parent, parsers ):
parsers_panel.SetListCtrl( self._parsers )
parsers_panel.AddButton( 'add', self._Add )
- parsers_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ parsers_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
parsers_panel.AddDeleteButton()
parsers_panel.AddSeparator()
parsers_panel.AddImportExportButtons( ( ClientParsing.PageParser, ), self._AddParser )
diff --git a/hydrus/client/gui/parsing/ClientGUIParsingLegacy.py b/hydrus/client/gui/parsing/ClientGUIParsingLegacy.py
index 1becc66c2..e554ede7c 100644
--- a/hydrus/client/gui/parsing/ClientGUIParsingLegacy.py
+++ b/hydrus/client/gui/parsing/ClientGUIParsingLegacy.py
@@ -6,7 +6,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
@@ -469,7 +469,7 @@ def qt_code( parsed_urls ):
self._test_fetch_result.setEnabled( True )
- result_lines = [ '*** ' + HydrusData.ToHumanInt( len( parsed_urls ) ) + ' RESULTS BEGIN ***' ]
+ result_lines = [ '*** ' + HydrusNumbers.ToHumanInt( len( parsed_urls ) ) + ' RESULTS BEGIN ***' ]
result_lines.extend( parsed_urls )
@@ -788,7 +788,7 @@ def qt_code( results ):
return
- result_lines = [ '*** ' + HydrusData.ToHumanInt( len( results ) ) + ' RESULTS BEGIN ***' ]
+ result_lines = [ '*** ' + HydrusNumbers.ToHumanInt( len( results ) ) + ' RESULTS BEGIN ***' ]
result_lines.extend( ( ClientParsing.ConvertParseResultToPrettyString( result ) for result in results ) )
diff --git a/hydrus/client/gui/parsing/ClientGUIParsingTest.py b/hydrus/client/gui/parsing/ClientGUIParsingTest.py
index 614c55eaa..f905d0e67 100644
--- a/hydrus/client/gui/parsing/ClientGUIParsingTest.py
+++ b/hydrus/client/gui/parsing/ClientGUIParsingTest.py
@@ -10,6 +10,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTemp
from hydrus.core import HydrusText
from hydrus.core.files import HydrusFileHandling
@@ -661,7 +662,7 @@ def _SetExampleData( self, example_data, example_bytes = None ):
preview = 'PREVIEW:' + '\n' + str( preview[:1024] )
- description = HydrusData.ToHumanInt( len( separation_example_data ) ) + ' subsidiary posts parsed'
+ description = HydrusNumbers.ToHumanInt( len( separation_example_data ) ) + ' subsidiary posts parsed'
except Exception as e:
diff --git a/hydrus/client/gui/search/ClientGUIACDropdown.py b/hydrus/client/gui/search/ClientGUIACDropdown.py
index 4582a1911..87598539a 100644
--- a/hydrus/client/gui/search/ClientGUIACDropdown.py
+++ b/hydrus/client/gui/search/ClientGUIACDropdown.py
@@ -11,6 +11,7 @@
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
@@ -2857,7 +2858,7 @@ def _AddEditMenu( self, menu: QW.QMenu ):
else:
- desc = '{} search terms'.format( HydrusData.ToHumanInt( len( editable_and_invertible_predicates ) ) )
+ desc = '{} search terms'.format( HydrusNumbers.ToHumanInt( len( editable_and_invertible_predicates ) ) )
label = 'edit {}'.format( desc )
diff --git a/hydrus/client/gui/search/ClientGUISearchPanels.py b/hydrus/client/gui/search/ClientGUISearchPanels.py
index b27a1cf54..14ed11736 100644
--- a/hydrus/client/gui/search/ClientGUISearchPanels.py
+++ b/hydrus/client/gui/search/ClientGUISearchPanels.py
@@ -182,7 +182,7 @@ def __init__( self, parent, favourite_searches_rows, initial_search_row_to_edit
self._favourite_searches_panel.SetListCtrl( self._favourite_searches )
self._favourite_searches_panel.AddButton( 'add', self._AddNewFavouriteSearch )
- self._favourite_searches_panel.AddButton( 'edit', self._EditFavouriteSearch, enabled_only_on_selection = True )
+ self._favourite_searches_panel.AddButton( 'edit', self._EditFavouriteSearch, enabled_only_on_single_selection = True )
self._favourite_searches_panel.AddDeleteButton()
#
diff --git a/hydrus/client/gui/services/ClientGUIClientsideServices.py b/hydrus/client/gui/services/ClientGUIClientsideServices.py
index 13a84f970..9effcd891 100644
--- a/hydrus/client/gui/services/ClientGUIClientsideServices.py
+++ b/hydrus/client/gui/services/ClientGUIClientsideServices.py
@@ -9,6 +9,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
@@ -207,7 +208,7 @@ def _Delete( self ):
if num_files > 0:
- message = 'The service {} needs to be empty before it can be deleted, but it seems to have {} files in it! Please delete or migrate all the files from it and then try again.'.format( service.GetName(), HydrusData.ToHumanInt( num_files ) )
+ message = 'The service {} needs to be empty before it can be deleted, but it seems to have {} files in it! Please delete or migrate all the files from it and then try again.'.format( service.GetName(), HydrusNumbers.ToHumanInt( num_files ) )
ClientGUIDialogsMessage.ShowInformation( self, message )
@@ -1799,7 +1800,7 @@ def do_it():
rows_s = int( weight / it_took )
- update_speed_string = ' at ' + HydrusData.ToHumanInt( rows_s ) + ' rows/s'
+ update_speed_string = ' at ' + HydrusNumbers.ToHumanInt( rows_s ) + ' rows/s'
c_u_p_total_weight_processed += weight
@@ -1808,7 +1809,7 @@ def do_it():
self._service.SyncThumbnails( job_status )
- job_status.SetStatusText( 'done! ' + HydrusData.ToHumanInt( c_u_p_num_rows ) + ' rows added.' )
+ job_status.SetStatusText( 'done! ' + HydrusNumbers.ToHumanInt( c_u_p_num_rows ) + ' rows added.' )
job_status.Finish()
@@ -1893,7 +1894,7 @@ def __init__( self, parent, service ):
menu_items.append( ( 'normal', 'from api request', 'Listen for an access permission request from an external program via the API.', self._AddFromAPI ) )
permissions_list_panel.AddMenuButton( 'add', menu_items )
- permissions_list_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ permissions_list_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
permissions_list_panel.AddButton( 'duplicate', self._Duplicate, enabled_only_on_selection = True )
permissions_list_panel.AddButton( 'delete', self._Delete, enabled_only_on_selection = True )
permissions_list_panel.AddSeparator()
@@ -2247,7 +2248,7 @@ def qt_code( text ):
else:
- text = '{} files and {} thumbnails are awaiting physical deletion from file storage.'.format( HydrusData.ToHumanInt( num_files ), HydrusData.ToHumanInt( num_thumbnails ) )
+ text = '{} files and {} thumbnails are awaiting physical deletion from file storage.'.format( HydrusNumbers.ToHumanInt( num_files ), HydrusNumbers.ToHumanInt( num_thumbnails ) )
QP.CallAfter( qt_code, text )
@@ -2313,13 +2314,13 @@ def qt_code( text ):
num_files = service_info[ HC.SERVICE_INFO_NUM_FILES ]
total_size = service_info[ HC.SERVICE_INFO_TOTAL_SIZE ]
- text = HydrusData.ToHumanInt( num_files ) + ' files, totalling ' + HydrusData.ToHumanBytes( total_size )
+ text = HydrusNumbers.ToHumanInt( num_files ) + ' files, totalling ' + HydrusData.ToHumanBytes( total_size )
if service.GetServiceType() in ( HC.LOCAL_FILE_DOMAIN, HC.COMBINED_LOCAL_MEDIA, HC.COMBINED_LOCAL_FILE, HC.FILE_REPOSITORY ):
num_deleted_files = service_info[ HC.SERVICE_INFO_NUM_DELETED_FILES ]
- text += ' - ' + HydrusData.ToHumanInt( num_deleted_files ) + ' deleted files'
+ text += ' - ' + HydrusNumbers.ToHumanInt( num_deleted_files ) + ' deleted files'
QP.CallAfter( qt_code, text )
@@ -3000,7 +3001,7 @@ def publish_callable( service_info_dict ):
message = 'Note that num file hashes and tags here include deleted content so will likely not line up with your review services value, which is only for current content.'
message += '\n' * 2
- tuples = [ ( HC.service_info_enum_str_lookup[ info_type ], HydrusData.ToHumanInt( service_info_dict[ info_type ] ) ) for info_type in service_info_types if info_type in service_info_dict ]
+ tuples = [ ( HC.service_info_enum_str_lookup[ info_type ], HydrusNumbers.ToHumanInt( service_info_dict[ info_type ] ) ) for info_type in service_info_types if info_type in service_info_dict ]
string_rows = [ '{}: {}'.format( info_type, info ) for ( info_type, info ) in tuples ]
message += '\n'.join( string_rows )
@@ -3481,7 +3482,7 @@ def _ConvertDataToListCtrlTuple( self, data ):
( multihash, num_files, total_size, note ) = data
pretty_multihash = multihash
- pretty_num_files = HydrusData.ToHumanInt( num_files )
+ pretty_num_files = HydrusNumbers.ToHumanInt( num_files )
pretty_total_size = HydrusData.ToHumanBytes( total_size )
pretty_note = note
@@ -3751,7 +3752,7 @@ def qt_code( text ):
num_files = service_info[ HC.SERVICE_INFO_NUM_FILE_HASHES ]
- text = HydrusData.ToHumanInt( num_files ) + ' files are rated'
+ text = HydrusNumbers.ToHumanInt( num_files ) + ' files are rated'
QP.CallAfter( qt_code, text )
@@ -3827,13 +3828,13 @@ def qt_code( text ):
num_tags = service_info[ HC.SERVICE_INFO_NUM_TAGS ]
num_mappings = service_info[ HC.SERVICE_INFO_NUM_MAPPINGS ]
- text = HydrusData.ToHumanInt( num_mappings ) + ' total mappings involving ' + HydrusData.ToHumanInt( num_tags ) + ' different tags on ' + HydrusData.ToHumanInt( num_files ) + ' different files'
+ text = HydrusNumbers.ToHumanInt( num_mappings ) + ' total mappings involving ' + HydrusNumbers.ToHumanInt( num_tags ) + ' different tags on ' + HydrusNumbers.ToHumanInt( num_files ) + ' different files'
if service.GetServiceType() == HC.TAG_REPOSITORY:
num_deleted_mappings = service_info[ HC.SERVICE_INFO_NUM_DELETED_MAPPINGS ]
- text += ' - ' + HydrusData.ToHumanInt( num_deleted_mappings ) + ' deleted mappings'
+ text += ' - ' + HydrusNumbers.ToHumanInt( num_deleted_mappings ) + ' deleted mappings'
QP.CallAfter( qt_code, text )
diff --git a/hydrus/client/gui/services/ClientGUIModalClientsideServiceActions.py b/hydrus/client/gui/services/ClientGUIModalClientsideServiceActions.py
new file mode 100644
index 000000000..d6523f077
--- /dev/null
+++ b/hydrus/client/gui/services/ClientGUIModalClientsideServiceActions.py
@@ -0,0 +1,174 @@
+import typing
+
+from qtpy import QtWidgets as QW
+
+from hydrus.core import HydrusConstants as HC
+from hydrus.core import HydrusNumbers
+from hydrus.core import HydrusTags
+
+from hydrus.client import ClientConstants as CC
+from hydrus.client import ClientGlobals as CG
+from hydrus.client import ClientLocation
+from hydrus.client import ClientMigration
+from hydrus.client.gui import ClientGUITopLevelWindowsPanels
+from hydrus.client.gui import ClientGUIDialogsQuick
+from hydrus.client.gui import QtPorting as QP
+from hydrus.client.gui.lists import ClientGUIListBoxes
+from hydrus.client.gui.panels import ClientGUIScrolledPanels
+from hydrus.client.gui.search import ClientGUIACDropdown
+from hydrus.client.gui.widgets import ClientGUICommon
+
+class ReviewPurgeTagsPanel( ClientGUIScrolledPanels.ReviewPanel ):
+
+ def __init__( self, parent, service_key: bytes, tags: typing.Collection[ str ] ):
+
+ # update the listboxstrings to have async count fetch, would be nice. could also show (0) count tags from init
+ # prob needs a refresh button also to refresh current counts
+
+ self._service_key = service_key
+
+ ClientGUIScrolledPanels.ReviewPanel.__init__( self, parent )
+
+ # what about a listboxtags that has an auto-async thing to produce a count suffix, mate? surely this is doable in some way
+ self._tags_to_remove = ClientGUIListBoxes.ListBoxTagsStringsAddRemove( self, service_key = service_key )
+
+ self._autocomplete = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self, self._tags_to_remove.EnterTags, ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_FILE_SERVICE_KEY ), self._service_key, show_paste_button = True )
+
+ # forcing things in case options try to set default
+ self._autocomplete.SetLocationContext( ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_FILE_SERVICE_KEY ) )
+ self._autocomplete.SetTagServiceKey( self._service_key )
+
+ self._default_petition_reason = 'Tags purged by janitor.'
+
+ self._petition_reason = QW.QLineEdit( self )
+ self._petition_reason.setPlaceholderText( self._default_petition_reason )
+
+ self._go_button = ClientGUICommon.BetterButton( self, 'Go!', self.Go )
+
+ #
+
+ self._tags_to_remove.SetTags( tags )
+
+ label = 'This will petition every instance of the tag from the repository, which, when you commit, will leave a deletion record (so only janitors would be able to re-add those tags for those files). If you have permission, you likely also want to add these tags to the repository\'s tag filter so they cannot be added to any other files in future.'
+ label += '\n' * 2
+ label += 'If you cannot find a tag in the autocomplete, then it probably only exists as a result of a sibling or parent relationship. These tags cannot be deleted here, since they do not actually exist on the repository as stored mappings--you need to change the underlying siblings/parents and/or delete the source mappings that implicate them.'
+
+ st = ClientGUICommon.BetterStaticText( self, label )
+
+ st.setWordWrap( True )
+
+ rows = []
+
+ rows.append( ( 'petition reason (optional): ', self._petition_reason ) )
+
+ gridbox = ClientGUICommon.WrapInGrid( self, rows )
+
+ vbox = QP.VBoxLayout()
+
+ QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
+ QP.AddToLayout( vbox, self._tags_to_remove, CC.FLAGS_EXPAND_BOTH_WAYS )
+ QP.AddToLayout( vbox, self._autocomplete, CC.FLAGS_EXPAND_PERPENDICULAR )
+ QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
+ QP.AddToLayout( vbox, self._go_button, CC.FLAGS_ON_RIGHT )
+
+ self.widget().setLayout( vbox )
+
+
+ def Go( self ):
+
+ tags_to_purge = self._tags_to_remove.GetTags()
+
+ if len( tags_to_purge ) == 0:
+
+ return
+
+
+ text = f'Start this purge job on {HydrusNumbers.ToHumanInt( len( tags_to_purge ) )} tags?'
+
+ result = ClientGUIDialogsQuick.GetYesNo( self, text )
+
+ if result != QW.QDialog.Accepted:
+
+ return
+
+
+ reason = self._petition_reason.text()
+
+ if reason == '':
+
+ reason = self._default_petition_reason
+
+
+ tag_filter = HydrusTags.TagFilter()
+
+ # we want the migration to cover the tags we want to delete
+ tag_filter.SetRules( ( '', ':' ), HC.FILTER_BLACKLIST )
+ tag_filter.SetRules( tags_to_purge, HC.FILTER_WHITELIST )
+
+ PurgeTags( self._service_key, tag_filter, reason )
+
+ self._tags_to_remove.Clear()
+
+
+
+def PurgeTags(
+ service_key: bytes,
+ tag_filter: HydrusTags.TagFilter,
+ reason: str
+):
+
+ location_context = ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_FILE_SERVICE_KEY )
+ desired_hash_type = 'sha256'
+ hashes = None
+ content_statuses = [ HC.CONTENT_STATUS_CURRENT ]
+ content_action = HC.CONTENT_UPDATE_PETITION
+
+ source = ClientMigration.MigrationSourceTagServiceMappings( CG.client_controller, service_key, location_context, desired_hash_type, hashes, tag_filter, content_statuses )
+
+ destination = ClientMigration.MigrationDestinationTagServiceMappings( CG.client_controller, service_key, content_action )
+ destination.SetReason( reason )
+
+ migration_job = ClientMigration.MigrationJob( CG.client_controller, 'purging tags', source, destination )
+
+ CG.client_controller.CallToThread( migration_job.Run )
+
+
+def OpenPurgeTagsWindow(
+ win: QW.QWidget,
+ service_key: bytes,
+ tags: typing.Collection[ str ]
+):
+
+ frame = ClientGUITopLevelWindowsPanels.FrameThatTakesScrollablePanel( win, 'purge tags' )
+
+ panel = ReviewPurgeTagsPanel( frame, service_key, tags )
+
+ frame.SetPanel( panel )
+
+
+def StartPurgeTagFilter(
+ win: QW.QWidget,
+ service_key: bytes
+):
+
+ tag_filter: HydrusTags.TagFilter = CG.client_controller.services_manager.GetService( service_key ).GetTagFilter()
+
+ text = f'This will start a purge job based on the rules in the current tag filter, "syncing" the mappings store to the filter by petitioning everything that retroactively violates the rules. The current tag filter for this service is:'
+ text += '\n' * 2
+ text += tag_filter.ToPermittedString()
+ text += '\n' * 2
+ text += 'If you do not make any changes to the tag filter, this is idempotent.'
+
+ result = ClientGUIDialogsQuick.GetYesNo( win, text )
+
+ if result != QW.QDialog.Accepted:
+
+ return
+
+
+ inverted_tag_filter = tag_filter.GetInvertedFilter() # we actively want to _delete_ what is currently _disallowed_, which means the migration needs to attack the opposite
+
+ reason = 'Tag Filter Purge Sync'
+
+ PurgeTags( service_key, inverted_tag_filter, reason )
+
diff --git a/hydrus/client/gui/services/ClientGUIModalServersideServiceActions.py b/hydrus/client/gui/services/ClientGUIModalServersideServiceActions.py
new file mode 100644
index 000000000..849ca017c
--- /dev/null
+++ b/hydrus/client/gui/services/ClientGUIModalServersideServiceActions.py
@@ -0,0 +1,83 @@
+import typing
+
+from qtpy import QtWidgets as QW
+
+from hydrus.core import HydrusConstants as HC
+from hydrus.client import ClientGlobals as CG
+from hydrus.client import ClientServices
+from hydrus.client import ClientThreading
+from hydrus.client.gui import ClientGUIAsync
+from hydrus.client.gui import ClientGUITags
+from hydrus.client.gui import ClientGUITopLevelWindowsPanels
+
+def ManageServiceOptionsTagFilter(
+ win: QW.QWidget,
+ service_key: bytes,
+ new_tags_to_block: typing.Optional[ typing.Collection[ str ] ] = None,
+ new_tags_to_allow: typing.Optional[ typing.Collection[ str ] ] = None
+):
+
+ service: ClientServices.ServiceRepository = CG.client_controller.services_manager.GetService( service_key )
+
+ tag_filter = service.GetTagFilter().Duplicate()
+
+ if new_tags_to_block is not None:
+
+ tag_filter.SetRules( new_tags_to_block, HC.FILTER_BLACKLIST )
+
+
+ if new_tags_to_allow is not None:
+
+ tag_filter.SetRules( new_tags_to_allow, HC.FILTER_WHITELIST )
+
+
+ with ClientGUITopLevelWindowsPanels.DialogEdit( win, 'edit tag repository tag filter' ) as dlg:
+
+ namespaces = CG.client_controller.network_engine.domain_manager.GetParserNamespaces()
+
+ message = 'The repository will apply this to all new pending tags that are uploaded to it. Anything that does not pass is silently discarded.'
+
+ panel = ClientGUITags.EditTagFilterPanel( dlg, tag_filter, message = message, namespaces = namespaces )
+
+ dlg.SetPanel( panel )
+
+ if dlg.exec() == QW.QDialog.Accepted:
+
+ tag_filter = panel.GetValue()
+
+ job_status = ClientThreading.JobStatus()
+
+ job_status.SetStatusTitle( 'setting tag filter' )
+ job_status.SetStatusText( 'uploading' + HC.UNICODE_ELLIPSIS )
+
+ CG.client_controller.pub( 'message', job_status )
+
+ def work_callable():
+
+ service.Request( HC.POST, 'tag_filter', { 'tag_filter' : tag_filter } )
+
+ return 1
+
+
+ def publish_callable( gumpf ):
+
+ job_status.SetStatusText( 'done!' )
+
+ job_status.FinishAndDismiss( 5 )
+
+ service.SetAccountRefreshDueNow()
+
+
+ def errback_ui_cleanup_callable():
+
+ job_status.SetStatusText( 'error!' )
+
+ job_status.Finish()
+
+
+ job = ClientGUIAsync.AsyncQtJob( win, work_callable, publish_callable, errback_ui_cleanup_callable = errback_ui_cleanup_callable )
+
+ job.start()
+
+
+
diff --git a/hydrus/client/gui/widgets/ClientGUIBandwidth.py b/hydrus/client/gui/widgets/ClientGUIBandwidth.py
index 8c21c63e9..cb5555c2e 100644
--- a/hydrus/client/gui/widgets/ClientGUIBandwidth.py
+++ b/hydrus/client/gui/widgets/ClientGUIBandwidth.py
@@ -4,6 +4,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTime
from hydrus.core.networking import HydrusNetworking
@@ -51,7 +52,7 @@ def display_call( desired_columns, rule ):
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
- pretty_max_allowed = '{} requests'.format( HydrusData.ToHumanInt( max_allowed ) )
+ pretty_max_allowed = '{} requests'.format( HydrusNumbers.ToHumanInt( max_allowed ) )
pretty_time_delta = HydrusTime.TimeDeltaToPrettyTimeDelta( time_delta )
@@ -70,7 +71,7 @@ def display_call( desired_columns, rule ):
listctrl_panel.SetListCtrl( self._listctrl )
listctrl_panel.AddButton( 'add', self._Add )
- listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
+ listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_single_selection = True )
listctrl_panel.AddDeleteButton()
#
@@ -117,7 +118,7 @@ def _ConvertRuleToListCtrlTuples( self, rule ):
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
- pretty_max_allowed = HydrusData.ToHumanInt( max_allowed ) + ' requests'
+ pretty_max_allowed = HydrusNumbers.ToHumanInt( max_allowed ) + ' requests'
sort_time_delta = ClientGUIListCtrl.SafeNoneInt( time_delta )
diff --git a/hydrus/client/importing/ClientImportFileSeeds.py b/hydrus/client/importing/ClientImportFileSeeds.py
index c09f3d1e4..caa76c60b 100644
--- a/hydrus/client/importing/ClientImportFileSeeds.py
+++ b/hydrus/client/importing/ClientImportFileSeeds.py
@@ -12,6 +12,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
@@ -1553,7 +1554,7 @@ def WorkOnURL( self, file_seed_cache: "FileSeedCache", status_hook, network_job_
num_urls_added = file_seed_cache.InsertFileSeeds( insertion_index, file_seeds )
status = CC.STATUS_SUCCESSFUL_AND_CHILD_FILES
- note = 'Found {} new URLs.'.format( HydrusData.ToHumanInt( num_urls_added ) )
+ note = 'Found {} new URLs.'.format( HydrusNumbers.ToHumanInt( num_urls_added ) )
self.SetStatus( status, note = note )
@@ -1655,7 +1656,7 @@ def WorkOnURL( self, file_seed_cache: "FileSeedCache", status_hook, network_job_
num_urls_added = file_seed_cache.InsertFileSeeds( insertion_index, child_file_seeds )
status = CC.STATUS_SUCCESSFUL_AND_CHILD_FILES
- note = 'Found {} new URLs.'.format( HydrusData.ToHumanInt( num_urls_added ) )
+ note = 'Found {} new URLs.'.format( HydrusNumbers.ToHumanInt( num_urls_added ) )
self.SetStatus( status, note = note )
@@ -1974,38 +1975,38 @@ def GetStatusText( self, simple = False ) -> str:
else:
- status_text += HydrusData.ToHumanInt( total_processed )
+ status_text += HydrusNumbers.ToHumanInt( total_processed )
show_new_on_file_seed_short_summary = CG.client_controller.new_options.GetBoolean( 'show_new_on_file_seed_short_summary' )
if show_new_on_file_seed_short_summary and num_successful_and_new:
- status_text += ' - {}N'.format( HydrusData.ToHumanInt( num_successful_and_new ) )
+ status_text += ' - {}N'.format( HydrusNumbers.ToHumanInt( num_successful_and_new ) )
simple_status_strings = []
if num_ignored > 0:
- simple_status_strings.append( '{}Ig'.format( HydrusData.ToHumanInt( num_ignored ) ) )
+ simple_status_strings.append( '{}Ig'.format( HydrusNumbers.ToHumanInt( num_ignored ) ) )
show_deleted_on_file_seed_short_summary = CG.client_controller.new_options.GetBoolean( 'show_deleted_on_file_seed_short_summary' )
if show_deleted_on_file_seed_short_summary and num_deleted > 0:
- simple_status_strings.append( '{}D'.format( HydrusData.ToHumanInt( num_deleted ) ) )
+ simple_status_strings.append( '{}D'.format( HydrusNumbers.ToHumanInt( num_deleted ) ) )
if num_failed > 0:
- simple_status_strings.append( '{}F'.format( HydrusData.ToHumanInt( num_failed ) ) )
+ simple_status_strings.append( '{}F'.format( HydrusNumbers.ToHumanInt( num_failed ) ) )
if num_skipped > 0:
- simple_status_strings.append( '{}S'.format( HydrusData.ToHumanInt( num_skipped ) ) )
+ simple_status_strings.append( '{}S'.format( HydrusNumbers.ToHumanInt( num_skipped ) ) )
if len( simple_status_strings ) > 0:
@@ -2022,13 +2023,13 @@ def GetStatusText( self, simple = False ) -> str:
if num_successful > 0:
- s = '{} successful'.format( HydrusData.ToHumanInt( num_successful ) )
+ s = '{} successful'.format( HydrusNumbers.ToHumanInt( num_successful ) )
if num_successful_and_new > 0:
if num_successful_but_redundant > 0:
- s += ' ({} already in db)'.format( HydrusData.ToHumanInt( num_successful_but_redundant ) )
+ s += ' ({} already in db)'.format( HydrusNumbers.ToHumanInt( num_successful_but_redundant ) )
else:
@@ -2041,22 +2042,22 @@ def GetStatusText( self, simple = False ) -> str:
if num_ignored > 0:
- status_strings.append( '{} ignored'.format( HydrusData.ToHumanInt( num_ignored ) ) )
+ status_strings.append( '{} ignored'.format( HydrusNumbers.ToHumanInt( num_ignored ) ) )
if num_deleted > 0:
- status_strings.append( '{} previously deleted'.format( HydrusData.ToHumanInt( num_deleted ) ) )
+ status_strings.append( '{} previously deleted'.format( HydrusNumbers.ToHumanInt( num_deleted ) ) )
if num_failed > 0:
- status_strings.append( '{} failed'.format( HydrusData.ToHumanInt( num_failed ) ) )
+ status_strings.append( '{} failed'.format( HydrusNumbers.ToHumanInt( num_failed ) ) )
if num_skipped > 0:
- status_strings.append( '{} skipped'.format( HydrusData.ToHumanInt( num_skipped ) ) )
+ status_strings.append( '{} skipped'.format( HydrusNumbers.ToHumanInt( num_skipped ) ) )
status_text = ', '.join( status_strings )
diff --git a/hydrus/client/importing/ClientImportGallery.py b/hydrus/client/importing/ClientImportGallery.py
index 4dbe55536..212ad5ef8 100644
--- a/hydrus/client/importing/ClientImportGallery.py
+++ b/hydrus/client/importing/ClientImportGallery.py
@@ -6,6 +6,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
@@ -1692,7 +1693,7 @@ def PendQueries( self, query_texts ):
all_search_urls_flat.extend( initial_search_urls )
- query_text = HydrusData.ToHumanInt( len( groups_of_query_data ) ) + ' queries'
+ query_text = HydrusNumbers.ToHumanInt( len( groups_of_query_data ) ) + ' queries'
groups_of_query_data = [ ( query_text, all_search_urls_flat ) ]
diff --git a/hydrus/client/importing/ClientImportGallerySeeds.py b/hydrus/client/importing/ClientImportGallerySeeds.py
index 04813d359..e76db7a15 100644
--- a/hydrus/client/importing/ClientImportGallerySeeds.py
+++ b/hydrus/client/importing/ClientImportGallerySeeds.py
@@ -11,6 +11,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
@@ -69,29 +70,29 @@ def GenerateGallerySeedLogStatus( statuses_to_counts ):
if num_successful > 0:
- s = HydrusData.ToHumanInt( num_successful ) + ' successful'
+ s = HydrusNumbers.ToHumanInt( num_successful ) + ' successful'
status_strings.append( s )
if num_ignored > 0:
- status_strings.append( HydrusData.ToHumanInt( num_ignored ) + ' ignored' )
+ status_strings.append( HydrusNumbers.ToHumanInt( num_ignored ) + ' ignored' )
if num_failed > 0:
- status_strings.append( HydrusData.ToHumanInt( num_failed ) + ' failed' )
+ status_strings.append( HydrusNumbers.ToHumanInt( num_failed ) + ' failed' )
if num_skipped > 0:
- status_strings.append( HydrusData.ToHumanInt( num_skipped ) + ' skipped' )
+ status_strings.append( HydrusNumbers.ToHumanInt( num_skipped ) + ' skipped' )
if num_unknown > 0:
- status_strings.append( HydrusData.ToHumanInt( num_unknown ) + ' pending' )
+ status_strings.append( HydrusNumbers.ToHumanInt( num_unknown ) + ' pending' )
status = ', '.join( status_strings )
@@ -532,11 +533,11 @@ def WorkOnURL( self, gallery_token_name, gallery_seed_log: "GallerySeedLog", fil
status = CC.STATUS_SUCCESSFUL_AND_NEW
- note = HydrusData.ToHumanInt( num_urls_added ) + ' new urls found'
+ note = HydrusNumbers.ToHumanInt( num_urls_added ) + ' new urls found'
if num_urls_already_in_file_seed_cache > 0:
- note += ' (' + HydrusData.ToHumanInt( num_urls_already_in_file_seed_cache ) + ' of page already in)'
+ note += ' (' + HydrusNumbers.ToHumanInt( num_urls_already_in_file_seed_cache ) + ' of page already in)'
if not can_search_for_more_files:
@@ -580,7 +581,7 @@ def WorkOnURL( self, gallery_token_name, gallery_seed_log: "GallerySeedLog", fil
added_new_gallery_pages = True
- note += ' - {} sub-gallery urls found'.format( HydrusData.ToHumanInt( len( new_sub_gallery_seeds ) ) )
+ note += ' - {} sub-gallery urls found'.format( HydrusNumbers.ToHumanInt( len( new_sub_gallery_seeds ) ) )
if self._can_generate_more_pages and can_add_more_gallery_urls:
@@ -657,16 +658,16 @@ def WorkOnURL( self, gallery_token_name, gallery_seed_log: "GallerySeedLog", fil
if num_dupe_next_page_urls == 0:
- note += ' - ' + HydrusData.ToHumanInt( num_new_next_page_urls ) + next_page_generation_phrase
+ note += ' - ' + HydrusNumbers.ToHumanInt( num_new_next_page_urls ) + next_page_generation_phrase
else:
- note += ' - ' + HydrusData.ToHumanInt( num_new_next_page_urls ) + next_page_generation_phrase + ', but ' + HydrusData.ToHumanInt( num_dupe_next_page_urls ) + ' had already been visited this run and were not added'
+ note += ' - ' + HydrusNumbers.ToHumanInt( num_new_next_page_urls ) + next_page_generation_phrase + ', but ' + HydrusNumbers.ToHumanInt( num_dupe_next_page_urls ) + ' had already been visited this run and were not added'
else:
- note += ' - ' + HydrusData.ToHumanInt( num_dupe_next_page_urls ) + next_page_generation_phrase + ', but they had already been visited this run and were not added'
+ note += ' - ' + HydrusNumbers.ToHumanInt( num_dupe_next_page_urls ) + next_page_generation_phrase + ', but they had already been visited this run and were not added'
diff --git a/hydrus/client/importing/ClientImportLocal.py b/hydrus/client/importing/ClientImportLocal.py
index 3a82bcbff..a82d95228 100644
--- a/hydrus/client/importing/ClientImportLocal.py
+++ b/hydrus/client/importing/ClientImportLocal.py
@@ -7,6 +7,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusThreading
@@ -682,7 +683,7 @@ def _CheckFolder( self, job_status: ClientThreading.JobStatus ):
new_paths = [ path for ( path, file_seed ) in paths_to_file_seeds.items() if not self._file_seed_cache.HasFileSeed( file_seed ) ]
- job_status.SetStatusText( f'checking: found {HydrusData.ToHumanInt( len( new_paths ) )} new files' )
+ job_status.SetStatusText( f'checking: found {HydrusNumbers.ToHumanInt( len( new_paths ) )} new files' )
old_new_paths = HydrusPaths.FilterOlderModifiedFiles( new_paths, self._last_modified_time_skip_period )
@@ -690,7 +691,7 @@ def _CheckFolder( self, job_status: ClientThreading.JobStatus ):
file_seeds = [ paths_to_file_seeds[ path ] for path in free_old_new_paths ]
- job_status.SetStatusText( f'checking: found {HydrusData.ToHumanInt( len( file_seeds ) )} new files to import' )
+ job_status.SetStatusText( f'checking: found {HydrusNumbers.ToHumanInt( len( file_seeds ) )} new files to import' )
self._file_seed_cache.AddFileSeeds( file_seeds )
@@ -880,7 +881,7 @@ def _ImportFiles( self, job_status ):
if num_files_imported > 0:
- HydrusData.Print( 'Import folder ' + self._name + ' imported ' + HydrusData.ToHumanInt( num_files_imported ) + ' files.' )
+ HydrusData.Print( 'Import folder ' + self._name + ' imported ' + HydrusNumbers.ToHumanInt( num_files_imported ) + ' files.' )
if len( presentation_hashes ) > 0:
diff --git a/hydrus/client/importing/ClientImportSimpleURLs.py b/hydrus/client/importing/ClientImportSimpleURLs.py
index 7fe01e74f..be3286aad 100644
--- a/hydrus/client/importing/ClientImportSimpleURLs.py
+++ b/hydrus/client/importing/ClientImportSimpleURLs.py
@@ -7,6 +7,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
@@ -341,13 +342,13 @@ def _WorkOnGallery( self ):
ClientImporting.WakeRepeatingJob( self._files_repeating_job )
- parser_status = 'page checked OK with formula "' + simple_downloader_formula.GetName() + '" - ' + HydrusData.ToHumanInt( num_new ) + ' new urls'
+ parser_status = 'page checked OK with formula "' + simple_downloader_formula.GetName() + '" - ' + HydrusNumbers.ToHumanInt( num_new ) + ' new urls'
num_already_in_file_seed_cache = len( file_seeds ) - num_new
if num_already_in_file_seed_cache > 0:
- parser_status += ' (' + HydrusData.ToHumanInt( num_already_in_file_seed_cache ) + ' already in queue)'
+ parser_status += ' (' + HydrusNumbers.ToHumanInt( num_already_in_file_seed_cache ) + ' already in queue)'
gallery_seed_status = CC.STATUS_SUCCESSFUL_AND_NEW
diff --git a/hydrus/client/importing/ClientImportSubscriptions.py b/hydrus/client/importing/ClientImportSubscriptions.py
index 49bb3d748..6f91e59fe 100644
--- a/hydrus/client/importing/ClientImportSubscriptions.py
+++ b/hydrus/client/importing/ClientImportSubscriptions.py
@@ -8,6 +8,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusThreading
from hydrus.core import HydrusText
@@ -596,7 +597,7 @@ def file_seeds_callable( gallery_page_of_file_seeds ):
# 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 ) )
+ stop_reason = 'saw {} contiguous previously seen urls at end of page, so assuming we caught up'.format( HydrusNumbers.ToHumanInt( current_contiguous_num_urls_already_in_file_seed_cache ) )
if num_urls_added == 0:
@@ -609,7 +610,7 @@ def file_seeds_callable( gallery_page_of_file_seeds ):
return ( num_urls_added, num_urls_already_in_file_seed_cache, can_search_for_more_files, stop_reason )
- job_status.SetStatusText( status_prefix + ': found ' + HydrusData.ToHumanInt( total_new_urls_for_this_sync ) + ' new urls, checking next page' )
+ job_status.SetStatusText( status_prefix + ': found ' + HydrusNumbers.ToHumanInt( total_new_urls_for_this_sync ) + ' new urls, checking next page' )
try:
@@ -696,7 +697,7 @@ def file_seeds_callable( gallery_page_of_file_seeds ):
( 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 ), HydrusTime.TimeDeltaToPrettyTimeDelta( death_time_delta, no_bigger_than_days = True ) ) )
+ HydrusData.ShowText( 'The query "{}" for subscription "{}" found fewer than {} files in the last {}, so it appears to be dead!'.format( query_name, self._name, HydrusNumbers.ToHumanInt( death_files_found ), HydrusTime.TimeDeltaToPrettyTimeDelta( death_time_delta, no_bigger_than_days = True ) ) )
else:
@@ -2090,13 +2091,13 @@ def ShowSnapshot( self ):
next_times = sorted( self._names_to_next_work_time.items(), key = lambda n_nwt_tuple: n_nwt_tuple[1] )
- message = '{} subs: {}'.format( HydrusData.ToHumanInt( len( self._names_to_subscriptions ) ), ', '.join( sub_names ) )
+ message = '{} subs: {}'.format( HydrusNumbers.ToHumanInt( len( self._names_to_subscriptions ) ), ', '.join( sub_names ) )
message += '\n' * 2
- message += '{} running: {}'.format( HydrusData.ToHumanInt( len( self._names_to_running_subscription_info ) ), ', '.join( running ) )
+ message += '{} running: {}'.format( HydrusNumbers.ToHumanInt( len( self._names_to_running_subscription_info ) ), ', '.join( running ) )
message += '\n' * 2
- message += '{} not runnable: {}'.format( HydrusData.ToHumanInt( len( self._names_that_cannot_run ) ), ', '.join( cannot_run ) )
+ message += '{} not runnable: {}'.format( HydrusNumbers.ToHumanInt( len( self._names_that_cannot_run ) ), ', '.join( cannot_run ) )
message += '\n' * 2
- message += '{} next times: {}'.format( HydrusData.ToHumanInt( len( self._names_to_next_work_time ) ), ', '.join( ( '{}: {}'.format( name, ClientTime.TimestampToPrettyTimeDelta( next_work_time ) ) for ( name, next_work_time ) in next_times ) ) )
+ message += '{} next times: {}'.format( HydrusNumbers.ToHumanInt( len( self._names_to_next_work_time ) ), ', '.join( ( '{}: {}'.format( name, ClientTime.TimestampToPrettyTimeDelta( next_work_time ) ) for ( name, next_work_time ) in next_times ) ) )
HydrusData.ShowText( message )
diff --git a/hydrus/client/importing/ClientImporting.py b/hydrus/client/importing/ClientImporting.py
index 76c5acf7a..b51adb508 100644
--- a/hydrus/client/importing/ClientImporting.py
+++ b/hydrus/client/importing/ClientImporting.py
@@ -4,6 +4,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusText
from hydrus.client import ClientConstants as CC
@@ -305,22 +306,22 @@ def status_hook( text ):
if num_successful > 0:
- text_components.append( HydrusData.ToHumanInt( num_successful ) + ' successful' )
+ text_components.append( HydrusNumbers.ToHumanInt( num_successful ) + ' successful' )
if num_redundant > 0:
- text_components.append( HydrusData.ToHumanInt( num_redundant ) + ' already in db' )
+ text_components.append( HydrusNumbers.ToHumanInt( num_redundant ) + ' already in db' )
if num_deleted > 0:
- text_components.append( HydrusData.ToHumanInt( num_deleted ) + ' deleted' )
+ text_components.append( HydrusNumbers.ToHumanInt( num_deleted ) + ' deleted' )
if num_failed > 0:
- text_components.append( HydrusData.ToHumanInt( num_failed ) + ' failed (errors written to log)' )
+ text_components.append( HydrusNumbers.ToHumanInt( num_failed ) + ' failed (errors written to log)' )
job_status.SetStatusText( ', '.join( text_components ) )
diff --git a/hydrus/client/importing/options/ClientImportOptions.py b/hydrus/client/importing/options/ClientImportOptions.py
index f3e214a9c..829be6359 100644
--- a/hydrus/client/importing/options/ClientImportOptions.py
+++ b/hydrus/client/importing/options/ClientImportOptions.py
@@ -2,6 +2,7 @@
import typing
from hydrus.core import HydrusData
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
@@ -206,7 +207,7 @@ def GetPrettyCurrentVelocity( self, file_seed_cache, last_check_time, no_prefix
( current_files_found, current_time_delta ) = self._GetCurrentFilesVelocity( file_seed_cache, last_check_time )
- pretty_current_velocity += HydrusData.ToHumanInt( current_files_found ) + ' files in previous ' + HydrusTime.TimeDeltaToPrettyTimeDelta( current_time_delta )
+ pretty_current_velocity += HydrusNumbers.ToHumanInt( current_files_found ) + ' files in previous ' + HydrusTime.TimeDeltaToPrettyTimeDelta( current_time_delta )
return pretty_current_velocity
@@ -225,7 +226,7 @@ def GetSummary( self ):
else:
- timing_statement = 'Trying to get ' + HydrusData.ToHumanInt( self._intended_files_per_check ) + ' files per check, never faster than ' + HydrusTime.TimeDeltaToPrettyTimeDelta( self._never_faster_than ) + ' and never slower than ' + HydrusTime.TimeDeltaToPrettyTimeDelta( self._never_slower_than ) + '.'
+ timing_statement = 'Trying to get ' + HydrusNumbers.ToHumanInt( self._intended_files_per_check ) + ' files per check, never faster than ' + HydrusTime.TimeDeltaToPrettyTimeDelta( self._never_faster_than ) + ' and never slower than ' + HydrusTime.TimeDeltaToPrettyTimeDelta( self._never_slower_than ) + '.'
( death_files_found, death_time_delta ) = self._death_file_velocity
@@ -236,7 +237,7 @@ def GetSummary( self ):
else:
- death_statement = 'Stopping if file velocity falls below ' + HydrusData.ToHumanInt( death_files_found ) + ' files per ' + HydrusTime.TimeDeltaToPrettyTimeDelta( death_time_delta ) + '.'
+ death_statement = 'Stopping if file velocity falls below ' + HydrusNumbers.ToHumanInt( death_files_found ) + ' files per ' + HydrusTime.TimeDeltaToPrettyTimeDelta( death_time_delta ) + '.'
return timing_statement + '\n' * 2 + death_statement
diff --git a/hydrus/client/importing/options/FileImportOptions.py b/hydrus/client/importing/options/FileImportOptions.py
index 47e26e634..5373265a1 100644
--- a/hydrus/client/importing/options/FileImportOptions.py
+++ b/hydrus/client/importing/options/FileImportOptions.py
@@ -4,6 +4,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
@@ -43,7 +44,7 @@ class FileImportOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_FILE_IMPORT_OPTIONS
SERIALISABLE_NAME = 'File Import Options'
- SERIALISABLE_VERSION = 10
+ SERIALISABLE_VERSION = 11
def __init__( self ):
@@ -63,6 +64,7 @@ def __init__( self ):
self._automatic_archive = False
self._associate_primary_urls = True
self._associate_source_urls = True
+ self._do_content_updates_on_already_in_db_files = True
self._presentation_import_options = PresentationImportOptions.PresentationImportOptions()
try:
@@ -86,7 +88,7 @@ def _GetSerialisableInfo( self ):
serialisable_filetype_filter_predicate = self._filetype_filter_predicate.GetSerialisableTuple()
pre_import_options = ( self._exclude_deleted, self._preimport_hash_check_type, self._preimport_url_check_type, self._preimport_url_check_looks_for_neighbour_spam, self._allow_decompression_bombs, serialisable_filetype_filter_predicate, self._min_size, self._max_size, self._max_gif_size, self._min_resolution, self._max_resolution, serialisable_import_destination_location_context )
- post_import_options = ( self._automatic_archive, self._associate_primary_urls, self._associate_source_urls )
+ post_import_options = ( self._automatic_archive, self._associate_primary_urls, self._associate_source_urls, self._do_content_updates_on_already_in_db_files )
serialisable_presentation_import_options = self._presentation_import_options.GetSerialisableTuple()
return ( pre_import_options, post_import_options, serialisable_presentation_import_options, self._is_default )
@@ -97,7 +99,7 @@ def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( pre_import_options, post_import_options, serialisable_presentation_import_options, self._is_default ) = serialisable_info
( self._exclude_deleted, self._preimport_hash_check_type, self._preimport_url_check_type, self._preimport_url_check_looks_for_neighbour_spam, self._allow_decompression_bombs, serialisable_filetype_filter_predicate, self._min_size, self._max_size, self._max_gif_size, self._min_resolution, self._max_resolution, serialisable_import_destination_location_context ) = pre_import_options
- ( self._automatic_archive, self._associate_primary_urls, self._associate_source_urls ) = post_import_options
+ ( self._automatic_archive, self._associate_primary_urls, self._associate_source_urls, self._do_content_updates_on_already_in_db_files ) = post_import_options
self._presentation_import_options = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_presentation_import_options )
self._filetype_filter_predicate = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_filetype_filter_predicate )
@@ -307,6 +309,21 @@ def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
return ( 10, new_serialisable_info )
+ if version == 10:
+
+ ( pre_import_options, post_import_options, serialisable_presentation_import_options, is_default ) = old_serialisable_info
+
+ ( automatic_archive, associate_primary_urls, associate_source_urls ) = post_import_options
+
+ do_content_updates_on_already_in_db_files = True
+
+ post_import_options = ( automatic_archive, associate_primary_urls, associate_source_urls, do_content_updates_on_already_in_db_files )
+
+ new_serialisable_info = ( pre_import_options, post_import_options, serialisable_presentation_import_options, is_default )
+
+ return ( 11, new_serialisable_info )
+
+
def AllowsDecompressionBombs( self ):
@@ -429,6 +446,11 @@ def GetAlreadyInDBPostImportContentUpdatePackage( self, media_result: ClientMedi
content_update_package = ClientContentUpdates.ContentUpdatePackage()
+ if not self._do_content_updates_on_already_in_db_files:
+
+ return content_update_package
+
+
destination_location_context = self.GetDestinationLocationContext()
desired_file_service_keys = destination_location_context.current_service_keys
@@ -464,6 +486,11 @@ def GetDestinationLocationContext( self ) -> ClientLocation.LocationContext:
return self._import_destination_location_context
+ def GetDoContentUpdatesOnAlreadyInDBFiles( self ) -> bool:
+
+ return self._do_content_updates_on_already_in_db_files
+
+
def GetPresentationImportOptions( self ) -> PresentationImportOptions.PresentationImportOptions:
return self._presentation_import_options
@@ -526,14 +553,14 @@ def GetSummary( self ):
( width, height ) = self._min_resolution
- statements.append( 'excluding < ( ' + HydrusData.ToHumanInt( width ) + ' x ' + HydrusData.ToHumanInt( height ) + ' )' )
+ statements.append( 'excluding < ( ' + HydrusNumbers.ToHumanInt( width ) + ' x ' + HydrusNumbers.ToHumanInt( height ) + ' )' )
if self._max_resolution is not None:
( width, height ) = self._max_resolution
- statements.append( 'excluding > ( ' + HydrusData.ToHumanInt( width ) + ' x ' + HydrusData.ToHumanInt( height ) + ' )' )
+ statements.append( 'excluding > ( ' + HydrusNumbers.ToHumanInt( width ) + ' x ' + HydrusNumbers.ToHumanInt( height ) + ' )' )
#
@@ -581,6 +608,11 @@ def SetIsDefault( self, value: bool ) -> None:
self._is_default = value
+ def SetDoContentUpdatesOnAlreadyInDBFiles( self, value ):
+
+ self._do_content_updates_on_already_in_db_files = value
+
+
def SetPostImportOptions( self, automatic_archive: bool, associate_primary_urls: bool, associate_source_urls: bool ):
self._automatic_archive = automatic_archive
@@ -621,4 +653,5 @@ def ShouldAssociateSourceURLs( self ) -> bool:
return self._associate_source_urls
+
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_FILE_IMPORT_OPTIONS ] = FileImportOptions
diff --git a/hydrus/client/media/ClientMedia.py b/hydrus/client/media/ClientMedia.py
index 3d0f991e4..aa0dd9715 100644
--- a/hydrus/client/media/ClientMedia.py
+++ b/hydrus/client/media/ClientMedia.py
@@ -5,7 +5,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
from hydrus.core.files import HydrusPSDHandling
@@ -134,7 +134,7 @@ def GetDescriptor( plural, classes, num_collections ):
collections_suffix = 's' if num_collections > 1 else ''
- return 'file{} in {} collection{}'.format( suffix, HydrusData.ToHumanInt( num_collections ), collections_suffix )
+ return 'file{} in {} collection{}'.format( suffix, HydrusNumbers.ToHumanInt( num_collections ), collections_suffix )
else:
@@ -188,7 +188,7 @@ def GetDescriptor( plural, classes, num_collections ):
filetype_summary = GetDescriptor( plural, mimes, num_collections )
- return f'{HydrusData.ToHumanInt( num_files )} {filetype_summary}'
+ return f'{HydrusNumbers.ToHumanInt( num_files )} {filetype_summary}'
def GetMediasTagCount( pool, tag_service_key, tag_display_type ):
@@ -1697,7 +1697,7 @@ def GetPrettyInfoLines( self, only_interesting_lines = False ):
info_string = size + ' ' + mime
- info_string += ' (' + HydrusData.ToHumanInt( self.GetNumFiles() ) + ' files)'
+ info_string += ' (' + HydrusNumbers.ToHumanInt( self.GetNumFiles() ) + ' files)'
return [ info_string ]
@@ -1954,7 +1954,7 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
framerate_insert = f', {round( num_frames / ( duration / 1000 ) )}fps'
- info_string += f' ({HydrusData.ToHumanInt( num_frames )} frames{framerate_insert})'
+ info_string += f' ({HydrusNumbers.ToHumanInt( num_frames )} frames{framerate_insert})'
if has_audio:
@@ -1966,7 +1966,7 @@ def timestamp_ms_is_interesting( timestamp_ms_1, timestamp_ms_2 ):
if num_words is not None:
- info_string += f' ({HydrusData.ToHumanInt( num_words )} words)'
+ info_string += f' ({HydrusNumbers.ToHumanInt( num_words )} words)'
lines = [ ( True, info_string ) ]
diff --git a/hydrus/client/media/ClientMediaFileFilter.py b/hydrus/client/media/ClientMediaFileFilter.py
index e79051a1c..0327c0e7a 100644
--- a/hydrus/client/media/ClientMediaFileFilter.py
+++ b/hydrus/client/media/ClientMediaFileFilter.py
@@ -5,6 +5,7 @@
from hydrus.core import HydrusText
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
@@ -423,7 +424,7 @@ def ToStringWithCount( self, media_list: ClientMedia.MediaList, filter_counts: d
my_count = filter_counts[ self ]
- s += ' ({})'.format( HydrusData.ToHumanInt( my_count ) )
+ s += ' ({})'.format( HydrusNumbers.ToHumanInt( my_count ) )
if self.filter_type == FILE_FILTER_ALL:
diff --git a/hydrus/client/media/ClientMediaManagers.py b/hydrus/client/media/ClientMediaManagers.py
index f62a51a8c..19f218fc0 100644
--- a/hydrus/client/media/ClientMediaManagers.py
+++ b/hydrus/client/media/ClientMediaManagers.py
@@ -7,7 +7,7 @@
from hydrus.core import HydrusTags
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusTime
from hydrus.client import ClientConstants as CC
@@ -625,7 +625,7 @@ def GetPrettyViewsLine( self, canvas_types: typing.Collection[ int ] ) -> str:
last_viewed_string = 'last {}'.format( HydrusTime.TimestampToPrettyTimeDelta( HydrusTime.SecondiseMS( max( last_viewed_times_ms ) ) ) )
- return 'viewed {} times{}, totalling {}, {}'.format( HydrusData.ToHumanInt( views_total ), info_string, HydrusTime.TimeDeltaToPrettyTimeDelta( viewtime_total ), last_viewed_string )
+ return 'viewed {} times{}, totalling {}, {}'.format( HydrusNumbers.ToHumanInt( views_total ), info_string, HydrusTime.TimeDeltaToPrettyTimeDelta( viewtime_total ), last_viewed_string )
def GetTimesManager( self ) -> TimesManager:
diff --git a/hydrus/client/metadata/ClientContentUpdates.py b/hydrus/client/metadata/ClientContentUpdates.py
index 7ad0f9db3..87eba0d78 100644
--- a/hydrus/client/metadata/ClientContentUpdates.py
+++ b/hydrus/client/metadata/ClientContentUpdates.py
@@ -2,10 +2,8 @@
import typing
from hydrus.core import HydrusConstants as HC
-from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
-from hydrus.core import HydrusSerialisable
+from hydrus.core import HydrusNumbers
from hydrus.core.networking import HydrusNetwork
from hydrus.client import ClientGlobals as CG
@@ -387,7 +385,7 @@ def ToString( self ) -> str:
s += ', '.join( locations ) + '->'
- s += ', '.join( actions ) + extra_words + ' ' + HydrusData.ToHumanInt( num_files ) + ' files'
+ s += ', '.join( actions ) + extra_words + ' ' + HydrusNumbers.ToHumanInt( num_files ) + ' files'
return s
diff --git a/hydrus/client/networking/ClientNetworkingContexts.py b/hydrus/client/networking/ClientNetworkingContexts.py
index cbd937731..012b9308e 100644
--- a/hydrus/client/networking/ClientNetworkingContexts.py
+++ b/hydrus/client/networking/ClientNetworkingContexts.py
@@ -193,7 +193,14 @@ def ToString( self ):
else:
- name = str( self.context_data )
+ if isinstance( self.context_data, bytes ):
+
+ name = self.context_data.hex()
+
+ else:
+
+ name = str( self.context_data )
+
return CC.network_context_type_string_lookup[ self.context_type ] + ': ' + name
diff --git a/hydrus/client/networking/ClientNetworkingDomain.py b/hydrus/client/networking/ClientNetworkingDomain.py
index 935cf9033..9bc216436 100644
--- a/hydrus/client/networking/ClientNetworkingDomain.py
+++ b/hydrus/client/networking/ClientNetworkingDomain.py
@@ -7,6 +7,7 @@
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
from hydrus.core.networking import HydrusNetworking
@@ -255,7 +256,7 @@ def _GetNormalisedAPIURLClassAndURL( self, url ):
else:
- message = 'Could not find an API/Redirect URL Class for ' + url + ' as it and its API url classes linked in a loop of size ' + HydrusData.ToHumanInt( loop_size ) + '!'
+ message = 'Could not find an API/Redirect URL Class for ' + url + ' as it and its API url classes linked in a loop of size ' + HydrusNumbers.ToHumanInt( loop_size ) + '!'
raise HydrusExceptions.URLClassException( message )
diff --git a/hydrus/client/networking/ClientNetworkingJobs.py b/hydrus/client/networking/ClientNetworkingJobs.py
index f68d636e7..ea8879af5 100644
--- a/hydrus/client/networking/ClientNetworkingJobs.py
+++ b/hydrus/client/networking/ClientNetworkingJobs.py
@@ -1348,7 +1348,7 @@ def GetLoginNetworkContext( self ):
- def GetNetworkContexts( self ):
+ def GetNetworkContexts( self ) -> typing.List[ ClientNetworkingContexts.NetworkContext ]:
with self._lock:
@@ -1903,7 +1903,7 @@ def TokensOK( self ) -> bool:
if consumed:
- self._status_text = 'starting soon'
+ self._status_text = 'gallery token ok - starting soon'
self._gallery_token_consumed = True
diff --git a/hydrus/client/search/ClientSearch.py b/hydrus/client/search/ClientSearch.py
index 1e50b1243..127c59958 100644
--- a/hydrus/client/search/ClientSearch.py
+++ b/hydrus/client/search/ClientSearch.py
@@ -7,7 +7,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
@@ -560,7 +560,7 @@ def ToString( self, absolute_number_renderer: typing.Optional[ typing.Callable ]
if absolute_number_renderer is None:
- absolute_number_renderer = HydrusData.ToHumanInt
+ absolute_number_renderer = HydrusNumbers.ToHumanInt
result = f'{number_test_operator_to_str_lookup[ self.operator ]} {absolute_number_renderer( self.value )}'
@@ -1674,11 +1674,11 @@ def GetSuffixString( self ) -> str:
if self.min_current_count > 0 or self.max_current_count > 0:
- number_text = HydrusData.ToHumanInt( self.min_current_count )
+ number_text = HydrusNumbers.ToHumanInt( self.min_current_count )
if self.max_current_count > self.min_current_count:
- number_text = '{}-{}'.format( number_text, HydrusData.ToHumanInt( self.max_current_count ) )
+ number_text = '{}-{}'.format( number_text, HydrusNumbers.ToHumanInt( self.max_current_count ) )
suffix_components.append( '({})'.format( number_text ) )
@@ -1686,11 +1686,11 @@ def GetSuffixString( self ) -> str:
if self.min_pending_count > 0 or self.max_pending_count > 0:
- number_text = HydrusData.ToHumanInt( self.min_pending_count )
+ number_text = HydrusNumbers.ToHumanInt( self.min_pending_count )
if self.max_pending_count > self.min_pending_count:
- number_text = '{}-{}'.format( number_text, HydrusData.ToHumanInt( self.max_pending_count ) )
+ number_text = '{}-{}'.format( number_text, HydrusNumbers.ToHumanInt( self.max_pending_count ) )
suffix_components.append( '(+{})'.format( number_text ) )
@@ -2590,7 +2590,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_FRAMERATE:
- absolute_number_renderer = lambda s: f'{HydrusData.ToHumanInt(s)}fps'
+ absolute_number_renderer = lambda s: f'{HydrusNumbers.ToHumanInt(s)}fps'
base = 'framerate'
has_phrase = ': has framerate'
@@ -2708,7 +2708,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
base = 'number of {} tags'.format( ClientTags.RenderNamespaceForUser( namespace ) )
- base += ' {} {}'.format( operator, HydrusData.ToHumanInt( value ) )
+ base += ' {} {}'.format( operator, HydrusNumbers.ToHumanInt( value ) )
@@ -2758,7 +2758,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
value = self._value
- base += ' is ' + HydrusData.ToHumanInt( value )
+ base += ' is ' + HydrusNumbers.ToHumanInt( value )
elif self._predicate_type in ( PREDICATE_TYPE_SYSTEM_AGE, PREDICATE_TYPE_SYSTEM_LAST_VIEWED_TIME, PREDICATE_TYPE_SYSTEM_MODIFIED_TIME, PREDICATE_TYPE_SYSTEM_ARCHIVED_TIME ):
@@ -2799,7 +2799,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
if quantity > 0:
- str_component = '{} {}'.format( HydrusData.ToHumanInt( quantity ), label )
+ str_component = '{} {}'.format( HydrusNumbers.ToHumanInt( quantity ), label )
if quantity > 1:
@@ -3006,7 +3006,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
else:
- base = f'{base} {is_phrase} {HydrusData.ToHumanInt( len( hashes ) )} hashes'
+ base = f'{base} {is_phrase} {HydrusNumbers.ToHumanInt( len( hashes ) )} hashes'
@@ -3072,7 +3072,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
else:
- hash_string = f'{HydrusData.ToHumanInt( len( hashes ) )} files'
+ hash_string = f'{HydrusNumbers.ToHumanInt( len( hashes ) )} files'
base += f' {hash_string} with distance of {max_hamming}'
@@ -3098,12 +3098,12 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
if len( pixel_hashes ) > 0:
- components.append( f'{HydrusData.ToHumanInt( len( pixel_hashes ) )} pixel')
+ components.append( f'{HydrusNumbers.ToHumanInt( len( pixel_hashes ) )} pixel')
if len( perceptual_hashes ) > 0:
- components.append( f'{HydrusData.ToHumanInt( len( perceptual_hashes ) )} perceptual')
+ components.append( f'{HydrusNumbers.ToHumanInt( len( perceptual_hashes ) )} perceptual')
component_string = ', '.join( components )
@@ -3200,7 +3200,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
o_text = 'unknown'
- base = f'{base}: {n_text} {o_text} {HydrusData.ToHumanInt( num )}'
+ base = f'{base}: {n_text} {o_text} {HydrusNumbers.ToHumanInt( num )}'
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_COUNT:
@@ -3228,7 +3228,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
o_text = ' '
- base += ' - has' + o_text + HydrusData.ToHumanInt( num_relationships ) + ' ' + HC.duplicate_type_string_lookup[ dupe_type ]
+ base += ' - has' + o_text + HydrusNumbers.ToHumanInt( num_relationships ) + ' ' + HC.duplicate_type_string_lookup[ dupe_type ]
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_KING:
@@ -3281,7 +3281,7 @@ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_u
if view_type == 'views':
- value_string = HydrusData.ToHumanInt( viewing_value )
+ value_string = HydrusNumbers.ToHumanInt( viewing_value )
elif view_type == 'viewtime':
diff --git a/hydrus/core/HydrusConstants.py b/hydrus/core/HydrusConstants.py
index eec0cf2da..798cf0590 100644
--- a/hydrus/core/HydrusConstants.py
+++ b/hydrus/core/HydrusConstants.py
@@ -105,7 +105,7 @@
# Misc
NETWORK_VERSION = 20
-SOFTWARE_VERSION = 580
+SOFTWARE_VERSION = 581
CLIENT_API_VERSION = 65
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
diff --git a/hydrus/core/HydrusData.py b/hydrus/core/HydrusData.py
index e6bc98e29..32069bbc9 100644
--- a/hydrus/core/HydrusData.py
+++ b/hydrus/core/HydrusData.py
@@ -20,6 +20,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusText
def default_dict_list(): return collections.defaultdict( list )
@@ -111,99 +112,6 @@ def ConvertIntToPixels( i ):
elif i == 1000000: return 'megapixels'
else: return 'megapixels'
-def ConvertIndexToPrettyOrdinalString( index: int ):
-
- if index >= 0:
-
- return ConvertIntToPrettyOrdinalString( index + 1 )
-
- else:
-
- return ConvertIntToPrettyOrdinalString( index )
-
-
-def ConvertIntToPrettyOrdinalString( num: int ):
-
- if num == 0:
-
- return 'unknown position'
-
-
- tens = ( abs( num ) % 100 ) // 10
-
- if tens == 1:
-
- ordinal = 'th'
-
- else:
-
- remainder = abs( num ) % 10
-
- if remainder == 1:
-
- ordinal = 'st'
-
- elif remainder == 2:
-
- ordinal = 'nd'
-
- elif remainder == 3:
-
- ordinal = 'rd'
-
- else:
-
- ordinal = 'th'
-
-
-
- s = '{}{}'.format( ToHumanInt( abs( num ) ), ordinal )
-
- if num < 0:
-
- if num == -1:
-
- s = 'last'
-
- else:
-
- s = '{} from last'.format( s )
-
-
-
- return s
-
-
-def ConvertManyStringsToNiceInsertableHumanSummary( texts: typing.Collection[ str ], do_sort: bool = True ) -> str:
- """
- The purpose of this guy is to convert your list of 20 subscription names or whatever to something you can present to the user without making a giganto tall dialog.
- """
- texts = list( texts )
-
- if do_sort:
-
- HydrusText.SortStringsIgnoringCase( texts )
-
-
- if len( texts ) == 1:
-
- return f' "{texts[0]}" '
-
- else:
-
- if len( texts ) <= 4:
-
- t = '\n'.join( texts )
-
- else:
-
- t = ', '.join( texts )
-
-
- return f'\n\n{t}\n\n'
-
-
-
def ConvertIntToUnit( unit ):
if unit == 1: return 'B'
@@ -270,7 +178,7 @@ def ConvertResolutionToPrettyString( resolution ):
( width, height ) = resolution
- return '{}x{}'.format( ToHumanInt( width ), ToHumanInt( height ) )
+ return '{}x{}'.format( HydrusNumbers.ToHumanInt( width ), HydrusNumbers.ToHumanInt( height ) )
def ConvertStatusToPrefix( status ):
@@ -313,7 +221,7 @@ def ConvertValueRangeToBytes( value, range ):
def ConvertValueRangeToPrettyString( value, range ):
- return ToHumanInt( value ) + '/' + ToHumanInt( range )
+ return HydrusNumbers.ToHumanInt( value ) + '/' + HydrusNumbers.ToHumanInt( range )
def DebugPrint( debug_info ):
@@ -1211,7 +1119,7 @@ def BaseToHumanBytes( size, sig_figs = 3 ):
if size < 1024:
- return ToHumanInt( size ) + 'B'
+ return HydrusNumbers.ToHumanInt( size ) + 'B'
suffixes = ( '', 'K', 'M', 'G', 'T', 'P' )
@@ -1270,18 +1178,6 @@ def BaseToHumanBytes( size, sig_figs = 3 ):
ToHumanBytes = BaseToHumanBytes
-def ToHumanInt( num ):
-
- num = int( num )
-
- # this got stomped on by mpv, which resets locale
- #text = locale.format_string( '%d', num, grouping = True )
-
- text = '{:,}'.format( num )
-
- return text
-
-
class HydrusYAMLBase( yaml.YAMLObject ):
yaml_loader = yaml.SafeLoader
diff --git a/hydrus/core/HydrusNumbers.py b/hydrus/core/HydrusNumbers.py
new file mode 100644
index 000000000..302e631d1
--- /dev/null
+++ b/hydrus/core/HydrusNumbers.py
@@ -0,0 +1,75 @@
+def ConvertIndexToPrettyOrdinalString( index: int ):
+
+ if index >= 0:
+
+ return ConvertIntToPrettyOrdinalString( index + 1 )
+
+ else:
+
+ return ConvertIntToPrettyOrdinalString( index )
+
+
+
+def ConvertIntToPrettyOrdinalString( num: int ):
+
+ if num == 0:
+
+ return 'unknown position'
+
+
+ tens = ( abs( num ) % 100 ) // 10
+
+ if tens == 1:
+
+ ordinal = 'th'
+
+ else:
+
+ remainder = abs( num ) % 10
+
+ if remainder == 1:
+
+ ordinal = 'st'
+
+ elif remainder == 2:
+
+ ordinal = 'nd'
+
+ elif remainder == 3:
+
+ ordinal = 'rd'
+
+ else:
+
+ ordinal = 'th'
+
+
+
+ s = '{}{}'.format( ToHumanInt( abs( num ) ), ordinal )
+
+ if num < 0:
+
+ if num == -1:
+
+ s = 'last'
+
+ else:
+
+ s = '{} from last'.format( s )
+
+
+
+ return s
+
+
+def ToHumanInt( num ):
+
+ num = int( num )
+
+ # this got stomped on by mpv, which resets locale
+ #text = locale.format_string( '%d', num, grouping = True )
+
+ text = '{:,}'.format( num )
+
+ return text
+
diff --git a/hydrus/core/HydrusTags.py b/hydrus/core/HydrusTags.py
index 973cd4abe..313d5d585 100644
--- a/hydrus/core/HydrusTags.py
+++ b/hydrus/core/HydrusTags.py
@@ -6,65 +6,9 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusSerialisable
-from hydrus.core import HydrusData
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusText
-def CensorshipMatch( tag, censorships ):
-
- for censorship in censorships:
-
- if censorship == '': # '' - all non namespaced tags
-
- ( namespace, subtag ) = SplitTag( tag )
-
- if namespace == '':
-
- return True
-
-
- elif censorship == ':': # ':' - all namespaced tags
-
- ( namespace, subtag ) = SplitTag( tag )
-
- if namespace != '':
-
- return True
-
-
- elif ':' in censorship:
-
- if censorship.endswith( ':' ): # 'series:' - namespaced tags
-
- ( namespace, subtag ) = SplitTag( tag )
-
- if namespace == censorship[:-1]:
-
- return True
-
-
- else: # 'series:evangelion' - exact match with namespace
-
- if tag == censorship:
-
- return True
-
-
-
- else:
-
- # 'table' - normal tag, or namespaced version of same
-
- ( namespace, subtag ) = SplitTag( tag )
-
- if subtag == censorship:
-
- return True
-
-
-
-
- return False
-
def CollapseMultipleSortedNumericTagsToMinMax( tags ):
if len( tags ) <= 2:
@@ -292,7 +236,7 @@ def ConvertTagSliceToPrettyString( tag_slice ):
return 'namespaced tags'
- elif tag_slice.count( ':' ) == 1 and tag_slice.endswith( ':' ):
+ elif IsNamespaceTagSlice( tag_slice ):
namespace = tag_slice[ : -1 ]
@@ -304,6 +248,17 @@ def ConvertTagSliceToPrettyString( tag_slice ):
+ALL_UNNAMESPACED_TAG_SLICE = ''
+ALL_NAMESPACED_TAG_SLICE = ':'
+
+CORE_TAG_SLICES = { ALL_UNNAMESPACED_TAG_SLICE, ALL_NAMESPACED_TAG_SLICE }
+
+def IsNamespaceTagSlice( tag_slice: str ):
+
+ # careful about the [-1] here! it works, but only because of the '' check in core_tag_slices
+ return tag_slice not in CORE_TAG_SLICES and tag_slice[-1] == ':' and tag_slice.count( ':' ) == 1
+
+
def IsUnnamespaced( tag ):
return SplitTag( tag )[0] == ''
@@ -371,6 +326,14 @@ def __init__( self ):
HydrusSerialisable.SerialisableBase.__init__( self )
+ # TODO: update this guy to more carefully navigate how it does advanced filters
+ # we want to support both:
+ # "all namespaces, except blocking creator:, but allowing creator:yo"
+ # "no namespaces, except allowing creator:, but still blocking creator:yo"
+ # the 'all namespaces' applies first, then namespaces, then tags
+ # I updated _TagOK to basically reflect this, but these cached values and whether the 'all' white/blacklist rule actually exists or is implicit is all fuzzy, so clean it up!
+ # also the UI should display this properly, atm it just does Case 1
+
self._lock = threading.Lock()
self._tag_slices_to_rules = {}
@@ -385,7 +348,7 @@ def __init__( self ):
self._namespaces_blacklist = set()
self._tags_blacklist = set()
- self._namespaced_interesting = False
+ self._namespaces_interesting = False
self._tags_interesting = False
@@ -437,9 +400,8 @@ def _InitialiseFromSerialisableInfo( self, serialisable_info ):
def _TagOK( self, tag, apply_unnamespaced_rules_to_namespaced_tags = False ):
- # since this is called a whole bunch and overhead piles up, we are now splaying the logic out to hardcoded tests
-
- blacklist_encountered = False
+ # this is called a whole bunch and overhead piles up, so try to splay the logic out to hardcoded tests
+ # we handle exceptions by testing tags before namespaces and namespaces before all namespaces
if self._tags_interesting:
@@ -450,64 +412,69 @@ def _TagOK( self, tag, apply_unnamespaced_rules_to_namespaced_tags = False ):
if tag in self._tags_blacklist:
- blacklist_encountered = True
+ return False
+
+
+ if apply_unnamespaced_rules_to_namespaced_tags:
+
+ ( namespace, subtag ) = SplitTag( tag )
+
+ if namespace != '':
+
+ if subtag in self._tags_whitelist:
+
+ return True
+
+
+ if subtag in self._tags_blacklist:
+
+ return False
+
+
- if self._namespaced_interesting or apply_unnamespaced_rules_to_namespaced_tags:
+ if self._namespaces_interesting:
( namespace, subtag ) = SplitTag( tag )
- if apply_unnamespaced_rules_to_namespaced_tags and self._tags_interesting and subtag != tag:
+ if namespace == '':
- if subtag in self._tags_whitelist:
+ if self._all_unnamespaced_whitelisted:
return True
- if subtag in self._tags_blacklist:
+ if self._all_unnamespaced_blacklisted:
- blacklist_encountered = True
+ return False
-
- if self._namespaced_interesting:
+ else:
- if namespace == '':
+ if namespace in self._namespaces_whitelist:
- if self._all_unnamespaced_whitelisted:
-
- return True
-
+ return True
- if self._all_unnamespaced_blacklisted:
-
- blacklist_encountered = True
-
+
+ if namespace in self._namespaces_blacklist:
- else:
+ return False
- if self._all_namespaced_whitelisted or namespace in self._namespaces_whitelist:
-
- return True
-
+
+ if self._all_namespaced_whitelisted:
- if self._all_namespaced_blacklisted or namespace in self._namespaces_blacklist:
-
- blacklist_encountered = True
-
+ return True
+
+
+ if self._all_namespaced_blacklisted:
+
+ return False
- if blacklist_encountered: # rule against and no exceptions
-
- return False
-
- else:
-
- return True # no rules against or explicitly for, so permitted
-
+ return True # no rules against or explicitly for, so permitted
def _UpdateRuleCache( self ):
@@ -522,7 +489,7 @@ def _UpdateRuleCache( self ):
self._namespaces_blacklist = set()
self._tags_blacklist = set()
- self._namespaced_interesting = False
+ self._namespaces_interesting = False
self._tags_interesting = False
for ( tag_slice, rule ) in self._tag_slices_to_rules.items():
@@ -538,7 +505,7 @@ def _UpdateRuleCache( self ):
self._all_unnamespaced_blacklisted = True
- self._namespaced_interesting = True
+ self._namespaces_interesting = True # yes, the namespace of a tag matters to the outcome
elif tag_slice == ':':
@@ -551,9 +518,9 @@ def _UpdateRuleCache( self ):
self._all_namespaced_blacklisted = True
- self._namespaced_interesting = True
+ self._namespaces_interesting = True # yes, the namespace of a tag matters to the outcome
- elif tag_slice.count( ':' ) == 1 and tag_slice.endswith( ':' ):
+ elif IsNamespaceTagSlice( tag_slice ):
if rule == HC.FILTER_WHITELIST:
@@ -564,7 +531,7 @@ def _UpdateRuleCache( self ):
self._namespaces_blacklist.add( tag_slice[:-1] )
- self._namespaced_interesting = True
+ self._namespaces_interesting = True
else:
@@ -612,7 +579,7 @@ def CleanRules( self ):
pass
- elif tag_slice.count( ':' ) == 1 and tag_slice.endswith( ':' ):
+ elif IsNamespaceTagSlice( tag_slice ):
example_tag = tag_slice + 'example'
@@ -680,7 +647,7 @@ def GetChangesSummaryText( self, old_tag_filter: "TagFilter" ):
if len( new_rules ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
- summary_components.append( 'Added {} rules'.format( HydrusData.ToHumanInt( len( new_rules ) ) ) )
+ summary_components.append( 'Added {} rules'.format( HydrusNumbers.ToHumanInt( len( new_rules ) ) ) )
else:
@@ -694,7 +661,7 @@ def GetChangesSummaryText( self, old_tag_filter: "TagFilter" ):
if len( new_rules ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
- summary_components.append( 'Changed {} rules'.format( HydrusData.ToHumanInt( len( new_rules ) ) ) )
+ summary_components.append( 'Changed {} rules'.format( HydrusNumbers.ToHumanInt( len( new_rules ) ) ) )
else:
@@ -708,7 +675,7 @@ def GetChangesSummaryText( self, old_tag_filter: "TagFilter" ):
if len( new_rules ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
- summary_components.append( 'Deleted {} rules'.format( HydrusData.ToHumanInt( len( new_rules ) ) ) )
+ summary_components.append( 'Deleted {} rules'.format( HydrusNumbers.ToHumanInt( len( new_rules ) ) ) )
else:
@@ -721,6 +688,26 @@ def GetChangesSummaryText( self, old_tag_filter: "TagFilter" ):
return '\n'.join( summary_components )
+ def GetInvertedFilter( self ) -> "TagFilter":
+
+ inverted_tag_filter = TagFilter()
+
+ if '' not in self._tag_slices_to_rules:
+
+ inverted_tag_filter.SetRule( '', HC.FILTER_BLACKLIST )
+
+
+ if ':' not in self._tag_slices_to_rules:
+
+ inverted_tag_filter.SetRule( ':', HC.FILTER_BLACKLIST )
+
+
+ inverted_tag_filter.SetRules( [ tag_slice for ( tag_slice, rule ) in self._tag_slices_to_rules.items() if rule == HC.FILTER_BLACKLIST ], HC.FILTER_WHITELIST )
+ inverted_tag_filter.SetRules( [ tag_slice for ( tag_slice, rule ) in self._tag_slices_to_rules.items() if rule == HC.FILTER_WHITELIST ], HC.FILTER_BLACKLIST )
+
+ return inverted_tag_filter
+
+
def GetTagSlicesToRules( self ):
with self._lock:
@@ -743,6 +730,10 @@ def SetRules( self, tag_slices, rule ):
self._tag_slices_to_rules[ tag_slice ] = rule
+ # TODO: do a thing here that says 'if we have a whitelist rule for something that would be allowed without the exception, then remove the whitelist'
+ # maybe in the updaterulecache, purgesurpluswhitelist, something like that
+ # and figure out how we _actually_ want to do ''/':' whitelist/blacklist
+
self._UpdateRuleCache()
@@ -791,7 +782,7 @@ def ToBlacklistString( self ):
if len( blacklist ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
- text = 'blacklisting on {} rules'.format( HydrusData.ToHumanInt( len( blacklist ) ) )
+ text = 'blacklisting on {} rules'.format( HydrusNumbers.ToHumanInt( len( blacklist ) ) )
else:
@@ -803,7 +794,7 @@ def ToBlacklistString( self ):
if len( whitelist ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
- text += ' except {} other rules'.format( HydrusData.ToHumanInt( len( whitelist ) ) )
+ text += ' except {} other rules'.format( HydrusNumbers.ToHumanInt( len( whitelist ) ) )
else:
@@ -816,14 +807,16 @@ def ToBlacklistString( self ):
- def ToCensoredString( self ):
+ def ToPermittedString( self ):
+
+ # TODO: Could make use of a modified version of the new `HydrusText.ConvertManyStringsToNiceInsertableHumanSummarySingleLine()` here, rather than WOAH_TOO_MANY_RULES_THRESHOLD
with self._lock:
blacklist = []
whitelist = []
- for ( tag_slice, rule ) in list(self._tag_slices_to_rules.items()):
+ for ( tag_slice, rule ) in self._tag_slices_to_rules.items():
if rule == HC.FILTER_BLACKLIST:
@@ -835,135 +828,98 @@ def ToCensoredString( self ):
- blacklist.sort()
- whitelist.sort()
+ blacklist_set = set( blacklist )
+ functional_blacklist_set = blacklist_set.difference( { '', ':' } )
+
+ whitelist_set = set( whitelist )
+ functional_whitelist_set = whitelist_set.difference( { '', ':' } )
if len( blacklist ) == 0:
- return 'all tags allowed'
+ return 'allowing all tags'
else:
- if set( blacklist ) == { '', ':' }:
-
- text = 'no tags allowed'
+ if blacklist_set.issuperset( { '', ':' } ):
- else:
-
- if len( blacklist ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
+ if len( whitelist ) == 0:
- text = 'all but {} rules allowed'.format( HydrusData.ToHumanInt( len( blacklist ) ) )
+ text = 'not allowing any tags'
else:
- text = 'all but ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in blacklist ) ) + ' allowed'
+ if len( whitelist ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
+
+ text = 'only allowing on {} rules'.format( HydrusNumbers.ToHumanInt( len( whitelist ) ) )
+
+ else:
+
+ text = 'only allowing ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in sorted( whitelist ) ) )
+
-
-
- if len( whitelist ) > 0:
-
- if len( whitelist ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
-
- text += ' except for {} other rules'.format( HydrusData.ToHumanInt( len( whitelist ) ) )
-
- else:
-
- text += ' except ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in whitelist ) )
+ if len( functional_blacklist_set ) > 0:
+
+ if len( functional_blacklist_set ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
+
+ text += ' while still disllowing {} rules'.format( HydrusNumbers.ToHumanInt( len( functional_blacklist_set ) ) )
+
+ else:
+
+ text += ' while still disallowing ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in sorted( functional_blacklist_set ) ) )
+
+
-
- return text
-
-
-
-
- def ToPermittedString( self ):
-
- with self._lock:
-
- blacklist = []
- whitelist = []
-
- for ( tag_slice, rule ) in self._tag_slices_to_rules.items():
-
- if rule == HC.FILTER_BLACKLIST:
-
- blacklist.append( tag_slice )
-
- elif rule == HC.FILTER_WHITELIST:
+ elif '' in blacklist_set or ':' in blacklist_set:
- whitelist.append( tag_slice )
-
-
-
- blacklist.sort()
- whitelist.sort()
-
- if len( blacklist ) == 0:
-
- return 'all tags'
-
- else:
-
- if set( blacklist ) == { '', ':' }:
-
- if len( whitelist ) == 0:
-
- text = 'no tags'
-
- elif len( whitelist ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
+ if '' in blacklist_set:
- text = '{} rules that allow'.format( HydrusData.ToHumanInt( len( whitelist ) ) )
+ text = 'allowing all namespaced tags'
else:
- text = 'only ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in whitelist ) )
+ text = 'allowing all unnamespaced tags'
- elif set( blacklist ) == { '' }:
-
- text = 'all namespaced tags'
-
if len( whitelist ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
- text += ' and {} other rules'.format( HydrusData.ToHumanInt( len( whitelist ) ) )
+ text += ' and {} other rules'.format( HydrusNumbers.ToHumanInt( len( functional_whitelist_set ) ) )
elif len( whitelist ) > 0:
- text += ' and ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in whitelist ) )
+ text += ' and ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in sorted( functional_whitelist_set ) ) )
- elif set( blacklist ) == { ':' }:
-
- text = 'all unnamespaced tags'
-
- if len( whitelist ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
+ if len( functional_blacklist_set ) > 0:
- text += ' and {} other rules'.format( HydrusData.ToHumanInt( len( whitelist ) ) )
-
- elif len( whitelist ) > 0:
-
- text += ' and ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in whitelist ) )
+ if len( functional_blacklist_set ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
+
+ text += ' while still disllowing {} rules'.format( HydrusNumbers.ToHumanInt( len( functional_blacklist_set ) ) )
+
+ else:
+
+ text += ' while still disallowing ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in sorted( functional_blacklist_set ) ) )
+
else:
if len( blacklist ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
- text = 'all tags except {} other rules'.format( HydrusData.ToHumanInt( len( blacklist ) ) )
+ text = 'allowing all tags except on {} rules'.format( HydrusNumbers.ToHumanInt( len( blacklist ) ) )
else:
- text = 'all tags except ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in blacklist ) )
+ text = 'allowing all tags except ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in sorted( blacklist ) ) )
- if len( whitelist ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
+ if len( functional_whitelist_set ) > self.WOAH_TOO_MANY_RULES_THRESHOLD:
- text += ' (except {} other rules)'.format( HydrusData.ToHumanInt( len( whitelist ) ) )
+ text += ' while still allowing {} rules'.format( HydrusNumbers.ToHumanInt( len( functional_whitelist_set ) ) )
- elif len( whitelist ) > 0:
+ elif len( functional_whitelist_set ) > 0:
- text += ' (except ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in whitelist ) ) + ')'
+ text += ' while still allowing ' + ', '.join( ( ConvertTagSliceToPrettyString( tag_slice ) for tag_slice in sorted( functional_whitelist_set ) ) )
diff --git a/hydrus/core/HydrusText.py b/hydrus/core/HydrusText.py
index 915e736a2..affbbe2a6 100644
--- a/hydrus/core/HydrusText.py
+++ b/hydrus/core/HydrusText.py
@@ -16,6 +16,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusNumbers
re_one_or_more_whitespace = re.compile( r'\s+' ) # this does \t and friends too
# want to keep the 'leading space' part here, despite tag.strip() elsewhere, in case of some crazy '- test' tag
@@ -58,6 +59,92 @@ def CleanNoteText( t: str ):
return t
+def ConvertManyStringsToNiceInsertableHumanSummary( texts: typing.Collection[ str ], do_sort: bool = True ) -> str:
+ """
+ The purpose of this guy is to convert your list of 20 subscription names or whatever to something you can present to the user without making a giganto tall dialog.
+ """
+ texts = list( texts )
+
+ if do_sort:
+
+ SortStringsIgnoringCase( texts )
+
+
+ if len( texts ) == 1:
+
+ return f' "{texts[0]}" '
+
+ else:
+
+ if len( texts ) <= 4:
+
+ t = '\n'.join( texts )
+
+ else:
+
+ t = ', '.join( texts )
+
+
+ return f'\n\n{t}\n\n'
+
+
+
+def ConvertManyStringsToNiceInsertableHumanSummarySingleLine( texts: typing.Collection[ str ], collective_description_noun: str, do_sort: bool = True ) -> str:
+ """
+ The purpose of this guy is to convert your list of 20 subscription names or whatever to something you can present to the user without making a giganto tall dialog.
+ Suitable for a menu!
+ """
+ if len( texts ) == 0:
+
+ return f'0(?) {collective_description_noun}'
+
+
+ texts = list( texts )
+
+ if do_sort:
+
+ SortStringsIgnoringCase( texts )
+
+
+ LINE_NO_LONGER_THAN = 48
+
+ if len( texts ) == 1:
+
+ text = texts[0]
+
+ if len( text ) + 2 > LINE_NO_LONGER_THAN:
+
+ return f'1 {collective_description_noun}'
+
+ else:
+
+ return f'"{text}"'
+
+
+ else:
+
+ if sum( ( len( text ) + 4 for text in texts ) ) > LINE_NO_LONGER_THAN:
+
+ first_text = texts[0]
+
+ possible = f'"{first_text}" & {HydrusNumbers.ToHumanInt(len(texts)-1)} other {collective_description_noun}'
+
+ if len( possible ) <= LINE_NO_LONGER_THAN:
+
+ return possible
+
+ else:
+
+ return f'{HydrusNumbers.ToHumanInt(len(texts))} {collective_description_noun}'
+
+
+ else:
+
+ return ', '.join( ( f'"{text}"' for text in texts ) )
+
+
+
+
def HexFilter( text ):
text = text.lower()
@@ -307,7 +394,7 @@ def RemoveNewlines( text: str ) -> str:
return text
-def SortStringsIgnoringCase( list_of_strings ):
+def SortStringsIgnoringCase( list_of_strings: typing.List[ str ] ):
list_of_strings.sort( key = lambda s: s.lower() )
diff --git a/hydrus/core/HydrusThreading.py b/hydrus/core/HydrusThreading.py
index e43419bb1..11877ca15 100644
--- a/hydrus/core/HydrusThreading.py
+++ b/hydrus/core/HydrusThreading.py
@@ -10,6 +10,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusProfiling
from hydrus.core import HydrusTime
from hydrus.core.interfaces import HydrusThreadingInterface
@@ -638,7 +639,7 @@ def GetCurrentJobSummary( self ) -> str:
with self._waiting_lock:
- return HydrusData.ToHumanInt( len( self._waiting ) ) + ' jobs'
+ return HydrusNumbers.ToHumanInt( len( self._waiting ) ) + ' jobs'
@@ -658,7 +659,7 @@ def GetPrettyJobSummary( self ) -> str:
job_lines = [ repr( job ) for job in self._waiting ]
- lines = [ HydrusData.ToHumanInt( num_jobs ) + ' jobs:' ] + job_lines
+ lines = [ HydrusNumbers.ToHumanInt( num_jobs ) + ' jobs:' ] + job_lines
text = '\n'.join( lines )
@@ -1043,7 +1044,7 @@ def WaitForProcessToFinish( p, timeout ):
p.kill()
- raise Exception( 'Process did not finish within ' + HydrusData.ToHumanInt( timeout ) + ' seconds!' )
+ raise Exception( 'Process did not finish within ' + HydrusNumbers.ToHumanInt( timeout ) + ' seconds!' )
time.sleep( 2 )
diff --git a/hydrus/core/HydrusTime.py b/hydrus/core/HydrusTime.py
index 6b5a454b5..af5a1b0e0 100644
--- a/hydrus/core/HydrusTime.py
+++ b/hydrus/core/HydrusTime.py
@@ -3,8 +3,9 @@
import time
import typing
-from hydrus.core import HydrusData
from hydrus.core import HydrusConstants as HC
+from hydrus.core import HydrusData
+from hydrus.core import HydrusNumbers
def DateTimeToPrettyTime( dt: datetime.datetime, include_24h_time = True, include_milliseconds = False ):
@@ -297,7 +298,7 @@ def TimeDeltaToPrettyTimeDelta( seconds: float, show_seconds = True, no_bigger_t
if time_quantity > 0:
- s = HydrusData.ToHumanInt( time_quantity ) + ' ' + time_string
+ s = HydrusNumbers.ToHumanInt( time_quantity ) + ' ' + time_string
if time_quantity > 1:
@@ -326,7 +327,7 @@ def TimeDeltaToPrettyTimeDelta( seconds: float, show_seconds = True, no_bigger_t
if int( seconds ) == seconds:
- result = HydrusData.ToHumanInt( seconds ) + ' seconds'
+ result = HydrusNumbers.ToHumanInt( seconds ) + ' seconds'
else:
@@ -513,7 +514,7 @@ def MillisecondsDurationToPrettyTime( duration_ms: typing.Optional[ int ] ) -> s
if int( detailed_seconds ) == detailed_seconds:
- detailed_seconds_result = f'{HydrusData.ToHumanInt( detailed_seconds )} seconds'
+ detailed_seconds_result = f'{HydrusNumbers.ToHumanInt( detailed_seconds )} seconds'
else:
diff --git a/hydrus/core/networking/HydrusNetwork.py b/hydrus/core/networking/HydrusNetwork.py
index 3df57bb08..1a13a9e37 100644
--- a/hydrus/core/networking/HydrusNetwork.py
+++ b/hydrus/core/networking/HydrusNetwork.py
@@ -8,6 +8,7 @@
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusTime
@@ -1485,7 +1486,7 @@ def ToString( self ):
hashes = self._content_data
- text = 'FILES: ' + HydrusData.ToHumanInt( len( hashes ) ) + ' files'
+ text = 'FILES: ' + HydrusNumbers.ToHumanInt( len( hashes ) ) + ' files'
elif self._content_type == HC.CONTENT_TYPE_MAPPING:
@@ -1497,7 +1498,7 @@ def ToString( self ):
( tag, hashes ) = self._content_data
- text = 'MAPPINGS: ' + tag + ' for ' + HydrusData.ToHumanInt( len( hashes ) ) + ' files'
+ text = 'MAPPINGS: ' + tag + ' for ' + HydrusNumbers.ToHumanInt( len( hashes ) ) + ' files'
elif self._content_type == HC.CONTENT_TYPE_TAG_PARENTS:
@@ -2565,11 +2566,11 @@ def GetContentSummary( self ) -> str:
if self._petition_header.content_type == HC.CONTENT_TYPE_MAPPINGS and num_sub_petitions > 1:
- return '{} mappings in {} petitions'.format( HydrusData.ToHumanInt( self.GetActualContentWeight() ), HydrusData.ToHumanInt( num_sub_petitions ) )
+ return '{} mappings in {} petitions'.format( HydrusNumbers.ToHumanInt( self.GetActualContentWeight() ), HydrusNumbers.ToHumanInt( num_sub_petitions ) )
else:
- return '{} {}'.format( HydrusData.ToHumanInt( self.GetActualContentWeight() ), HC.content_type_string_lookup[ self._petition_header.content_type ] )
+ return '{} {}'.format( HydrusNumbers.ToHumanInt( self.GetActualContentWeight() ), HC.content_type_string_lookup[ self._petition_header.content_type ] )
diff --git a/hydrus/core/networking/HydrusNetworking.py b/hydrus/core/networking/HydrusNetworking.py
index 7cf220ef2..c3f1aa1a0 100644
--- a/hydrus/core/networking/HydrusNetworking.py
+++ b/hydrus/core/networking/HydrusNetworking.py
@@ -12,6 +12,7 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
@@ -33,7 +34,7 @@ def ConvertBandwidthRuleToString( rule ):
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
- s = HydrusData.ToHumanInt( max_allowed ) + ' rqs'
+ s = HydrusNumbers.ToHumanInt( max_allowed ) + ' rqs'
if time_delta is None:
@@ -599,7 +600,7 @@ def GetCurrentMonthSummary( self ):
num_bytes = self._GetUsage( HC.BANDWIDTH_TYPE_DATA, None, True )
num_requests = self._GetUsage( HC.BANDWIDTH_TYPE_REQUESTS, None, True )
- return 'used ' + HydrusData.ToHumanBytes( num_bytes ) + ' in ' + HydrusData.ToHumanInt( num_requests ) + ' requests this month'
+ return 'used ' + HydrusData.ToHumanBytes( num_bytes ) + ' in ' + HydrusNumbers.ToHumanInt( num_requests ) + ' requests this month'
diff --git a/hydrus/server/ServerController.py b/hydrus/server/ServerController.py
index 5ebf5b6f5..37ce5dca5 100644
--- a/hydrus/server/ServerController.py
+++ b/hydrus/server/ServerController.py
@@ -11,6 +11,7 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSessions
from hydrus.core import HydrusThreading
@@ -245,7 +246,7 @@ def DoDeferredPhysicalDeletes( self ):
if num_files_deleted > 0 or num_thumbnails_deleted > 0:
- HydrusData.Print( 'Physically deleted {} files and {} thumbnails from file storage.'.format( HydrusData.ToHumanInt( num_files_deleted ), HydrusData.ToHumanInt( num_files_deleted ) ) )
+ HydrusData.Print( 'Physically deleted {} files and {} thumbnails from file storage.'.format( HydrusNumbers.ToHumanInt( num_files_deleted ), HydrusNumbers.ToHumanInt( num_files_deleted ) ) )
diff --git a/hydrus/server/ServerDB.py b/hydrus/server/ServerDB.py
index b14b715be..b5a775530 100644
--- a/hydrus/server/ServerDB.py
+++ b/hydrus/server/ServerDB.py
@@ -14,6 +14,7 @@
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusLists
+from hydrus.core import HydrusNumbers
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
@@ -2128,7 +2129,7 @@ def _RepositoryCreateUpdate( self, service_key, begin, end ):
- HydrusData.Print( 'Update OK. ' + HydrusData.ToHumanInt( total_definition_rows ) + ' definition rows and ' + HydrusData.ToHumanInt( total_content_rows ) + ' content rows in ' + HydrusData.ToHumanInt( len( updates ) ) + ' update files.' )
+ HydrusData.Print( 'Update OK. ' + HydrusNumbers.ToHumanInt( total_definition_rows ) + ' definition rows and ' + HydrusNumbers.ToHumanInt( total_content_rows ) + ' content rows in ' + HydrusNumbers.ToHumanInt( len( updates ) ) + ' update files.' )
return update_hashes
@@ -4709,7 +4710,7 @@ def _RepositoryRegenerateServiceInfoSpecific( self, service_id: int, info_types:
raise Exception( 'Was asked to generate service info for an unknown type: {}'.format( info_type ) )
- HydrusData.Print( 'Regenerated a service info number: {} - {} - {}'.format( service_name, HC.service_info_enum_str_lookup[ info_type ], HydrusData.ToHumanInt( info ) ) )
+ HydrusData.Print( 'Regenerated a service info number: {} - {} - {}'.format( service_name, HC.service_info_enum_str_lookup[ info_type ], HydrusNumbers.ToHumanInt( info ) ) )
self._Execute( 'INSERT OR IGNORE INTO service_info ( service_id, info_type, info ) VALUES ( ?, ?, ? )', ( service_id, info_type, info ) )
diff --git a/hydrus/test/TestHydrusData.py b/hydrus/test/TestHydrusData.py
index efa20d913..5df8e6595 100644
--- a/hydrus/test/TestHydrusData.py
+++ b/hydrus/test/TestHydrusData.py
@@ -1,21 +1,21 @@
import unittest
-from hydrus.core import HydrusData
+from hydrus.core import HydrusNumbers
class TestHydrusData( unittest.TestCase ):
def test_ordinals( self ):
- self.assertEqual( HydrusData.ConvertIntToPrettyOrdinalString( 1 ), '1st' )
- self.assertEqual( HydrusData.ConvertIntToPrettyOrdinalString( 2 ), '2nd' )
- self.assertEqual( HydrusData.ConvertIntToPrettyOrdinalString( 3 ), '3rd' )
- self.assertEqual( HydrusData.ConvertIntToPrettyOrdinalString( 4 ), '4th' )
+ self.assertEqual( HydrusNumbers.ConvertIntToPrettyOrdinalString( 1 ), '1st' )
+ self.assertEqual( HydrusNumbers.ConvertIntToPrettyOrdinalString( 2 ), '2nd' )
+ self.assertEqual( HydrusNumbers.ConvertIntToPrettyOrdinalString( 3 ), '3rd' )
+ self.assertEqual( HydrusNumbers.ConvertIntToPrettyOrdinalString( 4 ), '4th' )
- self.assertEqual( HydrusData.ConvertIntToPrettyOrdinalString( 11 ), '11th' )
- self.assertEqual( HydrusData.ConvertIntToPrettyOrdinalString( 12 ), '12th' )
+ self.assertEqual( HydrusNumbers.ConvertIntToPrettyOrdinalString( 11 ), '11th' )
+ self.assertEqual( HydrusNumbers.ConvertIntToPrettyOrdinalString( 12 ), '12th' )
- self.assertEqual( HydrusData.ConvertIntToPrettyOrdinalString( 213 ), '213th' )
+ self.assertEqual( HydrusNumbers.ConvertIntToPrettyOrdinalString( 213 ), '213th' )
- self.assertEqual( HydrusData.ConvertIntToPrettyOrdinalString( 1011 ), '1,011th' )
+ self.assertEqual( HydrusNumbers.ConvertIntToPrettyOrdinalString( 1011 ), '1,011th' )
diff --git a/static/default/parsers/rule34.paheal gallery page parser.png b/static/default/parsers/rule34.paheal gallery page parser.png
deleted file mode 100644
index 0c2484fea..000000000
Binary files a/static/default/parsers/rule34.paheal gallery page parser.png and /dev/null differ
diff --git a/static/default/parsers/rule34hentai gallery page parser.png b/static/default/parsers/rule34hentai gallery page parser.png
deleted file mode 100644
index d0d2b3644..000000000
Binary files a/static/default/parsers/rule34hentai gallery page parser.png and /dev/null differ
diff --git a/static/default/parsers/shimmie file page parser.png b/static/default/parsers/shimmie file page parser.png
index 14ba0b164..a6ad2b199 100644
Binary files a/static/default/parsers/shimmie file page parser.png and b/static/default/parsers/shimmie file page parser.png differ
diff --git a/static/default/parsers/shimmie gallery page parser.png b/static/default/parsers/shimmie gallery page parser.png
new file mode 100644
index 000000000..e8ba69d6f
Binary files /dev/null and b/static/default/parsers/shimmie gallery page parser.png differ
diff --git a/static/qss/Dark_Blue 1.1.qss b/static/qss/Dark_Blue 1.1.qss
new file mode 100644
index 000000000..3ed9ecc72
--- /dev/null
+++ b/static/qss/Dark_Blue 1.1.qss
@@ -0,0 +1,461 @@
+/*
+Dark Blue: A Dark Blue theme for Hydrus Network by B1N4RYJ4N
+Version..: 1.1
+
+Changelog for 1.1:
+- Added New Style Block for HydrusAnimationBar
+- QScrollBar increased width and a new handle color
+- Added some color properties for QTextEdit, QTextEdit#HydrusValid and QTextEdit#HydrusIndeterminate
+
+To achieve the intended results you must:
+
+1. Activate dark mode
+2. adjust the Qt style to Fusion
+3. adjust the Qt stylesheet to Dark_Blue
+4. adjust the current colourset under files > options > colors > current colourset to darkmode
+5. adjust your color values under files > options > colors > darkmode like so:
+
+ thumbnail background normal..: #1e1e1e
+ thumbnail background selected: #007acc
+ thumbnail border normal......: #569cd6
+ thumbnail border selected....: #cccccc
+ thumbnail grid background....: #1e1e1e
+ autocomplete background......: #536267
+ media viewer background......: #1e1e1e
+ media viewer text............: #708090
+ tag box background...........: #1e1e1e
+
+6. adjust your tag presentation color values under files > options > tag presentation > (On thumbnail top, On thumbnail bottom-right, On media viewer top) like so:
+
+ background colour............: #007acc
+ text colour..................: #ffffff
+*/
+
+
+/*
+ ___ _
+ / _ \___ _ __ ___ _ __ __ _| |
+ / /_\/ _ \ '_ \ / _ \ '__/ _` | |
+/ /_\\ __/ | | | __/ | | (_| | |
+\____/\___|_| |_|\___|_| \__,_|_|
+
+*/
+
+QAbstractItemView {
+ background-color: #252526;
+}
+
+/*
+ ____ __ __ _ _ _
+ /___ \/ / /\ \ (_) __| | __ _ ___| |_
+ // / /\ \/ \/ / |/ _` |/ _` |/ _ \ __|
+/ \_/ / \ /\ /| | (_| | (_| | __/ |_
+\___,_\ \/ \/ |_|\__,_|\__, |\___|\__|
+ |___/
+
+*/
+
+QWidget {
+ color: #CCCCCC;
+ background-color: #252526;
+ alternate-background-color: #252526;
+}
+
+QWidget::disabled {
+ background-color: #252526;
+}
+
+QWidget::item::selected {
+ color: #FFF;
+ background-color: #569cd6;
+}
+
+QWidget::item:hover {
+ color: #FFF;
+ background-color: #569cd6;
+}
+
+QWidget#HydrusAnimationBar
+{
+ qproperty-hab_border: #271F89;
+ qproperty-hab_background: #569CD6;
+ qproperty-hab_nub: #007ACC;
+}
+
+/*
+ ____ _____ _ _____ _
+ /___ \/__ \___ ___ | /__ (_)_ __
+ // / / / /\/ _ \ / _ \| | / /\/ | '_ \
+/ \_/ / / / | (_) | (_) | |/ / | | |_) |
+\___,_\ \/ \___/ \___/|_|\/ |_| .__/
+ |_|
+
+*/
+
+QToolTip {
+ color: #8be9fd;
+ border: 1px solid black;
+ background-color: #1E1E1E;
+ padding: 1px;
+}
+
+/*
+ ____
+ /___ \/\/\ ___ _ __ _ _
+ // / / \ / _ \ '_ \| | | |
+/ \_/ / /\/\ \ __/ | | | |_| |
+\___,_\/ \/\___|_| |_|\__,_|
+
+*/
+
+QMenu {
+ color: #CCCCCC;
+ background: #252526;
+}
+
+QMenu::item {
+ padding: 2px 20px 2px 20px;
+}
+
+QMenu::item:selected {
+ color: #FFF;
+ background: #569cd6;
+}
+
+/*
+ ____ ___
+ /___ \/\/\ ___ _ __ _ _ / __\ __ _ _ __
+ // / / \ / _ \ '_ \| | | |/__\/// _` | '__|
+/ \_/ / /\/\ \ __/ | | | |_| / \/ \ (_| | |
+\___,_\/ \/\___|_| |_|\__,_\_____/\__,_|_|
+
+*/
+
+QMenuBar::item {
+ background: transparent;
+}
+
+QMenuBar::item:selected {
+ color: #FFF;
+ background: #569cd6;
+}
+
+/*
+ ____ ___ _ ___ _ _
+ /___ \/ _ \_ _ ___| |__ / __\_ _| |_| |_ ___ _ __
+ // / / /_)/ | | / __| '_ \ /__\// | | | __| __/ _ \| '_ \
+/ \_/ / ___/| |_| \__ \ | | / \/ \ |_| | |_| || (_) | | | |
+\___,_\/ \__,_|___/_| |_\_____/\__,_|\__|\__\___/|_| |_|
+
+*/
+
+QPushButton {
+ color: #CCCCCC;
+ background-color: #252526;
+}
+
+QPushButton::hover {
+ color: #FFF;
+ background-color: #252526;
+}
+
+QPushButton#HydrusAccept {
+ color: #50fa7b;
+}
+
+QPushButton#HydrusCancel {
+ color: #ff5555;
+}
+
+QPushButton#HydrusOnOffButton[hydrus_on=true] {
+ color: #50fa7b;
+}
+
+QPushButton#HydrusOnOffButton[hydrus_on=false] {
+ color: #ff5555;
+}
+
+/*
+ ____ _____ _ ___
+ /___ \/__ \__ _| |__ / __\ __ _ _ __
+ // / / / /\/ _` | '_ \ /__\/// _` | '__|
+/ \_/ / / / | (_| | |_) / \/ \ (_| | |
+\___,_\ \/ \__,_|_.__/\_____/\__,_|_|
+
+*/
+
+QTabBar::tab {
+ color: #8be9fd;
+ background-color: #1a1a1a;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 3px;
+ padding-bottom: 2px;
+}
+
+QTabBar::tab:last {
+ border-top-right-radius: 3px;
+}
+
+QTabBar::tab:selected {
+ color: #FFF;
+ background-color: #007ACC;
+}
+
+QTabBar::tab:hover:!selected {
+ color: #FFF;
+ background-color: #007ACC;
+}
+
+/*
+ ____ __ _ __ _ _ _
+ /___ \/ /(_)_ __ ___ /__\_| (_) |_
+ // / / / | | '_ \ / _ \/_\/ _` | | __|
+/ \_/ / /__| | | | | __//_| (_| | | |_
+\___,_\____/_|_| |_|\___\__/\__,_|_|\__|
+
+*/
+
+QLineEdit {
+ border: 1px solid #569cd6;
+ border-radius: 1px;
+ background-color: #383B3D;
+ padding: 1px;
+}
+
+QLineEdit:focus{
+ color: #FFF;
+ border: 1px solid #CCC;
+}
+
+/*
+ ____ ___ ___
+ /___ \/ _ \_ __ ___ __ _ _ __ ___ ___ ___ / __\ __ _ _ __
+ // / / /_)/ '__/ _ \ / _` | '__/ _ \/ __/ __| /__\/// _` | '__|
+/ \_/ / ___/| | | (_) | (_| | | | __/\__ \__ \/ \/ \ (_| | |
+\___,_\/ |_| \___/ \__, |_| \___||___/___/\_____/\__,_|_|
+ |___/
+
+*/
+
+QProgressBar {
+ color: #FFF;
+ border: 1px solid #569cd6;
+ text-align: center;
+ padding: 1px;
+ border-radius: 0px;
+ background-color: #383B3D;
+ width: 15px;
+}
+
+QProgressBar::chunk {
+ color: #FFF;
+ background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,
+ stop: 0 #78d,
+ stop: 0.4999 #46a,
+ stop: 0.5 #45a,
+ stop: 1 #238 );
+ border-radius: 0px;
+ border: 0px;
+}
+
+/*
+ ____ _ _
+ /___ \/\ /\___ __ _ __| | ___ _ __/\ /(_) _____ __
+ // / / /_/ / _ \/ _` |/ _` |/ _ \ '__\ \ / / |/ _ \ \ /\ / /
+/ \_/ / __ / __/ (_| | (_| | __/ | \ V /| | __/\ V V /
+\___,_\/ /_/ \___|\__,_|\__,_|\___|_| \_/ |_|\___| \_/\_/
+
+*/
+
+QHeaderView::section {
+ background-color: #007ACC;
+ color: #f8f8f2;
+ padding-left: 4px;
+ border: 1px solid #569cd6;
+}
+
+/*
+ ____ __ _ _ ___
+ /___ \/ _\ ___ _ __ ___ | | | / __\ __ _ _ __
+ // / /\ \ / __| '__/ _ \| | |/__\/// _` | '__|
+/ \_/ / _\ \ (__| | | (_) | | / \/ \ (_| | |
+\___,_\ \__/\___|_| \___/|_|_\_____/\__,_|_|
+
+From Quassel Wiki: http://sprunge.us/iZGB
+*/
+
+QScrollBar {
+ background: #1A1A1A;
+ margin: 0;
+}
+
+QScrollBar:hover {
+ background: #1A1A1A;
+}
+
+QScrollBar:vertical {
+ width: 15px;
+}
+
+QScrollBar:horizontal {
+ height: 8px;
+}
+
+QScrollBar::handle {
+ padding: 0;
+ margin: 2px;
+ border-radius: 2px;
+ border: 2px solid #569CD6;
+ background: #1E1E1E;
+}
+
+QScrollBar::handle:vertical {
+ min-height: 20px;
+ min-width: 0px;
+}
+
+QScrollBar::handle:horizontal {
+ min-width: 20px;
+ min-height: 0px;
+}
+
+QScrollBar::handle:hover {
+ border-color: #007ACC;
+ background: #1E1E1E;
+}
+
+QScrollBar::handle:pressed {
+ background: #569CD6;
+ border-color: #007ACC;
+}
+
+QScrollBar::add-line , QScrollBar::sub-line {
+ height: 0px;
+ border: 0px;
+}
+
+QScrollBar::up-arrow, QScrollBar::down-arrow {
+ border: 0px;
+ width: 0px;
+ height: 0px;
+}
+
+QScrollBar::add-page, QScrollBar::sub-page {
+ background: none;
+}
+
+/*
+ ____ _____ _ __ _ _ _
+ /___ \/__ \_____ _| |_ /__\_| (_) |_
+ // / / / /\/ _ \ \/ / __|/_\/ _` | | __|
+/ \_/ / / / | __/> <| |_//_| (_| | | |_
+\___,_\ \/ \___/_/\_\\__\__/\__,_|_|\__|
+
+*/
+
+QTextEdit {
+ color: #a19cdf;
+ background-color: #383B3D;
+}
+
+QTextEdit#HydrusValid {
+ color: #0d035f;
+ background-color: #80ff80;
+}
+
+QTextEdit#HydrusIndeterminate {
+ color: #0d035f;
+ background-color: #8080ff;
+}
+
+QTextEdit#HydrusInvalid {
+ color: #0d035f;
+ background-color: #ff8080;
+}
+
+/*
+ ____ ___ _ _ _____ _ __ _ _ _
+ /___ \/ _ \ | __ _(_)_ __/__ \_____ _| |_ /__\_| (_) |_
+ // / / /_)/ |/ _` | | '_ \ / /\/ _ \ \/ / __|/_\/ _` | | __|
+/ \_/ / ___/| | (_| | | | | / / | __/> <| |_//_| (_| | | |_
+\___,_\/ |_|\__,_|_|_| |_\/ \___/_/\_\\__\__/\__,_|_|\__|
+
+*/
+
+QPlainTextEdit {
+ background-color: #383B3D;
+}
+
+/*
+ ____ __ _ _
+ /___ \/ / __ _| |__ ___| |
+ // / / / / _` | '_ \ / _ \ |
+/ \_/ / /__| (_| | |_) | __/ |
+\___,_\____/\__,_|_.__/ \___|_|
+
+*/
+
+QLabel#HydrusValid {
+ color: #50fa7b;
+}
+
+QLabel#HydrusIndeterminate {
+ color: #8080ff;
+}
+
+QLabel#HydrusInvalid {
+ color: #ff5555;
+}
+
+QLabel#HydrusWarning {
+ color: #ff5555;
+}
+
+/*
+ ____ __ _ __ _ _ _
+ /___ \/ /(_)_ __ ___ /__\_| (_) |_
+ // / / / | | '_ \ / _ \/_\/ _` | | __|
+/ \_/ / /__| | | | | __//_| (_| | | |_
+\___,_\____/_|_| |_|\___\__/\__,_|_|\__|
+
+*/
+
+QLineEdit#HydrusValid {
+ color: #0d035f;
+ background-color: #80ff80;
+}
+
+QLineEdit#HydrusIndeterminateValid {
+ color: #0d035f;
+ background-color: #8080ff;
+}
+
+QLineEdit#HydrusInvalid {
+ color: #0d035f;
+ background-color: #ff8080;
+}
+
+/*
+ ____ ___ _ _ ___
+ /___ \/ __\ |__ ___ ___| | __ / __\ _____ __
+ // / / / | '_ \ / _ \/ __| |/ //__\/// _ \ \/ /
+/ \_/ / /___| | | | __/ (__| \/ \ (_) > <
+\___,_\____/|_| |_|\___|\___|_|\_\_____/\___/_/\_\
+
+*/
+
+QCheckBox#HydrusWarning {
+ color: #ff5555;
+}
+
+/*
+
+Extra, hydev added this
+
+*/
+
+QLabel#HydrusHyperlink
+{
+ qproperty-link_color: #8be9fd;
+}