Version 284

This commit is contained in:
Hydrus Network Developer 2017-11-29 15:48:23 -06:00
parent cbbeec7197
commit a7f254485a
42 changed files with 2686 additions and 1302 deletions

View File

@ -8,6 +8,44 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 284</h3></li>
<ul>
<li>fixed subscription queries turning dead on the initial sync</li>
<li>all dead subscription queries have been set to check again in case they can revive</li>
<li>added query file velocity to edit subscription panel</li>
<li>subscription network contexts now reflect the new multiple subscription query system, and are named "sub_name: query_text". as every query now counts as its own separate subscription network context, this will stop query-heavy subscriptions from throttling so much on bandwidth limits</li>
<li>finished URLMatch object, which matches and normalises URLs into certain 'classes' like 'gelbooru post url'</li>
<li>expanded some URLMatch subdomain options</li>
<li>fixed some test logic in URLMatch</li>
<li>finished the last of the EditURLMatchPanel</li>
<li>split the 'manage network rules' dialog into two panels--it now has a 'url classes' tab</li>
<li>wrote a panel for managing url matches</li>
<li>added export/import/duplicate buttons to EditURLMatchesPanel</li>
<li>wrote some URLMatches for hentai-foundry as an initial test of the system and added a temp button to add them--please check them out to see how it all works</li>
<li>all, invert, inbox, and archive (and none, lol) thumbnail 'select' menu items now have counts</li>
<li>invert is now at the bottom</li>
<li>the thumbnail select menu now has local/remote entries if applicable (this typically is only true in 'all known files' file domain)</li>
<li>added png/clipboard export/import/duplicate code to the generic new listctrl button wrapper panel, which will save a bunch of time as the png/clipboard sharing system expands</li>
<li>added a human-facing serialisable name to all objects on the new serialisation system and tied the new import/export code into it for png presentation</li>
<li>the edit import folder dialog will now complain (but not veto) on an ok event if any of the entered paths do not exist</li>
<li>if you attempt to manually run an import folder while import folders are globally paused, you'll get a little popup telling you so</li>
<li>added an experimental 'thumbnail fill' setting to options->gui. it zooms the existing thumbnails so they fill the whole thumb space. feedback on this from those who would be interested in a prettier system would be appreciated</li>
<li>added 'paste tags' buttons to filename tagging options panel</li>
<li>the paths/urls in the file import cache are now their own object that holds the creation/modified/source times and current status and note. this object can also hold prospective urls, tags, and hashes for future use</li>
<li>a bunch of file import actions are faster</li>
<li>all the different importers now use this new file import object</li>
<li>fixed a screen position calculation in the new drag and drop filtering code that was accidentally including too many possible drop candidates on drops in the top-left corner of the main gui (if you had trouble moving tabs to the left, this should be it fixed!)</li>
<li>fixed a problem display volume/chapter/page tags that included unicode characters in thumbnail banners and media viewers</li>
<li>fixed a rare media display bug in the dupe filter</li>
<li>fixed some 'C++ part of panel has been deleted' bugs in review services if the frame is shut down before delayed db info is fetched</li>
<li>cleaned up some more 'C++ deleted' errors in import files selection dialog</li>
<li>fixed the network context custom header panel 'add' action, which wasn't saving the value of the panel</li>
<li>fixed a bunch of bugs in the newish QueueListBox class</li>
<li>SynchroniseRepositories daemon will be better about quitting early on application shutdown</li>
<li>cleaned up some pending pretty timestamp grammar</li>
<li>when the client cannot clean up a temporary file, it will print more error information</li>
<li>added pylzma to the 'running from source' library recommendations. this is not required, but if available it adds ZWS flash support</li>
</ul>
<li><h3>version 283</h3></li>
<ul>
<li>subscription popups show a bit more info about their individual queries</li>

View File

@ -17,7 +17,7 @@
<h3>what you will need</h3>
<p>You will need basic python experience, python 2.7 and a number of python modules. Most of it you can get through pip. I think this will do for most systems:</p>
<ul>
<li>pip install beautifulsoup4 hsaudiotag lxml lz4 nose numpy opencv-python pafy Pillow psutil pycrypto PyOpenSSL PyPDF2 PyYAML requests Send2Trash service_identity twisted youtube-dl</li>
<li>pip install beautifulsoup4 hsaudiotag lxml lz4 nose numpy opencv-python pafy Pillow psutil pycrypto pylzma PyOpenSSL PyPDF2 PyYAML requests Send2Trash service_identity twisted youtube-dl</li>
</ul>
<p>Although you may want to do all that in smaller batches. Ultimately, the best way to figure out if you have enough is to just keep running client.pyw and see what it complains about missing.</p>
<p>I use Ubuntu 17.04, which also requires something like:</p>

View File

@ -6627,7 +6627,7 @@ class DB( HydrusDB.HydrusDB ):
if stop_time is not None:
HG.client_controller.pub( 'splash_set_status_subtext', HydrusData.ConvertTimestampToPrettyPending( stop_time ) )
HG.client_controller.pub( 'splash_set_status_subtext', HydrusData.ConvertTimestampToPrettyPending( stop_time, prefix = '' ) )
if HydrusData.TimeHasPassed( stop_time ):
@ -9998,6 +9998,64 @@ class DB( HydrusDB.HydrusDB ):
self.pub_initial_message( message )
if version == 283:
have_heavy_subs = False
some_revived = False
try:
subscriptions = self._GetJSONDumpNamed( HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION )
for subscription in subscriptions:
save_it = False
if len( subscription._queries ) > 1:
have_heavy_subs = True
for query in subscription._queries:
if query.IsDead():
query.CheckNow()
save_it = True
if save_it:
self._SetJSONDump( subscription )
some_revived = True
except Exception as e:
HydrusData.Print( 'While attempting to revive dead subscription queries, I had this problem:' )
HydrusData.PrintException( e )
if some_revived:
message = 'The old subscription syncing code was setting many new queries \'dead\' after their first sync. All your dead subscription queries have been set to check again in case they can revive.'
self.pub_initial_message( message )
if have_heavy_subs:
message = 'The way subscriptions consume bandwidth has changed to stop heavy subs with many queries from being throttled so often. If you have big subscriptions with a bunch of work to do, they may catch up right now!'
self.pub_initial_message( message )
self._controller.pub( 'splash_set_title_text', 'updated db to v' + str( version + 1 ) )
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )

View File

@ -272,15 +272,15 @@ def DAEMONSynchroniseRepositories( controller ):
if not options[ 'pause_repo_sync' ]:
if HydrusThreading.IsThreadShuttingDown():
return
services = controller.services_manager.GetServices( HC.REPOSITORIES )
for service in services:
if HydrusThreading.IsThreadShuttingDown():
return
if options[ 'pause_repo_sync' ]:
return
@ -288,8 +288,13 @@ def DAEMONSynchroniseRepositories( controller ):
service.Sync( only_process_when_idle = True )
time.sleep( 5 )
if HydrusThreading.IsThreadShuttingDown():
return
time.sleep( 3 )

View File

@ -643,6 +643,7 @@ def SortTagsList( tags, sort_type ):
class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_APPLICATION_COMMAND
SERIALISABLE_NAME = 'Application Command'
SERIALISABLE_VERSION = 1
def __init__( self, command_type = None, data = None ):
@ -780,6 +781,7 @@ sqlite3.register_adapter( Booru, yaml.safe_dump )
class ClientOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS
SERIALISABLE_NAME = 'Client Options'
SERIALISABLE_VERSION = 3
def __init__( self, db_dir = None ):
@ -855,6 +857,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'booleans' ][ 'anchor_and_hide_canvas_drags' ] = HC.PLATFORM_WINDOWS
self._dictionary[ 'booleans' ][ 'thumbnail_fill' ] = False
#
self._dictionary[ 'colours' ] = HydrusSerialisable.SerialisableDictionary()
@ -1825,6 +1829,7 @@ class Credentials( HydrusData.HydrusYAMLBase ):
class DuplicateActionOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_DUPLICATE_ACTION_OPTIONS
SERIALISABLE_NAME = 'Duplicate Action Options'
SERIALISABLE_VERSION = 2
def __init__( self, tag_service_actions = None, rating_service_actions = None, delete_second_file = False, sync_archive = False, delete_both_files = False ):
@ -2190,6 +2195,7 @@ sqlite3.register_adapter( Imageboard, yaml.safe_dump )
class Shortcut( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT
SERIALISABLE_NAME = 'Shortcut'
SERIALISABLE_VERSION = 1
def __init__( self, shortcut_type = None, shortcut_key = None, modifiers = None ):
@ -2300,6 +2306,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class Shortcuts( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS
SERIALISABLE_NAME = 'Shortcuts'
SERIALISABLE_VERSION = 2
def __init__( self, name ):
@ -2534,6 +2541,7 @@ def ConvertMouseEventToShortcut( event ):
class TagCensor( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_TAG_CENSOR
SERIALISABLE_NAME = 'Tag Censorship Rules'
SERIALISABLE_VERSION = 1
def __init__( self ):
@ -2734,6 +2742,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class CheckerOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CHECKER_OPTIONS
SERIALISABLE_NAME = 'Checker Timing Options'
SERIALISABLE_VERSION = 1
def __init__( self, intended_files_per_check = 8, never_faster_than = 300, never_slower_than = 86400, death_file_velocity = ( 1, 86400 ) ):
@ -2754,18 +2763,18 @@ class CheckerOptions( HydrusSerialisable.SerialisableBase ):
current_files_found = seed_cache.GetNumNewFilesSince( since )
if len( seed_cache ) == 0:
# when a thread is only 30mins old (i.e. first file was posted 30 mins ago), we don't want to calculate based on a longer delete time delta
# we want next check to be like 30mins from now, not 12 hours
# so we'll say "5 files in 30 mins" rather than "5 files in 24 hours"
earliest_source_time = seed_cache.GetEarliestSourceTime()
if earliest_source_time is None:
current_time_delta = death_time_delta
else:
# when a thread is only 30mins old (i.e. first file was posted 30 mins ago), we don't want to calculate based on a longer delete time delta
# we want next check to be like 30mins from now, not 12 hours
# so we'll say "5 files in 30 mins" rather than "5 files in 24 hours"
earliest_source_time = seed_cache.GetEarliestSourceTime()
early_time_delta = max( last_check_time - earliest_source_time, 30 )
current_time_delta = min( early_time_delta, death_time_delta )
@ -2829,7 +2838,12 @@ class CheckerOptions( HydrusSerialisable.SerialisableBase ):
def GetPrettyCurrentVelocity( self, seed_cache, last_check_time ):
def GetRawCurrentVelocity( self, seed_cache, last_check_time ):
return self._GetCurrentFilesVelocity( seed_cache, last_check_time )
def GetPrettyCurrentVelocity( self, seed_cache, last_check_time, no_prefix = False ):
if len( seed_cache ) == 0:
@ -2844,9 +2858,18 @@ class CheckerOptions( HydrusSerialisable.SerialisableBase ):
else:
if no_prefix:
pretty_current_velocity = ''
else:
pretty_current_velocity = 'at last check, found '
( current_files_found, current_time_delta ) = self._GetCurrentFilesVelocity( seed_cache, last_check_time )
pretty_current_velocity = 'at last check, found ' + HydrusData.ConvertIntToPrettyString( current_files_found ) + ' files in previous ' + HydrusData.ConvertTimeDeltaToPrettyString( current_time_delta )
pretty_current_velocity += HydrusData.ConvertIntToPrettyString( current_files_found ) + ' files in previous ' + HydrusData.ConvertTimeDeltaToPrettyString( current_time_delta )
return pretty_current_velocity

View File

@ -754,3 +754,181 @@ def GetDefaultShortcuts():
shortcuts.append( media_viewer )
return shortcuts
def GetDefaultURLMatches():
import ClientNetworkingDomain
import ClientParsing
url_matches = []
#
name = 'hentai foundry artist pictures gallery page base'
url_type = HC.URL_TYPE_GALLERY
preferred_scheme = 'https'
netloc = 'www.hentai-foundry.com'
allow_subdomains = False
keep_subdomains = False
path_components = []
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'pictures', example_string = 'pictures' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'user', example_string = 'user' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_ANY, example_string = 'daruak' ) )
parameters = {}
example_url = 'https://www.hentai-foundry.com/pictures/user/daruak'
url_match = ClientNetworkingDomain.URLMatch( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, allow_subdomains = allow_subdomains, keep_subdomains = keep_subdomains, path_components = path_components, parameters = parameters, example_url = example_url )
url_matches.append( url_match )
#
name = 'hentai foundry artist pictures gallery page'
url_type = HC.URL_TYPE_GALLERY
preferred_scheme = 'https'
netloc = 'www.hentai-foundry.com'
allow_subdomains = False
keep_subdomains = False
path_components = []
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'pictures', example_string = 'pictures' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'user', example_string = 'user' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_ANY, example_string = 'daruak' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'page', example_string = 'page' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FLEXIBLE, match_value = ClientParsing.NUMERIC, example_string = '2' ) )
parameters = {}
example_url = 'https://www.hentai-foundry.com/pictures/user/daruak/page/2'
url_match = ClientNetworkingDomain.URLMatch( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, allow_subdomains = allow_subdomains, keep_subdomains = keep_subdomains, path_components = path_components, parameters = parameters, example_url = example_url )
url_matches.append( url_match )
#
name = 'hentai foundry artist scraps gallery page base'
url_type = HC.URL_TYPE_GALLERY
preferred_scheme = 'https'
netloc = 'www.hentai-foundry.com'
allow_subdomains = False
keep_subdomains = False
path_components = []
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'pictures', example_string = 'pictures' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'user', example_string = 'user' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_ANY, example_string = 'Sparrow' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'scraps', example_string = 'scraps' ) )
parameters = {}
example_url = 'https://www.hentai-foundry.com/pictures/user/Sparrow/scraps'
url_match = ClientNetworkingDomain.URLMatch( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, allow_subdomains = allow_subdomains, keep_subdomains = keep_subdomains, path_components = path_components, parameters = parameters, example_url = example_url )
url_matches.append( url_match )
#
name = 'hentai foundry artist scraps gallery page'
url_type = HC.URL_TYPE_GALLERY
preferred_scheme = 'https'
netloc = 'www.hentai-foundry.com'
allow_subdomains = False
keep_subdomains = False
path_components = []
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'pictures', example_string = 'pictures' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'user', example_string = 'user' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_ANY, example_string = 'Sparrow' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'scraps', example_string = 'scraps' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'page', example_string = 'page' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FLEXIBLE, match_value = ClientParsing.NUMERIC, example_string = '3' ) )
parameters = {}
example_url = 'https://www.hentai-foundry.com/pictures/user/Sparrow/scraps/page/3'
url_match = ClientNetworkingDomain.URLMatch( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, allow_subdomains = allow_subdomains, keep_subdomains = keep_subdomains, path_components = path_components, parameters = parameters, example_url = example_url )
url_matches.append( url_match )
#
name = 'hentai foundry tag search gallery page base'
url_type = HC.URL_TYPE_GALLERY
preferred_scheme = 'https'
netloc = 'www.hentai-foundry.com'
allow_subdomains = False
keep_subdomains = False
path_components = []
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'search', example_string = 'search' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'index', example_string = 'index' ) )
parameters = {}
parameters[ 'query' ] = ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_ANY, example_string = 'thick_thighs' )
example_url = 'https://www.hentai-foundry.com/search/index?query=thick_thighs'
url_match = ClientNetworkingDomain.URLMatch( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, allow_subdomains = allow_subdomains, keep_subdomains = keep_subdomains, path_components = path_components, parameters = parameters, example_url = example_url )
url_matches.append( url_match )
#
name = 'hentai foundry tag search gallery page'
url_type = HC.URL_TYPE_GALLERY
preferred_scheme = 'https'
netloc = 'www.hentai-foundry.com'
allow_subdomains = False
keep_subdomains = False
path_components = []
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'search', example_string = 'search' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'index', example_string = 'index' ) )
parameters = {}
parameters[ 'query' ] = ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_ANY, example_string = 'thick_thighs' )
parameters[ 'page' ] = ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FLEXIBLE, match_value = ClientParsing.NUMERIC, example_string = '5' )
example_url = 'https://www.hentai-foundry.com/search/index?query=thick_thighs&page=5'
url_match = ClientNetworkingDomain.URLMatch( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, allow_subdomains = allow_subdomains, keep_subdomains = keep_subdomains, path_components = path_components, parameters = parameters, example_url = example_url )
url_matches.append( url_match )
#
name = 'hentai foundry file page'
url_type = HC.URL_TYPE_POST
preferred_scheme = 'https'
netloc = 'www.hentai-foundry.com'
allow_subdomains = False
keep_subdomains = False
path_components = []
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'pictures', example_string = 'pictures' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FIXED, match_value = 'user', example_string = 'user' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_ANY, example_string = 'LittlePaw' ) )
path_components.append( ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FLEXIBLE, match_value = ClientParsing.NUMERIC, example_string = '554706' ) )
parameters = {}
example_url = 'https://www.hentai-foundry.com/pictures/user/LittlePaw/554706/Serpent-Girl'
url_match = ClientNetworkingDomain.URLMatch( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, allow_subdomains = allow_subdomains, keep_subdomains = keep_subdomains, path_components = path_components, parameters = parameters, example_url = example_url )
url_matches.append( url_match )
#
return url_matches

View File

@ -460,6 +460,7 @@ def ParsePageForURLs( html, starting_url ):
class GalleryIdentifier( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_GALLERY_IDENTIFIER
SERIALISABLE_NAME = 'Gallery Identifier'
SERIALISABLE_VERSION = 1
def __init__( self, site_type = None, additional_info = None ):

View File

@ -85,7 +85,9 @@ class FileDropTarget( wx.PyDropTarget ):
def OnDrop( self, x, y ):
drop_tlp = ClientGUICommon.GetXYTopTLP( x, y )
screen_position = self._parent.ClientToScreen( ( x, y ) )
drop_tlp = ClientGUICommon.GetXYTopTLP( screen_position )
my_tlp = ClientGUICommon.GetTLP( self._parent )
if drop_tlp == my_tlp:

View File

@ -215,6 +215,7 @@ def ParseExportPhrase( phrase ):
class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_EXPORT_FOLDER
SERIALISABLE_NAME = 'Export Folder'
SERIALISABLE_VERSION = 2
def __init__( self, name, path = '', export_type = HC.EXPORT_FOLDER_TYPE_REGULAR, file_search_context = None, period = 3600, phrase = None ):

View File

@ -637,6 +637,13 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
def _CheckImportFolder( self, name = None ):
options = self._controller.GetOptions()
if options[ 'pause_import_folders_sync' ]:
HydrusData.ShowText( 'Import folders are currently paused under the \'services\' menu. Please unpause them and try this again.' )
if name is None:
import_folders = self._controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FOLDER )
@ -1785,16 +1792,18 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
domain_manager = self._controller.network_engine.domain_manager
url_matches = domain_manager.GetURLMatches()
network_contexts_to_custom_header_dicts = domain_manager.GetNetworkContextsToCustomHeaderDicts()
panel = ClientGUIScrolledPanelsEdit.EditNetworkContextCustomHeadersPanel( dlg, network_contexts_to_custom_header_dicts )
panel = ClientGUIScrolledPanelsEdit.EditDomainManagerInfoPanel( dlg, url_matches, network_contexts_to_custom_header_dicts )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
network_contexts_to_custom_header_dicts = panel.GetValue()
( url_matches, network_contexts_to_custom_header_dicts ) = panel.GetValue()
domain_manager.SetURLMatches( url_matches )
domain_manager.SetNetworkContextsToCustomHeaderDicts( network_contexts_to_custom_header_dicts )

View File

@ -3625,7 +3625,16 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
if self._current_media is not None:
self.SetMedia( self._media_list.GetNext( self._current_media ) )
try:
other_media = self._media_list.GetNext( self._current_media )
self.SetMedia( other_media )
except HydrusExceptions.DataMissing:
return
@ -3802,25 +3811,29 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
def wx_close():
if self:
if not self:
wx.CallAfter( wx.MessageBox, 'All pairs have been filtered!' )
self._Close()
return
wx.CallAfter( wx.MessageBox, 'All pairs have been filtered!' )
self._Close()
def wx_continue( unprocessed_pairs ):
if self:
if not self:
self._unprocessed_pairs = unprocessed_pairs
self._currently_fetching_pairs = False
self._ShowNewPair()
return
self._unprocessed_pairs = unprocessed_pairs
self._currently_fetching_pairs = False
self._ShowNewPair()
result = HG.client_controller.Read( 'unique_duplicate_pairs', self._file_service_key, HC.DUPLICATE_UNKNOWN )
@ -3961,7 +3974,10 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
next_media = self._GetNext( self._current_media )
if next_media == self._current_media: next_media = None
if next_media == self._current_media:
next_media = None
hashes = { self._current_media.GetHash() }
@ -4050,7 +4066,10 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
next_media = self._GetNext( self._current_media )
if next_media == self._current_media: next_media = None
if next_media == self._current_media:
next_media = None
else:

View File

@ -65,11 +65,11 @@ def GetTLPParents( window ):
return parents
def GetXYTopTLP( x, y ):
def GetXYTopTLP( screen_position ):
tlps = wx.GetTopLevelWindows()
hittest_tlps = [ tlp for tlp in tlps if tlp.HitTest( ( x, y ) ) == wx.HT_WINDOW_INSIDE ]
hittest_tlps = [ tlp for tlp in tlps if tlp.HitTest( tlp.ScreenToClient( screen_position ) ) == wx.HT_WINDOW_INSIDE and tlp.IsShown() ]
if len( hittest_tlps ) == 0:
@ -2795,9 +2795,15 @@ class RadioBox( StaticBox ):
def SetSelection( self, index ): self._indices_to_radio_buttons[ index ].SetValue( True )
def SetSelection( self, index ):
self._indices_to_radio_buttons[ index ].SetValue( True )
def SetString( self, index, text ): self._indices_to_radio_buttons[ index ].SetLabelText( text )
def SetString( self, index, text ):
self._indices_to_radio_buttons[ index ].SetLabelText( text )
class TextAndGauge( wx.Panel ):
@ -2818,6 +2824,11 @@ class TextAndGauge( wx.Panel ):
def SetValue( self, text, value, range ):
if not self:
return
if text != self._st.GetLabelText():
self._st.SetLabelText( text )

View File

@ -913,7 +913,7 @@ class FrameInputLocalFiles( wx.Frame ):
self._job_key = ClientThreading.JobKey()
HG.client_controller.CallToThread( self.THREADParseImportablePaths, paths, self._job_key )
HG.client_controller.CallToThread( self.THREADParseImportablePaths, paths, self._job_key, self._progress_updater )
@ -970,6 +970,11 @@ class FrameInputLocalFiles( wx.Frame ):
def DoneParsing( self ):
if not self:
return
self._currently_parsing = False
self._ProcessQueue()
@ -1083,9 +1088,9 @@ class FrameInputLocalFiles( wx.Frame ):
self._tag_button.Enable()
def THREADParseImportablePaths( self, raw_paths, job_key ):
def THREADParseImportablePaths( self, raw_paths, job_key, progress_updater ):
self._progress_updater.Update( 'Finding all files', None, None )
progress_updater.Update( 'Finding all files', None, None )
file_paths = ClientFiles.GetAllPaths( raw_paths )
@ -1108,7 +1113,7 @@ class FrameInputLocalFiles( wx.Frame ):
message = 'Parsed ' + HydrusData.ConvertValueRangeToPrettyString( i, num_file_paths )
self._progress_updater.Update( message, i, num_file_paths )
progress_updater.Update( message, i, num_file_paths )
( i_paused, should_quit ) = job_key.WaitIfNeeded()
@ -1196,7 +1201,7 @@ class FrameInputLocalFiles( wx.Frame ):
HydrusData.Print( message )
self._progress_updater.Update( message, num_file_paths, num_file_paths )
progress_updater.Update( message, num_file_paths, num_file_paths )
self._done_parsing_updater.Update()

View File

@ -2759,6 +2759,11 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
return
if not os.path.exists( path ):
wx.MessageBox( 'The path you have entered--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )
if HC.BASE_DIR.startswith( path ) or HG.client_controller.GetDBDir().startswith( path ):
wx.MessageBox( 'You cannot set an import path that includes your install or database directory!' )
@ -2766,32 +2771,72 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
return
if self._action_successful.GetChoice() == CC.IMPORT_FOLDER_MOVE and self._location_successful.GetPath() in ( '', None ):
if self._action_successful.GetChoice() == CC.IMPORT_FOLDER_MOVE:
wx.MessageBox( 'You must enter a path for your successful file move location!' )
path = self._location_successful.GetPath()
return
if path in ( '', None ):
wx.MessageBox( 'You must enter a path for your successful file move location!' )
return
if not os.path.exists( path ):
wx.MessageBox( 'The path you have entered for your successful file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )
if self._action_redundant.GetChoice() == CC.IMPORT_FOLDER_MOVE and self._location_redundant.GetPath() in ( '', None ):
if self._action_redundant.GetChoice() == CC.IMPORT_FOLDER_MOVE:
wx.MessageBox( 'You must enter a path for your redundant file move location!' )
path = self._location_redundant.GetPath()
return
if path in ( '', None ):
wx.MessageBox( 'You must enter a path for your redundant file move location!' )
return
if not os.path.exists( path ):
wx.MessageBox( 'The path you have entered for your redundant file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )
if self._action_deleted.GetChoice() == CC.IMPORT_FOLDER_MOVE and self._location_deleted.GetPath() in ( '', None ):
if self._action_deleted.GetChoice() == CC.IMPORT_FOLDER_MOVE:
wx.MessageBox( 'You must enter a path for your deleted file move location!' )
path = self._location_deleted.GetPath()
return
if path in ( '', None ):
wx.MessageBox( 'You must enter a path for your deleted file move location!' )
return
if not os.path.exists( path ):
wx.MessageBox( 'The path you have entered for your deleted file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )
if self._action_failed.GetChoice() == CC.IMPORT_FOLDER_MOVE and self._location_failed.GetPath() in ( '', None ):
if self._action_failed.GetChoice() == CC.IMPORT_FOLDER_MOVE:
wx.MessageBox( 'You must enter a path for your failed file move location!' )
path = self._location_failed.GetPath()
return
if path in ( '', None ):
wx.MessageBox( 'You must enter a path for your failed file move location!' )
return
if not os.path.exists( path ):
wx.MessageBox( 'The path you have entered for your failed file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )
self.EndModal( wx.ID_OK )

View File

@ -451,6 +451,8 @@ class FilenameTaggingOptionsPanel( wx.Panel ):
self._tag_box = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self._tags_panel, self.EnterTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._tags_paste_button = ClientGUICommon.BetterButton( self._tags_panel, 'paste tags', self._PasteTags )
#
self._single_tags_panel = ClientGUICommon.StaticBox( self, 'tags just for selected files' )
@ -459,6 +461,8 @@ class FilenameTaggingOptionsPanel( wx.Panel ):
self._single_tags = ClientGUIListBoxes.ListBoxTagsStringsAddRemove( self._single_tags_panel, self._service_key, self.SingleTagsRemoved )
self._single_tags_paste_button = ClientGUICommon.BetterButton( self._single_tags_panel, 'paste tags', self._PasteSingleTags )
expand_parents = True
self._single_tag_box = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self._single_tags_panel, self.EnterTagsSingle, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
@ -530,9 +534,11 @@ class FilenameTaggingOptionsPanel( wx.Panel ):
self._tags_panel.AddF( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )
self._tags_panel.AddF( self._tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._tags_panel.AddF( self._tags_paste_button, CC.FLAGS_EXPAND_PERPENDICULAR )
self._single_tags_panel.AddF( self._single_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
self._single_tags_panel.AddF( self._single_tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._single_tags_panel.AddF( self._single_tags_paste_button, CC.FLAGS_EXPAND_PERPENDICULAR )
txt_hbox = wx.BoxSizer( wx.HORIZONTAL )
@ -586,6 +592,69 @@ class FilenameTaggingOptionsPanel( wx.Panel ):
self._dir_checkbox_3.Bind( wx.EVT_CHECKBOX, self.EventRefresh )
def _GetTagsFromClipboard( self ):
if wx.TheClipboard.Open():
data = wx.TextDataObject()
wx.TheClipboard.GetData( data )
wx.TheClipboard.Close()
text = data.GetText()
try:
tags = HydrusData.DeserialisePrettyTags( text )
tags = HydrusTags.CleanTags( tags )
return tags
except:
raise Exception( 'I could not understand what was in the clipboard' )
else:
raise Exception( 'I could not get permission to access the clipboard.' )
def _PasteTags( self ):
try:
tags = self._GetTagsFromClipboard()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
return
self.EnterTags( tags )
def _PasteSingleTags( self ):
try:
tags = self._GetTagsFromClipboard()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
return
self.EnterTagsSingle( tags )
def _ShowTXTHelp( self ):
message = 'If you would like to add custom tags with your files, add a .txt file beside the file like so:'
@ -699,10 +768,12 @@ class FilenameTaggingOptionsPanel( wx.Panel ):
self._single_tag_box.Enable()
self._single_tags_paste_button.Enable()
else:
self._single_tag_box.Disable()
self._single_tags_paste_button.Disable()
self._single_tags.SetTags( single_tags )

View File

@ -14,6 +14,8 @@ import HydrusTags
import os
import wx
( ListBoxEvent, EVT_LIST_BOX ) = wx.lib.newevent.NewCommandEvent()
class QueueListBox( wx.Panel ):
def __init__( self, parent, data_to_pretty_callable, add_callable, edit_callable ):
@ -24,7 +26,7 @@ class QueueListBox( wx.Panel ):
wx.Panel.__init__( self, parent )
self._listbox = wx.ListBox( self, style = wx.LB_MULTIPLE )
self._listbox = wx.ListBox( self, style = wx.LB_EXTENDED )
self._up_button = ClientGUICommon.BetterButton( self, u'\u2191', self._Up )
@ -63,6 +65,7 @@ class QueueListBox( wx.Panel ):
#
self._listbox.Bind( wx.EVT_LISTBOX, self.EventSelection )
self._listbox.Bind( wx.EVT_LISTBOX_DCLICK, self.EventEdit )
def _Add( self ):
@ -84,15 +87,15 @@ class QueueListBox( wx.Panel ):
def _Delete( self ):
indices = self._listbox.GetSelections()
indices.sort( reverse = True )
indices = list( self._listbox.GetSelections() )
if len( indices ) == 0:
return
indices.sort( reverse = True )
import ClientGUIDialogs
with ClientGUIDialogs.DialogYesNo( self, 'Remove all selected?' ) as dlg_yn:
@ -106,16 +109,18 @@ class QueueListBox( wx.Panel ):
wx.PostEvent( self.GetEventHandler(), ListBoxEvent( -1 ) )
def _Down( self ):
indices = self._listbox.GetSelections()
indices = list( self._listbox.GetSelections() )
indices.sort( reverse = True )
for i in indices:
if i > 0:
if i < self._listbox.GetCount() - 1:
if not self._listbox.IsSelected( i + 1 ): # is the one below not selected?
@ -124,6 +129,8 @@ class QueueListBox( wx.Panel ):
wx.PostEvent( self.GetEventHandler(), ListBoxEvent( -1 ) )
def _Edit( self ):
@ -144,7 +151,7 @@ class QueueListBox( wx.Panel ):
pretty_new_data = self._data_to_pretty_callable( new_data )
self._listbox.Set( pretty_new_data, i, new_data )
self._listbox.Insert( pretty_new_data, i, new_data )
else:
@ -152,9 +159,14 @@ class QueueListBox( wx.Panel ):
wx.PostEvent( self.GetEventHandler(), ListBoxEvent( -1 ) )
def _SwapRows( self, index_a, index_b ):
a_was_selected = self._listbox.IsSelected( index_a )
b_was_selected = self._listbox.IsSelected( index_b )
data_a = self._listbox.GetClientData( index_a )
data_b = self._listbox.GetClientData( index_b )
@ -167,6 +179,16 @@ class QueueListBox( wx.Panel ):
self._listbox.Delete( index_b )
self._listbox.Insert( pretty_data_a, index_b, data_a )
if b_was_selected:
self._listbox.Select( index_a )
if a_was_selected:
self._listbox.Select( index_b )
def _Up( self ):
@ -183,6 +205,8 @@ class QueueListBox( wx.Panel ):
wx.PostEvent( self.GetEventHandler(), ListBoxEvent( -1 ) )
def AddDatas( self, datas ):
@ -191,15 +215,22 @@ class QueueListBox( wx.Panel ):
self._AddData( data )
wx.PostEvent( self.GetEventHandler(), ListBoxEvent( -1 ) )
def Bind( self, event, handler ):
self._listbox.Bind( event, handler )
def EventEdit( self, event ):
self._Edit()
def EventSelection( self, event ):
if self._listbox.GetSelection() == wx.NOT_FOUND:
if len( self._listbox.GetSelections() ) == 0:
self._up_button.Disable()
self._delete_button.Disable()
@ -233,8 +264,6 @@ class QueueListBox( wx.Panel ):
return datas
( ListBoxEvent, EVT_LIST_BOX ) = wx.lib.newevent.NewCommandEvent()
class ListBox( wx.ScrolledWindow ):
TEXT_X_PADDING = 3

View File

@ -1,7 +1,12 @@
import ClientConstants as CC
import ClientData
import ClientGUICommon
import ClientSerialisable
import HydrusData
import HydrusExceptions
import HydrusGlobals as HG
import HydrusSerialisable
import os
import wx
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
from wx.lib.mixins.listctrl import ColumnSorterMixin
@ -923,6 +928,9 @@ class BetterListCtrlPanel( wx.Panel ):
self._listctrl = None
self._permitted_object_types = []
self._import_add_callable = lambda x: None
self._button_infos = []
@ -941,11 +949,177 @@ class BetterListCtrlPanel( wx.Panel ):
def _Duplicate( self ):
dupe_data = self._GetExportObject()
if dupe_data is not None:
dupe_data = dupe_data.Duplicate()
self._ImportObject( dupe_data )
def _ExportToClipboard( self ):
export_object = self._GetExportObject()
if export_object is not None:
json = export_object.DumpToString()
HG.client_controller.pub( 'clipboard', 'text', json )
def _ExportToPng( self ):
export_object = self._GetExportObject()
if export_object is not None:
import ClientGUITopLevelWindows
import ClientGUISerialisable
with ClientGUITopLevelWindows.DialogNullipotent( self, 'export to png' ) as dlg:
panel = ClientGUISerialisable.PngExportPanel( dlg, export_object )
dlg.SetPanel( panel )
dlg.ShowModal()
def _GetExportObject( self ):
to_export = HydrusSerialisable.SerialisableList()
for obj in self._listctrl.GetData( only_selected = True ):
to_export.append( obj )
if len( to_export ) == 0:
return None
elif len( to_export ) == 1:
return to_export[0]
else:
return to_export
def _HasSelected( self ):
return self._listctrl.HasSelected()
def _ImportFromClipboard( self ):
if wx.TheClipboard.Open():
data = wx.TextDataObject()
wx.TheClipboard.GetData( data )
wx.TheClipboard.Close()
raw_text = data.GetText()
try:
obj = HydrusSerialisable.CreateFromString( raw_text )
self._ImportObject( obj )
except Exception as e:
wx.MessageBox( 'I could not understand what was in the clipboard' )
else:
wx.MessageBox( 'I could not get permission to access the clipboard.' )
def _ImportFromPng( self ):
with wx.FileDialog( self, 'select the png with the encoded script', wildcard = 'PNG (*.png)|*.png' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = HydrusData.ToUnicode( dlg.GetPath() )
try:
payload = ClientSerialisable.LoadFromPng( path )
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
return
try:
obj = HydrusSerialisable.CreateFromNetworkString( payload )
self._ImportObject( obj )
except:
wx.MessageBox( 'I could not understand what was encoded in the png!' )
def _ImportObject( self, obj ):
bad_object_types = set()
if isinstance( obj, HydrusSerialisable.SerialisableList ):
for sub_obj in obj:
self._ImportObject( sub_obj )
else:
if isinstance( obj, self._permitted_object_types ):
self._import_add_callable( obj )
else:
bad_object_types.add( type( obj ).__name__ )
if len( bad_object_types ) > 0:
message = 'The imported objects included these types:'
message += os.linesep * 2
message += os.linesep.join( bad_object_types )
message += os.linesep * 2
message += 'Whereas this control only allows:'
message += os.linesep * 2
message += os.linesep.join( ( o.__name__ for o in self._permitted_object_types ) )
wx.MessageBox( message )
def _UpdateButtons( self ):
for ( button, enabled_check_func ) in self._button_infos:
@ -970,6 +1144,26 @@ class BetterListCtrlPanel( wx.Panel ):
self._UpdateButtons()
def AddImportExportButtons( self, permitted_object_types, import_add_callable ):
self._permitted_object_types = permitted_object_types
self._import_add_callable = import_add_callable
export_menu_items = []
export_menu_items.append( ( 'normal', 'to clipboard', 'Serialise the selected data and put it on your clipboard.', self._ExportToClipboard ) )
export_menu_items.append( ( 'normal', 'to png', 'Serialise the selected data and encode it to an image file you can easily share with other hydrus users.', self._ExportToPng ) )
import_menu_items = []
import_menu_items.append( ( 'normal', 'from clipboard', 'Load a data from text in your clipboard.', self._ImportFromClipboard ) )
import_menu_items.append( ( 'normal', 'from png', 'Load a data from an encoded png.', self._ImportFromPng ) )
self.AddMenuButton( 'export', export_menu_items, enabled_only_on_selection = True )
self.AddMenuButton( 'import', import_menu_items )
self.AddButton( 'duplicate', self._Duplicate, enabled_only_on_selection = True )
def AddMenuButton( self, label, menu_items, enabled_only_on_selection = False, enabled_check_func = None ):
button = ClientGUICommon.MenuButton( self, label, menu_items )

View File

@ -550,6 +550,7 @@ def GenerateDumpMultipartFormDataCTAndBody( fields ):
class ManagementController( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_MANAGEMENT_CONTROLLER
SERIALISABLE_NAME = 'Client Page Management Controller'
SERIALISABLE_VERSION = 3
def __init__( self, page_name = 'page' ):
@ -2354,7 +2355,7 @@ class ManagementPanelImporterThreadWatcher( ManagementPanelImporter ):
if watcher_status == '':
watcher_status = 'next check in ' + HydrusData.ConvertTimestampToPrettyPending( next_check_time )
watcher_status = 'next check ' + HydrusData.ConvertTimestampToPrettyPending( next_check_time )
if self._thread_pause_button.GetBitmap() != CC.GlobalBMPs.pause:

File diff suppressed because it is too large Load Diff

View File

@ -2325,6 +2325,7 @@ class PagesNotebook( wx.Notebook ):
class GUISession( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION
SERIALISABLE_NAME = 'GUI Session'
SERIALISABLE_VERSION = 3
def __init__( self, name ):

View File

@ -361,6 +361,11 @@ class ReviewServicePanel( wx.Panel ):
def _Refresh( self ):
if not self:
return
name = self._service.GetName()
service_type = self._service.GetServiceType()
@ -405,6 +410,11 @@ class ReviewServicePanel( wx.Panel ):
def _Refresh( self ):
if not self:
return
HG.client_controller.CallToThread( self.THREADFetchInfo )
@ -422,11 +432,13 @@ class ReviewServicePanel( wx.Panel ):
def wx_code( text ):
if self:
if not self:
self._file_info_st.SetLabelText( text )
return
self._file_info_st.SetLabelText( text )
service_info = HG.client_controller.Read( 'service_info', self._service.GetServiceKey() )
@ -477,6 +489,11 @@ class ReviewServicePanel( wx.Panel ):
def _Refresh( self ):
if not self:
return
credentials = self._service.GetCredentials()
( host, port ) = credentials.GetAddress()
@ -581,6 +598,11 @@ class ReviewServicePanel( wx.Panel ):
def _Refresh( self ):
if not self:
return
account = self._service.GetAccount()
account_type = account.GetAccountType()
@ -843,6 +865,11 @@ class ReviewServicePanel( wx.Panel ):
def _Refresh( self ):
if not self:
return
service_paused = self._service.IsPaused()
self._sync_now_button.Disable()
@ -924,51 +951,52 @@ class ReviewServicePanel( wx.Panel ):
def wx_code( download_text, download_value, processing_text, processing_value, range ):
if self:
if not self:
self._download_progress.SetValue( download_text, download_value, range )
self._processing_progress.SetValue( processing_text, processing_value, range )
return
if processing_value == download_value:
self._sync_now_button.Disable()
self._download_progress.SetValue( download_text, download_value, range )
self._processing_progress.SetValue( processing_text, processing_value, range )
if processing_value == download_value:
if download_value == 0:
self._export_updates_button.Disable()
else:
self._export_updates_button.Enable()
self._sync_now_button.Disable()
if processing_value == 0:
self._reset_button.Disable()
else:
self._reset_button.Enable()
if download_value == 0:
processing_work_to_do = processing_value < download_value
self._export_updates_button.Disable()
service_paused = self._service.IsPaused()
else:
options = HG.client_controller.GetOptions()
self._export_updates_button.Enable()
all_repo_sync_paused = options[ 'pause_repo_sync' ]
if processing_value == 0:
if service_paused or all_repo_sync_paused or not processing_work_to_do:
self._sync_now_button.Disable()
else:
self._sync_now_button.Enable()
self._reset_button.Disable()
else:
self._reset_button.Enable()
processing_work_to_do = processing_value < download_value
service_paused = self._service.IsPaused()
options = HG.client_controller.GetOptions()
all_repo_sync_paused = options[ 'pause_repo_sync' ]
if service_paused or all_repo_sync_paused or not processing_work_to_do:
self._sync_now_button.Disable()
else:
self._sync_now_button.Enable()
@ -1091,6 +1119,11 @@ class ReviewServicePanel( wx.Panel ):
def _Refresh( self ):
if not self:
return
HG.client_controller.CallToThread( self.THREADFetchInfo )
@ -1194,18 +1227,20 @@ class ReviewServicePanel( wx.Panel ):
def wx_code( ipfs_shares ):
if self:
if not self:
self._ipfs_shares.DeleteAllItems()
return
for ( multihash, num_files, total_size, note ) in ipfs_shares:
sort_tuple = ( multihash, num_files, total_size, note )
display_tuple = self._GetDisplayTuple( sort_tuple )
self._ipfs_shares.Append( display_tuple, sort_tuple )
self._ipfs_shares.DeleteAllItems()
for ( multihash, num_files, total_size, note ) in ipfs_shares:
sort_tuple = ( multihash, num_files, total_size, note )
display_tuple = self._GetDisplayTuple( sort_tuple )
self._ipfs_shares.Append( display_tuple, sort_tuple )
@ -1240,6 +1275,11 @@ class ReviewServicePanel( wx.Panel ):
def _Refresh( self ):
if not self:
return
self._name_and_type.SetLabelText( 'This is a Local Booru service. This box will regain its old information and controls in a later version.' )

View File

@ -206,6 +206,35 @@ class EditChooseMultiple( ClientGUIScrolledPanels.EditPanel ):
return datas
class EditDomainManagerInfoPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, url_matches, network_contexts_to_custom_header_dicts ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._notebook = wx.Notebook( self )
self._url_matches_panel = EditURLMatchesPanel( self._notebook, url_matches )
self._network_contexts_to_custom_header_dicts_panel = EditNetworkContextCustomHeadersPanel( self._notebook, network_contexts_to_custom_header_dicts )
self._notebook.AddPage( self._url_matches_panel, 'url classes', select = True )
self._notebook.AddPage( self._network_contexts_to_custom_header_dicts_panel, 'custom headers', select = False )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._notebook, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
def GetValue( self ):
url_matches = self._url_matches_panel.GetValue()
network_contexts_to_custom_header_dicts = self._network_contexts_to_custom_header_dicts_panel.GetValue()
return ( url_matches, network_contexts_to_custom_header_dicts )
class EditDuplicateActionOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, duplicate_action, duplicate_action_options ):
@ -1207,6 +1236,8 @@ class EditNetworkContextCustomHeadersPanel( ClientGUIScrolledPanels.EditPanel ):
if dlg.ShowModal() == wx.ID_OK:
( network_context, key, value, approved, reason ) = panel.GetValue()
data = ( network_context, ( key, value ), approved, reason )
self._list_ctrl.AddDatas( ( data, ) )
@ -1748,7 +1779,7 @@ class EditSubscriptionPanel( ClientGUIScrolledPanels.EditPanel ):
queries_panel = ClientGUIListCtrl.BetterListCtrlPanel( self._query_panel )
self._queries = ClientGUIListCtrl.BetterListCtrl( queries_panel, 'subscription_queries', 8, 20, [ ( 'query', 20 ), ( 'paused', 8 ), ( 'status', 8 ), ( 'last new file time', 20 ), ( 'last check time', 20 ), ( 'next check time', 20 ), ( 'file progress', 14 ), ( 'file summary', -1 ) ], self._ConvertQueryToListCtrlTuples, delete_key_callback = self._DeleteQuery, activation_callback = self._EditQuery )
self._queries = ClientGUIListCtrl.BetterListCtrl( queries_panel, 'subscription_queries', 8, 20, [ ( 'query', 20 ), ( 'paused', 8 ), ( 'status', 8 ), ( 'last new file time', 20 ), ( 'last check time', 20 ), ( 'next check time', 20 ), ( 'file velocity', 20 ), ( 'file progress', 14 ), ( 'file summary', -1 ) ], self._ConvertQueryToListCtrlTuples, delete_key_callback = self._DeleteQuery, activation_callback = self._EditQuery )
queries_panel.SetListCtrl( self._queries )
@ -1988,6 +2019,9 @@ class EditSubscriptionPanel( ClientGUIScrolledPanels.EditPanel ):
pretty_next_check_time = query.GetNextCheckStatusString()
file_velocity = self._checker_options.GetRawCurrentVelocity( query.GetSeedCache(), last_check_time )
pretty_file_velocity = self._checker_options.GetPrettyCurrentVelocity( query.GetSeedCache(), last_check_time, no_prefix = True )
( file_status, ( num_done, num_total ) ) = seed_cache.GetStatus()
file_value_range = ( num_total, num_done )
@ -1995,8 +2029,8 @@ class EditSubscriptionPanel( ClientGUIScrolledPanels.EditPanel ):
pretty_file_status = file_status
display_tuple = ( pretty_query_text, pretty_paused, pretty_status, pretty_last_new_file_time, pretty_last_check_time, pretty_next_check_time, pretty_file_value_range, pretty_file_status )
sort_tuple = ( query_text, paused, status, last_new_file_time, last_check_time, next_check_time, file_value_range, file_status )
display_tuple = ( pretty_query_text, pretty_paused, pretty_status, pretty_last_new_file_time, pretty_last_check_time, pretty_next_check_time, pretty_file_velocity, pretty_file_value_range, pretty_file_status )
sort_tuple = ( query_text, paused, status, last_new_file_time, last_check_time, next_check_time, file_velocity, file_value_range, file_status )
return ( display_tuple, sort_tuple )
@ -2215,7 +2249,7 @@ class EditSubscriptionPanel( ClientGUIScrolledPanels.EditPanel ):
else:
status = 'delaying for ' + HydrusData.ConvertTimestampToPrettyPending( self._no_work_until ) + ' because: ' + self._no_work_until_reason
status = 'delaying ' + HydrusData.ConvertTimestampToPrettyPending( self._no_work_until, prefix = 'for' ) + ' because: ' + self._no_work_until_reason
self._delay_st.SetLabelText( status )
@ -2807,7 +2841,7 @@ class EditTagImportOptions( ClientGUIScrolledPanels.EditPanel ):
return tag_import_options
class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
class EditURLMatchPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, url_match ):
@ -2815,6 +2849,13 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
self._name = wx.TextCtrl( self )
self._url_type = ClientGUICommon.BetterChoice( self )
for url_type in ( HC.URL_TYPE_POST, HC.URL_TYPE_GALLERY, HC.URL_TYPE_API, HC.URL_TYPE_FILE ):
self._url_type.Append( HC.url_type_string_lookup[ url_type ], url_type )
self._preferred_scheme = ClientGUICommon.BetterChoice( self )
self._preferred_scheme.Append( 'http', 'http' )
@ -2822,7 +2863,8 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
self._netloc = wx.TextCtrl( self )
self._subdomain_is_important = wx.CheckBox( self )
self._keep_subdomains= wx.CheckBox( self )
self._allow_subdomains = wx.CheckBox( self )
#
@ -2834,9 +2876,15 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
parameters_panel = ClientGUICommon.StaticBox( self, 'parameters' )
self._parameters = ClientGUIListCtrl.BetterListCtrl( parameters_panel, 'url_match_path_components', 5, 20, [ ( 'key', 14 ), ( 'value', -1 ) ], self._ConvertParameterToListCtrlTuples, delete_key_callback = self._DeleteParameters, activation_callback = self._EditParameters )
parameters_listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( parameters_panel )
# parameter buttons, do it with a new wrapper panel class
self._parameters = ClientGUIListCtrl.BetterListCtrl( parameters_listctrl_panel, 'url_match_path_components', 5, 45, [ ( 'key', 14 ), ( 'value', -1 ) ], self._ConvertParameterToListCtrlTuples, delete_key_callback = self._DeleteParameters, activation_callback = self._EditParameters )
parameters_listctrl_panel.SetListCtrl( self._parameters )
parameters_listctrl_panel.AddButton( 'add', self._AddParameters )
parameters_listctrl_panel.AddButton( 'edit', self._EditParameters, enabled_only_on_selection = True )
parameters_listctrl_panel.AddButton( 'delete', self._DeleteParameters, enabled_only_on_selection = True )
#
@ -2844,42 +2892,65 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
self._example_url_matches = ClientGUICommon.BetterStaticText( self )
self._normalised_url = wx.TextCtrl( self )
self._normalised_url.Disable()
#
name = url_match.GetName()
self._name.SetValue( name )
( preferred_scheme, netloc, subdomain_is_important, path_components, parameters, example_url ) = url_match.ToTuple()
( url_type, preferred_scheme, netloc, allow_subdomains, keep_subdomains, path_components, parameters, example_url ) = url_match.ToTuple()
self._url_type.SelectClientData( url_type )
self._preferred_scheme.SelectClientData( preferred_scheme )
self._netloc.SetValue( netloc )
self._subdomain_is_important.SetValue( subdomain_is_important )
self._allow_subdomains.SetValue( allow_subdomains )
self._keep_subdomains.SetValue( keep_subdomains )
self._path_components.AddDatas( path_components )
00
self._parameters.AddDatas( parameters.items() )
self._parameters.Sort()
self._example_url.SetValue( example_url )
example_url_width = ClientData.ConvertTextToPixelWidth( self._example_url, 75 )
self._example_url.SetMinSize( ( example_url_width, -1 ) )
self._UpdateControls()
#
path_components_panel.AddF( self._path_components, CC.FLAGS_EXPAND_BOTH_WAYS )
#
parameters_panel.AddF( parameters_listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
#
rows = []
rows.append( ( 'name: ', self._name ) )
rows.append( ( 'url type: ', self._url_type ) )
rows.append( ( 'preferred scheme: ', self._preferred_scheme ) )
rows.append( ( 'network location: ', self._netloc ) )
rows.append( ( 'keep subdomains?: ', self._subdomain_is_important ) )
rows.append( ( 'allow subdomains?: ', self._allow_subdomains ) )
rows.append( ( 'keep subdomains?: ', self._keep_subdomains ) )
gridbox_1 = ClientGUICommon.WrapInGrid( self, rows )
rows = []
rows.append( ( 'example url: ', self._example_url ) )
rows.append( ( 'normalised url: ', self._normalised_url ) )
gridbox_2 = ClientGUICommon.WrapInGrid( self, rows )
@ -2897,20 +2968,59 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
self._preferred_scheme.Bind( wx.EVT_CHOICE, self.EventUpdate )
self._netloc.Bind( wx.EVT_TEXT, self.EventUpdate )
self._subdomain_is_important.Bind( wx.EVT_CHECKBOX, self.EventUpdate )
self.Bind( wx.EVT_CHECKBOX, self.EventUpdate )
self._example_url.Bind( wx.EVT_TEXT, self.EventUpdate )
self.Bind( ClientGUIListBoxes.EVT_LIST_BOX, self.EventUpdate )
def _AddParameters( self ):
# throw up a dialog to take key text
# warn on key conflict I guess
with ClientGUIDialogs.DialogTextEntry( self, 'edit the key', default = 'key', allow_blank = False ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
key = dlg.GetValue()
else:
return
# throw up a string match dialog to take value
existing_keys = self._GetExistingKeys()
# add it
if key in existing_keys:
wx.MessageBox( 'That key already exists!' )
return
pass
import ClientGUIParsing
string_match = ClientParsing.StringMatch()
with ClientGUITopLevelWindows.DialogEdit( self, 'edit value' ) as dlg:
panel = ClientGUIParsing.EditStringMatchPanel( dlg, string_match )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
string_match = panel.GetValue()
else:
return
self._parameters.AddDatas( ( key, string_match ) )
self._parameters.Sort()
self._UpdateControls()
def _AddPathComponent( self ):
@ -2943,19 +3053,77 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
def _DeleteParameters( self ):
# ask for certain, then do it
with ClientGUIDialogs.DialogYesNo( self, 'Remove all selected?' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._parameters.DeleteSelected()
pass
self._UpdateControls()
def _EditParameters( self ):
# for each in list, throw up dialog for key, value
# delete and readd
# break on cancel, etc...
# sort at the end
selected_params = self._parameters.GetData( only_selected = True )
pass
for parameter in selected_params:
( original_key, original_string_match ) = parameter
with ClientGUIDialogs.DialogTextEntry( self, 'edit the key', default = original_key, allow_blank = False ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
key = dlg.GetValue()
else:
return
if key != original_key:
existing_keys = self._GetExistingKeys()
if key in existing_keys:
wx.MessageBox( 'That key already exists!' )
return
import ClientGUIParsing
with ClientGUITopLevelWindows.DialogEdit( self, 'edit value' ) as dlg:
panel = ClientGUIParsing.EditStringMatchPanel( dlg, original_string_match )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
string_match = panel.GetValue()
else:
return
self._parameters.DeleteDatas( ( parameter, ) )
new_parameter = ( key, string_match )
self._parameters.AddDatas( ( new_parameter, ) )
self._parameters.Sort()
self._UpdateControls()
def _EditPathComponent( self, string_match ):
@ -2968,7 +3136,7 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
dlg.SetPanel( panel )
if dlg.Showmodal() == wx.ID_OK:
if dlg.ShowModal() == wx.ID_OK:
new_string_match = panel.GetValue()
@ -2981,23 +3149,44 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
def _GetExistingKeys( self ):
params = self._parameters.GetData()
keys = { key for ( key, string_match ) in params }
return keys
def _GetValue( self ):
name = self._name.GetValue()
url_type = self._url_type.GetChoice()
preferred_scheme = self._preferred_scheme.GetChoice()
netloc = self._netloc.GetValue()
subdomain_is_important = self._subdomain_is_important.GetValue()
allow_subdomains = self._allow_subdomains.GetValue()
keep_subdomains = self._keep_subdomains.GetValue()
path_components = self._path_components.GetData()
parameters = self._parameters.GetData()
parameters = dict( self._parameters.GetData() )
example_url = self._example_url.GetValue()
url_match = ClientNetworkingDomain.URLMatch( name, preferred_scheme = preferred_scheme, netloc = netloc, subdomain_is_important = subdomain_is_important, path_components = path_components, parameters = parameters, example_url = example_url )
url_match = ClientNetworkingDomain.URLMatch( name, url_type = url_type, preferred_scheme = preferred_scheme, netloc = netloc, allow_subdomains = allow_subdomains, keep_subdomains = keep_subdomains, path_components = path_components, parameters = parameters, example_url = example_url )
return url_match
def _UpdateControls( self ):
if self._allow_subdomains.GetValue():
self._keep_subdomains.Enable()
else:
self._keep_subdomains.SetValue( False )
self._keep_subdomains.Disable()
url_match = self._GetValue()
try:
@ -3007,6 +3196,8 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
self._example_url_matches.SetLabelText( 'Example matches ok!' )
self._example_url_matches.SetForegroundColour( ( 0, 128, 0 ) )
self._normalised_url.SetValue( url_match.Normalise( self._example_url.GetValue() ) )
except HydrusExceptions.URLMatchException as e:
reason = unicode( e )
@ -3014,6 +3205,8 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
self._example_url_matches.SetLabelText( 'Example does not match - ' + reason )
self._example_url_matches.SetForegroundColour( ( 128, 0, 0 ) )
self._normalised_url.SetValue( '' )
def EventUpdate( self, event ):
@ -3039,6 +3232,143 @@ class EditURLMatch( ClientGUIScrolledPanels.EditPanel ):
return url_match
class EditURLMatchesPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, url_matches ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._list_ctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
self._list_ctrl = ClientGUIListCtrl.BetterListCtrl( self._list_ctrl_panel, 'url_matches', 15, 40, [ ( 'name', 36 ), ( 'example url', -1 ) ], self._ConvertDataToListCtrlTuples, delete_key_callback = self._Delete, activation_callback = self._Edit )
self._list_ctrl_panel.SetListCtrl( self._list_ctrl )
self._list_ctrl_panel.AddButton( 'add', self._Add )
self._list_ctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
self._list_ctrl_panel.AddButton( 'delete', self._Delete, enabled_only_on_selection = True )
self._list_ctrl_panel.AddSeparator()
self._list_ctrl_panel.AddImportExportButtons( ClientNetworkingDomain.URLMatch, self._AddURLMatch )
self._list_ctrl_panel.AddSeparator()
self._list_ctrl_panel.AddButton( 'add the hf examples', self._AddHFExamples )
self._list_ctrl.Sort( 0 )
#
self._list_ctrl.AddDatas( url_matches )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._list_ctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
def _Add( self ):
url_match = ClientNetworkingDomain.URLMatch( 'new url class' )
with ClientGUITopLevelWindows.DialogEdit( self, 'edit url class' ) as dlg:
panel = EditURLMatchPanel( dlg, url_match )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
url_match = panel.GetValue()
self._AddURLMatch( url_match )
def _AddHFExamples( self ):
for url_match in ClientDefaults.GetDefaultURLMatches():
self._AddURLMatch( url_match )
def _AddURLMatch( self, url_match ):
ClientGUIListCtrl.SetNonDupeName( url_match, self._GetExistingNames() )
self._list_ctrl.AddDatas( ( url_match, ) )
def _ConvertDataToListCtrlTuples( self, url_match ):
name = url_match.GetName()
example_url = url_match.GetExampleURL()
pretty_name = name
pretty_example_url = example_url
display_tuple = ( pretty_name, pretty_example_url )
sort_tuple = ( name, example_url )
return ( display_tuple, sort_tuple )
def _Delete( self ):
with ClientGUIDialogs.DialogYesNo( self, 'Remove all selected?' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._list_ctrl.DeleteSelected()
def _Edit( self ):
for url_match in self._list_ctrl.GetData( only_selected = True ):
with ClientGUITopLevelWindows.DialogEdit( self, 'edit url class' ) as dlg:
panel = EditURLMatchPanel( dlg, url_match )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
self._list_ctrl.DeleteDatas( ( url_match, ) )
url_match = panel.GetValue()
ClientGUIListCtrl.SetNonDupeName( url_match, self._GetExistingNames() )
self._list_ctrl.AddDatas( ( url_match, ) )
else:
break
def _GetExistingNames( self ):
url_matches = self._list_ctrl.GetData()
names = { url_match.GetName() for url_match in url_matches }
return names
def GetValue( self ):
url_matches = self._list_ctrl.GetData()
return url_matches
class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, checker_options ):

View File

@ -2590,6 +2590,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._show_thumbnail_title_banner = wx.CheckBox( self )
self._show_thumbnail_page = wx.CheckBox( self )
self._thumbnail_fill = wx.CheckBox( self )
self._thumbnail_visibility_scroll_percent = wx.SpinCtrl( self, min = 1, max = 99 )
self._thumbnail_visibility_scroll_percent.SetToolTipString( 'Lower numbers will cause fewer scrolls, higher numbers more.' )
@ -2665,6 +2667,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._show_thumbnail_page.SetValue( self._new_options.GetBoolean( 'show_thumbnail_page' ) )
self._thumbnail_fill.SetValue( self._new_options.GetBoolean( 'thumbnail_fill' ) )
self._thumbnail_visibility_scroll_percent.SetValue( self._new_options.GetInteger( 'thumbnail_visibility_scroll_percent' ) )
self._discord_dnd_fix.SetValue( self._new_options.GetBoolean( 'discord_dnd_fix' ) )
@ -2703,6 +2707,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
rows.append( ( 'Hide the preview window: ', self._hide_preview ) )
rows.append( ( 'Show \'title\' banner on thumbnails: ', self._show_thumbnail_title_banner ) )
rows.append( ( 'Show volume/chapter/page number on thumbnails: ', self._show_thumbnail_page ) )
rows.append( ( 'Zoom thumbnails so they \'fill\' their space (experimental): ', self._thumbnail_fill ) )
rows.append( ( 'Do not scroll down on key navigation if thumbnail at least this % visible: ', self._thumbnail_visibility_scroll_percent ) )
rows.append( ( 'BUGFIX: Discord file drag-and-drop fix (works for <=10, <50MB file DnDs): ', self._discord_dnd_fix ) )
rows.append( ( 'BUGFIX: Always show media viewer hover windows: ', self._always_show_hover_windows ) )
@ -2797,6 +2802,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._new_options.SetBoolean( 'show_thumbnail_title_banner', self._show_thumbnail_title_banner.GetValue() )
self._new_options.SetBoolean( 'show_thumbnail_page', self._show_thumbnail_page.GetValue() )
self._new_options.SetBoolean( 'thumbnail_fill', self._thumbnail_fill.GetValue() )
self._new_options.SetInteger( 'thumbnail_visibility_scroll_percent', self._thumbnail_visibility_scroll_percent.GetValue() )
@ -5184,7 +5190,7 @@ class ManageSubscriptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
else:
pretty_delay = 'delaying for ' + HydrusData.ConvertTimestampToPrettyPending( no_work_until ) + ' - ' + no_work_until_reason
pretty_delay = 'delaying ' + HydrusData.ConvertTimestampToPrettyPending( no_work_until, prefix = 'for' ) + ' - ' + no_work_until_reason
delay = no_work_until - HydrusData.GetNow()

View File

@ -57,28 +57,34 @@ class EditSeedCachePanel( ClientGUIScrolledPanels.EditPanel ):
def _ConvertSeedToListCtrlTuples( self, seed ):
sort_tuple = self._seed_cache.GetSeedInfo( seed )
seed_index = self._seed_cache.GetSeedIndex( seed )
( seed_index, seed, status, added_timestamp, last_modified_timestamp, source_timestamp, note ) = sort_tuple
seed_data = seed.seed_data
status = seed.status
added = seed.created
modified = seed.modified
source_time = seed.source_time
note = seed.note
pretty_seed_index = HydrusData.ConvertIntToPrettyString( seed_index )
pretty_seed = HydrusData.ToUnicode( seed )
pretty_seed_data = HydrusData.ToUnicode( seed_data )
pretty_status = CC.status_string_lookup[ status ]
pretty_added = HydrusData.ConvertTimestampToPrettyAgo( added_timestamp ) + ' ago'
pretty_modified = HydrusData.ConvertTimestampToPrettyAgo( last_modified_timestamp ) + ' ago'
pretty_added = HydrusData.ConvertTimestampToPrettyAgo( added ) + ' ago'
pretty_modified = HydrusData.ConvertTimestampToPrettyAgo( modified ) + ' ago'
if source_timestamp is None:
if source_time is None:
pretty_source_time = 'unknown'
else:
pretty_source_time = HydrusData.ConvertTimestampToHumanPrettyTime( source_timestamp )
pretty_source_time = HydrusData.ConvertTimestampToHumanPrettyTime( source_time )
pretty_note = note.split( os.linesep )[0]
display_tuple = ( pretty_seed_index, pretty_seed, pretty_status, pretty_added, pretty_modified, pretty_source_time, pretty_note )
display_tuple = ( pretty_seed_index, pretty_seed_data, pretty_status, pretty_added, pretty_modified, pretty_source_time, pretty_note )
sort_tuple = ( seed_index, seed_data, status, added, modified, source_time, note )
return ( display_tuple, sort_tuple )
@ -89,7 +95,7 @@ class EditSeedCachePanel( ClientGUIScrolledPanels.EditPanel ):
for seed in self._list_ctrl.GetData( only_selected = True ):
( seed_index, seed, status, added_timestamp, last_modified_timestamp, source_timestamp, note ) = self._seed_cache.GetSeedInfo( seed )
note = seed.note
if note != '':
@ -107,7 +113,7 @@ class EditSeedCachePanel( ClientGUIScrolledPanels.EditPanel ):
def _CopySelectedSeeds( self ):
def _CopySelectedSeedData( self ):
seeds = self._list_ctrl.GetData( only_selected = True )
@ -115,7 +121,7 @@ class EditSeedCachePanel( ClientGUIScrolledPanels.EditPanel ):
separator = os.linesep * 2
text = separator.join( seeds )
text = separator.join( ( seed.seed_data for seed in seeds ) )
HG.client_controller.pub( 'clipboard', 'text', text )
@ -141,9 +147,14 @@ class EditSeedCachePanel( ClientGUIScrolledPanels.EditPanel ):
def _SetSelected( self, status_to_set ):
seeds_to_set = self._list_ctrl.GetData( only_selected = True )
seeds = self._list_ctrl.GetData( only_selected = True )
self._seed_cache.UpdateSeedsStatus( seeds_to_set, status_to_set )
for seed in seeds:
seed.SetStatus( status_to_set )
self._seed_cache.NotifySeedsUpdated( seeds )
def _ShowMenuIfNeeded( self ):
@ -152,7 +163,7 @@ class EditSeedCachePanel( ClientGUIScrolledPanels.EditPanel ):
menu = wx.Menu()
ClientGUIMenus.AppendMenuItem( self, menu, 'copy sources', 'Copy all the selected sources to clipboard.', self._CopySelectedSeeds )
ClientGUIMenus.AppendMenuItem( self, menu, 'copy sources', 'Copy all the selected sources to clipboard.', self._CopySelectedSeedData )
ClientGUIMenus.AppendMenuItem( self, menu, 'copy notes', 'Copy all the selected notes to clipboard.', self._CopySelectedNotes )
ClientGUIMenus.AppendSeparator( menu )

File diff suppressed because it is too large Load Diff

View File

@ -462,6 +462,11 @@ class LocationsManager( object ):
return CC.COMBINED_LOCAL_FILE_SERVICE_KEY in self._current
def IsRemote( self ):
return CC.COMBINED_LOCAL_FILE_SERVICE_KEY not in self._current
def IsTrashed( self ):
return CC.TRASH_SERVICE_KEY in self._current
@ -713,12 +718,21 @@ class MediaList( object ):
def _GetNext( self, media ):
if media is None: return None
if media is None:
return None
next_index = self._sorted_media.index( media ) + 1
if next_index == len( self._sorted_media ): return self._GetFirst()
else: return self._sorted_media[ next_index ]
if next_index == len( self._sorted_media ):
return self._GetFirst()
else:
return self._sorted_media[ next_index ]
def _GetPrevious( self, media ):
@ -1733,13 +1747,13 @@ class MediaSingleton( Media ):
( volume, ) = volumes
title_string_append = 'volume ' + str( volume )
title_string_append = 'volume ' + HydrusData.ToUnicode( volume )
else:
volumes_sorted = HydrusTags.SortNumericTags( volumes )
title_string_append = 'volumes ' + str( volumes_sorted[0] ) + '-' + str( volumes_sorted[-1] )
title_string_append = 'volumes ' + HydrusData.ToUnicode( volumes_sorted[0] ) + '-' + HydrusData.ToUnicode( volumes_sorted[-1] )
if len( title_string ) > 0: title_string += ' - ' + title_string_append
@ -1752,13 +1766,13 @@ class MediaSingleton( Media ):
( chapter, ) = chapters
title_string_append = 'chapter ' + str( chapter )
title_string_append = 'chapter ' + HydrusData.ToUnicode( chapter )
else:
chapters_sorted = HydrusTags.SortNumericTags( chapters )
title_string_append = 'chapters ' + str( chapters_sorted[0] ) + '-' + str( chapters_sorted[-1] )
title_string_append = 'chapters ' + HydrusData.ToUnicode( chapters_sorted[0] ) + '-' + HydrusData.ToUnicode( chapters_sorted[-1] )
if len( title_string ) > 0: title_string += ' - ' + title_string_append
@ -1771,13 +1785,13 @@ class MediaSingleton( Media ):
( page, ) = pages
title_string_append = 'page ' + str( page )
title_string_append = 'page ' + HydrusData.ToUnicode( page )
else:
pages_sorted = HydrusTags.SortNumericTags( pages )
title_string_append = 'pages ' + str( pages_sorted[0] ) + '-' + str( pages_sorted[-1] )
title_string_append = 'pages ' + HydrusData.ToUnicode( pages_sorted[0] ) + '-' + HydrusData.ToUnicode( pages_sorted[-1] )
if len( title_string ) > 0: title_string += ' - ' + title_string_append
@ -1940,6 +1954,7 @@ class MediaResult( object ):
class MediaSort( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_MEDIA_SORT
SERIALISABLE_NAME = 'Media Sort'
SERIALISABLE_VERSION = 1
def __init__( self, sort_type = None, sort_asc = None ):

View File

@ -108,6 +108,7 @@ def ConvertStatusCodeAndDataIntoExceptionInfo( status_code, data, is_hydrus_serv
class NetworkBandwidthManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER
SERIALISABLE_NAME = 'Bandwidth Manager'
SERIALISABLE_VERSION = 1
def __init__( self ):
@ -511,6 +512,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class NetworkContext( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_CONTEXT
SERIALISABLE_NAME = 'Network Context'
SERIALISABLE_VERSION = 2
def __init__( self, context_type = None, context_data = None ):
@ -1788,6 +1790,8 @@ class NetworkJobSubscription( NetworkJobDownloader ):
class NetworkJobSubscriptionTemporary( NetworkJob ):
# temporary because we will move to the downloader_key stuff when that is available
def __init__( self, subscription_key, method, url, body = None, referral_url = None, temp_path = None ):
self._subscription_key = subscription_key
@ -1926,6 +1930,7 @@ class NetworkJobThreadWatcher( NetworkJob ):
class NetworkSessionManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_SESSION_MANAGER
SERIALISABLE_NAME = 'Session Manager'
SERIALISABLE_VERSION = 1
SESSION_TIMEOUT = 60 * 60

View File

@ -91,6 +91,7 @@ valid_str_lookup[ VALID_UNKNOWN ] = 'unknown'
class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER
SERIALISABLE_NAME = 'Domain Manager'
SERIALISABLE_VERSION = 1
def __init__( self ):
@ -266,6 +267,14 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
def GetURLMatches( self ):
with self._lock:
return list( self._url_matches )
def IsDirty( self ):
with self._lock:
@ -355,6 +364,18 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
def SetURLMatches( self, url_matches ):
with self._lock:
self._url_matches = HydrusSerialisable.SerialisableList()
self._url_matches.extend( url_matches )
self._SetDirty()
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER ] = NetworkDomainManager
class DomainValidationPopupProcess( object ):
@ -430,9 +451,15 @@ class DomainValidationPopupProcess( object ):
class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_URL_MATCH
SERIALISABLE_NAME = 'URL Match'
SERIALISABLE_VERSION = 1
def __init__( self, name, preferred_scheme = 'https', netloc = 'hostname.com', subdomain_is_important = False, path_components = None, parameters = None, example_url = 'https://hostname.com/post/page.php?id=123456&s=view' ):
def __init__( self, name, url_type = None, preferred_scheme = 'https', netloc = 'hostname.com', allow_subdomains = False, keep_subdomains = False, path_components = None, parameters = None, example_url = 'https://hostname.com/post/page.php?id=123456&s=view' ):
if url_type is None:
url_type = HC.URL_TYPE_POST
if path_components is None:
@ -450,14 +477,18 @@ class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
parameters[ 'id' ] = ClientParsing.StringMatch( match_type = ClientParsing.STRING_MATCH_FLEXIBLE, match_value = ClientParsing.NUMERIC, example_string = '123456' )
# an edit dialog panel for this that has example url and testing of current values
# a parent panel or something that lists all current urls in the db that match and how they will be clipped, is this ok? kind of thing.
# if the args are not serialisable stuff, lets overwrite here
path_components = HydrusSerialisable.SerialisableList( path_components )
parameters = HydrusSerialisable.SerialisableDictionary( parameters )
HydrusSerialisable.SerialisableBaseNamed.__init__( self, name )
self._url_type = url_type
self._preferred_scheme = preferred_scheme
self._netloc = netloc
self._subdomain_is_important = subdomain_is_important
self._allow_subdomains = allow_subdomains
self._keep_subdomains = keep_subdomains
self._path_components = path_components
self._parameters = parameters
@ -466,7 +497,7 @@ class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
def _ClipNetLoc( self, netloc ):
if self._subdomain_is_important:
if self._keep_subdomains:
# for domains like artistname.website.com, where removing the subdomain may break the url, we leave it alone
@ -475,7 +506,6 @@ class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
else:
# for domains like mediaserver4.website.com, where multiple subdomains serve the same content as the larger site
# if the main site doesn't deliver the same content as the subdomain, then subdomain_is_important
netloc = self._netloc
@ -488,12 +518,12 @@ class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
serialisable_path_components = self._path_components.GetSerialisableTuple()
serialisable_parameters = self._parameters.GetSerialisableTuple()
return ( self._preferred_scheme, self._netloc, self._subdomain_is_important, serialisable_path_components, serialisable_parameters, self._example_url )
return ( self._url_type, self._preferred_scheme, self._netloc, self._allow_subdomains, self._keep_subdomains, serialisable_path_components, serialisable_parameters, self._example_url )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( self._preferred_scheme, self._netloc, self._subdomain_is_important, serialisable_path_components, serialisable_parameters, self._example_url ) = serialisable_info
( self._url_type, self._preferred_scheme, self._netloc, self._allow_subdomains, self._keep_subdomains, serialisable_path_components, serialisable_parameters, self._example_url ) = serialisable_info
self._path_components = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_path_components )
self._parameters = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_parameters )
@ -550,6 +580,16 @@ class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
return ConvertURLIntoDomain( self._example_url )
def GetExampleURL( self ):
return self._example_url
def GetURLType( self ):
return self._url_type
def Normalise( self, url ):
p = urlparse.urlparse( url )
@ -568,10 +608,22 @@ class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
def Test( self, url ):
# split the url into parts according to urlparse
p = urlparse.urlparse( url )
# test p.netloc with netloc, taking subdomain_is_important into account
if self._allow_subdomains:
if p.netloc != self._netloc and not p.netloc.endswith( '.' + self._netloc ):
raise HydrusExceptions.URLMatchException( p.netloc + ' (potentially excluding subdomains) did not match ' + self._netloc )
else:
if p.netloc != self._netloc:
raise HydrusExceptions.URLMatchException( p.netloc + ' did not match ' + self._netloc )
url_path = p.path
@ -580,11 +632,11 @@ class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
url_path = url_path[ 1 : ]
url_path_components = p.path.split( '/' )
url_path_components = url_path.split( '/' )
if len( url_path_components ) < len( self._path_components ):
raise HydrusExceptions.URLMatchException( p.path + ' did not have ' + str( len( self._path_components ) ) + ' components' )
raise HydrusExceptions.URLMatchException( url_path + ' did not have ' + str( len( self._path_components ) ) + ' components' )
for ( url_path_component, expected_path_component ) in zip( url_path_components, self._path_components ):
@ -601,23 +653,25 @@ class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
url_parameters_list = urlparse.parse_qsl( p.query )
if len( url_parameters_list ) < len( self._parameters ):
url_parameters = dict( url_parameters_list )
if len( url_parameters ) < len( self._parameters ):
raise HydrusExceptions.URLMatchException( p.query + ' did not have ' + str( len( self._parameters ) ) + ' value pairs' )
for ( key, url_value ) in url_parameters_list:
for ( key, string_match ) in self._parameters.items():
if key not in self._parameters:
if key not in url_parameters:
raise HydrusExceptions.URLMatchException( key + ' not found in ' + p.query )
expected_value = self._parameters[ key ]
value = url_parameters[ key ]
try:
expected_value.Test( url_value )
string_match.Test( value )
except HydrusExceptions.StringMatchException as e:
@ -626,5 +680,10 @@ class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_URLS_IMPORT ] = URLMatch
def ToTuple( self ):
return ( self._url_type, self._preferred_scheme, self._netloc, self._allow_subdomains, self._keep_subdomains, self._path_components, self._parameters, self._example_url )
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_URL_MATCH ] = URLMatch

View File

@ -38,6 +38,7 @@ class LoginCredentials( object ):
class NetworkLoginManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_LOGIN_MANAGER
SERIALISABLE_NAME = 'Login Manager'
SERIALISABLE_VERSION = 1
SESSION_TIMEOUT = 60 * 45

View File

@ -157,6 +157,7 @@ def RenderTagRule( ( name, attrs, index ) ):
class ParseFormulaHTML( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_PARSE_FORMULA_HTML
SERIALISABLE_NAME = 'HTML Parsing Formula'
SERIALISABLE_VERSION = 4
def __init__( self, tag_rules = None, content_rule = None, string_match = None, string_converter = None ):
@ -432,6 +433,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class ParseNodeContent( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_PARSE_NODE_CONTENT
SERIALISABLE_NAME = 'Content Parsing Node'
SERIALISABLE_VERSION = 1
def __init__( self, name = None, content_type = None, formula = None, additional_info = None ):
@ -538,6 +540,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class ParseNodeContentLink( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_PARSE_NODE_CONTENT_LINK
SERIALISABLE_NAME = 'Content Parsing Link'
SERIALISABLE_VERSION = 1
def __init__( self, name = None, formula = None, children = None ):
@ -706,6 +709,7 @@ file_identifier_string_lookup[ FILE_IDENTIFIER_TYPE_USER_INPUT ] = 'custom user
class ParseRootFileLookup( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_PARSE_ROOT_FILE_LOOKUP
SERIALISABLE_NAME = 'File Lookup Script'
SERIALISABLE_VERSION = 2
def __init__( self, name, url = None, query_type = None, file_identifier_type = None, file_identifier_string_converter = None, file_identifier_arg_name = None, static_args = None, children = None ):
@ -1012,6 +1016,7 @@ transformation_type_str_lookup[ STRING_TRANSFORMATION_REVERSE ] = 'reverse text'
class StringConverter( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_STRING_CONVERTER
SERIALISABLE_NAME = 'String Converter'
SERIALISABLE_VERSION = 1
def __init__( self, transformations = None, example_string = None ):
@ -1180,6 +1185,7 @@ NUMERIC = 2
class StringMatch( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_STRING_MATCH
SERIALISABLE_NAME = 'String Match'
SERIALISABLE_VERSION = 1
def __init__( self, match_type = STRING_MATCH_ANY, match_value = '', min_chars = None, max_chars = None, example_string = 'example string' ):

View File

@ -634,8 +634,14 @@ class HydrusBitmap( object ):
( width, height ) = self._size
if self._format == wx.BitmapBufferFormat_RGB: return wx.BitmapFromBuffer( width, height, self._GetData() )
else: return wx.BitmapFromBufferRGBA( width, height, self._GetData() )
if self._format == wx.BitmapBufferFormat_RGB:
return wx.BitmapFromBuffer( width, height, self._GetData() )
else:
return wx.BitmapFromBufferRGBA( width, height, self._GetData() )
def GetWxImage( self ):
@ -663,5 +669,8 @@ class HydrusBitmap( object ):
return len( self._data )
def GetSize( self ): return self._size
def GetSize( self ):
return self._size

View File

@ -263,6 +263,7 @@ class FileQueryResult( object ):
class FileSearchContext( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_FILE_SEARCH_CONTEXT
SERIALISABLE_NAME = 'File Search Context'
SERIALISABLE_VERSION = 1
def __init__( self, file_service_key = CC.COMBINED_FILE_SERVICE_KEY, tag_service_key = CC.COMBINED_TAG_SERVICE_KEY, include_current_tags = True, include_pending_tags = True, predicates = None ):
@ -707,6 +708,7 @@ class FileSystemPredicates( object ):
class Predicate( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_PREDICATE
SERIALISABLE_NAME = 'File Search Predicate'
SERIALISABLE_VERSION = 1
def __init__( self, predicate_type = None, value = None, inclusive = True, min_current_count = 0, min_pending_count = 0, max_current_count = None, max_pending_count = None ):

View File

@ -182,22 +182,22 @@ def DumpToPng( width, payload, title, payload_description, text, path ):
HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
def GetPayloadTypeString( payload_obj ):
if isinstance( payload_obj, HydrusSerialisable.SerialisableList ):
return 'A list of ' + HydrusData.ConvertIntToPrettyString( len( payload_obj ) ) + ' ' + GetPayloadTypeString( payload_obj[0] ) + 's'
return 'A list of ' + HydrusData.ConvertIntToPrettyString( len( payload_obj ) ) + ' ' + GetPayloadTypeString( payload_obj[0] )
else:
if isinstance( payload_obj, ClientParsing.ParseRootFileLookup ):
if isinstance( payload_obj, HydrusSerialisable.SerialisableBase ):
return 'File Lookup Script'
return payload_obj.SERIALISABLE_NAME
elif isinstance( payload_obj, ClientImporting.Subscription ):
else:
return 'Subscription'
return repr( type( payload_obj ) )

View File

@ -494,7 +494,7 @@ class ServiceRemote( Service ):
if not HydrusData.TimeHasPassed( self._no_requests_until ):
return ( False, self._no_requests_reason + ' - next request in ' + HydrusData.ConvertTimestampToPrettyPending( self._no_requests_until ) )
return ( False, self._no_requests_reason + ' - next request ' + HydrusData.ConvertTimestampToPrettyPending( self._no_requests_until ) )
example_nj = ClientNetworking.NetworkJobHydrus( self._service_key, 'GET', self._GetBaseURL() )
@ -662,7 +662,7 @@ class ServiceRestricted( ServiceRemote ):
def GetNextAccountSyncStatus( self ):
return 'next account sync in ' + HydrusData.ConvertTimestampToPrettyPending( self._next_account_sync )
return 'next account sync ' + HydrusData.ConvertTimestampToPrettyPending( self._next_account_sync )
def HasPermission( self, content_type, action ):

View File

@ -49,7 +49,7 @@ options = {}
# Misc
NETWORK_VERSION = 18
SOFTWARE_VERSION = 283
SOFTWARE_VERSION = 284
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
@ -651,6 +651,18 @@ site_type_string_lookup[ SITE_TYPE_PIXIV_TAG ] = 'pixiv tag'
site_type_string_lookup[ SITE_TYPE_TUMBLR ] = 'tumblr'
site_type_string_lookup[ SITE_TYPE_THREAD_WATCHER ] = 'thread watcher'
URL_TYPE_POST = 0
URL_TYPE_API = 1
URL_TYPE_FILE = 2
URL_TYPE_GALLERY = 3
url_type_string_lookup = {}
url_type_string_lookup[ URL_TYPE_POST ] = 'post url'
url_type_string_lookup[ URL_TYPE_API ] = 'api url'
url_type_string_lookup[ URL_TYPE_FILE ] = 'file url'
url_type_string_lookup[ URL_TYPE_GALLERY ] = 'gallery url'
# default options
DEFAULT_LOCAL_FILE_PORT = 45865

View File

@ -469,7 +469,7 @@ def ConvertTimestampToPrettyExpires( timestamp ):
else: return 'expires in ' + ' '.join( ( m, s ) )
def ConvertTimestampToPrettyPending( timestamp ):
def ConvertTimestampToPrettyPending( timestamp, prefix = 'in' ):
if timestamp is None: return ''
if timestamp == 0: return 'imminent'
@ -507,12 +507,17 @@ def ConvertTimestampToPrettyPending( timestamp ):
if years == 1: y = '1 year'
else: y = str( years ) + ' years'
if years > 0: return ' '.join( ( y, mo ) )
elif months > 0: return ' '.join( ( mo, d ) )
elif days > 0: return ' '.join( ( d, h ) )
elif hours > 0: return ' '.join( ( h, m ) )
elif minutes > 0: return ' '.join( ( m, s ) )
else: return s
if prefix != '':
prefix += ' '
if years > 0: return prefix + ' '.join( ( y, mo ) )
elif months > 0: return prefix + ' '.join( ( mo, d ) )
elif days > 0: return prefix + ' '.join( ( d, h ) )
elif hours > 0: return prefix + ' '.join( ( h, m ) )
elif minutes > 0: return prefix + ' '.join( ( m, s ) )
else: return prefix + s
def ConvertTimestampToPrettySync( timestamp ):
@ -1390,6 +1395,7 @@ sqlite3.register_adapter( Account, yaml.safe_dump )
class AccountIdentifier( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_ACCOUNT_IDENTIFIER
SERIALISABLE_NAME = 'Account Identifier'
SERIALISABLE_VERSION = 1
TYPE_ACCOUNT_KEY = 1

View File

@ -911,6 +911,7 @@ class AccountType( object ):
class ClientToServerUpdate( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_TO_SERVER_UPDATE
SERIALISABLE_NAME = 'Client To Server Update'
SERIALISABLE_VERSION = 1
def __init__( self ):
@ -1012,6 +1013,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class Content( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CONTENT
SERIALISABLE_NAME = 'Content'
SERIALISABLE_VERSION = 1
def __init__( self, content_type = None, content_data = None ):
@ -1200,6 +1202,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class ContentUpdate( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CONTENT_UPDATE
SERIALISABLE_NAME = 'Content Update'
SERIALISABLE_VERSION = 1
def __init__( self ):
@ -1334,6 +1337,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class Credentials( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CREDENTIALS
SERIALISABLE_NAME = 'Credentials'
SERIALISABLE_VERSION = 1
def __init__( self, host = None, port = None, access_key = None ):
@ -1509,6 +1513,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class DefinitionsUpdate( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_DEFINITIONS_UPDATE
SERIALISABLE_NAME = 'Definitions Update'
SERIALISABLE_VERSION = 1
def __init__( self ):
@ -1585,6 +1590,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class Metadata( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_METADATA
SERIALISABLE_NAME = 'Metadata'
SERIALISABLE_VERSION = 1
CLIENT_DELAY = 20 * 60
@ -1711,7 +1717,7 @@ class Metadata( HydrusSerialisable.SerialisableBase ):
update_due = self._GetNextUpdateDueTime( from_client )
return 'next update due in ' + HydrusData.ConvertTimestampToPrettyPending( update_due )
return 'next update due ' + HydrusData.ConvertTimestampToPrettyPending( update_due )
@ -1798,7 +1804,7 @@ class Metadata( HydrusSerialisable.SerialisableBase ):
next_update_time = self._next_update_due + delay
status = 'metadata synchronised up to ' + HydrusData.ConvertTimestampToPrettyAgo( biggest_end ) + ' ago, next update due in ' + HydrusData.ConvertTimestampToPrettyPending( next_update_time )
status = 'metadata synchronised up to ' + HydrusData.ConvertTimestampToPrettyAgo( biggest_end ) + ' ago, next update due ' + HydrusData.ConvertTimestampToPrettyPending( next_update_time )
return ( num_update_hashes, status )
@ -1848,6 +1854,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class Petition( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_PETITION
SERIALISABLE_NAME = 'Petition'
SERIALISABLE_VERSION = 1
def __init__( self, action = None, petitioner_account = None, reason = None, contents = None ):

View File

@ -72,6 +72,7 @@ def GetLocalConnection( port, https = False ):
class BandwidthRules( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_BANDWIDTH_RULES
SERIALISABLE_NAME = 'Bandwidth Rules'
SERIALISABLE_VERSION = 1
def __init__( self ):
@ -280,6 +281,7 @@ HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIAL
class BandwidthTracker( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_BANDWIDTH_TRACKER
SERIALISABLE_NAME = 'Bandwidth Tracker'
SERIALISABLE_VERSION = 1
# I want to track and query using smaller periods even when the total time delta is larger than the next step up to increase granularity

View File

@ -67,10 +67,12 @@ def CleanUpTempPath( os_file_handle, temp_path ):
os.remove( temp_path )
except OSError:
except OSError as e:
HydrusData.Print( 'Could not delete the temporary file ' + temp_path )
HydrusData.PrintException( e )
def ConvertAbsPathToPortablePath( abs_path, base_dir_override = None ):

View File

@ -59,6 +59,7 @@ SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER = 53
SERIALISABLE_TYPE_SUBSCRIPTION_QUERY = 54
SERIALISABLE_TYPE_STRING_CONVERTER = 55
SERIALISABLE_TYPE_FILENAME_TAGGING_OPTIONS = 56
SERIALISABLE_TYPE_SEED = 57
SERIALISABLE_TYPES_TO_OBJECT_TYPES = {}
@ -103,6 +104,7 @@ def CreateFromSerialisableTuple( obj_tuple ):
class SerialisableBase( object ):
SERIALISABLE_TYPE = SERIALISABLE_TYPE_BASE
SERIALISABLE_NAME = 'Base Serialisable Object'
SERIALISABLE_VERSION = 1
def _GetSerialisableInfo( self ):
@ -157,6 +159,7 @@ class SerialisableBase( object ):
class SerialisableBaseNamed( SerialisableBase ):
SERIALISABLE_TYPE = SERIALISABLE_TYPE_BASE_NAMED
SERIALISABLE_NAME = 'Named Base Serialisable Object'
def __init__( self, name ):
@ -194,6 +197,7 @@ class SerialisableBaseNamed( SerialisableBase ):
class SerialisableDictionary( SerialisableBase, dict ):
SERIALISABLE_TYPE = SERIALISABLE_TYPE_DICTIONARY
SERIALISABLE_NAME = 'Serialisable Dictionary'
SERIALISABLE_VERSION = 1
def __init__( self, *args, **kwargs ):
@ -288,6 +292,7 @@ SERIALISABLE_TYPES_TO_OBJECT_TYPES[ SERIALISABLE_TYPE_DICTIONARY ] = Serialisabl
class SerialisableBytesDictionary( SerialisableBase, dict ):
SERIALISABLE_TYPE = SERIALISABLE_TYPE_BYTES_DICT
SERIALISABLE_NAME = 'Serialisable Dictionary With Bytestring Key/Value Support'
SERIALISABLE_VERSION = 1
def __init__( self, *args, **kwargs ):
@ -357,6 +362,7 @@ SERIALISABLE_TYPES_TO_OBJECT_TYPES[ SERIALISABLE_TYPE_BYTES_DICT ] = Serialisabl
class SerialisableList( SerialisableBase, list ):
SERIALISABLE_TYPE = SERIALISABLE_TYPE_LIST
SERIALISABLE_NAME = 'Serialisable List'
SERIALISABLE_VERSION = 1
def __init__( self, *args, **kwargs ):

View File

@ -88,8 +88,14 @@ def ConvertTagToSortable( t ):
for character in t:
if character.isdecimal(): int_component += character
else: break
if character.isdecimal():
int_component += character
else:
break
i += 1
@ -98,7 +104,6 @@ def ConvertTagToSortable( t ):
number = int( int_component )
return ( number, str_component )
else:

View File

@ -22,55 +22,77 @@ class TestData( unittest.TestCase ):
for i in range( 50 ):
seed = os.urandom( 16 ).encode( 'hex' )
url = 'https://wew.lad/' + os.urandom( 16 ).encode( 'hex' )
seed = ClientImporting.Seed( ClientImporting.SEED_TYPE_URL, url )
seed.source_time = one_day_before - 10
seed_cache.AddSeeds( ( seed, ) )
seed_cache.UpdateSeedSourceTime( seed, one_day_before - 10 )
for i in range( 50 ):
seed = os.urandom( 16 ).encode( 'hex' )
url = 'https://wew.lad/' + os.urandom( 16 ).encode( 'hex' )
seed = ClientImporting.Seed( ClientImporting.SEED_TYPE_URL, url )
seed.source_time = last_check_time - 600
seed_cache.AddSeeds( ( seed, ) )
seed_cache.UpdateSeedSourceTime( seed, last_check_time - 600 )
bare_seed_cache = ClientImporting.SeedCache()
bare_seed_cache.AddSeeds( ( 'early', ) )
bare_seed_cache.AddSeeds( ( 'in_time_delta', ) )
url = 'https://wew.lad/' + 'early'
bare_seed_cache.UpdateSeedSourceTime( 'early', one_day_before - 10 )
bare_seed_cache.UpdateSeedSourceTime( 'in_time_delta', one_day_before + 10 )
seed = ClientImporting.Seed( ClientImporting.SEED_TYPE_URL, url )
seed.source_time = one_day_before - 10
bare_seed_cache.AddSeeds( ( seed, ) )
url = 'https://wew.lad/' + 'in_time_delta'
seed = ClientImporting.Seed( ClientImporting.SEED_TYPE_URL, url )
seed.source_time = one_day_before + 10
bare_seed_cache.AddSeeds( ( seed, ) )
busy_seed_cache = ClientImporting.SeedCache()
busy_seed_cache.AddSeeds( ( 'early', ) )
url = 'https://wew.lad/' + 'early'
busy_seed_cache.UpdateSeedSourceTime( 'early', one_day_before - 10 )
seed = ClientImporting.Seed( ClientImporting.SEED_TYPE_URL, url )
seed.source_time = one_day_before - 10
busy_seed_cache.AddSeeds( ( seed, ) )
for i in range( 8640 ):
seed = os.urandom( 16 ).encode( 'hex' )
url = 'https://wew.lad/' + os.urandom( 16 ).encode( 'hex' )
seed = ClientImporting.Seed( ClientImporting.SEED_TYPE_URL, url )
seed.source_time = one_day_before + ( ( i + 1 ) * 10 ) - 1
busy_seed_cache.AddSeeds( ( seed, ) )
busy_seed_cache.UpdateSeedSourceTime( seed, one_day_before + ( ( i + 1 ) * 10 ) - 1 )
new_thread_seed_cache = ClientImporting.SeedCache()
for i in range( 10 ):
seed = os.urandom( 16 ).encode( 'hex' )
url = 'https://wew.lad/' + os.urandom( 16 ).encode( 'hex' )
seed = ClientImporting.Seed( ClientImporting.SEED_TYPE_URL, url )
seed.source_time = last_check_time - 600
new_thread_seed_cache.AddSeeds( ( seed, ) )
new_thread_seed_cache.UpdateSeedSourceTime( seed, last_check_time - 600 )
# empty
# should say ok if last_check_time is 0, so it can initialise