Version 454

closes #898, closes #876, closes #845
This commit is contained in:
Hydrus Network Developer 2021-09-08 16:41:52 -05:00
parent 2daf09a743
commit 4e416873fb
42 changed files with 1234 additions and 295 deletions

View File

@ -8,6 +8,41 @@
<div class="content">
<h3 id="changelog"><a href="#changelog">changelog</a></h3>
<ul>
<li><h3 id="version_454"><a href="#version_454">version 454</a></h3></li>
<ul>
<li>misc:</li>
<li>when a downloader page fetch gives a 500 Server Error, it is now handled better, status numbers are updated to 'failed' quickly, and I believe the post-500 downloader deadlock issue (downloaders staying as 'pending', not 'working', after several of these 500s) should also be fixed (issue #898 maybe, but many other reports also)</li>
<li>when finishing a very large archive/delete filter, the UI should not hang so much to commit the changes. the changes may be delayed a second or more, if your client is currently chugging, so if you are a user who hits F5 real quick after committing archive/delete, let me know how you get on. I've tried to mitigate for your situation, but it may not be perfect.(issue #845)</li>
<li>the Edit URL Class dialog will now refuse to OK if the API Converter fails.</li>
<li>the Edit URL Class dialog will now put up an 'are you sure?' messagebox if the URL Class matches its own example API URL</li>
<li>fixed some gallery error handling when a gallery cannot be parsed</li>
<li>improved 'cannot parse' error reporting text to include more information</li>
<li>the client api /manage_pages/add_files command should now always preserve the sort of both the file_ids or hashes parameter</li>
<li>fixed an instance where image bitmaps were being handled incorrectly (issue #876)</li>
<li>the client will no longer report shutdown work due for a repository when the work is on a currently paused content type</li>
<li>fixed some logic related to the advanced tag option of 'fetch page even if url recognised and already in db'</li>
<li>improved the error message when an unlucky duplicate boot causes a locked database error</li>
<li>I rearranged and clarified a couple of links in the 'advanced' area of the help index page</li>
<li>added some help texts and tooltips to the edit checker options dialog and fixed some borked layout</li>
<li>did a quick hack to fix some db repair code that wasn't dealing with some missing module information. I will brush the repair code up in the near future so that modules can repair themselves</li>
<li>.</li>
<li>some text colour stuff:</li>
<li>the green/red/blue duplicate comparison statements on the duplicate filter's right-hand hover window are now coloured by QSS. a new 'HydrusIndeterminate' object name handles the blue.</li>
<li>the green/red background in the advanced OR input is now governed by HydrusValid and HydrusInvalid</li>
<li>the various 'help for this panel' blue texts are also now HydrusIndeterminate</li>
<li>I have added dark or light HydrusIndeterminate and more Valid/Invalid class definitions to all the default qss styles. they may need some updates, so if you wrote any of these QSS files, please fiddle with them and send me any updates to roll in!</li>
<li>added a new user created QSS style called 'Dark Blue', thank you for the submission!</li>
<li>all text colours are now dynamically set, either QSS or through (tag colour) options! there are no more hardcoded text colours!</li>
<li>.</li>
<li>media viewer's image tile renderer:</li>
<li>a new option in 'speed and memory' allows you to change the typical square dimensions of tiles in the tile renderer. default is 768 pixels. you can go bigger to improve accuracy, but it'll cost a little memory and CPU inefficiency</li>
<li>the new 'nice' tile size calculator now tests more potential tile sizes, improving precision and reducing stretch-warping along tile borders for unusual zooms</li>
<li>the new nice size calculator is now also used when figuring out tile padding, making padding widths that do not cause stretch-warps</li>
<li>there remain slight stretch-warps in some bottom-most and right-most tiles</li>
<li>a new canvas tile debug report mode now draws blue lines on tile edges in the media viewer and spams some tile number info to popups</li>
<li>improved tile coordinate safety checks for extremely small images (e.g. 1x1) when blown up to ridiculous zooms so there are more than one tile per pixel</li>
<li>this took a bunch of work, and I am happy that I figured out some solutions, but I believe it may be impossible to get perfect answers here. please try out this new version and let me know how it goes, particularly in the duplicate viewer where warping is obvious. I think ultimately I may replace this with a single tile system that goes over the borders of your screen, eliminating the stitching problems entirely, although this will eat more memory and CPU</li>
</ul>
<li><h3 id="version_453"><a href="#version_453">version 453</a></h3></li>
<ul>
<li>qol and misc:</li>

View File

@ -37,13 +37,13 @@
<li><a href="tagging_schema.html">thoughts on a public tagging schema</a></li>
<li><a href="getting_started_subscriptions.html">getting started with subscriptions</a></li>
<li><a href="duplicates.html">filtering duplicates</a></li>
<li><a href="reducing_lag.html">reducing program lag</a></li>
</ul>
<li><h3 id="advanced"><a href="#advanced">advanced usage</a></h3></li>
<li><h3 id="advanced"><a href="#advanced">once you are used to things</a></h3></li>
<ul>
<li><a href="advanced.html">advanced usage - general</a></li>
<li><a href="advanced_siblings.html">advanced usage - tag siblings</a></li>
<li><a href="advanced_parents.html">advanced usage - tag parents</a></li>
<li><a href="advanced_siblings.html">tag siblings (making one tag appear like another)</a></li>
<li><a href="advanced_parents.html">tag parents (making one tag also add other tags)</a></li>
<li><a href="advanced.html">general clever tricks</a></li>
<li><a href="reducing_lag.html">reducing program lag</a></li>
<li><a href="database_migration.html">database migration</a></li>
<li><a href="launch_arguments.html">program launch arguments</a></li>
<li><a href="client_api.html">client api</a></li>

View File

@ -547,7 +547,7 @@ class Controller( HydrusController.HydrusController ):
self.CallBlockingToQt(self._splash, qt_code)
self.CallBlockingToQt( self._splash, qt_code )
for i in range( 10, 0, -1 ):

View File

@ -170,6 +170,11 @@ class BitmapManager( object ):
def GetQtImageFromBuffer( self, width, height, depth, data ):
if isinstance( data, memoryview ) and not data.c_contiguous:
data = data.copy()
qt_image_format = self._GetQtImageFormat( depth )
bytes_per_line = ( depth / 8 ) * width
@ -188,6 +193,11 @@ class BitmapManager( object ):
def GetQtPixmapFromBuffer( self, width, height, depth, data ):
if isinstance( data, memoryview ) and not data.c_contiguous:
data = data.copy()
qt_image_format = self._GetQtImageFormat( depth )
bytes_per_line = ( depth / 8 ) * width

View File

@ -306,6 +306,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'integers' ][ 'serverside_bandwidth_wait_time' ] = 60
self._dictionary[ 'integers' ][ 'thumbnail_visibility_scroll_percent' ] = 75
self._dictionary[ 'integers' ][ 'ideal_tile_dimension' ] = 768
self._dictionary[ 'integers' ][ 'total_pages_warning' ] = 165

View File

@ -101,6 +101,8 @@ class ImageRenderer( object ):
def _GetNumPyImage( self, clip_rect: QC.QRect, target_resolution: QC.QSize ):
clip_size = clip_rect.size()
clip_width = clip_size.width()
clip_height = clip_size.height()
( my_width, my_height ) = self._resolution
@ -119,27 +121,57 @@ class ImageRenderer( object ):
else:
if target_resolution.width() > clip_size.width():
if target_resolution.width() > clip_width:
# this is a tile that is being scaled!
# to reduce tiling artifacts, we want to oversample the clip for our tile so lanczos and friends can get good neighbour data and then crop it
# this is a tile that is being scaled up!
# to reduce tiling artifacts (disagreement at otherwise good borders), we want to oversample the clip for our tile so lanczos and friends can get good neighbour data and then crop it
# therefore, we'll figure out some padding for the clip, and then calculate what that means in the target end, and do a crop at the end
# we want to pad. that means getting a larger resolution and keeping a record of the padding
# can't pad if we are at 0 for x or y, or up against width/height max
# but if we can pad, we will get a larger clip size and then _clip_ a better target endpoint. this is tricky.
# can't pad if we are at 0 for x or y, or up against width/height max, but no problem in that case obviously
PADDING_AMOUNT = 4
# there is the float-int precision calculation problem again. we can't pick a padding of 3 in the clip if we are zooming by 150%--what do we clip off in the target: 4 or 5 pixels? whatever, we get warping
# first let's figure a decent zoom estimate:
left_padding = min( PADDING_AMOUNT, clip_rect.x() )
top_padding = min( PADDING_AMOUNT, clip_rect.y() )
right_padding = min( PADDING_AMOUNT, my_width - clip_rect.bottomRight().x() )
bottom_padding = min( PADDING_AMOUNT, my_height - clip_rect.bottomRight().y() )
zoom_estimate = target_resolution.width() / clip_width if target_resolution.width() > target_resolution.height() else target_resolution.height() / clip_height
clip_padding = QC.QMargins( left_padding, top_padding, right_padding, bottom_padding )
# now, if zoom is 150% (as a fraction, 3/2), we want a padding at the target of something that divides by 3 cleanly, or, since we are choosing at the clip in this case and will be multiplying, something that divides cleanly to 67%
# this is ugly and super inaccurate
target_padding = clip_padding * ( target_resolution.width() / clip_size.width() )
zoom_estimate_for_clip_padding_multiplier = 1 / zoom_estimate
# and we want a nice padding size limit, big enough to make clean numbers but not so big that we are rendering the 8 tiles in a square around the one we want
no_bigger_than = max( 4, ( clip_width + clip_height ) // 4 )
nice_number = HydrusData.GetNicelyDivisibleNumberForZoom( zoom_estimate_for_clip_padding_multiplier, no_bigger_than )
if nice_number != -1:
# lanczos, I think, uses 4x4 neighbour grid to render. we'll say padding of 4 pixels to be safe for now, although 2 or 3 is probably correct???
# however it works, numbers these small are not a big deal
while nice_number < 4:
nice_number *= 2
PADDING_AMOUNT = nice_number
# LIMITATION: There is still a problem here for the bottom and rightmost edges. These tiles are not squares, so the shorter/thinner dimension my be an unpleasant number and be warped _anyway_, regardless of nice padding
# perhaps there is a way to boost left or top padding so we are rendering a full square tile but still cropping our target at the end, but with a little less warping
# I played around with this idea but did not have much success
LEFT_PADDING_AMOUNT = PADDING_AMOUNT
TOP_PADDING_AMOUNT = PADDING_AMOUNT
left_padding = min( LEFT_PADDING_AMOUNT, clip_rect.x() )
top_padding = min( TOP_PADDING_AMOUNT, clip_rect.y() )
right_padding = min( PADDING_AMOUNT, ( my_width - 1 ) - clip_rect.bottomRight().x() )
bottom_padding = min( PADDING_AMOUNT, ( my_height - 1 ) - clip_rect.bottomRight().y() )
clip_padding = QC.QMargins( left_padding, top_padding, right_padding, bottom_padding )
target_padding = clip_padding * zoom_estimate
clip_rect_with_padding = clip_rect + clip_padding
@ -877,6 +909,11 @@ class HydrusBitmap( object ):
self._compressed = compressed
if isinstance( data, memoryview ) and not data.c_contiguous:
data = data.copy()
if self._compressed:
self._data = lz4.block.compress( data )

View File

@ -2191,10 +2191,17 @@ class ServiceRepository( ServiceRestricted ):
service_key = self._service_key
content_types_we_are_processing = self._GetContentTypesWeAreProcessing()
( num_local_updates, num_updates, content_types_to_num_processed_updates, content_types_to_num_updates ) = HG.client_controller.Read( 'repository_progress', service_key )
for ( content_type, num_processed_updates ) in content_types_to_num_processed_updates.items():
if content_type not in content_types_we_are_processing:
continue
if num_processed_updates < content_types_to_num_updates[ content_type ]:
return True

View File

@ -9575,7 +9575,7 @@ class DB( HydrusDB.HydrusDB ):
return predicates
def _GetMediaResults( self, hash_ids: typing.Iterable[ int ] ):
def _GetMediaResults( self, hash_ids: typing.Iterable[ int ], sorted = False ):
( cached_media_results, missing_hash_ids ) = self._weakref_media_result_cache.GetMediaResultsAndMissing( hash_ids )
@ -9706,6 +9706,13 @@ class DB( HydrusDB.HydrusDB ):
media_results = cached_media_results
if sorted:
hash_ids_to_media_results = { media_result.GetHashId() : media_result for media_result in media_results }
media_results = [ hash_ids_to_media_results[ hash_id ] for hash_id in hash_ids if hash_id in hash_ids_to_media_results ]
return media_results
@ -9724,6 +9731,11 @@ class DB( HydrusDB.HydrusDB ):
if sorted:
if len( hashes ) > len( query_hash_ids ):
hashes = HydrusData.DedupeList( hashes )
hashes_to_media_results = { media_result.GetHash() : media_result for media_result in media_results }
media_results = [ hashes_to_media_results[ hash ] for hash in hashes if hash in hashes_to_media_results ]
@ -14128,6 +14140,9 @@ class DB( HydrusDB.HydrusDB ):
BlockingSafeShowMessage( message )
# quick hack
self._Execute( 'CREATE TABLE IF NOT EXISTS external_caches.local_tags_cache ( tag_id INTEGER PRIMARY KEY, tag TEXT UNIQUE );' )
self._RegenerateTagMappingsCache()

View File

@ -1990,11 +1990,11 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
url = HG.client_controller.network_engine.domain_manager.NormaliseURL( url )
( url_type, match_name, can_parse ) = self._controller.network_engine.domain_manager.GetURLParseCapability( url )
( url_type, match_name, can_parse, cannot_parse_reason ) = self._controller.network_engine.domain_manager.GetURLParseCapability( url )
if url_type in ( HC.URL_TYPE_GALLERY, HC.URL_TYPE_POST, HC.URL_TYPE_WATCHABLE ) and not can_parse:
message = 'This URL was recognised as a "{}" but this URL class does not yet have a parsing script linked to it!'.format( match_name )
message = 'This URL was recognised as a "{}" but it cannot be parsed: {}'.format( match_name, cannot_parse_reason )
message += os.linesep * 2
message += 'Since this URL cannot be parsed, a downloader cannot be created for it! Please check your url class links under the \'networking\' menu.'
@ -4830,6 +4830,10 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
HG.cache_report_mode = not HG.cache_report_mode
elif name == 'canvas_tile_outline_mode':
HG.canvas_tile_outline_mode = not HG.canvas_tile_outline_mode
elif name == 'db_report_mode':
HG.db_report_mode = not HG.db_report_mode
@ -6051,8 +6055,9 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
report_modes = QW.QMenu( debug )
ClientGUIMenus.AppendMenuCheckItem( report_modes, 'callto report mode', 'Report whenever the thread pool is given a task.', HG.callto_report_mode, self._SwitchBoolean, 'callto_report_mode' )
ClientGUIMenus.AppendMenuCheckItem( report_modes, 'cache report mode', 'Have the image and thumb caches report their operation.', HG.cache_report_mode, self._SwitchBoolean, 'cache_report_mode' )
ClientGUIMenus.AppendMenuCheckItem( report_modes, 'callto report mode', 'Report whenever the thread pool is given a task.', HG.callto_report_mode, self._SwitchBoolean, 'callto_report_mode' )
ClientGUIMenus.AppendMenuCheckItem( report_modes, 'canvas tile borders mode', 'Draw tile borders.', HG.canvas_tile_outline_mode, self._SwitchBoolean, 'canvas_tile_outline_mode' )
ClientGUIMenus.AppendMenuCheckItem( report_modes, 'daemon report mode', 'Have the daemons report whenever they fire their jobs.', HG.daemon_report_mode, self._SwitchBoolean, 'daemon_report_mode' )
ClientGUIMenus.AppendMenuCheckItem( report_modes, 'db report mode', 'Have the db report query information, where supported.', HG.db_report_mode, self._SwitchBoolean, 'db_report_mode' )
ClientGUIMenus.AppendMenuCheckItem( report_modes, 'file import report mode', 'Have the db and file manager report file import progress.', HG.file_import_report_mode, self._SwitchBoolean, 'file_import_report_mode' )

View File

@ -555,7 +555,7 @@ class EditGUGsPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
@ -1666,6 +1666,12 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
api_lookup_url = url_class.GetAPIURL( normalised )
if url_class.Matches( api_lookup_url ):
self._example_url_classes.setText( 'Matches own API URL!' )
self._example_url_classes.setObjectName( 'HydrusInvalid' )
else:
api_lookup_url = 'none set'
@ -1679,6 +1685,9 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
self._api_url.setText( 'Could not convert - ' + reason )
self._example_url_classes.setText( 'API URL Problem!' )
self._example_url_classes.setObjectName( 'HydrusInvalid' )
try:
@ -1740,6 +1749,7 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
QW.QMessageBox.information( self, 'Information', message )
def EventURLTypeUpdate( self, event ):
url_type = self._url_type.GetValue()
@ -1760,18 +1770,65 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
url_class = self._GetValue()
example_url = self._example_url.text()
try:
url_class.Test( self._example_url.text() )
url_class.Test( example_url )
except HydrusExceptions.URLClassException:
raise HydrusExceptions.VetoException( 'Please enter an example url that matches the given rules!' )
if url_class.UsesAPIURL():
try:
api_lookup_url = url_class.GetAPIURL( example_url )
except HydrusExceptions.StringConvertException as e:
raise HydrusExceptions.VetoException( 'Problem making API URL!' )
return url_class
def UserIsOKToOK( self ):
url_class = self._GetValue()
example_url = self._example_url.text()
if url_class.UsesAPIURL():
try:
api_lookup_url = url_class.GetAPIURL( example_url )
except HydrusExceptions.StringConvertException as e:
return True
if url_class.Matches( api_lookup_url ):
message = 'This URL class matches its own API URL! This can break a downloader unless there is a more specific URL Class the matches the API URL before this. I recommend you fix this here, but you do not have to. Exit now?'
result = ClientGUIDialogsQuick.GetYesNo( self, message )
if result != QW.QDialog.Accepted:
return False
return True
class EditURLClassesPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent: QW.QWidget, url_classes: typing.Iterable[ ClientNetworkingDomain.URLClass ] ):
@ -1786,7 +1843,7 @@ class EditURLClassesPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
self._url_class_checker = QW.QLineEdit( self )
self._url_class_checker.textChanged.connect( self.EventURLClassCheckerText )

View File

@ -1273,7 +1273,7 @@ class EditLoginScriptPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#

View File

@ -64,7 +64,7 @@ class DownloaderExportPanel( ClientGUIScrolledPanels.ReviewPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
@ -418,7 +418,7 @@ class DownloaderExportPanel( ClientGUIScrolledPanels.ReviewPanel ):
example_url = url_class.GetExampleURL()
( url_type, match_name, can_parse ) = self._network_engine.domain_manager.GetURLParseCapability( example_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = self._network_engine.domain_manager.GetURLParseCapability( example_url )
if can_parse:
@ -506,7 +506,7 @@ class EditCompoundFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
@ -738,7 +738,7 @@ class EditContextVariableFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
@ -1185,7 +1185,7 @@ class EditHTMLFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
@ -1542,7 +1542,7 @@ class EditJSONFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
@ -1777,7 +1777,7 @@ class EditContentParserPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
@ -2835,7 +2835,7 @@ class EditPageParserPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#

View File

@ -1118,7 +1118,7 @@ class EditFileImportOptions( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().help, self._ShowHelp )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#

View File

@ -2550,6 +2550,9 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._image_tile_cache_timeout = ClientGUITime.TimeDeltaButton( image_tile_cache_panel, min = 300, hours = True, minutes = True )
self._image_tile_cache_timeout.setToolTip( 'The amount of time after which a rendered image tile in the cache will naturally be removed, if it is not shunted out due to a new member exceeding the size limit.' )
self._ideal_tile_dimension = QP.MakeQSpinBox( image_tile_cache_panel, min = 256, max = 4096 )
self._ideal_tile_dimension.setToolTip( 'This is the square size the system will aim for. Smaller tiles are more memory efficient but prone to warping and other artifacts. Extreme values may waste CPU.' )
#
buffer_panel = ClientGUICommon.StaticBox( self, 'video buffer' )
@ -2577,6 +2580,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._image_cache_timeout.SetValue( self._new_options.GetInteger( 'image_cache_timeout' ) )
self._image_tile_cache_timeout.SetValue( self._new_options.GetInteger( 'image_tile_cache_timeout' ) )
self._ideal_tile_dimension.setValue( self._new_options.GetInteger( 'ideal_tile_dimension' ) )
self._video_buffer_size_mb.setValue( self._new_options.GetInteger( 'video_buffer_size_mb' ) )
self._forced_search_limit.SetValue( self._new_options.GetNoneableInteger( 'forced_search_limit' ) )
@ -2689,6 +2694,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
rows.append( ( 'MB memory reserved for image tile cache:', image_tiles_sizer ) )
rows.append( ( 'Image tile cache timeout:', self._image_tile_cache_timeout ) )
rows.append( ( 'Ideal tile width/height px:', self._ideal_tile_dimension ) )
gridbox = ClientGUICommon.WrapInGrid( image_tile_cache_panel, rows )
@ -2830,6 +2836,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._new_options.SetInteger( 'image_cache_timeout', self._image_cache_timeout.GetValue() )
self._new_options.SetInteger( 'image_tile_cache_timeout', self._image_tile_cache_timeout.GetValue() )
self._new_options.SetInteger( 'ideal_tile_dimension', self._ideal_tile_dimension.value() )
self._new_options.SetInteger( 'media_viewer_prefetch_delay_base_ms', self._media_viewer_prefetch_delay_base_ms.value() )
self._new_options.SetInteger( 'media_viewer_prefetch_num_previous', self._media_viewer_prefetch_num_previous.value() )
self._new_options.SetInteger( 'media_viewer_prefetch_num_next', self._media_viewer_prefetch_num_next.value() )

View File

@ -80,7 +80,7 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
@ -1755,7 +1755,7 @@ class ReviewDownloaderImport( ClientGUIScrolledPanels.ReviewPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help -->', object_name = 'HydrusIndeterminate' )
self._repo_link = ClientGUICommon.BetterHyperLink( self, 'get user-made downloaders here', 'https://github.com/CuddleBear92/Hydrus-Presets-and-Scripts/tree/master/Downloaders' )

View File

@ -138,7 +138,7 @@ class EditSubscriptionPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#
@ -1255,7 +1255,7 @@ class EditSubscriptionsPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUIMenuButton.MenuBitmapButton( self, CC.global_pixmaps().help, menu_items )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
self._subscriptions_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )

View File

@ -653,7 +653,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().help, self._ShowHelp )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
#

View File

@ -24,7 +24,7 @@ class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
help_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().help, self._ShowHelp )
help_button.setToolTip( 'Show help regarding these checker options.' )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this panel -->', object_name = 'HydrusIndeterminate' )
from hydrus.client import ClientDefaults
@ -73,16 +73,20 @@ class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
self._reactive_check_panel = ClientGUICommon.StaticBox( self, 'reactive checking' )
self._intended_files_per_check = QP.MakeQSpinBox( self._reactive_check_panel, min=1, max=1000 )
self._intended_files_per_check.setToolTip( 'How many new files you want the checker to find on each check. If a source is producing about 2 files a day, and this is set to 6, you will probably get a check every three days. You probably want this to be a low number, like 1-4.' )
self._never_faster_than = TimeDeltaCtrl( self._reactive_check_panel, min = never_faster_than_min, days = True, hours = True, minutes = True, seconds = True )
self._never_faster_than.setToolTip( 'Even if the download source produces many new files, the checker will never ask for a check more often than this. This is a safety measure.' )
self._never_slower_than = TimeDeltaCtrl( self._reactive_check_panel, min = never_slower_than_min, days = True, hours = True, minutes = True, seconds = True )
self._never_slower_than.setToolTip( 'Even if the download source slows down significantly, the checker will make sure it checks at least this often anyway, just to catch a future wave in time.' )
#
self._static_check_panel = ClientGUICommon.StaticBox( self, 'static checking' )
self._flat_check_period = TimeDeltaCtrl( self._static_check_panel, min = flat_check_period_min, days = True, hours = True, minutes = True, seconds = True )
self._flat_check_period.setToolTip( 'Always use the same check delay. It is based on the time the last check completed, not the time the last check was due. If you want once a day with no skips, try setting this to 23 hours.' )
#
@ -100,6 +104,12 @@ class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
#
label = 'This checks more or less frequently based on how fast the download source is producing new files.'
st = ClientGUICommon.BetterStaticText( self._reactive_check_panel, label = label )
st.setWordWrap( True )
rows = []
rows.append( ( 'intended new files per check: ', self._intended_files_per_check ) )
@ -108,6 +118,7 @@ class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
gridbox = ClientGUICommon.WrapInGrid( self._reactive_check_panel, rows )
self._reactive_check_panel.Add( st, CC.FLAGS_EXPAND_PERPENDICULAR )
self._reactive_check_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
@ -131,7 +142,16 @@ class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, help_hbox, CC.FLAGS_ON_RIGHT )
QP.AddToLayout( vbox, help_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
label = 'If you do not understand this panel, use the buttons! The defaults are fine for most purposes!'
st = ClientGUICommon.BetterStaticText( self._reactive_check_panel, label = label )
st.setWordWrap( True )
st.setObjectName( 'HydrusWarning' )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, defaults_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
@ -150,6 +170,8 @@ class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
QP.AddToLayout( vbox, self._reactive_check_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._static_check_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.addStretch( 1 )
self.widget().setLayout( vbox )
#

View File

@ -1454,34 +1454,6 @@ def SetBackgroundColour( widget, colour ):
widget.setStyleSheet( '#{} {{ background-color: {} }}'.format( object_name, QG.QColor( colour ).name() ) )
def SetForegroundColour( widget, colour ):
widget.setAutoFillBackground( True )
object_name = widget.objectName()
if not object_name:
object_name = str( id( widget ) )
widget.setObjectName( object_name )
if isinstance( colour, QG.QColor ):
widget.setStyleSheet( '#{} {{ color: {} }}'.format( object_name, colour.name()) )
elif isinstance( colour, tuple ):
colour = QG.QColor( *colour )
widget.setStyleSheet( '#{} {{ color: {} }}'.format( object_name, colour.name() ) )
else:
widget.setStyleSheet( '#{} {{ color: {} }}'.format( object_name, QG.QColor( colour ).name() ) )
def SetStringSelection( combobox, string ):
index = combobox.findText( string )

View File

@ -3703,6 +3703,64 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
def CommitArchiveDelete( page_key, kept_hashes, deleted_hashes ):
if HC.options[ 'remove_filtered_files' ]:
all_hashes = set()
all_hashes.update( deleted_hashes )
all_hashes.update( kept_hashes )
HG.client_controller.pub( 'remove_media', page_key, all_hashes )
if not isinstance( deleted_hashes, list ):
deleted_hashes = list( deleted_hashes )
if not isinstance( kept_hashes, list ):
kept_hashes = list( kept_hashes )
# we do a second set of removes to deal with late processing and a quick F5ing user
for block_of_deleted_hashes in HydrusData.SplitListIntoChunks( deleted_hashes, 64 ):
service_keys_to_content_updates = {}
reason = 'Deleted in Archive/Delete filter.'
service_keys_to_content_updates[ CC.LOCAL_FILE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, block_of_deleted_hashes, reason = reason ) ]
HG.client_controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
if HC.options[ 'remove_filtered_files' ]:
HG.client_controller.pub( 'remove_media', page_key, block_of_deleted_hashes )
HG.client_controller.WaitUntilViewFree()
for block_of_kept_hashes in HydrusData.SplitListIntoChunks( kept_hashes, 64 ):
service_keys_to_content_updates = {}
service_keys_to_content_updates[ CC.COMBINED_LOCAL_FILE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, block_of_kept_hashes ) ]
HG.client_controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
if HC.options[ 'remove_filtered_files' ]:
HG.client_controller.pub( 'remove_media', page_key, block_of_kept_hashes )
HG.client_controller.WaitUntilViewFree()
class CanvasMediaListFilterArchiveDelete( CanvasMediaList ):
def __init__( self, parent, page_key, file_service_key, media_results ):
@ -3777,41 +3835,12 @@ class CanvasMediaListFilterArchiveDelete( CanvasMediaList ):
elif result == QW.QDialog.Accepted:
service_keys_to_content_updates = {}
if len( deleted_hashes ) > 0:
reason = 'Deleted in Archive/Delete filter.'
service_keys_to_content_updates[ CC.LOCAL_FILE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, deleted_hashes, reason = reason ) ]
if len( kept_hashes ) > 0:
service_keys_to_content_updates[ CC.COMBINED_LOCAL_FILE_SERVICE_KEY ] = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, kept_hashes ) ]
# do this in one go to ensure if the user hits F5 real quick, they won't see the files again
if len( service_keys_to_content_updates ) > 0:
HG.client_controller.Write( 'content_updates', service_keys_to_content_updates )
self._kept = set()
self._deleted = set()
self._current_media = self._GetFirst() # so the pubsub on close is better
if HC.options[ 'remove_filtered_files' ]:
all_hashes = set()
all_hashes.update( deleted_hashes )
all_hashes.update( kept_hashes )
HG.client_controller.pub( 'remove_media', self._page_key, all_hashes )
HG.client_controller.CallToThread( CommitArchiveDelete, self._page_key, kept_hashes, deleted_hashes )

View File

@ -722,37 +722,38 @@ class CanvasHoverFrameRightDuplicates( CanvasHoverFrame ):
( panel, st ) = self._comparison_statements_sts[ name ]
if name in statements_and_scores:
got_data = name in statements_and_scores
show_panel = got_data
if panel.isVisible() != show_panel:
panel.setVisible( show_panel )
if got_data:
( statement, score ) = statements_and_scores[ name ]
if not panel.isVisible():
panel.show()
st.setText( statement )
if score > 0:
colour = ( 0, 128, 0 )
object_name = 'HydrusValid'
elif score < 0:
colour = ( 128, 0, 0 )
object_name = 'HydrusInvalid'
else:
colour = ( 0, 0, 128 )
object_name = 'HydrusIndeterminate'
QP.SetForegroundColour( st, colour )
st.setObjectName( object_name )
else:
st.style().polish( st )
if panel.isVisible():
panel.hide()

View File

@ -1688,7 +1688,9 @@ class StaticImage( QW.QWidget ):
else:
self._zoom = self.width() / self._media.GetResolution()[ 0 ]
( media_width, media_height ) = self._media.GetResolution()
self._zoom = self.width() / media_width
# it is most convenient to have tiles that line up with the current zoom ratio
# 768 is a convenient size for meaty GPU blitting, but as a number it doesn't make for nice multiplication
@ -1698,25 +1700,30 @@ class StaticImage( QW.QWidget ):
# the trick of going ( 123456 // 16 ) * 16 to give you a nice multiple of 16 does not work with floats like 1.4 lmao.
# what we can do instead is phrase 1.4 as 7/5 and use 7 as our int. any number cleanly divisible by 7 is cleanly divisible by 1.4
ideal_tile_dimension = 768
ideal_tile_dimension = HG.client_controller.new_options.GetInteger( 'ideal_tile_dimension' )
frac = fractions.Fraction( self._zoom ).limit_denominator( 100 )
nice_number = HydrusData.GetNicelyDivisibleNumberForZoom( self._zoom, ideal_tile_dimension )
n = frac.numerator
if n > ideal_tile_dimension:
if nice_number == -1:
# we are in extreme zoom land. nice multiples are impossible with reasonable size tiles, so we'll have to settle for some problems
# a future solution is to get a bigger zoom and scale down
# a future solution is to just make overlapping screen covering tiles and never deal with seams lmao
tile_dimension = ideal_tile_dimension
else:
tile_dimension = ( ideal_tile_dimension // n ) * n
tile_dimension = ( ideal_tile_dimension // nice_number ) * nice_number
tile_dimension = max( min( tile_dimension, 2048 ), 1 )
if HG.canvas_tile_outline_mode:
HydrusData.ShowText( '{} from zoom {} and nice number {}'.format( tile_dimension, self._zoom, nice_number ) )
self._canvas_tile_size = QC.QSize( tile_dimension, tile_dimension )
@ -1751,6 +1758,14 @@ class StaticImage( QW.QWidget ):
painter.drawPixmap( 0, 0, tile.qt_pixmap )
if HG.canvas_tile_outline_mode:
painter.setPen( QG.QPen( QG.QColor( 0, 127, 255 ) ) )
painter.setBrush( QC.Qt.NoBrush )
painter.drawRect( tile_pixmap.rect() )
self._canvas_tiles[ tile_coordinate ] = ( tile_pixmap, canvas_clip_rect.topLeft() )
@ -1811,11 +1826,13 @@ class StaticImage( QW.QWidget ):
if native_clip_rect.width() == 0:
native_clip_rect.setX( max( native_clip_rect.x() - 1, 0 ) )
native_clip_rect.setWidth( 1 )
if native_clip_rect.height() == 0:
native_clip_rect.setY( max( native_clip_rect.y() - 1, 0 ) )
native_clip_rect.setHeight( 1 )

View File

@ -2766,7 +2766,7 @@ class EditAdvancedORPredicates( ClientGUIScrolledPanels.EditPanel ):
self._current_predicates = []
colour = ( 0, 0, 0 )
object_name = ''
output = ''
@ -2826,17 +2826,19 @@ class EditAdvancedORPredicates( ClientGUIScrolledPanels.EditPanel ):
output = os.linesep.join( ( pred.ToString() for pred in self._current_predicates ) )
colour = ( 0, 128, 0 )
object_name = 'HydrusValid'
except ValueError:
output = 'Could not parse!'
colour = ( 128, 0, 0 )
object_name = 'HydrusInvalid'
self._result_preview.setPlainText( output )
QP.SetForegroundColour( self._result_preview, colour )
self._result_preview.setObjectName( object_name )
self._result_preview.style().polish( self._result_preview )
def EventUpdateText( self, text ):

View File

@ -1445,7 +1445,7 @@ class EditServiceIPFSSubPanel( ClientGUICommon.StaticBox ):
help_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().help, self._ShowHelp )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this path remapping control -->', QG.QColor( 0, 0, 255 ) )
help_hbox = ClientGUICommon.WrapInText( help_button, self, 'help for this path remapping control -->', object_name = 'HydrusIndeterminate' )
self._nocopy_abs_path_translations = ClientGUIStringControls.StringToStringDictControl( self, abs_initial_dict, key_name = 'hydrus path', value_name = 'ipfs path', allow_add_delete = False, edit_keys = False )

View File

@ -89,15 +89,15 @@ def WrapInGrid( parent, rows, expand_text = False, add_stretch_at_end = True ):
return gridbox
def WrapInText( control, parent, text, colour = None ):
def WrapInText( control, parent, text, object_name = None ):
hbox = QP.HBoxLayout()
st = BetterStaticText( parent, text )
if colour is not None:
if object_name is not None:
QP.SetForegroundColour( st, colour )
st.setObjectName( object_name )
QP.AddToLayout( hbox, st, CC.FLAGS_CENTER_PERPENDICULAR )

View File

@ -680,7 +680,7 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
if self.file_seed_type == FILE_SEED_TYPE_URL:
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self.file_seed_data )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self.file_seed_data )
if url_type == HC.URL_TYPE_POST:
@ -706,7 +706,7 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
# if our given referral is a post url, we are most probably a multi-file url
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self._referral_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self._referral_url )
if url_type == HC.URL_TYPE_POST:
@ -775,7 +775,10 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
url_file_import_status = self.GetPreImportStatusPredictionURL( file_import_options, file_url = file_url )
url_file_import_status.AlreadyInDB()
if url_file_import_status.AlreadyInDB():
url_override = True
@ -895,7 +898,7 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
if self.file_seed_type == FILE_SEED_TYPE_URL:
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self.file_seed_data )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self.file_seed_data )
if url_type == HC.URL_TYPE_FILE:
@ -909,7 +912,7 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
if url_type == HC.URL_TYPE_UNKNOWN and self._referral_url is not None: # this is likely be a multi-file child of a post url file_seed
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self._referral_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self._referral_url )
if url_type == HC.URL_TYPE_POST: # we must have got here through parsing that m8, so let's assume this is an unrecognised file url
@ -927,7 +930,7 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
try:
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self.file_seed_data )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self.file_seed_data )
if url_type not in ( HC.URL_TYPE_POST, HC.URL_TYPE_FILE, HC.URL_TYPE_UNKNOWN ):
@ -936,7 +939,7 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
if url_type == HC.URL_TYPE_POST and not can_parse:
raise HydrusExceptions.VetoException( 'Did not have a parser for this URL!' )
raise HydrusExceptions.VetoException( 'Cannot parse {}: {}'.format( match_name, cannot_parse_reason ) )
tag_import_options = self._SetupTagImportOptions( tag_import_options )
@ -985,7 +988,7 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
if actual_fetched_url != url_to_check:
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( actual_fetched_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( actual_fetched_url )
if url_type == HC.URL_TYPE_POST and can_parse:
@ -1095,7 +1098,7 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
desired_url = desired_urls[0]
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( desired_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( desired_url )
if url_type in ( HC.URL_TYPE_FILE, HC.URL_TYPE_UNKNOWN ):
@ -1116,7 +1119,14 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
else:
raise HydrusExceptions.VetoException( 'Found a URL--{}--but could not understand/parse it!'.format( desired_url ) )
if can_parse:
raise HydrusExceptions.VetoException( 'Found a URL--{}--but could not understand it!'.format( desired_url ) )
else:
raise HydrusExceptions.VetoException( 'Found a URL--{}--but could not parse it: {}'.format( desired_url, cannot_parse_reason ) )
else:
@ -1232,8 +1242,10 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
time.sleep( 3 )
file_seed_cache.NotifyFileSeedsUpdated( ( self, ) )
finally:
file_seed_cache.NotifyFileSeedsUpdated( ( self, ) )
return did_substantial_work

View File

@ -384,8 +384,10 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
self._gallery_paused = True
self._gallery_seed_log.NotifyGallerySeedsUpdated( ( gallery_seed, ) )
finally:
self._gallery_seed_log.NotifyGallerySeedsUpdated( ( gallery_seed, ) )
return

View File

@ -288,7 +288,7 @@ class GallerySeed( HydrusSerialisable.SerialisableBase ):
def WorksInNewSystem( self ):
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self.url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( self.url )
if url_type == HC.URL_TYPE_GALLERY and can_parse:
@ -322,7 +322,7 @@ class GallerySeed( HydrusSerialisable.SerialisableBase ):
gallery_url = self.url
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( gallery_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( gallery_url )
if url_type not in ( HC.URL_TYPE_GALLERY, HC.URL_TYPE_WATCHABLE ):
@ -331,7 +331,7 @@ class GallerySeed( HydrusSerialisable.SerialisableBase ):
if not can_parse:
raise HydrusExceptions.VetoException( 'Did not have a parser for this URL!' )
raise HydrusExceptions.VetoException( 'Cannot parse {}: {}'.format( match_name, cannot_parse_reason) )
( url_to_check, parser ) = HG.client_controller.network_engine.domain_manager.GetURLToFetchAndParser( gallery_url )
@ -372,7 +372,7 @@ class GallerySeed( HydrusSerialisable.SerialisableBase ):
if actual_fetched_url != url_to_check:
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( actual_fetched_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( actual_fetched_url )
if url_type == HC.URL_TYPE_GALLERY:
@ -382,6 +382,12 @@ class GallerySeed( HydrusSerialisable.SerialisableBase ):
( url_to_check, parser ) = HG.client_controller.network_engine.domain_manager.GetURLToFetchAndParser( gallery_url )
else:
status = CC.STATUS_ERROR
note = 'Could not parse {}: {}'.format( match_name, cannot_parse_reason )
else:
@ -397,6 +403,10 @@ class GallerySeed( HydrusSerialisable.SerialisableBase ):
all_parse_results = []
status = CC.STATUS_SUCCESSFUL_AND_NEW
note = 'was redirected to a non-gallery url, which has been queued as a file import'
if do_parse:
@ -416,168 +426,161 @@ class GallerySeed( HydrusSerialisable.SerialisableBase ):
file_seeds = ClientImporting.ConvertAllParseResultsToFileSeeds( all_parse_results, gallery_url, file_import_options )
title = ClientParsing.GetTitleFromAllParseResults( all_parse_results )
if title is not None:
title = ClientParsing.GetTitleFromAllParseResults( all_parse_results )
title_hook( title )
if title is not None:
title_hook( title )
for file_seed in file_seeds:
for file_seed in file_seeds:
file_seed.SetExternalFilterableTags( self._external_filterable_tags )
file_seed.SetExternalAdditionalServiceKeysToTags( self._external_additional_service_keys_to_tags )
file_seed.SetExternalFilterableTags( self._external_filterable_tags )
file_seed.SetExternalAdditionalServiceKeysToTags( self._external_additional_service_keys_to_tags )
num_urls_total = len( file_seeds )
num_urls_total = len( file_seeds )
( num_urls_added, num_urls_already_in_file_seed_cache, can_search_for_more_files, stop_reason ) = file_seeds_callable( file_seeds )
status = CC.STATUS_SUCCESSFUL_AND_NEW
if do_parse:
( num_urls_added, num_urls_already_in_file_seed_cache, can_search_for_more_files, stop_reason ) = file_seeds_callable( file_seeds )
status = CC.STATUS_SUCCESSFUL_AND_NEW
note = HydrusData.ToHumanInt( num_urls_added ) + ' new urls found'
else:
note = 'was redirected to a non-gallery url, which has been queued as a file import'
if num_urls_already_in_file_seed_cache > 0:
note += ' (' + HydrusData.ToHumanInt( num_urls_already_in_file_seed_cache ) + ' of page already in)'
if not can_search_for_more_files:
note += ' - ' + stop_reason
if parser.CanOnlyGenerateGalleryURLs() or self._force_next_page_url_generation:
can_add_more_gallery_urls = True
else:
# only keep searching if we found any files, otherwise this could be a blank results page with another stub page
can_add_more_gallery_urls = num_urls_added > 0 and can_search_for_more_files
flattened_results = list( itertools.chain.from_iterable( all_parse_results ) )
sub_gallery_urls = ClientParsing.GetURLsFromParseResults( flattened_results, ( HC.URL_TYPE_SUB_GALLERY, ), only_get_top_priority = True )
sub_gallery_urls = HydrusData.DedupeList( sub_gallery_urls )
new_sub_gallery_urls = [ sub_gallery_url for sub_gallery_url in sub_gallery_urls if sub_gallery_url not in gallery_urls_seen_before ]
num_new_sub_gallery_urls = len( new_sub_gallery_urls )
if num_new_sub_gallery_urls > 0:
sub_gallery_seeds = [ GallerySeed( sub_gallery_url ) for sub_gallery_url in new_sub_gallery_urls ]
for sub_gallery_seed in sub_gallery_seeds:
if num_urls_already_in_file_seed_cache > 0:
sub_gallery_seed.SetRunToken( self._run_token )
sub_gallery_seed.SetExternalFilterableTags( self._external_filterable_tags )
sub_gallery_seed.SetExternalAdditionalServiceKeysToTags( self._external_additional_service_keys_to_tags )
note += ' (' + HydrusData.ToHumanInt( num_urls_already_in_file_seed_cache ) + ' of page already in)'
gallery_seed_log.AddGallerySeeds( sub_gallery_seeds )
added_new_gallery_pages = True
gallery_urls_seen_before.update( sub_gallery_urls )
note += ' - {} sub-gallery urls found'.format( HydrusData.ToHumanInt( num_new_sub_gallery_urls ) )
if self._can_generate_more_pages and can_add_more_gallery_urls:
next_page_urls = ClientParsing.GetURLsFromParseResults( flattened_results, ( HC.URL_TYPE_NEXT, ), only_get_top_priority = True )
if self.url in next_page_urls:
if not can_search_for_more_files:
next_page_urls.remove( self.url )
note += ' - ' + stop_reason
if url_to_check in next_page_urls:
if parser.CanOnlyGenerateGalleryURLs() or self._force_next_page_url_generation:
next_page_urls.remove( url_to_check )
if len( next_page_urls ) > 0:
next_page_generation_phrase = ' next gallery pages found'
can_add_more_gallery_urls = True
else:
# we have failed to parse a next page url, but we would still like one, so let's see if the url match can provide one
url_class = HG.client_controller.network_engine.domain_manager.GetURLClass( url_to_check )
if url_class is not None and url_class.CanGenerateNextGalleryPage():
try:
next_page_url = url_class.GetNextGalleryPage( url_to_check )
next_page_urls = [ next_page_url ]
except Exception as e:
note += ' - Attempted to generate a next gallery page url, but failed!'
note += os.linesep
note += traceback.format_exc()
next_page_generation_phrase = ' next gallery pages extrapolated from url class'
# only keep searching if we found any files, otherwise this could be a blank results page with another stub page
can_add_more_gallery_urls = num_urls_added > 0 and can_search_for_more_files
if len( next_page_urls ) > 0:
flattened_results = list( itertools.chain.from_iterable( all_parse_results ) )
sub_gallery_urls = ClientParsing.GetURLsFromParseResults( flattened_results, ( HC.URL_TYPE_SUB_GALLERY, ), only_get_top_priority = True )
sub_gallery_urls = HydrusData.DedupeList( sub_gallery_urls )
new_sub_gallery_urls = [ sub_gallery_url for sub_gallery_url in sub_gallery_urls if sub_gallery_url not in gallery_urls_seen_before ]
num_new_sub_gallery_urls = len( new_sub_gallery_urls )
if num_new_sub_gallery_urls > 0:
next_page_urls = HydrusData.DedupeList( next_page_urls )
sub_gallery_seeds = [ GallerySeed( sub_gallery_url ) for sub_gallery_url in new_sub_gallery_urls ]
new_next_page_urls = [ next_page_url for next_page_url in next_page_urls if next_page_url not in gallery_urls_seen_before ]
for sub_gallery_seed in sub_gallery_seeds:
sub_gallery_seed.SetRunToken( self._run_token )
sub_gallery_seed.SetExternalFilterableTags( self._external_filterable_tags )
sub_gallery_seed.SetExternalAdditionalServiceKeysToTags( self._external_additional_service_keys_to_tags )
duplicate_next_page_urls = gallery_urls_seen_before.intersection( new_next_page_urls )
gallery_seed_log.AddGallerySeeds( sub_gallery_seeds )
num_new_next_page_urls = len( new_next_page_urls )
num_dupe_next_page_urls = len( duplicate_next_page_urls )
added_new_gallery_pages = True
if num_new_next_page_urls > 0:
gallery_urls_seen_before.update( sub_gallery_urls )
note += ' - {} sub-gallery urls found'.format( HydrusData.ToHumanInt( num_new_sub_gallery_urls ) )
if self._can_generate_more_pages and can_add_more_gallery_urls:
next_page_urls = ClientParsing.GetURLsFromParseResults( flattened_results, ( HC.URL_TYPE_NEXT, ), only_get_top_priority = True )
if self.url in next_page_urls:
next_gallery_seeds = [ GallerySeed( next_page_url ) for next_page_url in new_next_page_urls ]
next_page_urls.remove( self.url )
for next_gallery_seed in next_gallery_seeds:
next_gallery_seed.SetRunToken( self._run_token )
next_gallery_seed.SetExternalFilterableTags( self._external_filterable_tags )
next_gallery_seed.SetExternalAdditionalServiceKeysToTags( self._external_additional_service_keys_to_tags )
if url_to_check in next_page_urls:
gallery_seed_log.AddGallerySeeds( next_gallery_seeds )
next_page_urls.remove( url_to_check )
added_new_gallery_pages = True
if len( next_page_urls ) > 0:
gallery_urls_seen_before.update( new_next_page_urls )
if num_dupe_next_page_urls == 0:
note += ' - ' + HydrusData.ToHumanInt( num_new_next_page_urls ) + next_page_generation_phrase
else:
note += ' - ' + HydrusData.ToHumanInt( num_new_next_page_urls ) + next_page_generation_phrase + ', but ' + HydrusData.ToHumanInt( num_dupe_next_page_urls ) + ' had already been visited this run and were not added'
next_page_generation_phrase = ' next gallery pages found'
else:
note += ' - ' + HydrusData.ToHumanInt( num_dupe_next_page_urls ) + next_page_generation_phrase + ', but they had already been visited this run and were not added'
# we have failed to parse a next page url, but we would still like one, so let's see if the url match can provide one
url_class = HG.client_controller.network_engine.domain_manager.GetURLClass( url_to_check )
if url_class is not None and url_class.CanGenerateNextGalleryPage():
try:
next_page_url = url_class.GetNextGalleryPage( url_to_check )
next_page_urls = [ next_page_url ]
except Exception as e:
note += ' - Attempted to generate a next gallery page url, but failed!'
note += os.linesep
note += traceback.format_exc()
next_page_generation_phrase = ' next gallery pages extrapolated from url class'
if len( next_page_urls ) > 0:
next_page_urls = HydrusData.DedupeList( next_page_urls )
new_next_page_urls = [ next_page_url for next_page_url in next_page_urls if next_page_url not in gallery_urls_seen_before ]
duplicate_next_page_urls = gallery_urls_seen_before.intersection( new_next_page_urls )
num_new_next_page_urls = len( new_next_page_urls )
num_dupe_next_page_urls = len( duplicate_next_page_urls )
if num_new_next_page_urls > 0:
next_gallery_seeds = [ GallerySeed( next_page_url ) for next_page_url in new_next_page_urls ]
for next_gallery_seed in next_gallery_seeds:
next_gallery_seed.SetRunToken( self._run_token )
next_gallery_seed.SetExternalFilterableTags( self._external_filterable_tags )
next_gallery_seed.SetExternalAdditionalServiceKeysToTags( self._external_additional_service_keys_to_tags )
gallery_seed_log.AddGallerySeeds( next_gallery_seeds )
added_new_gallery_pages = True
gallery_urls_seen_before.update( new_next_page_urls )
if num_dupe_next_page_urls == 0:
note += ' - ' + HydrusData.ToHumanInt( num_new_next_page_urls ) + next_page_generation_phrase
else:
note += ' - ' + HydrusData.ToHumanInt( num_new_next_page_urls ) + next_page_generation_phrase + ', but ' + HydrusData.ToHumanInt( num_dupe_next_page_urls ) + ' had already been visited this run and were not added'
else:
note += ' - ' + HydrusData.ToHumanInt( num_dupe_next_page_urls ) + next_page_generation_phrase + ', but they had already been visited this run and were not added'
@ -644,8 +647,10 @@ class GallerySeed( HydrusSerialisable.SerialisableBase ):
raise
gallery_seed_log.NotifyGallerySeedsUpdated( ( self, ) )
finally:
gallery_seed_log.NotifyGallerySeedsUpdated( ( self, ) )
return ( num_urls_added, num_urls_already_in_file_seed_cache, num_urls_total, result_404, added_new_gallery_pages, stop_reason )

View File

@ -707,8 +707,10 @@ class WatcherImport( HydrusSerialisable.SerialisableBase ):
HydrusData.PrintException( e )
self._gallery_seed_log.NotifyGallerySeedsUpdated( ( gallery_seed, ) )
finally:
self._gallery_seed_log.NotifyGallerySeedsUpdated( ( gallery_seed, ) )
with self._lock:

View File

@ -1564,7 +1564,7 @@ class HydrusResourceClientAPIRestrictedAddURLsGetURLInfo( HydrusResourceClientAP
normalised_url = HG.client_controller.network_engine.domain_manager.NormaliseURL( url )
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( normalised_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( normalised_url )
except HydrusExceptions.URLClassException as e:
@ -1573,6 +1573,11 @@ class HydrusResourceClientAPIRestrictedAddURLsGetURLInfo( HydrusResourceClientAP
body_dict = { 'normalised_url' : normalised_url, 'url_type' : url_type, 'url_type_string' : HC.url_type_string_lookup[ url_type ], 'match_name' : match_name, 'can_parse' : can_parse }
if not can_parse:
body_dict[ 'cannot_parse_reason' ] = cannot_parse_reason
body = json.dumps( body_dict )
response_context = HydrusServerResources.ResponseContext( 200, mime = HC.APPLICATION_JSON, body = body )
@ -1895,7 +1900,7 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
else:
media_results = HG.client_controller.Read( 'media_results_from_ids', file_ids )
media_results = HG.client_controller.Read( 'media_results_from_ids', file_ids, sorted = True )
elif 'hashes' in request.parsed_request_args:
@ -1912,7 +1917,7 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
else:
media_results = HG.client_controller.Read( 'media_results', hashes )
media_results = HG.client_controller.Read( 'media_results', hashes, sorted = True )
else:
@ -1985,7 +1990,7 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
normalised_url = HG.client_controller.network_engine.domain_manager.NormaliseURL( known_url )
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( normalised_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( normalised_url )
except HydrusExceptions.URLClassException as e:
@ -1994,6 +1999,11 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
detailed_dict = { 'normalised_url' : normalised_url, 'url_type' : url_type, 'url_type_string' : HC.url_type_string_lookup[ url_type ], 'match_name' : match_name, 'can_parse' : can_parse }
if not can_parse:
detailed_dict[ 'cannot_parse_reason' ] = cannot_parse_reason
detailed_known_urls.append( detailed_dict )
@ -2361,13 +2371,13 @@ class HydrusResourceClientAPIRestrictedManagePagesAddFiles( HydrusResourceClient
CheckHashLength( hashes )
media_results = HG.client_controller.Read( 'media_results', hashes )
media_results = HG.client_controller.Read( 'media_results', hashes, sorted = True )
elif 'file_ids' in request.parsed_request_args:
hash_ids = request.parsed_request_args.GetValue( 'file_ids', list, expected_list_type = int )
media_results = HG.client_controller.Read( 'media_results_from_ids', hash_ids )
media_results = HG.client_controller.Read( 'media_results_from_ids', hash_ids, sorted = True )
else:

View File

@ -1666,7 +1666,7 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
if url_class is None:
return ( HC.URL_TYPE_UNKNOWN, 'unknown url', False )
return ( HC.URL_TYPE_UNKNOWN, 'unknown url', False, 'unknown url class' )
url_type = url_class.GetURLType()
@ -1677,14 +1677,16 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
( url_to_fetch, parser ) = self._GetURLToFetchAndParser( url )
can_parse = True
cannot_parse_reason = ''
except HydrusExceptions.URLClassException:
except HydrusExceptions.URLClassException as e:
can_parse = False
cannot_parse_reason = str( e )
return ( url_type, match_name, can_parse )
return ( url_type, match_name, can_parse, cannot_parse_reason )
def GetURLToFetchAndParser( self, url ):
@ -2604,7 +2606,7 @@ class GalleryURLGenerator( HydrusSerialisable.SerialisableBaseNamed ):
example_url = self.GetExampleURL()
( url_type, match_name, can_parse ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( example_url )
( url_type, match_name, can_parse, cannot_parse_reason ) = HG.client_controller.network_engine.domain_manager.GetURLParseCapability( example_url )
except Exception as e:
@ -2618,7 +2620,7 @@ class GalleryURLGenerator( HydrusSerialisable.SerialisableBaseNamed ):
if not can_parse:
raise HydrusExceptions.ParseException( 'No Parser for the URL Class {}!'.format( match_name ) )
raise HydrusExceptions.ParseException( 'Cannot parse {}: {}'.format( match_name, cannot_parse_reason ) )

View File

@ -81,7 +81,7 @@ options = {}
# Misc
NETWORK_VERSION = 20
SOFTWARE_VERSION = 453
SOFTWARE_VERSION = 454
CLIENT_API_VERSION = 20
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -637,6 +637,11 @@ class HydrusDB( HydrusDBBase.DBBase ):
except Exception as e:
if 'locked' in str( e ):
raise HydrusExceptions.DBAccessException( 'Database appeared to be locked. Please ensure there is not another client already running on this database, and then try restarting the client.' )
raise HydrusExceptions.DBAccessException( str( e ) )

View File

@ -1,5 +1,6 @@
import collections
import cProfile
import fractions
import io
import itertools
import os
@ -627,6 +628,41 @@ def Get64BitHammingDistance( phash1, phash2 ):
return n
def GetNicelyDivisibleNumberForZoom( zoom, no_bigger_than ):
# it is most convenient to have tiles that line up with the current zoom ratio
# 768 is a convenient size for meaty GPU blitting, but as a number it doesn't make for nice multiplication
# a 'nice' size is one that divides nicely by our zoom, so that integer translations between canvas and native res aren't losing too much in the float remainder
# the trick of going ( 123456 // 16 ) * 16 to give you a nice multiple of 16 does not work with floats like 1.4 lmao.
# what we can do instead is phrase 1.4 as 7/5 and use 7 as our int. any number cleanly divisible by 7 is cleanly divisible by 1.4
base_frac = fractions.Fraction( zoom )
denominator_limit = 10000
frac = base_frac
while frac.numerator > no_bigger_than:
frac = base_frac.limit_denominator( denominator_limit )
denominator_limit //= 2
if denominator_limit < 10:
return -1
if frac.numerator == 0:
return -1
return frac.numerator
def GetEmptyDataDict():
data = collections.defaultdict( default_dict_list )

View File

@ -46,6 +46,8 @@ profile_slow_count = 0
profile_fast_count = 0
profile_counter_lock = threading.Lock()
canvas_tile_outline_mode = False
db_ui_hang_relief_mode = False
callto_report_mode = False
db_report_mode = False

View File

@ -1195,6 +1195,7 @@ class TestClientAPI( unittest.TestCase ):
expected_answer[ 'url_type_string' ] = 'unknown url'
expected_answer[ 'match_name' ] = 'unknown url'
expected_answer[ 'can_parse' ] = False
expected_answer[ 'cannot_parse_reason' ] = 'unknown url class'
self.assertEqual( d, expected_answer )
@ -2326,7 +2327,7 @@ class TestClientAPI( unittest.TestCase ):
detailed_known_urls_metadata_row[ 'detailed_known_urls' ] = [
{'normalised_url': 'https://gelbooru.com/index.php?id=4841557&page=post&s=view', 'url_type': 0, 'url_type_string': 'post url', 'match_name': 'gelbooru file page', 'can_parse': True},
{'normalised_url': 'https://img2.gelbooru.com//images/80/c8/80c8646b4a49395fb36c805f316c49a9.jpg', 'url_type': 5, 'url_type_string': 'unknown url', 'match_name': 'unknown url', 'can_parse': False}
{'normalised_url': 'https://img2.gelbooru.com//images/80/c8/80c8646b4a49395fb36c805f316c49a9.jpg', 'url_type': 5, 'url_type_string': 'unknown url', 'match_name': 'unknown url', 'can_parse': False, 'cannot_parse_reason' : 'unknown url class'}
]
detailed_known_urls_metadata.append( detailed_known_urls_metadata_row )

View File

@ -334,6 +334,33 @@ QTextEdit#HydrusValid
background-color: #80ff80;
}
QPlainTextEdit#HydrusValid
{
background-color: #80ff80;
}
/* Duplicates 'middle' text colour */
QLabel#HydrusIndeterminate
{
color: #8080ff;
}
QLineEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
QTextEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
QPlainTextEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
/* Example: This regex is invalid */
QLabel#HydrusInvalid
@ -351,6 +378,11 @@ QTextEdit#HydrusInvalid
background-color: #ff8080;
}
QPlainTextEdit#HydrusInvalid
{
background-color: #ff8080;
}
/* Example: Your files are going to be deleted! */
QLabel#HydrusWarning

431
static/qss/Dark_Blue.qss Normal file
View File

@ -0,0 +1,431 @@
/*
Dark Blue: A Dark Blue theme for Hydrus Network by B1N4RYJ4N
Version..: 1.0
To achieve the intended results you must:
1. Activate dark mode
2. adjust the Qt style to Fusion
3. adjust the Qt stylesheet to Dark_Blue
4. adjust the current colourset under files > options > colors > current colourset to darkmode
5. adjust your color values under files > options > colors > darkmode like so:
thumbnail background normal..: #1e1e1e
thumbnail background selected: #007acc
thumbnail border normal......: #569cd6
thumbnail border selected....: #cccccc
thumbnail grid background....: #1e1e1e
autocomplete background......: #536267
media viewer background......: #1e1e1e
media viewer text............: #708090
tag box background...........: #1e1e1e
6. adjust your tag presentation color values under files > options > tag presentation > (On thumbnail top, On thumbnail bottom-right, On media viewer top) like so:
background colour............: #007acc
text colour..................: #ffffff
*/
/*
___ _
/ _ \___ _ __ ___ _ __ __ _| |
/ /_\/ _ \ '_ \ / _ \ '__/ _` | |
/ /_\\ __/ | | | __/ | | (_| | |
\____/\___|_| |_|\___|_| \__,_|_|
*/
QAbstractItemView {
background-color: #252526;
}
/*
____ __ __ _ _ _
/___ \/ / /\ \ (_) __| | __ _ ___| |_
// / /\ \/ \/ / |/ _` |/ _` |/ _ \ __|
/ \_/ / \ /\ /| | (_| | (_| | __/ |_
\___,_\ \/ \/ |_|\__,_|\__, |\___|\__|
|___/
*/
QWidget {
color: #CCCCCC;
background-color: #252526;
alternate-background-color: #252526;
}
QWidget::disabled {
background-color: #252526;
}
QWidget::item::selected {
color: #FFF;
background-color: #569cd6;
}
QWidget::item:hover {
color: #FFF;
background-color: #569cd6;
}
/*
____ _____ _ _____ _
/___ \/__ \___ ___ | /__ (_)_ __
// / / / /\/ _ \ / _ \| | / /\/ | '_ \
/ \_/ / / / | (_) | (_) | |/ / | | |_) |
\___,_\ \/ \___/ \___/|_|\/ |_| .__/
|_|
*/
QToolTip {
color: #8be9fd;
border: 1px solid black;
background-color: #1E1E1E;
padding: 1px;
}
/*
____
/___ \/\/\ ___ _ __ _ _
// / / \ / _ \ '_ \| | | |
/ \_/ / /\/\ \ __/ | | | |_| |
\___,_\/ \/\___|_| |_|\__,_|
*/
QMenu {
color: #CCCCCC;
background: #252526;
}
QMenu::item {
padding: 2px 20px 2px 20px;
}
QMenu::item:selected {
color: #FFF;
background: #569cd6;
}
/*
____ ___
/___ \/\/\ ___ _ __ _ _ / __\ __ _ _ __
// / / \ / _ \ '_ \| | | |/__\/// _` | '__|
/ \_/ / /\/\ \ __/ | | | |_| / \/ \ (_| | |
\___,_\/ \/\___|_| |_|\__,_\_____/\__,_|_|
*/
QMenuBar::item {
background: transparent;
}
QMenuBar::item:selected {
color: #FFF;
background: #569cd6;
}
/*
____ ___ _ ___ _ _
/___ \/ _ \_ _ ___| |__ / __\_ _| |_| |_ ___ _ __
// / / /_)/ | | / __| '_ \ /__\// | | | __| __/ _ \| '_ \
/ \_/ / ___/| |_| \__ \ | | / \/ \ |_| | |_| || (_) | | | |
\___,_\/ \__,_|___/_| |_\_____/\__,_|\__|\__\___/|_| |_|
*/
QPushButton {
color: #CCCCCC;
background-color: #252526;
}
QPushButton::hover {
color: #FFF;
background-color: #252526;
}
QPushButton#HydrusAccept {
color: #50fa7b;
}
QPushButton#HydrusCancel {
color: #ff5555;
}
QPushButton#HydrusOnOffButton[hydrus_on=true] {
color: #50fa7b;
}
QPushButton#HydrusOnOffButton[hydrus_on=false] {
color: #ff5555;
}
/*
____ _____ _ ___
/___ \/__ \__ _| |__ / __\ __ _ _ __
// / / / /\/ _` | '_ \ /__\/// _` | '__|
/ \_/ / / / | (_| | |_) / \/ \ (_| | |
\___,_\ \/ \__,_|_.__/\_____/\__,_|_|
*/
QTabBar::tab {
color: #8be9fd;
background-color: #1a1a1a;
padding-left: 10px;
padding-right: 10px;
padding-top: 3px;
padding-bottom: 2px;
}
QTabBar::tab:last {
border-top-right-radius: 3px;
}
QTabBar::tab:selected {
color: #FFF;
background-color: #007ACC;
}
QTabBar::tab:hover:!selected {
color: #FFF;
background-color: #007ACC;
}
/*
____ __ _ __ _ _ _
/___ \/ /(_)_ __ ___ /__\_| (_) |_
// / / / | | '_ \ / _ \/_\/ _` | | __|
/ \_/ / /__| | | | | __//_| (_| | | |_
\___,_\____/_|_| |_|\___\__/\__,_|_|\__|
*/
QLineEdit {
border: 1px solid #569cd6;
border-radius: 1px;
background-color: #383B3D;
padding: 1px;
}
QLineEdit:focus{
color: #FFF;
border: 1px solid #CCC;
}
/*
____ ___ ___
/___ \/ _ \_ __ ___ __ _ _ __ ___ ___ ___ / __\ __ _ _ __
// / / /_)/ '__/ _ \ / _` | '__/ _ \/ __/ __| /__\/// _` | '__|
/ \_/ / ___/| | | (_) | (_| | | | __/\__ \__ \/ \/ \ (_| | |
\___,_\/ |_| \___/ \__, |_| \___||___/___/\_____/\__,_|_|
|___/
*/
QProgressBar {
color: #FFF;
border: 1px solid #569cd6;
text-align: center;
padding: 1px;
border-radius: 0px;
background-color: #383B3D;
width: 15px;
}
QProgressBar::chunk {
color: #FFF;
background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #78d,
stop: 0.4999 #46a,
stop: 0.5 #45a,
stop: 1 #238 );
border-radius: 0px;
border: 0px;
}
/*
____ _ _
/___ \/\ /\___ __ _ __| | ___ _ __/\ /(_) _____ __
// / / /_/ / _ \/ _` |/ _` |/ _ \ '__\ \ / / |/ _ \ \ /\ / /
/ \_/ / __ / __/ (_| | (_| | __/ | \ V /| | __/\ V V /
\___,_\/ /_/ \___|\__,_|\__,_|\___|_| \_/ |_|\___| \_/\_/
*/
QHeaderView::section {
background-color: #007ACC;
color: #f8f8f2;
padding-left: 4px;
border: 1px solid #569cd6;
}
/*
____ __ _ _ ___
/___ \/ _\ ___ _ __ ___ | | | / __\ __ _ _ __
// / /\ \ / __| '__/ _ \| | |/__\/// _` | '__|
/ \_/ / _\ \ (__| | | (_) | | / \/ \ (_| | |
\___,_\ \__/\___|_| \___/|_|_\_____/\__,_|_|
From Quassel Wiki: http://sprunge.us/iZGB
*/
QScrollBar {
background: #1A1A1A;
margin: 0;
}
QScrollBar:hover {
background: #1A1A1A;
}
QScrollBar:vertical {
width: 8px;
}
QScrollBar:horizontal {
height: 8px;
}
QScrollBar::handle {
padding: 0;
margin: 2px;
border-radius: 2px;
border: 2px solid #569cd6;
background: #1E1E1E;
}
QScrollBar::handle:vertical {
min-height: 20px;
min-width: 0px;
}
QScrollBar::handle:horizontal {
min-width: 20px;
min-height: 0px;
}
QScrollBar::handle:hover {
border-color: #007ACC;
background: #1E1E1E;
}
QScrollBar::handle:pressed {
background: #1E1E1E;
border-color: #007ACC;
}
QScrollBar::add-line , QScrollBar::sub-line {
height: 0px;
border: 0px;
}
QScrollBar::up-arrow, QScrollBar::down-arrow {
border: 0px;
width: 0px;
height: 0px;
}
QScrollBar::add-page, QScrollBar::sub-page {
background: none;
}
/*
____ _____ _ __ _ _ _
/___ \/__ \_____ _| |_ /__\_| (_) |_
// / / / /\/ _ \ \/ / __|/_\/ _` | | __|
/ \_/ / / / | __/> <| |_//_| (_| | | |_
\___,_\ \/ \___/_/\_\\__\__/\__,_|_|\__|
*/
QTextEdit {
background-color: #383B3D;
}
QTextEdit#HydrusValid {
background-color: #80ff80;
}
QTextEdit#HydrusIndeterminate {
background-color: #8080ff;
}
QTextEdit#HydrusInvalid {
background-color: #ff8080;
}
/*
____ ___ _ _ _____ _ __ _ _ _
/___ \/ _ \ | __ _(_)_ __/__ \_____ _| |_ /__\_| (_) |_
// / / /_)/ |/ _` | | '_ \ / /\/ _ \ \/ / __|/_\/ _` | | __|
/ \_/ / ___/| | (_| | | | | / / | __/> <| |_//_| (_| | | |_
\___,_\/ |_|\__,_|_|_| |_\/ \___/_/\_\\__\__/\__,_|_|\__|
*/
QPlainTextEdit {
background-color: #383B3D;
}
/*
____ __ _ _
/___ \/ / __ _| |__ ___| |
// / / / / _` | '_ \ / _ \ |
/ \_/ / /__| (_| | |_) | __/ |
\___,_\____/\__,_|_.__/ \___|_|
*/
QLabel#HydrusValid {
color: #50fa7b;
}
QLabel#HydrusIndeterminate {
color: #8080ff;
}
QLabel#HydrusInvalid {
color: #ff5555;
}
QLabel#HydrusWarning {
color: #ff5555;
}
/*
____ __ _ __ _ _ _
/___ \/ /(_)_ __ ___ /__\_| (_) |_
// / / / | | '_ \ / _ \/_\/ _` | | __|
/ \_/ / /__| | | | | __//_| (_| | | |_
\___,_\____/_|_| |_|\___\__/\__,_|_|\__|
*/
QLineEdit#HydrusValid {
background-color: #80ff80;
}
QLineEdit#HydrusIndeterminateValid {
background-color: #8080ff;
}
QLineEdit#HydrusInvalid {
background-color: #ff8080;
}
/*
____ ___ _ _ ___
/___ \/ __\ |__ ___ ___| | __ / __\ _____ __
// / / / | '_ \ / _ \/ __| |/ //__\/// _ \ \/ /
/ \_/ / /___| | | | __/ (__| </ \/ \ (_) > <
\___,_\____/|_| |_|\___|\___|_|\_\_____/\___/_/\_\
*/
QCheckBox#HydrusWarning {
color: #ff5555;
}

View File

@ -301,3 +301,87 @@ QTabBar::tab:hover:!selected
color: black;
background-color: #d7801a;
}
/* Hydev gonking in here with some late entries */
/* Example: This regex is valid */
QLabel#HydrusValid
{
color: #2ed42e;
}
QLineEdit#HydrusValid
{
background-color: #80ff80;
}
QTextEdit#HydrusValid
{
background-color: #80ff80;
}
QPlainTextEdit#HydrusValid
{
background-color: #80ff80;
}
/* Duplicates 'middle' text colour */
QLabel#HydrusIndeterminate
{
color: #8080ff;
}
QLineEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
QTextEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
QPlainTextEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
/* Example: This regex is invalid */
QLabel#HydrusInvalid
{
color: #ff7171;
}
QLineEdit#HydrusInvalid
{
background-color: #ff8080;
}
QTextEdit#HydrusInvalid
{
background-color: #ff8080;
}
QPlainTextEdit#HydrusInvalid
{
background-color: #ff8080;
}
/*
Buttons on dialogs
*/
QPushButton#HydrusAccept
{
color: #2ed42e;
}
QPushButton#HydrusCancel
{
color: #ff7171;
}

View File

@ -383,6 +383,33 @@ QTextEdit#HydrusValid
background-color: #80ff80;
}
QPlainTextEdit#HydrusValid
{
background-color: #80ff80;
}
/* Duplicates 'middle' text colour */
QLabel#HydrusIndeterminate
{
color: #8080ff;
}
QLineEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
QTextEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
QPlainTextEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
/* Example: This regex is invalid */
QLabel#HydrusInvalid
@ -400,6 +427,11 @@ QTextEdit#HydrusInvalid
background-color: #ff8080;
}
QPlainTextEdit#HydrusInvalid
{
background-color: #ff8080;
}
/* Example: Your files are going to be deleted! */
QLabel#HydrusWarning

View File

@ -334,6 +334,34 @@ QTextEdit#HydrusValid
background-color: #80ff80;
}
QPlainTextEdit#HydrusValid
{
background-color: #80ff80;
}
/* Duplicates 'middle' text colour */
QLabel#HydrusIndeterminate
{
color: #8080ff;
}
QLineEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
QTextEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
QPlainTextEdit#HydrusIndeterminate
{
background-color: #8080ff;
}
/* Example: This regex is invalid */
QLabel#HydrusInvalid
@ -351,6 +379,11 @@ QTextEdit#HydrusInvalid
background-color: #ff8080;
}
QPlainTextEdit#HydrusInvalid
{
background-color: #ff8080;
}
/* Example: Your files are going to be deleted! */
QLabel#HydrusWarning

View File

@ -27,6 +27,33 @@ QTextEdit#HydrusValid
background-color: #80ff80;
}
QPlainTextEdit#HydrusValid
{
background-color: #80ff80;
}
/* Duplicates 'middle' text colour */
QLabel#HydrusIndeterminate
{
color: #000080;
}
QLineEdit#HydrusIndeterminate
{
background-color: #000080;
}
QTextEdit#HydrusIndeterminate
{
background-color: #000080;
}
QPlainTextEdit#HydrusIndeterminate
{
background-color: #000080;
}
/* Example: This regex is invalid */
QLabel#HydrusInvalid
@ -44,6 +71,11 @@ QTextEdit#HydrusInvalid
background-color: #ff8080;
}
QPlainTextEdit#HydrusInvalid
{
background-color: #ff8080;
}
/* Example: Your files are going to be deleted! */
QLabel#HydrusWarning