Version 509

This commit is contained in:
Hydrus Network Developer 2022-12-07 16:41:53 -06:00
parent 5acdcc8af0
commit e3ca6aadd1
No known key found for this signature in database
GPG Key ID: 76249F053212133C
44 changed files with 558 additions and 244 deletions

View File

@ -22,15 +22,16 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: 3.9
-
name: Install mkdocs-material
run: python3 -m pip install mkdocs-material
-
name: Build docs to /help
run: mkdocs build -d help
run: |
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade wheel
python3 -m pip install mkdocs-material
mkdocs build -d help
-
name: Install PyOxidizer
run: python3 -m pip install pyoxidizer
run: python3 -m pip install pyoxidizer==0.22.0
-
name: Build Hydrus
run: |

View File

@ -0,0 +1,14 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEY4+qDBYJKwYBBAHaRw8BAQdASU9rid8CK3cAWZL7eWnJp9Q/A0yfUQ9iid8B
kOIHRay0MUh5ZHJ1cyBOZXR3b3JrIERldmVsb3BlciA8aHlkcnVzLmFkbWluQGdt
YWlsLmNvbT6ImQQTFgoAQRYhBCVN2xmDKUePYg2hr3YknwUyEhM8BQJjj6oMAhsD
BQkUsQQUBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEHYknwUyEhM8hJ8B
AOqxFvGKQmra0hDHrWYqiG+GTmKj/DGJI9nPlpkl9f7WAP9FQ+gKhdfI3Szd95va
C2H1iVaLCJiQpFecsxZBVredA7g4BGOPqgwSCisGAQQBl1UBBQEBB0Dk6fxwmYMm
o0LHiBDmU7+1igXdm7IxHp6KaM+B5LZ8HQMBCAeIfgQYFgoAJhYhBCVN2xmDKUeP
Yg2hr3YknwUyEhM8BQJjj6oMAhsMBQkUsQQUAAoJEHYknwUyEhM86+wA/3R1Ye2q
2rCA64BlJiwlvmJSK9wpajKtxGMhty7mUYfjAQDfnyEsQg8jEc5XF0YLrq+OdnP/
6/JN2h5YwyrpLCHICQ==
=TMs7
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -7,6 +7,50 @@ title: Changelog
!!! note
This is the new changelog, only the most recent builds. For all versions, see the [old changelog](old_changelog.html).
## [Version 509](https://github.com/hydrusnetwork/hydrus/releases/tag/v509)
### misc
* added an option 'mouse wheel can "scroll" through menu buttons' to _options->gui_. this turns off the behaviour where a mouse wheel event over, for instance, the file sort asc/desc button, will change the button's value rather than scrolling the underlying panel. if you found this annoying, you can finally turn it off!
* fixed an annoying 'save service' bug that some users saw last week with the introduction of serverside Tag Filters. some users had an old datatype in their service data storage--a legacy issue--but the system now coerces all datatypes and direct sub-objects to a saveable format on load or update
* the tag washing system now collapses more types of whitespace character to `space`. mostly this means tab is now converted to space, but some unicode stuff goes too
* the hangul filler character `\u3164` is no longer permitted as a namespace or subtag. it can be in longer tags, but isn't allowed on its own (where it appears to be a blank space). (hydev saw one in the wild, probably from some cheeky post title)
* let me know if you run across a newly invalid tag already in your system and the UI goes bananas--ideally hydrus should now catch this and either fix itself or report with a polite note, but let's see. if things go crazy, run _database->check and repair->fix invaliid tags_
* improved some image transparency detection and slicing logic. it is more accurate and saves more memory now. also, the system that saves thumbnails will more reliably use jpegs when it doesn't need png's transparency
* fixed some PSD thumbs showing a fully transparent transparency layer
* fixed a bug where you could enter capital letters into the namespace colour list in 'tag presentation' options panel
* the default twitter downloaders are all renamed to remove the confusing and technical 'syndication' label
* 'speedcopy' is now an optional supported library. a couple users have suggested this to make network copies on Windows and Linux much faster. I'd like some advanced users who run from source to try adding it to their venvs, and we'll see how it works out IRL in different situations (you can see if it is loaded under _help->about_)
* if you run from source, the 'advanced' setup route now offers a (t)est Qt install, which sets PySide6 6.4.1 (up from 6.3.21). feel free to try it out--it works well for me, but I want to test it more before trying to roll it to the releases
* in a side thing, thanks to the user who walked me through setting up signed commits to github with my own PGP key. you can see my new key in the contacts help page, id 76249F053212133C, and I am now committing with it. I'm not very familiar with the sheer mechanics of this tech, so bear with me, but I'm pretty sure I can sign or encrypt something if ever needed
### macOS build fix
* since v505, many macOS users were unable to boot the built app. it has taken multiple rounds of back and forth with users, but we figured it out. (looks like pyoxidizer updating from 0.22.0 to 0.23.0 simply broke qtpy/Qt bindings, so we force a rollback this week)
* also, the macOS app moves from PySide6 to PyQt6 this week. they are basically the same, but PyQt6 packages into a 258MB dmg, less than half the 548MB PySide6 one!
* let me know if the macOS app gives any more trouble. otherwise, to the people who helped out here, thank you very much for the help!
### mostly boring tag filter panel
* removed the 'add' buttons; added 'delete' buttons to the simple whitelist and blacklist panels; added 'block everything' to simple blacklist panel
* the panel now talks about the special sibling and namespace rules when you edit an explicit blacklist-mode-only filter (the tag import options blacklist works this way)
* the 'you didn't need to add that exception' text and 'filter is too complicated for this panel' texts now show/hide rather than waste empty space
* some of the simple-advanced interactions are better, but there's still some logical bork here. mostly stuff like when you hit the 'unnamespace' checkbox in the whitelist panel, it gets needlessly added to the 'except' column in the advanced, rather than just removed from the advanced 'exclude'. I'll fix this up in the near future
* the two namespace checkbox lists are now sized more appropriately
* the white/blacklist panels disable more simply and reliably
### boring cleanup
* the confusing 'view this file's duplicates' menu label, which was an artifact of an old submenu label, is removed. if the duplicate menu wants to present the 'view' commands for two locations, it'll title with the respective location, otherwise the commands speak for themselves, no label
* some old 'check(er) timings' nomenclature is renamed to 'checker options' across the board
* the hydrus serialisable dictionary now washes any nested lists or dicts to hydrus serialised equivalents, which should stop situations like the save service bug in future
* the hydrus serialisable list can now handle a mix of hydrus serialisables and python primitives. it also washes its lists or dicts to serialisable equivalents
* improved the data-stability of some image channel slicing
* fixed some PIL fallback thumbnail generation, and improved its 'has transparency' png/jpeg decision-making
* fixed the main thumbnail loader being confused at times about which thumbnail mime to load with. the check I have added is ultra-fast on data we are loading anyway, so we shouldn't notice a difference, but if you get slow thumb loads, let me know
* fixed the media container embed buttons using the file mime rather than the thumb mime when loading thumbnails (again causing transparency issues)
* fixed more generally bad mime handling in the thumbnail generation routine that could have caused more unusual transparency handling for clip, psd, or flash files
## [Version 508](https://github.com/hydrusnetwork/hydrus/releases/tag/v508)
### misc
@ -445,40 +489,3 @@ title: Changelog
* refactored the file service pathing db code (this does directory structures and multihashes for ipfs) to a new module
* refactored some tag display, tag filtering, and tag autocomplete calls down to appropriate db modules
* refactored and extended some tag sibling database methods and names to clarify whether they were working with ids or strings
## [Version 499](https://github.com/hydrusnetwork/hydrus/releases/tag/v499)
### mpv
* updated the mpv version for Windows. this is more complicated than it sounds and has been fraught with difficulty at times, so I do not try it often, but the situation seems to be much better now. today we are updating about twelve months. I may be imagining it, but things seem a bit smoother. a variety of weird file support should be better--an old transparent apng that I know crashed older mpv no longer causes a crash--and there's some acceleration now for very new CPU chipsets. I've also insisted on precise seeking (rather than keyframe seeking, which some users may have defaulted to). mpv-1.dll is now mpv-2.dll
* I don't have an easy Linux testbed any more, so I would be interested in a Linux 'running from source' user trying out a similar update and letting me know how it goes. try getting the latest libmpv1 and then update python-mpv to 1.0.1 on pip. your 'mpv api version' in _help->about_ should now be 2.0. this new python-mpv seems to have several compatibility improvements, which is what has plagued us before here
* mpv on macOS is still a frustrating question mark, but if this works on Linux, it may open another door. who knows, maybe the new version doesn't crash instantly on load
### search change for potential duplicates
* this is subtle and complicated, so if you are a casual user of duplicates, don't worry about it. duplicates page = better now
* for those who are more invested in dupes, I have altered the main potential duplicate search query. when the filter prepares some potential dupes to compare, or you load up some random thumbs in the page, or simply when the duplicates processing page presents counts, this all now only tests kings. previously, it could compare any member of a duplicate group to any other, and it would nominate kings as group representatives, but this lead to some odd situations where if you said 'must be pixel dupes', you could get two low quality pixel dupes offering their better king(s) up for actual comparison, giving you a comparison that was not a pixel dupe. same for the general searching of potentials, where if you search for 'bad quality', any bad quality file you set as a dupe but didn't delete could get matched (including in 'both match' mode), and offer a 'nicer' king as tribute that didn't have the tag. now, it only searches kings. kings match searches, and it is those kings that must match pixel dupe rules. this also means that kings will always be available on the current file domain, and no fallback king-nomination-from-filtered-members routine is needed any more
* the knock-on effect here is minimal, but in general all database work in the duplicate filter should be a little faster, and some of your numbers may be a few counts smaller, typically after discounting weird edge case split-up duplicate groups that aren't real/common enough to really worry about. if you use a waterfall of multiple local file services to process your files, you might see significantly smaller counts due to kings not always being in the same file domain as their bad members, so you may want to try 'all my files' or just see how it goes--might be far less confusing, now you are only given unambiguous kings. anyway, in general, I think no big differences here for most users except better precision in searching!
* but let me know how you get on IRL!
### misc
* thank's to a user's hard work, the default twitter downloader gets some upgrades this week: you can now download from twitter lists, a twitter user's likes, and twitter collections (which are curated lists of tweets). the downloaders still get a lot of 'ignored' results for text-only tweets, and you still have to be logged in to get nsfw, but this adds some neat tools to the toolbox
* thanks to a user, the Client API now reports brief caching information and should boost Hydrus Companion performance (issue #605)
* the simple shortcut list in the edit shortcut action dialog now no longer shows any duplicates (such as 'close media viewer' in the dupes window)
* added a new default reason for tag petitions, 'clearing mass-pasted junk'. 'not applicable' is now 'not applicable/incorrect'
* in the petition processing page, the content boxes now specifically say ADD or DELETE to reinforce what you are doing and to differentiate the two boxes when you have a pixel petition
* in the petition processing page, the content boxes now grow and shrink in height, up to a max of 20 rows, depending on how much stuff is in them. I _think_ I have pixel perfect heights here, so let me know if yours are wrong!
* the 'service info' rows in review services are now presented in nicer order
* updated the header/title formatting across the help documentation. when you search for a page title, it should now show up in results (e.g. you type 'running from source', you get that nicely at the top, not a confusing sub-header of that article). the section links are also all now capitalised
* misc refactoring
### bunch of fixes
* fixed a weird and possible crash-inducing scrolling bug in the tag list some users had in Qt6
* fixed a typo error in file lookup scripts from when I added multi-line support to the parsing system (issue #1221)
* fixed some bad labels in 'speed and memory' that talked about 'MB' when the widget allowed setting different units. also, I updated the 'video buffer' option on that page to a full 'bytes value' widget too (issue #1223)
* the 'bytes value' widget, where you can set '100 MB' and similar, now gives the 'unit' dropdown a little more minimum width. it was getting a little thin on some styles and not showing the full text in the dropdown menu (issue #1222)
* fixed a bug in similar-shape-search-tree-rebalancing maintenance in the rare case that the queue of branches in need of regeneration become out of sync with the main tree (issue #1219)
* fixed a bug in archive/delete filter where clicks that were making actions would start borked drag-and-drop panning states if you dragged before releasing the click. it would cause warped media movement if you then clicked on hover window greyspace
* fixed the 'this was a cloudflare problem' scanner for the new 1.2.64 version of cloudscraper
* updated the popupmanager's positioning update code to use a nicer event filter and gave its position calculation code a quick pass. it might fix some popup toaster position bugs, not sure
* fixed a weird menu creation bug involving a QStandardItem appearing in the menu actions
* fixed a similar weird QStandardItem bug in the media viewer canvas code
* fixed an error that could appear on force-emptied pages that receive sort signals

View File

@ -8,6 +8,8 @@ I welcome all your bug reports, questions, ideas, and comments. It is always int
You can contact me by email, twitter, discord, or the release threads on 8chan or Endchan--I do not mind which. Please know that I have difficulty with social media, and while I try to reply to all messages, it sometimes takes me a while to catch up.
If you need it, [here's](assets/hydev_key/Hydrus%20Network%20Developer_76249F053212133C_public.asc) my public GPG key.
The [Github Issue Tracker](https://github.com/hydrusnetwork/hydrus/issues) was turned off for some time, as it did not fit my workflow and I could not keep up, but it is now running again, managed by a team of volunteer users. Please feel free to submit feature requests there if you are comfortable with Github. I am not socially active on Github, please do not ping me there.
I am on the discord on Saturday afternoon, USA time, if you would like to talk live, and briefly on Wednesday after I put the release out. If that is not a good time for you, please leave me a DM and I will get to you when I can. There are also plenty of other hydrus users who idle who can help with support questions.

View File

@ -34,6 +34,45 @@
<div class="content">
<h1 id="changelog"><a href="#changelog">changelog</a></h1>
<ul>
<li>
<h2 id="version_509"><a href="#version_509">version 509</a></h2>
<ul>
<li><h3>misc</h3></li>
<li>added an option 'mouse wheel can "scroll" through menu buttons' to _options->gui_. this turns off the behaviour where a mouse wheel event over, for instance, the file sort asc/desc button, will change the button's value rather than scrolling the underlying panel. if you found this annoying, you can finally turn it off!</li>
<li>fixed an annoying 'save service' bug that some users saw last week with the introduction of serverside Tag Filters. some users had an old datatype in their service data storage--a legacy issue--but the system now coerces all datatypes and direct sub-objects to a saveable format on load or update</li>
<li>the tag washing system now collapses more types of whitespace character to `space`. mostly this means tab is now converted to space, but some unicode stuff goes too</li>
<li>the hangul filler character `\u3164` is no longer permitted as a namespace or subtag. it can be in longer tags, but isn't allowed on its own (where it appears to be a blank space). (hydev saw one in the wild, probably from some cheeky post title)</li>
<li>let me know if you run across a newly invalid tag already in your system and the UI goes bananas--ideally hydrus should now catch this and either fix itself or report with a polite note, but let's see. if things go crazy, run _database->check and repair->fix invaliid tags_</li>
<li>improved some image transparency detection and slicing logic. it is more accurate and saves more memory now. also, the system that saves thumbnails will more reliably use jpegs when it doesn't need png's transparency</li>
<li>fixed some PSD thumbs showing a fully transparent transparency layer</li>
<li>fixed a bug where you could enter capital letters into the namespace colour list in 'tag presentation' options panel</li>
<li>the default twitter downloaders are all renamed to remove the confusing and technical 'syndication' label</li>
<li>'speedcopy' is now an optional supported library. a couple users have suggested this to make network copies on Windows and Linux much faster. I'd like some advanced users who run from source to try adding it to their venvs, and we'll see how it works out IRL in different situations (you can see if it is loaded under _help->about_)</li>
<li>if you run from source, the 'advanced' setup route now offers a (t)est Qt install, which sets PySide6 6.4.1 (up from 6.3.21). feel free to try it out--it works well for me, but I want to test it more before trying to roll it to the releases</li>
<li>in a side thing, thanks to the user who walked me through setting up signed commits to github with my own PGP key. you can see my new key in the contacts help page, id 76249F053212133C, and I am now committing with it. I'm not very familiar with the sheer mechanics of this tech, so bear with me, but I'm pretty sure I can sign or encrypt something if ever needed</li>
<li><h3>macOS build fix</h3></li>
<li>since v505, many macOS users were unable to boot the built app. it has taken multiple rounds of back and forth with users, but we figured it out. (looks like pyoxidizer updating from 0.22.0 to 0.23.0 simply broke qtpy/Qt bindings, so we force a rollback this week)</li>
<li>also, the macOS app moves from PySide6 to PyQt6 this week. they are basically the same, but PyQt6 packages into a 258MB dmg, less than half the 548MB PySide6 one!</li>
<li>let me know if the macOS app gives any more trouble. otherwise, to the people who helped out here, thank you very much for the help!</li>
<li><h3>mostly boring tag filter panel</h3></li>
<li>removed the 'add' buttons; added 'delete' buttons to the simple whitelist and blacklist panels; added 'block everything' to simple blacklist panel</li>
<li>the panel now talks about the special sibling and namespace rules when you edit an explicit blacklist-mode-only filter (the tag import options blacklist works this way)</li>
<li>the 'you didn't need to add that exception' text and 'filter is too complicated for this panel' texts now show/hide rather than waste empty space</li>
<li>some of the simple-advanced interactions are better, but there's still some logical bork here. mostly stuff like when you hit the 'unnamespace' checkbox in the whitelist panel, it gets needlessly added to the 'except' column in the advanced, rather than just removed from the advanced 'exclude'. I'll fix this up in the near future</li>
<li>the two namespace checkbox lists are now sized more appropriately</li>
<li>the white/blacklist panels disable more simply and reliably</li>
<li><h3>boring cleanup</h3></li>
<li>the confusing 'view this file's duplicates' menu label, which was an artifact of an old submenu label, is removed. if the duplicate menu wants to present the 'view' commands for two locations, it'll title with the respective location, otherwise the commands speak for themselves, no label</li>
<li>some old 'check(er) timings' nomenclature is renamed to 'checker options' across the board</li>
<li>the hydrus serialisable dictionary now washes any nested lists or dicts to hydrus serialised equivalents, which should stop situations like the save service bug in future</li>
<li>the hydrus serialisable list can now handle a mix of hydrus serialisables and python primitives. it also washes its lists or dicts to serialisable equivalents</li>
<li>improved the data-stability of some image channel slicing</li>
<li>fixed some PIL fallback thumbnail generation, and improved its 'has transparency' png/jpeg decision-making</li>
<li>fixed the main thumbnail loader being confused at times about which thumbnail mime to load with. the check I have added is ultra-fast on data we are loading anyway, so we shouldn't notice a difference, but if you get slow thumb loads, let me know</li>
<li>fixed the media container embed buttons using the file mime rather than the thumb mime when loading thumbnails (again causing transparency issues)</li>
<li>fixed more generally bad mime handling in the thumbnail generation routine that could have caused more unusual transparency handling for clip, psd, or flash files</li>
</ul>
</li>
<li>
<h2 id="version_508"><a href="#version_508">version 508</a></h2>
<ul>

View File

@ -6,6 +6,7 @@ import time
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusImageHandling
from hydrus.core import HydrusThreading
from hydrus.core import HydrusData
@ -652,16 +653,6 @@ class ThumbnailCache( object ):
hash = display_media.GetHash()
mime = display_media.GetMime()
thumbnail_mime = HC.IMAGE_JPEG
# we don't actually know this, it comes down to detailed stuff, but since this is png vs jpeg it isn't a huge deal down in the guts of image loading
# only really matters with transparency, so anything that can be transparent we'll prime with a png thing
# ain't like I am encoding EXIF rotation in my jpeg thumbs
if mime in ( HC.IMAGE_APNG, HC.IMAGE_PNG, HC.IMAGE_GIF, HC.IMAGE_ICON, HC.IMAGE_WEBP ):
thumbnail_mime = HC.IMAGE_PNG
locations_manager = display_media.GetLocationsManager()
try:
@ -682,6 +673,8 @@ class ThumbnailCache( object ):
try:
thumbnail_mime = HydrusFileHandling.GetThumbnailMime( path )
numpy_image = ClientImageHandling.GenerateNumPyImage( path, thumbnail_mime )
except Exception as e:

View File

@ -268,6 +268,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'booleans' ][ 'start_note_editing_at_end' ] = True
self._dictionary[ 'booleans' ][ 'menu_choice_buttons_can_mouse_scroll' ] = True
self._dictionary[ 'booleans' ][ 'focus_preview_on_ctrl_click' ] = False
self._dictionary[ 'booleans' ][ 'focus_preview_on_ctrl_click_only_static' ] = False
self._dictionary[ 'booleans' ][ 'focus_preview_on_shift_click' ] = False

View File

@ -708,6 +708,7 @@ class ParseFormula( HydrusSerialisable.SerialisableBase ):
if collapse_newlines:
# maybe should use HydrusText.DeserialiseNewlinedTexts, but that might change/break some existing parsers with the strip() trim
raw_texts = [ HydrusText.RemoveNewlines( raw_text ) for raw_text in raw_texts ]

View File

@ -967,7 +967,7 @@ class HydrusBitmap( object ):
if isinstance( data, memoryview ) and not data.c_contiguous:
data = data.copy()
data = data.tobytes() # this _should_ work and is an emergency relief
if self._compressed:

View File

@ -152,7 +152,7 @@ def ConvertSubtagToSearchable( subtag ):
subtag = subtag.translate( IGNORED_TAG_SEARCH_CHARACTERS_UNICODE_TRANSLATE )
subtag = HydrusText.re_multiple_spaces.sub( ' ', subtag )
subtag = HydrusText.re_one_or_more_whitespace.sub( ' ', subtag )
subtag = subtag.strip()

View File

@ -304,7 +304,7 @@ def LoadFromNumPyImage( numpy_image: numpy.array ):
if depth != 1:
numpy_image = numpy_image[:,:,0] # let's fetch one channel. if the png is a perfect RGB conversion of the original (or, let's say, a Firefox bmp export), this actually works
numpy_image = numpy_image[:,:,0].copy() # let's fetch one channel. if the png is a perfect RGB conversion of the original (or, let's say, a Firefox bmp export), this actually works

View File

@ -193,7 +193,10 @@ class Service( object ):
self._LoadFromDictionary( dictionary )
def __hash__( self ): return self._service_key.__hash__()
def __hash__( self ):
return self._service_key.__hash__()
def _CheckFunctional( self ):
@ -1018,7 +1021,7 @@ class ServiceRestricted( ServiceRemote ):
dictionary[ 'account' ] = HydrusNetwork.Account.GenerateSerialisableTupleFromAccount( self._account )
dictionary[ 'next_account_sync' ] = self._next_account_sync
dictionary[ 'network_sync_paused' ] = self._network_sync_paused
dictionary[ 'service_options' ] = self._service_options
dictionary[ 'service_options' ] = HydrusSerialisable.SerialisableDictionary( self._service_options )
return dictionary
@ -1049,12 +1052,7 @@ class ServiceRestricted( ServiceRemote ):
dictionary[ 'service_options' ] = HydrusSerialisable.SerialisableDictionary()
self._service_options = dictionary[ 'service_options' ]
def _SetNewServiceOptions( self, service_options ):
self._service_options.update( service_options )
self._service_options = HydrusSerialisable.SerialisableDictionary( dictionary[ 'service_options' ] )
def _SetNewTagFilter( self, tag_filter: HydrusTags.TagFilter ):
@ -1062,6 +1060,11 @@ class ServiceRestricted( ServiceRemote ):
self._service_options[ 'tag_filter' ] = tag_filter
def _UpdateServiceOptions( self, service_options ):
self._service_options.update( service_options )
def CanSyncAccount( self, including_external_communication = True ):
with self._lock:
@ -1415,7 +1418,7 @@ class ServiceRestricted( ServiceRemote ):
service_options = options_response[ 'service_options' ]
self._SetNewServiceOptions( service_options )
self._UpdateServiceOptions( service_options )
except HydrusExceptions.SerialisationException:
@ -1653,18 +1656,6 @@ class ServiceRepository( ServiceRestricted ):
def _SetNewServiceOptions( self, service_options ):
if 'update_period' in service_options and 'update_period' in self._service_options and service_options[ 'update_period' ] != self._service_options[ 'update_period' ]:
update_period = service_options[ 'update_period' ]
self._metadata.CalculateNewNextUpdateDue( update_period )
ServiceRestricted._SetNewServiceOptions( self, service_options )
def _SyncDownloadMetadata( self ):
with self._lock:
@ -2282,6 +2273,18 @@ class ServiceRepository( ServiceRestricted ):
def _UpdateServiceOptions( self, service_options ):
if 'update_period' in service_options and 'update_period' in self._service_options and service_options[ 'update_period' ] != self._service_options[ 'update_period' ]:
update_period = service_options[ 'update_period' ]
self._metadata.CalculateNewNextUpdateDue( update_period )
ServiceRestricted._UpdateServiceOptions( self, service_options )
def CanDoIdleShutdownWork( self ):
with self._lock:

View File

@ -10713,6 +10713,40 @@ class DB( HydrusDB.HydrusDB ):
if version == 508:
try:
domain_manager = self.modules_serialisable.GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER )
domain_manager.Initialise()
#
domain_manager.RenameGUG( 'twitter syndication collection lookup', 'twitter collection lookup' )
domain_manager.RenameGUG( 'twitter syndication likes lookup', 'twitter likes lookup' )
domain_manager.RenameGUG( 'twitter syndication list lookup', 'twitter list lookup' )
domain_manager.RenameGUG( 'twitter syndication profile lookup', 'twitter profile lookup' )
domain_manager.RenameGUG( 'twitter syndication profile lookup (with replies)', 'twitter profile lookup (with replies)' )
#
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 )
self._controller.frame_splash_status.SetTitleText( 'updated db to v{}'.format( HydrusData.ToHumanInt( version + 1 ) ) )
self._Execute( 'UPDATE version SET version = ?;', ( version + 1, ) )

View File

@ -26,6 +26,7 @@ from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusEncryption
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusImageHandling
from hydrus.core import HydrusPaths
from hydrus.core import HydrusGlobals as HG
@ -755,6 +756,7 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes, CAC.ApplicationCo
library_versions.append( ( 'lxml present: ', str( ClientParsing.LXML_IS_OK ) ) )
library_versions.append( ( 'lz4 present: ', str( HydrusCompression.LZ4_OK ) ) )
library_versions.append( ( 'pyopenssl present:', str( HydrusEncryption.OPENSSL_OK ) ) )
library_versions.append( ( 'speedcopy present:', str( HydrusFileHandling.SPEEDCOPY_OK ) ) )
library_versions.append( ( 'install dir', HC.BASE_DIR ) )
library_versions.append( ( 'db dir', HG.client_controller.db_dir ) )
library_versions.append( ( 'temp dir', HydrusTemp.GetCurrentTempDir() ) )

View File

@ -80,14 +80,12 @@ def AddDuplicatesMenu( win: QW.QWidget, menu: QW.QMenu, location_context: Client
ClientGUIMenus.AppendSeparator( duplicates_menu )
label = 'view this file\'s relations'
if job_location_context is combined_local_location_context:
if len( view_duplicate_relations_jobs ) > 1:
label = '{} ({})'.format( label, HG.client_controller.services_manager.GetName( CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
label = '-for {}-'.format( job_location_context.ToString( HG.client_controller.services_manager.GetName ) )
ClientGUIMenus.AppendMenuLabel( duplicates_menu, label, label )
ClientGUIMenus.AppendMenuLabel( duplicates_menu, label, label )
if HC.DUPLICATE_MEMBER in file_duplicate_types_to_counts:
@ -111,7 +109,7 @@ def AddDuplicatesMenu( win: QW.QWidget, menu: QW.QMenu, location_context: Client
if count > 0:
label = HydrusData.ToHumanInt( count ) + ' ' + HC.duplicate_type_string_lookup[ duplicate_type ]
label = 'view {} {}'.format( HydrusData.ToHumanInt( count ), HC.duplicate_type_string_lookup[ duplicate_type ] )
ClientGUIMenus.AppendMenuItem( duplicates_menu, label, 'Show these duplicates in a new page.', ClientGUIMedia.ShowDuplicatesInNewPage, job_location_context, focused_hash, duplicate_type )

View File

@ -14,6 +14,7 @@ from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusImageHandling
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
from hydrus.client import ClientApplicationCommand as CAC
@ -1226,6 +1227,10 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
tt = 'In many places across the program (typically import status lists), the client will state a timestamp as "5 days ago". If you would prefer a standard ISO string, like "2018-03-01 12:40:23", check this.'
self._always_show_iso_time.setToolTip( tt )
self._menu_choice_buttons_can_mouse_scroll = QW.QCheckBox( self._misc_panel )
tt = 'Many buttons that produce menus when clicked are also "scrollable", so if you wheel your mouse over them, the selection will scroll through the underlying menu. If this is annoying for you, turn it off here!'
self._menu_choice_buttons_can_mouse_scroll.setToolTip( tt )
self._human_bytes_sig_figs = ClientGUICommon.BetterSpinBox( self._misc_panel, min = 1, max = 6 )
self._human_bytes_sig_figs.setToolTip( 'When the program presents a bytes size above 1KB, like 21.3KB or 4.11GB, how many total digits do we want in the number? 2 or 3 is best.')
@ -1265,6 +1270,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._always_show_iso_time.setChecked( self._new_options.GetBoolean( 'always_show_iso_time' ) )
self._menu_choice_buttons_can_mouse_scroll.setChecked( self._new_options.GetBoolean( 'menu_choice_buttons_can_mouse_scroll' ) )
self._human_bytes_sig_figs.setValue( self._new_options.GetInteger( 'human_bytes_sig_figs' ) )
self._discord_dnd_fix.setChecked( self._new_options.GetBoolean( 'discord_dnd_fix' ) )
@ -1303,6 +1310,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
rows = []
rows.append( ( 'Prefer ISO time ("2018-03-01 12:40:23") to "5 days ago": ', self._always_show_iso_time ) )
rows.append( ( 'Mouse wheel can "scroll" through menu buttons: ', self._menu_choice_buttons_can_mouse_scroll ) )
rows.append( ( 'Copy temp files for drag-and-drop (works for <=25, <200MB file DnDs--fixes Discord!): ', self._discord_dnd_fix ) )
rows.append( ( 'Drag-and-drop export filename pattern: ', self._discord_dnd_filename_pattern ) )
rows.append( ( '', self._export_pattern_button ) )
@ -1383,6 +1391,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
HC.options[ 'confirm_client_exit' ] = self._confirm_client_exit.isChecked()
self._new_options.SetBoolean( 'always_show_iso_time', self._always_show_iso_time.isChecked() )
self._new_options.SetBoolean( 'menu_choice_buttons_can_mouse_scroll', self._menu_choice_buttons_can_mouse_scroll.isChecked() )
self._new_options.SetInteger( 'human_bytes_sig_figs', self._human_bytes_sig_figs.value() )
@ -3667,11 +3676,18 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
namespace = dlg.GetValue()
namespace = namespace.lower().strip()
if namespace.endswith( ':' ):
namespace = namespace[:-1]
if namespace != 'system':
namespace = HydrusTags.StripTextOfGumpf( namespace )
if namespace in ( '', ':' ):
QW.QMessageBox.warning( self, 'Not allowed', 'Sorry, that namespace means unnamespaced/default namespaced, which are already listed.' )

View File

@ -1366,7 +1366,7 @@ class EditSubscriptionsPanel( ClientGUIScrolledPanels.EditPanel ):
self._subscriptions_panel.NewButtonRow()
self._subscriptions_panel.AddButton( 'select subscriptions', self.SelectSubscriptions )
self._subscriptions_panel.AddButton( 'overwrite checker timings', self.SetCheckerOptions, enabled_only_on_selection = True )
self._subscriptions_panel.AddButton( 'overwrite checker options', self.SetCheckerOptions, enabled_only_on_selection = True )
self._subscriptions_panel.AddButton( 'overwrite file import options', self.SetFileImportOptions, enabled_only_on_selection = True )
self._subscriptions_panel.AddButton( 'overwrite tag import options', self.SetTagImportOptions, enabled_only_on_selection = True )
self._subscriptions_panel.AddButton( 'overwrite note import options', self.SetNoteImportOptions, enabled_only_on_selection = True )
@ -2817,7 +2817,7 @@ class EditSubscriptionsPanel( ClientGUIScrolledPanels.EditPanel ):
checker_options = subscriptions[0].GetCheckerOptions()
with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit check timings' ) as dlg:
with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit checker options' ) as dlg:
panel = ClientGUITime.EditCheckerOptions( dlg, checker_options )

View File

@ -697,6 +697,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
#
self._redundant_st = ClientGUICommon.BetterStaticText( self, '', ellipsize_end = True )
self._redundant_st.setVisible( False )
self._current_filter_st = ClientGUICommon.BetterStaticText( self, 'currently keeping: ', ellipsize_end = True )
@ -751,6 +752,17 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
test_text_vbox = QP.VBoxLayout()
if self._only_show_blacklist:
message = 'This is a fixed blacklist. It will apply rules against all test tag siblings and apply unnamespaced rules to namespaced test tags.'
st = ClientGUICommon.BetterStaticText( self, message )
st.setWordWrap( True )
QP.AddToLayout( test_text_vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( test_text_vbox, self._test_result_st, CC.FLAGS_EXPAND_PERPENDICULAR )
hbox = QP.HBoxLayout()
@ -799,13 +811,6 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._UpdateStatus()
def _AdvancedAddBlacklistButton( self ):
tag_slice = self._advanced_blacklist_input.GetValue()
self._AdvancedAddBlacklist( tag_slice )
def _AdvancedAddBlacklistMultiple( self, tag_slices ):
for tag_slice in tag_slices:
@ -839,13 +844,6 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._UpdateStatus()
def _AdvancedAddWhitelistButton( self ):
tag_slice = self._advanced_whitelist_input.GetValue()
self._AdvancedAddWhitelist( tag_slice )
def _AdvancedAddWhitelistMultiple( self, tag_slices ):
for tag_slice in tag_slices:
@ -865,7 +863,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._UpdateStatus()
def _AdvancedDeleteBlacklist( self ):
def _AdvancedDeleteBlacklistButton( self ):
selected_tag_slices = self._advanced_blacklist.GetSelectedTagSlices()
@ -882,7 +880,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._UpdateStatus()
def _AdvancedDeleteWhitelist( self ):
def _AdvancedDeleteWhitelistButton( self ):
selected_tag_slices = self._advanced_whitelist.GetSelectedTagSlices()
@ -1100,68 +1098,62 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
#
blacklist_panel = ClientGUICommon.StaticBox( advanced_panel, 'exclude these' )
self._advanced_blacklist_panel = ClientGUICommon.StaticBox( advanced_panel, 'exclude these' )
self._advanced_blacklist = ClientGUIListBoxes.ListBoxTagsFilter( blacklist_panel, read_only = self._read_only )
self._advanced_blacklist = ClientGUIListBoxes.ListBoxTagsFilter( self._advanced_blacklist_panel, read_only = self._read_only )
self._advanced_blacklist_input = ClientGUIControls.TextAndPasteCtrl( blacklist_panel, self._AdvancedAddBlacklistMultiple, allow_empty_input = True )
self._advanced_blacklist_input = ClientGUIControls.TextAndPasteCtrl( self._advanced_blacklist_panel, self._AdvancedAddBlacklistMultiple, allow_empty_input = True )
add_blacklist_button = ClientGUICommon.BetterButton( blacklist_panel, 'add', self._AdvancedAddBlacklistButton )
delete_blacklist_button = ClientGUICommon.BetterButton( blacklist_panel, 'delete', self._AdvancedDeleteBlacklist )
blacklist_everything_button = ClientGUICommon.BetterButton( blacklist_panel, 'block everything', self._AdvancedBlacklistEverything )
delete_blacklist_button = ClientGUICommon.BetterButton( self._advanced_blacklist_panel, 'delete', self._AdvancedDeleteBlacklistButton )
blacklist_everything_button = ClientGUICommon.BetterButton( self._advanced_blacklist_panel, 'block everything', self._AdvancedBlacklistEverything )
#
whitelist_panel = ClientGUICommon.StaticBox( advanced_panel, 'except for these' )
self._advanced_whitelist_panel = ClientGUICommon.StaticBox( advanced_panel, 'except for these' )
self._advanced_whitelist = ClientGUIListBoxes.ListBoxTagsFilter( whitelist_panel, read_only = self._read_only )
self._advanced_whitelist = ClientGUIListBoxes.ListBoxTagsFilter( self._advanced_whitelist_panel, read_only = self._read_only )
self._advanced_whitelist_input = ClientGUIControls.TextAndPasteCtrl( whitelist_panel, self._AdvancedAddWhitelistMultiple, allow_empty_input = True )
self._advanced_whitelist_input = ClientGUIControls.TextAndPasteCtrl( self._advanced_whitelist_panel, self._AdvancedAddWhitelistMultiple, allow_empty_input = True )
self._advanced_add_whitelist_button = ClientGUICommon.BetterButton( whitelist_panel, 'add', self._AdvancedAddWhitelistButton )
delete_whitelist_button = ClientGUICommon.BetterButton( whitelist_panel, 'delete', self._AdvancedDeleteWhitelist )
delete_whitelist_button = ClientGUICommon.BetterButton( self._advanced_whitelist_panel, 'delete', self._AdvancedDeleteWhitelistButton )
#
if self._read_only:
self._advanced_blacklist_input.hide()
add_blacklist_button.hide()
delete_blacklist_button.hide()
blacklist_everything_button.hide()
self._advanced_whitelist_input.hide()
self._advanced_add_whitelist_button.hide()
delete_whitelist_button.hide()
button_hbox = QP.HBoxLayout()
QP.AddToLayout( button_hbox, self._advanced_blacklist_input, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( button_hbox, add_blacklist_button, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( button_hbox, delete_blacklist_button, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( button_hbox, blacklist_everything_button, CC.FLAGS_CENTER_PERPENDICULAR )
blacklist_panel.Add( self._advanced_blacklist, CC.FLAGS_EXPAND_BOTH_WAYS )
blacklist_panel.Add( button_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self._advanced_blacklist_panel.Add( self._advanced_blacklist, CC.FLAGS_EXPAND_BOTH_WAYS )
self._advanced_blacklist_panel.Add( button_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
#
button_hbox = QP.HBoxLayout()
QP.AddToLayout( button_hbox, self._advanced_whitelist_input, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( button_hbox, self._advanced_add_whitelist_button, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( button_hbox, delete_whitelist_button, CC.FLAGS_CENTER_PERPENDICULAR )
whitelist_panel.Add( self._advanced_whitelist, CC.FLAGS_EXPAND_BOTH_WAYS )
whitelist_panel.Add( button_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self._advanced_whitelist_panel.Add( self._advanced_whitelist, CC.FLAGS_EXPAND_BOTH_WAYS )
self._advanced_whitelist_panel.Add( button_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
#
hbox = QP.HBoxLayout()
QP.AddToLayout( hbox, blacklist_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, whitelist_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, self._advanced_blacklist_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, self._advanced_whitelist_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
advanced_panel.setLayout( hbox )
@ -1175,11 +1167,16 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
#
self._simple_blacklist_error_st = ClientGUICommon.BetterStaticText( blacklist_panel )
self._simple_blacklist_error_st.setVisible( False )
self._simple_blacklist_global_checkboxes = ClientGUICommon.BetterCheckBoxList( blacklist_panel )
self._simple_blacklist_global_checkboxes.Append( 'unnamespaced tags', '' )
self._simple_blacklist_global_checkboxes.Append( 'namespaced tags', ':' )
( w, h ) = ClientGUIFunctions.ConvertTextToPixels( self._simple_blacklist_global_checkboxes, ( 20, 3 ) )
self._simple_blacklist_global_checkboxes.setFixedHeight( h )
self._simple_blacklist_namespace_checkboxes = ClientGUICommon.BetterCheckBoxList( blacklist_panel )
@ -1197,6 +1194,9 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._simple_blacklist_input = ClientGUIControls.TextAndPasteCtrl( blacklist_panel, self._SimpleAddBlacklistMultiple, allow_empty_input = True )
delete_blacklist_button = ClientGUICommon.BetterButton( blacklist_panel, 'remove', self._SimpleDeleteBlacklistButton )
blacklist_everything_button = ClientGUICommon.BetterButton( blacklist_panel, 'block everything', self._AdvancedBlacklistEverything )
#
if self._read_only:
@ -1206,17 +1206,25 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._simple_blacklist_input.hide()
delete_blacklist_button.hide()
blacklist_everything_button.hide()
left_vbox = QP.VBoxLayout()
QP.AddToLayout( left_vbox, self._simple_blacklist_global_checkboxes, CC.FLAGS_EXPAND_PERPENDICULAR )
left_vbox.addStretch( 1 )
QP.AddToLayout( left_vbox, self._simple_blacklist_namespace_checkboxes, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( left_vbox, self._simple_blacklist_namespace_checkboxes, CC.FLAGS_EXPAND_BOTH_WAYS )
button_hbox = QP.HBoxLayout()
QP.AddToLayout( button_hbox, self._simple_blacklist_input, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( button_hbox, delete_blacklist_button, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( button_hbox, blacklist_everything_button, CC.FLAGS_CENTER_PERPENDICULAR )
right_vbox = QP.VBoxLayout()
QP.AddToLayout( right_vbox, self._simple_blacklist, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( right_vbox, self._simple_blacklist_input, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( right_vbox, button_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
main_hbox = QP.HBoxLayout()
@ -1242,12 +1250,17 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
#
self._simple_whitelist_error_st = ClientGUICommon.BetterStaticText( whitelist_panel )
self._simple_whitelist_error_st.setVisible( False )
self._simple_whitelist_global_checkboxes = ClientGUICommon.BetterCheckBoxList( whitelist_panel )
self._simple_whitelist_global_checkboxes.Append( 'unnamespaced tags', '' )
self._simple_whitelist_global_checkboxes.Append( 'namespaced tags', ':' )
( w, h ) = ClientGUIFunctions.ConvertTextToPixels( self._simple_whitelist_global_checkboxes, ( 20, 3 ) )
self._simple_whitelist_global_checkboxes.setFixedHeight( h )
self._simple_whitelist_namespace_checkboxes = ClientGUICommon.BetterCheckBoxList( whitelist_panel )
for namespace in self._namespaces:
@ -1264,6 +1277,8 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._simple_whitelist_input = ClientGUIControls.TextAndPasteCtrl( whitelist_panel, self._SimpleAddWhitelistMultiple, allow_empty_input = True )
delete_whitelist_button = ClientGUICommon.BetterButton( whitelist_panel, 'remove', self._SimpleDeleteWhitelistButton )
#
if self._read_only:
@ -1272,19 +1287,23 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._simple_whitelist_namespace_checkboxes.setEnabled( False )
self._simple_whitelist_input.hide()
delete_whitelist_button.hide()
left_vbox = QP.VBoxLayout()
QP.AddToLayout( left_vbox, self._simple_whitelist_global_checkboxes, CC.FLAGS_EXPAND_PERPENDICULAR )
left_vbox.addStretch( 1 )
QP.AddToLayout( left_vbox, self._simple_whitelist_namespace_checkboxes, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( left_vbox, self._simple_whitelist_namespace_checkboxes, CC.FLAGS_EXPAND_BOTH_WAYS )
button_hbox = QP.HBoxLayout()
QP.AddToLayout( button_hbox, self._simple_whitelist_input, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( button_hbox, delete_whitelist_button, CC.FLAGS_CENTER_PERPENDICULAR )
right_vbox = QP.VBoxLayout()
QP.AddToLayout( right_vbox, self._simple_whitelist, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( right_vbox, self._simple_whitelist_input, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( right_vbox, button_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
main_hbox = QP.HBoxLayout()
@ -1386,17 +1405,16 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
def _ShowRedundantError( self, text ):
self._redundant_st.setVisible( True )
self._redundant_st.setText( text )
HG.client_controller.CallLaterQtSafe( self._redundant_st, 2, 'clear redundant error', self._redundant_st.setText, '' )
HG.client_controller.CallLaterQtSafe( self._redundant_st, 2, 'clear redundant error', self._redundant_st.setVisible, False )
def _SimpleAddBlacklistMultiple( self, tag_slices ):
for tag_slice in tag_slices:
self._AdvancedAddBlacklist( tag_slice )
self._AdvancedAddBlacklistMultiple( tag_slices )
def _SimpleAddWhitelistMultiple( self, tag_slices ):
@ -1427,6 +1445,44 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
pass
def _SimpleDeleteBlacklistButton( self ):
selected_tag_slices = self._simple_blacklist.GetSelectedTagSlices()
if len( selected_tag_slices ) > 0:
result = ClientGUIDialogsQuick.GetYesNo( self, 'Remove all selected?' )
if result == QW.QDialog.Accepted:
self._simple_blacklist.RemoveTagSlices( selected_tag_slices )
self._simple_blacklist.tagsRemoved.emit( selected_tag_slices )
self._UpdateStatus()
def _SimpleDeleteWhitelistButton( self ):
selected_tag_slices = self._simple_whitelist.GetSelectedTagSlices()
if len( selected_tag_slices ) > 0:
result = ClientGUIDialogsQuick.GetYesNo( self, 'Remove all selected?' )
if result == QW.QDialog.Accepted:
self._simple_whitelist.RemoveTagSlices( selected_tag_slices )
self._simple_whitelist.tagsRemoved.emit( selected_tag_slices )
self._UpdateStatus()
def _SimpleWhitelistRemoved( self, tag_slices ):
tag_slices = set( tag_slices )
@ -1457,16 +1513,13 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
( whitelist_possible, blacklist_possible ) = self._GetWhiteBlacklistsPossible()
whitelist_tag_slices = self._advanced_whitelist.GetTagSlices()
blacklist_tag_slices = self._advanced_blacklist.GetTagSlices()
self._whitelist_panel.setEnabled( whitelist_possible )
self._simple_whitelist_error_st.setVisible( not whitelist_possible )
if whitelist_possible:
self._simple_whitelist_error_st.clear()
self._simple_whitelist.setEnabled( True )
self._simple_whitelist_global_checkboxes.setEnabled( True )
self._simple_whitelist_input.setEnabled( True )
whitelist_tag_slices = set( whitelist_tag_slices )
if not self._CurrentlyBlocked( '' ):
@ -1503,12 +1556,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
else:
self._simple_whitelist_error_st.setText( 'The filter is currently more complicated than a simple whitelist, so cannot be shown here.' )
self._simple_whitelist.setEnabled( False )
self._simple_whitelist_global_checkboxes.setEnabled( False )
self._simple_whitelist_namespace_checkboxes.setEnabled( False )
self._simple_whitelist_input.setEnabled( False )
self._simple_whitelist_error_st.setText( 'The filter is currently more complicated than a simple whitelist, so it cannot be shown here.' )
self._simple_whitelist.SetTagSlices( '' )
@ -1525,17 +1573,14 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
#
whitelist_tag_slices = self._advanced_whitelist.GetTagSlices()
blacklist_tag_slices = self._advanced_blacklist.GetTagSlices()
self._blacklist_panel.setEnabled( blacklist_possible )
self._simple_blacklist_error_st.setVisible( not blacklist_possible )
if blacklist_possible:
self._simple_blacklist_error_st.clear()
self._simple_blacklist.setEnabled( True )
self._simple_blacklist_global_checkboxes.setEnabled( True )
self._simple_blacklist_input.setEnabled( True )
if self._CurrentlyBlocked( ':' ):
self._simple_blacklist_namespace_checkboxes.setEnabled( False )
@ -1563,12 +1608,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
else:
self._simple_blacklist_error_st.setText( 'The filter is currently more complicated than a simple blacklist, so cannot be shown here.' )
self._simple_blacklist.setEnabled( False )
self._simple_blacklist_global_checkboxes.setEnabled( False )
self._simple_blacklist_namespace_checkboxes.setEnabled( False )
self._simple_blacklist_input.setEnabled( False )
self._simple_blacklist_error_st.setText( 'The filter is currently more complicated than a simple blacklist, so it cannot be shown here.' )
self._simple_blacklist.SetTagSlices( '' )
@ -1588,16 +1628,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
whitelist_tag_slices = self._advanced_whitelist.GetTagSlices()
blacklist_tag_slices = self._advanced_blacklist.GetTagSlices()
if len( blacklist_tag_slices ) == 0:
self._advanced_whitelist_input.setEnabled( False )
self._advanced_add_whitelist_button.setEnabled( False )
else:
self._advanced_whitelist_input.setEnabled( True )
self._advanced_add_whitelist_button.setEnabled( True )
self._advanced_whitelist_input.setEnabled( len( blacklist_tag_slices ) > 0 )
#
@ -1658,9 +1689,9 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
tags_to_siblings = HG.client_controller.Read( 'tag_siblings_lookup', CC.COMBINED_TAG_SERVICE_KEY, test_tags )
for ( test_tag, siblings ) in tags_to_siblings.items():
for test_tag_and_siblings in tags_to_siblings.values():
results.append( False not in ( tag_filter.TagOK( sibling_tag, apply_unnamespaced_rules_to_namespaced_tags = True ) for sibling_tag in siblings ) )
results.append( False not in ( tag_filter.TagOK( t, apply_unnamespaced_rules_to_namespaced_tags = True ) for t in test_tag_and_siblings ) )
return results

View File

@ -61,15 +61,44 @@ if 'QT_API' in os.environ:
else:
from hydrus.core import HydrusConstants as HC
if HC.RUNNING_FROM_MACOS_APP:
os.environ[ 'FORCE_QT_API' ] = '1'
QT_API_INITIAL_VALUE = None
try:
if 'QT_API' not in os.environ:
import PySide6 # Qt6
try:
import PySide6 # Qt6
os.environ[ 'QT_API' ] = 'pyside6'
except ImportError as e:
pass
os.environ[ 'QT_API' ] = 'pyside6'
if 'QT_API' not in os.environ:
except ImportError as e:
try:
import PyQt6 # Qt6
os.environ[ 'QT_API' ] = 'pyqt6'
except ImportError as e:
pass
if 'QT_API' not in os.environ:
try:
@ -83,6 +112,20 @@ else:
if 'QT_API' not in os.environ:
try:
import PyQt5 # Qt5
os.environ[ 'QT_API' ] = 'pyqt5'
except ImportError as e:
pass
def get_qt_api_str_status():
@ -106,7 +149,9 @@ def get_qt_api_str_status():
current_qt = 'Currently QT_API is not set.'
return '{} {}'.format( initial_qt, current_qt )
forced_qt = 'FORCE_QT_API is ON.' if 'FORCE_QT_API' in os.environ else 'FORCE_QT_API is not set.'
return '{} {} {}'.format( initial_qt, current_qt, forced_qt )
except Exception as e:
@ -173,7 +218,7 @@ except ModuleNotFoundError as e:
message += '\n' * 2
message += 'Here is info on your available Qt Libraries:\n{}'.format( get_qt_library_str_status() )
message += 'Here is info on your available Qt Libraries:\n\n{}'.format( get_qt_library_str_status() )
message += '\n' * 2

View File

@ -7,6 +7,7 @@ from qtpy import QtGui as QG
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusFileHandling
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusImageHandling
from hydrus.core import HydrusPaths
@ -2664,11 +2665,11 @@ class EmbedButton( QW.QWidget ):
if needs_thumb:
mime = self._media.GetMime()
thumbnail_path = HG.client_controller.client_files_manager.GetThumbnailPath( self._media )
self._thumbnail_qt_pixmap = ClientRendering.GenerateHydrusBitmap( thumbnail_path, mime ).GetQtPixmap()
thumbnail_mime = HydrusFileHandling.GetThumbnailMime( thumbnail_path )
self._thumbnail_qt_pixmap = ClientRendering.GenerateHydrusBitmap( thumbnail_path, thumbnail_mime ).GetQtPixmap()
self.update()
@ -2692,11 +2693,11 @@ class OpenExternallyPanel( QW.QWidget ):
if self._media.GetLocationsManager().IsLocal() and self._media.GetMime() in HC.MIMES_WITH_THUMBNAILS:
mime = self._media.GetMime()
thumbnail_path = HG.client_controller.client_files_manager.GetThumbnailPath( self._media )
qt_pixmap = ClientRendering.GenerateHydrusBitmap( thumbnail_path, mime ).GetQtPixmap()
thumbnail_mime = HydrusFileHandling.GetThumbnailMime( thumbnail_path )
qt_pixmap = ClientRendering.GenerateHydrusBitmap( thumbnail_path, thumbnail_mime ).GetQtPixmap()
thumbnail_window = ClientGUICommon.BufferedWindowIcon( self, qt_pixmap )

View File

@ -4,7 +4,7 @@ import typing
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusExceptions
from hydrus.client import ClientConstants as CC
@ -229,7 +229,9 @@ class MenuChoiceButton( MenuMixin, ClientGUICommon.BetterButton ):
def wheelEvent( self, event ):
if self._value_index is not None:
can_do_it = HG.client_controller.new_options.GetBoolean( 'menu_choice_buttons_can_mouse_scroll' )
if self._value_index is not None and can_do_it:
if event.angleDelta().y() > 0:

View File

@ -80,7 +80,7 @@ options = {}
# Misc
NETWORK_VERSION = 20
SOFTWARE_VERSION = 508
SOFTWARE_VERSION = 509
CLIENT_API_VERSION = 37
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -17,6 +17,25 @@ from hydrus.core import HydrusText
from hydrus.core import HydrusVideoHandling
from hydrus.core.networking import HydrusNetwork
try:
import speedcopy
speedcopy.patch_copyfile()
SPEEDCOPY_OK = True
except Exception as e:
if not isinstance( e, ImportError ):
HydrusData.Print( 'Failed to initialise speedcopy:' )
HydrusData.PrintException( e )
SPEEDCOPY_OK = False
# Mime
headers_and_mime_thumbnails = [
@ -80,19 +99,19 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
elif mime == HC.APPLICATION_PSD:
( os_file_handle, temp_path ) = HydrusTemp.GetTempPath( suffix = '.png' )
( os_file_handle, temp_path ) = HydrusTemp.GetTempPath( suffix = '.jpeg' )
try:
HydrusVideoHandling.RenderImageToPNGPath( path, temp_path )
HydrusVideoHandling.RenderImageToImagePath( path, temp_path )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, mime, clip_rect = clip_rect )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_JPEG, clip_rect = clip_rect )
except:
thumb_path = os.path.join( HC.STATIC_DIR, 'psd.png' )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, mime, clip_rect = clip_rect )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
finally:
@ -107,13 +126,13 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
HydrusClipHandling.ExtractDBPNGToPath( path, temp_path )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, mime, clip_rect = clip_rect )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
except:
thumb_path = os.path.join( HC.STATIC_DIR, 'clip.png' )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, mime, clip_rect = clip_rect )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
finally:
@ -128,13 +147,13 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
HydrusFlashHandling.RenderPageToFile( path, temp_path, 1 )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, mime, clip_rect = clip_rect )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( temp_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
except:
thumb_path = os.path.join( HC.STATIC_DIR, 'flash.png' )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, mime, clip_rect = clip_rect )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesFromStaticImagePath( thumb_path, target_resolution, HC.IMAGE_PNG, clip_rect = clip_rect )
finally:
@ -160,7 +179,7 @@ def GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames,
numpy_image = HydrusImageHandling.ResizeNumPyImage( numpy_image, target_resolution ) # just in case ffmpeg doesn't deliver right
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesNumPy( numpy_image, mime )
thumbnail_bytes = HydrusImageHandling.GenerateThumbnailBytesNumPy( numpy_image )
renderer.Stop()

View File

@ -345,12 +345,7 @@ def GenerateNumPyImage( path, mime, force_pil = False ) -> numpy.array:
if NumPyImageHasOpaqueAlphaChannel( numpy_image ):
convert = cv2.COLOR_RGBA2RGB
numpy_image = cv2.cvtColor( numpy_image, convert )
numpy_image = StripOutAnyOpaqueAlphaChannel( numpy_image )
return numpy_image
@ -441,7 +436,7 @@ def GenerateThumbnailBytesFromStaticImagePath( path, target_resolution, mime, cl
try:
thumbnail_bytes = GenerateThumbnailBytesNumPy( thumbnail_numpy_image, mime )
thumbnail_bytes = GenerateThumbnailBytesNumPy( thumbnail_numpy_image )
return thumbnail_bytes
@ -460,24 +455,19 @@ def GenerateThumbnailBytesFromStaticImagePath( path, target_resolution, mime, cl
thumbnail_pil_image = pil_image.resize( target_resolution, PILImage.ANTIALIAS )
thumbnail_bytes = GenerateThumbnailBytesPIL( pil_image, mime )
thumbnail_bytes = GenerateThumbnailBytesPIL( thumbnail_pil_image )
return thumbnail_bytes
def GenerateThumbnailBytesNumPy( numpy_image, mime ) -> bytes:
def GenerateThumbnailBytesNumPy( numpy_image ) -> bytes:
( im_height, im_width, depth ) = numpy_image.shape
numpy_image = StripOutAnyOpaqueAlphaChannel( numpy_image )
if depth == 4:
if NumPyImageHasOpaqueAlphaChannel( numpy_image ):
convert = cv2.COLOR_RGBA2BGR
else:
convert = cv2.COLOR_RGBA2BGRA
convert = cv2.COLOR_RGBA2BGRA
else:
@ -514,11 +504,11 @@ def GenerateThumbnailBytesNumPy( numpy_image, mime ) -> bytes:
raise HydrusExceptions.CantRenderWithCVException( 'Thumb failed to encode!' )
def GenerateThumbnailBytesPIL( pil_image: PILImage.Image, mime ) -> bytes:
def GenerateThumbnailBytesPIL( pil_image: PILImage.Image ) -> bytes:
f = io.BytesIO()
if mime == HC.IMAGE_PNG or pil_image.mode == 'RGBA':
if PILImageHasAlpha( pil_image ):
pil_image.save( f, 'PNG' )
@ -1122,7 +1112,7 @@ def NumPyImageHasOpaqueAlphaChannel( numpy_image: numpy.array ) -> bool:
# if the alpha channel is all opaque, there is no use storing that info in our pixel hash
# opaque means 255
alpha_channel = numpy_image[:,:,3]
alpha_channel = numpy_image[:,:,3].copy()
if ( alpha_channel == numpy.full( ( shape[0], shape[1] ), 255, dtype = 'uint8' ) ).all():
@ -1234,3 +1224,19 @@ def RotateEXIFPILImage( pil_image: PILImage.Image )-> PILImage.Image:
return pil_image
def StripOutAnyOpaqueAlphaChannel( numpy_image: numpy.array ) -> numpy.array:
if NumPyImageHasOpaqueAlphaChannel( numpy_image ):
numpy_image = numpy_image[:,:,:3].copy()
# old way, which doesn't actually remove the channel lmao lmao lmao
'''
convert = cv2.COLOR_RGBA2RGB
numpy_image = cv2.cvtColor( numpy_image, convert )
'''
return numpy_image

View File

@ -348,6 +348,17 @@ class SerialisableDictionary( SerialisableBase, dict ):
for ( key, value ) in self.items():
# after being caught out on a recursive legacy thing here, we now coerce. I'm 99.7% confident it can't hurt
# careful not to do it through for SerialisableBase--don't want to coerce a SerialisableBytesDict to a SerialisableDict
if isinstance( value, list ) and not isinstance( value, SerialisableBase ):
value = SerialisableList( value )
elif isinstance( value, dict ) and not isinstance( value, SerialisableBase ):
value = SerialisableDictionary( value )
if isinstance( key, SerialisableBase ):
serialisable_key = key.GetSerialisableTuple()
@ -551,7 +562,7 @@ class SerialisableList( SerialisableBase, list ):
SERIALISABLE_TYPE = SERIALISABLE_TYPE_LIST
SERIALISABLE_NAME = 'Serialisable List'
SERIALISABLE_VERSION = 1
SERIALISABLE_VERSION = 2
def __init__( self, *args, **kwargs ):
@ -561,34 +572,90 @@ class SerialisableList( SerialisableBase, list ):
def _GetSerialisableInfo( self ):
return [ obj.GetSerialisableTuple() for obj in self ]
serialisable_list_result = []
for obj in self:
# after being caught out on a recursive legacy thing here, we now coerce. I'm 99.7% confident it can't hurt
# careful not to do it through for SerialisableBase--don't want to coerce a SerialisableBytesDict to a SerialisableDict
if isinstance( obj, list ) and not isinstance( obj, SerialisableBase ):
obj = SerialisableList( obj )
elif isinstance( obj, dict ) and not isinstance( obj, SerialisableBase ):
obj = SerialisableDictionary( obj )
if isinstance( obj, SerialisableBase ):
is_serialised = True
obj_data = obj.GetSerialisableTuple()
else:
is_serialised = False
obj_data = obj
serialisable_list_result.append( ( is_serialised, obj_data ) )
return serialisable_list_result
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
have_shown_load_error = False
for obj_tuple in serialisable_info:
for ( is_serialised, obj_data ) in serialisable_info:
try:
if is_serialised:
obj = CreateFromSerialisableTuple( obj_tuple )
obj_tuple = obj_data
except HydrusExceptions.SerialisationException as e:
if not have_shown_load_error:
try:
HydrusData.ShowText( 'An object in a list could not load. It has been discarded from the list. More may also have failed to load, but to stop error spam, they will go silently. Your client may be running on code versions behind its database. Depending on the severity of this error, you may need to rollback to a previous backup. If you have no backup, you may want to kill your hydrus process now to stop the cleansed list being saved back to the db.' )
HydrusData.ShowException( e )
obj = CreateFromSerialisableTuple( obj_tuple )
have_shown_load_error = True
except HydrusExceptions.SerialisationException as e:
if not have_shown_load_error:
HydrusData.ShowText( 'An object in a list could not load. It has been discarded from the list. More may also have failed to load, but to stop error spam, they will go silently. Your client may be running on code versions behind its database. Depending on the severity of this error, you may need to rollback to a previous backup. If you have no backup, you may want to kill your hydrus process now to stop the cleansed list being saved back to the db.' )
HydrusData.ShowException( e )
have_shown_load_error = True
continue
continue
else:
obj = obj_data
self.append( obj )
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
serialised_objects = old_serialisable_info
is_serialised = True
new_serialisable_info = [ ( is_serialised, serialised_object ) for serialised_object in serialised_objects ]
return ( 2, new_serialisable_info )
SERIALISABLE_TYPES_TO_OBJECT_TYPES[ SERIALISABLE_TYPE_LIST ] = SerialisableList

View File

@ -307,23 +307,30 @@ def SplitTag( tag ):
return ( '', tag )
NULL_CHARACTER = '\x00'
HANGUL_FILLER_CHARACTER = '\u3164'
def StripTextOfGumpf( t ):
t = HydrusText.re_newlines.sub( '', t )
t = HydrusText.re_multiple_spaces.sub( ' ', t )
t = HydrusText.re_one_or_more_whitespace.sub( ' ', t )
t = t.strip()
t = HydrusText.re_leading_space_or_garbage.sub( '', t )
t = HydrusText.re_leading_garbage.sub( '', t )
t = t.strip()
if NULL_CHARACTER in t:
t = t.replace( NULL_CHARACTER, '' )
if t == HANGUL_FILLER_CHARACTER:
t = ''
return t
def TagOK( t ):

View File

@ -14,10 +14,9 @@ import re
from hydrus.core import HydrusExceptions
re_newlines = re.compile( '[\r\n]+' )
re_multiple_spaces = re.compile( r'\s+' )
re_one_or_more_whitespace = re.compile( r'\s+' ) # this does \t and friends too
# want to keep the 'leading space' part here, despite tag.strip() elsewhere, in case of some crazy '- test' tag
re_leading_space_or_garbage = re.compile( r'^(\s|-|system:)+' )
re_leading_garbage = re.compile( r'^(-|system:)+' )
re_leading_single_colon = re.compile( '^:(?!:)' )
re_leading_byte_order_mark = re.compile( '^\ufeff' ) # unicode .txt files prepend with this, wew
@ -254,9 +253,9 @@ def NonFailingUnicodeDecode( data, encoding ):
return ( text, encoding )
def RemoveNewlines( text ):
def RemoveNewlines( text: str ) -> str:
text = re.sub( r'[\r\n]', '', text )
text = ''.join( text.splitlines() )
return text

View File

@ -591,10 +591,22 @@ def HasVideoStream( path ) -> bool:
return ParseFFMPEGHasVideo( lines )
def RenderImageToPNGPath( path, temp_png_path ):
def RenderImageToImagePath( path, temp_image_path ):
# this should auto-convert if you give it .png or .jpg
# I noticed that ffmpeg got confused about alpha channels with PSDs, gave fully opaque alpha over an otherwise perfectly acceptable catgirl image, so we'll do jpeg for PSD from now on
# -y to overwrite the temp path
cmd = [ FFMPEG_PATH, '-y', "-i", path, temp_png_path ]
if temp_image_path.endswith( '.jpeg' ):
# '-q:v 1' does high quality
cmd = [ FFMPEG_PATH, '-y', "-i", path, "-q:v", "1", temp_image_path ]
else:
cmd = [ FFMPEG_PATH, '-y', "-i", path, temp_image_path ]
sbp_kwargs = HydrusData.GetSubprocessKWArgs()

View File

@ -2682,7 +2682,7 @@ class ServerServiceRestricted( ServerService ):
dictionary[ 'service_options' ] = HydrusSerialisable.SerialisableDictionary()
self._service_options = dictionary[ 'service_options' ]
self._service_options = HydrusSerialisable.SerialisableDictionary( dictionary[ 'service_options' ] )
if 'server_message' not in self._service_options:

View File

@ -45,10 +45,11 @@ goto :parse_fail
ECHO:
ECHO If you are on Windows ^<=8.1, choose 5.
SET /P qt=Do you want Qt(5) or Qt(6)?
SET /P qt=Do you want Qt(5), Qt(6), or (t)est?
IF "%qt%" == "5" goto :question_mpv
IF "%qt%" == "6" goto :question_mpv
IF "%qt%" == "t" goto :question_mpv
goto :parse_fail
:question_mpv
@ -103,6 +104,7 @@ IF "%install_type%" == "s" (
IF "%qt%" == "5" python -m pip install -r static\requirements\advanced\requirements_qt5.txt
IF "%qt%" == "6" python -m pip install -r static\requirements\advanced\requirements_qt6.txt
IF "%qt%" == "t" python -m pip install -r static\requirements\advanced\requirements_qt6_test.txt
IF "%mpv%" == "o" python -m pip install -r static\requirements\advanced\requirements_old_mpv.txt
IF "%mpv%" == "n" python -m pip install -r static\requirements\advanced\requirements_new_mpv.txt

View File

@ -35,12 +35,14 @@ if [ $install_type = "s" ]; then
elif [ $install_type = "a" ]; then
echo
echo "If you are <= 10.13 (High Sierra), choose 5."
echo "Do you want Qt(5) or Qt(6)? "
echo "Do you want Qt(5), Qt(6), or (t)est? "
read qt
if [ $qt = "5" ]; then
:
elif [ $qt = "6" ]; then
:
elif [ $qt = "t" ]; then
:
else
echo "Sorry, did not understand that input!"
popd
@ -104,6 +106,8 @@ elif [ $install_type = "a" ]; 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 = "t" ]; then
python -m pip install -r static/requirements/advanced/requirements_qt6_test.txt
fi
if [ $mpv = "o" ]; then

View File

@ -35,12 +35,14 @@ if [ $install_type = "s" ]; then
elif [ $install_type = "a" ]; then
echo
echo "If you are <=Ubuntu 18.04 or equivalent, choose 5."
echo "Do you want Qt(5) or Qt(6)? "
echo "Do you want Qt(5), Qt(6), or (t)est? "
read qt
if [ $qt = "5" ]; then
:
elif [ $qt = "6" ]; then
:
elif [ $qt = "t" ]; then
:
else
echo "Sorry, did not understand that input!"
exit 1
@ -103,6 +105,8 @@ elif [ $install_type = "a" ]; 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 = "t" ]; then
python -m pip install -r static/requirements/advanced/requirements_qt6_test.txt
fi
if [ $mpv = "o" ]; then

View File

@ -26,4 +26,5 @@ QtPy==2.2.1
requests==2.28.1
setuptools==65.4.1
PySide6==6.3.2
zope==5.5.0
PyQt6==6.3.1

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,2 @@
QtPy==2.3.0
PySide6==6.4.1