Version 561

closes #1514
This commit is contained in:
Hydrus Network Developer 2024-02-07 15:22:05 -06:00
parent 380b0fd520
commit 09daba5aec
No known key found for this signature in database
GPG Key ID: 76249F053212133C
38 changed files with 1133 additions and 439 deletions

View File

@ -7,6 +7,49 @@ title: Changelog
!!! note
This is the new changelog, only the most recent builds. For all versions, see the [old changelog](old_changelog.html).
## [Version 561](https://github.com/hydrusnetwork/hydrus/releases/tag/v561)
### rearranging thumbnails
* on the thumbnail menu, there is a new 'move' submenu. you can move the current selection of files to the start or end of the media list, or to one before or after the earliest selected file, or to the file you right-clicked on to create the menu, or to the first file's position if the selection is not contiguous. if the selection is non-contiguous, it will be made so in the move
* added these rearrange commands to the shortcuts system, as 'move thumbnails' under the 'thumbnails' set. I wasn't sure whether to add some default shortcuts, like ctrl+numpad 7/3/4/6 for home/end/left/right or something--let me know what you think
### misc
* thanks to user help, fixed a stupid typo from last week that caused some bad errors (including crashes, in some cases) when doing non-simple duplicate filtering (issue #1514). this is the issue the v560a hotfix was made for
* fixed another stupid content update typo that was causing 'already in db' results to not get metadata updates
* as a hardcoded shortcut, Ctrl+C or Ctrl+Insert now copies the currently selected tags in any taglist. it'll output the full tag/predicate text, with namespace, no counts
* I've shortened some thumbnail/media-viewer menu labels, made the 'delete' line into a submenu, and ensured the top info line is always a short variant, with detailed info bumped off to the submenu off the top line. I hate how these menus are often super-wide and thus a pain to navigate to the submenus, so let me know what situations still make them wide
* the file log arrow button menu now has entries for 'delete already in db' and 'delete everything'
* the 'add these tags to the favourites list?' yes/no now only fires if you try to add more than five tags ot once
* the various dialogs in the client that auto-yes or auto-no now show a live countdown in their title string
* the window position saving system is now stricter about what it records. maximised and fullscreen state is only saved if 'remember size' is false, and the last size/position is not saved at all if 'remember size/position' is false (previously, it would save these values but not restore them, but let's try being more precise here)
* fixed a 'omg what happened, closing the window now' error in the duplicate filter if you try to 'go back' while it is loading a new set of pairs to show
* fixed the 'vacuum db' command to correctly save 'last vacuumed time' for all files vacuumed in a job, not just the last!
* whenever a `copy2` file copy (which includes copying file times and permission bits) fails for permission reasons, hydrus now falls back to a normal `copy` and logs the failure, including the modified time that failed to copy (which is the bit we actually care about here)
### db update stuff
* if there is a known bitrot issue on update, you now get a nicer error message. rather than the actual error, you are now told which version is safe to update to. to christen this system, I've added a check for the recent millisecond timestamp conversion, which caused some issues for users updating older clients. **if your client is v551 or older and you try to update to v561 or later, you will be told to update to v558 first.** sorry for the inconvenience here, and thank you for the reports (issue #1512)
* if you try to boot a database more than 50 versions earlier than the code, the client-based version popups now happen in the correct order, with the >50 exception firing before the >15 warning
* when an update asks a not-super-important yes/no question, I will now make it auto-yes or auto-no after ten minutes with the recommended value. this will ensure that automatic updaters will still progress (previously, they were hanging forever!)
### some downloader stuff
* thanks to a user, the derpibooru now fetches the post description as a note and the source as an associable URL. I tweaked the submitted stuff a bit, simplifying the parsing and discluding 'No description provided.' notes
* thanks to a user, the e621 parser can now grab files from posts where the (spicy, I think) content is normally not shown due to a guest login. the posts still won't show up in guest-login gallery searches, so this won't alter your normal results, but if you run into a post like this in your browser and drag-and-drop it onto the client, it now works
* I tried to improve the parsing system's de-newlining. this thing is a long-time hack--I've never liked it and I want to replace it with proper multi-line support--but for now I've made sure the de-newliner strips each line of leading/trailing whitespace and discards empty lines. the mode that _doesn't_ collapse newlines (note parsing, for the most part) now _does_ strip leading/trailing newlines along with other whitespace, meaning you no longer have to try and strip extra `<p>` and `<br>` tags and stuff yourself when grabbing notes. also, the formula UI where it says 'Newlines are collapsed before...' now says when it won't be collapsing newlines due to it being a note parser
* the String Match processing step now explicitly removes newlines before it runs, meaning it can still catch multi-line notes properly. you can now run a proper regex on a multi-line note
### boring cleanup
* optimised some thumbnail handling code, stuff like fetching the current list of sorted selected media
* large collections will be a little faster to select and otherwise do operations on
* sketched out a new `ClientGlobals` and client controller interface and started refactoring various HG.client_controller to the new CG. this makes no important running changes, but it cleans the messy HG file and will help future coding and type checking in the IDE as it is fleshed out
* added some help text to the edit file maintenance panel and fixed some gonk layout in the 'add new work' panel
* fixed some instances of the 'unknown' import status showing as a blank string
* fixed an error message in the export folder export job that fired when a file to be exported is missing--it was just giving blank instead of the file hash, and its direction to file maintenance was old and unclear
## [Version 560](https://github.com/hydrusnetwork/hydrus/releases/tag/v560)
### editing times for multiple files
@ -455,48 +498,3 @@ title: Changelog
* fixed a timing issue that meant popup messages were auto-dismissed from the popup toaster up to a second after they were being 'deleted' by their parent functiions. subscription flow felt more laggy because of this
* fixed the file info manager's duplicate call to duplicate unusual metadata like has_exif and blurhash
* removed some old code that isn't used any more
## [Version 551](https://github.com/hydrusnetwork/hydrus/releases/tag/v551)
### misc
* thanks to a user, we have a new checkbox under _options->thumbnails_ that disables thumbnail fading. they'll just blink into place in one frame as soon as ready
* after looking at this code myself, I gave it a full clean. the actual thumbnail fade animation is now handled with some proper objects rather than a scatter of variables passed around
* I also doubled the default fade time to 500ms. I expect I'll add an option for it, especially if we rework all this into the proper Qt animation engine and get it performing better
* fixed the crashes users on PyQt were seeing! I made one tiny change (1->1.0) last week, and PyQt didn't like it, so any view of Mr Bones or 'open externally' panels, or the media viewer top-right ratings hover was leading to program instability
* the system predicates for 'has/no duration', 'has/no frames', 'has/no notes', 'has/no words' (i.e. the respective 'num x' system pred, but either = 0 or >0) are now aware that they are each others' inverse, so if you ctrl+double-click or do similar edit actions, they'll flip
* updated the 'PTR for dummies' page to link to a new QuickSync source, kindly maintained and hosted by a user
### code cleanup and misc bug fixes
* sped up some random iteration across the program (e.g. when deciding which order to waterfall thumbnails in, which can suffer from overhead if you do a fast giganto-scroll)
* cleaned up the code that does image alpha channel (transparency) detection, comparison, and stripping
* unified how the variety of image loads and conversions perform the 'strip this image of useless transparency data' normalisation step. thumbnails from krita, svg, and pdf are now stripped of useless alpha. also, all 'import this serialised object png' avenues now handle pngs with spurious alpha
* I think I fixed the alpha channel stripping code to handle 'LA' (greyscale with transparency) files. if you try to import a hydrus serialised object png file that is for some crazy reason now LA, I think it'll work!
* when a files popup message filters its current files and the count goes to 0 (happens if you re-click the button after deleting everything it has to show), the message now auto-dismisses itself (previously it was nuking the button but staying as a thin strip of null panel space)
* fixed a bug where `system:date` predicates were displaying labels an hour off (usually midnight -> 11pm, thus cycling back to the previous day) thanks to the clocks changed (in the USA) last weekend. I suspect there is more of this, here and there, so let me know what you see
* fixed a counting typo error with the delete files code when you delete the last file in a domain but the domain thinks it already has 0 files
* fixed up similar code across the database to forestall future typos on SQLite SUMs
* improved and unified the 'hydrus temp dir' management code. if the specific per-process hydrus temp dir is cleared out by an external factor (I'm guessing just the OS cleaning up during a long running client session), hydrus should just simply make a new folder as needed. with luck, this will fix a problem with drag and drop export that ran into this
### many file move/copy error handling improvements
* _tl;dr: if hydrus can't put a file somewhere, it deals with that better now_
* improved how file move/merge function reports its errors, and how all its callers handle them
* the 'rename a file's file extension when its filetype changes' job now correctly recognises when it fails to rename a file due to a reason other than the file being currently in use
* import folders now correctly detect when they fail to 'move' action a file out after processing
* the check file integrity routine now correctly detects when it fails to move a damaged file from file storage to a landing zone in the main db directory. this failure now cancels the job properly and prints a nicer error to the log
* improved how the file copy/mirror function reports its errors, and how all its callers handle them
* saving a serialised object png now properly catches a 'transfer from temp dir to dest location' move error
* the internal database backup and restore routines now detect file copy errors better
* a drag and drop export operation that wants to put the files in the temp dir and also fails to collect its files nicely now correctly raises an error
* failing to set the mpv file on options save (and the subsequent mpv-load action) now reports its error correctly
* exporting update files now handles a missing update file more gracefully
* mergedirectory and mirrordirectory now fail instantly after any single error, rather than several
* added some more file/directory pre-checks to all the merge/mirror functions
* deleted some old unused code here
### client api
* thanks to a user, the Client API now has a 'generate_hashes' endpoint that returns the sha256 hash (and pixel hash and perceptual hashes of any appropriate image file) of any file you give it
* the client api version is now 55

View File

@ -181,7 +181,7 @@ As a result, if you get a failure on trying to do a big update, try cutting the
If you narrow the gap down to just one version and still get an error, please let me know. I am very interested in these sorts of problems and will be happy to help figure out a fix with you (and everyone else who might be affected).
_All that said, and while updating is complex and every client is different, various user reports over the years suggest this route works and is efficient: 204 > 238 > 246 > 291 > 328 > 335 > 376 > 421 > 466 > 474 ? 480 > 521_
_All that said, and while updating is complex and every client is different, various user reports over the years suggest this route works and is efficient: 204 > 238 > 246 > 291 > 328 > 335 > 376 > 421 > 466 > 474 ? 480 > 521 ? 558_
## Backing up

View File

@ -34,6 +34,42 @@
<div class="content">
<h1 id="changelog"><a href="#changelog">changelog</a></h1>
<ul>
<li>
<h2 id="version_561"><a href="#version_561">version 561</a></h2>
<ul>
<li><h3>rearranging thumbnails</h3></li>
<li>on the thumbnail menu, there is a new 'move' submenu. you can move the current selection of files to the start or end of the media list, or to one before or after the earliest selected file, or to the file you right-clicked on to create the menu, or to the first file's position if the selection is not contiguous. if the selection is non-contiguous, it will be made so in the move</li>
<li>added these rearrange commands to the shortcuts system, as 'move thumbnails' under the 'thumbnails' set. I wasn't sure whether to add some default shortcuts, like ctrl+numpad 7/3/4/6 for home/end/left/right or something--let me know what you think</li>
<li><h3>misc</h3></li>
<li>thanks to user help, fixed a stupid typo from last week that caused some bad errors (including crashes, in some cases) when doing non-simple duplicate filtering (issue #1514). this is the issue the v560a hotfix was made for</li>
<li>fixed another stupid content update typo that was causing 'already in db' results to not get metadata updates</li>
<li>as a hardcoded shortcut, Ctrl+C or Ctrl+Insert now copies the currently selected tags in any taglist. it'll output the full tag/predicate text, with namespace, no counts</li>
<li>I've shortened some thumbnail/media-viewer menu labels, made the 'delete' line into a submenu, and ensured the top info line is always a short variant, with detailed info bumped off to the submenu off the top line. I hate how these menus are often super-wide and thus a pain to navigate to the submenus, so let me know what situations still make them wide</li>
<li>the file log arrow button menu now has entries for 'delete already in db' and 'delete everything'</li>
<li>the 'add these tags to the favourites list?' yes/no now only fires if you try to add more than five tags ot once</li>
<li>the various dialogs in the client that auto-yes or auto-no now show a live countdown in their title string</li>
<li>the window position saving system is now stricter about what it records. maximised and fullscreen state is only saved if 'remember size' is false, and the last size/position is not saved at all if 'remember size/position' is false (previously, it would save these values but not restore them, but let's try being more precise here)</li>
<li>fixed a 'omg what happened, closing the window now' error in the duplicate filter if you try to 'go back' while it is loading a new set of pairs to show</li>
<li>fixed the 'vacuum db' command to correctly save 'last vacuumed time' for all files vacuumed in a job, not just the last!</li>
<li>whenever a `copy2` file copy (which includes copying file times and permission bits) fails for permission reasons, hydrus now falls back to a normal `copy` and logs the failure, including the modified time that failed to copy (which is the bit we actually care about here)</li>
<li><h3>db update stuff</h3></li>
<li>if there is a known bitrot issue on update, you now get a nicer error message. rather than the actual error, you are now told which version is safe to update to. to christen this system, I've added a check for the recent millisecond timestamp conversion, which caused some issues for users updating older clients. **if your client is v551 or older and you try to update to v561 or later, you will be told to update to v558 first.** sorry for the inconvenience here, and thank you for the reports (issue #1512)</li>
<li>if you try to boot a database more than 50 versions earlier than the code, the client-based version popups now happen in the correct order, with the >50 exception firing before the >15 warning</li>
<li>when an update asks a not-super-important yes/no question, I will now make it auto-yes or auto-no after ten minutes with the recommended value. this will ensure that automatic updaters will still progress (previously, they were hanging forever!)</li>
<li><h3>some downloader stuff</h3></li>
<li>thanks to a user, the derpibooru now fetches the post description as a note and the source as an associable URL. I tweaked the submitted stuff a bit, simplifying the parsing and discluding 'No description provided.' notes</li>
<li>thanks to a user, the e621 parser can now grab files from posts where the (spicy, I think) content is normally not shown due to a guest login. the posts still won't show up in guest-login gallery searches, so this won't alter your normal results, but if you run into a post like this in your browser and drag-and-drop it onto the client, it now works</li>
<li>I tried to improve the parsing system's de-newlining. this thing is a long-time hack--I've never liked it and I want to replace it with proper multi-line support--but for now I've made sure the de-newliner strips each line of leading/trailing whitespace and discards empty lines. the mode that _doesn't_ collapse newlines (note parsing, for the most part) now _does_ strip leading/trailing newlines along with other whitespace, meaning you no longer have to try and strip extra `<p>` and `<br>` tags and stuff yourself when grabbing notes. also, the formula UI where it says 'Newlines are collapsed before...' now says when it won't be collapsing newlines due to it being a note parser</li>
<li>the String Match processing step now explicitly removes newlines before it runs, meaning it can still catch multi-line notes properly. you can now run a proper regex on a multi-line note</li>
<li><h3>boring cleanup</h3></li>
<li>optimised some thumbnail handling code, stuff like fetching the current list of sorted selected media</li>
<li>large collections will be a little faster to select and otherwise do operations on</li>
<li>sketched out a new `ClientGlobals` and client controller interface and started refactoring various HG.client_controller to the new CG. this makes no important running changes, but it cleans the messy HG file and will help future coding and type checking in the IDE as it is fleshed out</li>
<li>added some help text to the edit file maintenance panel and fixed some gonk layout in the 'add new work' panel</li>
<li>fixed some instances of the 'unknown' import status showing as a blank string</li>
<li>fixed an error message in the export folder export job that fired when a file to be exported is missing--it was just giving blank instead of the file hash, and its direction to file maintenance was old and unclear</li>
</ul>
</li>
<li>
<h2 id="version_560"><a href="#version_560">version 560</a></h2>
<ul>

View File

@ -159,6 +159,10 @@ SIMPLE_OPEN_FILE_IN_FILE_EXPLORER = 146
SIMPLE_COPY_LITTLE_BMP = 147
SIMPLE_MOVE_THUMBNAIL_FOCUS = 148
SIMPLE_SELECT_FILES = 149
SIMPLE_REARRANGE_THUMBNAILS = 150
REARRANGE_THUMBNAILS_TYPE_FIXED = 0
REARRANGE_THUMBNAILS_TYPE_COMMAND = 1
MOVE_HOME = 0
MOVE_END = 1
@ -168,6 +172,7 @@ MOVE_UP = 4
MOVE_DOWN = 5
MOVE_PAGE_UP = 6
MOVE_PAGE_DOWN = 7
MOVE_TO_FOCUS = 8
move_enum_to_str_lookup = {
MOVE_HOME : 'home',
@ -177,7 +182,8 @@ move_enum_to_str_lookup = {
MOVE_UP : 'up',
MOVE_DOWN : 'down',
MOVE_PAGE_UP : 'page up',
MOVE_PAGE_DOWN : 'page down'
MOVE_PAGE_DOWN : 'page down',
MOVE_TO_FOCUS : 'to focus'
}
SELECTION_STATUS_NORMAL = 0
@ -338,7 +344,8 @@ simple_enum_to_str_lookup = {
SIMPLE_GLOBAL_FORCE_ANIMATION_SCANBAR_SHOW : 'force the animation scanbar to show (flip on/off)',
SIMPLE_OPEN_COMMAND_PALETTE : 'open the command palette',
SIMPLE_MOVE_THUMBNAIL_FOCUS : 'move the thumbnail focus',
SIMPLE_SELECT_FILES : 'select files'
SIMPLE_SELECT_FILES : 'select files',
SIMPLE_REARRANGE_THUMBNAILS : 'move thumbnails',
}
legacy_simple_str_to_enum_lookup = {
@ -758,6 +765,19 @@ class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
s = f'{s} ({file_filter.ToString()})'
elif action == SIMPLE_REARRANGE_THUMBNAILS:
( rearrange_type, rearrange_data ) = self.GetSimpleData()
if rearrange_type == REARRANGE_THUMBNAILS_TYPE_COMMAND:
s = f'{s} ({move_enum_to_str_lookup[ rearrange_data ]})'
elif rearrange_type == REARRANGE_THUMBNAILS_TYPE_FIXED:
s = f'{s} (to index {HydrusData.ToHumanInt(rearrange_data)})'
return s

View File

@ -452,7 +452,7 @@ STATUS_SKIPPED = 8
STATUS_SUCCESSFUL_AND_CHILD_FILES = 9
status_string_lookup = {
STATUS_UNKNOWN : '',
STATUS_UNKNOWN : 'unknown',
STATUS_SUCCESSFUL_AND_NEW : 'successful',
STATUS_SUCCESSFUL_BUT_REDUNDANT : 'already in db',
STATUS_DELETED : 'deleted',
@ -461,7 +461,7 @@ status_string_lookup = {
STATUS_PAUSED : 'paused',
STATUS_VETOED : 'ignored',
STATUS_SKIPPED : 'skipped',
STATUS_SUCCESSFUL_AND_CHILD_FILES : 'completed'
STATUS_SUCCESSFUL_AND_CHILD_FILES : 'created children'
}
SUCCESSFUL_IMPORT_STATES = { STATUS_SUCCESSFUL_AND_NEW, STATUS_SUCCESSFUL_BUT_REDUNDANT, STATUS_SUCCESSFUL_AND_CHILD_FILES }

View File

@ -27,6 +27,7 @@ from hydrus.client import ClientConstants as CC
from hydrus.client import ClientDaemons
from hydrus.client import ClientDefaults
from hydrus.client import ClientFiles
from hydrus.client import ClientGlobals as CG
from hydrus.client import ClientOptions
from hydrus.client import ClientServices
from hydrus.client import ClientThreading
@ -35,6 +36,7 @@ from hydrus.client.db import ClientDB
from hydrus.client.gui import ClientGUIDialogsMessage
from hydrus.client.gui import ClientGUISplash
from hydrus.client.gui import QtPorting as QP
from hydrus.client.interfaces import ClientControllerInterface
if not HG.twisted_is_broke:
@ -127,13 +129,13 @@ class App( QW.QApplication ):
# this is also called explicitly right at the end of the program. I set setQuitonLastWindowClosed False and then call quit explicitly, so it needs to be idempotent on the exit calls
if HG.client_controller is not None:
if CG.client_controller is not None:
if HG.client_controller.ProgramIsShuttingDown():
if CG.client_controller.ProgramIsShuttingDown():
screw_it_time = HydrusTime.GetNow() + 30
while not HG.client_controller.ProgramIsShutDown():
while not CG.client_controller.ProgramIsShutDown():
time.sleep( 0.5 )
@ -145,14 +147,14 @@ class App( QW.QApplication ):
else:
HG.client_controller.SetDoingFastExit( True )
CG.client_controller.SetDoingFastExit( True )
HG.client_controller.Exit()
CG.client_controller.Exit()
class Controller( HydrusController.HydrusController ):
class Controller( ClientControllerInterface.ClientControllerInterface, HydrusController.HydrusController ):
my_instance = None
@ -169,10 +171,12 @@ class Controller( HydrusController.HydrusController ):
self.gui = None
HydrusController.HydrusController.__init__( self, db_dir )
ClientControllerInterface.ClientControllerInterface.__init__( self )
self._name = 'client'
HG.client_controller = self
CG.client_controller = self
# just to set up some defaults, in case some db update expects something for an odd yaml-loading reason
self.options = ClientDefaults.GetClientDefaultOptions()
@ -1062,7 +1066,7 @@ class Controller( HydrusController.HydrusController ):
if self.new_options.GetBoolean( 'boot_with_network_traffic_paused' ):
HG.client_controller.new_options.SetBoolean( 'pause_all_new_network_traffic', True )
CG.client_controller.new_options.SetBoolean( 'pause_all_new_network_traffic', True )
self.parsing_cache = ClientCaches.ParsingCache()
@ -1261,7 +1265,7 @@ class Controller( HydrusController.HydrusController ):
self.frame_splash_status.SetTitleText( 'booting gui' + HC.UNICODE_ELLIPSIS )
subscriptions = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION )
subscriptions = CG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION )
self.files_maintenance_manager = ClientFiles.FilesMaintenanceManager( self )
@ -1316,7 +1320,7 @@ class Controller( HydrusController.HydrusController ):
def qt_code_pregui():
shortcut_sets = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT_SET )
shortcut_sets = CG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT_SET )
from hydrus.client.gui import ClientGUIShortcuts
@ -2106,7 +2110,7 @@ class Controller( HydrusController.HydrusController ):
def SynchroniseAccounts( self ):
if HG.client_controller.new_options.GetBoolean( 'pause_all_new_network_traffic' ):
if CG.client_controller.new_options.GetBoolean( 'pause_all_new_network_traffic' ):
return
@ -2126,7 +2130,7 @@ class Controller( HydrusController.HydrusController ):
def SynchroniseRepositories( self ):
if HG.client_controller.new_options.GetBoolean( 'pause_all_new_network_traffic' ):
if CG.client_controller.new_options.GetBoolean( 'pause_all_new_network_traffic' ):
return
@ -2223,6 +2227,12 @@ class Controller( HydrusController.HydrusController ):
QP.CallAfter( QW.QApplication.exit )
except HydrusExceptions.DBVersionException as e:
self.BlockingSafeShowCriticalMessage( 'database version error', str( e ) )
QP.CallAfter( QW.QApplication.exit, 1 )
except HydrusExceptions.DBAccessException as e:
trace = traceback.format_exc()

View File

@ -0,0 +1,7 @@
import typing
from hydrus.client.interfaces import ClientControllerInterface
# TODO: move all HG.client_controller references here, and the various like 'mpv report mode' stuff
# make a ServerGlobals too, I think!
client_controller: typing.Optional[ ClientControllerInterface.ClientControllerInterface ] = None

View File

@ -786,6 +786,11 @@ class ParseFormula( HydrusSerialisable.SerialisableBase ):
# 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 ]
else:
# note this does get rid of leading/trailing newlines, which is fine!
raw_texts = [ raw_text.strip() for raw_text in raw_texts ]
texts = self._string_processor.ProcessStrings( raw_texts )

View File

@ -11,6 +11,7 @@ from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
from hydrus.client import ClientTime
@ -733,7 +734,9 @@ class StringMatch( StringProcessingStep ):
try:
result = re.search( r, text )
text_to_test = ''.join( text.splitlines() ).strip()
result = re.search( r, text_to_test )
except Exception as e:

View File

@ -28,6 +28,7 @@ from hydrus.client import ClientAPI
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientDefaults
from hydrus.client import ClientFiles
from hydrus.client import ClientGlobals as CG
from hydrus.client import ClientLocation
from hydrus.client import ClientOptions
from hydrus.client import ClientServices
@ -177,7 +178,7 @@ def report_content_speed_to_job_status( job_status, rows_done, total_rows, preci
popup_message = 'content row ' + HydrusData.ConvertValueRangeToPrettyString( rows_done, total_rows ) + ': processing ' + row_name + ' at ' + rows_s + ' rows/s'
HG.client_controller.frame_splash_status.SetText( popup_message, print_to_log = False )
CG.client_controller.frame_splash_status.SetText( popup_message, print_to_log = False )
job_status.SetStatusText( popup_message, 2 )
def report_speed_to_job_status( job_status, precise_timestamp, num_rows, row_name ):
@ -188,7 +189,7 @@ def report_speed_to_job_status( job_status, precise_timestamp, num_rows, row_nam
popup_message = 'processing ' + row_name + ' at ' + rows_s + ' rows/s'
HG.client_controller.frame_splash_status.SetText( popup_message, print_to_log = False )
CG.client_controller.frame_splash_status.SetText( popup_message, print_to_log = False )
job_status.SetStatusText( popup_message, 2 )
def report_speed_to_log( precise_timestamp, num_rows, row_name ):
@ -212,7 +213,7 @@ class JobDatabaseClient( HydrusData.JobDatabase ):
if HG.db_ui_hang_relief_mode:
if QC.QThread.currentThread() == HG.client_controller.main_qt_thread:
if QC.QThread.currentThread() == CG.client_controller.main_qt_thread:
HydrusData.Print( 'ui-hang event processing: begin' )
QW.QApplication.instance().processEvents()
@ -1093,7 +1094,7 @@ class DB( HydrusDB.HydrusDB ):
orphans_found = True
client_files_manager = HG.client_controller.client_files_manager
client_files_manager = CG.client_controller.client_files_manager
those_that_exist_on_disk = set()
@ -1168,7 +1169,7 @@ class DB( HydrusDB.HydrusDB ):
hashes = self.modules_hashes_local_cache.GetHashes( [ row[0] for row in import_rows ] )
HG.client_controller.pub( 'new_page_query', location_context, initial_hashes = hashes, page_name = 'reparented file records' )
CG.client_controller.pub( 'new_page_query', location_context, initial_hashes = hashes, page_name = 'reparented file records' )
@ -1947,7 +1948,7 @@ class DB( HydrusDB.HydrusDB ):
if max_num_pairs is None:
max_num_pairs = HG.client_controller.new_options.GetInteger( 'duplicate_filter_max_batch_size' )
max_num_pairs = CG.client_controller.new_options.GetInteger( 'duplicate_filter_max_batch_size' )
# we need to batch non-intersecting decisions here to keep it simple at the gui-level
@ -2446,7 +2447,7 @@ class DB( HydrusDB.HydrusDB ):
if len( hashes_that_need_refresh ) > 0:
HG.client_controller.pub( 'new_file_info', hashes_that_need_refresh )
CG.client_controller.pub( 'new_file_info', hashes_that_need_refresh )
@ -4570,7 +4571,7 @@ class DB( HydrusDB.HydrusDB ):
needed_hashes = []
client_files_manager = HG.client_controller.client_files_manager
client_files_manager = CG.client_controller.client_files_manager
for hash_id in needed_hash_ids:
@ -5619,7 +5620,7 @@ class DB( HydrusDB.HydrusDB ):
text = 'searching potential duplicates: {}'.format( HydrusData.ToHumanInt( num_done ) )
HG.client_controller.frame_splash_status.SetSubtext( text )
CG.client_controller.frame_splash_status.SetSubtext( text )
for ( i, hash_id ) in enumerate( group_of_hash_ids ):
@ -5638,7 +5639,7 @@ class DB( HydrusDB.HydrusDB ):
should_stop = HG.client_controller.ShouldStopThisWork( maintenance_mode, stop_time = stop_time )
should_stop = CG.client_controller.ShouldStopThisWork( maintenance_mode, stop_time = stop_time )
if should_stop:
@ -6013,7 +6014,7 @@ class DB( HydrusDB.HydrusDB ):
if action == HC.CONTENT_UPDATE_ADD:
if not HG.client_controller.tag_display_manager.TagOK( ClientTags.TAG_DISPLAY_STORAGE, service_key, tag ):
if not CG.client_controller.tag_display_manager.TagOK( ClientTags.TAG_DISPLAY_STORAGE, service_key, tag ):
continue
@ -6026,7 +6027,7 @@ class DB( HydrusDB.HydrusDB ):
elif action == HC.CONTENT_UPDATE_PEND:
if not HG.client_controller.tag_display_manager.TagOK( ClientTags.TAG_DISPLAY_STORAGE, service_key, tag ):
if not CG.client_controller.tag_display_manager.TagOK( ClientTags.TAG_DISPLAY_STORAGE, service_key, tag ):
continue
@ -9803,6 +9804,8 @@ class DB( HydrusDB.HydrusDB ):
# bitrot gap here, update code above should never run!
if version == 553:
try:
@ -9879,7 +9882,7 @@ class DB( HydrusDB.HydrusDB ):
from hydrus.client.gui import ClientGUIDialogsQuick
result = ClientGUIDialogsQuick.GetYesNo( None, message, title = 'Regen animation thumbnails?' )
result = ClientGUIDialogsQuick.GetYesNo( None, message, title = 'Regen animation thumbnails?', auto_yes_time = 600 )
return result == QW.QDialog.Accepted
@ -10055,6 +10058,38 @@ class DB( HydrusDB.HydrusDB ):
if version == 560:
try:
domain_manager = self.modules_serialisable.GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER )
domain_manager.Initialise()
#
domain_manager.OverwriteDefaultParsers( [
'derpibooru.org file page parser',
'e621 file page parser'
] )
#
domain_manager.TryToLinkURLClassesAndParsers()
#
self.modules_serialisable.SetJSONDump( domain_manager )
except Exception as e:
HydrusData.PrintException( e )
message = 'Trying to update some downloaders failed! Please let hydrus dev know!'
self.pub_initial_message( message )
self._controller.frame_splash_status.SetTitleText( 'updated db to v{}'.format( HydrusData.ToHumanInt( version + 1 ) ) )
@ -10464,6 +10499,8 @@ class DB( HydrusDB.HydrusDB ):
self._CloseDBConnection()
successful_names = []
try:
for name in ok_names:
@ -10492,6 +10529,8 @@ class DB( HydrusDB.HydrusDB ):
HydrusData.Print( 'Vacuumed ' + db_path + ' in ' + HydrusTime.TimeDeltaToPrettyTimeDelta( time_took ) )
successful_names.append( name )
except Exception as e:
HydrusData.Print( 'vacuum failed:' )
@ -10504,8 +10543,6 @@ class DB( HydrusDB.HydrusDB ):
HydrusData.ShowText( text )
self._InitDBConnection()
return
@ -10516,7 +10553,11 @@ class DB( HydrusDB.HydrusDB ):
self._InitDBConnection()
self.modules_db_maintenance.RegisterSuccessfulVacuum( name )
for name in successful_names:
# can't do this without the db connection lol
self.modules_db_maintenance.RegisterSuccessfulVacuum( name )
job_status.SetStatusText( 'done!' )
@ -10644,7 +10685,7 @@ class DB( HydrusDB.HydrusDB ):
for filename in self._db_filenames.values():
HG.client_controller.frame_splash_status.SetText( filename )
CG.client_controller.frame_splash_status.SetText( filename )
source = os.path.join( path, filename )
dest = os.path.join( self._db_dir, filename )
@ -10675,7 +10716,7 @@ class DB( HydrusDB.HydrusDB ):
HG.client_controller.frame_splash_status.SetText( 'media files' )
CG.client_controller.frame_splash_status.SetText( 'media files' )
client_files_source = os.path.join( path, 'client_files' )
client_files_default = os.path.join( self._db_dir, 'client_files' )

View File

@ -567,7 +567,7 @@ class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
except HydrusExceptions.FileMissingException:
raise Exception( 'A file to be exported, hash "{}", was missing! You should run file maintenance (under database->maintenance->files) to check the files for the export folder\'s search, and possibly all your files.' )
raise Exception( f'A file to be exported, hash "{hash.hex()}", was missing! You should run "missing file" file maintenance (under database->file maintenance->manage scheduled jobs) to check if any other files in your export folder\'s search--or your whole database--are also missing.' )
try:

View File

@ -40,6 +40,7 @@ from hydrus.core.networking import HydrusNetworking
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientGlobals as CG
from hydrus.client import ClientLocation
from hydrus.client import ClientParsing
from hydrus.client import ClientPaths
@ -101,7 +102,7 @@ MENU_ORDER = [ 'file', 'undo', 'pages', 'database', 'network', 'services', 'tags
def GetTagServiceKeyForMaintenance( win: QW.QWidget ):
tag_services = HG.client_controller.services_manager.GetServices( HC.REAL_TAG_SERVICES )
tag_services = CG.client_controller.services_manager.GetServices( HC.REAL_TAG_SERVICES )
choice_tuples = [ ( 'all services', None, 'Do it for everything. Can take a long time!' ) ]
@ -124,7 +125,7 @@ def THREADUploadPending( service_key ):
try:
service = HG.client_controller.services_manager.GetService( service_key )
service = CG.client_controller.services_manager.GetService( service_key )
service_name = service.GetName()
service_type = service.GetServiceType()
@ -143,7 +144,7 @@ def THREADUploadPending( service_key ):
job_status.SetStatusTitle( 'uploading pending to ' + service_name )
nums_pending = HG.client_controller.Read( 'nums_pending' )
nums_pending = CG.client_controller.Read( 'nums_pending' )
nums_pending_for_this_service = nums_pending[ service_key ]
@ -215,13 +216,13 @@ def THREADUploadPending( service_key ):
unauthorised_job_status.FinishAndDismiss( 120 )
call = HydrusData.Call( HG.client_controller.pub, 'open_manage_services_and_try_to_auto_create_account', service_key )
call = HydrusData.Call( CG.client_controller.pub, 'open_manage_services_and_try_to_auto_create_account', service_key )
call.SetLabel( 'open manage services and check for auto-creatable accounts' )
unauthorised_job_status.SetUserCallable( call )
HG.client_controller.pub( 'message', unauthorised_job_status )
CG.client_controller.pub( 'message', unauthorised_job_status )
if len( paused_content_types ) > 0:
@ -248,9 +249,9 @@ def THREADUploadPending( service_key ):
current_ideal_weight = 100
result = HG.client_controller.Read( 'pending', service_key, content_types_to_request, ideal_weight = current_ideal_weight )
result = CG.client_controller.Read( 'pending', service_key, content_types_to_request, ideal_weight = current_ideal_weight )
HG.client_controller.pub( 'message', job_status )
CG.client_controller.pub( 'message', job_status )
no_results_found = result is None
@ -258,7 +259,7 @@ def THREADUploadPending( service_key ):
time_started_this_loop = HydrusTime.GetNowPrecise()
nums_pending = HG.client_controller.Read( 'nums_pending' )
nums_pending = CG.client_controller.Read( 'nums_pending' )
nums_pending_for_this_service = nums_pending[ service_key ]
@ -297,7 +298,7 @@ def THREADUploadPending( service_key ):
media_result = result
client_files_manager = HG.client_controller.client_files_manager
client_files_manager = CG.client_controller.client_files_manager
hash = media_result.GetHash()
mime = media_result.GetMime()
@ -330,7 +331,7 @@ def THREADUploadPending( service_key ):
if len( content_updates ) > 0:
HG.client_controller.WriteSynchronous( 'content_updates', ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdates( service_key, content_updates ) )
CG.client_controller.WriteSynchronous( 'content_updates', ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdates( service_key, content_updates ) )
elif service_type == HC.IPFS:
@ -376,9 +377,9 @@ def THREADUploadPending( service_key ):
return
HG.client_controller.pub( 'notify_new_pending' )
CG.client_controller.pub( 'notify_new_pending' )
HG.client_controller.WaitUntilViewFree()
CG.client_controller.WaitUntilViewFree()
total_time_this_loop_took = HydrusTime.GetNowPrecise() - time_started_this_loop
@ -391,7 +392,7 @@ def THREADUploadPending( service_key ):
current_ideal_weight = min( 500, int( current_ideal_weight * 1.05 ) )
result = HG.client_controller.Read( 'pending', service_key, content_types_to_request, ideal_weight = current_ideal_weight )
result = CG.client_controller.Read( 'pending', service_key, content_types_to_request, ideal_weight = current_ideal_weight )
finished_all_uploads = result is None
@ -414,7 +415,7 @@ def THREADUploadPending( service_key ):
HydrusData.ShowText( 'Found a possible hash in that error message--trying to show it in a new page.' )
HG.client_controller.pub( 'imported_files_to_page', [ possible_hash ], 'files that did not upload right' )
CG.client_controller.pub( 'imported_files_to_page', [ possible_hash ], 'files that did not upload right' )
job_status.SetStatusText( service.GetName() + ' error' )
@ -425,7 +426,7 @@ def THREADUploadPending( service_key ):
finally:
HG.client_controller.pub( 'notify_pending_upload_finished', service_key )
CG.client_controller.pub( 'notify_pending_upload_finished', service_key )
HydrusData.Print( job_status.ToString() )
@ -460,7 +461,7 @@ def THREADUploadPending( service_key ):
)
HG.client_controller.Write( 'delete_service_info', service_key, types_to_delete )
CG.client_controller.Write( 'delete_service_info', service_key, types_to_delete )
@ -842,7 +843,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
library_version_lines.append( 'psd_tools present: {}'.format( HydrusPSDHandling.PSD_TOOLS_OK ) )
library_version_lines.append( 'speedcopy (experimental test) present: {}'.format( HydrusFileHandling.SPEEDCOPY_OK ) )
library_version_lines.append( 'install dir: {}'.format( HC.BASE_DIR ) )
library_version_lines.append( 'db dir: {}'.format( HG.client_controller.db_dir ) )
library_version_lines.append( 'db dir: {}'.format( CG.client_controller.db_dir ) )
library_version_lines.append( 'temp dir: {}'.format( HydrusTemp.GetCurrentTempDir() ) )
library_version_lines.append( 'db cache size per file: {}MB'.format( HG.db_cache_size ) )
library_version_lines.append( 'db journal mode: {}'.format( HG.db_journal_mode ) )
@ -1037,7 +1038,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
self._controller.Write( 'backup', path )
HG.client_controller.new_options.SetNoneableInteger( 'last_backup_time', HydrusTime.GetNow() )
CG.client_controller.new_options.SetNoneableInteger( 'last_backup_time', HydrusTime.GetNow() )
self._did_a_backup_this_session = True
@ -1257,7 +1258,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
HydrusData.ShowText( message )
HG.client_controller.CallToThread( do_it )
CG.client_controller.CallToThread( do_it )
@ -1527,7 +1528,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
#
service_keys = list( HG.client_controller.services_manager.GetServiceKeys( ( HC.TAG_REPOSITORY, ) ) )
service_keys = list( CG.client_controller.services_manager.GetServiceKeys( ( HC.TAG_REPOSITORY, ) ) )
if len( service_keys ) > 0:
@ -1537,13 +1538,13 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
job_status.SetStatusTitle( 'auto-account creation test' )
call = HydrusData.Call( HG.client_controller.pub, 'open_manage_services_and_try_to_auto_create_account', service_key )
call = HydrusData.Call( CG.client_controller.pub, 'open_manage_services_and_try_to_auto_create_account', service_key )
call.SetLabel( 'open manage services and check for auto-creatable accounts' )
job_status.SetUserCallable( call )
HG.client_controller.pub( 'message', job_status )
CG.client_controller.pub( 'message', job_status )
#
@ -1594,13 +1595,13 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
note_import_options = NoteImportOptions.NoteImportOptions()
note_import_options.SetIsDefault( True )
call = HydrusData.Call( HG.client_controller.pub, 'make_new_subscription_gap_downloader', ( b'', 'safebooru tag search' ), 'skirt', file_import_options, tag_import_options, note_import_options, 2 )
call = HydrusData.Call( CG.client_controller.pub, 'make_new_subscription_gap_downloader', ( b'', 'safebooru tag search' ), 'skirt', file_import_options, tag_import_options, note_import_options, 2 )
call.SetLabel( 'start a new downloader for this to fill in the gap!' )
job_status.SetUserCallable( call )
HG.client_controller.pub( 'message', job_status )
CG.client_controller.pub( 'message', job_status )
#
@ -1755,7 +1756,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
if result == QW.QDialog.Accepted:
services = HG.client_controller.services_manager.GetServices()
services = CG.client_controller.services_manager.GetServices()
choice_tuples = [ ( service.GetName(), service.GetServiceKey(), service.GetName() ) for service in services ]
@ -1958,7 +1959,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
page.PageHidden()
HG.client_controller.pub( 'pause_all_media' )
CG.client_controller.pub( 'pause_all_media' )
for tlw in visible_tlws:
@ -2062,7 +2063,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
try:
job_status.SetStatusTitle( 'importing updates' )
HG.client_controller.pub( 'message', job_status )
CG.client_controller.pub( 'message', job_status )
for ( i, update_path ) in enumerate( update_paths ):
@ -2178,7 +2179,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
additional_service_keys_to_tags = ClientTags.ServiceKeysToTags()
url = HG.client_controller.network_engine.domain_manager.NormaliseURL( url )
url = CG.client_controller.network_engine.domain_manager.NormaliseURL( url )
( url_type, match_name, can_parse, cannot_parse_reason ) = self._controller.network_engine.domain_manager.GetURLParseCapability( url )
@ -2398,7 +2399,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
def work_callable( args ):
all_locations_are_default = HG.client_controller.client_files_manager.AllLocationsAreDefault()
all_locations_are_default = CG.client_controller.client_files_manager.AllLocationsAreDefault()
return all_locations_are_default
@ -2413,7 +2414,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
self._menubar_database_update_backup.setVisible( all_locations_are_default and backup_path is not None )
last_backup_time = HG.client_controller.new_options.GetNoneableInteger( 'last_backup_time' )
last_backup_time = CG.client_controller.new_options.GetNoneableInteger( 'last_backup_time' )
message = 'update database backup'
@ -2437,8 +2438,8 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
self._menubar_database_multiple_location_label.setVisible( not all_locations_are_default )
self._menubar_database_file_maintenance_during_idle.setChecked( HG.client_controller.new_options.GetBoolean( 'file_maintenance_during_idle' ) )
self._menubar_database_file_maintenance_during_active.setChecked( HG.client_controller.new_options.GetBoolean( 'file_maintenance_during_active' ) )
self._menubar_database_file_maintenance_during_idle.setChecked( CG.client_controller.new_options.GetBoolean( 'file_maintenance_during_idle' ) )
self._menubar_database_file_maintenance_during_active.setChecked( CG.client_controller.new_options.GetBoolean( 'file_maintenance_during_active' ) )
return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
@ -2454,8 +2455,8 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
def work_callable( args ):
import_folder_names = HG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FOLDER )
export_folder_names = HG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_EXPORT_FOLDER )
import_folder_names = CG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FOLDER )
export_folder_names = CG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_EXPORT_FOLDER )
return ( import_folder_names, export_folder_names )
@ -2506,7 +2507,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
simple_non_windows = not HC.PLATFORM_WINDOWS and not HG.client_controller.new_options.GetBoolean( 'advanced_mode' )
simple_non_windows = not HC.PLATFORM_WINDOWS and not CG.client_controller.new_options.GetBoolean( 'advanced_mode' )
windows_or_advanced_non_windows = not simple_non_windows
@ -2534,11 +2535,11 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
self._menubar_network_nudge_subs.setVisible( advanced_mode )
self._menubar_network_all_traffic_paused.setChecked( HG.client_controller.new_options.GetBoolean( 'pause_all_new_network_traffic' ) )
self._menubar_network_all_traffic_paused.setChecked( CG.client_controller.new_options.GetBoolean( 'pause_all_new_network_traffic' ) )
self._menubar_network_subscriptions_paused.setChecked( HG.client_controller.new_options.GetBoolean( 'pause_subs_sync' ) )
self._menubar_network_subscriptions_paused.setChecked( CG.client_controller.new_options.GetBoolean( 'pause_subs_sync' ) )
self._menubar_network_paged_import_queues_paused.setChecked( HG.client_controller.new_options.GetBoolean( 'pause_all_file_queues' ) )
self._menubar_network_paged_import_queues_paused.setChecked( CG.client_controller.new_options.GetBoolean( 'pause_all_file_queues' ) )
return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
@ -2555,11 +2556,11 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
def work_callable( args ):
gui_session_names = HG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION_CONTAINER )
gui_session_names = CG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION_CONTAINER )
if len( gui_session_names ) > 0:
gui_session_names_to_backup_timestamps_ms = HG.client_controller.Read( 'serialisable_names_to_backup_timestamps_ms', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION_CONTAINER )
gui_session_names_to_backup_timestamps_ms = CG.client_controller.Read( 'serialisable_names_to_backup_timestamps_ms', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION_CONTAINER )
else:
@ -2724,7 +2725,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
def work_callable( args ):
nums_pending = HG.client_controller.Read( 'nums_pending' )
nums_pending = CG.client_controller.Read( 'nums_pending' )
return nums_pending
@ -2984,8 +2985,8 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
def publish_callable( result ):
self._menubar_tags_tag_display_maintenance_during_idle.setChecked( HG.client_controller.new_options.GetBoolean( 'tag_display_maintenance_during_idle' ) )
self._menubar_tags_tag_display_maintenance_during_active.setChecked( HG.client_controller.new_options.GetBoolean( 'tag_display_maintenance_during_active' ) )
self._menubar_tags_tag_display_maintenance_during_idle.setChecked( CG.client_controller.new_options.GetBoolean( 'tag_display_maintenance_during_idle' ) )
self._menubar_tags_tag_display_maintenance_during_active.setChecked( CG.client_controller.new_options.GetBoolean( 'tag_display_maintenance_during_active' ) )
return ClientGUIAsync.AsyncQtUpdater( self, loading_callable, work_callable, publish_callable )
@ -3357,8 +3358,8 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
profile_mode_message += 'More information is available in the help, under \'reducing program lag\'.'
ClientGUIMenus.AppendMenuItem( profiling, 'what is this?', 'Show profile info.', ClientGUIDialogsMessage.ShowInformation, self, profile_mode_message )
ClientGUIMenus.AppendMenuCheckItem( profiling, 'profile mode', 'Run detailed \'profiles\'.', HG.profile_mode, HG.client_controller.FlipProfileMode )
ClientGUIMenus.AppendMenuCheckItem( profiling, 'query planner mode', 'Run detailed \'query plans\'.', HG.query_planner_mode, HG.client_controller.FlipQueryPlannerMode )
ClientGUIMenus.AppendMenuCheckItem( profiling, 'profile mode', 'Run detailed \'profiles\'.', HG.profile_mode, CG.client_controller.FlipProfileMode )
ClientGUIMenus.AppendMenuCheckItem( profiling, 'query planner mode', 'Run detailed \'query plans\'.', HG.query_planner_mode, CG.client_controller.FlipQueryPlannerMode )
ClientGUIMenus.AppendMenu( debug, profiling, 'profiling' )
@ -3387,7 +3388,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
gui_actions = ClientGUIMenus.GenerateMenu( debug )
default_location_context = HG.client_controller.new_options.GetDefaultLocalLocationContext()
default_location_context = CG.client_controller.new_options.GetDefaultLocalLocationContext()
def flip_macos_antiflicker():
@ -3413,7 +3414,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
ClientGUIMenus.AppendMenuItem( gui_actions, 'make a QMessageBox', 'Open a modal message dialog.', self._DebugMakeQMessageBox )
ClientGUIMenus.AppendMenuItem( gui_actions, 'make a new page in five seconds', 'Throw a delayed page at the main notebook, giving you time to minimise or otherwise alter the client before it arrives.', self._controller.CallLater, 5, self._controller.pub, 'new_page_query', default_location_context )
ClientGUIMenus.AppendMenuItem( gui_actions, 'refresh pages menu in five seconds', 'Delayed refresh the pages menu, giving you time to minimise or otherwise alter the client before it arrives.', self._controller.CallLater, 5, self._menu_updater_pages.update )
ClientGUIMenus.AppendMenuItem( gui_actions, 'publish some sub files in five seconds', 'Publish some files like a subscription would.', self._controller.CallLater, 5, lambda: HG.client_controller.pub( 'imported_files_to_page', [ HydrusData.GenerateKey() for i in range( 5 ) ], 'example sub files' ) )
ClientGUIMenus.AppendMenuItem( gui_actions, 'publish some sub files in five seconds', 'Publish some files like a subscription would.', self._controller.CallLater, 5, lambda: CG.client_controller.pub( 'imported_files_to_page', [ HydrusData.GenerateKey() for i in range( 5 ) ], 'example sub files' ) )
ClientGUIMenus.AppendMenuItem( gui_actions, 'make a parentless text ctrl dialog', 'Make a parentless text control in a dialog to test some character event catching.', self._DebugMakeParentlessTextCtrl )
ClientGUIMenus.AppendMenuItem( gui_actions, 'reset multi-column list settings to default', 'Reset all multi-column list widths and other display settings to default.', self._DebugResetColumnListManager )
ClientGUIMenus.AppendMenuItem( gui_actions, 'force a main gui layout now', 'Tell the gui to relayout--useful to test some gui bootup layout issues.', self.adjustSize )
@ -3681,7 +3682,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
special_command_menu = ClientGUIMenus.GenerateMenu( menu )
ClientGUIMenus.AppendMenuItem( special_command_menu, 'clear all multiwatcher highlights', 'Command all multiwatcher pages to clear their highlighted watchers.', HG.client_controller.pub, 'clear_multiwatcher_highlights' )
ClientGUIMenus.AppendMenuItem( special_command_menu, 'clear all multiwatcher highlights', 'Command all multiwatcher pages to clear their highlighted watchers.', CG.client_controller.pub, 'clear_multiwatcher_highlights' )
ClientGUIMenus.AppendMenu( menu, special_command_menu, 'special commands' )
@ -3816,7 +3817,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
if load_a_blank_page:
default_location_context = HG.client_controller.new_options.GetDefaultLocalLocationContext()
default_location_context = CG.client_controller.new_options.GetDefaultLocalLocationContext()
self._notebook.NewPageQuery( default_location_context, on_deepest_notebook = True )
@ -3883,7 +3884,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
def _STARTManageAccountTypes( self, service_key ):
admin_service = HG.client_controller.services_manager.GetService( service_key )
admin_service = CG.client_controller.services_manager.GetService( service_key )
job_status = ClientThreading.JobStatus()
@ -3920,7 +3921,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
def _ManageAccountTypes( self, service_key, account_types ):
admin_service = HG.client_controller.services_manager.GetService( service_key )
admin_service = CG.client_controller.services_manager.GetService( service_key )
title = 'edit account types'
@ -4011,7 +4012,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
url_class_keys_to_display = domain_manager.GetURLClassKeysToDisplay()
show_unmatched_urls_in_media_viewer = HG.client_controller.new_options.GetBoolean( 'show_unmatched_urls_in_media_viewer' )
show_unmatched_urls_in_media_viewer = CG.client_controller.new_options.GetBoolean( 'show_unmatched_urls_in_media_viewer' )
panel = ClientGUIDownloaders.EditDownloaderDisplayPanel( dlg, self._controller.network_engine, gugs, gug_keys_to_display, url_classes, url_class_keys_to_display, show_unmatched_urls_in_media_viewer )
@ -4024,7 +4025,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
domain_manager.SetGUGKeysToDisplay( gug_keys_to_display )
domain_manager.SetURLClassKeysToDisplay( url_class_keys_to_display )
HG.client_controller.new_options.SetBoolean( 'show_unmatched_urls_in_media_viewer', show_unmatched_urls_in_media_viewer )
CG.client_controller.new_options.SetBoolean( 'show_unmatched_urls_in_media_viewer', show_unmatched_urls_in_media_viewer )
@ -4378,7 +4379,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
self._controller.pub( 'notify_new_colourset' )
self._controller.pub( 'notify_new_favourite_tags' )
self._menu_item_help_darkmode.setChecked( HG.client_controller.new_options.GetString( 'current_colourset' ) == 'darkmode' )
self._menu_item_help_darkmode.setChecked( CG.client_controller.new_options.GetString( 'current_colourset' ) == 'darkmode' )
self._UpdateSystemTrayIcon()
@ -4537,7 +4538,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit tag repository tag filter' ) as dlg:
namespaces = HG.client_controller.network_engine.domain_manager.GetParserNamespaces()
namespaces = CG.client_controller.network_engine.domain_manager.GetParserNamespaces()
message = 'The repository will apply this to all new pending tags that are uploaded to it. Anything that does not pass is silently discarded.'
@ -4678,7 +4679,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
query_log_container = ClientImportSubscriptionQuery.SubscriptionQueryLogContainer( missing_query_log_container_name )
HG.client_controller.WriteSynchronous( 'serialisable', query_log_container )
CG.client_controller.WriteSynchronous( 'serialisable', query_log_container )
for subscription in subscriptions:
@ -4692,7 +4693,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
HG.client_controller.subscriptions_manager.SetSubscriptions( subscriptions ) # save the reset
CG.client_controller.subscriptions_manager.SetSubscriptions( subscriptions ) # save the reset
else:
@ -4718,7 +4719,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
for surplus_query_log_container_name in surplus_query_log_container_names:
surplus_query_log_container = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER, surplus_query_log_container_name )
surplus_query_log_container = CG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER, surplus_query_log_container_name )
backup_path = os.path.join( sub_dir, 'qlc_{}.json'.format( surplus_query_log_container_name ) )
@ -4727,7 +4728,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
f.write( surplus_query_log_container.DumpToString() )
HG.client_controller.WriteSynchronous( 'delete_serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER, surplus_query_log_container_name )
CG.client_controller.WriteSynchronous( 'delete_serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER, surplus_query_log_container_name )
else:
@ -4772,9 +4773,9 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
try:
HG.client_controller.subscriptions_manager.PauseSubscriptionsForEditing()
CG.client_controller.subscriptions_manager.PauseSubscriptionsForEditing()
while HG.client_controller.subscriptions_manager.SubscriptionsRunning():
while CG.client_controller.subscriptions_manager.SubscriptionsRunning():
time.sleep( 0.1 )
@ -4789,7 +4790,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
job_status.FinishAndDismiss()
subscriptions = HG.client_controller.subscriptions_manager.GetSubscriptions()
subscriptions = CG.client_controller.subscriptions_manager.GetSubscriptions()
expected_query_log_container_names = set()
@ -4798,7 +4799,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
expected_query_log_container_names.update( subscription.GetAllQueryLogContainerNames() )
actual_query_log_container_names = set( HG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER ) )
actual_query_log_container_names = set( CG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER ) )
missing_query_log_container_names = expected_query_log_container_names.difference( actual_query_log_container_names )
@ -4814,14 +4815,14 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
controller.pub( 'message', done_job_status )
HG.client_controller.WriteSynchronous(
CG.client_controller.WriteSynchronous(
'serialisable_atomic',
overwrite_types_and_objs = ( [ HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION ], subscriptions ),
set_objs = edited_query_log_containers,
deletee_types_to_names = { HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION_QUERY_LOG_CONTAINER : deletee_query_log_container_names }
)
HG.client_controller.subscriptions_manager.SetSubscriptions( subscriptions )
CG.client_controller.subscriptions_manager.SetSubscriptions( subscriptions )
except HydrusExceptions.QtDeadWindowException:
@ -4829,7 +4830,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
except HydrusExceptions.CancelledException:
HG.client_controller.subscriptions_manager.Wake()
CG.client_controller.subscriptions_manager.Wake()
finally:
@ -4838,7 +4839,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
finally:
HG.client_controller.subscriptions_manager.ResumeSubscriptionsAfterEditing()
CG.client_controller.subscriptions_manager.ResumeSubscriptionsAfterEditing()
@ -5126,7 +5127,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
busy_tooltip = None
( db_status, job_name ) = HG.client_controller.GetDBStatus()
( db_status, job_name ) = CG.client_controller.GetDBStatus()
if job_name is not None and job_name != '':
@ -5529,7 +5530,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
def _STARTReviewAllAccounts( self, service_key ):
admin_service = HG.client_controller.services_manager.GetService( service_key )
admin_service = CG.client_controller.services_manager.GetService( service_key )
job_status = ClientThreading.JobStatus()
@ -5719,7 +5720,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
# job key
client_api_service = HG.client_controller.services_manager.GetService( CC.CLIENT_API_SERVICE_KEY )
client_api_service = CG.client_controller.services_manager.GetService( CC.CLIENT_API_SERVICE_KEY )
port = client_api_service.GetPort()
@ -5731,7 +5732,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
client_api_service._port = port
HG.client_controller.RestartClientServerServices()
CG.client_controller.RestartClientServerServices()
time.sleep( 5 )
@ -5742,7 +5743,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
access_key = api_permissions.GetAccessKey()
HG.client_controller.client_api_manager.AddAccess( api_permissions )
CG.client_controller.client_api_manager.AddAccess( api_permissions )
#
@ -5752,7 +5753,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
job_status.SetStatusTitle( 'client api test' )
HG.client_controller.pub( 'message', job_status )
CG.client_controller.pub( 'message', job_status )
import requests
import json
@ -5790,7 +5791,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
job_status.SetStatusText( 'add url test' )
local_tag_services = HG.client_controller.services_manager.GetServices( ( HC.LOCAL_TAG, ) )
local_tag_services = CG.client_controller.services_manager.GetServices( ( HC.LOCAL_TAG, ) )
local_tag_service = random.choice( local_tag_services )
@ -5946,7 +5947,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
return page.GetPageKey()
page_key = HG.client_controller.CallBlockingToQt( HG.client_controller.gui, qt_session_gubbins )
page_key = CG.client_controller.CallBlockingToQt( CG.client_controller.gui, qt_session_gubbins )
#
@ -5969,7 +5970,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
#
HG.client_controller.client_api_manager.DeleteAccess( ( access_key, ) )
CG.client_controller.client_api_manager.DeleteAccess( ( access_key, ) )
#
@ -5977,14 +5978,14 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
client_api_service._port = None
HG.client_controller.RestartClientServerServices()
CG.client_controller.RestartClientServerServices()
job_status.FinishAndDismiss()
HG.client_controller.CallToThread( do_it )
CG.client_controller.CallToThread( do_it )
def _RunUITest( self ):
@ -5995,41 +5996,41 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
t = 0.25
default_location_context = HG.client_controller.new_options.GetDefaultLocalLocationContext()
default_location_context = CG.client_controller.new_options.GetDefaultLocalLocationContext()
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self._notebook.NewPageQuery, default_location_context, page_name = 'test', on_deepest_notebook = True )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self._notebook.NewPageQuery, default_location_context, page_name = 'test', on_deepest_notebook = True )
t += 0.25
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_PAGE_OF_PAGES ) )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_PAGE_OF_PAGES ) )
t += 0.25
HG.client_controller.CallLaterQtSafe( self, t, 'test job', page_of_pages.NewPageQuery, default_location_context, page_name ='test', on_deepest_notebook = False )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', page_of_pages.NewPageQuery, default_location_context, page_name ='test', on_deepest_notebook = False )
t += 0.25
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_DUPLICATE_FILTER_PAGE ) )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_DUPLICATE_FILTER_PAGE ) )
t += 0.25
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_GALLERY_DOWNLOADER_PAGE ) )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_GALLERY_DOWNLOADER_PAGE ) )
t += 0.25
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_SIMPLE_DOWNLOADER_PAGE ) )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_SIMPLE_DOWNLOADER_PAGE ) )
t += 0.25
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_URL_DOWNLOADER_PAGE ) )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_URL_DOWNLOADER_PAGE ) )
t += 0.25
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_WATCHER_DOWNLOADER_PAGE ) )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_NEW_WATCHER_DOWNLOADER_PAGE ) )
t += 0.25
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProposeSaveGUISession, CC.LAST_SESSION_SESSION_NAME )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProposeSaveGUISession, CC.LAST_SESSION_SESSION_NAME )
return page_of_pages
@ -6038,7 +6039,7 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
self._notebook.CloseCurrentPage()
HG.client_controller.CallLaterQtSafe( self, 0.5, 'test job', self._UnclosePage )
CG.client_controller.CallLaterQtSafe( self, 0.5, 'test job', self._UnclosePage )
def qt_close_pages( page_of_pages ):
@ -6051,23 +6052,23 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
for i in indices:
HG.client_controller.CallLaterQtSafe( self, t, 'test job', page_of_pages._ClosePage, i )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', page_of_pages._ClosePage, i )
t += 0.25
t += 0.25
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self._notebook.CloseCurrentPage )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self._notebook.CloseCurrentPage )
t += 0.25
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.DeleteAllClosedPages )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self.DeleteAllClosedPages )
def qt_test_ac():
default_location_context = HG.client_controller.new_options.GetDefaultLocalLocationContext()
default_location_context = CG.client_controller.new_options.GetDefaultLocalLocationContext()
SYS_PRED_REFRESH = 1.0
@ -6075,17 +6076,17 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
t = 0.5
HG.client_controller.CallLaterQtSafe( self, t, 'test job', page.SetSearchFocus )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', page.SetSearchFocus )
ac_widget = page.GetManagementPanel()._tag_autocomplete._text_ctrl
t += 0.5
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SET_MEDIA_FOCUS ) )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SET_MEDIA_FOCUS ) )
t += 0.5
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SET_SEARCH_FOCUS ) )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self.ProcessApplicationCommand, CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_SET_SEARCH_FOCUS ) )
t += 0.5
@ -6093,36 +6094,36 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
for c in 'the colour of her hair':
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, ord( c ), text = c )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, ord( c ), text = c )
t += 0.01
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
t += SYS_PRED_REFRESH
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
t += SYS_PRED_REFRESH
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
t += 0.05
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
t += SYS_PRED_REFRESH
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
t += 0.05
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
t += SYS_PRED_REFRESH
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
for i in range( 16 ):
@ -6130,44 +6131,44 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
for j in range( i + 1 ):
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
t += 0.1
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
t += SYS_PRED_REFRESH
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, None, QC.Qt.Key_Return )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, None, QC.Qt.Key_Return )
t += 1.0
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Down )
t += 0.05
HG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', uias.Char, ac_widget, QC.Qt.Key_Return )
t += 1.0
HG.client_controller.CallLaterQtSafe( self, t, 'test job', self._notebook.CloseCurrentPage )
CG.client_controller.CallLaterQtSafe( self, t, 'test job', self._notebook.CloseCurrentPage )
def do_it():
# pages
page_of_pages = HG.client_controller.CallBlockingToQt( self, qt_open_pages )
page_of_pages = CG.client_controller.CallBlockingToQt( self, qt_open_pages )
time.sleep( 4 )
HG.client_controller.CallBlockingToQt( self, qt_close_unclose_one_page )
CG.client_controller.CallBlockingToQt( self, qt_close_unclose_one_page )
time.sleep( 1.5 )
HG.client_controller.CallBlockingToQt( self, qt_close_pages, page_of_pages )
CG.client_controller.CallBlockingToQt( self, qt_close_pages, page_of_pages )
time.sleep( 5 )
@ -6175,10 +6176,10 @@ class FrameGUI( CAC.ApplicationCommandProcessorMixin, ClientGUITopLevelWindows.M
# a/c
HG.client_controller.CallBlockingToQt( self, qt_test_ac )
CG.client_controller.CallBlockingToQt( self, qt_test_ac )
HG.client_controller.CallToThread( do_it )
CG.client_controller.CallToThread( do_it )
def _RunServerTest( self, do_boot = True ):
@ -6868,7 +6869,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
def _UpdateSystemTrayIcon( self, currently_booting = False ):
if not ClientGUISystemTray.SystemTrayAvailable() or ( not HC.PLATFORM_WINDOWS and not HG.client_controller.new_options.GetBoolean( 'advanced_mode' ) ):
if not ClientGUISystemTray.SystemTrayAvailable() or ( not HC.PLATFORM_WINDOWS and not CG.client_controller.new_options.GetBoolean( 'advanced_mode' ) ):
return
@ -7013,7 +7014,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
else:
HG.client_controller.pub( 'pause_all_media' )
CG.client_controller.pub( 'pause_all_media' )
title = job_status.GetStatusTitle()
@ -7310,7 +7311,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._new_options.SetString( 'current_colourset', new_colourset )
HG.client_controller.pub( 'notify_new_colourset' )
CG.client_controller.pub( 'notify_new_colourset' )
def FlipNetworkTrafficPaused( self ):
@ -7416,7 +7417,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
shown = not self._currently_minimised_to_system_tray
windows_or_advanced_mode = HC.PLATFORM_WINDOWS or HG.client_controller.new_options.GetBoolean( 'advanced_mode' )
windows_or_advanced_mode = HC.PLATFORM_WINDOWS or CG.client_controller.new_options.GetBoolean( 'advanced_mode' )
good_to_go = ClientGUISystemTray.SystemTrayAvailable() and windows_or_advanced_mode
@ -7817,11 +7818,11 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
elif action == CAC.SIMPLE_GLOBAL_PROFILE_MODE_FLIP:
HG.client_controller.FlipProfileMode()
CG.client_controller.FlipProfileMode()
elif action == CAC.SIMPLE_GLOBAL_FORCE_ANIMATION_SCANBAR_SHOW:
HG.client_controller.new_options.FlipBoolean( 'force_animation_scanbar_show' )
CG.client_controller.new_options.FlipBoolean( 'force_animation_scanbar_show' )
elif action == CAC.SIMPLE_OPEN_COMMAND_PALETTE:
@ -8069,7 +8070,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
try:
text = HG.client_controller.GetClipboardText()
text = CG.client_controller.GetClipboardText()
except HydrusExceptions.DataMissing:
@ -8318,7 +8319,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
return
HG.client_controller.pub( 'pause_all_media' )
CG.client_controller.pub( 'pause_all_media' )
try:
@ -8484,7 +8485,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
text = 'Is now a good time for the client to do up to ' + HydrusData.ToHumanInt( idle_shutdown_max_minutes ) + ' minutes\' maintenance work? (Will auto-no in 15 seconds)'
text += os.linesep * 2
if HG.client_controller.IsFirstStart():
if CG.client_controller.IsFirstStart():
text += 'Since this is your first session, this maintenance should just be some quick initialisation work. It should only take a few seconds.'
text += os.linesep * 2

View File

@ -1,10 +1,12 @@
import os
import time
from qtpy import QtWidgets as QW
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusTime
from hydrus.client import ClientPaths
from hydrus.client.gui import ClientGUIScrolledPanelsButtonQuestions
@ -81,6 +83,41 @@ def GetInterstitialFilteringAnswer( win, label ):
return result
def run_auto_yes_no_gubbins( dlg: QW.QDialog, time_to_fire, original_title, action_description, end_state ):
def qt_set_title():
time_string = HydrusTime.TimestampToPrettyTimeDelta( time_to_fire, just_now_threshold = 0, just_now_string = 'imminently' )
title = f'{original_title} (will {action_description} {time_string})'
dlg.setWindowTitle( title )
def qt_fire_button():
if dlg.isModal():
dlg.done( end_state )
while not HydrusTime.TimeHasPassed( time_to_fire ):
job = HG.client_controller.CallLaterQtSafe( dlg, 0.0, 'dialog auto yes/no title set', qt_set_title )
if job.IsDead(): # window closed
return
time.sleep( 1 )
job = HG.client_controller.CallLaterQtSafe( dlg, 0.0, 'dialog auto yes/no fire', qt_fire_button )
def GetYesNo( win, message, title = 'Are you sure?', yes_label = 'yes', no_label = 'no', auto_yes_time = None, auto_no_time = None, check_for_cancelled = False ):
with ClientGUITopLevelWindowsPanels.DialogCustomButtonQuestion( win, title ) as dlg:
@ -89,32 +126,22 @@ def GetYesNo( win, message, title = 'Are you sure?', yes_label = 'yes', no_label
dlg.SetPanel( panel )
if auto_yes_time is None and auto_no_time is None:
return dlg.exec() if not check_for_cancelled else ( dlg.exec(), dlg.WasCancelled() )
else:
if auto_yes_time is not None or auto_no_time is not None:
if auto_yes_time is not None:
job = HG.client_controller.CallLaterQtSafe( dlg, auto_yes_time, 'dialog auto-yes', dlg.done, QW.QDialog.Accepted )
HG.client_controller.CallToThread( run_auto_yes_no_gubbins, dlg, HydrusTime.GetNow() + auto_yes_time, dlg.windowTitle(), 'auto-yes', QW.QDialog.Accepted )
elif auto_no_time is not None:
job = HG.client_controller.CallLaterQtSafe( dlg, auto_no_time, 'dialog auto-no', dlg.done, QW.QDialog.Rejected )
try:
return dlg.exec() if not check_for_cancelled else ( dlg.exec(), dlg.WasCancelled() )
finally:
job.Cancel()
HG.client_controller.CallToThread( run_auto_yes_no_gubbins, dlg, HydrusTime.GetNow() + auto_no_time, dlg.windowTitle(), 'auto-no', QW.QDialog.Rejected )
return dlg.exec() if not check_for_cancelled else ( dlg.exec(), dlg.WasCancelled() )
def GetYesYesNo( win, message, title = 'Are you sure?', yes_tuples = None, no_label = 'no' ):
with ClientGUITopLevelWindowsPanels.DialogCustomButtonQuestion( win, title ) as dlg:

View File

@ -219,7 +219,8 @@ def ShowFilesInNewPage( file_seed_cache: ClientImportFileSeeds.FileSeedCache, sh
def PopulateFileSeedCacheMenu( win: QW.QWidget, menu: QW.QMenu, file_seed_cache: ClientImportFileSeeds.FileSeedCache ):
num_successful = file_seed_cache.GetFileSeedCount( CC.STATUS_SUCCESSFUL_AND_NEW ) + file_seed_cache.GetFileSeedCount( CC.STATUS_SUCCESSFUL_BUT_REDUNDANT )
num_already_in = file_seed_cache.GetFileSeedCount( CC.STATUS_SUCCESSFUL_BUT_REDUNDANT )
num_successful = file_seed_cache.GetFileSeedCount( CC.STATUS_SUCCESSFUL_AND_NEW ) + num_already_in
num_vetoed = file_seed_cache.GetFileSeedCount( CC.STATUS_VETOED )
num_deleted = file_seed_cache.GetFileSeedCount( CC.STATUS_DELETED )
num_errors = file_seed_cache.GetFileSeedCount( CC.STATUS_ERROR )
@ -242,6 +243,11 @@ def PopulateFileSeedCacheMenu( win: QW.QWidget, menu: QW.QMenu, file_seed_cache:
ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'successful\' file import items from the queue'.format( HydrusData.ToHumanInt( num_successful ) ), 'Tell this log to clear out successful files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_SUCCESSFUL_AND_NEW, CC.STATUS_SUCCESSFUL_BUT_REDUNDANT, CC.STATUS_SUCCESSFUL_AND_CHILD_FILES ) )
if num_already_in > 0:
ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'already in db\' file import items from the queue'.format( HydrusData.ToHumanInt( num_already_in ) ), 'Tell this log to clear out successful but non-new files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_SUCCESSFUL_BUT_REDUNDANT, ) )
if num_deleted > 0:
ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'previously deleted\' file import items from the queue'.format( HydrusData.ToHumanInt( num_deleted ) ), 'Tell this log to clear out deleted files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_DELETED, ) )
@ -262,6 +268,13 @@ def PopulateFileSeedCacheMenu( win: QW.QWidget, menu: QW.QMenu, file_seed_cache:
ClientGUIMenus.AppendMenuItem( menu, 'delete {} \'skipped\' file import items from the queue'.format( HydrusData.ToHumanInt( num_skipped ) ), 'Tell this log to clear out skipped files, reducing the size of the queue.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_SKIPPED, ) )
if len( file_seed_cache ) > 0:
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuItem( menu, 'delete everything from the queue', 'Tell this log to clear out everything, resetting the queue to empty.', ClearFileSeeds, win, file_seed_cache, ( CC.STATUS_UNKNOWN, CC.STATUS_SUCCESSFUL_AND_NEW, CC.STATUS_SUCCESSFUL_BUT_REDUNDANT, CC.STATUS_DELETED, CC.STATUS_ERROR, CC.STATUS_VETOED, CC.STATUS_SKIPPED, CC.STATUS_SUCCESSFUL_AND_CHILD_FILES ) )
ClientGUIMenus.AppendSeparator( menu )
if num_successful > 0:
@ -354,7 +367,7 @@ class EditFileSeedCachePanel( ClientGUIScrolledPanels.EditPanel ):
note = file_seed.note
pretty_file_seed_data = str( file_seed_data )
pretty_status = CC.status_string_lookup[ status ]
pretty_status = CC.status_string_lookup[ status ] if status != CC.STATUS_UNKNOWN else ''
pretty_added = ClientTime.TimestampToPrettyTimeDelta( added )
pretty_modified = ClientTime.TimestampToPrettyTimeDelta( modified )

View File

@ -297,7 +297,7 @@ class EditGallerySeedLogPanel( ClientGUIScrolledPanels.EditPanel ):
pretty_gallery_seed_index = HydrusData.ToHumanInt( gallery_seed_index )
pretty_url = url
pretty_status = CC.status_string_lookup[ status ]
pretty_status = CC.status_string_lookup[ status ] if status != CC.STATUS_UNKNOWN else ''
pretty_added = ClientTime.TimestampToPrettyTimeDelta( added )
pretty_modified = ClientTime.TimestampToPrettyTimeDelta( modified )
pretty_note = note.split( os.linesep )[0]

View File

@ -2614,6 +2614,12 @@ class ReviewFileMaintenance( ClientGUIScrolledPanels.ReviewPanel ):
vbox = QP.VBoxLayout()
text = 'Here is the outstanding file maintenance work. This will be slowly completed in the background, usually a file every few seconds (you can edit this under _options->maintenance and processing_). Although you can rush work if you want to, it is best to generally leave it alone.'
st = ClientGUICommon.BetterStaticText( self._current_work_panel, text )
st.setWordWrap( True )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, jobs_listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self._current_work_panel.setLayout( vbox )
@ -2643,7 +2649,7 @@ class ReviewFileMaintenance( ClientGUIScrolledPanels.ReviewPanel ):
QP.AddToLayout( hbox, self._run_search_st, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, self._run_search, CC.FLAGS_CENTER_PERPENDICULAR )
self._search_panel.Add( self._tag_autocomplete, CC.FLAGS_EXPAND_PERPENDICULAR )
self._search_panel.Add( self._tag_autocomplete, CC.FLAGS_EXPAND_BOTH_WAYS )
self._search_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
@ -2692,12 +2698,17 @@ class ReviewFileMaintenance( ClientGUIScrolledPanels.ReviewPanel ):
vbox = QP.VBoxLayout()
label = 'First, select which files you wish to queue up for the job using a normal search. Hit \'run this search\' to select those files.'
label += os.linesep
label += 'Then select the job type and click \'add job\'.'
label = 'Here you can queue up new file maintenance work. You determine the files to run a job on by loading a normal file search.'
label += '\n' * 2
label += 'Once your search is set, you need to hit \'run this search\' to actually load up the files. Then select the job type to apply and click \'add job\'. Click \'see description\' for more information on the job.'
label += '\n' * 2
label += 'Be cautious--do not queue up an integrity scan for all your files on a whim! If you don\'t know what a job does, do not add it!'
QP.AddToLayout( vbox, ClientGUICommon.BetterStaticText(self._new_work_panel,label=label), CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._search_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
st = ClientGUICommon.BetterStaticText( self._new_work_panel, label )
st.setWordWrap( True )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._search_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( vbox, self._button_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._action_panel, CC.FLAGS_EXPAND_PERPENDICULAR )

View File

@ -439,8 +439,9 @@ SHORTCUTS_THUMBNAILS_ACTIONS = [
CAC.SIMPLE_LAUNCH_THE_ARCHIVE_DELETE_FILTER,
CAC.SIMPLE_OPEN_FILE_IN_EXTERNAL_PROGRAM,
CAC.SIMPLE_OPEN_FILE_IN_FILE_EXPLORER,
CAC.SIMPLE_SELECT_FILES,
CAC.SIMPLE_MOVE_THUMBNAIL_FOCUS,
CAC.SIMPLE_SELECT_FILES
CAC.SIMPLE_REARRANGE_THUMBNAILS
]
simple_shortcut_name_to_action_lookup = {

View File

@ -192,8 +192,11 @@ def SaveTLWSizeAndPosition( tlw: QW.QWidget, frame_key ):
( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = new_options.GetFrameLocation( frame_key )
maximised = tlw.isMaximized()
fullscreen = tlw.isFullScreen()
if remember_size:
maximised = tlw.isMaximized()
fullscreen = tlw.isFullScreen()
if not ( maximised or fullscreen ):
@ -201,8 +204,15 @@ def SaveTLWSizeAndPosition( tlw: QW.QWidget, frame_key ):
if safe_position is not None:
last_size = ( tlw.size().width(), tlw.size().height() )
last_position = ( safe_position.x(), safe_position.y() )
if remember_size:
last_size = ( tlw.size().width(), tlw.size().height() )
if remember_position:
last_position = ( safe_position.x(), safe_position.y() )

View File

@ -2898,6 +2898,11 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
def _RewindProcessing( self ) -> bool:
if self._currently_fetching_pairs:
return False
def test_we_can_pop():
if len( self._processed_pairs ) == 0:
@ -4513,15 +4518,20 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
info_lines = self._current_media.GetPrettyInfoLines()
top_line = info_lines.pop( 0 )
info_menu = ClientGUIMenus.GenerateMenu( menu )
ClientGUIMediaMenus.AddPrettyInfoLines( info_menu, info_lines )
ClientGUIMenus.AppendSeparator( info_menu )
ClientGUIMediaMenus.AddFileViewingStatsMenu( info_menu, ( self._current_media, ) )
ClientGUIMenus.AppendMenu( menu, info_menu, top_line )
filetype_summary = ClientMedia.GetMediasFiletypeSummaryString( [ self._current_media ] )
size_summary = HydrusData.ToHumanBytes( self._current_media.GetSize() )
info_summary = f'{filetype_summary}, {size_summary}'
ClientGUIMenus.AppendMenu( menu, info_menu, info_summary )
#
@ -4550,11 +4560,9 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
ClientGUIMenus.AppendMenuItem( zoom_menu, 'zoom to max', 'Set the zoom to the maximum possible.', self._media_container.ZoomMax )
ClientGUIMenus.AppendMenu( menu, zoom_menu, 'current zoom: {}'.format( ClientData.ConvertZoomToPercentage( self._media_container.GetCurrentZoom() ) ) )
ClientGUIMenus.AppendMenu( menu, zoom_menu, 'zoom: {}'.format( ClientData.ConvertZoomToPercentage( self._media_container.GetCurrentZoom() ) ) )
AddAudioVolumeMenu( menu, self.CANVAS_TYPE )
if self.parentWidget().isFullScreen():
ClientGUIMenus.AppendMenuItem( menu, 'exit fullscreen', 'Make this media viewer a regular window with borders.', self.parentWidget().FullscreenSwitch )
@ -4585,6 +4593,10 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
ClientGUIMenus.AppendMenuItem( menu, 'stop slideshow', 'Stop the current slideshow.', self._PausePlaySlideshow )
ClientGUIMenus.AppendSeparator( menu )
AddAudioVolumeMenu( menu, self.CANVAS_TYPE )
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuItem( menu, 'remove from view', 'Remove this file from the list you are viewing.', self._Remove )
@ -4611,9 +4623,16 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
local_file_service_keys_we_are_in = sorted( locations_manager.GetCurrent().intersection( local_file_service_keys ), key = HG.client_controller.services_manager.GetName )
for file_service_key in local_file_service_keys_we_are_in:
if len( local_file_service_keys_we_are_in ) > 0:
ClientGUIMenus.AppendMenuItem( menu, 'delete from {}'.format( HG.client_controller.services_manager.GetName( file_service_key ) ), 'Delete this file.', self._Delete, file_service_key = file_service_key )
delete_menu = ClientGUIMenus.GenerateMenu( menu )
for file_service_key in local_file_service_keys_we_are_in:
ClientGUIMenus.AppendMenuItem( delete_menu, 'from {}'.format( HG.client_controller.services_manager.GetName( file_service_key ) ), 'Delete this file.', self._Delete, file_service_key = file_service_key )
ClientGUIMenus.AppendMenu( menu, delete_menu, 'delete' )
#

View File

@ -1174,6 +1174,29 @@ class ListBox( QW.QScrollArea ):
self._last_view_start = None
def _CopySelectedTexts( self ):
if len( self._selected_terms ) > 1:
# keep order
terms = [ term for term in self._ordered_terms if term in self._selected_terms ]
else:
# nice and fast
terms = self._selected_terms
texts = [ term.GetCopyableText() for term in terms ]
if len( texts ) > 0:
text = '\n'.join( texts )
HG.client_controller.pub( 'clipboard', 'text', text )
def _DataHasChanged( self ):
self._SetVirtualSize()
@ -1984,6 +2007,10 @@ class ListBox( QW.QScrollArea ):
self.widget().update()
elif ctrl and key_code in ( ord( 'C' ), ord( 'c' ), QC.Qt.Key_Insert ):
self._CopySelectedTexts()
else:
hit_logical_index = None
@ -2011,7 +2038,7 @@ class ListBox( QW.QScrollArea ):
ctrl = False
hit_logical_index = ( self._last_hit_logical_index - 1 ) % len( self._ordered_terms )
elif ctrl and key_code in ( ord( 'N' ), ord( 'n' ) ):
# remove ctrl key to make it act exactly like the down arrow
@ -3289,15 +3316,18 @@ class ListBoxTags( ListBox ):
def add_favourite_tags( tags ):
message = f'Add{HydrusData.ConvertManyStringsToNiceInsertableHumanSummary( tags )}to the favourites list?'
from hydrus.client.gui import ClientGUIDialogsQuick
result = ClientGUIDialogsQuick.GetYesNo( self, message )
if result != QW.QDialog.Accepted:
if len( tags ) > 5:
return
message = f'Add{HydrusData.ConvertManyStringsToNiceInsertableHumanSummary( tags )}to the favourites list?'
from hydrus.client.gui import ClientGUIDialogsQuick
result = ClientGUIDialogsQuick.GetYesNo( self, message )
if result != QW.QDialog.Accepted:
return
favourite_tags = set( HG.client_controller.new_options.GetStringList( 'favourite_tags' ) )

View File

@ -11,6 +11,7 @@ from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTime
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientGlobals as CG
from hydrus.client import ClientSerialisable
from hydrus.client.gui import ClientGUIDragDrop
from hydrus.client.gui import ClientGUICore as CGC
@ -47,7 +48,7 @@ class BetterListCtrl( QW.QTreeWidget ):
self._column_list_type = column_list_type
self._column_list_status: ClientGUIListStatus.ColumnListStatus = HG.client_controller.column_list_manager.GetStatus( self._column_list_type )
self._column_list_status: ClientGUIListStatus.ColumnListStatus = CG.client_controller.column_list_manager.GetStatus( self._column_list_type )
self._original_column_list_status = self._column_list_status
self.setAlternatingRowColors( True )
@ -106,7 +107,7 @@ class BetterListCtrl( QW.QTreeWidget ):
self.setMinimumWidth( total_width )
'''
main_tlw = HG.client_controller.GetMainTLW()
main_tlw = CG.client_controller.GetMainTLW()
# if last section is set too low, for instance 3, the column seems unable to ever shrink from initial (expanded to fill space) size
# _ _ ___ _ _ __ __ ___
@ -180,8 +181,8 @@ class BetterListCtrl( QW.QTreeWidget ):
self.header().setContextMenuPolicy( QC.Qt.CustomContextMenu )
self.header().customContextMenuRequested.connect( self._ShowHeaderMenu )
HG.client_controller.sub( self, 'NotifySettingsUpdated', 'reset_all_listctrl_status' )
HG.client_controller.sub( self, 'NotifySettingsUpdated', 'reset_listctrl_status' )
CG.client_controller.sub( self, 'NotifySettingsUpdated', 'reset_all_listctrl_status' )
CG.client_controller.sub( self, 'NotifySettingsUpdated', 'reset_listctrl_status' )
def _AddDataInfo( self, data_info ):
@ -227,7 +228,7 @@ class BetterListCtrl( QW.QTreeWidget ):
self._column_list_status = self._GenerateCurrentStatus()
HG.client_controller.column_list_manager.SaveStatus( self._column_list_status )
CG.client_controller.column_list_manager.SaveStatus( self._column_list_status )
def _GenerateCurrentStatus( self ) -> ClientGUIListStatus.ColumnListStatus:
@ -236,7 +237,7 @@ class BetterListCtrl( QW.QTreeWidget ):
status.SetColumnListType( self._column_list_type )
main_tlw = HG.client_controller.GetMainTLW()
main_tlw = CG.client_controller.GetMainTLW()
columns = []
@ -391,7 +392,7 @@ class BetterListCtrl( QW.QTreeWidget ):
name = CGLC.column_list_type_name_lookup[ self._column_list_type ]
ClientGUIMenus.AppendMenuItem( menu, f'reset default column widths for "{name}" lists', 'Reset the column widths and other display settings for all lists of this type', HG.client_controller.column_list_manager.ResetToDefaults, self._column_list_type )
ClientGUIMenus.AppendMenuItem( menu, f'reset default column widths for "{name}" lists', 'Reset the column widths and other display settings for all lists of this type', CG.client_controller.column_list_manager.ResetToDefaults, self._column_list_type )
CGC.core().PopupMenu( self, menu )
@ -713,7 +714,7 @@ class BetterListCtrl( QW.QTreeWidget ):
self.blockSignals( True )
self.header().blockSignals( True )
self._column_list_status: ClientGUIListStatus.ColumnListStatus = HG.client_controller.column_list_manager.GetStatus( self._column_list_type )
self._column_list_status: ClientGUIListStatus.ColumnListStatus = CG.client_controller.column_list_manager.GetStatus( self._column_list_type )
self._original_column_list_status = self._column_list_status
#
@ -722,7 +723,7 @@ class BetterListCtrl( QW.QTreeWidget ):
#
main_tlw = HG.client_controller.GetMainTLW()
main_tlw = CG.client_controller.GetMainTLW()
MIN_SECTION_SIZE_CHARS = 3
@ -934,7 +935,7 @@ class BetterListCtrl( QW.QTreeWidget ):
last_column_chars = self._original_column_list_status.GetColumnWidth( last_column_type )
main_tlw = HG.client_controller.GetMainTLW()
main_tlw = CG.client_controller.GetMainTLW()
width += ClientGUIFunctions.ConvertTextToPixelWidth( main_tlw, last_column_chars )
@ -1165,7 +1166,7 @@ class BetterListCtrlPanel( QW.QWidget ):
json = export_object.DumpToString()
HG.client_controller.pub( 'clipboard', 'text', json )
CG.client_controller.pub( 'clipboard', 'text', json )
@ -1297,11 +1298,11 @@ class BetterListCtrlPanel( QW.QWidget ):
def _ImportFromClipboard( self ):
if HG.client_controller.ClipboardHasImage():
if CG.client_controller.ClipboardHasImage():
try:
qt_image = HG.client_controller.GetClipboardImage()
qt_image = CG.client_controller.GetClipboardImage()
except:
@ -1332,7 +1333,7 @@ class BetterListCtrlPanel( QW.QWidget ):
try:
raw_text = HG.client_controller.GetClipboardText()
raw_text = CG.client_controller.GetClipboardText()
except HydrusExceptions.DataMissing as e:

View File

@ -588,9 +588,8 @@ class MediaPanel( CAC.ApplicationCommandProcessorMixin, ClientMedia.ListeningMed
num_selected = self._GetNumSelected()
( num_files_descriptor, selected_files_descriptor ) = self._GetSortedSelectedMimeDescriptors()
num_files_string = '{} {}'.format( HydrusData.ToHumanInt( num_files ), num_files_descriptor )
num_files_string = ClientMedia.GetMediasFiletypeSummaryString( self._sorted_media )
selected_files_string = ClientMedia.GetMediasFiletypeSummaryString( self._selected_media )
s = num_files_string # 23 files
@ -615,14 +614,10 @@ class MediaPanel( CAC.ApplicationCommandProcessorMixin, ClientMedia.ListeningMed
s += ' - '
# if 1 selected, we show the whole mime string, so no need to specify
if num_selected == 1 or selected_files_descriptor == num_files_descriptor:
if num_selected == 1 or selected_files_string == num_files_string:
selected_files_string = HydrusData.ToHumanInt( num_selected )
else:
selected_files_string = '{} {}'.format( HydrusData.ToHumanInt( num_selected ), selected_files_descriptor )
if num_selected == 1: # 23 files - 1 video selected, file_info
@ -771,17 +766,8 @@ class MediaPanel( CAC.ApplicationCommandProcessorMixin, ClientMedia.ListeningMed
def _GetSelectedMediaOrdered( self ):
medias = []
for media in self._sorted_media:
if media in self._selected_media:
medias.append( media )
return medias
# note that this is fast because sorted_media is custom
return sorted( self._selected_media, key = lambda m: self._sorted_media.index( m ) )
def _GetSimilarTo( self, max_hamming ):
@ -2249,6 +2235,72 @@ class MediaPanel( CAC.ApplicationCommandProcessorMixin, ClientMedia.ListeningMed
self._CopyHashesToClipboard( 'sha512' )
elif action == CAC.SIMPLE_REARRANGE_THUMBNAILS:
ordered_selected_media = self._GetSelectedMediaOrdered()
( rearrange_type, rearrange_data ) = command.GetSimpleData()
insertion_index = None
if rearrange_type == CAC.REARRANGE_THUMBNAILS_TYPE_FIXED:
insertion_index = rearrange_data
elif rearrange_type == CAC.REARRANGE_THUMBNAILS_TYPE_COMMAND:
rearrange_command = rearrange_data
if rearrange_command == CAC.MOVE_HOME:
insertion_index = 0
elif rearrange_command == CAC.MOVE_END:
insertion_index = len( self._sorted_media )
else:
if len( self._selected_media ) > 0:
if rearrange_command in ( CAC.MOVE_LEFT, CAC.MOVE_RIGHT ):
ordered_selected_media = self._GetSelectedMediaOrdered()
earliest_index = self._sorted_media.index( ordered_selected_media[0] )
if rearrange_command == CAC.MOVE_LEFT:
if earliest_index > 0:
insertion_index = earliest_index - 1
elif rearrange_command == CAC.MOVE_RIGHT:
insertion_index = earliest_index + 1
elif rearrange_command == CAC.MOVE_TO_FOCUS:
if self._focused_media is not None:
focus_index = self._sorted_media.index( self._focused_media )
insertion_index = focus_index
if insertion_index is None:
return
self.MoveMedia( ordered_selected_media, insertion_index = insertion_index )
elif action == CAC.SIMPLE_SHOW_DUPLICATES:
if self._HasFocusSingleton():
@ -3180,6 +3232,13 @@ class MediaPanelThumbnails( MediaPanel ):
def _NotifyThumbnailsHaveMoved( self ):
self._DirtyAllPages()
self.widget().update()
def _RecalculateVirtualSize( self, called_from_resize_event = False ):
my_size = QP.ScrollAreaVisibleRect( self ).size()
@ -3561,6 +3620,15 @@ class MediaPanelThumbnails( MediaPanel ):
self.ShowMenu()
def MoveMedia( self, medias: typing.List[ ClientMedia.Media ], insertion_index: int ):
MediaPanel.MoveMedia( self, medias, insertion_index )
self._NotifyThumbnailsHaveMoved()
self._ScrollToMedia( medias[0] )
def NewThumbnails( self, hashes ):
affected_thumbnails = self._GetMedia( hashes )
@ -3654,11 +3722,15 @@ class MediaPanelThumbnails( MediaPanel ):
rows = -1
columns = 0
else: # MOVE_DOWN
elif move_direction == CAC.MOVE_DOWN:
rows = 1
columns = 0
else:
raise NotImplementedError()
self._MoveThumbnailFocus( rows, columns, shift )
@ -3835,7 +3907,7 @@ class MediaPanelThumbnails( MediaPanel ):
rescind_unpin_phrase = 'rescind unpin from'
archive_phrase = 'archive selected'
inbox_phrase = 'return selected to inbox'
inbox_phrase = 're-inbox selected'
local_delete_phrase = 'delete selected'
delete_physically_phrase = 'delete selected physically now'
undelete_phrase = 'undelete selected'
@ -3860,7 +3932,7 @@ class MediaPanelThumbnails( MediaPanel ):
rescind_unpin_phrase = 'rescind unpin from'
archive_phrase = 'archive'
inbox_phrase = 'return to inbox'
inbox_phrase = 're-inbox'
local_delete_phrase = 'delete'
delete_physically_phrase = 'delete physically now'
undelete_phrase = 'undelete'
@ -4000,12 +4072,12 @@ class MediaPanelThumbnails( MediaPanel ):
selection_info_menu = ClientGUIMenus.GenerateMenu( menu )
selected_files_string = ClientMedia.GetMediasFiletypeSummaryString( self._selected_media )
selection_info_menu_label = f'{selected_files_string}, {self._GetPrettyTotalSize( only_selected = True )}'
if multiple_selected:
( num_files_descriptor, selected_files_descriptor ) = self._GetSortedSelectedMimeDescriptors()
selection_info_menu_label = '{} {}, {}'.format( HydrusData.ToHumanInt( num_selected ), selected_files_descriptor, self._GetPrettyTotalSize( only_selected = True ) )
pretty_total_duration = self._GetPrettyTotalDuration( only_selected = True )
if pretty_total_duration != '':
@ -4019,13 +4091,11 @@ class MediaPanelThumbnails( MediaPanel ):
pretty_info_lines = list( focus_singleton.GetPrettyInfoLines() )
top_line = pretty_info_lines.pop( 0 )
selection_info_menu_label = top_line
ClientGUIMediaMenus.AddPrettyInfoLines( selection_info_menu, pretty_info_lines )
ClientGUIMenus.AppendSeparator( selection_info_menu )
ClientGUIMediaMenus.AddFileViewingStatsMenu( selection_info_menu, self._selected_media )
if len( disparate_current_file_service_keys ) > 0:
@ -4098,7 +4168,6 @@ class MediaPanelThumbnails( MediaPanel ):
ClientGUIMediaMenus.AddServiceKeyLabelsToMenu( selection_info_menu, common_petitioned_ipfs_service_keys, unpin_phrase )
if len( selection_info_menu.actions() ) == 0:
selection_info_menu.deleteLater()
@ -4131,6 +4200,26 @@ class MediaPanelThumbnails( MediaPanel ):
AddSelectMenu( self, menu, filter_counts, all_specific_file_domains, has_local_and_remote )
AddRemoveMenu( self, menu, filter_counts, all_specific_file_domains, has_local_and_remote )
if len( self._selected_media ) > 0:
ordered_selected_media = self._GetSelectedMediaOrdered()
try:
earliest_index = self._sorted_media.index( ordered_selected_media[0] )
num_selected = len( self._selected_media )
selection_is_contiguous = num_selected > 0 and self._sorted_media.index( ordered_selected_media[-1] ) - earliest_index == num_selected - 1
AddMoveMenu( self, menu, self._selected_media, self._sorted_media, self._focused_media, selection_is_contiguous, earliest_index )
except HydrusExceptions.DataMissing:
pass
ClientGUIMenus.AppendSeparator( menu )
if has_local:
@ -4159,9 +4248,18 @@ class MediaPanelThumbnails( MediaPanel ):
local_file_service_keys_we_are_in = sorted( current_file_service_keys.intersection( user_command_deletable_file_service_keys ), key = HG.client_controller.services_manager.GetName )
for file_service_key in local_file_service_keys_we_are_in:
if len( local_file_service_keys_we_are_in ) > 0:
ClientGUIMenus.AppendMenuItem( menu, '{} from {}'.format( local_delete_phrase, HG.client_controller.services_manager.GetName( file_service_key ) ), 'Delete the selected files.', self._Delete, file_service_key )
delete_menu = ClientGUIMenus.GenerateMenu( menu )
for file_service_key in local_file_service_keys_we_are_in:
service_name = HG.client_controller.services_manager.GetName( file_service_key )
ClientGUIMenus.AppendMenuItem( delete_menu, f'from {service_name}', f'Delete the selected files from {service_name}.', self._Delete, file_service_key )
ClientGUIMenus.AppendMenu( menu, delete_menu, local_delete_phrase )
if selection_has_trash:
@ -4528,9 +4626,7 @@ class MediaPanelThumbnails( MediaPanel ):
MediaPanel.Sort( self, media_sort )
self._DirtyAllPages()
self.widget().update()
self._NotifyThumbnailsHaveMoved()
def ThumbnailsReset( self ):
@ -4768,7 +4864,81 @@ class MediaPanelThumbnails( MediaPanel ):
def AddRemoveMenu( win: MediaPanel, menu, filter_counts, all_specific_file_domains, has_local_and_remote ):
def AddMoveMenu( win: MediaPanel, menu: QW.QMenu, selected_media: typing.Set[ ClientMedia.Media ], sorted_media: ClientMedia.SortedList, focused_media: typing.Optional[ ClientMedia.Media ], selection_is_contiguous: bool, earliest_index: int ):
if len( selected_media ) == 0 or len( selected_media ) == len( sorted_media ):
return
move_menu = ClientGUIMenus.GenerateMenu( menu )
if earliest_index > 0:
ClientGUIMenus.AppendMenuItem(
move_menu,
'to start',
'Move the selected thumbnails to the start of the media list.',
win.ProcessApplicationCommand,
CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_REARRANGE_THUMBNAILS, ( CAC.REARRANGE_THUMBNAILS_TYPE_COMMAND, CAC.MOVE_HOME ) )
)
ClientGUIMenus.AppendMenuItem(
move_menu,
'back one',
'Move the selected thumbnails back one position.',
win.ProcessApplicationCommand,
CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_REARRANGE_THUMBNAILS, ( CAC.REARRANGE_THUMBNAILS_TYPE_COMMAND, CAC.MOVE_LEFT ) )
)
if focused_media is not None:
try:
focused_index = sorted_media.index( focused_media )
if focused_index != earliest_index or not selection_is_contiguous:
ClientGUIMenus.AppendMenuItem(
move_menu,
'to here',
'Move the selected thumbnails to the focused position (most likely the one you clicked on).',
win.ProcessApplicationCommand,
CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_REARRANGE_THUMBNAILS, ( CAC.REARRANGE_THUMBNAILS_TYPE_COMMAND, CAC.MOVE_TO_FOCUS ) )
)
except HydrusExceptions.DataMissing:
pass
if earliest_index + len( selected_media ) < len( sorted_media ):
ClientGUIMenus.AppendMenuItem(
move_menu,
'forward one',
'Move the selected thumbnails forward one position.',
win.ProcessApplicationCommand,
CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_REARRANGE_THUMBNAILS, ( CAC.REARRANGE_THUMBNAILS_TYPE_COMMAND, CAC.MOVE_RIGHT ) )
)
ClientGUIMenus.AppendMenuItem(
move_menu,
'to end',
'Move the selected thumbnails to the end of the media list.',
win.ProcessApplicationCommand,
CAC.ApplicationCommand.STATICCreateSimpleCommand( CAC.SIMPLE_REARRANGE_THUMBNAILS, ( CAC.REARRANGE_THUMBNAILS_TYPE_COMMAND, CAC.MOVE_END ) )
)
ClientGUIMenus.AppendMenu( menu, move_menu, 'move' )
def AddRemoveMenu( win: MediaPanel, menu: QW.QMenu, filter_counts, all_specific_file_domains, has_local_and_remote ):
file_filter_all = ClientMediaFileFilter.FileFilter( ClientMediaFileFilter.FILE_FILTER_ALL )

View File

@ -133,7 +133,17 @@ class EditCompoundFormulaPanel( EditSpecificFormulaPanel ):
edit_panel.Add( formulae_hbox, CC.FLAGS_EXPAND_BOTH_WAYS )
edit_panel.Add( ae_button_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, 'Newlines are removed from parsed strings right after parsing, before string processing.', ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
if collapse_newlines:
label = 'Newlines are removed from parsed strings right after parsing, before string processing.'
else:
label = 'Newlines are not collapsed here (probably a note parser)'
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, label, ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
edit_panel.Add( self._string_processor_button, CC.FLAGS_EXPAND_PERPENDICULAR )
#
@ -322,7 +332,17 @@ class EditContextVariableFormulaPanel( EditSpecificFormulaPanel ):
gridbox = ClientGUICommon.WrapInGrid( edit_panel, rows )
edit_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, 'Newlines are removed from parsed strings right after parsing, before string processing.', ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
if collapse_newlines:
label = 'Newlines are removed from parsed strings right after parsing, before string processing.'
else:
label = 'Newlines are not collapsed here (probably a note parser)'
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, label, ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
edit_panel.Add( self._string_processor_button, CC.FLAGS_EXPAND_PERPENDICULAR )
#
@ -847,7 +867,17 @@ class EditHTMLFormulaPanel( EditSpecificFormulaPanel ):
edit_panel.Add( tag_rules_hbox, CC.FLAGS_EXPAND_BOTH_WAYS )
edit_panel.Add( ae_button_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, 'Newlines are removed from parsed strings right after parsing, before string processing.', ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
if collapse_newlines:
label = 'Newlines are removed from parsed strings right after parsing, before string processing.'
else:
label = 'Newlines are not collapsed here (probably a note parser)'
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, label, ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
edit_panel.Add( self._string_processor_button, CC.FLAGS_EXPAND_PERPENDICULAR )
#
@ -1195,7 +1225,17 @@ class EditJSONFormulaPanel( EditSpecificFormulaPanel ):
edit_panel.Add( parse_rules_hbox, CC.FLAGS_EXPAND_BOTH_WAYS )
edit_panel.Add( ae_button_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, 'Newlines are removed from parsed strings right after parsing, before string processing.', ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
if collapse_newlines:
label = 'Newlines are removed from parsed strings right after parsing, before string processing.'
else:
label = 'Newlines are not collapsed here (probably a note parser)'
edit_panel.Add( ClientGUICommon.BetterStaticText( edit_panel, label, ellipsize_end = True ), CC.FLAGS_EXPAND_PERPENDICULAR )
edit_panel.Add( self._string_processor_button, CC.FLAGS_EXPAND_PERPENDICULAR )
#

View File

@ -331,6 +331,7 @@ class RatingNumericalSubPanel( QW.QWidget ):
class RatingIncDecSubPanel( QW.QWidget ):
def __init__( self, parent: QW.QWidget ):
@ -396,6 +397,7 @@ class RatingIncDecSubPanel( QW.QWidget ):
self._ratings_incdec.SetValue( action )
class SimpleSubPanel( QW.QWidget ):
def __init__( self, parent: QW.QWidget, shortcuts_name: str ):
@ -430,6 +432,31 @@ class SimpleSubPanel( QW.QWidget ):
#
self._thumbnail_rearrange_panel = QW.QWidget( self )
self._thumbnail_rearrange_type = ClientGUICommon.BetterChoice( self )
for rearrange_type in [
CAC.MOVE_HOME,
CAC.MOVE_END,
CAC.MOVE_LEFT,
CAC.MOVE_RIGHT,
CAC.MOVE_TO_FOCUS
]:
self._thumbnail_rearrange_type.addItem( CAC.move_enum_to_str_lookup[ rearrange_type ], rearrange_type )
#
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, self._thumbnail_rearrange_type, CC.FLAGS_EXPAND_BOTH_WAYS )
self._thumbnail_rearrange_panel.setLayout( vbox )
#
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, self._duplicate_type, CC.FLAGS_EXPAND_BOTH_WAYS )
@ -530,6 +557,7 @@ class SimpleSubPanel( QW.QWidget ):
QP.AddToLayout( vbox, self._seek_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
QP.AddToLayout( vbox, self._thumbnail_move_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
QP.AddToLayout( vbox, self._file_filter_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
QP.AddToLayout( vbox, self._thumbnail_rearrange_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.setLayout( vbox )
@ -542,6 +570,7 @@ class SimpleSubPanel( QW.QWidget ):
action = self._simple_actions.GetValue()
self._duplicates_type_panel.setVisible( action == CAC.SIMPLE_REARRANGE_THUMBNAILS )
self._duplicates_type_panel.setVisible( action == CAC.SIMPLE_SHOW_DUPLICATES )
self._seek_panel.setVisible( action == CAC.SIMPLE_MEDIA_SEEK_DELTA )
self._thumbnail_move_panel.setVisible( action == CAC.SIMPLE_MOVE_THUMBNAIL_FOCUS )
@ -586,6 +615,12 @@ class SimpleSubPanel( QW.QWidget ):
simple_data = file_filter
elif action == CAC.SIMPLE_REARRANGE_THUMBNAILS:
rearrange_type = self._thumbnail_rearrange_type.GetValue()
simple_data = ( CAC.REARRANGE_THUMBNAILS_TYPE_COMMAND, rearrange_type )
else:
simple_data = None
@ -633,6 +668,12 @@ class SimpleSubPanel( QW.QWidget ):
self._file_filter.SetValue( file_filter )
elif action == CAC.SIMPLE_REARRANGE_THUMBNAILS:
( rearrange_type, rearrange_data ) = command.GetSimpleData()
self._thumbnail_rearrange_type.SetValue( rearrange_data )
self._UpdateControls()
@ -742,6 +783,7 @@ class TagSubPanel( QW.QWidget ):
self._tag_value.setText( tag )
class ApplicationCommandWidget( ClientGUIScrolledPanels.EditPanel ):
PANEL_SIMPLE = 0

View File

@ -552,7 +552,7 @@ class FileImportJob( object ):
hashes = { self.GetHash() }
content_update_package = ClientContentUpdates.ContentUpdatePackage.AddContentUpdate( CC.COMBINED_LOCAL_FILE_SERVICE_KEY, ClientContentUpdates.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, hashes ) )
content_update_package = ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdate( CC.COMBINED_LOCAL_FILE_SERVICE_KEY, ClientContentUpdates.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, hashes ) )
HG.client_controller.Write( 'content_updates', content_update_package )

View File

@ -0,0 +1,28 @@
from qtpy import QtWidgets as QW
from hydrus.core.interfaces import HydrusControllerInterface
from hydrus.client import ClientThreading
class ClientControllerInterface( HydrusControllerInterface.HydrusControllerInterface ):
def CallBlockingToQt( self, win: QW.QWidget, func: callable, *args, **kwargs ) -> object:
raise NotImplementedError()
def CallAfterQtSafe( self, window: QW.QWidget, label: str, func: callable, *args, **kwargs ) -> ClientThreading.QtAwareJob:
raise NotImplementedError()
def CallLaterQtSafe( self, window: QW.QWidget, initial_delay: float, label: str, func: callable, *args, **kwargs ) -> ClientThreading.QtAwareJob:
raise NotImplementedError()
def CallRepeatingQtSafe( self, window: QW.QWidget, initial_delay: float, period: float, label: str, func: callable, *args, **kwargs ) -> ClientThreading.QtAwareRepeatingJob:
raise NotImplementedError()

View File

View File

@ -114,6 +114,82 @@ def GetMediaResultsTagCount( media_results, tag_service_key, tag_display_type ):
return GetTagsManagersTagCount( tags_managers, tag_service_key, tag_display_type )
def GetMediasFiletypeSummaryString( medias: typing.Collection[ "Media" ] ):
def GetDescriptor( plural, classes, num_collections ):
suffix = 's' if plural else ''
if len( classes ) == 0:
return 'file' + suffix
if len( classes ) == 1:
( mime, ) = classes
if mime == HC.APPLICATION_HYDRUS_CLIENT_COLLECTION:
collections_suffix = 's' if num_collections > 1 else ''
return 'file{} in {} collection{}'.format( suffix, HydrusData.ToHumanInt( num_collections ), collections_suffix )
else:
return HC.mime_string_lookup[ mime ] + suffix
if len( classes.difference( HC.IMAGES ) ) == 0:
return 'image' + suffix
elif len( classes.difference( HC.ANIMATIONS ) ) == 0:
return 'animation' + suffix
elif len( classes.difference( HC.VIDEO ) ) == 0:
return 'video' + suffix
elif len( classes.difference( HC.AUDIO ) ) == 0:
return 'audio file' + suffix
else:
return 'file' + suffix
num_files = sum( [ media.GetNumFiles() for media in medias ] )
if num_files > 1000:
filetype_summary = 'files'
else:
mimes = { media.GetMime() for media in medias }
if HC.APPLICATION_HYDRUS_CLIENT_COLLECTION in mimes:
num_collections = len( [ media for media in medias if isinstance( media, MediaCollection ) ] )
else:
num_collections = 0
plural = len( medias ) > 1 or sum( ( m.GetNumFiles() for m in medias ) ) > 1
filetype_summary = GetDescriptor( plural, mimes, num_collections )
return f'{HydrusData.ToHumanInt( num_files )} {filetype_summary}'
def GetMediasTagCount( pool, tag_service_key, tag_display_type ):
tags_managers = []
@ -1091,6 +1167,13 @@ class MediaList( object ):
return len( self._sorted_media ) == 0
def MoveMedia( self, medias: typing.List[ Media ], insertion_index: int ):
self._sorted_media.move_items( medias, insertion_index )
self._RecalcHashes()
def ProcessContentUpdatePackage( self, full_content_update_package: ClientContentUpdates.ContentUpdatePackage ):
if not full_content_update_package.HasContent():
@ -2950,6 +3033,9 @@ class SortedList( object ):
def index( self, item ):
"""
This is fast!
"""
if self._indices_dirty:
@ -2968,11 +3054,71 @@ class SortedList( object ):
return result
def insert_items( self, items ):
def insert_items( self, items, insertion_index = None ):
self.append_items( items )
if insertion_index is None:
self.append_items( items )
self.sort()
else:
# don't forget we can insert elements in the final slot for an append, where index >= len( muh_list )
for ( i, item ) in enumerate( items ):
self._sorted_list.insert( insertion_index + i, item )
self._DirtyIndices()
self.sort()
def move_items( self, new_items: typing.List, insertion_index: int ):
items_to_move = []
items_before_insertion_index = 0
if insertion_index < 0:
insertion_index = max( 0, len( self._sorted_list ) + ( insertion_index + 1 ) )
for new_item in new_items:
try:
index = self.index( new_item )
except HydrusExceptions.DataMissing:
continue
items_to_move.append( new_item )
if index < insertion_index:
items_before_insertion_index += 1
if items_before_insertion_index > 0: # i.e. we are moving to the right
items_before_insertion_index -= 1
adjusted_insertion_index = insertion_index# - items_before_insertion_index
if len( items_to_move ) == 0:
return
self.remove_items( items_to_move )
self.insert_items( items_to_move, insertion_index = adjusted_insertion_index )
def remove_items( self, items ):

View File

@ -38,6 +38,7 @@ from hydrus.core.networking import HydrusServerResources
from hydrus.client import ClientAPI
from hydrus.client import ClientOptions
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientGlobals as CG
from hydrus.client import ClientLocation
from hydrus.client import ClientThreading
from hydrus.client import ClientTime
@ -154,7 +155,7 @@ def CheckFileService( file_service_key: bytes ):
try:
service = HG.client_controller.services_manager.GetService( file_service_key )
service = CG.client_controller.services_manager.GetService( file_service_key )
except:
@ -173,7 +174,7 @@ def CheckTagService( tag_service_key: bytes ):
try:
service = HG.client_controller.services_manager.GetService( tag_service_key )
service = CG.client_controller.services_manager.GetService( tag_service_key )
except:
@ -226,7 +227,7 @@ def GetServicesDict():
HC.LOCAL_FILE_TRASH_DOMAIN
]
services = HG.client_controller.services_manager.GetServices( service_types )
services = CG.client_controller.services_manager.GetServices( service_types )
services_dict = {}
@ -264,7 +265,7 @@ def GetServiceKeyFromName( service_name: str ):
try:
service_key = HG.client_controller.services_manager.GetServiceKeyFromName( HC.ALL_SERVICES, service_name )
service_key = CG.client_controller.services_manager.GetServiceKeyFromName( HC.ALL_SERVICES, service_name )
except HydrusExceptions.DataMissing:
@ -739,7 +740,7 @@ def ParseHashes( request: HydrusServerRequest.HydrusRequest, optional = False ):
hash_id = request.parsed_request_args.GetValue( 'file_id', int )
hash_ids_to_hashes = HG.client_controller.Read( 'hash_ids_to_hashes', hash_ids = [ hash_id ] )
hash_ids_to_hashes = CG.client_controller.Read( 'hash_ids_to_hashes', hash_ids = [ hash_id ] )
if len( hash_ids_to_hashes ) > 0:
@ -753,7 +754,7 @@ def ParseHashes( request: HydrusServerRequest.HydrusRequest, optional = False ):
hash_ids = request.parsed_request_args.GetValue( 'file_ids', list, expected_list_type = int )
hash_ids_to_hashes = HG.client_controller.Read( 'hash_ids_to_hashes', hash_ids = hash_ids )
hash_ids_to_hashes = CG.client_controller.Read( 'hash_ids_to_hashes', hash_ids = hash_ids )
hashes.extend( [ hash_ids_to_hashes[ hash_id ] for hash_id in hash_ids ] )
@ -1016,15 +1017,15 @@ class HydrusResourceBooruFile( HydrusResourceBooru ):
is_attachment = request.parsed_request_args.GetValue( 'download', bool, default_value = False )
HG.client_controller.local_booru_manager.CheckFileAuthorised( share_key, hash )
CG.client_controller.local_booru_manager.CheckFileAuthorised( share_key, hash )
media_result = HG.client_controller.local_booru_manager.GetMediaResult( share_key, hash )
media_result = CG.client_controller.local_booru_manager.GetMediaResult( share_key, hash )
try:
mime = media_result.GetMime()
path = HG.client_controller.client_files_manager.GetFilePath( hash, mime )
path = CG.client_controller.client_files_manager.GetFilePath( hash, mime )
except HydrusExceptions.FileMissingException:
@ -1045,7 +1046,7 @@ class HydrusResourceBooruGallery( HydrusResourceBooru ):
share_key = request.parsed_request_args.GetValue( 'share_key', bytes )
local_booru_manager = HG.client_controller.local_booru_manager
local_booru_manager = CG.client_controller.local_booru_manager
local_booru_manager.CheckShareAuthorised( share_key )
@ -1127,7 +1128,7 @@ class HydrusResourceBooruPage( HydrusResourceBooru ):
share_key = request.parsed_request_args.GetValue( 'share_key', bytes )
hash = request.parsed_request_args.GetValue( 'hash', bytes )
local_booru_manager = HG.client_controller.local_booru_manager
local_booru_manager = CG.client_controller.local_booru_manager
local_booru_manager.CheckFileAuthorised( share_key, hash )
@ -1216,7 +1217,7 @@ class HydrusResourceBooruThumbnail( HydrusResourceBooru ):
share_key = request.parsed_request_args.GetValue( 'share_key', bytes )
hash = request.parsed_request_args.GetValue( 'hash', bytes )
local_booru_manager = HG.client_controller.local_booru_manager
local_booru_manager = CG.client_controller.local_booru_manager
local_booru_manager.CheckFileAuthorised( share_key, hash )
@ -1230,7 +1231,7 @@ class HydrusResourceBooruThumbnail( HydrusResourceBooru ):
try:
path = HG.client_controller.client_files_manager.GetThumbnailPath( media_result )
path = CG.client_controller.client_files_manager.GetThumbnailPath( media_result )
if not os.path.exists( path ):
@ -1306,7 +1307,7 @@ class HydrusResourceClientAPI( HydrusServerResources.HydrusResource ):
HydrusServerResources.HydrusResource._reportRequestStarted( self, request )
HG.client_controller.ResetIdleTimerFromClientAPI()
CG.client_controller.ResetIdleTimerFromClientAPI()
def _checkService( self, request: HydrusServerRequest.HydrusRequest ):
@ -1424,7 +1425,7 @@ class HydrusResourceClientAPIRestricted( HydrusResourceClientAPI ):
try:
api_permissions = HG.client_controller.client_api_manager.GetPermissions( access_key )
api_permissions = CG.client_controller.client_api_manager.GetPermissions( access_key )
except HydrusExceptions.DataMissing as e:
@ -1482,7 +1483,7 @@ class HydrusResourceClientAPIRestricted( HydrusResourceClientAPI ):
try:
access_key = HG.client_controller.client_api_manager.GetAccessKey( session_key )
access_key = CG.client_controller.client_api_manager.GetAccessKey( session_key )
except HydrusExceptions.DataMissing as e:
@ -1504,7 +1505,7 @@ class HydrusResourceClientAPIRestrictedAccountSessionKey( HydrusResourceClientAP
def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ):
new_session_key = HG.client_controller.client_api_manager.GenerateSessionKey( request.client_api_permissions.GetAccessKey() )
new_session_key = CG.client_controller.client_api_manager.GenerateSessionKey( request.client_api_permissions.GetAccessKey() )
body_dict = {}
@ -1583,7 +1584,7 @@ class HydrusResourceClientAPIRestrictedGetService( HydrusResourceClientAPIRestri
try:
service_key = HG.client_controller.services_manager.GetServiceKeyFromName( allowed_service_types, service_name )
service_key = CG.client_controller.services_manager.GetServiceKeyFromName( allowed_service_types, service_name )
except HydrusExceptions.DataMissing:
@ -1597,7 +1598,7 @@ class HydrusResourceClientAPIRestrictedGetService( HydrusResourceClientAPIRestri
try:
service = HG.client_controller.services_manager.GetService( service_key )
service = CG.client_controller.services_manager.GetService( service_key )
except HydrusExceptions.DataMissing:
@ -1662,7 +1663,7 @@ class HydrusResourceClientAPIRestrictedGetServices( HydrusResourceClientAPIRestr
for ( service_types, name ) in jobs:
services = HG.client_controller.services_manager.GetServices( service_types )
services = CG.client_controller.services_manager.GetServices( service_types )
services_list = []
@ -1726,7 +1727,7 @@ class HydrusResourceClientAPIRestrictedAddFilesAddFile( HydrusResourceClientAPIR
( os_file_handle, temp_path ) = request.temp_file_info
file_import_options = HG.client_controller.new_options.GetDefaultFileImportOptions( FileImportOptions.IMPORT_TYPE_QUIET )
file_import_options = CG.client_controller.new_options.GetDefaultFileImportOptions( FileImportOptions.IMPORT_TYPE_QUIET )
file_import_job = ClientImportFiles.FileImportJob( temp_path, file_import_options )
@ -1773,7 +1774,7 @@ class HydrusResourceClientAPIRestrictedAddFilesArchiveFiles( HydrusResourceClien
content_update_package = ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdate( CC.COMBINED_LOCAL_FILE_SERVICE_KEY, content_update )
HG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
CG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -1797,11 +1798,11 @@ class HydrusResourceClientAPIRestrictedAddFilesDeleteFiles( HydrusResourceClient
hashes = set( ParseHashes( request ) )
location_context.LimitToServiceTypes( HG.client_controller.services_manager.GetServiceType, ( HC.COMBINED_LOCAL_FILE, HC.COMBINED_LOCAL_MEDIA, HC.LOCAL_FILE_DOMAIN ) )
location_context.LimitToServiceTypes( CG.client_controller.services_manager.GetServiceType, ( HC.COMBINED_LOCAL_FILE, HC.COMBINED_LOCAL_MEDIA, HC.LOCAL_FILE_DOMAIN ) )
if HG.client_controller.new_options.GetBoolean( 'delete_lock_for_archived_files' ):
if CG.client_controller.new_options.GetBoolean( 'delete_lock_for_archived_files' ):
media_results = HG.client_controller.Read( 'media_results', hashes )
media_results = CG.client_controller.Read( 'media_results', hashes )
undeletable_media_results = [ m for m in media_results if m.IsDeleteLocked() ]
@ -1821,7 +1822,7 @@ class HydrusResourceClientAPIRestrictedAddFilesDeleteFiles( HydrusResourceClient
content_update_package = ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdate( service_key, content_update )
HG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
CG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -1840,7 +1841,7 @@ class HydrusResourceClientAPIRestrictedAddFilesUnarchiveFiles( HydrusResourceCli
content_update_package = ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdate( CC.COMBINED_LOCAL_FILE_SERVICE_KEY, content_update )
HG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
CG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -1855,7 +1856,7 @@ class HydrusResourceClientAPIRestrictedAddFilesUndeleteFiles( HydrusResourceClie
hashes = set( ParseHashes( request ) )
location_context.LimitToServiceTypes( HG.client_controller.services_manager.GetServiceType, ( HC.LOCAL_FILE_DOMAIN, HC.COMBINED_LOCAL_MEDIA ) )
location_context.LimitToServiceTypes( CG.client_controller.services_manager.GetServiceType, ( HC.LOCAL_FILE_DOMAIN, HC.COMBINED_LOCAL_MEDIA ) )
content_update = ClientContentUpdates.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_UNDELETE, hashes )
@ -1863,7 +1864,7 @@ class HydrusResourceClientAPIRestrictedAddFilesUndeleteFiles( HydrusResourceClie
content_update_package = ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdate( service_key, content_update )
HG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
CG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -1952,7 +1953,7 @@ class HydrusResourceClientAPIRestrictedAddNotesSetNotes( HydrusResourceClientAPI
hash_id = request.parsed_request_args.GetValue( 'file_id', int )
hash_ids_to_hashes = HG.client_controller.Read( 'hash_ids_to_hashes', hash_ids = [ hash_id ] )
hash_ids_to_hashes = CG.client_controller.Read( 'hash_ids_to_hashes', hash_ids = [ hash_id ] )
hash = hash_ids_to_hashes[ hash_id ]
@ -1983,7 +1984,7 @@ class HydrusResourceClientAPIRestrictedAddNotesSetNotes( HydrusResourceClientAPI
note_import_options.SetExtendExistingNoteIfPossible( extend_existing_note_if_possible )
note_import_options.SetConflictResolution( conflict_resolution )
media_result = HG.client_controller.Read( 'media_result', hash )
media_result = CG.client_controller.Read( 'media_result', hash )
existing_names_to_notes = media_result.GetNotesManager().GetNamesToNotes()
@ -1998,7 +1999,7 @@ class HydrusResourceClientAPIRestrictedAddNotesSetNotes( HydrusResourceClientAPI
content_update_package = ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdates( CC.LOCAL_NOTES_SERVICE_KEY, content_updates )
HG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
CG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
body_dict = {}
@ -2025,7 +2026,7 @@ class HydrusResourceClientAPIRestrictedAddNotesDeleteNotes( HydrusResourceClient
hash_id = request.parsed_request_args.GetValue( 'file_id', int )
hash_ids_to_hashes = HG.client_controller.Read( 'hash_ids_to_hashes', hash_ids = [ hash_id ] )
hash_ids_to_hashes = CG.client_controller.Read( 'hash_ids_to_hashes', hash_ids = [ hash_id ] )
hash = hash_ids_to_hashes[ hash_id ]
@ -2040,7 +2041,7 @@ class HydrusResourceClientAPIRestrictedAddNotesDeleteNotes( HydrusResourceClient
content_update_package = ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdates( CC.LOCAL_NOTES_SERVICE_KEY, content_updates )
HG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
CG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -2232,7 +2233,7 @@ class HydrusResourceClientAPIRestrictedAddTagsAddTags( HydrusResourceClientAPIRe
if content_update_package.HasContent():
HG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
CG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -2252,7 +2253,7 @@ class HydrusResourceClientAPIRestrictedAddTagsSearchTags( HydrusResourceClientAP
def _GetParsedAutocompleteText( self, search, tag_service_key ) -> ClientSearchAutocomplete.ParsedAutocompleteText:
tag_autocomplete_options = HG.client_controller.tag_display_manager.GetTagAutocompleteOptions( tag_service_key )
tag_autocomplete_options = CG.client_controller.tag_display_manager.GetTagAutocompleteOptions( tag_service_key )
collapse_search_characters = True
@ -2283,7 +2284,7 @@ class HydrusResourceClientAPIRestrictedAddTagsSearchTags( HydrusResourceClientAP
search_namespaces_into_full_tags = parsed_autocomplete_text.GetTagAutocompleteOptions().SearchNamespacesIntoFullTags()
predicates = HG.client_controller.Read( 'autocomplete_predicates', tag_display_type, file_search_context, search_text = autocomplete_search_text, job_status = job_status, search_namespaces_into_full_tags = search_namespaces_into_full_tags )
predicates = CG.client_controller.Read( 'autocomplete_predicates', tag_display_type, file_search_context, search_text = autocomplete_search_text, job_status = job_status, search_namespaces_into_full_tags = search_namespaces_into_full_tags )
display_tag_service_key = tag_context.display_service_key
@ -2337,7 +2338,7 @@ class HydrusResourceClientAPIRestrictedAddTagsGetTagSiblingsParents( HydrusResou
tags = HydrusTags.CleanTags( tags )
tags_to_service_keys_to_siblings_and_parents = HG.client_controller.Read( 'tag_siblings_and_parents_lookup', tags )
tags_to_service_keys_to_siblings_and_parents = CG.client_controller.Read( 'tag_siblings_and_parents_lookup', tags )
tags_dict = {}
@ -2440,7 +2441,7 @@ class HydrusResourceClientAPIRestrictedAddURLsAssociateURL( HydrusResourceClient
domain_manager = HG.client_controller.network_engine.domain_manager
domain_manager = CG.client_controller.network_engine.domain_manager
try:
@ -2481,7 +2482,7 @@ class HydrusResourceClientAPIRestrictedAddURLsAssociateURL( HydrusResourceClient
if content_update_package.HasContent():
HG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
CG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -2505,14 +2506,14 @@ class HydrusResourceClientAPIRestrictedAddURLsGetURLFiles( HydrusResourceClientA
try:
normalised_url = HG.client_controller.network_engine.domain_manager.NormaliseURL( url )
normalised_url = CG.client_controller.network_engine.domain_manager.NormaliseURL( url )
except HydrusExceptions.URLClassException as e:
raise HydrusExceptions.BadRequestException( e )
url_statuses = HG.client_controller.Read( 'url_statuses', normalised_url )
url_statuses = CG.client_controller.Read( 'url_statuses', normalised_url )
json_happy_url_statuses = []
@ -2567,9 +2568,9 @@ class HydrusResourceClientAPIRestrictedAddURLsGetURLInfo( HydrusResourceClientAP
try:
normalised_url = HG.client_controller.network_engine.domain_manager.NormaliseURL( url )
normalised_url = CG.client_controller.network_engine.domain_manager.NormaliseURL( url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( normalised_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = CG.client_controller.network_engine.domain_manager.GetURLParseCapability( normalised_url )
except HydrusExceptions.URLClassException as e:
@ -2654,12 +2655,12 @@ class HydrusResourceClientAPIRestrictedAddURLsImportURL( HydrusResourceClientAPI
def do_it():
return HG.client_controller.gui.ImportURLFromAPI( url, filterable_tags, additional_service_keys_to_tags, destination_page_name, destination_page_key, show_destination_page )
return CG.client_controller.gui.ImportURLFromAPI( url, filterable_tags, additional_service_keys_to_tags, destination_page_name, destination_page_key, show_destination_page )
try:
( normalised_url, result_text ) = HG.client_controller.CallBlockingToQt( HG.client_controller.gui, do_it )
( normalised_url, result_text ) = CG.client_controller.CallBlockingToQt( CG.client_controller.gui, do_it )
except HydrusExceptions.URLClassException as e:
@ -2706,7 +2707,7 @@ class HydrusResourceClientAPIRestrictedEditRatingsSetRating( HydrusResourceClien
rating = request.parsed_request_args[ 'rating' ]
rating_service = HG.client_controller.services_manager.GetService( rating_service_key )
rating_service = CG.client_controller.services_manager.GetService( rating_service_key )
rating_service_type = rating_service.GetServiceType()
@ -2771,7 +2772,7 @@ class HydrusResourceClientAPIRestrictedEditRatingsSetRating( HydrusResourceClien
content_update_package = ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdate( rating_service_key, content_update )
HG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
CG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -2798,7 +2799,7 @@ class HydrusResourceClientAPIRestrictedEditTimesSetTime( HydrusResourceClientAPI
raise HydrusExceptions.BadRequestException( 'Did not find any hashes to apply the times to!' )
media_results = HG.client_controller.Read( 'media_results', hashes )
media_results = CG.client_controller.Read( 'media_results', hashes )
if 'timestamp' in request.parsed_request_args:
@ -2857,12 +2858,12 @@ class HydrusResourceClientAPIRestrictedEditTimesSetTime( HydrusResourceClientAPI
file_service_key = request.parsed_request_args.GetValue( 'file_service_key', bytes )
if not HG.client_controller.services_manager.ServiceExists( file_service_key ):
if not CG.client_controller.services_manager.ServiceExists( file_service_key ):
raise HydrusExceptions.BadRequestException( 'Sorry, do not know that service!' )
if HG.client_controller.services_manager.GetServiceType( file_service_key ) not in HC.REAL_FILE_SERVICES:
if CG.client_controller.services_manager.GetServiceType( file_service_key ) not in HC.REAL_FILE_SERVICES:
raise HydrusExceptions.BadRequestException( 'Sorry, you have to specify a file service service key!' )
@ -2915,7 +2916,7 @@ class HydrusResourceClientAPIRestrictedEditTimesSetTime( HydrusResourceClientAPI
content_update_package = ClientContentUpdates.ContentUpdatePackage.STATICCreateFromContentUpdates( CC.COMBINED_LOCAL_FILE_SERVICE_KEY, content_updates )
HG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
CG.client_controller.WriteSynchronous( 'content_updates', content_update_package )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -2996,7 +2997,7 @@ class HydrusResourceClientAPIRestrictedGetFilesSearchFiles( HydrusResourceClient
request.disconnect_callables.append( job_status.Cancel )
hash_ids = HG.client_controller.Read( 'file_query_ids', file_search_context, job_status = job_status, sort_by = sort_by, apply_implicit_limit = False )
hash_ids = CG.client_controller.Read( 'file_query_ids', file_search_context, job_status = job_status, sort_by = sort_by, apply_implicit_limit = False )
request.client_api_permissions.SetLastSearchResults( hash_ids )
@ -3005,7 +3006,7 @@ class HydrusResourceClientAPIRestrictedGetFilesSearchFiles( HydrusResourceClient
if return_hashes:
hash_ids_to_hashes = HG.client_controller.Read( 'hash_ids_to_hashes', hash_ids = hash_ids )
hash_ids_to_hashes = CG.client_controller.Read( 'hash_ids_to_hashes', hash_ids = hash_ids )
# maintain sort
body_dict[ 'hashes' ] = [ hash_ids_to_hashes[ hash_id ].hex() for hash_id in hash_ids ]
@ -3035,7 +3036,7 @@ class HydrusResourceClientAPIRestrictedGetFilesGetFile( HydrusResourceClientAPIR
request.client_api_permissions.CheckPermissionToSeeFiles( ( file_id, ) )
( media_result, ) = HG.client_controller.Read( 'media_results_from_ids', ( file_id, ) )
( media_result, ) = CG.client_controller.Read( 'media_results_from_ids', ( file_id, ) )
elif 'hash' in request.parsed_request_args:
@ -3043,7 +3044,7 @@ class HydrusResourceClientAPIRestrictedGetFilesGetFile( HydrusResourceClientAPIR
hash = request.parsed_request_args.GetValue( 'hash', bytes )
media_result = HG.client_controller.Read( 'media_result', hash )
media_result = CG.client_controller.Read( 'media_result', hash )
else:
@ -3060,7 +3061,7 @@ class HydrusResourceClientAPIRestrictedGetFilesGetFile( HydrusResourceClientAPIR
hash = media_result.GetHash()
mime = media_result.GetMime()
path = HG.client_controller.client_files_manager.GetFilePath( hash, mime )
path = CG.client_controller.client_files_manager.GetFilePath( hash, mime )
if not os.path.exists( path ):
@ -3093,7 +3094,7 @@ class HydrusResourceClientAPIRestrictedGetFilesGetRenderedFile( HydrusResourceCl
request.client_api_permissions.CheckPermissionToSeeFiles( ( file_id, ) )
( media_result, ) = HG.client_controller.Read( 'media_results_from_ids', ( file_id, ) )
( media_result, ) = CG.client_controller.Read( 'media_results_from_ids', ( file_id, ) )
elif 'hash' in request.parsed_request_args:
@ -3101,7 +3102,7 @@ class HydrusResourceClientAPIRestrictedGetFilesGetRenderedFile( HydrusResourceCl
hash = request.parsed_request_args.GetValue( 'hash', bytes )
media_result = HG.client_controller.Read( 'media_result', hash )
media_result = CG.client_controller.Read( 'media_result', hash )
else:
@ -3118,7 +3119,7 @@ class HydrusResourceClientAPIRestrictedGetFilesGetRenderedFile( HydrusResourceCl
raise HydrusExceptions.BadRequestException('Requested file is not an image!')
renderer: ClientRendering.ImageRenderer = HG.client_controller.GetCache( 'images' ).GetImageRenderer( media_result )
renderer: ClientRendering.ImageRenderer = CG.client_controller.GetCache( 'images' ).GetImageRenderer( media_result )
while not renderer.IsReady():
@ -3184,7 +3185,7 @@ class HydrusResourceClientAPIRestrictedGetFilesFileHashes( HydrusResourceClientA
CheckHashLength( source_hashes, hash_type = source_hash_type )
source_to_desired = HG.client_controller.Read( 'file_hashes', source_hashes, source_hash_type, desired_hash_type )
source_to_desired = CG.client_controller.Read( 'file_hashes', source_hashes, source_hash_type, desired_hash_type )
encoded_source_to_desired = { source_hash.hex() : desired_hash.hex() for ( source_hash, desired_hash ) in source_to_desired.items() }
@ -3235,7 +3236,7 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
hashes = ParseHashes( request )
hash_ids_to_hashes = HG.client_controller.Read( 'hash_ids_to_hashes', hashes = hashes, create_new_hash_ids = create_new_file_ids )
hash_ids_to_hashes = CG.client_controller.Read( 'hash_ids_to_hashes', hashes = hashes, create_new_hash_ids = create_new_file_ids )
hashes_to_hash_ids = { hash : hash_id for ( hash_id, hash ) in hash_ids_to_hashes.items() }
@ -3268,7 +3269,7 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
elif only_return_basic_information:
file_info_managers = HG.client_controller.Read( 'file_info_managers_from_ids', hash_ids )
file_info_managers = CG.client_controller.Read( 'file_info_managers_from_ids', hash_ids )
hashes_to_file_info_managers = { file_info_manager.hash : file_info_manager for file_info_manager in file_info_managers }
@ -3318,11 +3319,11 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
else:
media_results = HG.client_controller.Read( 'media_results_from_ids', hash_ids )
media_results = CG.client_controller.Read( 'media_results_from_ids', hash_ids )
hashes_to_media_results = { media_result.GetFileInfoManager().hash : media_result for media_result in media_results }
services_manager = HG.client_controller.services_manager
services_manager = CG.client_controller.services_manager
rating_service_keys = services_manager.GetServiceKeys( HC.RATINGS_SERVICES )
tag_service_keys = services_manager.GetServiceKeys( HC.ALL_TAG_SERVICES )
@ -3331,9 +3332,9 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
ipfs_service_keys = services_manager.GetServiceKeys( ( HC.IPFS, ) )
thumbnail_bounding_dimensions = HG.client_controller.options[ 'thumbnail_dimensions' ]
thumbnail_scale_type = HG.client_controller.new_options.GetInteger( 'thumbnail_scale_type' )
thumbnail_dpr_percent = HG.client_controller.new_options.GetInteger( 'thumbnail_dpr_percent' )
thumbnail_bounding_dimensions = CG.client_controller.options[ 'thumbnail_dimensions' ]
thumbnail_scale_type = CG.client_controller.new_options.GetInteger( 'thumbnail_scale_type' )
thumbnail_dpr_percent = CG.client_controller.new_options.GetInteger( 'thumbnail_dpr_percent' )
for hash in hashes:
@ -3461,9 +3462,9 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
try:
normalised_url = HG.client_controller.network_engine.domain_manager.NormaliseURL( known_url )
normalised_url = CG.client_controller.network_engine.domain_manager.NormaliseURL( known_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( normalised_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = CG.client_controller.network_engine.domain_manager.GetURLParseCapability( normalised_url )
except HydrusExceptions.URLClassException as e:
@ -3606,7 +3607,7 @@ class HydrusResourceClientAPIRestrictedGetFilesGetThumbnail( HydrusResourceClien
request.client_api_permissions.CheckPermissionToSeeFiles( ( file_id, ) )
( media_result, ) = HG.client_controller.Read( 'media_results_from_ids', ( file_id, ) )
( media_result, ) = CG.client_controller.Read( 'media_results_from_ids', ( file_id, ) )
elif 'hash' in request.parsed_request_args:
@ -3614,7 +3615,7 @@ class HydrusResourceClientAPIRestrictedGetFilesGetThumbnail( HydrusResourceClien
hash = request.parsed_request_args.GetValue( 'hash', bytes )
media_result = HG.client_controller.Read( 'media_result', hash )
media_result = CG.client_controller.Read( 'media_result', hash )
else:
@ -3632,7 +3633,7 @@ class HydrusResourceClientAPIRestrictedGetFilesGetThumbnail( HydrusResourceClien
try:
path = HG.client_controller.client_files_manager.GetThumbnailPath( media_result )
path = CG.client_controller.client_files_manager.GetThumbnailPath( media_result )
if not os.path.exists( path ):
@ -3679,7 +3680,7 @@ class HydrusResourceClientAPIRestrictedManageCookiesGetCookies( HydrusResourceCl
network_context = ClientNetworkingContexts.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, domain )
session = HG.client_controller.network_engine.session_manager.GetSession( network_context )
session = CG.client_controller.network_engine.session_manager.GetSession( network_context )
body_cookies_list = []
@ -3736,7 +3737,7 @@ class HydrusResourceClientAPIRestrictedManageCookiesSetCookies( HydrusResourceCl
network_context = ClientNetworkingContexts.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, domain )
session = HG.client_controller.network_engine.session_manager.GetSession( network_context )
session = CG.client_controller.network_engine.session_manager.GetSession( network_context )
if value is None:
@ -3751,10 +3752,10 @@ class HydrusResourceClientAPIRestrictedManageCookiesSetCookies( HydrusResourceCl
ClientNetworkingFunctions.AddCookieToSession( session, name, value, domain, path, expires )
HG.client_controller.network_engine.session_manager.SetSessionDirty( network_context )
CG.client_controller.network_engine.session_manager.SetSessionDirty( network_context )
if HG.client_controller.new_options.GetBoolean( 'notify_client_api_cookies' ) and len( domains_cleared ) + len( domains_set ) > 0:
if CG.client_controller.new_options.GetBoolean( 'notify_client_api_cookies' ) and len( domains_cleared ) + len( domains_set ) > 0:
domains_cleared = sorted( domains_cleared )
domains_set = sorted( domains_set )
@ -3777,7 +3778,7 @@ class HydrusResourceClientAPIRestrictedManageCookiesSetCookies( HydrusResourceCl
job_status.FinishAndDismiss( 5 )
HG.client_controller.pub( 'message', job_status )
CG.client_controller.pub( 'message', job_status )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -3799,7 +3800,7 @@ class HydrusResourceClientAPIRestrictedManageCookiesSetUserAgent( HydrusResource
user_agent = ClientDefaults.DEFAULT_USER_AGENT
HG.client_controller.network_engine.domain_manager.SetCustomHeader( ClientNetworkingContexts.GLOBAL_NETWORK_CONTEXT, 'User-Agent', value = user_agent )
CG.client_controller.network_engine.domain_manager.SetCustomHeader( ClientNetworkingContexts.GLOBAL_NETWORK_CONTEXT, 'User-Agent', value = user_agent )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -3856,7 +3857,7 @@ class HydrusResourceClientAPIRestrictedManageCookiesGetHeaders( HydrusResourceCl
network_context = GenerateNetworkContextFromRequest( request )
ncs_to_header_dicts = HG.client_controller.network_engine.domain_manager.GetNetworkContextsToCustomHeaderDicts()
ncs_to_header_dicts = CG.client_controller.network_engine.domain_manager.GetNetworkContextsToCustomHeaderDicts()
body_dict = {}
@ -3898,7 +3899,7 @@ class HydrusResourceClientAPIRestrictedManageCookiesSetHeaders( HydrusResourceCl
for ( key, info_dict ) in http_header_objects.items():
ncs_to_header_dicts = HG.client_controller.network_engine.domain_manager.GetNetworkContextsToCustomHeaderDicts()
ncs_to_header_dicts = CG.client_controller.network_engine.domain_manager.GetNetworkContextsToCustomHeaderDicts()
if network_context in ncs_to_header_dicts:
@ -3942,7 +3943,7 @@ class HydrusResourceClientAPIRestrictedManageCookiesSetHeaders( HydrusResourceCl
if key in headers_dict:
HG.client_controller.network_engine.domain_manager.DeleteCustomHeader( network_context, key )
CG.client_controller.network_engine.domain_manager.DeleteCustomHeader( network_context, key )
headers_cleared.add( key )
@ -3976,7 +3977,7 @@ class HydrusResourceClientAPIRestrictedManageCookiesSetHeaders( HydrusResourceCl
if do_it:
HG.client_controller.network_engine.domain_manager.SetCustomHeader( network_context, key, value = value, approved = approved, reason = reason )
CG.client_controller.network_engine.domain_manager.SetCustomHeader( network_context, key, value = value, approved = approved, reason = reason )
@ -3994,11 +3995,11 @@ class HydrusResourceClientAPIRestrictedManageCookiesSetHeaders( HydrusResourceCl
headers_altered.add( key )
HG.client_controller.network_engine.domain_manager.SetCustomHeader( network_context, key, approved = approved, reason = reason )
CG.client_controller.network_engine.domain_manager.SetCustomHeader( network_context, key, approved = approved, reason = reason )
if HG.client_controller.new_options.GetBoolean( 'notify_client_api_cookies' ) and len( headers_cleared ) + len( headers_set ) + len( headers_altered ) > 0:
if CG.client_controller.new_options.GetBoolean( 'notify_client_api_cookies' ) and len( headers_cleared ) + len( headers_set ) + len( headers_altered ) > 0:
message_lines = [ 'Headers sent from API:' ]
@ -4025,7 +4026,7 @@ class HydrusResourceClientAPIRestrictedManageCookiesSetHeaders( HydrusResourceCl
job_status.FinishAndDismiss( 5 )
HG.client_controller.pub( 'message', job_status )
CG.client_controller.pub( 'message', job_status )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -4056,7 +4057,7 @@ class HydrusResourceClientAPIRestrictedManageDatabaseLockOff( HydrusResourceClie
raise HydrusExceptions.BadRequestException( 'The server is not busy!' )
HG.client_controller.db.PauseAndDisconnect( False )
CG.client_controller.db.PauseAndDisconnect( False )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -4074,13 +4075,13 @@ class HydrusResourceClientAPIRestrictedManageDatabaseLockOn( HydrusResourceClien
raise HydrusExceptions.BadRequestException( 'The client was already locked!' )
HG.client_controller.db.PauseAndDisconnect( True )
CG.client_controller.db.PauseAndDisconnect( True )
TIME_BLOCK = 0.25
for i in range( int( 5 / TIME_BLOCK ) ):
if not HG.client_controller.db.IsConnected():
if not CG.client_controller.db.IsConnected():
break
@ -4115,7 +4116,7 @@ class HydrusResourceClientAPIRestrictedManageDatabaseMrBones( HydrusResourceClie
request.disconnect_callables.append( job_status.Cancel )
boned_stats = HG.client_controller.Read( 'boned_stats', file_search_context = file_search_context, job_status = job_status )
boned_stats = CG.client_controller.Read( 'boned_stats', file_search_context = file_search_context, job_status = job_status )
body_dict = { 'boned_stats' : boned_stats }
@ -4136,11 +4137,11 @@ class HydrusResourceClientAPIRestrictedManageDatabaseGetClientOptions( HydrusRes
OLD_OPTIONS_DEFAULT = ClientDefaults.GetClientDefaultOptions()
old_options = HG.client_controller.options
old_options = CG.client_controller.options
old_options = { key : value for ( key, value ) in old_options.items() if key in OLD_OPTIONS_DEFAULT }
new_options: ClientOptions.ClientOptions = HG.client_controller.new_options
new_options: ClientOptions.ClientOptions = CG.client_controller.new_options
options_dict = {
'booleans' : new_options.GetAllBooleans(),
@ -4196,7 +4197,7 @@ class HydrusResourceClientAPIRestrictedManageFileRelationshipsGetRelationships(
hashes = ParseHashes( request )
# maybe in future we'll just get the media results and dump the dict from there, but whatever
hashes_to_file_duplicates = HG.client_controller.Read( 'file_relationships_for_api', location_context, hashes )
hashes_to_file_duplicates = CG.client_controller.Read( 'file_relationships_for_api', location_context, hashes )
body_dict = { 'file_relationships' : hashes_to_file_duplicates }
@ -4220,7 +4221,7 @@ class HydrusResourceClientAPIRestrictedManageFileRelationshipsGetPotentialsCount
max_hamming_distance
) = ParseDuplicateSearch( request )
count = HG.client_controller.Read( 'potential_duplicates_count', file_search_context_1, file_search_context_2, dupe_search_type, pixel_dupes_preference, max_hamming_distance )
count = CG.client_controller.Read( 'potential_duplicates_count', file_search_context_1, file_search_context_2, dupe_search_type, pixel_dupes_preference, max_hamming_distance )
body_dict = { 'potential_duplicates_count' : count }
@ -4244,9 +4245,9 @@ class HydrusResourceClientAPIRestrictedManageFileRelationshipsGetPotentialPairs(
max_hamming_distance
) = ParseDuplicateSearch( request )
max_num_pairs = request.parsed_request_args.GetValue( 'max_num_pairs', int, default_value = HG.client_controller.new_options.GetInteger( 'duplicate_filter_max_batch_size' ) )
max_num_pairs = request.parsed_request_args.GetValue( 'max_num_pairs', int, default_value = CG.client_controller.new_options.GetInteger( 'duplicate_filter_max_batch_size' ) )
filtering_pairs_media_results = HG.client_controller.Read( 'duplicate_pairs_for_filtering', file_search_context_1, file_search_context_2, dupe_search_type, pixel_dupes_preference, max_hamming_distance, max_num_pairs = max_num_pairs )
filtering_pairs_media_results = CG.client_controller.Read( 'duplicate_pairs_for_filtering', file_search_context_1, file_search_context_2, dupe_search_type, pixel_dupes_preference, max_hamming_distance, max_num_pairs = max_num_pairs )
filtering_pairs_hashes = [ ( m1.GetHash().hex(), m2.GetHash().hex() ) for ( m1, m2 ) in filtering_pairs_media_results ]
@ -4272,7 +4273,7 @@ class HydrusResourceClientAPIRestrictedManageFileRelationshipsGetRandomPotential
max_hamming_distance
) = ParseDuplicateSearch( request )
hashes = HG.client_controller.Read( 'random_potential_duplicate_hashes', file_search_context_1, file_search_context_2, dupe_search_type, pixel_dupes_preference, max_hamming_distance )
hashes = CG.client_controller.Read( 'random_potential_duplicate_hashes', file_search_context_1, file_search_context_2, dupe_search_type, pixel_dupes_preference, max_hamming_distance )
body_dict = { 'random_potential_duplicate_hashes' : [ hash.hex() for hash in hashes ] }
@ -4292,7 +4293,7 @@ class HydrusResourceClientAPIRestrictedManageFileRelationshipsSetKings( HydrusRe
for hash in hashes:
HG.client_controller.WriteSynchronous( 'duplicate_set_king', hash )
CG.client_controller.WriteSynchronous( 'duplicate_set_king', hash )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -4363,7 +4364,7 @@ class HydrusResourceClientAPIRestrictedManageFileRelationshipsSetRelationships(
all_hashes.update( ( hash_a, hash_b ) )
media_results = HG.client_controller.Read( 'media_results', all_hashes )
media_results = CG.client_controller.Read( 'media_results', all_hashes )
hashes_to_media_results = { media_result.GetHash() : media_result for media_result in media_results }
@ -4383,7 +4384,7 @@ class HydrusResourceClientAPIRestrictedManageFileRelationshipsSetRelationships(
if do_default_content_merge:
duplicate_content_merge_options = HG.client_controller.new_options.GetDuplicateContentMergeOptions( duplicate_type )
duplicate_content_merge_options = CG.client_controller.new_options.GetDuplicateContentMergeOptions( duplicate_type )
content_update_packages.append( duplicate_content_merge_options.ProcessPairIntoContentUpdatePackage( first_media, second_media, file_deletion_reason = file_deletion_reason, delete_first = delete_first, delete_second = delete_second ) )
@ -4418,7 +4419,7 @@ class HydrusResourceClientAPIRestrictedManageFileRelationshipsSetRelationships(
else:
local_file_service_keys = HG.client_controller.services_manager.GetServiceKeys( ( HC.LOCAL_FILE_DOMAIN, ) )
local_file_service_keys = CG.client_controller.services_manager.GetServiceKeys( ( HC.LOCAL_FILE_DOMAIN, ) )
deletee_service_keys = media.GetLocationsManager().GetCurrent().intersection( local_file_service_keys )
@ -4439,7 +4440,7 @@ class HydrusResourceClientAPIRestrictedManageFileRelationshipsSetRelationships(
if len( database_write_rows ) > 0:
HG.client_controller.WriteSynchronous( 'duplicate_pair_status', database_write_rows )
CG.client_controller.WriteSynchronous( 'duplicate_pair_status', database_write_rows )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -4462,7 +4463,7 @@ class HydrusResourceClientAPIRestrictedManagePagesAddFiles( HydrusResourceClient
def do_it( page_key, media_results ):
page = HG.client_controller.gui.GetPageFromPageKey( page_key )
page = CG.client_controller.gui.GetPageFromPageKey( page_key )
from hydrus.client.gui.pages import ClientGUIPages
@ -4488,11 +4489,11 @@ class HydrusResourceClientAPIRestrictedManagePagesAddFiles( HydrusResourceClient
hashes = ParseHashes( request )
media_results = HG.client_controller.Read( 'media_results', hashes, sorted = True )
media_results = CG.client_controller.Read( 'media_results', hashes, sorted = True )
try:
HG.client_controller.CallBlockingToQt( HG.client_controller.gui, do_it, page_key, media_results )
CG.client_controller.CallBlockingToQt( CG.client_controller.gui, do_it, page_key, media_results )
except HydrusExceptions.DataMissing as e:
@ -4511,14 +4512,14 @@ class HydrusResourceClientAPIRestrictedManagePagesFocusPage( HydrusResourceClien
def do_it( page_key ):
return HG.client_controller.gui.ShowPage( page_key )
return CG.client_controller.gui.ShowPage( page_key )
page_key = request.parsed_request_args.GetValue( 'page_key', bytes )
try:
HG.client_controller.CallBlockingToQt( HG.client_controller.gui, do_it, page_key )
CG.client_controller.CallBlockingToQt( CG.client_controller.gui, do_it, page_key )
except HydrusExceptions.DataMissing as e:
@ -4536,10 +4537,10 @@ class HydrusResourceClientAPIRestrictedManagePagesGetPages( HydrusResourceClient
def do_it():
return HG.client_controller.gui.GetCurrentSessionPageAPIInfoDict()
return CG.client_controller.gui.GetCurrentSessionPageAPIInfoDict()
page_info_dict = HG.client_controller.CallBlockingToQt( HG.client_controller.gui, do_it )
page_info_dict = CG.client_controller.CallBlockingToQt( CG.client_controller.gui, do_it )
body_dict = { 'pages' : page_info_dict }
@ -4557,14 +4558,14 @@ class HydrusResourceClientAPIRestrictedManagePagesGetPageInfo( HydrusResourceCli
def do_it( page_key, simple ):
return HG.client_controller.gui.GetPageAPIInfoDict( page_key, simple )
return CG.client_controller.gui.GetPageAPIInfoDict( page_key, simple )
page_key = request.parsed_request_args.GetValue( 'page_key', bytes )
simple = request.parsed_request_args.GetValue( 'simple', bool, default_value = True )
page_info_dict = HG.client_controller.CallBlockingToQt( HG.client_controller.gui, do_it, page_key, simple )
page_info_dict = CG.client_controller.CallBlockingToQt( CG.client_controller.gui, do_it, page_key, simple )
if page_info_dict is None:
@ -4587,14 +4588,14 @@ class HydrusResourceClientAPIRestrictedManagePagesRefreshPage( HydrusResourceCli
def do_it( page_key ):
return HG.client_controller.gui.RefreshPage( page_key )
return CG.client_controller.gui.RefreshPage( page_key )
page_key = request.parsed_request_args.GetValue( 'page_key', bytes )
try:
HG.client_controller.CallBlockingToQt( HG.client_controller.gui, do_it, page_key )
CG.client_controller.CallBlockingToQt( CG.client_controller.gui, do_it, page_key )
except HydrusExceptions.DataMissing as e:
@ -4700,7 +4701,7 @@ class HydrusResourceClientAPIRestrictedManagePopupsAddPopup( HydrusResourceClien
HandlePopupUpdate( job_status, request )
HG.client_controller.pub( 'message', job_status )
CG.client_controller.pub( 'message', job_status )
body_dict = {
'job_status': JobStatusToDict( job_status )
@ -4718,7 +4719,7 @@ def GetJobStatusFromRequest( request: HydrusServerRequest.HydrusRequest ) -> Cli
job_status_key = request.parsed_request_args.GetValue( 'job_status_key', bytes )
job_status_queue: ClientGUIPopupMessages.JobStatusPopupQueue = HG.client_controller.job_status_popup_queue
job_status_queue: ClientGUIPopupMessages.JobStatusPopupQueue = CG.client_controller.job_status_popup_queue
job_status = job_status_queue.GetJobStatus( job_status_key )
@ -4743,7 +4744,7 @@ class HydrusResourceClientAPIRestrictedManagePopupsCallUserCallable( HydrusResou
raise HydrusExceptions.BadRequestException('This job doesn\'t have a user callable!')
HG.client_controller.CallBlockingToQt( HG.client_controller.gui, user_callable )
CG.client_controller.CallBlockingToQt( CG.client_controller.gui, user_callable )
response_context = HydrusServerResources.ResponseContext( 200 )
@ -4819,7 +4820,7 @@ class HydrusResourceClientAPIRestrictedManagePopupsGetPopups( HydrusResourceClie
def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ):
job_status_queue: ClientGUIPopupMessages.JobStatusPopupQueue = HG.client_controller.job_status_popup_queue
job_status_queue: ClientGUIPopupMessages.JobStatusPopupQueue = CG.client_controller.job_status_popup_queue
only_in_view = request.parsed_request_args.GetValue( 'only_in_view', bool, default_value = False )

View File

@ -105,7 +105,7 @@ options = {}
# Misc
NETWORK_VERSION = 20
SOFTWARE_VERSION = 560
SOFTWARE_VERSION = 561
CLIENT_API_VERSION = 60
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -236,16 +236,28 @@ class HydrusDB( HydrusDBBase.DBBase ):
self._ReportOverupdatedDB( version )
if version < HC.SOFTWARE_VERSION - 50:
raise HydrusExceptions.DBVersionException( 'Your current database version of hydrus ' + str( version ) + ' is too old for this software version ' + str( HC.SOFTWARE_VERSION ) + ' to update. Please try updating with version ' + str( version + 45 ) + ' or earlier first.' )
bitrot_rows = [
( 'client', 551, 558, 'millisecond timestamp conversion' )
]
for ( bitrot_db_name, latest_affected_version, safe_update_version, reason ) in bitrot_rows:
if self._db_name == bitrot_db_name and version <= latest_affected_version:
raise HydrusExceptions.DBVersionException( f'Sorry, due to a bitrot issue ({reason}), you cannot update to this software version (v{HC.SOFTWARE_VERSION}) if your database is on v{latest_affected_version} or earlier (you are on v{version}). Please download and update to v{safe_update_version} first!' )
if version < ( HC.SOFTWARE_VERSION - 15 ):
self._ReportUnderupdatedDB( version )
if version < HC.SOFTWARE_VERSION - 50:
raise Exception( 'Your current database version of hydrus ' + str( version ) + ' is too old for this software version ' + str( HC.SOFTWARE_VERSION ) + ' to update. Please try updating with version ' + str( version + 45 ) + ' or earlier first.' )
self._RepairDB( version )
while version < HC.SOFTWARE_VERSION:

View File

@ -47,6 +47,7 @@ class DBException( HydrusException ):
class DBAccessException( HydrusException ): pass
class DBCredentialsException( HydrusException ): pass
class DBVersionException( HydrusException ): pass
class FileMissingException( HydrusException ): pass
class DirectoryMissingException( HydrusException ): pass
class SerialisationException( HydrusException ): pass

View File

@ -635,8 +635,17 @@ def safe_copy2( source, dest ):
if FileModifiedTimeIsOk( mtime ):
# this overwrites on conflict without hassle
shutil.copy2( source, dest )
try:
# this overwrites on conflict without hassle
shutil.copy2( source, dest )
except PermissionError:
HydrusData.Print( f'Failed to copy2 metadata from {source} to {dest}! mtime was {HydrusTime.TimestampToPrettyTime( mtime )}' )
shutil.copy( source, dest )
else:

View File

@ -281,16 +281,24 @@ def NonFailingUnicodeDecode( data, encoding ):
return ( text, encoding )
def RemoveNewlines( text: str ) -> str:
text = ''.join( text.splitlines() )
good_lines = [ l.strip() for l in text.splitlines() ]
good_lines = [ l for l in good_lines if l != '' ]
# I really want to make this ' '.join(), but I'm sure that would break some old parsers
text = ''.join( good_lines )
return text
def SortStringsIgnoringCase( list_of_strings ):
list_of_strings.sort( key = lambda s: s.lower() )
def StripIOInputLine( t ):
t = re_leading_byte_order_mark.sub( '', t )

View File

@ -16,6 +16,7 @@ from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths
from hydrus.core import HydrusTags
from hydrus.core import HydrusText
from hydrus.core import HydrusTime
@ -5988,7 +5989,7 @@ class TestClientAPI( unittest.TestCase ):
file_path = HG.test_controller.client_files_manager.GetFilePath( hash, HC.IMAGE_PNG, check_file_exists = False )
shutil.copy2( path, file_path )
HydrusPaths.safe_copy2( path, file_path )
thumb_hash = b'\x17\xde\xd6\xee\x1b\xfa\x002\xbdj\xc0w\x92\xce5\xf0\x12~\xfe\x915\xb3\xb3tA\xac\x90F\x95\xc2T\xc5'
@ -5996,7 +5997,7 @@ class TestClientAPI( unittest.TestCase ):
thumb_path = HG.test_controller.client_files_manager._GenerateExpectedThumbnailPath( hash )
shutil.copy2( path, thumb_path )
HydrusPaths.safe_copy2( path, thumb_path )
api_permissions = set_up_permissions[ 'search_green_files' ]

View File

@ -25,6 +25,7 @@ from hydrus.client import ClientConstants as CC
from hydrus.client import ClientDefaults
from hydrus.client import ClientFiles
from hydrus.client import ClientFilesPhysical
from hydrus.client import ClientGlobals as CG
from hydrus.client import ClientOptions
from hydrus.client import ClientManagers
from hydrus.client import ClientServices
@ -195,6 +196,8 @@ class Controller( object ):
HG.server_controller = self
HG.test_controller = self
CG.client_controller = self
self.db = self
self.gui = self

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB