diff --git a/docs/changelog.md b/docs/changelog.md
index ff25df817..0d3863ea5 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -7,6 +7,42 @@ title: Changelog
!!! note
This is the new changelog, only the most recent builds. For all versions, see the [old changelog](old_changelog.html).
+## [Version 557](https://github.com/hydrusnetwork/hydrus/releases/tag/v557)
+
+### misc
+
+* optimised large tag filter edit UI. you can now paste 5,000 items into an empty tag filter blacklist in less than a second, and if you have a big tag filter, removing or adding one thing is now instant (previously, this stuff would lag 4 seconds or more, sometimes multiple minutes!!)
+* the ugoira 'num frames' counting method now discludes files ending in .js/.json, to catch future bundling of frame timings
+* the cbz scanning tech should now recognise cbzs with four or fewer pages
+* a legacy 'is this image all good?' check that happens on PIL-loading is now gone. this improves rendering for a variety of truncated files and clarifies some error messages (previously, this thing was just failing silently)
+* fixed the delete file pre-flight logic so users on the non-advanced delete dialog can now delete repository updates. previously, they saw the menu entry, but hitting it was a no-op
+
+### better hash predicate parsing
+
+* `system:hash` labels are a little different now. they'll say `system:hash (md5) is abcd...`, with the algorithm after the "hash". hash is omitted for sha256 (the hydrus default). this eases parsing
+* `system:similar to data` labels are a little different. they'll say 'distance' instead of 'max hamming', and the number and type of hashes they hold, and if they hold only pixel hashes, the distance is not stated
+* `system:hash` predicate parsing is now more flexible. you can put the hash type pretty much anywhere now.
+* `system:similar to` and `system:similar to data` predicate parsing is now more flexible. more combinations are allowed, and you can not include distance and it'll be fine
+* these three hash predicates now copy to clipboard with all their hashes explicitly enumerated, making strings that are fully parsable! this is a big step forward in a completely sealed import-export predicate parsing loop; now I have the tech set up to export a different phrase to clipboard than what you see in the label, I just need the examples of where it goes wrong. if there is a system predicate that copies to clipboard in a way that won't parse back, let me know and I'll see if I can fix it.
+* added more unit tests for this parsing
+
+### documentation and cleanup
+
+* wrote a guide on how to install 'Git for Windows' for the 'running from source' help. although most of the settings in its marathon 12-page install wizard can be left as default, the technical questions can be intimidating, so I've written them all out for a nice simple install. also brushed up some of the surrounding help here
+* added a warning to the regular 'installing and updating' help regarding the danger of test-running extract releases before updating (you can overwrite your database by accident)
+* thanks to a user, the filetypes help document is updated with Ugoira and CBZ info
+* all the 'HydrusFiletypeHandling' files are refactored to a new 'files' module. there's a bunch of them these days!
+* the hydrus.core.images module is moved beneath this 'files' module too
+* the file log list panel right-click menu now says 'open URLs'/'open files' locations' depending on whether you are looking at a URL import log or local HDD import log
+
+### client api
+
+* the `file_metadata` call now returns `filetype_forced` and, if so, also `original_mime` to talk about the new forced filetype system
+* the client api help and unit tests are updated to test this is working ok
+* fixed a typo that was causing too much work in the updated file info manager call (and was often returning 'null' results for half-cached `file_metadata` requests with `only_return_basic_information=true`)
+* thanks to a user, the `/add_urls/get_url_info` Client API call now has a cache timeout of ten minutes, and the `/add_urls/get_url_files` call now has a timeout of 30 seconds if all the files are 'already in db'. this should automatically reduce some overhead for several programs that talk to the Client API a lot about URLs
+* the client api version is now 58
+
## [Version 556](https://github.com/hydrusnetwork/hydrus/releases/tag/v556)
### misc
@@ -419,30 +455,3 @@ title: Changelog
* I made a new application command to hold the file filter. I just pre-populate the UI with a dropdown with commond choices for now, but in future it could hold a customisable file filter, once, ha ha, I have some UI to actually edit one!
* cleaned up various shortcut code
* misc linting cleanup
-
-## [Version 547](https://github.com/hydrusnetwork/hydrus/releases/tag/v547)
-
-### mpv crash fixes
-
-* tl;dr: mpv less crashy now
-* if mpv fails to load a file but not in an outright 'error' manner (this appears to mean a file using a rare format that a submodule of mpv can't handle), the client now recognises this has happened, either right after the first load, or, if the error takes longer to occur, a subsequent status interrogation, and makes several new steps to restore program stability: disconnecting the mpv window from all commands, freezing the scanbar, loading the default hydrus.png as emergency backstop, and making a popup to let the user know what just happened. previously, Qt would get rapidly unhappy as it asked things to draw on screen over the null-state player, particularly if you show/hid the scanbar several times, and it would, if not removed promptly from screen, typically lead to a program crash
-* furthermore, the scanbar now never interrogates the mpv window during its paint event. a mysterious interaction of C++ level objects during error state was causing the underlying instability here, and now I cannot reproduce this even if I try
-* I also hardened the mpv window's 'no-media' state. now, rather than showing 'nothing' when media is unloaded, each mpv player now actually idles on a black png lol
-* this tech will kick in for more extreme file failures, too, which have a different handler but seem to give the same detectable dump-out state
-* fixed a silent-but-for-debug-mode error while destroying damaged mpv windows right when the program is terminating
-
-### misc
-
-* thanks to a user, we now have import support for 'djvu' files. basically an open source PDF style format
-* fixed pasting an image into 'system:similar files', which I missed updating in last week's code cleanup!
-* a light but spammy legacy job that refreshed every search page's empty autocomplete every five minutes (to get updated system predicates/numbers) now only occurs to autocompletes on the current page. relatedly, when you switch to a search page you haven't looked at in five minutes, it triggers the same update immediately. this should save a tiny bit of idle CPU time and, more importantly, clear out the background job queue on larger-session clients
-* I _think_ I fixed some instances of the media viewer notes window initialising with a gigantic width on some OSes. if you often get a super wide notes window when you first open the media viewer, with it fixing itself when you cycle to a different file and back, let me know if things are any better
-* when you have a popup message that has a 'show x files' button, usually from a subscription, that routine now excludes files that have been deleted since the button was created. it updates its existing file count on a click, also, to how many files it actually will generate. if you click one of these buttons, delete some files, and then click it again, it should no longer produce ghost files in the new search page. I'm going to add some more tech to optionally handle the system:hash predicate in a page in similar ways, 'locking' it to the current page content and preserving file sort so it works nice with 'remove files' etc..
-* fixed a stupid typo that was swapping the 'allow non-local connections' server setting when making the interface for IPv6 hosts. there is a secondary check of all client IPs on every request, so I am confident this was not enabling non-local connections when undesired on IPv6, but it was disabling them by deploying the loopback interface when they should have been allowed! sorry for the trouble, and well done to the person who noticed this
-* while pursing an odd and rare problem where a download job can start even though it should be waiting on a login process, I cleaned some of the login code and logic, lowering the timeout for session cookie expiring from 60 to 45 minutes and smoothing out some confusing status-checking in the pre-login stage. I could never reproduce the problem, though, so if you have had this issue, please let me know more and I'll see if I can reproduce this reliably
-
-### simple cleanup
-
-* cleaned up some filetype parsing code that was getting a little messy, also reduced some overhead
-* unified the thumbnail/file filetype parsing a little, with better fallback states when a hydrus thumbnail happens for some reason not to be a jpeg or png
-* fixed an out of date menu reference in the 'help my media files are broke.txt' document. 'clear orphan files' is under 'file maintenance' now, not 'db maintenance'
diff --git a/docs/developer_api.md b/docs/developer_api.md
index 4927f18fc..c0ed6c6d4 100644
--- a/docs/developer_api.md
+++ b/docs/developer_api.md
@@ -1563,6 +1563,7 @@ Response:
"hash" : "4c77267f93415de0bc33b7725b8c331a809a924084bee03ab2f5fae1c6019eb2",
"size" : 63405,
"mime" : "image/jpeg",
+ "filetype_forced" : false,
"filetype_human" : "jpeg",
"filetype_enum" : 1,
"ext" : ".jpg",
@@ -1617,6 +1618,7 @@ Response:
"hash" : "3e7cb9044fe81bda0d7a84b5cb781cba4e255e4871cba6ae8ecd8207850d5b82",
"size" : 199713,
"mime" : "video/webm",
+ "filetype_forced" : false,
"filetype_human" : "webm",
"filetype_enum" : 21,
"ext" : ".webm",
@@ -1735,6 +1737,7 @@ Response:
"hash" : "4c77267f93415de0bc33b7725b8c331a809a924084bee03ab2f5fae1c6019eb2",
"size" : 63405,
"mime" : "image/jpeg",
+ "filetype_forced" : false,
"filetype_human" : "jpeg",
"filetype_enum" : 1,
"ext" : ".jpg",
@@ -1750,6 +1753,7 @@ Response:
"hash" : "3e7cb9044fe81bda0d7a84b5cb781cba4e255e4871cba6ae8ecd8207850d5b82",
"size" : 199713,
"mime" : "video/webm",
+ "filetype_forced" : false,
"filetype_human" : "webm",
"filetype_enum" : 21,
"ext" : ".webm",
@@ -1778,6 +1782,8 @@ The `thumbnail_width` and `thumbnail_height` are a generally reliable prediction
If the file has a thumbnail, `blurhash` gives a base 83 encoded string of its [blurhash](https://blurha.sh/). `pixel_hash` is an SHA256 of the image's pixel data and should exactly match for pixel-identical files (it is used in the duplicate system for 'must be pixel duplicates').
+If the file's filetype is forced by the user, `filetype_forced` becomes `true` and a second mime string, `original_mime` is added.
+
#### tags
The `tags` structure is similar to the [/add\_tags/add\_tags](#add_tags_add_tags) scheme, excepting that the status numbers are:
diff --git a/docs/getting_started_installing.md b/docs/getting_started_installing.md
index 7c3c98a4a..554d0a12a 100644
--- a/docs/getting_started_installing.md
+++ b/docs/getting_started_installing.md
@@ -119,7 +119,7 @@ To run the client:
## Updating
!!! warning
- Hydrus is imageboard-tier software, wild and fun but unprofessional. It is written by one Anon spinning a lot of plates. Mistakes happen from time to time, usually in the update process. There are also no training wheels to stop you from accidentally overwriting your whole db if you screw around. Be careful when updating. Make backups beforehand!
+ Hydrus is imageboard-tier software, wild and fun--but also unprofessional. It is written by one Anon spinning a lot of plates. Mistakes happen from time to time, usually in the update process. There are also no training wheels to stop you from accidentally overwriting your whole db if you screw around. Be careful when updating. Make backups beforehand!
**Hydrus does not auto-update. It will stay the same version unless you download and install a new one.**
@@ -142,9 +142,14 @@ The update process:
* If the client is running, close it!
* If you maintain a backup, run it now!
* If you use the installer, just download the new installer and run it. It should detect where the last install was and overwrite everything automatically.
-* If you extract, then just extract the new version right on top of your current install and overwrite manually.
+* If you extract, then just extract the new version right on top of your current install and overwrite manually. *It is wise to extract it straight from the archive to your install folder.*
* Start your client or server. It may take a few minutes to update its database. I will say in the release post if it is likely to take longer.
+??? warning "Be extremely careful making test runs of the Extract release"
+ **Do not test-run the extract before copying it over your install!** Running the program anywhere will create database files in the /db/ dir, and if you then copy that once-run folder on top of your real install, you will overwrite your real database! Of course it doesn't really matter, because you made a full backup before you started, right? :^)
+
+ If you need to perform tests of an update, make sure you have a good backup before you start and then remember to delete any functional test extracts before extracting from the original archive once more for the actual 'install'.
+
Unless the update specifically disables or reconfigures something, all your files and tags and settings will be remembered after the update.
Releases typically need to update your database to their version. New releases can retroactively perform older database updates, so if the new version is v255 but your database is on v250, you generally only need to get the v255 release, and it'll do all the intervening v250->v251, v251->v252, etc... update steps in order as soon as you boot it. If you need to update from a release more than, say, ten versions older than current, see below. You might also like to skim the release posts or [changelog](changelog.md) to see what is new.
diff --git a/docs/old_changelog.html b/docs/old_changelog.html
index eecf18ee5..a6ac2cd97 100644
--- a/docs/old_changelog.html
+++ b/docs/old_changelog.html
@@ -34,6 +34,37 @@
+ -
+
+
+ misc
+ - optimised large tag filter edit UI. you can now paste 5,000 items into an empty tag filter blacklist in less than a second, and if you have a big tag filter, removing or adding one thing is now instant (previously, this stuff would lag 4 seconds or more, sometimes multiple minutes!!)
+ - the ugoira 'num frames' counting method now discludes files ending in .js/.json, to catch future bundling of frame timings
+ - the cbz scanning tech should now recognise cbzs with four or fewer pages
+ - a legacy 'is this image all good?' check that happens on PIL-loading is now gone. this improves rendering for a variety of truncated files and clarifies some error messages (previously, this thing was just failing silently)
+ - fixed the delete file pre-flight logic so users on the non-advanced delete dialog can now delete repository updates. previously, they saw the menu entry, but hitting it was a no-op
+ better hash predicate parsing
+ - `system:hash` labels are a little different now. they'll say `system:hash (md5) is abcd...`, with the algorithm after the "hash". hash is omitted for sha256 (the hydrus default). this eases parsing
+ - `system:similar to data` labels are a little different. they'll say 'distance' instead of 'max hamming', and the number and type of hashes they hold, and if they hold only pixel hashes, the distance is not stated
+ - `system:hash` predicate parsing is now more flexible. you can put the hash type pretty much anywhere now.
+ - `system:similar to` and `system:similar to data` predicate parsing is now more flexible. more combinations are allowed, and you can not include distance and it'll be fine
+ - these three hash predicates now copy to clipboard with all their hashes explicitly enumerated, making strings that are fully parsable! this is a big step forward in a completely sealed import-export predicate parsing loop; now I have the tech set up to export a different phrase to clipboard than what you see in the label, I just need the examples of where it goes wrong. if there is a system predicate that copies to clipboard in a way that won't parse back, let me know and I'll see if I can fix it.
+ - added more unit tests for this parsing
+ documentation and cleanup
+ - wrote a guide on how to install 'Git for Windows' for the 'running from source' help. although most of the settings in its marathon 12-page install wizard can be left as default, the technical questions can be intimidating, so I've written them all out for a nice simple install. also brushed up some of the surrounding help here
+ - added a warning to the regular 'installing and updating' help regarding the danger of test-running extract releases before updating (you can overwrite your database by accident)
+ - thanks to a user, the filetypes help document is updated with Ugoira and CBZ info
+ - all the 'HydrusFiletypeHandling' files are refactored to a new 'files' module. there's a bunch of them these days!
+ - the hydrus.core.images module is moved beneath this 'files' module too
+ - the file log list panel right-click menu now says 'open URLs'/'open files' locations' depending on whether you are looking at a URL import log or local HDD import log
+ client api
+ - the `file_metadata` call now returns `filetype_forced` and, if so, also `original_mime` to talk about the new forced filetype system
+ - the client api help and unit tests are updated to test this is working ok
+ - fixed a typo that was causing too much work in the updated file info manager call (and was often returning 'null' results for half-cached `file_metadata` requests with `only_return_basic_information=true`)
+ - thanks to a user, the `/add_urls/get_url_info` Client API call now has a cache timeout of ten minutes, and the `/add_urls/get_url_files` call now has a timeout of 30 seconds if all the files are 'already in db'. this should automatically reduce some overhead for several programs that talk to the Client API a lot about URLs
+ - the client api version is now 58
+
+
-
@@ -430,7 +461,7 @@
- moved the new blurhash methods to a new `HydrusBlurhash` file
- moved various normalisation routines to a new `HydrusImageNormalisation` file
- moved various channel scanning and adjusting code to a new `HydrusImageColours` file
- - moved the hydrus image files to the new 'hydrus.core.images' module
+ - moved the hydrus image files to the new 'hydrus.core.files.images' module
- cleaned up some image loading code
- deleted ancient and no-longer-used client db code regarding imageboard definitions, status texts, and more
- removed the ancient `OPENCV_OK` fallback code, which was only used, superfluously, in a couple of final places. OpenCV is not optional to run hydrus, server or client
diff --git a/docs/running_from_source.md b/docs/running_from_source.md
index ce40aa857..93d07cb43 100644
--- a/docs/running_from_source.md
+++ b/docs/running_from_source.md
@@ -25,7 +25,31 @@ There are now setup scripts that make this easy on Windows and Linux. You do not
=== "Windows"
- First of all, you will need to install Python. Get 3.10 or 3.11 [here](https://www.python.org/downloads/windows/). During the install process, make sure it has something like 'Add Python to PATH' checked. This makes Python available to your Windows.
+ ??? info "Git for Windows"
+ Git is an excellent tool for synchronising code across platforms. Instead of downloading and extracting the whole .zip every time you want to update, it allows you to just run one line and all the code updates are applied in about three seconds. You can also run special versions of the program, or test out changes I committed two minutes ago without having to wait for me to make a whole build. You don't have to, but I recommend you get it.
+
+ Installing it is simple, but it can be intimidating. These are a bunch of very clever tools coming over from Linux-land, and the installer has a 10+ page wizard with several technical questions. Luckily, the 'default' is broadly fine, but I'll write everything out so you can follow along. I can't promise this list will stay perfectly up to date, so let me know if there is something complex and new you don't understand. This is also a record that I can refer to when I set up a new machine.
+
+ - First off, get it [here](https://gitforwindows.org/). Run the installer.
+ - On the first page, with checkboxes, I recommend you uncheck 'Windows Explorer Integration', with its 'Open Git xxx here' sub-checkboxes. This stuff will just be annoying for our purposes.
+ - Then set your text editor. Select the one you use, and if you don't recognise anything, set 'notepad'.
+ - Now we enter the meat of the wizard pages. Everything except the default console window is best left as default:
+ - `Let Git decide` on using "master" as the default main branch name
+ - `Git from the command line and also from 3rd-party software`
+ - `Use bundled OpenSSH`
+ - `Use the OpenSSL library`
+ - `Checkout Windows-style, commit Unix-style line endings`
+ - **(NOT DEFAULT)** `Use Windows' default console window`. Let's keep things simple, but it isn't a big deal.
+ - `Fast-forward or merge`
+ - `Git Credential Manager`
+ - Do `Enable file system caching`/Do not `Enable symbolic links`
+ - Do not enable experimental stuff
+
+ Git should now be installed on your system. Any new terminal window (shift+right-click on any folder and hit 'Open in terminal') now has the `git` command!
+
+
+ First of all, you will need to install Python. Get 3.10 or 3.11 [here](https://www.python.org/downloads/windows/). During the install process, make sure it has something like 'Add Python to PATH' checked. This makes Python available everywhere in Windows.
+
=== "Linux"
@@ -35,9 +59,16 @@ There are now setup scripts that make this easy on Windows and Linux. You do not
You should already have python of about the correct version.
-If you are already on a very new version of python, that's ok--you might need to select the 'advanced' setup later on and choose the '(t)est' options. If you are stuck on a much older version of python, try the same thing, but with the '(o)lder' options (but I can't promise it will work!).
+If you are already on a very new python, like 3.12+, that's ok--you might need to select the 'advanced' setup later on and choose the '(t)est' options. If you are stuck on a much older version of python, try the same thing, but with the '(o)lder' options (but I can't promise it will work!).
+
+Then, get the hydrus source. It is best to get it with Git: make a new folder somewhere, open a terminal in it, and then enter:
+
+ git clone https://github.com/hydrusnetwork/hydrus
-Then, get the hydrus source. The github repo is [https://github.com/hydrusnetwork/hydrus](https://github.com/hydrusnetwork/hydrus). If you are familiar with git, you can just clone the repo to the location you want with `git clone https://github.com/hydrusnetwork/hydrus`, but if not, then just go to the [latest release](https://github.com/hydrusnetwork/hydrus/releases/latest) and download and extract the source code .zip somewhere. Make sure the directory has write permissions (e.g. don't put it in "Program Files"). Extracting straight to a spare drive, something like "D:\Hydrus Network", is ideal.
+The whole repository will be copied to that location. If Git is not available, then just go to the [latest release](https://github.com/hydrusnetwork/hydrus/releases/latest) and download and extract the source code .zip somewhere.
+
+!!! warning "Read-only install locations"
+ Make sure the install directory has convenient write permissions (e.g. on Windows, don't put it in "Program Files"). Extracting straight to a spare drive, something like "D:\Hydrus Network", is ideal.
We will call the base extract directory, the one with 'hydrus_client.py' in it, `install_dir`.
@@ -207,7 +238,7 @@ The first start will take a little longer (it has to compile all the code into s
To update, you do the same thing as for the extract builds.
1. If you installed by extracting the source zip, then download the [latest release](https://github.com/hydrusnetwork/hydrus/releases/latest) source zip and extract it over the top of the folder you have, overwriting the existing source files.
-2. If you installed with git, then just run `git pull` as normal.
+2. If you installed with git, then just run `git pull` as normal. I have added easy 'git_pull' scripts to the install directory for your convenience (on Windows, just double-click 'git_pull.bat').
If you get a library version error when you try to boot, run the venv setup again. It is worth doing this anyway, every now and then, just to stay up to date.
@@ -365,19 +396,21 @@ Almost everything you get through pip is provided as pre-compiled 'wheels' these
- Get Visual Studio 14/whatever build tools
- Pick a different library version
-Option B is always the simpler. If opencv-headless as the requirements.txt specifies won't compile in Python 3.10, then try a newer version--there will probably be one of these new highly compatible wheels and it'll just work in seconds. Check my build scripts and various requirements.txts for ideas on what versions to try for your python etc...
+Option B is always simpler. If opencv-headless as the requirements.txt specifies won't compile in your python, then try a newer version--there will probably be one of these new highly compatible wheels and it'll just work in seconds. Check my build scripts and various requirements.txts for ideas on what versions to try for your python etc...
If you are confident you need Visual Studio tools, then prepare for headaches. Although the tools are free from Microsoft, it can be a pain to get them through the official (and often huge) downloader installer from Microsoft. Expect a 5GB+ install with an eye-watering number of checkboxes that probably needs some stackexchange searches to figure out.
-On Windows 10, [Chocolatey](https://chocolatey.org/) has been the easy answer. Get it installed and and use this one simple line:
+On Windows 10, [Chocolatey](https://chocolatey.org/) has been the easy answer. These can be useful:
```
-choco install -y vcbuildtools visualstudio2017buildtools windows-sdk-10.0
+choco install -y vcredist-all
+choco install -y vcbuildtools (this is Visual Studio 2015)
+choco install -y visualstudio2017buildtools
+choco install -y visualstudio2022buildtools
+choco install -y windows-sdk-10.0
```
-Trust me, just do this, it will save a ton of headaches!
-
-_Update:_ On Windows 11, in 2023-01, I had trouble with the above. There's a couple '11' SDKs that installed ok, but the vcbuildtools stuff had unusual errors. I hadn't done this in years, so maybe they are broken for Windows 10 too! The good news is that a basic stock Win 11 install with Python 3.10 is fine getting everything on our requirements and even making a build without any extra compiler tech.
+_Update:_ On Windows 11, I have had some trouble with the above. The VS2015 seems not to install any more. A basic stock Win 11 install with Python 3.10 or 3.11 is fine getting everything on our requirements, but freezing with PyInstaller may have trouble finding certain 'api-***.dll' files. I am now trying to figure this out with my latest dev machine as of 2024-01. If you try this, let me know what you find out!
### Additional Windows Info { id="additional_windows" }
diff --git a/hydrus/client/ClientController.py b/hydrus/client/ClientController.py
index 54d881c69..7bc2c6c8e 100644
--- a/hydrus/client/ClientController.py
+++ b/hydrus/client/ClientController.py
@@ -1009,7 +1009,7 @@ def InitModel( self ):
if self.new_options.GetBoolean( 'use_system_ffmpeg' ):
- from hydrus.core import HydrusVideoHandling
+ from hydrus.core.files import HydrusVideoHandling
if HydrusVideoHandling.FFMPEG_PATH.startswith( HC.BIN_DIR ):
diff --git a/hydrus/client/ClientDuplicates.py b/hydrus/client/ClientDuplicates.py
index 7f7630221..fb66831c5 100644
--- a/hydrus/client/ClientDuplicates.py
+++ b/hydrus/client/ClientDuplicates.py
@@ -10,9 +10,8 @@
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusImageHandling
-from hydrus.core.images import HydrusImageMetadata
-from hydrus.core.images import HydrusImageOpening
+from hydrus.core.files.images import HydrusImageMetadata
+from hydrus.core.files.images import HydrusImageOpening
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientThreading
diff --git a/hydrus/client/ClientFiles.py b/hydrus/client/ClientFiles.py
index 25a746193..5ed29d358 100644
--- a/hydrus/client/ClientFiles.py
+++ b/hydrus/client/ClientFiles.py
@@ -8,19 +8,19 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
-from hydrus.core import HydrusPSDHandling
from hydrus.core import HydrusLists
from hydrus.core import HydrusPaths
from hydrus.core import HydrusThreading
from hydrus.core import HydrusTime
-from hydrus.core import HydrusVideoHandling
-from hydrus.core.images import HydrusBlurhash
-from hydrus.core.images import HydrusImageColours
-from hydrus.core.images import HydrusImageHandling
-from hydrus.core.images import HydrusImageMetadata
-from hydrus.core.images import HydrusImageOpening
+from hydrus.core.files import HydrusFileHandling
+from hydrus.core.files import HydrusPSDHandling
+from hydrus.core.files import HydrusVideoHandling
+from hydrus.core.files.images import HydrusBlurhash
+from hydrus.core.files.images import HydrusImageColours
+from hydrus.core.files.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageMetadata
+from hydrus.core.files.images import HydrusImageOpening
from hydrus.core.networking import HydrusNetworking
from hydrus.client import ClientConstants as CC
diff --git a/hydrus/client/ClientImageHandling.py b/hydrus/client/ClientImageHandling.py
index 39bf67abf..b51184313 100644
--- a/hydrus/client/ClientImageHandling.py
+++ b/hydrus/client/ClientImageHandling.py
@@ -9,7 +9,7 @@
from hydrus.client import ClientConstants as CC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageHandling
cv_interpolation_enum_lookup = {}
diff --git a/hydrus/client/ClientOptions.py b/hydrus/client/ClientOptions.py
index 896119197..98febd0aa 100644
--- a/hydrus/client/ClientOptions.py
+++ b/hydrus/client/ClientOptions.py
@@ -443,7 +443,7 @@ def _InitialiseDefaults( self ):
self._dictionary[ 'integers' ][ 'max_connection_attempts_allowed' ] = 5
self._dictionary[ 'integers' ][ 'max_request_attempts_allowed_get' ] = 5
- from hydrus.core.images import HydrusImageHandling
+ from hydrus.core.files.images import HydrusImageHandling
self._dictionary[ 'integers' ][ 'thumbnail_scale_type' ] = HydrusImageHandling.THUMBNAIL_SCALE_DOWN_ONLY
diff --git a/hydrus/client/ClientPDFHandling.py b/hydrus/client/ClientPDFHandling.py
index 81ba69f20..4a328d88f 100644
--- a/hydrus/client/ClientPDFHandling.py
+++ b/hydrus/client/ClientPDFHandling.py
@@ -22,8 +22,8 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusPDFHandling
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusPDFHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.client.gui import ClientGUIFunctions
diff --git a/hydrus/client/ClientRendering.py b/hydrus/client/ClientRendering.py
index 3f92f781e..2c780e16e 100644
--- a/hydrus/client/ClientRendering.py
+++ b/hydrus/client/ClientRendering.py
@@ -7,15 +7,15 @@
from qtpy import QtCore as QC
from qtpy import QtGui as QG
-from hydrus.core import HydrusAnimationHandling
from hydrus.core import HydrusCompression
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 HydrusVideoHandling
-from hydrus.core.images import HydrusImageColours
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusAnimationHandling
+from hydrus.core.files import HydrusVideoHandling
+from hydrus.core.files.images import HydrusImageColours
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.client import ClientFiles
from hydrus.client import ClientImageHandling
diff --git a/hydrus/client/ClientSVGHandling.py b/hydrus/client/ClientSVGHandling.py
index daf85c053..408c908f1 100644
--- a/hydrus/client/ClientSVGHandling.py
+++ b/hydrus/client/ClientSVGHandling.py
@@ -5,7 +5,7 @@
from qtpy import QtCore as QC
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusSVGHandling
+from hydrus.core.files import HydrusSVGHandling
from hydrus.client.gui import ClientGUIFunctions
diff --git a/hydrus/client/ClientSerialisable.py b/hydrus/client/ClientSerialisable.py
index 1eb5669cf..a4a6c4a85 100644
--- a/hydrus/client/ClientSerialisable.py
+++ b/hydrus/client/ClientSerialisable.py
@@ -14,7 +14,7 @@
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTemp
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.client import ClientConstants as CC
from hydrus.client.gui import ClientGUIFunctions
diff --git a/hydrus/client/ClientVideoHandling.py b/hydrus/client/ClientVideoHandling.py
index 71cb8d0ce..00b3da983 100644
--- a/hydrus/client/ClientVideoHandling.py
+++ b/hydrus/client/ClientVideoHandling.py
@@ -4,8 +4,8 @@
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusImageHandling
-from hydrus.core.images import HydrusImageNormalisation
+from hydrus.core.files.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageNormalisation
class GIFRenderer( object ):
diff --git a/hydrus/client/caches/ClientCaches.py b/hydrus/client/caches/ClientCaches.py
index 87397dd81..0b60c8b0c 100644
--- a/hydrus/client/caches/ClientCaches.py
+++ b/hydrus/client/caches/ClientCaches.py
@@ -6,13 +6,13 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusThreading
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusBlurhash
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusFileHandling
+from hydrus.core.files.images import HydrusBlurhash
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientFiles
diff --git a/hydrus/client/gui/ClientGUI.py b/hydrus/client/gui/ClientGUI.py
index 887a3243a..95bbc9c93 100644
--- a/hydrus/client/gui/ClientGUI.py
+++ b/hydrus/client/gui/ClientGUI.py
@@ -22,7 +22,6 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusEncryption
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusMemory
from hydrus.core import HydrusPaths
@@ -32,9 +31,10 @@
from hydrus.core import HydrusTemp
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
-from hydrus.core import HydrusVideoHandling
-from hydrus.core import HydrusPSDHandling
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusFileHandling
+from hydrus.core.files import HydrusPSDHandling
+from hydrus.core.files import HydrusVideoHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.core.networking import HydrusNetwork
from hydrus.core.networking import HydrusNetworking
diff --git a/hydrus/client/gui/ClientGUIFileSeedCache.py b/hydrus/client/gui/ClientGUIFileSeedCache.py
index 20c6b818d..2af94fb21 100644
--- a/hydrus/client/gui/ClientGUIFileSeedCache.py
+++ b/hydrus/client/gui/ClientGUIFileSeedCache.py
@@ -295,7 +295,7 @@ def PopulateFileSeedCacheMenu( win: QW.QWidget, menu: QW.QMenu, file_seed_cache:
class EditFileSeedCachePanel( ClientGUIScrolledPanels.EditPanel ):
- def __init__( self, parent, controller, file_seed_cache ):
+ def __init__( self, parent, controller, file_seed_cache: ClientImportFileSeeds.FileSeedCache ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
@@ -582,7 +582,16 @@ def _GetListCtrlMenu( self ):
ClientGUIMenus.AppendSeparator( menu )
- ClientGUIMenus.AppendMenuItem( menu, 'open sources', 'Open all the selected sources in your file explorer or web browser.', self._OpenSelectedFileSeedData )
+ if self._file_seed_cache.IsURLFileSeeds():
+
+ open_sources_text = 'open URLs'
+
+ else:
+
+ open_sources_text = 'open files\' locations'
+
+
+ ClientGUIMenus.AppendMenuItem( menu, open_sources_text, 'Open all the selected sources in your file explorer or web browser.', self._OpenSelectedFileSeedData )
ClientGUIMenus.AppendSeparator( menu )
diff --git a/hydrus/client/gui/ClientGUIFunctions.py b/hydrus/client/gui/ClientGUIFunctions.py
index dc7b6cb58..678a4523a 100644
--- a/hydrus/client/gui/ClientGUIFunctions.py
+++ b/hydrus/client/gui/ClientGUIFunctions.py
@@ -13,7 +13,7 @@
from hydrus.client.gui import ClientGUIDialogsMessage
from hydrus.client.gui import QtInit
from hydrus.client.gui import QtPorting as QP
-from hydrus.core.images import HydrusImageNormalisation
+from hydrus.core.files.images import HydrusImageNormalisation
def ClientToScreen( win: QW.QWidget, pos: QC.QPoint ) -> QC.QPoint:
diff --git a/hydrus/client/gui/ClientGUIMediaActions.py b/hydrus/client/gui/ClientGUIMediaActions.py
index e0d49ee4c..c69061131 100644
--- a/hydrus/client/gui/ClientGUIMediaActions.py
+++ b/hydrus/client/gui/ClientGUIMediaActions.py
@@ -11,8 +11,8 @@
from hydrus.core import HydrusLists
from hydrus.core import HydrusThreading
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusImageMetadata
-from hydrus.core.images import HydrusImageOpening
+from hydrus.core.files.images import HydrusImageMetadata
+from hydrus.core.files.images import HydrusImageOpening
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
diff --git a/hydrus/client/gui/ClientGUIScrolledPanelsEdit.py b/hydrus/client/gui/ClientGUIScrolledPanelsEdit.py
index f61451153..5771bbf00 100644
--- a/hydrus/client/gui/ClientGUIScrolledPanelsEdit.py
+++ b/hydrus/client/gui/ClientGUIScrolledPanelsEdit.py
@@ -560,11 +560,11 @@ def __init__( self, parent: QW.QWidget, media, default_reason, suggested_file_se
self._default_reason = default_reason
- local_file_services = list( HG.client_controller.services_manager.GetServices( ( HC.LOCAL_FILE_DOMAIN, ) ) )
+ local_file_service_domains = list( HG.client_controller.services_manager.GetServices( ( HC.LOCAL_FILE_DOMAIN, ) ) )
if suggested_file_service_key is None:
- suggested_file_service_key = local_file_services[0].GetServiceKey()
+ suggested_file_service_key = local_file_service_domains[0].GetServiceKey()
self._media = self._FilterForDeleteLock( ClientMedia.FlattenMedia( media ), suggested_file_service_key )
@@ -575,7 +575,7 @@ def __init__( self, parent: QW.QWidget, media, default_reason, suggested_file_se
self._simple_description = ClientGUICommon.BetterStaticText( self, label = 'init' )
- self._num_actionable_local_file_services = 0
+ self._num_actionable_local_file_service_domains = 0
self._permitted_action_choices = []
self._this_dialog_includes_service_keys = False
@@ -816,10 +816,10 @@ def _InitialisePermittedActionChoices( self ):
possible_file_service_keys = []
- local_file_services = list( HG.client_controller.services_manager.GetServices( ( HC.LOCAL_FILE_DOMAIN, ) ) )
- local_file_service_keys = { service.GetServiceKey() for service in local_file_services }
+ local_file_service_domains = list( HG.client_controller.services_manager.GetServices( ( HC.LOCAL_FILE_DOMAIN, ) ) )
+ local_file_service_domain_keys = { service.GetServiceKey() for service in local_file_service_domains }
- possible_file_service_keys.extend( ( ( lfs.GetServiceKey(), lfs.GetServiceKey() ) for lfs in local_file_services ) )
+ possible_file_service_keys.extend( ( ( lfs.GetServiceKey(), lfs.GetServiceKey() ) for lfs in local_file_service_domains ) )
possible_file_service_keys.append( ( CC.TRASH_SERVICE_KEY, CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
@@ -827,6 +827,11 @@ def _InitialisePermittedActionChoices( self ):
possible_file_service_keys.append( ( CC.COMBINED_LOCAL_FILE_SERVICE_KEY, CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
+ else:
+
+ # if not advanced, we still want regular users, in odd fixing situations, able to delete update files
+ possible_file_service_keys.append( ( CC.LOCAL_UPDATE_SERVICE_KEY, CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
+
possible_file_service_keys.extend( ( ( rfs.GetServiceKey(), rfs.GetServiceKey() ) for rfs in HG.client_controller.services_manager.GetServices( ( HC.FILE_REPOSITORY, ) ) ) )
@@ -842,7 +847,7 @@ def _InitialisePermittedActionChoices( self ):
possible_file_service_keys_and_hashes = [ ( fsk, keys_to_hashes[ fsk ] ) for fsk in possible_file_service_keys if fsk in keys_to_hashes and len( keys_to_hashes[ fsk ] ) > 0 ]
- self._num_actionable_local_file_services = len( local_file_service_keys.intersection( ( fsk[0] for ( fsk, hashes ) in possible_file_service_keys_and_hashes ) ) )
+ self._num_actionable_local_file_service_domains = len( local_file_service_domain_keys.intersection( ( fsk[0] for ( fsk, hashes ) in possible_file_service_keys_and_hashes ) ) )
possibilities_involve_spicy_physical_delete = False
@@ -872,7 +877,7 @@ def _InitialisePermittedActionChoices( self ):
file_desc = '{} files'.format( HydrusData.ToHumanInt( num_to_delete ) )
- if self._num_actionable_local_file_services == 1:
+ if self._num_actionable_local_file_service_domains == 1:
template = 'Send {} from {} to trash?'
@@ -898,7 +903,7 @@ def _InitialisePermittedActionChoices( self ):
num_local_services_done += 1
# this is an ugly place to put this, and the mickey-mouse append, but it works
- if self._num_actionable_local_file_services > 1 and num_local_services_done == self._num_actionable_local_file_services:
+ if self._num_actionable_local_file_service_domains > 1 and num_local_services_done == self._num_actionable_local_file_service_domains:
self._permitted_action_choices.append( ( text, ( deletee_file_service_key, list_of_service_keys_to_content_updates, save_reason, hashes_physically_deleted, text ) ) )
@@ -1000,7 +1005,7 @@ def _InitialisePermittedActionChoices( self ):
unnatural_spicy_physical_delete = possibilities_involve_spicy_physical_delete and not we_are_advanced_delete_dialog
- if self._num_actionable_local_file_services == 1 and not unnatural_spicy_physical_delete and not HC.options[ 'confirm_trash' ]:
+ if self._num_actionable_local_file_service_domains == 1 and not unnatural_spicy_physical_delete and not HC.options[ 'confirm_trash' ]:
# this dialog will never show
self._question_is_already_resolved = True
diff --git a/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py b/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py
index 655de0c33..77ac3323a 100644
--- a/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py
+++ b/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py
@@ -16,7 +16,7 @@
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
diff --git a/hydrus/client/gui/ClientGUIScrolledPanelsReview.py b/hydrus/client/gui/ClientGUIScrolledPanelsReview.py
index 94cc918f4..7d9fac360 100644
--- a/hydrus/client/gui/ClientGUIScrolledPanelsReview.py
+++ b/hydrus/client/gui/ClientGUIScrolledPanelsReview.py
@@ -15,7 +15,6 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
@@ -24,6 +23,7 @@
from hydrus.core import HydrusText
from hydrus.core import HydrusThreading
from hydrus.core import HydrusTime
+from hydrus.core.files import HydrusFileHandling
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
diff --git a/hydrus/client/gui/ClientGUITags.py b/hydrus/client/gui/ClientGUITags.py
index cab689b4a..dda3a4e4f 100644
--- a/hydrus/client/gui/ClientGUITags.py
+++ b/hydrus/client/gui/ClientGUITags.py
@@ -823,70 +823,94 @@ def __init__( self, parent, tag_filter, only_show_blacklist = False, namespaces
self.SetValue( tag_filter )
- def _AdvancedAddBlacklist( self, tag_slice ):
+ def _AdvancedAddBlacklistMultiple( self, tag_slices ):
+
+ tag_slices = [ self._CleanTagSliceInput( tag_slice ) for tag_slice in tag_slices ]
+
+ tag_slices = HydrusData.DedupeList( tag_slices )
+
+ current_blacklist = set( self._advanced_blacklist.GetTagSlices() )
- tag_slice = self._CleanTagSliceInput( tag_slice )
+ to_remove = set( tag_slices ).intersection( current_blacklist )
- if tag_slice in self._advanced_blacklist.GetTagSlices():
+ if len( to_remove ) > 0:
- self._advanced_blacklist.RemoveTagSlices( ( tag_slice, ) )
+ self._advanced_blacklist.RemoveTagSlices( to_remove )
- else:
+
+ to_add = [ tag_slice for tag_slice in tag_slices if tag_slice not in to_remove ]
+
+ if len( to_add ) > 0:
- self._advanced_whitelist.RemoveTagSlices( ( tag_slice, ) )
+ self._advanced_whitelist.RemoveTagSlices( to_add )
- if self._CurrentlyBlocked( tag_slice ):
+ already_blocked = [ tag_slice for tag_slice in to_add if self._CurrentlyBlocked( tag_slice ) ]
+
+ if len( already_blocked ) > 0:
+
+ if len( already_blocked ) == 1:
+
+ message = f'{HydrusTags.ConvertTagSliceToString( already_blocked[0] )} is already blocked by a broader rule!'
+
+ else:
+
+ separator = '\n' if len( already_blocked ) < 5 else ', '
+
+ message = 'The tags\n\n' + separator.join( [ HydrusTags.ConvertTagSliceToString( tag_slice ) for tag_slice in already_blocked ] ) + '\n\nare already blocked by a broader rule!'
+
- self._ShowRedundantError( HydrusTags.ConvertTagSliceToString( tag_slice ) + ' is already blocked by a broader rule!' )
+ self._ShowRedundantError( message )
- self._advanced_blacklist.AddTagSlices( ( tag_slice, ) )
+ self._advanced_blacklist.AddTagSlices( to_add )
self._UpdateStatus()
- def _AdvancedAddBlacklistMultiple( self, tag_slices ):
+ def _AdvancedAddWhitelistMultiple( self, tag_slices ):
- for tag_slice in tag_slices:
-
- self._AdvancedAddBlacklist( tag_slice )
-
+ tag_slices = [ self._CleanTagSliceInput( tag_slice ) for tag_slice in tag_slices ]
-
- def _AdvancedAddWhitelist( self, tag_slice ):
+ current_whitelist = set( self._advanced_whitelist.GetTagSlices() )
- tag_slice = self._CleanTagSliceInput( tag_slice )
+ to_remove = set( tag_slices ).intersection( current_whitelist )
- if tag_slice in self._advanced_whitelist.GetTagSlices():
+ if len( to_remove ) > 0:
- self._advanced_whitelist.RemoveTagSlices( ( tag_slice, ) )
+ self._advanced_whitelist.RemoveTagSlices( to_remove )
- else:
+
+ to_add = [ tag_slice for tag_slice in tag_slices if tag_slice not in to_remove ]
+
+ if len( to_add ) > 0:
- self._advanced_blacklist.RemoveTagSlices( ( tag_slice, ) )
+ self._advanced_blacklist.RemoveTagSlices( to_add )
- # if it is still blocked after that, it needs whitelisting explicitly
+ already_permitted = [ tag_slice for tag_slice in to_add if tag_slice not in ( '', ':' ) and not self._CurrentlyBlocked( tag_slice ) ]
- if not self._CurrentlyBlocked( tag_slice ) and tag_slice not in ( '', ':' ):
+ if len( already_permitted ) > 0:
+
+ if len( already_permitted ) == 1:
+
+ message = f'{HydrusTags.ConvertTagSliceToString( to_add[0] )} is already permitted by a broader rule!'
+
+ else:
+
+ separator = '\n' if len( already_permitted ) < 5 else ', '
+
+ message = 'The tags\n\n' + separator.join( [ HydrusTags.ConvertTagSliceToString( tag_slice ) for tag_slice in already_permitted ] ) + '\n\nare already permitted by a broader rule!'
+
- self._ShowRedundantError( HydrusTags.ConvertTagSliceToString( tag_slice ) + ' is already permitted by a broader rule!' )
+ self._ShowRedundantError( message )
- self._advanced_whitelist.AddTagSlices( ( tag_slice, ) )
+ self._advanced_whitelist.AddTagSlices( tag_slices )
self._UpdateStatus()
- def _AdvancedAddWhitelistMultiple( self, tag_slices ):
-
- for tag_slice in tag_slices:
-
- self._AdvancedAddWhitelist( tag_slice )
-
-
-
def _AdvancedBlacklistEverything( self ):
self._advanced_blacklist.SetTagSlices( [] )
@@ -1456,25 +1480,24 @@ def _SimpleAddBlacklistMultiple( self, tag_slices ):
def _SimpleAddWhitelistMultiple( self, tag_slices ):
- for tag_slice in tag_slices:
+ tag_slices = set( tag_slices )
+
+ for simple in ( '', ':' ):
- if tag_slice in ( '', ':' ) and tag_slice in self._simple_whitelist.GetTagSlices():
-
- self._AdvancedAddBlacklist( tag_slice )
+ if simple in tag_slices and simple in self._simple_whitelist.GetTagSlices():
- else:
+ tag_slices.discard( simple )
- self._AdvancedAddWhitelist( tag_slice )
+ self._AdvancedAddBlacklistMultiple( ( simple, ) )
+ self._AdvancedAddWhitelistMultiple( tag_slices )
+
def _SimpleBlacklistRemoved( self, tag_slices ):
- for tag_slice in tag_slices:
-
- self._AdvancedAddBlacklist( tag_slice )
-
+ self._AdvancedAddBlacklistMultiple( tag_slices )
def _SimpleBlacklistReset( self ):
@@ -1530,14 +1553,11 @@ def _SimpleWhitelistRemoved( self, tag_slices ):
tag_slices.discard( simple )
- self._AdvancedAddBlacklist( simple )
+ self._AdvancedAddBlacklistMultiple( ( simple, ) )
- for tag_slice in tag_slices:
-
- self._AdvancedAddWhitelist( tag_slice )
-
+ self._AdvancedAddWhitelistMultiple( tag_slices )
def _SimpleWhitelistReset( self ):
@@ -1808,7 +1828,7 @@ def EventSimpleBlacklistNamespaceCheck( self, index ):
tag_slice = self._simple_blacklist_namespace_checkboxes.GetData( index )
- self._AdvancedAddBlacklist( tag_slice )
+ self._AdvancedAddBlacklistMultiple( ( tag_slice, ) )
@@ -1820,7 +1840,7 @@ def EventSimpleBlacklistGlobalCheck( self, index ):
tag_slice = self._simple_blacklist_global_checkboxes.GetData( index )
- self._AdvancedAddBlacklist( tag_slice )
+ self._AdvancedAddBlacklistMultiple( ( tag_slice, ) )
@@ -1832,12 +1852,12 @@ def EventSimpleWhitelistNamespaceCheck( self, index ):
tag_slice = self._simple_whitelist_namespace_checkboxes.GetData( index )
- self._AdvancedAddWhitelist( tag_slice )
+ self._AdvancedAddWhitelistMultiple( ( tag_slice, ) )
def EventSimpleWhitelistGlobalCheck( self, index ):
-
+
index = index.row()
if index != -1:
@@ -1846,11 +1866,11 @@ def EventSimpleWhitelistGlobalCheck( self, index ):
if tag_slice in ( '', ':' ) and tag_slice in self._simple_whitelist.GetTagSlices():
- self._AdvancedAddBlacklist( tag_slice )
+ self._AdvancedAddBlacklistMultiple( ( tag_slice, ) )
else:
- self._AdvancedAddWhitelist( tag_slice )
+ self._AdvancedAddWhitelistMultiple( ( tag_slice, ) )
@@ -1859,15 +1879,8 @@ def GetValue( self ):
tag_filter = HydrusTags.TagFilter()
- for tag_slice in self._advanced_blacklist.GetTagSlices():
-
- tag_filter.SetRule( tag_slice, HC.FILTER_BLACKLIST )
-
-
- for tag_slice in self._advanced_whitelist.GetTagSlices():
-
- tag_filter.SetRule( tag_slice, HC.FILTER_WHITELIST )
-
+ tag_filter.SetRules( self._advanced_blacklist.GetTagSlices(), HC.FILTER_BLACKLIST )
+ tag_filter.SetRules( self._advanced_whitelist.GetTagSlices(), HC.FILTER_WHITELIST )
return tag_filter
diff --git a/hydrus/client/gui/canvas/ClientGUICanvas.py b/hydrus/client/gui/canvas/ClientGUICanvas.py
index ca03af66f..78d1babfc 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvas.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvas.py
@@ -12,7 +12,7 @@
from hydrus.core import HydrusPaths
from hydrus.core import HydrusTags
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
diff --git a/hydrus/client/gui/canvas/ClientGUICanvasMedia.py b/hydrus/client/gui/canvas/ClientGUICanvasMedia.py
index f6698cb0a..e5caf9f88 100644
--- a/hydrus/client/gui/canvas/ClientGUICanvasMedia.py
+++ b/hydrus/client/gui/canvas/ClientGUICanvasMedia.py
@@ -19,12 +19,11 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
-from hydrus.core import HydrusPSDHandling
from hydrus.core import HydrusPaths
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusFileHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
diff --git a/hydrus/client/gui/canvas/ClientGUIMPV.py b/hydrus/client/gui/canvas/ClientGUIMPV.py
index 7ab0fd948..d15b427b3 100644
--- a/hydrus/client/gui/canvas/ClientGUIMPV.py
+++ b/hydrus/client/gui/canvas/ClientGUIMPV.py
@@ -7,11 +7,11 @@
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
-from hydrus.core import HydrusAnimationHandling
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths
+from hydrus.core.files import HydrusAnimationHandling
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
diff --git a/hydrus/client/gui/lists/ClientGUIListBoxesData.py b/hydrus/client/gui/lists/ClientGUIListBoxesData.py
index 81a0c89d6..1b47a6d49 100644
--- a/hydrus/client/gui/lists/ClientGUIListBoxesData.py
+++ b/hydrus/client/gui/lists/ClientGUIListBoxesData.py
@@ -516,7 +516,7 @@ def GetCopyableText( self, with_counts: bool = False ) -> str:
else:
- text = self._predicate.ToString( with_count = with_counts )
+ text = self._predicate.ToString( with_count = with_counts, for_parsable_export = True )
return text
diff --git a/hydrus/client/gui/pages/ClientGUIResults.py b/hydrus/client/gui/pages/ClientGUIResults.py
index a7eb6c623..0a7acbdc6 100644
--- a/hydrus/client/gui/pages/ClientGUIResults.py
+++ b/hydrus/client/gui/pages/ClientGUIResults.py
@@ -15,7 +15,7 @@
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.core.networking import HydrusNetwork
from hydrus.client import ClientApplicationCommand as CAC
diff --git a/hydrus/client/gui/parsing/ClientGUIParsingTest.py b/hydrus/client/gui/parsing/ClientGUIParsingTest.py
index 072a29fdb..30f53b37c 100644
--- a/hydrus/client/gui/parsing/ClientGUIParsingTest.py
+++ b/hydrus/client/gui/parsing/ClientGUIParsingTest.py
@@ -9,10 +9,10 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusTemp
from hydrus.core import HydrusText
+from hydrus.core.files import HydrusFileHandling
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientParsing
diff --git a/hydrus/client/gui/search/ClientGUIPredicatesSingle.py b/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
index e755f781a..4fc78488f 100644
--- a/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
+++ b/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
@@ -9,8 +9,8 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
-from hydrus.core import HydrusFileHandling
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusFileHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientImageHandling
diff --git a/hydrus/client/importing/ClientImportFileSeeds.py b/hydrus/client/importing/ClientImportFileSeeds.py
index 576d5b572..f5ba7067a 100644
--- a/hydrus/client/importing/ClientImportFileSeeds.py
+++ b/hydrus/client/importing/ClientImportFileSeeds.py
@@ -12,13 +12,13 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusTemp
from hydrus.core import HydrusTime
+from hydrus.core.files import HydrusFileHandling
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
@@ -2869,7 +2869,7 @@ def GetEarliestSourceTime( self ):
return earliest_timestamp
- def GetExampleFileSeed( self ):
+ def GetExampleURLFileSeed( self ):
with self._lock:
@@ -3096,6 +3096,18 @@ def InsertFileSeeds( self, index: int, file_seeds: typing.Collection[ FileSeed ]
return len( new_file_seeds )
+ def IsURLFileSeeds( self ) -> bool:
+
+ if len( self._file_seeds ) == 0:
+
+ return True
+
+
+ first = self._file_seeds[0]
+
+ return first.file_seed_type == FILE_SEED_TYPE_URL
+
+
def NotifyFileSeedsUpdated( self, file_seeds: typing.Collection[ FileSeed ] ):
if len( file_seeds ) == 0:
diff --git a/hydrus/client/importing/ClientImportFiles.py b/hydrus/client/importing/ClientImportFiles.py
index 7961eaf5e..0226b506f 100644
--- a/hydrus/client/importing/ClientImportFiles.py
+++ b/hydrus/client/importing/ClientImportFiles.py
@@ -1,14 +1,14 @@
from hydrus.core import HydrusConstants as HC
-from hydrus.core import HydrusPSDHandling
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusBlurhash
-from hydrus.core.images import HydrusImageHandling
-from hydrus.core.images import HydrusImageMetadata
-from hydrus.core.images import HydrusImageOpening
+from hydrus.core.files import HydrusFileHandling
+from hydrus.core.files import HydrusPSDHandling
+from hydrus.core.files.images import HydrusBlurhash
+from hydrus.core.files.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageMetadata
+from hydrus.core.files.images import HydrusImageOpening
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientFiles
diff --git a/hydrus/client/importing/ClientImportLocal.py b/hydrus/client/importing/ClientImportLocal.py
index da68cb41b..6fd5a8a5a 100644
--- a/hydrus/client/importing/ClientImportLocal.py
+++ b/hydrus/client/importing/ClientImportLocal.py
@@ -7,12 +7,12 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusThreading
from hydrus.core import HydrusTime
+from hydrus.core.files import HydrusFileHandling
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
diff --git a/hydrus/client/importing/ClientImportSubscriptionQuery.py b/hydrus/client/importing/ClientImportSubscriptionQuery.py
index fdc805ad2..d8b86a41a 100644
--- a/hydrus/client/importing/ClientImportSubscriptionQuery.py
+++ b/hydrus/client/importing/ClientImportSubscriptionQuery.py
@@ -799,7 +799,7 @@ def UpdateFileStatus( self, query_log_container: SubscriptionQueryLogContainer )
file_seed_cache = query_log_container.GetFileSeedCache()
self._file_seed_cache_status = file_seed_cache.GetStatus()
- self._example_file_seed = file_seed_cache.GetExampleFileSeed()
+ self._example_file_seed = file_seed_cache.GetExampleURLFileSeed()
def WantsToResyncWithLogContainer( self ):
diff --git a/hydrus/client/media/ClientMedia.py b/hydrus/client/media/ClientMedia.py
index 77dc9f31e..16bd5fb8f 100644
--- a/hydrus/client/media/ClientMedia.py
+++ b/hydrus/client/media/ClientMedia.py
@@ -6,9 +6,9 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
-from hydrus.core import HydrusPSDHandling
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
+from hydrus.core.files import HydrusPSDHandling
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientLocation
diff --git a/hydrus/client/networking/ClientLocalServerResources.py b/hydrus/client/networking/ClientLocalServerResources.py
index 0e98444f2..eb4deedfc 100644
--- a/hydrus/client/networking/ClientLocalServerResources.py
+++ b/hydrus/client/networking/ClientLocalServerResources.py
@@ -24,13 +24,13 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths
from hydrus.core import HydrusTags
from hydrus.core import HydrusTemp
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusFileHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.core.networking import HydrusNetworkVariableHandling
from hydrus.core.networking import HydrusServerRequest
from hydrus.core.networking import HydrusServerResources
@@ -2505,6 +2505,8 @@ def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ):
json_happy_url_statuses = []
+ we_only_saw_successful = True
+
for file_import_status in url_statuses:
if do_file_system_check:
@@ -2520,6 +2522,11 @@ def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ):
json_happy_url_statuses.append( d )
+ if file_import_status.status not in CC.SUCCESSFUL_IMPORT_STATES:
+
+ we_only_saw_successful = False
+
+
body_dict = { 'normalised_url' : normalised_url, 'url_file_statuses' : json_happy_url_statuses }
@@ -2527,6 +2534,12 @@ def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ):
response_context = HydrusServerResources.ResponseContext( 200, mime = request.preferred_mime, body = body )
+ if we_only_saw_successful:
+
+ # not likely to change much, so no worries about reducing overhead here
+ response_context.SetMaxAge( 30 )
+
+
return response_context
@@ -2561,7 +2574,8 @@ def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ):
body = Dumps( body_dict, request.preferred_mime )
- response_context = HydrusServerResources.ResponseContext( 200, mime = request.preferred_mime, body = body )
+ # max age of ten minutes here
+ response_context = HydrusServerResources.ResponseContext( 200, mime = request.preferred_mime, body = body, max_age = 600 )
return response_context
@@ -3116,7 +3130,16 @@ def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ):
'num_words' : file_info_manager.num_words,
'has_audio' : file_info_manager.has_audio
}
-
+
+ filetype_forced = file_info_manager.FiletypeIsForced()
+
+ metadata_row[ 'filetype_forced' ] = filetype_forced
+
+ if filetype_forced:
+
+ metadata_row[ 'original_mime' ] = HC.mime_mimetype_string_lookup[ file_info_manager.original_mime ]
+
+
if include_blurhash:
metadata_row[ 'blurhash' ] = file_info_manager.blurhash
@@ -3179,6 +3202,15 @@ def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ):
'pixel_hash' : None if file_info_manager.pixel_hash is None else file_info_manager.pixel_hash.hex()
}
+ filetype_forced = file_info_manager.FiletypeIsForced()
+
+ metadata_row[ 'filetype_forced' ] = filetype_forced
+
+ if filetype_forced:
+
+ metadata_row[ 'original_mime' ] = HC.mime_mimetype_string_lookup[ file_info_manager.original_mime ]
+
+
if file_info_manager.mime in HC.MIMES_WITH_THUMBNAILS:
if width is not None and height is not None and width > 0 and height > 0:
diff --git a/hydrus/client/search/ClientSearch.py b/hydrus/client/search/ClientSearch.py
index 964df813f..be583a2fd 100644
--- a/hydrus/client/search/ClientSearch.py
+++ b/hydrus/client/search/ClientSearch.py
@@ -2381,7 +2381,7 @@ def SetKnownSiblings( self, siblings: typing.Set[ str ] ):
self._RecalculateMatchableSearchTexts()
- def ToString( self, with_count: bool = True, tag_display_type: int = ClientTags.TAG_DISPLAY_DISPLAY_ACTUAL, render_for_user: bool = False, or_under_construction: bool = False ) -> str:
+ def ToString( self, with_count: bool = True, render_for_user: bool = False, or_under_construction: bool = False, for_parsable_export: bool = False ) -> str:
base = ''
count_text = ''
@@ -2839,13 +2839,25 @@ def ToString( self, with_count: bool = True, tag_display_type: int = ClientTags.
is_phrase = 'is not'
- if len( hashes ) == 1:
+ if len( hashes ) > 1:
- base = '{} hash {} {}'.format( hash_type, is_phrase, hashes[0].hex() )
+ is_phrase += ' in'
+
+
+ if hash_type != 'sha256':
+
+ base = f'hash ({hash_type})'
+
+
+ if len( hashes ) == 1 or for_parsable_export:
+
+ hashes_string = ', '.join( ( hash.hex() for hash in hashes ) )
+
+ base = f'{base} {is_phrase} {hashes_string}'
else:
- base = '{} hash {} in {} hashes'.format( hash_type, is_phrase, HydrusData.ToHumanInt( len( hashes ) ) )
+ base = f'{base} {is_phrase} {HydrusData.ToHumanInt( len( hashes ) )} hashes'
@@ -2905,18 +2917,59 @@ def ToString( self, with_count: bool = True, tag_display_type: int = ClientTags.
( hashes, max_hamming ) = self._value
- base += ' {} files using max hamming of {}'.format( HydrusData.ToHumanInt( len( hashes ) ), max_hamming )
+ if for_parsable_export:
+
+ hash_string = ', '.join( ( hash.hex() for hash in hashes ) )
+
+ else:
+
+ hash_string = f'{HydrusData.ToHumanInt( len( hashes ) )} files'
+
+
+ base += f' {hash_string} with distance of {max_hamming}'
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_SIMILAR_TO_DATA:
- base = 'similar to'
+ base = 'similar to data'
if self._value is not None:
( pixel_hashes, perceptual_hashes, max_hamming ) = self._value
- base += ' {} similar data hashes using max hamming of {}'.format( HydrusData.ToHumanInt( len( pixel_hashes ) + len( perceptual_hashes ) ), max_hamming )
+ all_hashes = list( pixel_hashes ) + list( perceptual_hashes )
+
+ if for_parsable_export:
+
+ hash_string = ', '.join( ( hash.hex() for hash in all_hashes ) )
+
+ else:
+
+ components = []
+
+ if len( pixel_hashes ) > 0:
+
+ components.append( f'{HydrusData.ToHumanInt( len( pixel_hashes ) )} pixel')
+
+
+ if len( perceptual_hashes ) > 0:
+
+ components.append( f'{HydrusData.ToHumanInt( len( perceptual_hashes ) )} perceptual')
+
+
+ component_string = ', '.join( components )
+
+ hash_string = f'({component_string} hashes)'
+
+
+ if len( perceptual_hashes ) > 0:
+
+ base += f' {hash_string} with distance of {max_hamming}'
+
+ else:
+
+ base += f' {hash_string}'
+
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_FILE_SERVICE:
diff --git a/hydrus/core/HydrusConstants.py b/hydrus/core/HydrusConstants.py
index 1405ff9a1..e7789228e 100644
--- a/hydrus/core/HydrusConstants.py
+++ b/hydrus/core/HydrusConstants.py
@@ -105,8 +105,8 @@
# Misc
NETWORK_VERSION = 20
-SOFTWARE_VERSION = 556
-CLIENT_API_VERSION = 57
+SOFTWARE_VERSION = 557
+CLIENT_API_VERSION = 58
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
diff --git a/hydrus/core/HydrusTags.py b/hydrus/core/HydrusTags.py
index 65243dcd1..f8c0b8342 100644
--- a/hydrus/core/HydrusTags.py
+++ b/hydrus/core/HydrusTags.py
@@ -739,6 +739,19 @@ def SetRule( self, tag_slice, rule ):
+ def SetRules( self, tag_slices, rule ):
+
+ with self._lock:
+
+ for tag_slice in tag_slices:
+
+ self._tag_slices_to_rules[ tag_slice ] = rule
+
+
+ self._UpdateRuleCache()
+
+
+
def TagOK( self, tag, apply_unnamespaced_rules_to_namespaced_tags = False ):
with self._lock:
diff --git a/hydrus/core/HydrusAnimationHandling.py b/hydrus/core/files/HydrusAnimationHandling.py
similarity index 98%
rename from hydrus/core/HydrusAnimationHandling.py
rename to hydrus/core/files/HydrusAnimationHandling.py
index ef3067e43..31ec30d35 100644
--- a/hydrus/core/HydrusAnimationHandling.py
+++ b/hydrus/core/files/HydrusAnimationHandling.py
@@ -7,8 +7,8 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
-from hydrus.core.images import HydrusImageHandling
-from hydrus.core.images import HydrusImageOpening
+from hydrus.core.files.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageOpening
def GetAnimationProperties( path, mime ):
diff --git a/hydrus/core/HydrusArchiveHandling.py b/hydrus/core/files/HydrusArchiveHandling.py
similarity index 79%
rename from hydrus/core/HydrusArchiveHandling.py
rename to hydrus/core/files/HydrusArchiveHandling.py
index 1bd58cdf7..83caec14d 100644
--- a/hydrus/core/HydrusArchiveHandling.py
+++ b/hydrus/core/files/HydrusArchiveHandling.py
@@ -69,7 +69,7 @@ def GetZipAsPath( path_to_zip, path_in_zip="" ):
return zipfile.Path( path_to_zip, at=path_in_zip )
-def IsOpenableZip( path_to_zip ):
+def IsEncryptedZip( path_to_zip ):
ENCRYPTED_FLAG = 0x1
@@ -85,18 +85,48 @@ def IsOpenableZip( path_to_zip ):
if is_encrypted:
- return False
+ return True
- return True
+ return False
except:
- return False
+ raise HydrusExceptions.DamagedOrUnusualFileException( 'Could not open this zip at all!' )
+
+
+
+def filename_has_image_ext( filename: str ):
+
+ if '.' in filename:
+
+ ext_with_dot = '.' + filename.split( '.' )[-1]
+
+ if ext_with_dot in HC.IMAGE_FILE_EXTS:
+
+ return True
+
+
+
+ return False
+
+
+def filename_has_video_ext( filename: str ):
+
+ if '.' in filename:
+
+ ext_with_dot = '.' + filename.split( '.' )[-1]
+
+ if ext_with_dot in HC.VIDEO_FILE_EXTS:
+
+ return True
+
+ return False
+
def ZipLooksLikeCBZ( path_to_zip ):
@@ -148,27 +178,22 @@ def ZipLooksLikeCBZ( path_to_zip ):
continue
- if '.' in filename:
+ if filename_has_image_ext( filename ):
- ext_with_dot = '.' + filename.split( '.' )[-1]
+ num_images += 1
- if ext_with_dot in HC.IMAGE_FILE_EXTS:
-
- num_images += 1
-
- directories_to_image_filenames[ directory_path ].add( filename )
-
- continue
-
- elif ext_with_dot in HC.VIDEO_FILE_EXTS:
-
- # this catches some zips nicely
- return False
-
- else:
-
- num_weird_files += 1
-
+ directories_to_image_filenames[ directory_path ].add( filename )
+
+ continue
+
+ elif filename_has_video_ext( filename ):
+
+ # this catches some zips nicely
+ return False
+
+ else:
+
+ num_weird_files += 1
@@ -191,16 +216,16 @@ def ZipLooksLikeCBZ( path_to_zip ):
directories_to_looks_good_scores = {}
- for ( directory_path, filenames ) in directories_to_image_filenames.items():
+ for ( directory_path, image_filenames ) in directories_to_image_filenames.items():
# ok, so a zip that has fifteen different filename styles is not a cbz
# one that is all "Coolguy Adventures-c4-p001.jpg" however is!
- # so let's take all the numbers and figure out how commonly the filenames are templated
+ # so let's take all the numbers and figure out how commonly the image filenames are templated
- unique_numberless_filenames = { re.sub( r'\d', '', filename ) for filename in filenames }
+ unique_numberless_filenames = { re.sub( r'\d', '', filename ) for filename in image_filenames }
- magical_uniqueness_percentage = len( unique_numberless_filenames ) / len( filenames )
+ magical_uniqueness_percentage = ( len( unique_numberless_filenames ) - 1 ) / len( image_filenames )
directories_to_looks_good_scores[ directory_path ] = magical_uniqueness_percentage
@@ -209,7 +234,7 @@ def ZipLooksLikeCBZ( path_to_zip ):
average_directory_good = sum( all_percentages ) / len( all_percentages )
- # experimentally, I haven't seen it go above 0.138 on a legit cbz
+ # experimentally, I haven't seen this go above 0.103 on a legit cbz
if average_directory_good > 0.2:
return False
diff --git a/hydrus/core/HydrusAudioHandling.py b/hydrus/core/files/HydrusAudioHandling.py
similarity index 100%
rename from hydrus/core/HydrusAudioHandling.py
rename to hydrus/core/files/HydrusAudioHandling.py
diff --git a/hydrus/core/HydrusClipHandling.py b/hydrus/core/files/HydrusClipHandling.py
similarity index 100%
rename from hydrus/core/HydrusClipHandling.py
rename to hydrus/core/files/HydrusClipHandling.py
diff --git a/hydrus/core/HydrusDocumentHandling.py b/hydrus/core/files/HydrusDocumentHandling.py
similarity index 100%
rename from hydrus/core/HydrusDocumentHandling.py
rename to hydrus/core/files/HydrusDocumentHandling.py
diff --git a/hydrus/core/HydrusFileHandling.py b/hydrus/core/files/HydrusFileHandling.py
similarity index 96%
rename from hydrus/core/HydrusFileHandling.py
rename to hydrus/core/files/HydrusFileHandling.py
index eeee3640d..68e79b110 100644
--- a/hydrus/core/HydrusFileHandling.py
+++ b/hydrus/core/files/HydrusFileHandling.py
@@ -1,26 +1,26 @@
import hashlib
import os
-from hydrus.core import HydrusAnimationHandling
-from hydrus.core import HydrusArchiveHandling
-from hydrus.core import HydrusClipHandling
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
-from hydrus.core import HydrusDocumentHandling
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFlashHandling
-from hydrus.core import HydrusKritaHandling
-from hydrus.core import HydrusProcreateHandling
-from hydrus.core import HydrusPSDHandling
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
-from hydrus.core import HydrusSVGHandling
-from hydrus.core import HydrusPDFHandling
from hydrus.core import HydrusTemp
from hydrus.core import HydrusText
-from hydrus.core import HydrusUgoiraHandling
-from hydrus.core import HydrusVideoHandling
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusAnimationHandling
+from hydrus.core.files import HydrusArchiveHandling
+from hydrus.core.files import HydrusClipHandling
+from hydrus.core.files import HydrusDocumentHandling
+from hydrus.core.files import HydrusFlashHandling
+from hydrus.core.files import HydrusKritaHandling
+from hydrus.core.files import HydrusPDFHandling
+from hydrus.core.files import HydrusProcreateHandling
+from hydrus.core.files import HydrusPSDHandling
+from hydrus.core.files import HydrusSVGHandling
+from hydrus.core.files import HydrusUgoiraHandling
+from hydrus.core.files import HydrusVideoHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.core.networking import HydrusNetwork
try:
@@ -692,7 +692,14 @@ def GetMime( path, ok_to_look_for_hydrus_updates = False ):
if mime == HC.APPLICATION_ZIP:
- if not HydrusArchiveHandling.IsOpenableZip( path ):
+ try:
+
+ if HydrusArchiveHandling.IsEncryptedZip( path ):
+
+ return HC.APPLICATION_ZIP
+
+
+ except HydrusExceptions.DamagedOrUnusualFileException:
return HC.APPLICATION_ZIP
diff --git a/hydrus/core/HydrusFlashHandling.py b/hydrus/core/files/HydrusFlashHandling.py
similarity index 100%
rename from hydrus/core/HydrusFlashHandling.py
rename to hydrus/core/files/HydrusFlashHandling.py
diff --git a/hydrus/core/HydrusKritaHandling.py b/hydrus/core/files/HydrusKritaHandling.py
similarity index 95%
rename from hydrus/core/HydrusKritaHandling.py
rename to hydrus/core/files/HydrusKritaHandling.py
index 86ab0a05d..f89103021 100644
--- a/hydrus/core/HydrusKritaHandling.py
+++ b/hydrus/core/files/HydrusKritaHandling.py
@@ -1,9 +1,9 @@
import io
import typing
-from hydrus.core import HydrusArchiveHandling
from hydrus.core import HydrusExceptions
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusArchiveHandling
+from hydrus.core.files.images import HydrusImageHandling
from PIL import Image as PILImage
import xml.etree.ElementTree as ET
diff --git a/hydrus/core/HydrusPDFHandling.py b/hydrus/core/files/HydrusPDFHandling.py
similarity index 100%
rename from hydrus/core/HydrusPDFHandling.py
rename to hydrus/core/files/HydrusPDFHandling.py
diff --git a/hydrus/core/HydrusPSDHandling.py b/hydrus/core/files/HydrusPSDHandling.py
similarity index 93%
rename from hydrus/core/HydrusPSDHandling.py
rename to hydrus/core/files/HydrusPSDHandling.py
index 154d4a360..25a0574e8 100644
--- a/hydrus/core/HydrusPSDHandling.py
+++ b/hydrus/core/files/HydrusPSDHandling.py
@@ -4,11 +4,11 @@
from PIL import Image as PILImage
from hydrus.core import HydrusExceptions
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageHandling
try:
- from hydrus.core import HydrusPSDTools
+ from hydrus.core.files import HydrusPSDTools
PSD_TOOLS_OK = True
diff --git a/hydrus/core/HydrusPSDTools.py b/hydrus/core/files/HydrusPSDTools.py
similarity index 97%
rename from hydrus/core/HydrusPSDTools.py
rename to hydrus/core/files/HydrusPSDTools.py
index d94227465..f3e0fd811 100644
--- a/hydrus/core/HydrusPSDTools.py
+++ b/hydrus/core/files/HydrusPSDTools.py
@@ -6,7 +6,7 @@
from psd_tools.api.pil_io import get_pil_mode, get_pil_channels, _create_image
from hydrus.core import HydrusExceptions
-from hydrus.core.images import HydrusImageNormalisation
+from hydrus.core.files.images import HydrusImageNormalisation
def PSDHasICCProfile( path: str ):
diff --git a/hydrus/core/HydrusProcreateHandling.py b/hydrus/core/files/HydrusProcreateHandling.py
similarity index 97%
rename from hydrus/core/HydrusProcreateHandling.py
rename to hydrus/core/files/HydrusProcreateHandling.py
index 94a3c9254..2cecd0d10 100644
--- a/hydrus/core/HydrusProcreateHandling.py
+++ b/hydrus/core/files/HydrusProcreateHandling.py
@@ -1,5 +1,5 @@
-from hydrus.core import HydrusArchiveHandling
from hydrus.core import HydrusExceptions
+from hydrus.core.files import HydrusArchiveHandling
import plistlib
# Mostly based on https://github.com/jaromvogel/ProcreateViewer/blob/master/ProcreatePython/ProcreateImageData.py
diff --git a/hydrus/core/HydrusSVGHandling.py b/hydrus/core/files/HydrusSVGHandling.py
similarity index 100%
rename from hydrus/core/HydrusSVGHandling.py
rename to hydrus/core/files/HydrusSVGHandling.py
diff --git a/hydrus/core/HydrusUgoiraHandling.py b/hydrus/core/files/HydrusUgoiraHandling.py
similarity index 90%
rename from hydrus/core/HydrusUgoiraHandling.py
rename to hydrus/core/files/HydrusUgoiraHandling.py
index 2d5e15220..ff8337630 100644
--- a/hydrus/core/HydrusUgoiraHandling.py
+++ b/hydrus/core/files/HydrusUgoiraHandling.py
@@ -1,10 +1,10 @@
import zipfile
-from hydrus.core import HydrusArchiveHandling
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusTemp
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusArchiveHandling
+from hydrus.core.files.images import HydrusImageHandling
def ExtractFrame( path_to_zip, frame_index, extract_path ):
@@ -58,7 +58,14 @@ def GetUgoiraProperties( path_to_zip ):
with zipfile.ZipFile( path_to_zip ) as zip_handle:
- num_frames = len( zip_handle.infolist() )
+ # let's discount the .js or .json
+
+ def is_js_stuff( s ):
+
+ return s.endswith( '.js' ) or s.endswith( '.json' )
+
+
+ num_frames = len( [ 1 for zip_info in zip_handle.infolist() if not is_js_stuff( zip_info.filename ) ] )
except:
diff --git a/hydrus/core/HydrusVideoHandling.py b/hydrus/core/files/HydrusVideoHandling.py
similarity index 99%
rename from hydrus/core/HydrusVideoHandling.py
rename to hydrus/core/files/HydrusVideoHandling.py
index 9b6ac6f51..7f45db2b9 100644
--- a/hydrus/core/HydrusVideoHandling.py
+++ b/hydrus/core/files/HydrusVideoHandling.py
@@ -5,12 +5,12 @@
import re
import subprocess
-from hydrus.core import HydrusAudioHandling
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusText
from hydrus.core import HydrusThreading
+from hydrus.core.files import HydrusAudioHandling
FFMPEG_MISSING_ERROR_PUBBED = False
FFMPEG_NO_CONTENT_ERROR_PUBBED = False
diff --git a/hydrus/core/images/__init__.py b/hydrus/core/files/__init__.py
similarity index 100%
rename from hydrus/core/images/__init__.py
rename to hydrus/core/files/__init__.py
diff --git a/hydrus/core/images/HydrusBlurhash.py b/hydrus/core/files/images/HydrusBlurhash.py
similarity index 96%
rename from hydrus/core/images/HydrusBlurhash.py
rename to hydrus/core/files/images/HydrusBlurhash.py
index 11ef4b807..ae4a505ed 100644
--- a/hydrus/core/images/HydrusBlurhash.py
+++ b/hydrus/core/files/images/HydrusBlurhash.py
@@ -3,7 +3,7 @@
from hydrus.external import blurhash as external_blurhash
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageHandling
def GetBlurhashFromNumPy( numpy_image: numpy.array ) -> str:
diff --git a/hydrus/core/images/HydrusImageColours.py b/hydrus/core/files/images/HydrusImageColours.py
similarity index 100%
rename from hydrus/core/images/HydrusImageColours.py
rename to hydrus/core/files/images/HydrusImageColours.py
diff --git a/hydrus/core/images/HydrusImageHandling.py b/hydrus/core/files/images/HydrusImageHandling.py
similarity index 97%
rename from hydrus/core/images/HydrusImageHandling.py
rename to hydrus/core/files/images/HydrusImageHandling.py
index 5cdf8340c..381fcc5da 100644
--- a/hydrus/core/images/HydrusImageHandling.py
+++ b/hydrus/core/files/images/HydrusImageHandling.py
@@ -1,4 +1,4 @@
-from hydrus.core.images import HydrusImageInit # right up top
+from hydrus.core.files.images import HydrusImageInit # right up top
import cv2
import hashlib
@@ -29,12 +29,12 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
-from hydrus.core import HydrusPSDHandling
-from hydrus.core import HydrusKritaHandling
-from hydrus.core.images import HydrusImageColours
-from hydrus.core.images import HydrusImageMetadata
-from hydrus.core.images import HydrusImageNormalisation
-from hydrus.core.images import HydrusImageOpening
+from hydrus.core.files import HydrusKritaHandling
+from hydrus.core.files import HydrusPSDHandling
+from hydrus.core.files.images import HydrusImageColours
+from hydrus.core.files.images import HydrusImageMetadata
+from hydrus.core.files.images import HydrusImageNormalisation
+from hydrus.core.files.images import HydrusImageOpening
def EnableLoadTruncatedImages():
@@ -165,15 +165,6 @@ def GenerateNumPyImage( path, mime, force_pil = False ) -> numpy.array:
pil_image = HydrusImageOpening.RawOpenPILImage( path )
- try:
-
- pil_image.verify()
-
- except Exception as e:
-
- raise HydrusExceptions.UnsupportedFileException() from e
-
-
if pil_image.mode == 'LAB':
force_pil = True
diff --git a/hydrus/core/images/HydrusImageInit.py b/hydrus/core/files/images/HydrusImageInit.py
similarity index 100%
rename from hydrus/core/images/HydrusImageInit.py
rename to hydrus/core/files/images/HydrusImageInit.py
diff --git a/hydrus/core/images/HydrusImageMetadata.py b/hydrus/core/files/images/HydrusImageMetadata.py
similarity index 100%
rename from hydrus/core/images/HydrusImageMetadata.py
rename to hydrus/core/files/images/HydrusImageMetadata.py
diff --git a/hydrus/core/images/HydrusImageNormalisation.py b/hydrus/core/files/images/HydrusImageNormalisation.py
similarity index 98%
rename from hydrus/core/images/HydrusImageNormalisation.py
rename to hydrus/core/files/images/HydrusImageNormalisation.py
index 8c3446f89..edcf9e5d2 100644
--- a/hydrus/core/images/HydrusImageNormalisation.py
+++ b/hydrus/core/files/images/HydrusImageNormalisation.py
@@ -9,8 +9,8 @@
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
-from hydrus.core.images import HydrusImageColours
-from hydrus.core.images import HydrusImageMetadata
+from hydrus.core.files.images import HydrusImageColours
+from hydrus.core.files.images import HydrusImageMetadata
try:
diff --git a/hydrus/core/images/HydrusImageOpening.py b/hydrus/core/files/images/HydrusImageOpening.py
similarity index 100%
rename from hydrus/core/images/HydrusImageOpening.py
rename to hydrus/core/files/images/HydrusImageOpening.py
diff --git a/hydrus/core/files/images/__init__.py b/hydrus/core/files/images/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/hydrus/core/networking/HydrusNetworkVariableHandling.py b/hydrus/core/networking/HydrusNetworkVariableHandling.py
index 0cee31163..81a9b1fc9 100644
--- a/hydrus/core/networking/HydrusNetworkVariableHandling.py
+++ b/hydrus/core/networking/HydrusNetworkVariableHandling.py
@@ -15,9 +15,9 @@
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusSerialisable
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files import HydrusFileHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.core.networking import HydrusNetwork
INT_PARAMS = { 'expires', 'num', 'since', 'content_type', 'action', 'status' }
diff --git a/hydrus/core/networking/HydrusServerResources.py b/hydrus/core/networking/HydrusServerResources.py
index 0ab972fc4..6b90480aa 100644
--- a/hydrus/core/networking/HydrusServerResources.py
+++ b/hydrus/core/networking/HydrusServerResources.py
@@ -1300,19 +1300,45 @@ def GetBodyBytes( self ):
return self._body_bytes
- def GetCookies( self ): return self._cookies
+ def GetCookies( self ):
+
+ return self._cookies
+
- def GetMime( self ): return self._mime
+ def GetMime( self ):
+
+ return self._mime
+
- def GetPath( self ): return self._path
+ def GetPath( self ):
+
+ return self._path
+
- def GetStatusCode( self ): return self._status_code
+ def GetStatusCode( self ):
+
+ return self._status_code
+
- def GetMaxAge( self ): return self._max_age
+ def GetMaxAge( self ):
+
+ return self._max_age
+
+
+ def SetMaxAge( self, age ):
+
+ self._max_age = age
+
- def HasBody( self ): return self._body_bytes is not None
+ def HasBody( self ):
+
+ return self._body_bytes is not None
+
- def HasPath( self ): return self._path is not None
+ def HasPath( self ):
+
+ return self._path is not None
+
def IsAttachmentDownload( self ):
diff --git a/hydrus/external/SystemPredicateParser.py b/hydrus/external/SystemPredicateParser.py
index e0c849e6b..cfa08fff1 100644
--- a/hydrus/external/SystemPredicateParser.py
+++ b/hydrus/external/SystemPredicateParser.py
@@ -188,6 +188,7 @@ class Operators( Enum ):
RELATIONAL_TIME = auto() # One of '=', '<', '>', UNICODE_APPROX_EQUAL ('≈') (takes '~=' too), and the various 'since', 'before', 'the day of', 'the month of' time-based analogues
RELATIONAL_FOR_RATING_SERVICE = auto() # RELATIONAL, but in the middle of a 'service_name = 4/5' kind of thing
EQUAL = auto() # One of '=' or '!='
+ EQUAL_NOT_CONSUMING = auto() # One of '=' or '!=', doesn't consume this text so later things can look at it
FILESERVICE_STATUS = auto() # One of 'is not currently in', 'is currently in', 'is not pending to', 'is pending to'
TAG_RELATIONAL = auto() # A tuple of a string (a potential tag name) and a relational operator (as a string)
ONLY_EQUAL = auto() # None (meaning =, since thats the only accepted operator)
@@ -241,7 +242,7 @@ class Units( Enum ):
'similar to data': (Predicate.SIMILAR_TO_DATA, None, Value.SIMILAR_TO_HASHLIST_WITH_DISTANCE, None),
'limit': (Predicate.LIMIT, Operators.ONLY_EQUAL, Value.NATURAL, None),
'file ?type': (Predicate.FILETYPE, Operators.ONLY_EQUAL, Value.FILETYPE_LIST, None),
- 'hash': (Predicate.HASH, Operators.EQUAL, Value.HASHLIST_WITH_ALGORITHM, None),
+ 'hash': (Predicate.HASH, Operators.EQUAL_NOT_CONSUMING, Value.HASHLIST_WITH_ALGORITHM, None),
'archived? (date|time)|(date|time) archived|archived.': (Predicate.ARCHIVED_DATE, Operators.RELATIONAL_TIME, Value.DATE_OR_TIME_INTERVAL, None),
'modified (date|time)|(date|time) modified|modified': (Predicate.MOD_DATE, Operators.RELATIONAL_TIME, Value.DATE_OR_TIME_INTERVAL, None),
'last view(ed)? (date|time)|(date|time) last viewed|last viewed': (Predicate.LAST_VIEWED_TIME, Operators.RELATIONAL_TIME, Value.DATE_OR_TIME_INTERVAL, None),
@@ -406,28 +407,70 @@ def parse_value( string: str, spec ):
elif spec == Value.SHA256_HASHLIST_WITH_DISTANCE:
- match = re.match( '(?P([0-9a-f]+(\s|,)+)+)(with\s+)?distance\s+(?P0|([1-9][0-9]*))', string )
+ match = re.match( '(?P([0-9a-f]{4}[0-9a-f]+(\s|,)*)+)(with\s+)?(distance\s+)?(of\s+)?(?P0|([1-9][0-9]*))?', string )
if match:
hashes = set( hsh.strip() for hsh in re.sub( '\s', ' ', match[ 'hashes' ].replace( ',', ' ' ) ).split( ' ' ) if len( hsh ) > 0 )
- distance = int( match[ 'distance' ] )
+
+ d = match.groupdict()
+
+ if 'distance' in d and d[ 'distance' ] is not None:
+
+ distance = int( match[ 'distance' ] )
+
+ else:
+
+ distance = 4
+
+
return string[ len( match[ 0 ] ): ], (hashes, distance)
raise ValueError( "Invalid value, expected a list of hashes with distance" )
elif spec == Value.SIMILAR_TO_HASHLIST_WITH_DISTANCE:
- match = re.match( '(?P([0-9a-f]+(\s|,)+)+)(with\s+)?distance\s+(?P0|([1-9][0-9]*))', string )
+ match = re.match( '(?P([0-9a-f]{4}[0-9a-f]+(\s|,)*)+)(with\s+)?(distance\s+)?(of\s+)?(?P0|([1-9][0-9]*))?', string )
if match:
hashes = set( hsh.strip() for hsh in re.sub( '\s', ' ', match[ 'hashes' ].replace( ',', ' ' ) ).split( ' ' ) if len( hsh ) > 0 )
pixel_hashes = { hash for hash in hashes if len( hash ) == 64 }
perceptual_hashes = { hash for hash in hashes if len( hash ) == 16 }
- distance = int( match[ 'distance' ] )
+
+ d = match.groupdict()
+
+ if 'distance' in d and d[ 'distance' ] is not None:
+
+ distance = int( match[ 'distance' ] )
+
+ else:
+
+ distance = 8
+
+
return string[ len( match[ 0 ] ): ], (pixel_hashes, perceptual_hashes, distance)
raise ValueError( "Invalid value, expected a list of hashes with distance" )
elif spec == Value.HASHLIST_WITH_ALGORITHM:
- match = re.match( '(?P([0-9a-f]+(\s|,)*)+)((with\s+)?algorithm)?\s*(?Psha256|sha512|md5|sha1|)', string )
+
+ # hydev KISS hijack here, instead of clever regex to capture algorithm in all sorts of situations, let's just grab the hex we see and scan the rest for non-hex phrases mate
+ # old pattern: match = re.match( '(?P([0-9a-f]+(\s|,)*)+)((with\s+)?algorithm)?\s*(?Psha256|sha512|md5|sha1|)', string )
+
+ algorithm = 'sha256'
+
+ for possible_algorithm in ( 'md5', 'sha1', 'sha512' ):
+
+ if possible_algorithm in string:
+
+ algorithm = possible_algorithm
+
+ break
+
+
+
+ # {8} here to make sure we are looking at proper hash hex and not some short 'a' or 'de' word
+ match = re.search( '(?P([0-9a-f]{8}[0-9a-f]+(\s|,)*)+)', string )
+
if match:
hashes = set( hsh.strip() for hsh in re.sub( '\s', ' ', match[ 'hashes' ].replace( ',', ' ' ) ).split( ' ' ) if len( hsh ) > 0 )
- algorithm = match[ 'algorithm' ] if len( match[ 'algorithm' ] ) > 0 else 'sha256'
- return string[ len( match[ 0 ] ): ], (hashes, algorithm)
- raise ValueError( "Invalid value, expected a list of hashes with algorithm" )
+ return string[ match.endpos : ], (hashes, algorithm)
+
+ raise ValueError( "Invalid value, expected a list of hashes and perhaps an algorithm" )
+
+
elif spec == Value.FILETYPE_LIST:
valid_values = sorted( FILETYPES.keys(), key = lambda k: len( k ), reverse = True )
ftype_regex = '(' + '|'.join( [ '(' + val + ')' for val in valid_values ] ) + ')'
@@ -740,12 +783,26 @@ def parse_operator( string: str, spec ):
raise ValueError( "Invalid rating operator" )
elif spec == Operators.EQUAL:
if string.startswith( '==' ): return string[ 2: ], '='
- if string.startswith( '=' ): return string[ 1: ], '='
if string.startswith( UNICODE_NOT_EQUAL ): return string[ 1: ], '!='
if string.startswith( '!=' ): return string[ 2: ], '!='
+ if string.startswith( '=' ): return string[ 1: ], '='
if string.startswith( 'is not' ): return string[ 6: ], '!='
- if string.startswith( 'is' ): return string[ 2: ], '='
if string.startswith( 'isn\'t' ): return string[ 5: ], '!='
+ if string.startswith( 'is' ): return string[ 2: ], '='
+ raise ValueError( "Invalid equality operator" )
+ elif spec == Operators.EQUAL_NOT_CONSUMING:
+
+ # hydev checking in here with some nonsense that catches an awkward situation
+ # system:hash (md5) = blah
+ # we want to see the = but not eat the md5, so in this special case, which isn't hard to parse otherwise, we'll just look for it and return no changes
+
+ if '==' in string: return string, '='
+ if UNICODE_NOT_EQUAL in string: return string, '!='
+ if '!=' in string: return string, '!='
+ if '=' in string: return string, '='
+ if 'is not' in string: return string, '!='
+ if 'isn\'t' in string: return string, '!='
+ if 'is' in string: return string, '='
raise ValueError( "Invalid equality operator" )
elif spec == Operators.FILESERVICE_STATUS:
match = re.match( '(is )?currently in', string )
diff --git a/hydrus/test/TestClientAPI.py b/hydrus/test/TestClientAPI.py
index e4e2eff93..882e4689b 100644
--- a/hydrus/test/TestClientAPI.py
+++ b/hydrus/test/TestClientAPI.py
@@ -19,7 +19,7 @@
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientAPI
@@ -4884,6 +4884,11 @@ def _test_file_metadata( self, connection, set_up_permissions ):
ratings_dict[ HG.test_controller.example_incdec_rating_service_key ] = int( random.random() * 16 )
+ if random.random() > 0.8:
+
+ file_info_manager.original_mime = HC.IMAGE_PNG
+
+
ratings_manager = ClientMediaManagers.RatingsManager( {} )
notes_manager = ClientMediaManagers.NotesManager( { 'note' : 'hello', 'note2' : 'hello2' } )
file_viewing_stats_manager = ClientMediaManagers.FileViewingStatsManager.STATICGenerateEmptyManager( timestamps_manager )
@@ -4921,6 +4926,15 @@ def _test_file_metadata( self, connection, set_up_permissions ):
'num_words' : file_info_manager.num_words
}
+ filetype_forced = file_info_manager.FiletypeIsForced()
+
+ metadata_row[ 'filetype_forced' ] = filetype_forced
+
+ if filetype_forced:
+
+ metadata_row[ 'original_mime' ] = HC.mime_mimetype_string_lookup[ file_info_manager.original_mime ]
+
+
only_return_basic_information_metadata.append( dict( metadata_row ) )
metadata_row[ 'blurhash' ] = file_info_manager.blurhash
diff --git a/hydrus/test/TestClientDB.py b/hydrus/test/TestClientDB.py
index 38f631e75..9014cac49 100644
--- a/hydrus/test/TestClientDB.py
+++ b/hydrus/test/TestClientDB.py
@@ -7,7 +7,7 @@
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
-from hydrus.core.images import HydrusImageHandling
+from hydrus.core.files.images import HydrusImageHandling
from hydrus.core.networking import HydrusNetwork
from hydrus.client import ClientConstants as CC
diff --git a/hydrus/test/TestClientTags.py b/hydrus/test/TestClientTags.py
index f3d4d7c74..dba242230 100644
--- a/hydrus/test/TestClientTags.py
+++ b/hydrus/test/TestClientTags.py
@@ -1843,7 +1843,7 @@ def test_predicate_strings_and_namespaces( self ):
p = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HASH, ( ( bytes.fromhex( 'abcd' ), ), 'sha256' ) )
- self.assertEqual( p.ToString(), 'system:sha256 hash is abcd' )
+ self.assertEqual( p.ToString(), 'system:hash is abcd' )
self.assertEqual( p.GetNamespace(), 'system' )
self.assertEqual( p.GetTextsAndNamespaces( render_for_user ), [ ( p.ToString(), 'namespace', p.GetNamespace() ) ] )
@@ -1941,13 +1941,13 @@ def test_predicate_strings_and_namespaces( self ):
p = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO_FILES, ( ( bytes.fromhex( 'abcd' ), ), 5 ) )
- self.assertEqual( p.ToString(), 'system:similar to 1 files using max hamming of 5' )
+ self.assertEqual( p.ToString(), 'system:similar to 1 files with distance of 5' )
self.assertEqual( p.GetNamespace(), 'system' )
self.assertEqual( p.GetTextsAndNamespaces( render_for_user ), [ ( p.ToString(), 'namespace', p.GetNamespace() ) ] )
p = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO_DATA, ( ( os.urandom( 32 ), ), ( os.urandom( 32 ), ), 2 ) )
- self.assertEqual( p.ToString(), 'system:similar to 2 similar data hashes using max hamming of 2' )
+ self.assertEqual( p.ToString(), 'system:similar to data (1 pixel, 1 perceptual hashes) with distance of 2' )
self.assertEqual( p.GetNamespace(), 'system' )
self.assertEqual( p.GetTextsAndNamespaces( render_for_user ), [ ( p.ToString(), 'namespace', p.GetNamespace() ) ] )
@@ -2021,11 +2021,15 @@ def test_predicate_strings_and_namespaces( self ):
def test_system_predicate_parsing( self ):
for ( expected_result_text, sys_pred_text ) in [
- ( 'system:similar to 4 files using max hamming of 3', "system:similar to e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c08 e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c09 e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c10, e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c11 with distance 3" ),
- ( 'system:similar to 1 files using max hamming of 5', "system:similar to e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c00 distance 5" ),
- ( 'system:similar to 2 similar data hashes using max hamming of 4', "system:similar to data b51d75120456d6e2155416f26416a96290b0a524bf1582af50b4fcf46dedcd91 b51d75120456d6e2155416f26416a96290b0a524bf1582af50b4fcf46dedcd92 distance 4" ),
- ( 'system:similar to 2 similar data hashes using max hamming of 4', "system:similar to data 0702790ffeae5c8a 51ad07c228ab7469 distance 4" ),
- ( 'system:similar to 4 similar data hashes using max hamming of 4', "system:similar to data b51d75120456d6e2155416f26416a96290b0a524bf1582af50b4fcf46dedcd91 b51d75120456d6e2155416f26416a96290b0a524bf1582af50b4fcf46dedcd92 51ad07c228ab7469 0702790ffeae5c8a distance 4" ),
+ ( 'system:similar to 4 files with distance of 3', "system:similar to e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c08 e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c09 e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c10, e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c11 with distance 3" ),
+ ( 'system:similar to 1 files with distance of 5', "system:similar to e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c00 distance 5" ),
+ ( 'system:similar to 1 files with distance of 4', "system:similar to e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c00" ),
+ ( 'system:similar to 1 files with distance of 5', "system:similar to e2c1592ce2a3338767bb7990738ae06357cbdfe917669da9a0d04069f9759c00 with distance of 5" ),
+ ( 'system:similar to data (2 pixel hashes)', "system:similar to data b51d75120456d6e2155416f26416a96290b0a524bf1582af50b4fcf46dedcd91 b51d75120456d6e2155416f26416a96290b0a524bf1582af50b4fcf46dedcd92 distance 4" ),
+ ( 'system:similar to data (2 pixel hashes)', "system:similar to data b51d75120456d6e2155416f26416a96290b0a524bf1582af50b4fcf46dedcd91 b51d75120456d6e2155416f26416a96290b0a524bf1582af50b4fcf46dedcd92" ),
+ ( 'system:similar to data (2 perceptual hashes) with distance of 4', "system:similar to data 0702790ffeae5c8a 51ad07c228ab7469 distance 4" ),
+ ( 'system:similar to data (2 perceptual hashes) with distance of 8', "system:similar to data 0702790ffeae5c8a 51ad07c228ab7469" ),
+ ( 'system:similar to data (2 pixel, 2 perceptual hashes) with distance of 4', "system:similar to data b51d75120456d6e2155416f26416a96290b0a524bf1582af50b4fcf46dedcd91 b51d75120456d6e2155416f26416a96290b0a524bf1582af50b4fcf46dedcd92 51ad07c228ab7469 0702790ffeae5c8a distance 4" ),
( 'system:everything', "system:everything" ),
( 'system:inbox', "system:inbox " ),
( 'system:archive', "system:archive " ),
@@ -2071,13 +2075,13 @@ def test_system_predicate_parsing( self ):
( 'system:filetype is apng, jpeg, png', "system:filetype = image/jpg, image/png, apng" ),
( 'system:filetype is image', "system:filetype is image" ),
( 'system:filetype is animated gif, static gif, jpeg', "system:filetype = static gif, animated gif, jpeg" ),
- ( 'system:sha256 hash is in 3 hashes', "system:hash = abcdef01 abcdef02 abcdef03" ),
- ( 'system:md5 hash is in 3 hashes', "system:hash = abcdef01 abcdef, abcdef04 md5" ),
- ( 'system:md5 hash is abcdef01', "system:hash = abcdef01 md5" ),
- ( 'system:md5 hash is abcdef01', "system:Hash = Abcdef01 md5" ),
- ( 'system:md5 hash is not abcdef01', "system:Hash != Abcdef01 md5" ),
- ( 'system:md5 hash is not abcdef01', "system:Hash is not Abcdef01 md5" ),
- ( 'system:sha256 hash is abcdef0102', "system:hash = abcdef0102" ),
+ ( 'system:hash is in 3 hashes', "system:hash = cf09faad262075f96bf9a30052b8ec224e096948a4f3a2776df5fa5a777bcfd8 a1b0ab771d11d9a6d1f993efee9d253d3aa78914387a7c8ceab520af88ab3de2 98a7d2f4735a5fcc70e7c94e2dadcc6ea45123fb2035b9cfe7ad1d78e48cae9e" ),
+ ( 'system:hash (md5) is in 3 hashes', "system:hash = ada7a31713ba24652c52e52c6f212e47 546fd4b8c39fc53e77e2f28b59cd1b18, cec888bacb79825621738454a4c9d226 md5" ),
+ ( 'system:hash (md5) is 666d0a395c8d4eebb5b15a0771972a01', "system:hash (md5) = 666d0a395c8d4eebb5b15a0771972a01" ),
+ ( 'system:hash (md5) is 123fec741ebe7596c1faf8d7689693b8', "system:Hash = 123feC741ebe7596c1faf8d7689693b8 md5" ),
+ ( 'system:hash (sha1) is not 2496baf1ded134b5ff7e44f72155240b9561ab5a', "system:Hash (sha1) != 2496baf1ded134b5ff7e44f72155240b9561ab5a" ),
+ ( 'system:hash is not b49f25453c6351403d62cc4d065321106c90f98b5653e83d289dbe0d55ba8c94', "system:Hash is not b49f25453c6351403d62cc4d065321106c90f98b5653e83d289dbe0d55ba8c94 sha256" ),
+ ( 'system:hash is 4d4ff3d42459f824295a36138782c444028f533b6ae5f0f67b27e9bf3c93de5d', "system:hash = 4d4ff3d42459f824295a36138782c444028f533b6ae5f0f67b27e9bf3c93de5d" ),
( 'system:archived time: since 60 days ago', "system:archived date < 60 days" ),
( 'system:archived time: since 60 days ago', "system:archive date < 60 days" ),
( 'system:archived time: since 60 days ago', "system:archived time < 60 days" ),