From 428372fb576e85d2a9968a9c464437519b64a039 Mon Sep 17 00:00:00 2001 From: Hydrus Network Developer Date: Wed, 20 Sep 2023 14:58:17 -0500 Subject: [PATCH] Version 544 closes #1435 --- docs/changelog.md | 90 +++---- docs/database_migration.md | 39 ++- docs/developer_api.md | 19 +- docs/images/db_migration.png | Bin 30782 -> 29907 bytes docs/images/db_migration_move_done.png | Bin 0 -> 34177 bytes docs/images/db_migration_move_pending.png | Bin 0 -> 37193 bytes docs/old_changelog.html | 35 +++ hydrus/client/ClientFiles.py | 71 +++-- hydrus/client/ClientFilesPhysical.py | 77 ++++++ hydrus/client/ClientRendering.py | 96 +++++-- hydrus/client/db/ClientDB.py | 63 +++++ hydrus/client/gui/ClientGUIScrolledPanels.py | 13 +- .../gui/ClientGUIScrolledPanelsReview.py | 155 +++++++++-- .../gui/lists/ClientGUIListConstants.py | 2 + .../client/gui/widgets/ClientGUIControls.py | 12 +- .../networking/ClientLocalServerResources.py | 48 +++- hydrus/core/HydrusConstants.py | 6 +- hydrus/core/HydrusFileHandling.py | 36 +-- hydrus/core/HydrusPSDHandling.py | 8 +- hydrus/core/HydrusPSDTools.py | 41 ++- hydrus/core/HydrusProcreateHandling.py | 10 +- .../core/networking/HydrusServerResources.py | 46 ++-- hydrus/test/TestClientAPI.py | 34 +++ hydrus/test/TestClientDB.py | 76 ++++++ requirements.txt | 2 +- setup_venv.bat | 79 +++++- setup_venv.command | 254 +++++++++++------- setup_venv.sh | 77 +++++- static/build_files/linux/requirements.txt | 2 +- static/build_files/macos/requirements.txt | 2 +- static/build_files/windows/requirements.txt | 2 +- .../sankakucomplex login 2018.11.08.png | Bin 3245 -> 0 bytes .../parsers/inkbunny file page parser.png | Bin 3032 -> 2936 bytes .../parsers/sankaku gallery page parser.png | Bin 2577 -> 2644 bytes .../advanced/requirements_core.txt | 2 - .../advanced/requirements_pillow_new.txt | 2 + .../advanced/requirements_pillow_old.txt | 2 + .../advanced/requirements_server.txt | 2 +- 38 files changed, 1080 insertions(+), 323 deletions(-) create mode 100644 docs/images/db_migration_move_done.png create mode 100644 docs/images/db_migration_move_pending.png delete mode 100644 static/default/login_scripts/sankakucomplex login 2018.11.08.png create mode 100644 static/requirements/advanced/requirements_pillow_new.txt create mode 100644 static/requirements/advanced/requirements_pillow_old.txt diff --git a/docs/changelog.md b/docs/changelog.md index 6eb7f132..946ebe64 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,46 @@ title: Changelog !!! note This is the new changelog, only the most recent builds. For all versions, see the [old changelog](old_changelog.html). +## [Version 544](https://github.com/hydrusnetwork/hydrus/releases/tag/v544) + +### webp vulnerability + +* the main webp library (libwebp) that many programs use for webp support had a remote execution (very bad) vulnerability. you probably noticed your chrome/firefox updated this week, which was fixing this. we use the same thing via the `Pillow` library, which also rolled out a fix. I'm not sure how vulnerable hydrus ever was, since we are usually jank about how we do anything, but best to be safe about these things. there were apparently exploits for this floating around +* the builds today have the fix, so if you use them, update as normal and you are good +* if you run from source, **rebuild your venv at your earliest convenience**, and you'll get the new version of Pillow and be good. note, if you use the advanced setup, that there is a new question about `Pillow` +* unfortunately, Windows 7 users (or anyone else running from source on Python 3.7) cannot get the fix! it needs Pillow 10.0.1, which is >=Python 3.8. it seems many large programs are dropping support for Win 7 this year, so while I will continue to support it for a reasonable while longer, I think the train may be running out of track bros + +### max size in file storage system + +* the `migrate database` dialog now allows you to set a 'max size' for all but one of your media locations. if you have a 500GB drive you want to store some stuff on, you no longer have to balance the weights in your head--just set a max size of 450GB and hydrus will figure it out for you. it is not super precise (and it isn't healthy to fill drives up to 98% anyway), so make sure you leave some padding +* also, please note that this will not automatically rebalance _yet_. right now, the only way files move between locations is through the 'move files now' button on the dialog, so if you have a location that is full up according to its max size rule and then spend a month importing many files, it will go over its limit until and unless you revisit 'migrate database' and move files again. I hope to have automatic background rebalancing in the near future +* updated the 'database migration' help to talk about this and added a new migration example +* the 'edit num bytes' widget now supports terabytes (TB) +* I fleshed out the logic and fixed several bugs in the migration code, mostly to do with the new max size stuff and distributing weights appropriately in various situations + +### misc + +* when an image file fails to render in the media viewer, it now draws a bordered box with a brief 'failed to render' note. previously, it janked with a second of lag, made some popups, and left the display on eternal blank hang. now it finishes its job cleanly and returns a 'nah m8' 'image' result +* I reworked the Mr Bones layout a bit. the search is now on the left, and the rows of the main count table are separated for readability +* it turns out that bitmap (.bmp) files can support ICC Profiles, so I've told hydrus to look for them in new bitmaps and retroactively scan all your existing ones +* fixed an issue with the recent PSD code updates that was breaking boot for clients running from source without the psd-tools library (this affected the Docker build) +* updated all the 'setup_venv' scripts. all the formatting and text has had a pass, and there is now a question on (n)ew or (old) Pillow +* to stop FFMPEG's false positives where it can think a txt file is an mpeg, the main hydrus filetype scanning routine will no longer send files with common text extensions to ffmpeg. if you do have an mp3 called music.txt, rename it before import! +* thanks to a user, the inkbunny file page parser fetches the correct source time again (#1431) +* thanks to a user, the old sankaku gallery parser can find the 'next page' again +* removed the broken sankaku login script for new users. I recommend people move to Hydrus Companion for all tricky login situations (#1435) +* thanks to a user, procreate file parsing, which had the width/height flipped, is fixed. all existing procreate files will regen their metadata and thumbs + +### client api + +* thanks to a user, the Client API now has a `/get_files/render` command, which gives you a 100% zoom png render of the given file. useful if you want to display a PSD on a web page! +* I screwed up Mr Bones's Client API request last week. this is now fixed +* Mr Bones now supports a full file search context on the Client API, just like the main UI now. same parameters as `/get_files/search_files`, the help talks about it. He also cancels his work early if the request is terminated +* Mr Bones gets several new unit tests to guarantee long-term ride reliability +* the Client API (and all hydrus servers) now return proper JSON on an error. there's the error summary, specific exception name, and http status code. the big bad 500-error-of-last-resort still tacks on the large serverside traceback to the summary, so we'll see if that is still annoying and split it off if needed +* the new `/add_tags/get_siblings_and_parents` now properly cleans the tags you give it, trimming whitespace and lowercasing letters and so on +* the client api version is now 52 + ## [Version 543](https://github.com/hydrusnetwork/hydrus/releases/tag/v543) ### misc @@ -370,53 +410,3 @@ title: Changelog * all fetches of multiple rows of data from multi-column lists now happen sorted. this is just a little thing, but it'll probably dejank a few operations where you edit several things at once or get some errors and are trying to figure out which of five things caused it * the hydrus official mimetype for psd files is now 'image/vnd.adobe.photoshop' (instead of 'application/x-photoshop') * with krita file (which are actually just zip files) support, we now have the very barebones of archive tech started. I'll expand it a bit more and we should be able to improve support for other archive-like formats in the future - -## [Version 534](https://github.com/hydrusnetwork/hydrus/releases/tag/v534) - -### user submissions - -* thanks to a user, we now have SAI2 (.sai2) file support! -* thanks to a user, the duplicate filter now says if one file has audio. this complements the recent Hydrus Video Deduplicator (https://github.com/appleappleapplenanner/hydrus-video-deduplicator), which can queue videos up in your dupe filter -* thanks to a user, we now have some nice svg images in the help->links(?) menu instead of gritty bitmaps -* thanks to a user, some help documentation for recent client vs hydrus_client changes got fixed - -### quality of life/new stuff - -* the media viewer's top-area 'removed from x' lines for files deleted from a local file service no longer appear--unless that file is currently in the trash. on clients with busy multiple local file services, they were mostly just annoying and spammy. if you need this data, hit up the right-click menu of the file--it is still listed there -* the 'loading' media page now draws a background in the same colour as the thumbnail grid, so new searches or refreshes will no longer flash to a default grey colour--it should just be a smooth thumbs gone/thumbs back now -* added a new shortcut action, 'copy small bmp of image for quick source lookups', for last week's new bitmap copy action -* it turns out PNG and WEBP files can have EXIF data, and our existing scanner works with them, so the EXIF scanner now looks at PNGs and WEBPs too. PNGs appear to be rare, about 1-in-200. I will retroactively scan your existing WEBPs, since they have EXIF more commonly, maybe as high as 1-in-5, and are less common as a filetype anyway so the scan will be less work, but when you update you will get a yes/no dialog asking if you want to do PNGs too. it isn't a big deal and you can always queue it up later if you want - -### fixes - -* I banged my head against the notes layout code and actually had great success--a variety of borked note-spilling-over-into-nothing and note-stretching-itself-crazy and note-has-fifty-pixels-of-margin issues are fixed. let me know if you still have crazy notes anywhere -* the duplicate filter right-hand hover is now more aggressive about getting out of the way of the notes hover, especially when the notes hover jitter-resizes itself a few extra pixels of height. the notes hover should no longer ever overlap the duplicate filter hover's top buttons--very sorry this took so long -* when you drag and drop thumbnails out of the program while using an automatic pattern to rename them (_options->gui_), all the filenames are now unique, adding '(x)' after their name as needed for dedupe. previously, on duplicates, it was basically doing a weird spam-merge -* fixed an issue when sanitizing export filenames when the destination directory's file system type cannot be determined -* fixed a bug when doing a search in a deleted file domain with 'file import time' as the file sort -* fixed a bug when hitting the shortcut for 'open file in media viewer' on a non-local file -* fixed a bug when the client wants to figure out view options for a file that has mime 'application/unknown' -* I may have improved the 'woah the db caches are unsynced' error handling in the 'what siblings and parents does this have?' check when you right-click tags - -### weird bitmap pastes - -* fixed the new 'paste image' button under `system:similar files` for a certain category of unusual clipboard bitmaps, including several that hydrus itself generates, where it turns out the QImage storage system stores extra padding bytes on each line of pixels -* fixed the new 'paste image' button when the incoming bitmap has a useless alpha channel (e.g. 100% transparent). this was not being stripped like it is for imported images, and so some similar files data was not lining up -* many bitmaps copied from other programs like Firefox remain slightly different to what hydrus generates (even though both are at 100% scale). my best guess here is that there is some differing ICC-profile-like colour adjustment happening somewhere, probably either a global browser setting, the browser obeying a global GPU setting, a simply better application of such image metadata on the browser's side, or maybe a stripping of such data, since it seems a 'copy image' event in Firefox also generates and attaches to your clipboard a temporary png file in your temp folder, so maybe the bitmap that we pull from the clipboard is actually generated during some conversion process amidst all that, and it loses some jpeg colour data. whatever the case here, it changes the pixel hash and subtly alters the perceptual hash in many cases. I'm bumping the default distance on this search predicate up to 8 now, to catch the weirder instances - -### misc - -* the 'does the db partition have 500MB free?' check that runs on database boot now occurs after some initial database cleanup, and it will use half the total database size instead, if that is smaller than 500MB, down to 64MB (issue #1373) -* added a note to the 'running from source' help that the newer mpv dll seems to work on Qt5 and Windows 7 (issue #1338) -* the twitter parsers and gugs are removed from the defaults for new users. a shame, but we'll see what happens in future -* more misc linting cleanup - -### ratings on the client api - -* the services object now shows `star_shape` and `min_stars` and `max_stars` for like/dislike and numerical rating services -* the file metadata object now has a 'ratings' key, which lists `rating_service_key->rating` for all the client's rating services. this thing is simple and uses human-friendly values, but it can hold several different data types, so check the help for details and examples -* a new permission, 'edit ratings', is added. -* a new command, `/edit_ratings/set_rating`, is added. Guess what it does! (issue #343) -* the help is updated for these -* the unit tests are updated for these -* the client api version is now 48 diff --git a/docs/database_migration.md b/docs/database_migration.md index cad30ea2..27f5dc6c 100644 --- a/docs/database_migration.md +++ b/docs/database_migration.md @@ -2,6 +2,9 @@ title: Database Migration --- +!!! warning + I am working on this system right now and will be moving the 'move files now' action to a more granular, always-on background migration. This document will update to reflect those changes! + # database migration ## the hydrus database { id="intro" } @@ -38,19 +41,39 @@ Backing such an arrangement up is obviously more complicated, and the internal c !!! danger **As always, I recommend creating a backup before you try any of this, just in case it goes wrong.** -If you would like to move your files and thumbnails to new locations, I generally recommend you not move their folders around yourself--the database has an internal knowledge of where it thinks its file and thumbnail folders are, and if you move them while it is closed, it will become confused and you will have to manually relocate what is missing on the next boot via a repair dialog. This is not impossible to figure out, but if the program's 'client files' folder confuses you at all, I'd recommend you stay away. Instead, you can simply do it through the gui: +If you would like to move your files and thumbnails to new locations, I generally recommend you not move their folders around yourself--the database has an internal knowledge of where it thinks its file and thumbnail folders are, and if you move them while it is closed, it will become confused. + +??? note "Missing Locations" + If your folders are in the wrong locations on a client boot, a repair dialog appears, and you can manually update the client's internal understanding. This is not impossible to figure out, _and in some tricky storage situations doing this on purpose can be faster than letting the client migrate things itself_, but generally it is best and safest to do everything through the dialog. Go _database->migrate database_, giving you this dialog: ![](images/db_migration.png) -To move your files somewhere else, add the new location, empty/remove the old location, and then click 'move files now'. +The buttons let you add more locations and remove old ones. The operations on this dialog are simple and atomic--at no point is your db ever invalid. -**Portable** means that the path is beneath the main db dir and so is stored as a relative path. Portable paths will still function if the database changes location between boots (for instance, if you run the client from a USB drive and it mounts under a different location). +**Beneath db?** means that the path is beneath the main db dir and so is stored internally as a relative path. Portable paths will still function if the database changes location between boots (for instance, if you run the client from a USB drive and it mounts under a different location). **Weight** means the relative amount of media you would like to store in that location. It only matters if you are spreading your files across multiple locations. If location A has a weight of 1 and B has a weight of 2, A will get approximately one third of your files and B will get approximately two thirds. -The operations on this dialog are simple and atomic--at no point is your db ever invalid. Once you have the locations and ideal usage set how you like, hit the 'move files now' button to actually shuffle your files around. It will take some time to finish, but you can pause and resume it later if the job is large or you want to undo or alter something. +**Max Size** means the max total size of files the client will want to store in that location. Again, it only matters if you are spreading your files across multiple locations, but it is a simple way to ensure you don't go over a particular smaller hard drive's size. One location must always be limitless. This is not precise, so give it some padding. When one location is maxed out, the remaining locations will distribute the remainder of the files according to their respective weights. _For the meantime, this will not update by itself. If you import many files, the location may go over its limit and you will have to revisit 'migrate database' to rebalance your files again. Bear with me--I will fix this soon with the background migrate._ + +Let's set up an example move: + +![](images/db_migration_move_pending.png) + +I made several changes: + +* Added `C:\hydrus_files` to store files. +* Added `D:\hydrus_files` to store files, with a max size of 128MB. +* Set `C:\hydrus_thumbs` as the location to store thumbnails. +* Removed the original `C:\Hydrus Network\db\client_files` location. + +While the ideal usage has changed significantly, note that the current usage remains the same. Nothing moves until you click 'move files now'. Moving files will take some time to finish. Once done, it looks like this: + +![](images/db_migration_move_done.png) + +The current and ideal usages line up, and the defunct `C:\Hydrus Network\db\client_files` location, which no longer stores anything, is removed from the list. ## informing the software that the SQLite database is not in the default location { id="launch_parameter" } @@ -72,7 +95,7 @@ Rather than typing the path out in a terminal every time you want to launch your Note that an install with an 'external' database no longer needs access to write to its own path, so you can store it anywhere you like, including protected read-only locations (e.g. in 'Program Files'). Just double-check your shortcuts are good. -## finally { id="finally" } +## backups { id="finally" } If your database now lives in one or more new locations, make sure to update your backup routine to follow them! @@ -91,15 +114,15 @@ Specifically: * Create two empty folders on your SSD with names like 'hydrus\_db' and 'hydrus\_thumbnails'. * Set the 'thumbnail location override' to 'hydrus_thumbnails'. You should get that new location in the list, currently empty but prepared to take all your thumbs. * Hit 'move files now' to actually move the thumbnails. Since this involves moving a lot of individual files from a high-latency source, it will take a long time to finish. The hydrus client may hang periodically as it works, but you can just leave it to work on its own--it will get there in the end. You can also watch it do its disk work under Task Manager. -* Now hit 'add location' and select your new 'hydrus\_files'. 'hydrus\_files' should be added and willing to take 50% of the files. -* Select the old location (probably 'install\_dir/db/client\_files') and hit 'decrease weight' until it has weight 0 and you are prompted to remove it completely. 'hydrus_files' should now be willing to take all the files from the old location. +* Now hit 'add location' and select your new 'hydrus\_files'. 'hydrus\_files' should be appear and be willing to take 50% of the files. +* Select the old location (probably 'install\_dir/db/client\_files') and hit 'remove location' or 'decrease weight' until it has weight 0 and you are prompted to remove it completely. 'hydrus_files' should now be willing to take all 100% of the files from the old location. * Hit 'move files now' again to make this happen. This should be fast since it is just moving a bunch of folders across the same partition. * With everything now 'non-portable' and hence decoupled from the db, you can now easily migrate the install and db to 'hydrus_db' simply by shutting the client down and moving the install folder in a file explorer. * Update your shortcut to the new hydrus_client.exe location and try to boot. * Update your backup scheme to match your new locations. * Enjoy a much faster client. -You should now have _something_ like this: +You should now have _something_ like this (let's say the D drive is the fast SSD, and E is the high capacity HDD): ![](images/db_migration_example.png) diff --git a/docs/developer_api.md b/docs/developer_api.md index c292b2a2..38edc114 100644 --- a/docs/developer_api.md +++ b/docs/developer_api.md @@ -46,7 +46,7 @@ In general, the API deals with standard UTF-8 JSON. POST requests and 200 OK res ``` -On 200 OK, the API returns JSON for everything except actual file/thumbnail requests. On 4XX and 5XX, assume it will return plain text, which may be a raw traceback that I'd be interested in seeing. You'll typically get 400 for a missing parameter, 401/403/419 for missing/insufficient/expired access, and 500 for a real deal serverside error. +The API returns JSON for everything except actual file/thumbnail requests. For errors, you'll typically get 400 for a missing/invalid parameter, 401/403/419 for missing/insufficient/expired access, and 500 for a real deal serverside error. !!! note For any request sent to the API, the total size of the initial request line (this includes the URL and any parameters) and the headers must not be larger than 2 megabytes. @@ -353,11 +353,11 @@ Arguments: * 7 - Edit File Notes * 8 - Edit File Relationships * 9 - Edit File Ratings - + ``` title="Example request" /request_new_permissions?name=my%20import%20script&basic_permissions=[0,1] ``` - + Response: : Some JSON with your access key, which is 64 characters of hex. This will not be valid until the user approves the request in the client ui. ```json title="Example response" @@ -2764,7 +2764,16 @@ _Get the data from help->how boned am I?. This is a simple Object of numbers jus Restricted access: : YES. Manage Database permission needed. -Arguments: None +Arguments (in percent-encoded JSON): +: + * `tags`: (optional, a list of tags you wish to search for) + * [file domain](#parameters_file_domain) (optional, defaults to 'all my files') + * `tag_service_key`: (optional, hexadecimal, the tag domain on which to search, defaults to 'all my files') + + ``` title="Example requests" + /manage_database/mr_bones + /manage_database/mr_bones?tags=%5B%22blonde_hair%22%2C%20%22blue_eyes%22%5D + ``` ```json title="Example response" { @@ -2783,3 +2792,5 @@ Arguments: None } } ``` + +The arguments here are the same as for [GET /get\_files/search\_files](#get_files_search_files). You can set any or none of them to set a search domain like in the dialog. diff --git a/docs/images/db_migration.png b/docs/images/db_migration.png index eaa0c7aa7695df34cde2021f029ee800f588e32e..ac2a0715522e6809e4a5100dcaf647246ba0d6f5 100644 GIT binary patch literal 29907 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 GLH`d&or!V) literal 30782 zcmeFZWmKHavNlYFKp-Iy0t9ym?ry=|-F5K6T?2%Wput@Nfx%q@1W#~xch}%BF!0@z zJbUkFf9JgGth3hp{@eK7Qe9nLeO*;O&3sZ)kbI7Uhk}5B@LXC-Oa%eq5j6tB!w1Mu zfD%=@R!#&2%xNz*Ef*DIcM=CDdvhyW5Q&SY1Be9VVP%eh;4xd0V&z6oS{Zy_6>9L% zxz*|QkSCu<-w{)H98$bKQQ-lttPCkh9{yzv>)!pned_(M*b)!S*f>4s+S?y^MfUtO z7to!pvZ~`-0U-n1Z)MGmb{?x`82n?TQ_Q+Bftd!0Z#S+@IF3=r_EZgvmi)41_SJ5N zM@MCs-Xew>)D7vF}uDm6Wy#x*kH+0t1t(@oSx^7fFVZ=dKX_c|vOWP$< zrTk2b5^b5PTzKnF5uU|o-+p<(UhmGt8nz+!Eur7m^kvx2I2UYNCar=NQqBBcx4sT&6kX z{iSiu^~Q0>9-{!2*LMtq$xC}p#x=jqCBw^^jk;21`;Mkd`kU7gSPbSq(e^n+R2dfG zd%HhR3;N5ZZM$dNF<~XQ*mL_NhtRS5`yYwMM@1pnOe%&z_}mzhO>x{ILA)_$Da7p3nMD zOa`e}cKjRdNm4sa8bt&y#_0%#1F5xG_>ak?nN^cBJe~+#*vCSt40)>OgD!Y1WUmuX ztr`=j7oyobj{RS9k#GDMxVKpk|8STZeZyu)EDp9#d`)eliCd@&&Vrg}xieNN zQPvrUlp>G%S@(kG>rY)hY6fl`A<9#cl_5vryQ%3B3#;?G7yM;T6~^Id4RQ?{Tpny2 z2czXYos>B|+Kq|NyBuvZG>8dQ59d$A*A#>sXpu6|l^jl=Eipd! z<{44nLsP$)z1h!4K%A=$kgRP+Lya8|Y3Ku2BgOu38Bx|nidfOH*b@wp{TJBK$_h&_y~xfjBz$=4}{)0myb z&{Z4}`fk8Jl~06y6XU^~E5{x_v?^^m>1{?Wsj0qbP@(RqWdv zl>Wz!KVMk#6F$=JjkK&Rof2?P_Yatifja4C;dM6>tvRfc;ybs#?cE*Q&#>fkyIk2W z2s;pY?PajHf9=sxrbU_K)bWNrjyLu$s;EuypX*Xy5p z)!VzK!I8tn*1Jjuj=w^xh9awYUp+R+SK1TZ5#LvdG=Zd^)=>B(-GFWLkf~>MyffN ze=x3~Yoc?N;x*j7Q+=Mx{79G}>oH~tr>^150iFX4xoDD9Rpzws#42T+f7H%h{yfQ< z6~FE!aq>AWTE325H)I&}Ox=$nz+FZ>diQIPg&?Z zj4oxOx#{sbbd7E2A4rx~cbnH$T^aCSD#f){IuAj?o2(z5Qfg2eI^&Ta=_NgfLFb${ z7ra={Nlbj!gDi0PUvT(ytRrEjvSE*+!5)G|Lhz|6XXAY)rJx$L8@oNotYObOb}F7+ zouez6MaadxogdgP?fQ-v9BP4%)Aasv+uob4&!e=HBRTNZtYkHn7h2Ba|` z!m`TEeL~A^@@#IhKdAjoulBvmT7w{>PWnUJMRE+@A5*j`FNx>P&{Y^?eof}E+eXb{ zjEJr8y=*{6n@LKV1e#}@<5Bh$Bz=q4doz~y*!G=>JSS04N(;x{uPsii0cr)`{(YUC-HP{6a z%a4T{NtuEmlOeR`G<0$oh|68;<+&yCSi>{#69&Z$8!BzF^K`qj^LP8O_UcHL1P2A7 zD8AC<&tke;V-`)zaBnmzBZD$p}4 zc$Yg#=%TXgTiB(qjS%hvIpryjGL{+1bG=8#=oK2^k5cWQ0`t5>FJfdA<0N=h_Lgs5 z9$`!og2}@*p3qRoTa{Cw#qF?HWHVxpJ{Nwjq?XDdaDrD|o;-3QuHwX2CRd|MR1h

n?O+VU}23dzTSTk&t&1h#e6o~*Mx!+5<}GYRc_@uX*S zX(QDR;rj$5M*&XgaTGVX$yPLSW}6gSN@jEzaiLGTEK07!u@y+&lg32Y>Og6a-Cw@P zexgS9%Ib@kK^Bq%t}6W~PS7`cx36NvJ=Kn%aP4p7y zz4!^zY!i_jwR_78brWlp<00eC)U{>;bHD0hSNGr!GGdNWti{?S8#C0B0Q+BrCwFfp zpB2y~gr_)n4h^zWOqRAHy=r4dCSF-JG#0UEb<%5m_WfhBj-FblQiEq}Y5bFl0A21% zX`g6O#6Ula9}4{R?6#7-)5Cc)YPCfPl&@Y$X|WtqxEB6=D2q*1RHI05{slc~5?kVJ z`%AGPw}NG%$9Y`K4^f}+(6m1K4jwen*_oZd?nsY`!dgb$y7g(3X`ggjh-E}yrG0WT z?~K0mghTN_MFG+Fy#ReT%}^axDAD+1UhX}Jvc=hJZeN4aOY?M-s)||cPbg1|LQqC< ze$5Q9c6BrEKh2x`e9m8r)3o&kBDZ{L%v_;fNbtrD_4UpxIk#4k3!;|@udq_Whf6wD ze#oTYuJgyEkSB~D*Q?W3Cr)-KOeqIBDt&m+Q(VsSx^NnsbhZ`^Q6%_NAwk4Rij@Q> zOSL})BTJqH&CPQrARU)mMmvKav*~`dx4@}{_lk8|0fd+m56Nt2Gv(EbeQx7jY;@+7 zZIuv4dQX3~luG-=Q44D~v=w<{=52Kv|DkpHH6nu8DCwHbh~L-?;uBQvNM4KpNrmz7 zINB0kyfR)CrZGf`5I2>6&v*o?{*B~mb2A0d`^KH?O}5SB|=t8`-Ay!c1njU{K{tL3nS(b;Ty&DCMap zPoskVH2Yd6qJ+g;l)9fF&F~2_)7dAHPVTHKhAWO&%9O}G(wvuv-~qwUwGFbNOF`eU z^v3;~SoJUl+2kPzsOl+b_dIEHiOAVSIyotPp*foO}fjxL*Xh zj9I%hs^=rpijL!A)Z>t+FkF4X!tDI@5T~6&oUAau{IWW#<+I1&7ZMuE6Oj>%*(aZV zUXH8EaAvJbMXr*46#->fcG@_VB`o-RnB=^R0=WtnKzrM&jcpFdM)yD z`=?8}=$?!)yoz*}&Y*dvyz%Xy|{{a7N&pIqx`HzZV?RTr#t@mA#as_J(x!S6)}KKg_K3` z3`y;^=&RX}>nCq?CXyoSb-xt2p}ce|UVdg6AokMOhTuo#*pNund=!^VoV+D7_-QpvIJb?RmbeVmw+oTFtw*qnGA-`=f>o=nk{KGcr1(vDL6KP?|eh|z^8Vh zGyLk2v~Tb}<#V;Wg}lp3W_PlCbgo3`pxZL6^t6(QftOnM1^d0@2k(cc>>bYHXr;{7OP<|v~!k~!0GakSpcFZ}d*L*2fa zgw%Kk+vM>Ga}b&`I6yE?HhaWkS!e$}<4=W(`QPdN-37mKH-RD@LNp&3U$?OeV zDKb8Zff%fqhVLf%qP#-=kPEZYq1?BlnLssRdZhnjVL)MSru#&9h|70BWW2$z@<(50 zm6Sp1Q)gKOcI+SvuuFd17j}ppY_{F38|||hVK84758mwq^_hk;Hon-Ez=>^DbkQI- ze!>7Wl}bp*>D=e{>mNr6#vOBA-|H}v<|LkPuKHAs6xUk3tn^xcAHz_$vJ}QCqxI-L z4c8Cr0A05eyg|IiU@2iCkvX)UyIt<>!Ok;om72p_*gfA8E{R8_jC z`l1R&$IQx4G?`_gwQY8s&8me%oVaHV~`#M zV1}GbJk+kj%Kji;gyZEm8LP54#6q48*-Pwtc&^9Kt&g##$PYx_oh&E2pvfnzs@AdXP3ac~f;@P%(+4OC=GT1NAE3U+*NP|YdHqXX~ zZGTUB)4QXY2-MuNxg?p)^x@07+OHYpkO8_cOrgE30-3Hs8ku#G11PpOx&j6v9$W%- zYF62<(vx3&&swEgK0FAMgvbm*U;6T$HPQ+zGSEevPmP&1g!=_OSmiA$G&Dm}Vm<#5 zEP$ja8O<>CtsYACotj@=5g|j7a3Pdk?=I3vmu9 z+F`_*etRC9CEmiQ-a1;yHSJL9hVg3o)qYupb#vp*6N4-XMC8-}){(i8YEA74rD(Qv z;Tg1NFZ84GU*J4S6d5!9Q0hcI?2e{7X8Hp1SyW3c%uV1rQsPYF1+-z<++xuyWEe*p zb)(GvIxoX}*qDvgkNo(2+s|q$=AD;GAVW{phIK2^Fo+zc7?lFWditKm`rWO=@K0nB z8_^?$z5cp07GjS3M*t@Ct4hQpY_cE_wEyn)oWD9n++!l4pqHjr ziQH=>x8%ng6#DFA&A`s~*xj7%^t5+GZ*|(D#7cDsdaKLu2MTOhS2RLn^-z5cm#RkC zsul{u_KFz1Fx7=Tm2YItSxlQp4?JYFmnH_(su*D@kr%tC0|^&1?VFZa-o_d8s6~T* zb!R-bybo7(&S5EDK^w8+Q3e8G&9l&kRiwNW3tfMk!t1e~7PblgIjFAcfTl65v|zKS z+;2+lR9&kDsH81kV&>R&-{=oSf0yA!x1MG440;LN^Nkje>Uc}=(@-^6ofZunL%ADBXae&;=BQO5W6S7%QAdiEZOM`rjaglN z^9`HeR;!rz8~5JsGA(i-F-a+-LMWrk>iwul*y|UOH^+yIYlnm+<1%s_d{hYPRpDan zWO-~w*+*D@&vJB}dyt?J z#*J+3LDA5EUJ{2&#PKopNNB9$wW2>tZkAw=zLzm|1i!S8lImBJ{xjVty1L)Ib-zTNj2`ze$%!Q=O zYO9Aah8v&j>D94&zW6r(;7Nu{&u4-jS=H{1cb9C?yl2Dis4it3UN(noXAh*5HT$?) z+en#HAp5R~?l*JZH)(Gd#N7ZPe<}3e_Hb4M&aRnMK+g8APNpDnH;|nR z`QJsDnf|lCgR7J6@9CJCGJ$MCV4$fpFe=Nx4Jj!tuk_Cza1&TqfgOJL0*w7{EnTe4 z|7EOy(+ytpdpdtt1nB-xx&PMsA9MfR7-%Ig&nsqc>Izp+T19vB`} z08y;0Jj|TzAT|awV^b~$b{;k}24hZR;1R^l!^UdL!^v&V`dgHlDX)aR6WAD7PAjmn z1&GPP&f<3k+~B;zO40&ktc=Y6Dp9gEb}8fk!>oB>~iOJ!kZWc#B8d|`Nj=>VoRhI=X?;CByTHoT%vAY&JM zCpCL}TLCh-QY7%6|E!h=Hk6sMi?Nuo3kVR(%*w{g%+1TnqsGF;%fiOX&d$Kh!OQ%& z_V#90=AQpkYxwpd;s3MfQdZ8u_@2Lu{_H7LkmH}HKaaLnzc&*J$?t8!Yi#;w3eLuE zAhX|k0(aQHzxzTxfv&e349rO%(%F? zxy+2&SUCSU!#|}v+nc+%8#{r7EdV;KD-zt-XZp$lDUs>RP)%ZW&^&fNnuPpGtYWyGX`oCr_lz$~tAUhxeatBhS0+_P| zkY+tGk(CrfIDmg@d;kHrVi2A>NNGDGAfRHxzaJo^q`d|TkzAzZ#gVp<39+9uQi$(p z14Xzl;#w}E_F#BSj_^BB1euYzTe(=0zyn3i05lu~1QG;kF<~{2+5H872fc~(Em&0e z1I6&QjAUly5Nb|v&+GSVT3OP}Et8Wjot1G6>msvwM^(edHpRFDK~^z8hqBtml{Yc) zJu&h4#y;_@f9*J>ZzQj*-6)HfcUH(jPV4++%W_#E*-8qF*%-vAMMc62mGL}Vl_UWB!a3;W%D@||?( zJ)kiHLM06i&8PdiuV2MZHwVJ*-50-mHmruyvD+{54-n(xj%@TLV%^x&Gm~;Z09q@( zu}L#}E>qklZEeKrf9JDiXgh9pzB@5a0aU)DxpeoxbdQO9%@h4~j;=*`04Ow)Nd3g- zV?vJVJaW9)GLM?xH5JJhglMTD8OS|rlx?B%xrXp!N6a}xv`22r8<9GqolSuD} zEN|hkIynKAyU@L|p>GHKGgYAyISt31_yziK8J=Md(a(4|H90>GsrkSQ2H+L zhlB!^%fM3`P59HyL5Pr##@=K}-9oe7f*0&p$PhMxWVp@xdb<1KNLvun`Q6PC1nhke zyLA1vEZlTD$cL$mIe4^7t?yrgl>XDjb8_#~Q@Sy+JVZd+r{M`$Oa9|9*p2^}5Mn=W z(|47CP=VWD9kVU>IEcBpnUh#yYAEONWj^9Bgl@~xhy~MX54xktXB%Ail5mFRQ10dY z4Q?P%&u_0Ep$4J>8ut51MiNXOE;NokjMFlz^uDwsa-_4 zemQoZ&x(jAw||zwYBFrT3CElXZy9b6!B&1WzevrYhR?Dw!c=g3f4B^PGG#6tc9wTG zVDFnnJRwL4KAOC22AN-PD)?H2oQX}DLi4`bA0AG^><`D?-3Sz9RJ~M%3VmfHRl?Fz z%0Occ{HM}OLrxfp+GA1ajkb}FyKNUOqcDkpbGQaO9lb~VaK-z0)BEhKHNgYKm{~~= zdbJ*57C9nPYwEkk3&PIEs;5FiLTVgV+8?Sk2w&dw?L^Pw1y2bT82Ys68XnxD`a(GA zOZ-(@uI~JYdHt=oyqYqm7xE47%O(8%w4565b%vz9mSQ$Fm+nykt$C1aB_lk)V|?S^ zzEu^_FIqQ}+&lkqSmSlNg12^buDDBW2Z_Au@Z9pIDDz=GaG#zxFF@ULKaX9A+H2E{ zSYsus+85fsd*QarCm*8Po3sa?!RHA5}8HKHQ$$9)n2k;b=0dFPCV~KmfWx5GL#+U)@;6Bk98H2 z)qn4w7s-Y1y2V69rwqiQ1t)tUAU>_&)duQ1vy^9v&tQOCW2;NUUQ-8vjW* zxwWO`aFqY8X}XZVK~7E%^f=1DxNgx)*8h~tUzML@>$cSI#CJ!shJ%+3LS*aD-#56H z^f2CD&@-{CplspuTGFqfv#lDT*fH1?RD9}rXLV`S8vpMNJJk9EFFc&7_JDVDd%(W)}vbQ8zh^W}%{_7N*LwEZ_ zc{QZPHC9-rzS1b9Zv3>u9gZg;4M_mH+tib?=5uTB=s))tpmK_Uy}ZaeTCIrf(jJA zjS&q&lb30jn3(uO4k*qy2X%^;UR_bdL-jAlfP+^Agja}x@JS(v2Uz_E{}VSJuiy`o z{*t1JWs|uhFC!u))L#;gByJgwCzjocXzH`b{2tNB>3Ni1$&ydA64&jby3T9Dk?!t~ z6jqDhuc0REcul7(<;@oxOlRILY^-@_$&+x9f2dCrwT5@y9_t(abvbd}fj*(bwWeKF z`R><*Q$%i>xaJb((O946PS6UI)y~n-6r`gA+Q@dV8(;|2VQM-KxVsql)W4bSGa`9r;f(Zwt}hF(;?WEJk_~@m zLt@xz`JKg47anxg8Qi>O0~NX-m^$+gE1kTn6B?~Q6>S)}2AwziOLt^SK7}t4b85Kd zPbX_*pUs{vXiVUm)JmpcqX^gSoqFgq-_jue`wGFEGiTUMW8&^*X}wH~|G)%v&0Y^u zB$Ms6ksftEB$~OmiQin@rr7*@Wm{k2e%E2_GT|Wz+c?o~h+p}!hbEb)P{9*}QOzUR zJe0UTg=Jk(ZL28bUBTbB&MuH)qjDmr(6v&}DtTR!Lw~yN4C$Uf0UUoHY$K+ zsJuRAeBOMi=%mMH2w5bif;(NS+BXpfu!{G3AX$3i_*iA@zLX)2;zoSoQB#%_yig_8 z-r!=*n@NVjP$t>UReG`8!pTOgqzUJhEfL3($VmQSL#~==OVii;tro(m5&x?i zM#F~UK0`f~YKp=kKi#Wp#%39KG>y3*!y7BXL_ss>lI@AN9jm5m23fh)YwO-ZxhakA za15SLcgvy19VGXu(R;39u^y`zL{eCTjw%$< z++-o#K|w_8oQ&2hcpC*0qmZ1{uaLZ3DVo?ETKjgKp>hh@1rsVSwSK=HGW~^et`=eG zd3*k;xUs$QR&Xlu>ji3P_;G5t-PM98^jEC0?^@^5L_DPg^GiZp^*#5NT*$)G_{KZf4vtha@h9A+lc9^rciv=G8~me$ye?98OMc#?Hb*|p z#gV4OJ3@Bf#Sr8)~jP0o6y7+5;>g< zOLVpVg?ZpxV|af&w-X@U{0HyXLFbiBd(b8QQm&y}dsyNW(iwm|HZJUi;w@~3m-AjF z8Mm=0@pp$u;XJ;-(O#}X_C$iwk%(=an7&!({^CXA_R`%-%u~fw#A84UgwTh-Iq6+? zYc(VM*(#D)NZ}9cdx4<6O9g+GiwFE(K7_wB*c)1HoF7Opnk1gX)1oBi(%r@`>@=xS zSn6)RFkIN$#pohTyKd`Ty&Z>9FN+G^EPMtaaOcC0l)t#>zlw#!r(ATFBwC1Q#xlhw z#zhZSpAqfJqu;#wnG_lgsT3Ee5YjggC&Td6AAk0n5%SPN?X)oa=9Bid?jbex7GVV*b%SU>A9^iEPhj{!Yd5Fo z58(XI%*WHGPFeu*S6T^ZwpISXa$YFB%Pz^Q@Lr5@HxrI6jN7+qV)^SEufK3}(fy=i;NRDdJEnNO%%b!T9qa;nt5_ z%dtUiFX8s?3D2#atJ`L>VQp*YhWb}kCw8^o5tHv~6SI9&n2%ZslvugBUpILiXBQR~ zxkAr&zDBf(mhIfhyEZtiJmi4KTSooZOa{e?7l(;O(8I4&nxz2B{f_dO{{%{of5NUm zu@u6eVCjDf(f))@Rm|K^Z`Pf@t)nWMM+ct#0OAY(!SN z_YC_P>IMY@CyzkYI75~+Hb5(6g-XZYYdoWEmOfps$S9c$trLIsDd~PTH%QvoGtqj% z&lS;PH@8ql-7tv1jjYfeK9||R*z8vcWd-6`>42yA1gKTfc`8m#YMc+uZ;PGJ=}b%| z@RGq%wZCVXq9cwjlUgvo7t5bNlmBNg=Cj^4%g=@a)&+8MiYQ+6W93fIpFS4A&9jz9 zAy&)kD$&s->f+(bZ!RloRerpt7ZYkB&$b^+=XdwW%5~%T+q#3tD>?kO^1E~$Lr!t+ z#bPiPgLNg}16l2to>Wub!=~~=xpKytdKD4UR=LEn+-2hz(OorgWjcw|zV0f^`j@ao z6poc-BP(N~bgAK#fTPw`T@@!=-RCzl(I zYPy%lz408sWJ;KB^z|1N)T`M+2IQ)^O1@><5O_4>sngVG&9L=yU&Szf^&CU+^=A!w zt}&T3&98t{KA&u*9rFy{aORLo|Fj1Wr{#%G8qyO{~h{h1}Ut>;qcM&1hL&-J9`ERJwf|RYTYl z9!NuTOlXyQ2`)}o;{cTX9u)d24PUM79omPW-QA+8CqVxq!=RNQlzapHP8Sn_m_k-0 zQDD&4>L=94g6>L9CF;BQ-OOan(`6acF2%ou5xtx!%k3F zQ5epsd;t$A;2Lh&Xrmv^7HZVwkFI>qDKNxR2cUI>V@h0WGMrr+U#~V=Vg+?AePC8& zC9%aMb)zUw|HKV1Ai85kDRx&i&lQB0O@J0cpf9tZTG8y32obRQ)Np+wX0;0}$j|zj?MniyGV+{=JopK)ryu+r~ z%DQ&0b<;CIM19KIE?9G3Q@9}80go!EI`|7oEYwVHpGbq=o*xWgHlh;&#c$_*3Q2Gv zDM1(VxN6YB$bT76Vk$!?b}f0 zE)f(!+0-90Ex7M#Fn4q1eGaB5bFMAgv+cs}s0VZkjHNl^#tJA`ln?Ma_tF<1j+Tq- znILhMAPW`aRUa9BwZydTm=6{&1L&G8Ix8i8<}$HR4Zy;Rj+UZa>Wv!OnhS~WYH!_% zDI*ta{Y8Tbq?D6JHvp?5WzOx{#y*L?vPwjwI*-&ZbiJ9tN-L<7jW2}atv?-n?`@Pf z#Q{&vlSSK+9;pDkBdkQOM0bOgFHu)Fx3#VOYgPkPXJT=`-`QVOT>NB7La1KW4~XO% zMY#LazX(2mPuWwj3C6OXi7yJl{|mV(e|im?b3lIhm6nw3rnU)eLct}wHnM~mvh${?7%Jgf;a#?jNTvKUv+_VRU=nZu< zb?PR~hx(f2kGUs;rJLS1g_GBsidRG?3x3r~=PSxF&dW z+js+e@lEM6#T&)kD ze6*3LNh-lg1~=;Ip^B`yg%4hIw2x9o@4H>ou{hq`+G@1PET}iF+F`%Og&s(z6xEbh zt+aqId5^qP+u%!v2Q$WG+dB|6zD1F)-+7mvuS@%q>r^&n7E>_)-EIvj_*?frHy?o$ zyHxt56A(U)0LZN;pJN@Iur=`^d?cwFgU|+d^&YSPVngkev+g!Q`JGXsA@KhJPMemo zDpAniBtyIRxH%l$vq|fv3K`ZuiW;x2<3C9G2eqi1yr=VFCMnU%ki6-2F)+Rk_w+NL z>s6bo-|IUh0`?o+B>Y*;g&JTEZ_&X`aL-uKYFHJ%3NL?wFLxshh?Ev>FIPp?k$r#a~?A zBo)Nop}xmCNHcd=K~YDQM~diPlW(Hy{Gq^hl7pIVJb}HTT#v4>-^zfojI3?5Uv;Gt zk-4C&-dY`lJBjt7DUIllmv3|;9d8=@-}WbM)=tDrrmCrrA}5!~foXI<&mcbHw@-9+a3Waz`JY|g~}(4lRsygtV{5={7`JN zn7Ua0Vk#QYY}nlV)NP{8$c+g0|Z&Ut?JJWn$${Wk|a@wMKQb&)y1oX>*N z+JvzC#?hRz1k*RW71U;nH=^jEt&C1i3f5e=quk7nbB*i;+s?)4JJuF*tgT-0&l>Ef zy#G6i)spL8Z%3=elfK52)mPMqd+BR$hgEt_amDN$%!?XI3EU?)6PT%RpQ*-`@I07` zO$s#8@A^y*ws8JKbjYcw4KUn1Cv9zsN1ZxUY?+LGyosFt`_h+ACFO3vg7jx3V_2Vh zjiaMd>D43@m>XZu-a0`Tf0Z9*>hX1EUwlZoZAKvRQ3}YQH8qbm6SU&m>$Dq?ww+ZM zm4OKE$1){df2*LMbhFD(eArms*I@8QH?`kmp~^7vYi(Q;yY(M({K`;PYeCTX#Q=r< zXCOU;44+J($Rx4;0$XCdXx}%tI9fVd|9RZLkj+53+X%88B>dH-Et<@8~(; zijlxUE1uklF>Q^_}a@OcTqWUMbv|4Xhf79{|PO+)UE*=nakuey2*J3(Y zVA4EakS;6Jt5*ql`xX_Oe)yJ0mv7L#W~xzbhi0gW3N(0O5uu#YZFX*<5AbyXw7kMD zZ=tUy`K$qWD}tmar&}jIe5pa9!%r&DEXc)Ggp9BC;;nZIfC%|>G(05_#59_1m*PMo zx6UI29?fe!ffVa^IsE7Un9fLR{GslccENg+d&76-LzE%ZNrptKWb~PRK(c(5SYj!x zXxkEZ671{5^U{i2PcV3G%fxdb#&3A-vyB`z57s$bnkqESAT&HgB5adAIo!E1gH&lu@SK62Zn42v0thTVM zEhkIfXtXwaYI?|P5nyEa;-^>vnRW5@l+kTBz}$I|qmzq5!QnW`5HBe2;@GT4Xu|-- z#mvNL?AD%o;jT^JXM;`Ot|{O>8W1*h99R?lHrw8*wFnPp)5gn|YPC!OVPmR-rKVxg z?3mY-@pX!h+DP*Jv72X8?{vIdLXn6s9cAW=5P3`WWjKPVu(@iaOG*lRPtQY9 zXdIX+E0NuLj@$(T`8U#&fCY`>ncU-942zwSUEN0XFTPo z!4K}{$SwUiCpQhry91xtb)ULQ)79FkiuW=0b?a$0*9z9yl@RKtKJ{W%paEXOdegov z7UMNON>w9C7V6TlVegPHebDI=tx)H+N|>9)KUp9RWJpZ5&5PNg3Tx+Xi0gGdJwRG! z1Egh{R?yzRICtx{Y`r&dHj*cXG^avj+ACXHz{TiEp6=9@*K3wU^y(vERx?Pkxl}-! zaPgDni<#1Z`jQsTHOKn7fSR?Y-&y01!;ZRPF_?*&CV6cK3y)`Xf|zU~{d(JYzE zCzh^sc@^QptGe6W;Aa#(8#8KHiltsb!?#vndYu<-;oa@_++@xqg6#a8$ZXpQR^bWP ztJ>~HAy#5X3JFv3@ zV;!A}*kZKC32Dq8I89S)2KBFbPv~$&cfIEr<`U=FKPjyG9BDJ^_0H#F3C@bp9r8y% zX@+c+EcqOOLk1w&lILlmDe;&MuXxu4$sI+Gq17E98_SkSl$FOdR7`f;n9uS;bYb3B z)ioshmFWuxa0opuJibZ~>m*gRUd75!~yk6Po*@3w~6WALE-6nwdF2uk0xzGz(P@+y$4Y80y4n zhJ!mP%Q!wBfz{R3i@bt_5!sqEcq@$=t?ey)?7#Tea{fL$D1hnu7ySD zpQPAe0y$fXTBfH?qD_&`M7B(bXk?zBx|vV*!T1NSzB(+BK%NX1O#Ya97%RQPzJ;}S z#v-9h88|Mmw@n3*;U&n`rAI{rIF#WGOHF){&(N&ZAiUdu7GKBc>Ai=Q@6x@JuLh4$ zecxB{+}u!5c2#{93{RdeD$k*M+Onn?{?@CAb@7 zXvG1>Cd$4b+2Gn-b)V?kWXB~t-3gtIL5F6XnoRL$-^ zDm?qe_gq*JAZ3%V{h-|woL})joS%X_^WYa&{Vk-rHFDP};3Bd6J7!RVR!5 zM*6c9Ni3krKVHB5ujyjeYpgS{K5#InYzl98k^pU_!u-d%ura)dr>WZAU-*Wt1oiDa za0f(+Qb}l>@El>5kQsas37l^N39It|Ky0mg$O;V;YEF<0RC%WY(V?Bef^MW%_TCG> zm?3ZZx)d21H5HZYm>4QoMd|3&@Y_Qlh3^6pIfpTeYmjZ{;34dOb?rHr$6*W2cXwe2 z)`Rr8O35>?GgkqXPQ=INn!_y7GiQ4V0r}Y@`DnH?IS^_gQ4a8Y4g*qlX!G4i4{tol z1^Fp&{!9iJ+AXqKfzW=RLVzjMPCpW{+ylu&jcVME&Vq`YX@};W# zs-J8_rqg_gyu5qLoXXLb+-1|xck+IZ}gkey0iH)WJ8b=fy(`2an{>+Jxs=&kn$Hkg7pLV_uAEITzaxz#}@IfcZMz z`ss+Jd8vl@E^+73Y)4pz6!6|Kn|G~y>Oku~%X&_YvV-E*1o>V2hA=PPdSPjPOIy86 z=K@)x5u2|{w;xdEIRs~nr7JFF2hb#$gM2f+1c;D-4mRp@)Nb^v++EQe#%)bLvN}qf z*`;<)pKZF_RQ^Cy#@9F$zrRsjZy$ECA9ikeu`{kykkdC!Jw8Ed>OqxxDsML?q#1G{ z=OsVw&$-#agn2-}Ab}hZF%zCpHKuhj62$fWptmBzS4cAUCGk&BGC3)DrU0xMyuF$3Btge z>o-fyEMc<&tDhNBY=Af~g+$b7^QPBcHp%;09X;4oU5t4dZ#$o3T9N)_ail_5Q*I6V zqR(?Ko=x}KwAX%QRsf$ZoB&jiDd%c^xtOYm8!FuF>RM<>D!p){P;mSSfNELNU|@1< zUhA<6`G$~5FRYuZH{R<{!O3U9Tbaxx+4J?)Ey#Cn<>>PbOk-D#AIVuoRgGws$$s(W znnll}!!s!#9(mg<xU&7AaLXC)BCCz-@8wHNS+B}U2FUdz=7+v#~ zKMCH-@hs8gSAB{pNRPaB_GQ%1#9d6FsyvdwZ~yv)8bs$(bYYnZ5>VW|6Ap+0g^skbO8Pm?YcSY7W|N zDBjHb=|5Vm;hiAvH4t)x8%_5uZfGT1th)+0XZ(11jCVq-h&8^T_Bih1z(>-$m_su8 zL<(XXY$vceGI;ee9uf-)H@hudbM)(rUpQB;x6`&E#F|UIAKQ7_Qd{2_Gp+!hGf9A^ z(;PT*;`O=Ge1lKI1(CW?`3Al@Qbesc2(1Y|4umj_k(Utr*iz{+O_0UE2%F3vn zEb}$Eg2_s5YNr_n`x0Kf`&6S**L<4gS?1}o7ckwV(e0b$Bs8~pIddZG|KjrojN&q| zf5%93kiT~+U8`E}uzrH|pexVMc5i&M$-Q&R*qXRSnK$ohJg;(AB7N4mm-U<`)5Ut+ z&nWC72@DnHP`ImRoZC~)lgyBc%QeeHdDEv=3a%ZRrw8?nD^&TE&Q1Msh}WgEla~&u zZVLD!*pxv*K_g)}Ooa^%IuON7h1u8qXDNqEY7kT$Mhe(-BR42;ovy~L=VfXM01cix zbp5zLWef!mbhbEWn3FrN8COHuK;!@mwWDW!n{ z6rgzAL^7FwaHy$NC3AYSzgWWNXs!iI7gjDr+O~bj*RdYZ_UP6;Q0Df*M$W7 z@d^XHi&ISsT&{V8!eJf7sx^3?MAv)Py{9Z7BCgbu_1sv7<@akq|A#}*+AA31Zb(B8 z(c8vHc~uJ-zc1;$LHVyQ5y7cP2%|s5k5uQlXiKdX2O)7}PT(~G_^;9x`I`}fsNa8;ZN0Y7 zYMNB~*o$VZ>RGztpA@Lol6zszTO>L)$Yh3+`=GZ$E z5KB^M_C0q#00jENW<55iUDaFU^wnuS&7dOzj0ey0dMW;9zL}-vGS|;0q*l)yV!sl}&#vX{eAentzUG@_6c%%}2h#s0k2baZ}#7gw-_pnU%(OWTAZ zPS7iRSj_E?yw^a4y>t`2!b3iA%;Vgea-vgEpp3^~;r2?sEIwbVctCHra;}rv4Yvtb z*WLum=i=~bOoG=!_=3%eYf5MWG1rR0hIuKaY+R_v-{VteAvsH_o0)2%u1;|T+s zl2nj%kroqXUh$GSf{s?4j`1)vb!w|+LcoPR<>kgIzXj%Z)i=`PgOF_sp?@Nja6GS7 z70#?#c5>!0?917vjGtx_|1(DRr^(t)c1LyqFk4V7XcJ6BKHq z2#T%Nq_11%i7Urj*t9uk;}@EZ!!~1h#pFw03b3-4rU}oF;>J7Ez7LH!{Z2^dG|wF# zSY5$2q93!GR!{atd)3+2hGL#Z>QJ2~x^h9iISxVDu3a%Xlcue6X)RELpcdix zP=ar@Sy30Kl9>YoH6;uMCeBCPA$vD1Z{yJIv7J8-ToLqJdT$&y+x6rtFD7-5 z6cL7SY0+|7u`q3x^k7TSq&^4rI{m(!-@3!3QKZOeNi$SIt=tKBld?^+8_str9|V00 zxr6&IW*N5mtnpKf{G_LuKWxDcMO&Wdc~!20Xjkw2?aq1#hk7BlGiA9I7@Bw?p4;o) zc0k;f_?2gf*){()3|xv}j9E*qxjPQjjONIDufk{A2Errarh@~@Q0A!?LD~8HRy4&7 zDs6tStvcrzn}M4c0(jqFG*urKv=gp6OmYi? z7vT-;pRUJ1@*mmwOj|MkcDScnvoa2olI@X_mpmZfdHm*4Uj(iJ?AUsp1`Wr$-7b&@ zg>=Cwou-Qu`BD-T7Fq1Vh04sCYNf3w2uI~~+%?sAx{0J!!}ALesb$xx)JyeU8=w%) zBUDi78&)(@?|AJcU@*-Pb#Q=5M%nK!yk0ZwF-Pl(T_-f(CIuXpVIjO!>MHXhQnyc9 zOl+>Zdn=sprE?8TY4b_-16i6o##!I89@GBTQ|+`%3$5HJdg0Z>z6e{X85$Ehk&`dw z^bmPqo12u2jFt$?lAVPX?roJwfci)KZx-U6!*gu8QMrLjijA75pNU+v5=2FjqnMy4r{H=vmQCGf71GWK zk%C^8OaF^ReNJ%B1O__`@dR`4o5>pc(~ay;?FB3GS+p8o2|||p?R>^`Q2`y}!HFAX z16P;_lVCze89-*D8PhUm&D$h8bsSNZAk34TWI63EFo2AstuZ@3w6_=&0w_iO<4gBe zPlfP*eZ&$HF(z`c0;Gkw-r|Ss~xd`1bQT2eW zF08CB59=pRyX+NCBafN>pc1PSGK^JHU|J1p=p(nPCrEG)A1_g>H>@P)QK_1bS1e)_ zOS;`T?k>Lx=ff}BtEFOXdvl@=#v)k)5o;usmErIXB!7LNR~N5Wb6qF*U0vlJ+)lZ5 zP+(myvoxRBz{c_;z>d@3;;y>BBza?G$yl^XJ<%gu+Iy_Ov<1&T)h|YHZZVoYZap2B z3~%KVm~OWa(A;I-R_@t8khg{zhtR-oL5LO(9H~@XL9)=>$^L~)-r+{vwnu^hHWZ=m+Sn(r8xi)<{t1e3L2 z2TY#MPv;dg8o6uQ6kj)M;P96jVA{51x2Gc%qs@osS+jR(uJGt>CS35k)+PA+R_BI3{N#e zAzN-(;;sy_z?f%2Ktx!0tf;EDE>Co0Y5cgm(-U8pRa>uB2l*2yxA5G0`aE31YGq1_ z=Pje&aF?y>2a%deqGNWarR*(p1Lg2*u7E!z)KkEItmQ?N`cF+FC88GM7}`cjwP6CT zNTsTp84XyjA>=Uk{Xl8`=Kr!>gU~0N zt9J_#Sqar0>Nw|dZz}9q@L{eL07x?0tJawGgML}7&BEdJ!e|)th^8Cvl-bWZQC-!4 zk8NjFJN_vlW92DP9nH1wp@r4BoAnNrUx_%|VlW)(8|ZylxjBNi_9Y+xM)x_ZMhR4{ zlwUu%X*Ni5jcm!4|5%$uX*mO+;P4Op`C|hUzv``h9q|d6do|=dH+|x^R#>d*{z2wG zmoXRZ$=5%eTFFDCMZ=a{HMn>hg}H#&M)dZ=d)l#H15-ThZC!LTp0wyoVMM~&PyG8B z#6Pj5fAX#W2K#;<`=3M?{sV{zRQ8YS;{VSo`Jdgo|GEi)H4MOX|2?+*uea`B0SKj+da%m^yyZ+fy2xIjB94pVVj);d| zu`E1=zu$HT=z-Gc$k|V4M8KD9;jZV$pmL16d?k0%ryZ*M*9)b{{b1}`+$FKX6xV`+ zf@`%(UJrY_q<&Ha|L>v4+ppd%QA%<*_b2W(8GxH_4o(n|?u8qQ>+dtf?JG-i^T#G$ z=?Yfl4TsqKU13>vqZz-90e`D`df){K3aZ+3)BdPK@OAJp8dY4dk219?tv7c~&Pa>$wTwuulmN1c%Ec(L5>bwgZr^@Ns7`UaTBC|q4o0M1&f8IYeZh~txQhga zV)?{}a&ksRcvzN~s!m7++CFZ*xx7D#2T=+hpRRI+R2bw)0jpHn{KkuHyAM(v*04M4 zVF8vCN%T}gwL8bc?lx+Cn3g6u{_z^mdHbRki%eW`>Z2%sn8w0_jm%TkW_@++TD_lO zzspqz3Dc{{>X1~izfs@i6z zMQIX=OW6=&F@O6=_GGbi?7&JvY{v5Hce=?=V#7;oodqe-9MA=i`biNRx}9_pI@u!H zwL>12ChVJ-2p($Z?>H+Fg5Piq52P<^Cs%NmHfUcBK_W$qDpbs%%PD5vF8DUUSb2k+>Js0=8uF5hxlMHb~*P`u$o zbaS&RabY$`p)_Ty*r4qXtSfqA?r~ZnG-ZR*Z=TkdbHAdCM7buUja=YqU`02S3Zy$U z-4|3>onFw5?L4RQaTBf4C?DEn5!(<8vLLWUbuX(^CyOO!LK z4D;|n44O186QnfmR(M&j4W!H6R>Cpn;;U*}ykkS#gCD#!lYF;Spv`MK^e?M$@vx;w zUXdP66s<-tckkg;gT*&QbWIsH#8DuM>C4K?$#Ckv8qE)C#btit!60K0-60}1CVftn8+9>eD_jDx_kE>p=R%~G!8{u zVju{c?R=}s9(q1Xmn%!ai-A~@w92RVDrOmOq-4_QuL5%Y-^V#xz-^^bhZ zt-46pg5pOl0sR9*aydJkL#JG|xE|hB2DA~hQ=3??1(b10KQH!R{CTX4{7Pw4zR6y) z6<=i2V<%?orUlQ51eA`1M-YxSB|f|hazCU667}(+V#883bxK8!jjY-bB2qL_CMx8{ zh2YglUpTtWaHxS=*&?PM8AwpWhIe{M4fR%``}$bdMsFESmr9eNVzpb%KSI;RcOMkk z%|xSqaEh74&|_j`5pD(bwU4&>p+?z%z~8Nk<4w`p>M)kcGRaauLMry2y`uEr4KK?1(9U*iJ-i;ZFkXY4i&RVW3ISJx9YRcUNo|&TP8;#PpSLmC zF&(3|M5FIEjc7w~KCTXBnX!<)Xd;$=179lWIN|w*U36GIjLvD5^JU?}gdxmg(mGQ~KQP5z+x=>b@SwIY-<0LvTsJEf(`JGxa^jC<}?wcLj zU=_hUbfANKxp*GwO3c@g2YgT_BQL-!lu-atog=OwbDnC}W;Q0KSiokAx~ghxy+({@ z4DCDrCrwuF6l+`Qp8V)?$4ln$+b8}Kg)9wT!$z|nMJ2Fh7Z!j?cU!J0NQj02^dPG;7k4D ze?Sm^rgPTt`MQGL`b$B0Ia{6>W#kB*bQFl^ zn6-y^s}-wl7ZEqtF^5-mP^84hl|vNG#%V*aZmFxq0o%1W?Ksn_eqR1^=+qc8L@V0c zu!qLhwmxScx-*v+F#M*)ysWXkJSwT!ev-OAChw{BvCj^8@yoMc;@#&z{Y@PKEasoN zDR*vn-cMovslxngR_5Y24OWM8hiX|0{cLF+s+rvG%k#;x1F4kvZ@Vr;sTfa7#vi<3 z3+d|dm=J>9im(lx{&0?(X>w^Yya5I#HE_D*)H@vX)!^!#VgsKXpj0Dwe5l@{z>MBdJK6eiypfu?Nr}TQn@_w^sxY_h zo0HRvH6b{+?KOq&p!zzucru;|uD!1-f7Im~VL!H<*hsp1`}nbk7N9DEyx8baNoRXk z;wOhlhun-#hbTL(fcuFO5TZs~Nf`;irxc#Dj(7L8T1N3+`(bvuIU zNby6?XT$-jkz+1JAyfWwEAKKx+0nV|b~ogRP*^%TS&5FZDpLjoSq$xhH0;mEqbd1K zn9_swZo$2fMZ=E(dD?rY<#(-6`_IyNnnD*|x{H~SbSWSKKH(Z!Gotj9^jp`x1Po%Eb zuHn!TEsgElth)-nN#Acpy(J!e*PxGcBIf-tazMrc1nLOf5I(CrX&f*#ozaDW~FRzb-?U>l)(qZ=Y^ zUE>aL&9n5wHN20ut$Se?1%~~j!Toj^zFBi-NkQjuFs$y=n;@&FX%4vs(_02E5&Y~d z5%B8OOB0t}Zl#%=?VpVk2oM*KF#UyTMq!CdFD7?Q4At?V`aH>DqLJ4tqKLwR*U^Eq zgNCOsD{8SBJk~GLfs#+(_xH6@R`Nyfb8EcpRY&WxglXlUE(UFnjE;)Z z#QE-C$b=w0ez?MNZgfX!#>61r-Pp+uwDER`HL`u#IFj1nV+s@JIDvu-bw`p5)%=^E zw%e(ENZ2~7*jmEWhZz*u^H4@cMAT3?)H6FcSOUwu2p-^;x!y9CLk?6t2i^+O8`?eq_10AaFV~skym&?o+VQIb94FL;BYMoDDts)u5oxzR z$MWn_sVosnJB}zn7qRAQB6y;Qo)CpNer$X|b$#X2p9S9J;!bgyn@uWVm0dZb+FH@I z6MNK1hnxcei;D?knBm1Q^#gjJFOKz?X%ju|Ry+k1ipdX;{r_7Yq4=%BZ$1Cpl+RMf$R4!&Le$loZN+YtE;LNXBoQ)_{~$dyPq12MaUR z3l2rSg~yJ)i<0iix>=V$I9E1M+?g0~pMST38=ZkM7Hsl&iO9BCj9}@mIJftS?X?n* zpDy;CzB^07UoQ5MP|47d;n|^Ql9g6h2^Xt�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 J+_ZWAe*p6LVYL7N literal 0 HcmV?d00001 diff --git a/docs/images/db_migration_move_pending.png b/docs/images/db_migration_move_pending.png new file mode 100644 index 0000000000000000000000000000000000000000..f56a88947a4836a4ee9d7c3487c19f038298174f GIT binary patch literal 37193 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 E0K1aV&;S4c literal 0 HcmV?d00001 diff --git a/docs/old_changelog.html b/docs/old_changelog.html index 4b584f86..54220f49 100644 --- a/docs/old_changelog.html +++ b/docs/old_changelog.html @@ -34,6 +34,41 @@

changelog

    +
  • +

    version 544

    +
      +
    • webp vulnerability

    • +
    • the main webp library (libwebp) that many programs use for webp support had a remote execution (very bad) vulnerability. you probably noticed your chrome/firefox updated this week, which was fixing this. we use the same thing via the `Pillow` library, which also rolled out a fix. I'm not sure how vulnerable hydrus ever was, since we are usually jank about how we do anything, but best to be safe about these things. there were apparently exploits for this floating around
    • +
    • the builds today have the fix, so if you use them, update as normal and you are good
    • +
    • if you run from source, **rebuild your venv at your earliest convenience**, and you'll get the new version of Pillow and be good. note, if you use the advanced setup, that there is a new question about `Pillow`
    • +
    • unfortunately, Windows 7 users (or anyone else running from source on Python 3.7) cannot get the fix! it needs Pillow 10.0.1, which is >=Python 3.8. it seems many large programs are dropping support for Win 7 this year, so while I will continue to support it for a reasonable while longer, I think the train may be running out of track bros
    • +
    • max size in file storage system

    • +
    • the `migrate database` dialog now allows you to set a 'max size' for all but one of your media locations. if you have a 500GB drive you want to store some stuff on, you no longer have to balance the weights in your head--just set a max size of 450GB and hydrus will figure it out for you. it is not super precise (and it isn't healthy to fill drives up to 98% anyway), so make sure you leave some padding
    • +
    • also, please note that this will not automatically rebalance _yet_. right now, the only way files move between locations is through the 'move files now' button on the dialog, so if you have a location that is full up according to its max size rule and then spend a month importing many files, it will go over its limit until and unless you revisit 'migrate database' and move files again. I hope to have automatic background rebalancing in the near future
    • +
    • updated the 'database migration' help to talk about this and added a new migration example
    • +
    • I fleshed out the logic and fixed several bugs in the migration code, mostly to do with the new max size stuff and distributing weights appropriately in various situations
    • +
    • misc

    • +
    • when an image file fails to render in the media viewer, it now draws a bordered box with a brief 'failed to render' note. previously, it janked with a second of lag, made some popups, and left the display on eternal blank hang. now it finishes its job cleanly and returns a 'nah m8' 'image' result
    • +
    • I reworked the Mr Bones layout a bit. the search is now on the left, and the rows of the main count table are separated for readability
    • +
    • it turns out that bitmap (.bmp) files can support ICC Profiles, so I've told hydrus to look for them in new bitmaps and retroactively scan all your existing ones
    • +
    • the 'edit num bytes' widget now supports terabytes (TB)
    • +
    • fixed an issue with the recent PSD code updates that was breaking boot for clients running from source without the psd-tools library (this affected the Docker build)
    • +
    • updated all the 'setup_venv' scripts. all the formatting and text has had a pass, and there is now a question on (n)ew or (old) Pillow
    • +
    • to stop FFMPEG's false positives where it can think a txt file is an mpeg, the main hydrus filetype scanning routine will no longer send files with common text extensions to ffmpeg. if you do have an mp3 called music.txt, rename it before import!
    • +
    • thanks to a user, the inkbunny file page parser fetches the correct source time again (#1431)
    • +
    • thanks to a user, the old sankaku gallery parser can find the 'next page' again
    • +
    • removed the broken sankaku login script for new users. I recommend people move to Hydrus Companion for all tricky login situations (#1435)
    • +
    • thanks to a user, procreate file parsing, which had the width/height flipped, is fixed. all existing procreate files will regen their metadata and thumbs
    • +
    • client api

    • +
    • thanks to a user, the Client API now has a `/get_files/render` command, which gives you a 100% zoom png render of the given file. useful if you want to display a PSD on a web page!
    • +
    • I screwed up Mr Bones's Client API request last week. this is now fixed
    • +
    • Mr Bones now supports a full file search context on the Client API, just like the main UI now. same parameters as `/get_files/search_files`, the help talks about it. He also cancels his work early if the request is terminated
    • +
    • Mr Bones gets several new unit tests to guarantee long-term ride reliability
    • +
    • the Client API (and all hydrus servers) now return proper JSON on an error. there's the error summary, specific exception name, and http status code. the big bad 500-error-of-last-resort still tacks on the large serverside traceback to the summary, so we'll see if that is still annoying and split it off if needed
    • +
    • the new `/add_tags/get_siblings_and_parents` now properly cleans the tags you give it, trimming whitespace and lowercasing letters and so on
    • +
    • the client api version is now 52
    • +
    +
  • version 543

      diff --git a/hydrus/client/ClientFiles.py b/hydrus/client/ClientFiles.py index f947df2c..c3d5beac 100644 --- a/hydrus/client/ClientFiles.py +++ b/hydrus/client/ClientFiles.py @@ -727,23 +727,36 @@ class ClientFilesManager( object ): # we want these overweight guys to nonetheless distribute their stuff according to relative weights # so, what we'll do is we'll play a game with a split-pot, where bust players can't get dosh from later rounds - second_round_total_ideal_weight = total_ideal_weight second_round_base_locations = [] + desperately_overweight_locations = [] overweight_locations = [] - underweight_locations = [] + available_locations = [] + starving_locations = [] # first round, we need to sort out who is bust + total_normalised_weight_lost_in_first_round = 0 + for base_location in all_media_base_locations: - current_weight = current_base_locations_to_normalised_weights[ base_location ] current_num_bytes = current_base_locations_to_size_estimate[ base_location ] - if base_location.NeedsToRemoveSubfolders( current_num_bytes ): + if not base_location.AbleToAcceptSubfolders( current_num_bytes, smallest_subfolder_num_bytes ): - overweight_locations.append( base_location ) - second_round_total_ideal_weight -= current_weight + if base_location.max_num_bytes is None: + + total_normalised_weight_lost_in_first_round = base_location.ideal_weight / total_ideal_weight + + else: + + total_normalised_weight_lost_in_first_round += base_location.max_num_bytes / all_local_files_total_size + + + if base_location.NeedsToRemoveSubfolders( current_num_bytes ): + + desperately_overweight_locations.append( base_location ) + else: @@ -754,32 +767,56 @@ class ClientFilesManager( object ): random.shuffle( second_round_base_locations ) # second round, let's distribute the remainder + # I fixed some logic and it seems like everything here is now AbleToAccept, so maybe we want another quick pass on this + # or just wait until I do the slow migration and we'll figure something out with the staticmethod on BaseLocation that just gets ideal weights + # I also added this jank regarding / ( 1 - first_round_weight ), which makes sure we are distributing the remaining weight correctly + + second_round_total_ideal_weight = sum( ( base_location.ideal_weight for base_location in second_round_base_locations ) ) for base_location in second_round_base_locations: - current_weight = current_base_locations_to_normalised_weights[ base_location ] + current_normalised_weight = current_base_locations_to_normalised_weights[ base_location ] current_num_bytes = current_base_locations_to_size_estimate[ base_location ] - if base_location.WouldLikeToRemoveSubfolders( current_weight, second_round_total_ideal_weight, largest_subfolder_normalised_weight ): + # can be both overweight and able to eat more + + if base_location.WouldLikeToRemoveSubfolders( current_normalised_weight / ( 1 - total_normalised_weight_lost_in_first_round ), second_round_total_ideal_weight, largest_subfolder_normalised_weight ): overweight_locations.append( base_location ) - elif base_location.EagerToAcceptSubfolders( current_weight, second_round_total_ideal_weight, smallest_subfolder_normalised_weight, current_num_bytes, smallest_subfolder_num_bytes ): + + if base_location.EagerToAcceptSubfolders( current_normalised_weight / ( 1 - total_normalised_weight_lost_in_first_round ), second_round_total_ideal_weight, smallest_subfolder_normalised_weight, current_num_bytes, smallest_subfolder_num_bytes ): - underweight_locations.insert( 0, base_location ) + starving_locations.insert( 0, base_location ) elif base_location.AbleToAcceptSubfolders( current_num_bytes, smallest_subfolder_num_bytes ): - underweight_locations.append( base_location ) + available_locations.append( base_location ) # - if len( underweight_locations ) > 0 and len( overweight_locations ) > 0: + if len( desperately_overweight_locations ) > 0: - overweight_location = overweight_locations.pop( 0 ) - underweight_location = underweight_locations.pop( 0 ) + potential_sources = desperately_overweight_locations + potential_destinations = starving_locations + available_locations + + elif len( overweight_locations ) > 0: + + potential_sources = overweight_locations + potential_destinations = starving_locations + + else: + + potential_sources = [] + potential_destinations = [] + + + if len( potential_sources ) > 0 and len( potential_destinations ) > 0: + + source_base_location = potential_sources.pop( 0 ) + destination_base_location = potential_destinations.pop( 0 ) random.shuffle( file_prefixes ) @@ -791,10 +828,10 @@ class ClientFilesManager( object ): base_location = subfolder.base_location - if base_location == overweight_location: + if base_location == source_base_location: - overweight_subfolder = ClientFilesPhysical.FilesStorageSubfolder( file_prefix, overweight_location ) - underweight_subfolder = ClientFilesPhysical.FilesStorageSubfolder( file_prefix, underweight_location ) + overweight_subfolder = ClientFilesPhysical.FilesStorageSubfolder( file_prefix, source_base_location ) + underweight_subfolder = ClientFilesPhysical.FilesStorageSubfolder( file_prefix, destination_base_location ) return ( overweight_subfolder, underweight_subfolder ) diff --git a/hydrus/client/ClientFilesPhysical.py b/hydrus/client/ClientFilesPhysical.py index de5d326b..c03abbbb 100644 --- a/hydrus/client/ClientFilesPhysical.py +++ b/hydrus/client/ClientFilesPhysical.py @@ -158,6 +158,11 @@ class FilesStorageBaseLocation( object ): def NeedsToRemoveSubfolders( self, current_num_bytes: int ): + if self.ideal_weight == 0: + + return True + + if self.max_num_bytes is not None and current_num_bytes > self.max_num_bytes: return True @@ -184,6 +189,78 @@ class FilesStorageBaseLocation( object ): return current_normalised_weight - weight_of_subfolder > ideal_normalised_weight + @staticmethod + def STATICGetIdealWeights( current_num_bytes: int, base_locations: typing.List[ "FilesStorageBaseLocation" ] ) -> typing.Dict[ "FilesStorageBaseLocation", float ]: + + # This is kind of tacked on logic versus the eager/able/needs/would stuff, but I'm collecting it here so at least the logic, pseudo-doubled, is in one place + # this is used by the migrate database listctrl atm, but maybe we can merge all this together sometime + + result = {} + + limited_locations = sorted( [ base_location for base_location in base_locations if base_location.max_num_bytes is not None ], key = lambda b_l: b_l.max_num_bytes ) + unlimited_locations = [ base_location for base_location in base_locations if base_location.max_num_bytes is None ] + + # ok we are first playing a game of elimination. eliminate limited locations that are overweight and distribute the extra for the next round + next_round_of_limited_locations = [] + players_eliminated = False + + amount_of_normalised_weight_lost_to_bust_players = 0.0 + + while len( limited_locations ) > 0: + + total_ideal_weight = sum( ( base_location.ideal_weight for base_location in limited_locations ) ) + sum( ( base_location.ideal_weight for base_location in unlimited_locations ) ) + + limited_location_under_examination = limited_locations.pop( 0 ) + + normalised_weight = limited_location_under_examination.ideal_weight / total_ideal_weight + + max_num_bytes = limited_location_under_examination.max_num_bytes + + if normalised_weight * current_num_bytes > max_num_bytes: + + true_ideal_normalised_weight = max_num_bytes / current_num_bytes + + result[ limited_location_under_examination ] = true_ideal_normalised_weight + + amount_of_normalised_weight_lost_to_bust_players += true_ideal_normalised_weight + + current_num_bytes -= max_num_bytes + + players_eliminated = True + + else: + + next_round_of_limited_locations.append( limited_location_under_examination ) + + + if len( limited_locations ) == 0: + + if players_eliminated: + + limited_locations = next_round_of_limited_locations + + next_round_of_limited_locations = [] + players_eliminated = False + + else: + + unlimited_locations.extend( next_round_of_limited_locations ) + + + + + # ok, all the bust players have been eliminated. the remaining pot is distributed according to relative weights as normal + + total_ideal_weight = sum( ( base_location.ideal_weight for base_location in unlimited_locations ) ) + + for base_location in unlimited_locations: + + result[ base_location ] = ( base_location.ideal_weight / total_ideal_weight ) * ( 1 - amount_of_normalised_weight_lost_to_bust_players ) + + + return result + + class FilesStorageSubfolder( object ): diff --git a/hydrus/client/ClientRendering.py b/hydrus/client/ClientRendering.py index 35b28940..252c9265 100644 --- a/hydrus/client/ClientRendering.py +++ b/hydrus/client/ClientRendering.py @@ -78,6 +78,8 @@ class ImageRenderer( ClientCachesBase.CacheableObject ): ClientCachesBase.CacheableObject.__init__( self ) self._numpy_image = None + self._render_failed = False + self._is_ready = False self._hash = media.GetHash() self._mime = media.GetMime() @@ -235,28 +237,38 @@ class ImageRenderer( ClientCachesBase.CacheableObject ): except Exception as e: - HydrusData.ShowText( 'Problem rendering image at "{}"! Error follows:'.format( self._path ) ) + self._numpy_image = self._InitialiseErrorImage( e ) - HydrusData.ShowException( e ) + self._render_failed = True + HydrusData.Print( 'Problem rendering image at "{}"! Error follows:'.format( self._path ) ) + + HydrusData.PrintException( e, do_wait = False ) + + + self._is_ready = True if not self._this_is_for_metadata_alone: - if self._numpy_image is None: - - m = 'There was a problem rendering the image with hash {}! It may be damaged.'.format( - self._hash.hex() - ) - - m += os.linesep * 2 - m += 'Jobs to check its integrity and metadata have been scheduled. If it is damaged, it may be redownloaded or removed from the client completely. If it is not damaged, it may be fixed automatically or further action may be required.' - - HydrusData.ShowText( m ) - - HG.client_controller.Write( 'file_maintenance_add_jobs_hashes', { self._hash }, ClientFiles.REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_TRY_URL_ELSE_REMOVE_RECORD ) - HG.client_controller.Write( 'file_maintenance_add_jobs_hashes', { self._hash }, ClientFiles.REGENERATE_FILE_DATA_JOB_FILE_METADATA ) - - else: + # TODO: Move this error code to a nice button or something + # old recovery code, before the ErrorImage + # I think move to show a nice 'check integrity' button when a file errors, so the user can kick it off, and we avoid the popup spam + ''' + # (if image failed to render) + m = 'There was a problem rendering the image with hash {}! It may be damaged.'.format( + self._hash.hex() + ) + + m += os.linesep * 2 + m += 'Jobs to check its integrity and metadata have been scheduled. If it is damaged, it may be redownloaded or removed from the client completely. If it is not damaged, it may be fixed automatically or further action may be required.' + + HydrusData.ShowText( m ) + + HG.client_controller.Write( 'file_maintenance_add_jobs_hashes', { self._hash }, ClientFiles.REGENERATE_FILE_DATA_JOB_FILE_INTEGRITY_DATA_TRY_URL_ELSE_REMOVE_RECORD ) + HG.client_controller.Write( 'file_maintenance_add_jobs_hashes', { self._hash }, ClientFiles.REGENERATE_FILE_DATA_JOB_FILE_METADATA ) + ''' + + if not self._render_failed: my_resolution_size = QC.QSize( self._resolution[0], self._resolution[1] ) my_numpy_size = QC.QSize( self._numpy_image.shape[1], self._numpy_image.shape[0] ) @@ -280,6 +292,48 @@ class ImageRenderer( ClientCachesBase.CacheableObject ): + def _InitialiseErrorImage( self, e: Exception ): + + ( width, height ) = self._resolution + + qt_image = QG.QImage( width, height, QG.QImage.Format_RGB888 ) + + painter = QG.QPainter( qt_image ) + + painter.setBackground( QG.QBrush( QC.Qt.white ) ) + + painter.eraseRect( painter.viewport() ) + + pen = QG.QPen( QG.QColor( 20, 20, 20 ) ) + + pen.setWidth( 5 ) + + painter.setPen( pen ) + painter.setBrush( QC.Qt.NoBrush ) + + painter.drawRect( 0, 0, width - 1, height - 1 ) + + from hydrus.client.gui import ClientGUIFunctions + + font = painter.font() + + font.setPixelSize( height // 20 ) + + painter.setFont( font ) + + text = 'Image failed to render:' + text += '\n' + text += str( e ) + text += '\n' + text += 'Full info written to the log.' + + painter.drawText( QC.QRectF( 0, 0, width, height ), QC.Qt.AlignCenter, text ) + + del painter + + return ClientGUIFunctions.ConvertQtImageToNumPy( qt_image ) + + def GetEstimatedMemoryFootprint( self ): if self._numpy_image is None: @@ -415,9 +469,15 @@ class ImageRenderer( ClientCachesBase.CacheableObject ): def IsReady( self ): - return self._numpy_image is not None + return self._is_ready + def RenderFailed( self ): + + return self._render_failed + + + class ImageTile( ClientCachesBase.CacheableObject ): def __init__( self, hash: bytes, clip_rect: QC.QRect, qt_pixmap: QG.QPixmap ): diff --git a/hydrus/client/db/ClientDB.py b/hydrus/client/db/ClientDB.py index a1ab7f8e..13644e3b 100644 --- a/hydrus/client/db/ClientDB.py +++ b/hydrus/client/db/ClientDB.py @@ -2601,6 +2601,11 @@ class DB( HydrusDB.HydrusDB ): job_key = None ): + if job_key is None: + + job_key = ClientThreading.JobKey() + + boned_stats = {} ( num_total, size_total ) = self._Execute( f'SELECT COUNT( hash_id ), SUM( size ) FROM {files_table_name} CROSS JOIN files_info USING ( hash_id );' ).fetchone() @@ -9714,6 +9719,64 @@ class DB( HydrusDB.HydrusDB ): + if version == 543: + + try: + + domain_manager = self.modules_serialisable.GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER ) + + domain_manager.Initialise() + + # + + domain_manager.OverwriteDefaultParsers( [ + 'sankaku gallery page parser', + 'inkbunny file page parser' + ] ) + + # + + domain_manager.TryToLinkURLClassesAndParsers() + + # + + self.modules_serialisable.SetJSONDump( domain_manager ) + + except Exception as e: + + HydrusData.PrintException( e ) + + message = 'Trying to update some downloader objects failed! Please let hydrus dev know!' + + self.pub_initial_message( message ) + + + try: + + self._controller.frame_splash_status.SetSubtext( f'scheduling some maintenance work' ) + + all_local_hash_ids = self.modules_files_storage.GetCurrentHashIdsList( self.modules_services.combined_local_file_service_id ) + + with self._MakeTemporaryIntegerTable( all_local_hash_ids, 'hash_id' ) as temp_hash_ids_table_name: + + hash_ids = self._STS( self._Execute( f'SELECT hash_id FROM {temp_hash_ids_table_name} CROSS JOIN files_info USING ( hash_id ) WHERE mime = ?;', ( HC.IMAGE_BMP, ) ) ) + self.modules_files_maintenance_queue.AddJobs( hash_ids, ClientFiles.REGENERATE_FILE_DATA_JOB_FILE_HAS_ICC_PROFILE ) + + hash_ids = self._STS( self._Execute( f'SELECT hash_id FROM {temp_hash_ids_table_name} CROSS JOIN files_info USING ( hash_id ) WHERE mime = ?;', ( HC.APPLICATION_PROCREATE, ) ) ) + self.modules_files_maintenance_queue.AddJobs( hash_ids, ClientFiles.REGENERATE_FILE_DATA_JOB_FILE_METADATA ) + self.modules_files_maintenance_queue.AddJobs( hash_ids, ClientFiles.REGENERATE_FILE_DATA_JOB_FORCE_THUMBNAIL ) + + + except Exception as e: + + HydrusData.PrintException( e ) + + message = 'Some file updates failed to schedule! This is not super important, but hydev would be interested in seeing the error that was printed to the log.' + + self.pub_initial_message( message ) + + + self._controller.frame_splash_status.SetTitleText( 'updated db to v{}'.format( HydrusData.ToHumanInt( version + 1 ) ) ) self._Execute( 'UPDATE version SET version = ?;', ( version + 1, ) ) diff --git a/hydrus/client/gui/ClientGUIScrolledPanels.py b/hydrus/client/gui/ClientGUIScrolledPanels.py index da8c9a3e..41d3d005 100644 --- a/hydrus/client/gui/ClientGUIScrolledPanels.py +++ b/hydrus/client/gui/ClientGUIScrolledPanels.py @@ -203,7 +203,7 @@ class EditPanel( ResizingScrolledPanel ): class EditSingleCtrlPanel( CAC.ApplicationCommandProcessorMixin, EditPanel ): - def __init__( self, parent, ok_on_these_commands = None ): + def __init__( self, parent, ok_on_these_commands = None, message = None ): EditPanel.__init__( self, parent ) CAC.ApplicationCommandProcessorMixin.__init__( self ) @@ -221,6 +221,17 @@ class EditSingleCtrlPanel( CAC.ApplicationCommandProcessorMixin, EditPanel ): self._vbox = QP.VBoxLayout( margin = 0 ) + if message is not None: + + from hydrus.client.gui.widgets import ClientGUICommon + + st = ClientGUICommon.BetterStaticText( self, label = message ) + + st.setWordWrap( True ) + + QP.AddToLayout( self._vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR ) + + self.widget().setLayout( self._vbox ) self._my_shortcuts_handler = ClientGUIShortcuts.ShortcutsHandler( self, [ 'media' ] ) diff --git a/hydrus/client/gui/ClientGUIScrolledPanelsReview.py b/hydrus/client/gui/ClientGUIScrolledPanelsReview.py index 7bd47439..7a2139c9 100644 --- a/hydrus/client/gui/ClientGUIScrolledPanelsReview.py +++ b/hydrus/client/gui/ClientGUIScrolledPanelsReview.py @@ -51,6 +51,7 @@ from hydrus.client.gui.lists import ClientGUIListCtrl from hydrus.client.gui.search import ClientGUIACDropdown from hydrus.client.gui.search import ClientGUILocation from hydrus.client.gui.widgets import ClientGUICommon +from hydrus.client.gui.widgets import ClientGUIControls from hydrus.client.gui.widgets import ClientGUIMenuButton from hydrus.client.importing.options import FileImportOptions from hydrus.client.metadata import ClientTags @@ -159,7 +160,7 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ): current_media_base_locations_listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( file_locations_panel ) - self._current_media_base_locations_listctrl = ClientGUIListCtrl.BetterListCtrl( current_media_base_locations_listctrl_panel, CGLC.COLUMN_LIST_DB_MIGRATION_LOCATIONS.ID, 8, self._ConvertLocationToListCtrlTuples ) + self._current_media_base_locations_listctrl = ClientGUIListCtrl.BetterListCtrl( current_media_base_locations_listctrl_panel, CGLC.COLUMN_LIST_DB_MIGRATION_LOCATIONS.ID, 8, self._ConvertLocationToListCtrlTuples, activation_callback = self._SetMaxNumBytes ) self._current_media_base_locations_listctrl.setSelectionMode( QW.QAbstractItemView.SingleSelection ) self._current_media_base_locations_listctrl.Sort() @@ -169,6 +170,7 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ): current_media_base_locations_listctrl_panel.AddButton( 'add new location for files', self._SelectPathToAdd ) current_media_base_locations_listctrl_panel.AddButton( 'increase location weight', self._IncreaseWeight, enabled_check_func = self._CanIncreaseWeight ) current_media_base_locations_listctrl_panel.AddButton( 'decrease location weight', self._DecreaseWeight, enabled_check_func = self._CanDecreaseWeight ) + current_media_base_locations_listctrl_panel.AddButton( 'set max size', self._SetMaxNumBytes, enabled_check_func = self._CanSetMaxNumBytes ) current_media_base_locations_listctrl_panel.AddButton( 'remove location', self._RemoveSelectedBaseLocation, enabled_check_func = self._CanRemoveLocation ) self._thumbnails_location = QW.QLineEdit( file_locations_panel ) @@ -358,6 +360,30 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ): + return False + + + def _CanSetMaxNumBytes( self ): + + only_one_location_is_limitless = len( [ 1 for base_location in self._media_base_locations if base_location.max_num_bytes is None ] ) == 1 + + base_locations = self._current_media_base_locations_listctrl.GetData( only_selected = True ) + + if len( base_locations ) > 0: + + base_location = base_locations[0] + + if base_location in self._media_base_locations: + + if base_location.max_num_bytes is None and only_one_location_is_limitless: + + return False + + + return True + + + return False @@ -377,7 +403,7 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ): self._SaveToDB() - def _ConvertLocationToListCtrlTuples( self, base_location ): + def _ConvertLocationToListCtrlTuples( self, base_location: ClientFilesPhysical.FilesStorageBaseLocation ): f_space = self._all_local_files_total_size ( t_space_min, t_space_max ) = self._GetThumbnailSizeEstimates() @@ -468,8 +494,6 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ): # - total_ideal_weight = sum( ( base_location.ideal_weight for base_location in self._media_base_locations ) ) - if base_location in self._media_base_locations: ideal_weight = base_location.ideal_weight @@ -490,15 +514,19 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ): - if base_location in self._media_base_locations: + if base_location.max_num_bytes is None: - ideal_fp = base_location.ideal_weight / total_ideal_weight + pretty_max_num_bytes = 'n/a' + sort_max_num_bytes = -1 else: - ideal_fp = 0.0 + pretty_max_num_bytes = HydrusData.ToHumanBytes( base_location.max_num_bytes ) + sort_max_num_bytes = base_location.max_num_bytes + ideal_fp = self._media_base_locations_to_ideal_usage.get( base_location, 0.0 ) + if self._ideal_thumbnails_base_location_override is None: ideal_tp = ideal_fp @@ -545,8 +573,8 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ): pretty_ideal_usage = 'nothing' - display_tuple = ( pretty_location, pretty_portable, pretty_free_space, pretty_current_usage, pretty_ideal_weight, pretty_ideal_usage ) - sort_tuple = ( pretty_location, portable, free_space, ideal_weight, ideal_usage, current_usage ) + display_tuple = ( pretty_location, pretty_portable, pretty_free_space, pretty_current_usage, pretty_ideal_weight, pretty_max_num_bytes, pretty_ideal_usage ) + sort_tuple = ( pretty_location, portable, free_space, ideal_weight, ideal_usage, sort_max_num_bytes, current_usage ) return ( display_tuple, sort_tuple ) @@ -763,6 +791,54 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ): + def _SetMaxNumBytes( self ): + + if not self._CanSetMaxNumBytes(): + + return + + + base_locations = self._current_media_base_locations_listctrl.GetData( only_selected = True ) + + if len( base_locations ) > 0: + + base_location = base_locations[0] + + if base_location in self._media_base_locations: + + max_num_bytes = base_location.max_num_bytes + + with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit max size' ) as dlg: + + message = 'If a location goes over its set size, it will schedule to migrate some files to other locations. At least one media location must have no limit.' + message += '\n' * 2 + message += 'This is not precise (it works on average size, and thumbnails can add a bit), so give it some padding. Also, in general, remember it is not healthy to fill any hard drive more than 90% full.' + message += '\n' * 2 + message += 'Also, this feature is under active development. The client will go over this limit if your collection grows significantly--the only way things rebalance atm are if you click "move files now"--but in future I will have things automatically migrate in the background to ensure limits are obeyed. This is just for advanced users to play with for now!' + + panel = ClientGUIScrolledPanels.EditSingleCtrlPanel( dlg, message = message ) + + control = ClientGUIControls.NoneableBytesControl( panel, initial_value = 100 * ( 1024 ** 3 ) ) + + control.SetValue( max_num_bytes ) + + panel.SetControl( control ) + + dlg.SetPanel( panel ) + + if dlg.exec() == QW.QDialog.Accepted: + + max_num_bytes = control.GetValue() + + base_location.max_num_bytes = max_num_bytes + + self._SaveToDB() + + + + + + def _SetThumbnailLocation( self ): with QP.DirDialog( self, 'Select thumbnail location' ) as dlg: @@ -798,10 +874,12 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ): ( self._media_base_locations, self._ideal_thumbnails_base_location_override ) = self._controller.Read( 'ideal_client_files_locations' ) + self._media_base_locations_to_ideal_usage = ClientFilesPhysical.FilesStorageBaseLocation.STATICGetIdealWeights( self._all_local_files_total_size, self._media_base_locations ) + approx_total_db_size = self._controller.db.GetApproxTotalFileSize() self._current_db_path_st.setText( 'database (about '+HydrusData.ToHumanBytes(approx_total_db_size)+'): '+self._controller.GetDBDir() ) - self._current_install_path_st.setText( 'install: '+HC.BASE_DIR ) + self._current_install_path_st.setText( 'install: ' + HC.BASE_DIR ) approx_total_client_files = self._all_local_files_total_size ( approx_total_thumbnails_min, approx_total_thumbnails_max ) = self._GetThumbnailSizeEstimates() @@ -2887,8 +2965,8 @@ class ReviewHowBonedAmI( ClientGUIScrolledPanels.ReviewPanel ): big_hbox = QP.HBoxLayout() - QP.AddToLayout( big_hbox, vbox, CC.FLAGS_EXPAND_BOTH_WAYS ) QP.AddToLayout( big_hbox, self._search_panel, CC.FLAGS_EXPAND_BOTH_WAYS ) + QP.AddToLayout( big_hbox, vbox, CC.FLAGS_EXPAND_BOTH_WAYS ) self.widget().setLayout( big_hbox ) @@ -3085,7 +3163,16 @@ class ReviewHowBonedAmI( ClientGUIScrolledPanels.ReviewPanel ): # - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = '\u251cCurrent:' ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = '' ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + + # + + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Current:' ), CC.FLAGS_ON_LEFT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_total ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( current_num_percent ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_total ) ), CC.FLAGS_ON_RIGHT ) @@ -3094,7 +3181,25 @@ class ReviewHowBonedAmI( ClientGUIScrolledPanels.ReviewPanel ): # - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = '\u2502\u251cInbox:' ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Deleted:' ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_deleted ) ), CC.FLAGS_ON_RIGHT ) + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( deleted_num_percent ) ), CC.FLAGS_ON_RIGHT ) + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_deleted ) ), CC.FLAGS_ON_RIGHT ) + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( deleted_size_percent ) ), CC.FLAGS_ON_RIGHT ) + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( deleted_average_filesize ) ), CC.FLAGS_ON_RIGHT ) + + # + + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = '' ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + + # + + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Inbox:' ), CC.FLAGS_ON_LEFT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_inbox ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( inbox_num_percent ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_inbox ) ), CC.FLAGS_ON_RIGHT ) @@ -3103,22 +3208,13 @@ class ReviewHowBonedAmI( ClientGUIScrolledPanels.ReviewPanel ): # - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = '\u2502\u2514Archive:' ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Archive:' ), CC.FLAGS_ON_LEFT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_archive ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( archive_num_percent ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_archive ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( archive_size_percent ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( archive_average_filesize ) ), CC.FLAGS_ON_RIGHT ) - # - - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = '\u2514Deleted:' ), CC.FLAGS_ON_LEFT ) - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_deleted ) ), CC.FLAGS_ON_RIGHT ) - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( deleted_num_percent ) ), CC.FLAGS_ON_RIGHT ) - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_deleted ) ), CC.FLAGS_ON_RIGHT ) - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( deleted_size_percent ) ), CC.FLAGS_ON_RIGHT ) - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( deleted_average_filesize ) ), CC.FLAGS_ON_RIGHT ) - else: panel_vbox = QP.VBoxLayout() @@ -3150,7 +3246,16 @@ class ReviewHowBonedAmI( ClientGUIScrolledPanels.ReviewPanel ): # - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = '\u251cInbox:' ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = '' ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, QW.QWidget( self._files_content_panel ), CC.FLAGS_ON_LEFT ) + + # + + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Inbox:' ), CC.FLAGS_ON_LEFT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_inbox ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( inbox_num_percent ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_inbox ) ), CC.FLAGS_ON_RIGHT ) @@ -3159,7 +3264,7 @@ class ReviewHowBonedAmI( ClientGUIScrolledPanels.ReviewPanel ): # - QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = '\u2514Archive:' ), CC.FLAGS_ON_LEFT ) + QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = 'Archive:' ), CC.FLAGS_ON_LEFT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanInt( num_archive ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = ClientData.ConvertZoomToPercentage( archive_num_percent ) ), CC.FLAGS_ON_RIGHT ) QP.AddToLayout( text_table_layout, ClientGUICommon.BetterStaticText( self._files_content_panel, label = HydrusData.ToHumanBytes( size_archive ) ), CC.FLAGS_ON_RIGHT ) diff --git a/hydrus/client/gui/lists/ClientGUIListConstants.py b/hydrus/client/gui/lists/ClientGUIListConstants.py index 175b537f..f1aa58b9 100644 --- a/hydrus/client/gui/lists/ClientGUIListConstants.py +++ b/hydrus/client/gui/lists/ClientGUIListConstants.py @@ -1049,6 +1049,7 @@ class COLUMN_LIST_DB_MIGRATION_LOCATIONS( COLUMN_LIST_DEFINITION ): CURRENT_USAGE = 3 WEIGHT = 4 IDEAL_USAGE = 5 + MAX_NUM_BYTES = 6 column_list_type_name_lookup[ COLUMN_LIST_DB_MIGRATION_LOCATIONS.ID ] = 'db file storage locations' @@ -1058,6 +1059,7 @@ register_column_type( COLUMN_LIST_DB_MIGRATION_LOCATIONS.ID, COLUMN_LIST_DB_MIGR register_column_type( COLUMN_LIST_DB_MIGRATION_LOCATIONS.ID, COLUMN_LIST_DB_MIGRATION_LOCATIONS.FREE_SPACE, 'disk free space', False, 12, True ) register_column_type( COLUMN_LIST_DB_MIGRATION_LOCATIONS.ID, COLUMN_LIST_DB_MIGRATION_LOCATIONS.CURRENT_USAGE, 'current usage', False, 24, True ) register_column_type( COLUMN_LIST_DB_MIGRATION_LOCATIONS.ID, COLUMN_LIST_DB_MIGRATION_LOCATIONS.WEIGHT, 'weight', False, 8, True ) +register_column_type( COLUMN_LIST_DB_MIGRATION_LOCATIONS.ID, COLUMN_LIST_DB_MIGRATION_LOCATIONS.MAX_NUM_BYTES, 'max size', False, 10, True ) register_column_type( COLUMN_LIST_DB_MIGRATION_LOCATIONS.ID, COLUMN_LIST_DB_MIGRATION_LOCATIONS.IDEAL_USAGE, 'ideal usage', False, 24, True ) default_column_list_sort_lookup[ COLUMN_LIST_DB_MIGRATION_LOCATIONS.ID ] = ( COLUMN_LIST_DB_MIGRATION_LOCATIONS.LOCATION, True ) diff --git a/hydrus/client/gui/widgets/ClientGUIControls.py b/hydrus/client/gui/widgets/ClientGUIControls.py index b3812c9f..68808af1 100644 --- a/hydrus/client/gui/widgets/ClientGUIControls.py +++ b/hydrus/client/gui/widgets/ClientGUIControls.py @@ -280,8 +280,9 @@ class BytesControl( QW.QWidget ): self._unit.addItem( 'B', 1 ) self._unit.addItem( 'KB', 1024 ) - self._unit.addItem( 'MB', 1024 * 1024 ) - self._unit.addItem( 'GB', 1024 * 1024 * 1024 ) + self._unit.addItem( 'MB', 1024 ** 2 ) + self._unit.addItem( 'GB', 1024 ** 3 ) + self._unit.addItem( 'TB', 1024 ** 4 ) # @@ -311,7 +312,7 @@ class BytesControl( QW.QWidget ): def GetSeparatedValue( self ): - return (self._spin.value(), self._unit.GetValue()) + return ( self._spin.value(), self._unit.GetValue() ) def GetValue( self ): @@ -321,12 +322,12 @@ class BytesControl( QW.QWidget ): def SetSeparatedValue( self, value, unit ): - return (self._spin.setValue( value ), self._unit.SetValue( unit )) + return ( self._spin.setValue( value ), self._unit.SetValue( unit ) ) def SetValue( self, value: int ): - max_unit = 1024 * 1024 * 1024 + max_unit = 1024 ** 4 unit = 1 @@ -341,6 +342,7 @@ class BytesControl( QW.QWidget ): self._unit.SetValue( unit ) + class NoneableBytesControl( QW.QWidget ): valueChanged = QC.Signal() diff --git a/hydrus/client/networking/ClientLocalServerResources.py b/hydrus/client/networking/ClientLocalServerResources.py index 2b64f320..9c635166 100644 --- a/hydrus/client/networking/ClientLocalServerResources.py +++ b/hydrus/client/networking/ClientLocalServerResources.py @@ -2232,6 +2232,8 @@ class HydrusResourceClientAPIRestrictedAddTagsGetTagSiblingsParents( HydrusResou CheckTags( tags ) + tags = HydrusTags.CleanTags( tags ) + tags_to_service_keys_to_siblings_and_parents = HG.client_controller.Read( 'tag_siblings_and_parents_lookup', tags ) tags_dict = {} @@ -2819,7 +2821,7 @@ class HydrusResourceClientAPIRestrictedGetFilesGetRenderedFile( HydrusResourceCl def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ): try: - + media_result: ClientMedia.MediaSingleton if 'file_id' in request.parsed_request_args: @@ -2847,34 +2849,34 @@ class HydrusResourceClientAPIRestrictedGetFilesGetRenderedFile( HydrusResourceCl raise HydrusExceptions.NotFoundException( 'One or more of those file identifiers was missing!' ) - if not media_result.IsStaticImage(): - - raise HydrusExceptions.BadRequestException('Requested file is not an image!') - - hash = media_result.GetHash() - + if not media_result.IsStaticImage(): + + raise HydrusExceptions.BadRequestException('Requested file is not an image!') + + renderer: ClientRendering.ImageRenderer = HG.client_controller.GetCache( 'images' ).GetImageRenderer( media_result ) while not renderer.IsReady(): - + if request.disconnected: - + return + time.sleep( 0.1 ) - + numpy_image = renderer.GetNumPyImage() - - body = HydrusImageHandling.GeneratePNGBytesNumPy(numpy_image) + + body = HydrusImageHandling.GeneratePNGBytesNumPy( numpy_image ) is_attachment = request.parsed_request_args.GetValue( 'download', bool, default_value = False ) response_context = HydrusServerResources.ResponseContext( 200, mime = HC.IMAGE_PNG, body = body, is_attachment = is_attachment, max_age = 86400 * 365 ) return response_context - + class HydrusResourceClientAPIRestrictedGetFilesFileHashes( HydrusResourceClientAPIRestrictedGetFiles ): @@ -3787,7 +3789,25 @@ class HydrusResourceClientAPIRestrictedManageDatabaseMrBones( HydrusResourceClie def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ): - boned_stats = HG.client_controller.Read( 'boned_stats' ) + location_context = ParseLocationContext( request, ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY ) ) + + tag_service_key = ParseTagServiceKey( request ) + + if tag_service_key == CC.COMBINED_TAG_SERVICE_KEY and location_context.IsAllKnownFiles(): + + raise HydrusExceptions.BadRequestException( 'Sorry, search for all known tags over all known files is not supported!' ) + + + tag_context = ClientSearch.TagContext( service_key = tag_service_key ) + predicates = ParseClientAPISearchPredicates( request ) + + file_search_context = ClientSearch.FileSearchContext( location_context = location_context, tag_context = tag_context, predicates = predicates ) + + job_key = ClientThreading.JobKey( cancellable = True ) + + request.disconnect_callables.append( job_key.Cancel ) + + boned_stats = HG.client_controller.Read( 'boned_stats', file_search_context = file_search_context, job_key = job_key ) body_dict = { 'boned_stats' : boned_stats } diff --git a/hydrus/core/HydrusConstants.py b/hydrus/core/HydrusConstants.py index f7778c30..809338ef 100644 --- a/hydrus/core/HydrusConstants.py +++ b/hydrus/core/HydrusConstants.py @@ -100,8 +100,8 @@ options = {} # Misc NETWORK_VERSION = 20 -SOFTWARE_VERSION = 543 -CLIENT_API_VERSION = 51 +SOFTWARE_VERSION = 544 +CLIENT_API_VERSION = 52 SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 ) @@ -919,7 +919,7 @@ APPLICATIONS_WITH_THUMBNAILS = set( { IMAGE_SVG, APPLICATION_PDF, APPLICATION_FL MIMES_WITH_THUMBNAILS = set( IMAGES ).union( ANIMATIONS ).union( VIDEO ).union( APPLICATIONS_WITH_THUMBNAILS ) -FILES_THAT_CAN_HAVE_ICC_PROFILE = { IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_TIFF, APPLICATION_PSD }.union( PIL_HEIF_MIMES ) +FILES_THAT_CAN_HAVE_ICC_PROFILE = { IMAGE_BMP, IMAGE_JPEG, IMAGE_PNG, IMAGE_GIF, IMAGE_TIFF, APPLICATION_PSD }.union( PIL_HEIF_MIMES ) FILES_THAT_CAN_HAVE_EXIF = { IMAGE_JPEG, IMAGE_TIFF, IMAGE_PNG, IMAGE_WEBP }.union( PIL_HEIF_MIMES ) # images and animations that PIL can handle diff --git a/hydrus/core/HydrusFileHandling.py b/hydrus/core/HydrusFileHandling.py index daa9be70..89ee6345 100644 --- a/hydrus/core/HydrusFileHandling.py +++ b/hydrus/core/HydrusFileHandling.py @@ -690,25 +690,31 @@ def GetMime( path, ok_to_look_for_hydrus_updates = False ): return HC.IMAGE_SVG - # it is important this goes at the end, because ffmpeg has a billion false positives! + # it is important this goes at the end, because ffmpeg has a billion false positives! and it takes CPU to true negative # for instance, it once thought some hydrus update files were mpegs - try: + # it also thinks txt files can be mpegs + likely_to_false_positive = True in ( path.endswith( ext ) for ext in ( '.txt', '.log', '.json' ) ) + + if not likely_to_false_positive: - mime = HydrusVideoHandling.GetMime( path ) - - if mime != HC.APPLICATION_UNKNOWN: + try: - return mime + mime = HydrusVideoHandling.GetMime( path ) + + if mime != HC.APPLICATION_UNKNOWN: + + return mime + + + except HydrusExceptions.UnsupportedFileException: + + pass + + except Exception as e: + + HydrusData.Print( 'FFMPEG had trouble with: ' + path ) + HydrusData.PrintException( e, do_wait = False ) - - except HydrusExceptions.UnsupportedFileException: - - pass - - except Exception as e: - - HydrusData.Print( 'FFMPEG had trouble with: ' + path ) - HydrusData.PrintException( e, do_wait = False ) return HC.APPLICATION_UNKNOWN diff --git a/hydrus/core/HydrusPSDHandling.py b/hydrus/core/HydrusPSDHandling.py index 18cd6e71..2a099b4f 100644 --- a/hydrus/core/HydrusPSDHandling.py +++ b/hydrus/core/HydrusPSDHandling.py @@ -3,7 +3,8 @@ import typing from PIL import Image as PILImage -from hydrus.core import HydrusExceptions, HydrusImageHandling +from hydrus.core import HydrusExceptions +from hydrus.core import HydrusImageHandling try: @@ -55,8 +56,9 @@ def GenerateThumbnailBytesFromPSDPath( path: str, target_resolution: typing.Tupl def GetPSDResolution( path: str ): if not PSD_TOOLS_OK: - + raise HydrusExceptions.UnsupportedFileException( 'psd_tools unavailable' ) + return HydrusPSDTools.GetPSDResolution( path ) @@ -75,4 +77,4 @@ def GetPSDResolutionFallback( path: str ): width: int = struct.unpack( '>L', width_bytes )[0] return ( width, height ) - \ No newline at end of file + diff --git a/hydrus/core/HydrusPSDTools.py b/hydrus/core/HydrusPSDTools.py index 2c51ad19..8dfb52e8 100644 --- a/hydrus/core/HydrusPSDTools.py +++ b/hydrus/core/HydrusPSDTools.py @@ -2,14 +2,11 @@ from PIL import Image as PILImage from hydrus.core import HydrusExceptions - from psd_tools import PSDImage from psd_tools.constants import Resource, ColorMode, Resource from psd_tools.api.numpy_io import has_transparency, get_transparency_index from psd_tools.api.pil_io import get_pil_mode, get_pil_channels, _create_image - - def PSDHasICCProfile( path: str ): psd = PSDImage.open( path ) @@ -24,11 +21,11 @@ def MergedPILImageFromPSD( path: str ) -> PILImage: #pil_image = psd.topil( apply_icc = False ) if psd.has_preview(): - + pil_image = convert_image_data_to_pil(psd) - + else: - + raise HydrusExceptions.UnsupportedFileException('PSD file has no embedded preview!') @@ -46,51 +43,69 @@ def MergedPILImageFromPSD( path: str ) -> PILImage: def GetPSDResolution( path: str ): psd = PSDImage.open( path ) - + return ( psd.width, psd.height ) - + # modified from psd-tools source: # https://github.com/psd-tools/psd-tools/blob/main/src/psd_tools/api/pil_io.py -def convert_image_data_to_pil(psd: PSDImage): +def convert_image_data_to_pil( psd: PSDImage ): + alpha = None - + channel_data = psd._record.image_data.get_data(psd._record.header) size = (psd.width, psd.height) channels = [_create_image(size, c, psd.depth) for c in channel_data] - + # has_transparency not quite correct # see https://github.com/psd-tools/psd-tools/issues/369 # and https://github.com/psd-tools/psd-tools/pull/370 no_alpha = psd._record.layer_and_mask_information.layer_info is not None and psd._record.layer_and_mask_information.layer_info.layer_count > 0 - + if has_transparency(psd) and not no_alpha: + alpha = channels[get_transparency_index(psd)] + if psd.color_mode == ColorMode.INDEXED: + image = channels[0] image.putpalette(psd._record.color_mode_data.interleave()) + elif psd.color_mode == ColorMode.MULTICHANNEL: + image = channels[0] # Multi-channel mode is a collection of alpha. + else: + mode = get_pil_mode(psd.color_mode) image = PILImage.merge(mode, channels[:get_pil_channels(mode)]) + if not image: + return None + return post_process(image, alpha) - + def post_process(image, alpha): + # Fix inverted CMYK. if image.mode == 'CMYK': + from PIL import ImageChops image = ImageChops.invert(image) + # In Pillow, alpha channel is only available in RGB or L. if alpha and image.mode in ('RGB', 'L'): + image.putalpha(alpha) + + return image + diff --git a/hydrus/core/HydrusProcreateHandling.py b/hydrus/core/HydrusProcreateHandling.py index a92bcc8b..643b6882 100644 --- a/hydrus/core/HydrusProcreateHandling.py +++ b/hydrus/core/HydrusProcreateHandling.py @@ -79,15 +79,13 @@ def GetProcreateResolution( path ): # canvas is rotated 90 or -90 degrees - height = size[1] - - width = size[0] + width = size[1] + height = size[0] else: - height = size[0] - - width = size[1] + width = size[0] + height = size[1] except: diff --git a/hydrus/core/networking/HydrusServerResources.py b/hydrus/core/networking/HydrusServerResources.py index 6b607357..3041bfb7 100644 --- a/hydrus/core/networking/HydrusServerResources.py +++ b/hydrus/core/networking/HydrusServerResources.py @@ -1,3 +1,4 @@ +import json import os import time @@ -822,64 +823,79 @@ class HydrusResource( Resource ): try: self._CleanUpTempFile( request ) except: pass - default_mime = HC.TEXT_HTML - default_encoding = str + error_summary = str( e ) if isinstance( e, HydrusExceptions.BadRequestException ): - response_context = ResponseContext( 400, mime = default_mime, body = default_encoding( e ) ) + status_code = 400 elif isinstance( e, ( HydrusExceptions.MissingCredentialsException, HydrusExceptions.DoesNotSupportCORSException ) ): - response_context = ResponseContext( 401, mime = default_mime, body = default_encoding( e ) ) + status_code = 401 elif isinstance( e, HydrusExceptions.InsufficientCredentialsException ): - response_context = ResponseContext( 403, mime = default_mime, body = default_encoding( e ) ) + status_code = 403 elif isinstance( e, ( HydrusExceptions.NotFoundException, HydrusExceptions.DataMissing, HydrusExceptions.FileMissingException ) ): - response_context = ResponseContext( 404, mime = default_mime, body = default_encoding( e ) ) + status_code = 404 elif isinstance( e, HydrusExceptions.NotAcceptable ): - response_context = ResponseContext( 406, mime = default_mime, body = default_encoding( e ) ) + status_code = 406 elif isinstance( e, HydrusExceptions.ConflictException ): - response_context = ResponseContext( 409, mime = default_mime, body = default_encoding( e ) ) + status_code = 409 elif isinstance( e, HydrusExceptions.RangeNotSatisfiableException ): - response_context = ResponseContext( 416, mime = default_mime, body = default_encoding( e ) ) + status_code = 416 elif isinstance( e, HydrusExceptions.SessionException ): - response_context = ResponseContext( 419, mime = default_mime, body = default_encoding( e ) ) + status_code = 419 elif isinstance( e, HydrusExceptions.NetworkVersionException ): - response_context = ResponseContext( 426, mime = default_mime, body = default_encoding( e ) ) + status_code = 426 elif isinstance( e, ( HydrusExceptions.ServerBusyException, HydrusExceptions.ShutdownException ) ): - response_context = ResponseContext( 503, mime = default_mime, body = default_encoding( e ) ) + status_code = 503 elif isinstance( e, HydrusExceptions.BandwidthException ): - response_context = ResponseContext( 509, mime = default_mime, body = default_encoding( e ) ) + status_code = 509 elif isinstance( e, HydrusExceptions.ServerException ): - response_context = ResponseContext( 500, mime = default_mime, body = default_encoding( e ) ) + status_code = 500 else: + status_code = 500 + HydrusData.DebugPrint( failure.getTraceback() ) - response_context = ResponseContext( 500, mime = default_mime, body = default_encoding( 'The repository encountered an error it could not handle! Here is a dump of what happened, which will also be written to your client.log file. If it persists, please forward it to hydrus.admin@gmail.com:' + os.linesep * 2 + failure.getTraceback() ) ) + error_summary = f'The "{self._service.GetName()}" encountered an error it could not handle!\n\nHere is a dump of what happened, which will also be written to your log. If it persists, please forward it to hydrus.admin@gmail.com:\n\n' + failure.getTraceback() + # TODO: maybe pull the cbor stuff down to hydrus core here and respond with Dumps( blah, requested_mime ) instead + + default_mime = HC.APPLICATION_JSON + + body_dict = { + 'error' : error_summary, + 'exception_type' : str( type( e ).__name__ ), + 'status_code' : status_code + } + + body = json.dumps( body_dict ) + + response_context = ResponseContext( status_code, mime = default_mime, body = body ) + request.hydrus_response_context = response_context self._callbackRenderResponseContext( request ) diff --git a/hydrus/test/TestClientAPI.py b/hydrus/test/TestClientAPI.py index 9b85b78c..57a68e3e 100644 --- a/hydrus/test/TestClientAPI.py +++ b/hydrus/test/TestClientAPI.py @@ -3478,6 +3478,40 @@ class TestClientAPI( unittest.TestCase ): self.assertEqual( boned_stats, dict( expected_data ) ) + [ ( args, kwargs ) ] = HG.test_controller.GetRead( 'boned_stats' ) + + file_search_context = kwargs[ 'file_search_context' ] + + self.assertEqual( len( file_search_context.GetPredicates() ), 0 ) + + # + + HG.test_controller.SetRead( 'boned_stats', expected_data ) + + path = '/manage_database/mr_bones?tags={}'.format( urllib.parse.quote( json.dumps( [ 'skirt', 'blue_eyes' ] ) ) ) + + connection.request( 'GET', path, headers = headers ) + + response = connection.getresponse() + + data = response.read() + + text = str( data, 'utf-8' ) + + self.assertEqual( response.status, 200 ) + + d = json.loads( text ) + + boned_stats = d[ 'boned_stats' ] + + self.assertEqual( boned_stats, dict( expected_data ) ) + + [ ( args, kwargs ) ] = HG.test_controller.GetRead( 'boned_stats' ) + + file_search_context = kwargs[ 'file_search_context' ] + + self.assertEqual( len( file_search_context.GetPredicates() ), 2 ) + def _test_manage_duplicates( self, connection, set_up_permissions ): diff --git a/hydrus/test/TestClientDB.py b/hydrus/test/TestClientDB.py index 55c2ead6..0ae2c4ff 100644 --- a/hydrus/test/TestClientDB.py +++ b/hydrus/test/TestClientDB.py @@ -1582,6 +1582,82 @@ class TestClientDB( unittest.TestCase ): self.assertEqual( mr_num_words, None ) + def test_mr_bones( self ): + + TestClientDB._clear_db() + + test_files = [] + + test_files.append( 'muh_swf.swf' ) + test_files.append( 'muh_mp4.mp4' ) + + file_import_options = FileImportOptions.FileImportOptions() + file_import_options.SetIsDefault( True ) + + for filename in test_files: + + HG.test_controller.SetRead( 'hash_status', ClientImportFiles.FileImportStatus.STATICGetUnknownStatus() ) + + path = os.path.join( HC.STATIC_DIR, 'testing', filename ) + + file_import_job = ClientImportFiles.FileImportJob( path, file_import_options ) + + file_import_job.GeneratePreImportHashAndStatus() + + file_import_job.GenerateInfo() + + file_import_status = self._write( 'import_file', file_import_job ) + + + swf_hash_hex = 'edfef9905fdecde38e0752a5b6ab7b6df887c3968d4246adc9cffc997e168cdf' + + media_result = self._read( 'media_result', bytes.fromhex( swf_hash_hex ) ) + + earliest_import_time = media_result.GetLocationsManager().GetTimestampsManager().GetImportedTimestamp( CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY ) + + result = self._read( 'boned_stats' ) + + expected_result = { + 'earliest_import_time': earliest_import_time, + 'num_archive': 0, + 'num_deleted': 0, + 'num_inbox': 2, + 'size_archive': 0, + 'size_deleted': 0, + 'size_inbox': 1027308, + 'total_alternate_files': 0, + 'total_duplicate_files': 0, + 'total_viewtime': (0, 0, 0, 0) + } + + self.assertEqual( result, expected_result ) + + # + + location_context = ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY ) + + predicates = [ ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MIME, value = ( HC.APPLICATION_FLASH, ) ) ] + + file_search_context = ClientSearch.FileSearchContext( location_context = location_context, predicates = predicates ) + + result = self._read( 'boned_stats', file_search_context = file_search_context ) + + expected_result = { + 'earliest_import_time': earliest_import_time, + 'num_archive': 0, + 'num_deleted': 0, + 'num_inbox': 1, + 'size_archive': 0, + 'size_deleted': 0, + 'size_inbox': 456774, + 'total_alternate_files': 0, + 'total_duplicate_files': 0, + 'total_viewtime': (0, 0, 0, 0) + } + + self.assertEqual( result, expected_result ) + + def test_nums_pending( self ): TestClientDB._clear_db() diff --git a/requirements.txt b/requirements.txt index ef17f3be..4215885b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ lxml>=4.5.0 lz4>=3.0.0 numpy>=1.16.0 psd-tools>=1.9.28 -Pillow>=9.5.0 +Pillow>=10.0.1 pillow-heif>=0.12.0 psutil>=5.0.0 pyOpenSSL>=19.1.0 diff --git a/setup_venv.bat b/setup_venv.bat index b9a3ca47..1d9a2c32 100644 --- a/setup_venv.bat +++ b/setup_venv.bat @@ -2,6 +2,31 @@ pushd "%~dp0" +ECHO r::::::::::::::::::::::::::::::::::r +ECHO : : +ECHO : :PP. : +ECHO : vBBr : +ECHO : 7BB: : +ECHO : rBB: : +ECHO : :DQRE: rBB: :gMBb: : +ECHO : :BBBi rBB: 7BBB. : +ECHO : KBB: rBB: rBBI : +ECHO : qBB: rBB: rQBU : +ECHO : qBB: rBB: iBBS : +ECHO : qBB: iBB: 7BBj : +ECHO : iBBY iBB. 2BB. : +ECHO : SBQq iBQ: EBBY : +ECHO : :MQBZMBBDRBBP. : +ECHO : .YBB7 : +ECHO : :BB. : +ECHO : 7BBi : +ECHO : rBB: : +ECHO : : +ECHO r::::::::::::::::::::::::::::::::::r +ECHO: +ECHO hydrus +ECHO: + where /q python IF ERRORLEVEL 1 ( @@ -29,8 +54,8 @@ IF EXIST "venv\" ( :questions -ECHO: -ECHO Users on older Windows need the advanced install. +ECHO -------- +ECHO Users on older Windows or Python ^>=3.11 need the advanced install. ECHO: ECHO Your Python version is: python --version @@ -44,10 +69,15 @@ goto :parse_fail :question_qt +ECHO -------- +ECHO We are now going to choose which versions of some larger libraries we are going to use. If something doesn't install, or hydrus won't boot, just run this script again and it will delete everything and start over. ECHO: -ECHO Qt is the User Interface library. We are now on Qt6. -ECHO If you are on Windows ^<=8.1, choose 5. -ECHO If you have multi-monitor menu position bugs with the normal Qt6, try the (o)lder build on Python ^<=3.10 or (m)iddle on Python ^>=3.11. + +ECHO Qt - User Interface +ECHO: +ECHO Most people want "6". +ECHO If you are on Windows ^<=8.1, choose "5". +ECHO If you have multi-monitor menu position bugs with the normal Qt6, try "o" on Python ^<=3.10 or "m" on Python ^>=3.11. SET /P qt="Do you want Qt(5), Qt(6), Qt6 (o)lder, Qt6 (m)iddle or (t)est? " IF "%qt%" == "5" goto :question_mpv @@ -59,21 +89,39 @@ goto :parse_fail :question_mpv +ECHO -------- +ECHO mpv - audio and video playback ECHO: -ECHO mpv is the main way to play audio and video. We need to tell hydrus how to talk to your mpv dll. -ECHO Try the n first. If it doesn't work, fall back to o. +ECHO We need to tell hydrus how to talk to your mpv dll. +ECHO Most people want "n". +ECHO If it doesn't work, fall back to "o". SET /P mpv="Do you want (o)ld mpv, (n)ew mpv, or (t)est mpv? " -IF "%mpv%" == "o" goto :question_opencv -IF "%mpv%" == "n" goto :question_opencv -IF "%mpv%" == "t" goto :question_opencv +IF "%mpv%" == "o" goto :question_pillow +IF "%mpv%" == "n" goto :question_pillow +IF "%mpv%" == "t" goto :question_pillow +goto :parse_fail + +:question_pillow + +ECHO -------- +ECHO Pillow - Images +ECHO: +ECHO Most people want "n". +ECHO If you are Python 3.7 or earlier, choose "o". +SET /P pillow="Do you want (o)ld pillow or (n)ew pillow? " + +IF "%pillow%" == "o" goto :question_opencv +IF "%pillow%" == "n" goto :question_opencv goto :parse_fail :question_opencv +ECHO -------- +ECHO OpenCV - Images ECHO: -ECHO OpenCV is the main image processing library. -ECHO Try the n first. If it doesn't work, fall back to o. Very new python versions might need t. +ECHO Most people want "n". +ECHO If it doesn't work, fall back to "o". Python ^>=3.11 might need "t". SET /P opencv="Do you want (o)ld OpenCV, (n)ew OpenCV, or (t)est OpenCV? " IF "%opencv%" == "o" goto :create @@ -83,6 +131,7 @@ goto :parse_fail :create +ECHO -------- echo Creating new venv... python -m venv venv @@ -118,6 +167,7 @@ IF "%install_type%" == "d" ( python -m pip install pyside2 python -m pip install PyQtChart PyQt5 python -m pip install PyQt6-Charts PyQt6 + python -m pip install -r static\requirements\advanced\requirements_pillow_new.txt python -m pip install -r static\requirements\advanced\requirements_mpv_test.txt python -m pip install -r static\requirements\advanced\requirements_opencv_test.txt python -m pip install -r static\requirements\hydev\requirements_windows_build.txt @@ -135,6 +185,9 @@ IF "%install_type%" == "a" ( IF "%qt%" == "m" python -m pip install -r static\requirements\advanced\requirements_qt6_middle.txt IF "%qt%" == "t" python -m pip install -r static\requirements\advanced\requirements_qt6_test.txt + IF "%pillow%" == "o" python -m pip install -r static\requirements\advanced\requirements_pillow_old.txt + IF "%pillow%" == "n" python -m pip install -r static\requirements\advanced\requirements_pillow_new.txt + IF "%mpv%" == "o" python -m pip install -r static\requirements\advanced\requirements_mpv_old.txt IF "%mpv%" == "n" python -m pip install -r static\requirements\advanced\requirements_mpv_new.txt IF "%mpv%" == "t" python -m pip install -r static\requirements\advanced\requirements_mpv_test.txt @@ -147,6 +200,7 @@ IF "%install_type%" == "a" ( CALL venv\Scripts\deactivate.bat +ECHO -------- SET /P done="Done!" popd @@ -155,6 +209,7 @@ EXIT /B 0 :parse_fail +ECHO -------- SET /P done="Sorry, did not understand that input!" popd diff --git a/setup_venv.command b/setup_venv.command index 40616aa7..45b0db7b 100644 --- a/setup_venv.command +++ b/setup_venv.command @@ -2,24 +2,50 @@ pushd "$(dirname "$0")" || exit 1 +echo " r::::::::::::::::::::::::::::::::::r" +echo " : :" +echo " : :PP. :" +echo " : vBBr :" +echo " : 7BB: :" +echo " : rBB: :" +echo " : :DQRE: rBB: :gMBb: :" +echo " : :BBBi rBB: 7BBB. :" +echo " : KBB: rBB: rBBI :" +echo " : qBB: rBB: rQBU :" +echo " : qBB: rBB: iBBS :" +echo " : qBB: iBB: 7BBj :" +echo " : iBBY iBB. 2BB. :" +echo " : SBQq iBQ: EBBY :" +echo " : :MQBZMBBDRBBP. :" +echo " : .YBB7 :" +echo " : :BB. :" +echo " : 7BBi :" +echo " : rBB: :" +echo " : :" +echo " r::::::::::::::::::::::::::::::::::r" +echo +echo " hydrus" +echo + py_command=python3 if ! type -P $py_command >/dev/null 2>&1; then - echo "No python3 found, using python." - py_command=python + echo "No \"python3\" found, using \"python\"." + py_command=python fi if [ -d "venv" ]; then - echo "Virtual environment will be reinstalled. Hit Enter to start." - read -r - echo "Deleting old venv..." - rm -rf venv + echo "Virtual environment will be reinstalled. Hit Enter to start." + read -r + echo "Deleting old venv..." + rm -rf venv else - echo "If you do not know what this is, check the 'running from source' help. Hit Enter to start." - read -r + echo "If you do not know what this is, check the 'running from source' help. Hit Enter to start." + read -r fi -echo "If your macOS is slightly old, do the advanced install. Let hydev know what works for you." +echo "--------" +echo "If your macOS is old, or you are on >=Python 3.11, do the advanced install. Let hydev know what works for you." echo echo "Your Python version is:" $py_command --version @@ -29,68 +55,93 @@ echo "Do you want the (s)imple or (a)dvanced install? " read -r install_type if [ "$install_type" = "s" ]; then - : + : elif [ "$install_type" = "a" ]; then - echo - echo "Qt is the User Interface library. We are now on Qt6." - echo "If you are <= 10.13 (High Sierra), choose 5." - echo "If you are <=10.15 (Catalina) or otherwise have trouble with the normal Qt6, try the (o)lder build on Python ^<=3.10 or (m)iddle on Python ^>=3.11." + echo "--------" + echo "We are now going to choose which versions of some larger libraries we are going to use. If something doesn't install, or hydrus won't boot, just run this script again and it will delete everything and start over." + echo + echo "Qt - User Interface" + echo "Most people want \"6\"." + echo "If you are <= 10.13 (High Sierra), choose \"5\"." + echo "If you are <=10.15 (Catalina) or otherwise have trouble with the normal Qt6, try \"o\" on Python <=3.10 or \"m\" on Python >=3.11." echo "Do you want Qt(5), Qt(6), Qt6 (o)lder, Qt6 (m)iddle or (t)est? " - read -r qt - if [ "$qt" = "5" ]; then - : - elif [ "$qt" = "6" ]; then - : - elif [ "$qt" = "o" ]; then - : - elif [ "$qt" = "m" ]; then - : - elif [ "$qt" = "t" ]; then - : - else - echo "Sorry, did not understand that input!" - popd || exit 1 - exit 1 - fi - - echo - echo "mpv is broken on macOS. As a safe default, choose n." - echo "Do you want (o)ld mpv, (n)ew mpv, or (t)est mpv? " - read -r mpv - if [ "$mpv" = "o" ]; then - : - elif [ "$mpv" = "n" ]; then - : - elif [ "$mpv" = "t" ]; then - : - else - echo "Sorry, did not understand that input!" - popd || exit 1 - exit 1 - fi - - echo - echo "OpenCV is the main image processing library." - echo "Try the n first. If it doesn't work, fall back to o. Very new python versions might need t." - echo "Do you want (o)ld OpenCV, (n)ew OpenCV, or (t)est OpenCV? " - read -r opencv - if [ "$opencv" = "o" ]; then - : - elif [ "$opencv" = "n" ]; then - : - elif [ "$opencv" = "t" ]; then - : - else - echo "Sorry, did not understand that input!" - popd || exit 1 - exit 1 - fi + read -r qt + if [ "$qt" = "5" ]; then + : + elif [ "$qt" = "6" ]; then + : + elif [ "$qt" = "o" ]; then + : + elif [ "$qt" = "m" ]; then + : + elif [ "$qt" = "t" ]; then + : + else + echo "Sorry, did not understand that input!" + popd || exit 1 + exit 1 + fi + + echo "--------" + echo "mpv - audio and video playback" + echo + echo "mpv is broken on macOS. As a safe default, choose \"n\"." + echo "Do you want (o)ld mpv, (n)ew mpv, or (t)est mpv? " + read -r mpv + if [ "$mpv" = "o" ]; then + : + elif [ "$mpv" = "n" ]; then + : + elif [ "$mpv" = "t" ]; then + : + else + echo "Sorry, did not understand that input!" + popd || exit 1 + exit 1 + fi + + echo "--------" + echo "Pillow - Images" + echo + echo "Most people want \"n\"." + echo "If you are Python 3.7 or earlier, choose \"o\"" + echo "Do you want (o)ld pillow or (n)ew pillow? " + read -r pillow + if [ "$pillow" = "o" ]; then + : + elif [ "$pillow" = "n" ]; then + : + else + echo "Sorry, did not understand that input!" + popd || exit 1 + exit 1 + fi + + echo "--------" + echo "OpenCV - Images" + echo + echo "Most people want \"n\"." + echo "If it doesn't work, fall back to \"o\". Python >=3.11 might need \"t\"." + echo "Do you want (o)ld OpenCV, (n)ew OpenCV, or (t)est OpenCV? " + read -r opencv + if [ "$opencv" = "o" ]; then + : + elif [ "$opencv" = "n" ]; then + : + elif [ "$opencv" = "t" ]; then + : + else + echo "Sorry, did not understand that input!" + popd || exit 1 + exit 1 + fi else - echo "Sorry, did not understand that input!" - popd || exit 1 - exit 1 + echo "Sorry, did not understand that input!" + popd || exit 1 + exit 1 fi +echo "--------" echo "Creating new venv..." $py_command -m venv venv @@ -98,8 +149,8 @@ source venv/bin/activate if ! source venv/bin/activate; then echo "The venv failed to activate, stopping now!" - popd || exit 1 - exit 1 + popd || exit 1 + exit 1 fi python -m pip install --upgrade pip @@ -107,41 +158,48 @@ python -m pip install --upgrade pip python -m pip install --upgrade wheel if [ "$install_type" = "s" ]; then - python -m pip install -r requirements.txt + python -m pip install -r requirements.txt elif [ "$install_type" = "a" ]; then - python -m pip install -r static/requirements/advanced/requirements_core.txt - - if [ "$qt" = "5" ]; then - python -m pip install -r static/requirements/advanced/requirements_qt5.txt - elif [ "$qt" = "6" ]; then - python -m pip install -r static/requirements/advanced/requirements_qt6.txt - elif [ "$qt" = "o" ]; then - python -m pip install -r static/requirements/advanced/requirements_qt6_older.txt - elif [ "$qt" = "m" ]; then - python -m pip install -r static/requirements/advanced/requirements_qt6_middle.txt - elif [ "$qt" = "t" ]; then - python -m pip install -r static/requirements/advanced/requirements_qt6_test.txt - fi - - if [ "$mpv" = "o" ]; then - python -m pip install -r static/requirements/advanced/requirements_mpv_old.txt - elif [ "$mpv" = "n" ]; then - python -m pip install -r static/requirements/advanced/requirements_mpv_new.txt - elif [ "$mpv" = "t" ]; then - python -m pip install -r static/requirements/advanced/requirements_mpv_test.txt - fi - - if [ "$opencv" = "o" ]; then - python -m pip install -r static/requirements/advanced/requirements_opencv_old.txt - elif [ "$opencv" = "n" ]; then - python -m pip install -r static/requirements/advanced/requirements_opencv_new.txt - elif [ "$opencv" = "t" ]; then - python -m pip install -r static/requirements/advanced/requirements_opencv_test.txt - fi + python -m pip install -r static/requirements/advanced/requirements_core.txt + + if [ "$qt" = "5" ]; then + python -m pip install -r static/requirements/advanced/requirements_qt5.txt + elif [ "$qt" = "6" ]; then + python -m pip install -r static/requirements/advanced/requirements_qt6.txt + elif [ "$qt" = "o" ]; then + python -m pip install -r static/requirements/advanced/requirements_qt6_older.txt + elif [ "$qt" = "m" ]; then + python -m pip install -r static/requirements/advanced/requirements_qt6_middle.txt + elif [ "$qt" = "t" ]; then + python -m pip install -r static/requirements/advanced/requirements_qt6_test.txt + fi + + if [ "$mpv" = "o" ]; then + python -m pip install -r static/requirements/advanced/requirements_mpv_old.txt + elif [ "$mpv" = "n" ]; then + python -m pip install -r static/requirements/advanced/requirements_mpv_new.txt + elif [ "$mpv" = "t" ]; then + python -m pip install -r static/requirements/advanced/requirements_mpv_test.txt + fi + + if [ "$pillow" = "o" ]; then + python -m pip install -r static/requirements/advanced/requirements_pillow_old.txt + elif [ "$pillow" = "n" ]; then + python -m pip install -r static/requirements/advanced/requirements_pillow_new.txt + fi + + if [ "$opencv" = "o" ]; then + python -m pip install -r static/requirements/advanced/requirements_opencv_old.txt + elif [ "$opencv" = "n" ]; then + python -m pip install -r static/requirements/advanced/requirements_opencv_new.txt + elif [ "$opencv" = "t" ]; then + python -m pip install -r static/requirements/advanced/requirements_opencv_test.txt + fi fi deactivate +echo "--------" echo "Done!" read -r diff --git a/setup_venv.sh b/setup_venv.sh index fbcf420a..7109b28c 100644 --- a/setup_venv.sh +++ b/setup_venv.sh @@ -2,10 +2,35 @@ pushd "$(dirname "$0")" || exit 1 +echo " r::::::::::::::::::::::::::::::::::r" +echo " : :" +echo " : :PP. :" +echo " : vBBr :" +echo " : 7BB: :" +echo " : rBB: :" +echo " : :DQRE: rBB: :gMBb: :" +echo " : :BBBi rBB: 7BBB. :" +echo " : KBB: rBB: rBBI :" +echo " : qBB: rBB: rQBU :" +echo " : qBB: rBB: iBBS :" +echo " : qBB: iBB: 7BBj :" +echo " : iBBY iBB. 2BB. :" +echo " : SBQq iBQ: EBBY :" +echo " : :MQBZMBBDRBBP. :" +echo " : .YBB7 :" +echo " : :BB. :" +echo " : 7BBi :" +echo " : rBB: :" +echo " : :" +echo " r::::::::::::::::::::::::::::::::::r" +echo +echo " hydrus" +echo + py_command=python3 if ! type -P $py_command >/dev/null 2>&1; then - echo "No python3 found, using python." + echo "No \"python3\" found, using \"python\"." py_command=python fi @@ -19,7 +44,8 @@ else read -r fi -echo "Users on older OSes need the advanced install." +echo "--------" +echo "Users on older OSes or Python >=3.11 need the advanced install." echo echo "Your Python version is:" $py_command --version @@ -31,10 +57,13 @@ read -r install_type if [ "$install_type" = "s" ]; then : elif [ "$install_type" = "a" ]; then + echo "--------" + echo "We are now going to choose which versions of some larger libraries we are going to use. If something doesn't install, or hydrus won't boot, just run this script again and it will delete everything and start over." echo - echo "Qt is the User Interface library. We are now on Qt6." - echo "If you are <=Ubuntu 18.04 or equivalent, choose 5." - echo "If you cannot boot with the normal Qt6, try the (o)lder build on Python ^<=3.10 or (m)iddle on Python ^>=3.11." + echo "Qt - User Interface" + echo "Most people want \"6\"." + echo "If you are <=Ubuntu 18.04 or equivalent, choose \"5\"." + echo "If you cannot boot with the normal Qt6, try \"o\" on Python ^<=3.10 or \"m\" on Python ^>=3.11." echo "Do you want Qt(5), Qt(6), Qt6 (o)lder, Qt6 (m)iddle or (t)est? " read -r qt if [ "$qt" = "5" ]; then @@ -52,9 +81,12 @@ elif [ "$install_type" = "a" ]; then exit 1 fi + echo "--------" + echo "mpv - audio and video playback" echo - echo "mpv is the main way to play audio and video. We need to tell hydrus how to talk to your existing mpv install." - echo "Try the n first. If it doesn't work, fall back to o." + echo "We need to tell hydrus how to talk to your existing mpv install." + echo "Most people want \"n\"." + echo "If it doesn't work, fall back to \"o\"." echo "Do you want (o)ld mpv, (n)ew mpv, or (t)est mpv? " read -r mpv if [ "$mpv" = "o" ]; then @@ -69,9 +101,28 @@ elif [ "$install_type" = "a" ]; then exit 1 fi + echo "--------" + echo "Pillow - Images" echo - echo "OpenCV is the main image processing library." - echo "Try the n first. If it doesn't work, fall back to o. Very new python versions might need t." + echo "Most people want \"n\"." + echo "If you are Python 3.7 or earlier, choose \"o\"" + echo "Do you want (o)ld pillow or (n)ew pillow? " + read -r pillow + if [ "$pillow" = "o" ]; then + : + elif [ "$pillow" = "n" ]; then + : + else + echo "Sorry, did not understand that input!" + popd || exit 1 + exit 1 + fi + + echo "--------" + echo "OpenCV - Images" + echo + echo "Most people want \"n\"." + echo "If it doesn't work, fall back to \"o\". Python >=3.11 might need \"t\"." echo "Do you want (o)ld OpenCV, (n)ew OpenCV, or (t)est OpenCV? " read -r opencv if [ "$opencv" = "o" ]; then @@ -91,6 +142,7 @@ else exit 1 fi +echo "--------" echo "Creating new venv..." $py_command -m venv venv @@ -129,6 +181,12 @@ elif [ "$install_type" = "a" ]; then python -m pip install -r static/requirements/advanced/requirements_mpv_test.txt fi + if [ "$pillow" = "o" ]; then + python -m pip install -r static/requirements/advanced/requirements_pillow_old.txt + elif [ "$pillow" = "n" ]; then + python -m pip install -r static/requirements/advanced/requirements_pillow_new.txt + fi + if [ "$opencv" = "o" ]; then python -m pip install -r static/requirements/advanced/requirements_opencv_old.txt elif [ "$opencv" = "n" ]; then @@ -140,6 +198,7 @@ fi deactivate +echo "--------" echo "Done!" popd || exit diff --git a/static/build_files/linux/requirements.txt b/static/build_files/linux/requirements.txt index 414c9f14..3b43e585 100644 --- a/static/build_files/linux/requirements.txt +++ b/static/build_files/linux/requirements.txt @@ -11,7 +11,7 @@ html5lib>=1.0.1 lxml>=4.5.0 lz4>=3.0.0 numpy>=1.16.0 -Pillow>=9.5.0 +Pillow>=10.0.1 pillow-heif>=0.12.0 psd-tools>=1.9.28 psutil>=5.0.0 diff --git a/static/build_files/macos/requirements.txt b/static/build_files/macos/requirements.txt index 793b3f76..2e74d2b0 100644 --- a/static/build_files/macos/requirements.txt +++ b/static/build_files/macos/requirements.txt @@ -11,7 +11,7 @@ html5lib>=1.0.1 lxml>=4.5.0 lz4>=3.0.0 numpy>=1.16.0 -Pillow>=9.5.0 +Pillow>=10.0.1 pillow-heif>=0.12.0 psd-tools>=1.9.28 psutil>=5.0.0 diff --git a/static/build_files/windows/requirements.txt b/static/build_files/windows/requirements.txt index 88976afe..33b95eee 100644 --- a/static/build_files/windows/requirements.txt +++ b/static/build_files/windows/requirements.txt @@ -11,7 +11,7 @@ html5lib>=1.0.1 lxml>=4.5.0 lz4>=3.0.0 numpy>=1.16.0 -Pillow>=9.5.0 +Pillow>=10.0.1 pillow-heif>=0.12.0 psd-tools>=1.9.28 psutil>=5.0.0 diff --git a/static/default/login_scripts/sankakucomplex login 2018.11.08.png b/static/default/login_scripts/sankakucomplex login 2018.11.08.png deleted file mode 100644 index 74e1ca5b4e0dfee5e229e699833489b60fad57d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3245 zcma)4o)tU>=NHg$*$ z0E|IU9Zl=dDU#hw>+3l7SBnx7J!rnpyT2&iEXo|66IW>qJ3`S?7Mht))l!^F^DJA! zWy_#)QZQp@Q&|s#QSO=ce!Z}|9mqwOnQFhltOnE3HRiQmOUAg$fwT#7tn)Z&l}N< zrF0Qn$v+61!IKbrmd7_andtMYs@sR&ha=3Rx)$LO_sd5rhxUJ|Y~~B7L24dt)5@p8 zAqVgLlsy?)kgHGjj|&S`RN4n}4Q{U#z#yvyR&_tGb8FaKn}LqBn!lx7&|gTO>#$+I z3jrb5He5RN4H3!CK-4&33m45`<)!|0zyqVV7WLa9Tp-*i3gUTiZazn|)r2z&p?!dF zjbcjw`t2Mc()VPJndkV*V%nK+%M~Qlwe@Ui|fMA~d9VgeirZUg%Zj4wE1| zVBX=8v2vDeKkcdGXH4k>A&{URWKD@Hq(CnKW(lZcUerV(^VxvTd&ckmGXBs_4um%6 zwZ2IZgaW^DxVeOOEU!Vc>QT zHTecG1vo#w!&V|@Cri5GpF1jVEkXw|2DYON5MQ3c@$4YLw>wiglAc$=bzA%AtS-X# zY$yH}{6Mc;G5R%gbeyfke;N7s={XK{#(@|?rFOT6H>GX9hlxpn!>-sc9o|2yiQi0n z^{1ou9`a2Vut4|zT}w9U1JFVLr+CDUQrPRTk@xn^>4JWv-mZOqeV^e7)5tsh6bCa&IcNz`Mqx~&fFE7}8AZ7YR}m%SscTM6DiUBmL^#IIM6 zQt8E@k^=%w{QtIw$f9I>G5RI$rNu~Vf}gLr#$w@nQPs4qWoIhQ@~PT84X#Nv=a@8_d%MLrySFfLPT2PJu!=d+1^ z!r9f`AsH`VWNY->X>kDW6eb~A)9>%bQ-BTTu8XX}s+@y=ExT=>N?;y|_%EYoz-A>S zkQ(1XWrUY0JFI;P?UW{|*3)%3D?pL%S4F2C1reS_=FHOw=b61wOHiZ@k;-Zq9XxMl5jfeX{DK6fX+=i4mA; zQeaxb1^2BcD!mQsVy1As6g4D-2T<7D6_Z_v;Y~RdVTGJFx|AFI?2O91($|&}v!KN? zux;RInH9-Jxx^sJ278)}@j}P&!m&nsNy>)QTNRH(O;Fz>{BIXa#A}^*HFTi-K<{`? z_aB{nDBc%&)@)hNZ5(Y@S;AlPp_yi1+W1;`3D$FUL7gMBFp=$U_=#IbztkMKzBRCK zS2$NRL&(Q+Izgar$ZkIN1yaj{`SXtOD#0zMVmUYf=44pJWsQ${(xIsTeIodmEY8}k zKH^d&;lgWv-+ZLCQqG(*njfFjYR&cU3bbY0yu*cVuW!yUoVXJrvD+YZvOKU(p~XVz zD)f7*a9I&)+-Ttv9~BN50u28z5cV`yhqb-Z`yP+VbDUAE`KQGo$HQiZW79h`6jLlZ z@WNyWVSx9nxV4L+3KcJ>xa=!BVgGiA{EF#QLi4oEBypL&8SGt9%Ij^aTgxL%w^Ur> zM6!_gp5t~U_rH9R*#Qik`_aALLV~eOb!KFO5N<%%7u_x+f2L`*9Yi%GDd2Et{YD-WQ3Ln5Kiy=$!~wxJSL)S&$T>j z>X@uB?0GiZ+#mg}h=%SxGnb_};}>I>T0;+iVs0E%)1e<40eQQ!{S9d6n;7dL%j}N* z1y3AoR&75yn+=zi9v68~<|PW$sK9o@ID|TvRc9>Pj-53eN`LRwMN9*~b0?FsX~$`} z+EMMfJFIiGzlE)PF~`L8vHV%Riw|d`+pcMVn)bQRK+D%FqF>(^;#PL|J5^bH1b?&| z2QgvM@9^WB^1wd$8svwz0BwfCcrX@XTvW&8bNdx{^dMG(YJD=7 zr}@gGHMXy(extdN3oFln%B%@fgvaXLz=I_*l}#AkDkyhQIqFQ}{yM9?P$LM$KRdX} z)7FDdwrj>ZEGE%wfkW%EU5fJz+&;<1&^GCbK0JAW^7s&e3TD^zrEJ-(AdTHxn%RSQ zFE8biH_*ir+wd&sRhR~0CjON38oSwZct_KpYfMRxb69}U=?jiuVX5?EM;a^lvdrD{ zx$}kM=~ZGKMsAX50EOM1I34?k$Pt@{1m25Vsiq#<;UySi6v(gEOSwRGEAc#X$&?on z2{)DTtGw+S53c*pWbJKnyS-2TNZND^J}Kw)<%w-{T2pn5YDYZu&u%TDx9MbzVpg)z z;m7V)Tdg4!e$XPAcpss6EklPvC#s*N93y^&Q?B&!g7LN!nL)-YmMYDJv>5#7aM`|Ajyme5!I zR?L#Q!F*Nu`w2yu=C92cBNI1-5Oa0D@66A)O#UjADpWhD>~3ry1+H{9rC4YLuImfM zy*SQv*JwJM%mL4UxjmkXPd)das>`mvY9ifqI57~mQu$)P2dkdhJd$hwtzqluoH_lwG(lJefUuFx(G5zoZJW W5YkD0#%uQ94}t2M>QrbsBL4@wq)Vd! diff --git a/static/default/parsers/inkbunny file page parser.png b/static/default/parsers/inkbunny file page parser.png index 2b3696dd335e120e3e31628bba1683055ed6d9da..8d01629e345d5b2791a0b5f4396d7ed6f4cfb475 100644 GIT binary patch literal 2936 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 UIcxvp_eTvt>6+>^AYJ1B3lVF500000 literal 3032 zcma)8_fON07X6g*m8}dBTC{8sEmKfdQ7Fo^tbz(M6xqA%jeraVS+cPVMRq|15en8X zdlrfe*=6s&TGsRZ0WW#U&CN~D$;tWQ=H$faJ$}T@0A>IHfLT)mVE_PBe@_5K_umaB zP-_DKBSsVPz{q!O!z{!sc?hh6g|JwI)9;7r(q3haq-wDcoob=O8b077B~PgeeYovq zH>?Y41Sjg{r59)A$+?PJiWQ2IUWv}MX83>LkCHuQRa19sW`%485l)&;BGnL`pe^!w zRSuM}mz{=g+L9-rN9DQFy#QXxYh3Y^(1GQ!dVm!Gu>xVmmIdOLOnnM=#e5@p3F9~w zexQ10hK3%WL<;Y8y+bX@{H&aly0>vy)^_*^xYy55C3%_05ml{=Mk7sYz+`|4K_K3m z!)0ZacOfrBKdb8ld;RNN0UW^>+ZBc~4AdFRv`pK%-oa5#pVVnAc`FxfdX6Eiq3%rh z4&_kcTb%K9`1>=BO0l4~w0*b<#$3_RnB%?XN@kyLkP4g9fxk5FnP1UtBPtx>LKO~w z4H0SaLyZD^S<^+XG&zIxMwge%CDVfaIjqwA%-X@5&t!mq?u^wdln-yK<=)`raayY; zlvU-^6L6h4KC}AvqpQiK#z0(u>fZ~IOeyE@3(a)7fU~+xj)sU`;h>APq9>_ftKVi7+JK?ZfJ^C>)!8a&_CVe=9O;$Ppx*+h4Nj`_Si7I*})w*7*(L=VFcAt~! zz4dQvcIfh;0%Y(wn|Qd&hD`Ye-F>(G)1c(ts_fS{{=Yin9W?<%*j#o*7hRkMQidi0=;DRNt4s(o z6BG0#bY%x_Jim@)GE9nTqL2Hie0z(=!#J5#+j+VZ}F+X%a}czGx_6{wo6A zqN)1mz(O1==eyLB7#0R4jQYP7CgsDS!s3I(m&Y3;LJq>sNaU3Q$#FSU@2Du=fmhyF ztti{WX`_O3rv$ylfPy@0lVAJO705+Kx(Ch=_hWe(C|2ol6YsoAG(q~s$%}Q$#TD>> z;>`0r`OlhCDV)suil$s0JG3VlBBD&$im%b?U=SWF`XRd~@_J#G>TP!Y9A}yOYF7N} z6%~=5Z$&y&O(qf7+*I@6Gb>~bz8AIq<+vm0M={If%5o4kh%_% zhfQIqRs44Zd_|l?&Y~rNiMm>XQ^(z@;p)vaNmsAYy5B{b`K>(pls29xoAvZIm1M>2 zacrrD`a||1ml~>e<9$|;ryHyqDL9=#W*fy0C00rTo2~9z0#E|;{I?CNda;g#zNL80 zeyiWsfXjo*^qg0)QSMBFp}c#3B5bl0TR-4-`gu23YHtZNnA+yO<>zME&$^EQ zX#U>`eN4Ao$ZUa%(#TLxBoQz7dXCY2;EfaJrFy~z*TQu!(5nB1AQ!m?W}Mnmxd@;5 z@W)pkzOtwPn-iB#7)wv%lN@5Qi_h@~&by)*1&U0G4hBFBsUv{y<{x))jxkM4#`kp& zvsP}7&EKxAO6fSdx5JEnm}F$-7-z?y-L{KdCM)VAc5R<@gULVMQ8zOXWXJkOMm2R-NU#yjG+$HjU=E)25DT@IqmW8e?`Qi&pOVG4=VLkk7KDp%21BZgQj+EVn|G0=DrXQUMy3dwX9 z)Aa7*dtPF2+6W`T<5iN&Jo)1`r;s8oi7B@7=N=<)G2(N%*Ik?EmW*O_$oD6Z&@_LL zEgJswvDSNzr#nxV+M`)UqdHofAIY1fEsKjF>KQ>ZqQ-3?Jf!9cZLEP+C}#nXIV4tX zu)mh0+H+B}C}f|oc&9ttw83qe6%`E-I4ZL8E8SlFv9WD%t1?=~d~UgCWjtJK^V!Y` zUzw{KQk_J(8^U(2`bCzCqp)*h$y#o}4QBSw*Yej$8M4&rk9#hW?M}J10NoU-&`CeT zfevXQCMWU{4XZxbWHb6<>Hr*|st)>}-xm=T2EsFNvreWg1F4Y~*h3G0cdd;gN*3rR z!nu0KJp9ZE>VGl(?GwJuFhHwR%N#cS{h+)+v-3sTXs5_aB%#Pz2N&3Qq2e-hdWK80 z?r*7^z4x9F+;>#cz%AO)bC;?1^-E4Lo_uK2ft@#3s)G73; zN6!yTDHV-t`k(Hlrd9o#l8HOp?SPrRdbi*EYH$A6dX4;9FwXs0)x{&C`Np5)k*45s z3#&HO;`#81PTg_T0E>1c{5+V-9=6ma*;|K=w~Muia_dD$?-miy&-r_hDhJ%Xr!BU5Q?;I@pw-I_1Mi?YvIw*j?kasC`U|>AC(CSu!g- zBi8b~;=Nfg57^a5Y3}1*@k|*BESYU;(;U5o^f|=w#9!xPW^H=p{aYXQfS|ilGmZ77v(x-U)#1Qk zuaON!7BniEoDVUD;zDlO`!(kVovp@D>S+ILjh{E%fdzJC4qlGHU9e0SK25XD@%^l+ zaJc!SW(v3ax;SGb{#2&mxX1OWN^D&~n!jRj0k~#S0rMRfsBhVPFr z1$%R^`-3~#UIJwsVH9~cr<~g({pSNN zBPg~@H|U0SIaa~MXY|o_ypqv!Rfp>sKC!woT5!Ua?31YYMSO!@iN4{^xpyo`Y?qd^gwiq`j{SIwT{ z&QqyFnzyailmkYZiWKl9i_B}Ln4FDT4aH$s>m7ygftI}8~b+Z{|}4cQ50U&oA4mT=QWg==Ofw@(?7lw#dUCvcbTf>Ju4%`Y%VS`%XlMUp1%^S_o~ zKl}WPX(r1}93z07hHg^=ixu5Swg|B75BaeAgr4<`#)Z0wM?C19n; z`JCHzlfy+1dF(gal=`Nv`S~m5m8IG=%x|u9?o+}SRzr8!>5u}5s}Xdi&xEv_M(`(W z0zE*LAmn38W`+M=7t_KT0(ox;b)Ji+3~`rF4Vdxl*QvZ;cKS-ig|hP&k^xmq;iNq< zQn^{RpE`9d&Mi6pl)jGyzzYBn<{t#HE}iOal#Y}GL<n z)nm_yt1!b=#JN0`-4Mrf&gmlfmnm-zZ1%IS#UN)7S-n~dEoBC63F|{Twj_B$3&(KPh{4Jv%?>EkJ<)5hg zv#4w7AH-z%P?nEOBT&%B#^@4lIGbJ)&Q}(4IbTTB>n!PCT)N46RhN_=s8&o+G-iI} zrczP_Llh!)f-j`_x!ArJ!xM$5XDM;Tkki5LMb*PL&%uJ5+3u_neOR7q*1O;nws5vZ z2AK>(Gv<=t!ur&zX8I4qy^IrryVm}m=0nLfCmFv3EUWKN6%Q25bm=BH>DD6CahV%g zx`2AkxTSwa6|sFx@? zVp35r;Qp6~R(p*d%9n2(Usrt_**g^jeWOV3p6Tmg*|sojzf_%`5rOjOq)0}*DXp-w zeN<}@4U@YZT#g<%r{~S`t;NU%sdj77c(<~$4{Sa(N)o$e*SeQWLS89wQKW=_t6 zXas!4K=-U9;KV|lS+J?fdr)N-x?#TvA5DN*2 znhoxleF*n(Iwum#x_MmY;5CP?#Gl%8FH^egLQgy{L$(_X$wtAiB6D3sveK^geB^v+Ei{9lr0e*oZmnJkm z&^x*Pd6nk0X0H3$9&qeOA2e(#P9%$nJRh(F-;XpfU<6D7$1Y z2#fDFT)`fSiEz|5@d}!>_!R=l*W&E^7ibm$^*Qde87QaD0 zt)^Rr$@K#~H+TzTXboWA7TsL^oB=2|a;S3bZ+8CBG%yXczntTsGP z!kvrQ9Gi|BJ_do{=0HdP$#KtN8r9kEAOAvSAnbge!ZfqUQzfQ zSLWZBZwAA8w{gva*#iSHQ#Xf|tCm*d4HM>Tnx&GHg^s(n@`B@MenJk|kGnc4y{eF4_!O&glD6`A3r*xiM!-TGE;efAc8 zu;Az5sLpcFYr@?Vx>vvtN2lo?y5G$P3m?#0ezi7iUTOyx|e56OgVZtm3NsmzaTnT(jSPmeT%A2vDRMH(;RzqTX&1~a?8U9!;|lT+7A zD9no8rdUcz5JM9jiJ0sjEBUjvY4RuO;fHgVCw6A@=!$3jd#Vb-#h|e7+gYh6;WBuE zq#)0+Gi2{|WmK`27Ndr<^GS2eaE}^Y`=y&*)7fz+!mUr%)9sg|?X12XdIvj?+WmbE zJIVAO3e`m4ZH|p!z;QD<1>eo$RieR9o!mX&&~LO8I=AbmHFo3L-f}!O9SOS}I*szf z(OYl!VfIOMw1-U#+fjcr2b~x(zXNX9)hkRV?){M|ENyBSli$0$%3z@VeieJVRN-5k dN3;kYt$QHL3Ad*@t1t`fhrNxXb&aJD@gFrt-TD9k delta 2569 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 W6Qumm2^r9F8R}tmtF=1.0.1 lxml>=4.5.0 lz4>=3.0.0 numpy>=1.16.0 -Pillow>=9.5.0 -pillow-heif>=0.12.0 psd-tools>=1.9.28 psutil>=5.0.0 pyOpenSSL>=19.1.0 diff --git a/static/requirements/advanced/requirements_pillow_new.txt b/static/requirements/advanced/requirements_pillow_new.txt new file mode 100644 index 00000000..e68991c0 --- /dev/null +++ b/static/requirements/advanced/requirements_pillow_new.txt @@ -0,0 +1,2 @@ +Pillow>=10.0.1 +pillow-heif>=0.12.0 diff --git a/static/requirements/advanced/requirements_pillow_old.txt b/static/requirements/advanced/requirements_pillow_old.txt new file mode 100644 index 00000000..5f6fd0ab --- /dev/null +++ b/static/requirements/advanced/requirements_pillow_old.txt @@ -0,0 +1,2 @@ +Pillow>=9.5.0 +pillow-heif>=0.12.0 diff --git a/static/requirements/advanced/requirements_server.txt b/static/requirements/advanced/requirements_server.txt index f0320504..7cd269a3 100644 --- a/static/requirements/advanced/requirements_server.txt +++ b/static/requirements/advanced/requirements_server.txt @@ -4,7 +4,7 @@ cloudscraper>=1.2.33 html5lib>=1.0.1 lz4>=3.0.0 numpy>=1.16.0 -Pillow>=9.5.0 +Pillow>=10.0.1 pillow-heif>=0.12.0 psutil>=5.0.0 pyOpenSSL>=19.1.0