Version 277

This commit is contained in:
Hydrus Network Developer 2017-10-11 12:38:14 -05:00
parent 0fd301bedd
commit b4a288b994
22 changed files with 780 additions and 68 deletions

View File

@ -8,6 +8,27 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 277</h3></li>
<ul>
<li>expanded the domain manager into a legit serialisable object that holds data and saves itself on changes. to begin with, it supports custom http headers for particular on network contexts</li>
<li>the global User-Agent (which now defaults to 'hydrus client') is set through this new manager, as attached to the 'global' network context</li>
<li>wrote a basic panel to edit custom http headers under services->manage network rules (this panel will be fleshed out with more in future--please ignore if you are not an advanced user)</li>
<li>wrote a panel to edit an individual custom http header</li>
<li>wrote a panel to edit/select network contexts. this will get a bunch more use elsewhere as the overhaul continues</li>
<li>flushed out and tested the new popup yes/no button system</li>
<li>sanakucomplex.com has a User-Agent entry in the new custom header system--on the first sank request, you will be presented with a yes/no popup asking if it is ok to use.</li>
<li>if you ok the user-agent, sankaku now seems to work again!</li>
<li>sankakucomplex.com network context will get some specific conservative bandwidth rules (80 rqs per 7m, 1 rq per 4s) on update</li>
<li>added an option to options->gui to reverse the page tab shift behaviour</li>
<li>'don't scroll down on key navigation event if thumb is at least this % visible' value is now 75%. it is also editable under options->gui</li>
<li>the hydrus icon used on frames is now the non-transparent version that shows up better on dark backgrounds</li>
<li>the standard hydrus.ico used in web favicons and the executable builds is now also non-transparent (this should also propagate up to OS shortcuts and taskbar icons wherever the frame icon is not inherited)</li>
<li>file export drag and drop events will now defocus the currently focused media on successful drop (e.g. if you drag a video to an external media player, it will stop rendering clientside), just like for open externally</li>
<li>database analyze maintenance should be more reliable with respect to fresh repository syncs</li>
<li>reduced default impermanent session timeout in new networking engine to 60m (should fix/reduce some hentai foundry 503s)</li>
<li>misc ui improvements and speedup</li>
<li>misc fixes</li>
</ul>
<li><h3>version 276</h3></li>
<ul>
<li>the new thread watcher object will no longer produce check periods shorter than the time since the latest file. this effectively throttles checking on threads that were posting very fast but have since suddenly stopped completely</li>

View File

@ -3176,7 +3176,7 @@ class UndoManager( object ):
class WebSessionManagerClient( object ):
SESSION_TIMEOUT = 90 * 60
SESSION_TIMEOUT = 60 * 60
def __init__( self, controller ):

View File

@ -580,6 +580,8 @@ class Controller( HydrusController.HydrusController ):
ClientDefaults.SetDefaultBandwidthManagerRules( bandwidth_manager )
bandwidth_manager._dirty = True
wx.MessageBox( 'Your bandwidth manager was missing on boot! I have recreated a new empty one with default rules. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
@ -589,10 +591,21 @@ class Controller( HydrusController.HydrusController ):
session_manager = ClientNetworking.NetworkSessionManager()
session_manager._dirty = True
wx.MessageBox( 'Your session manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
domain_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER )
if domain_manager is None:
domain_manager = ClientNetworking.NetworkSessionManager()
domain_manager._dirty = True
wx.MessageBox( 'Your domain manager was missing on boot! I have recreated a new empty one. Please check that your hard drive and client are ok and let the hydrus dev know the details if there is a mystery.' )
login_manager = ClientNetworking.NetworkLoginManager()
@ -1056,6 +1069,13 @@ class Controller( HydrusController.HydrusController ):
self.network_engine.bandwidth_manager.SetClean()
if self.network_engine.domain_manager.IsDirty():
self.WriteSynchronous( 'serialisable', self.network_engine.domain_manager )
self.network_engine.domain_manager.SetClean()
if self.network_engine.session_manager.IsDirty():
self.WriteSynchronous( 'serialisable', self.network_engine.session_manager )

View File

@ -3,6 +3,7 @@ import ClientDefaults
import ClientImageHandling
import ClientMedia
import ClientNetworking
import ClientNetworkingDomain
import ClientRatings
import ClientSearch
import ClientServices
@ -2852,6 +2853,12 @@ class DB( HydrusDB.HydrusDB ):
self._SetJSONDump( bandwidth_manager )
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
ClientDefaults.SetDefaultDomainManagerData( domain_manager )
self._SetJSONDump( domain_manager )
session_manager = ClientNetworking.NetworkSessionManager()
self._SetJSONDump( session_manager )
@ -7830,14 +7837,9 @@ class DB( HydrusDB.HydrusDB ):
self._AnalyzeStaleBigTables()
finally:
if this_is_first_sync:
self._AnalyzeStaleBigTables()
self._AnalyzeStaleBigTables()
job_key.SetVariable( 'popup_text_1', 'finished' )
job_key.DeleteVariable( 'popup_gauge_1' )
@ -9799,6 +9801,35 @@ class DB( HydrusDB.HydrusDB ):
if version == 276:
domain_manager = ClientNetworkingDomain.NetworkDomainManager()
ClientDefaults.SetDefaultDomainManagerData( domain_manager )
self._SetJSONDump( domain_manager )
#
bandwidth_manager = self._GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER )
rules = HydrusNetworking.BandwidthRules()
rules.AddRule( HC.BANDWIDTH_TYPE_REQUESTS, 60 * 7, 80 )
rules.AddRule( HC.BANDWIDTH_TYPE_REQUESTS, 4, 1 )
bandwidth_manager.SetRules( ClientNetworking.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, 'sankakucomplex.com' ), rules )
self._SetJSONDump( bandwidth_manager )
message = 'The Sankaku downloader appears to be working again with a User-Agent substitution. There are also some new, conservative Sankaku bandwidth rules.'
message += os.linesep * 2
message += 'If you do not currently have the Sankaku downloader(s) in your booru list but would like to try, please check the user-run wiki (linked off the \'contact\' help page) for the .yaml \'preset\' import files.'
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

@ -851,6 +851,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'booleans' ][ 'verify_regular_https' ] = True
self._dictionary[ 'booleans' ][ 'reverse_page_shift_drag_behaviour' ] = False
#
self._dictionary[ 'colours' ] = HydrusSerialisable.SerialisableDictionary()
@ -915,6 +917,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'integers' ][ 'network_timeout' ] = 10
self._dictionary[ 'integers' ][ 'thumbnail_visibility_scroll_percent' ] = 75
#
self._dictionary[ 'keys' ] = {}
@ -1047,7 +1051,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'misc' ] = HydrusSerialisable.SerialisableDictionary()
self._dictionary[ 'misc' ][ 'default_thread_watcher_options' ] = WatcherOptions( intended_files_per_check = 8, never_faster_than = 300, never_slower_than = 86400, death_file_velocity = ( 1, 86400 ) )
self._dictionary[ 'misc' ][ 'default_thread_watcher_options' ] = WatcherOptions( intended_files_per_check = 4, never_faster_than = 300, never_slower_than = 86400, death_file_velocity = ( 1, 86400 ) )
#

View File

@ -2,6 +2,7 @@ import ClientConstants as CC
import ClientData
import ClientImporting
import ClientNetworking
import ClientNetworkingDomain
import HydrusConstants as HC
import HydrusGlobals as HG
import HydrusNetworking
@ -81,6 +82,42 @@ def SetDefaultBandwidthManagerRules( bandwidth_manager ):
bandwidth_manager.SetRules( ClientNetworking.NetworkContext( CC.NETWORK_CONTEXT_THREAD_WATCHER_THREAD ), rules )
#
rules = HydrusNetworking.BandwidthRules()
rules.AddRule( HC.BANDWIDTH_TYPE_REQUESTS, 60 * 7, 80 )
rules.AddRule( HC.BANDWIDTH_TYPE_REQUESTS, 4, 1 )
bandwidth_manager.SetRules( ClientNetworking.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, 'sankakucomplex.com' ), rules )
def SetDefaultDomainManagerData( domain_manager ):
network_contexts_to_custom_header_dicts = {}
#
custom_header_dict = {}
custom_header_dict[ 'User-Agent' ] = ( 'hydrus client', ClientNetworkingDomain.VALID_APPROVED, 'This is the default User-Agent identifier for the client for all network connections.' )
network_contexts_to_custom_header_dicts[ ClientNetworking.GLOBAL_NETWORK_CONTEXT ] = custom_header_dict
#
custom_header_dict = {}
custom_header_dict[ 'User-Agent' ] = ( 'SCChannelApp/2.0.1 (Android; black)', ClientNetworkingDomain.VALID_UNKNOWN, 'Sankaku seem to currently have a User-Agent whitelist on file requests. Setting this User-Agent allows the sankaku downloader to work.' )
network_context = ClientNetworking.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, 'sankakucomplex.com' )
network_contexts_to_custom_header_dicts[ network_context ] = custom_header_dict
#
domain_manager.SetNetworkContextsToCustomHeaderDicts( network_contexts_to_custom_header_dicts )
def GetClientDefaultOptions():
options = {}
@ -462,9 +499,20 @@ def GetDefaultBoorus():
thumb_classname = 'thumb'
image_id = 'highres'
image_data = None
tag_classnames_to_namespaces = { 'tag-type-general' : '', 'tag-type-character' : 'character', 'tag-type-copyright' : 'series', 'tag-type-artist' : 'creator', 'tag-type-medium' : 'medium' }
tag_classnames_to_namespaces = { 'tag-type-general' : '', 'tag-type-character' : 'character', 'tag-type-copyright' : 'series', 'tag-type-artist' : 'creator', 'tag-type-medium' : 'medium', 'tag-type-meta' : 'meta', 'tag-type-studio' : 'studio' }
#boorus[ 'sankaku chan' ] = ClientData.Booru( name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces )
boorus[ 'sankaku chan' ] = ClientData.Booru( name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces )
name = 'sankaku idol'
search_url = 'https://idol.sankakucomplex.com/?tags=%tags%&page=%index%'
search_separator = '+'
advance_by_page_num = True
thumb_classname = 'thumb'
image_id = 'highres'
image_data = None
tag_classnames_to_namespaces = { 'tag-type-general' : '', 'tag-type-character' : 'character', 'tag-type-copyright' : 'series', 'tag-type-artist' : 'creator', 'tag-type-medium' : 'medium', 'tag-type-meta' : 'meta', 'tag-type-photo_set' : 'photo set', 'tag-type-idol' : 'person' }
boorus[ 'sankaku idol' ] = ClientData.Booru( name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces )
name = 'rule34hentai'
search_url = 'http://rule34hentai.net/post/list/%tags%/%index%'

View File

@ -13,6 +13,7 @@ import ClientGUIMenus
import ClientGUIPages
import ClientGUIParsing
import ClientGUIPopupMessages
import ClientGUIScrolledPanelsEdit
import ClientGUIScrolledPanelsManagement
import ClientGUIScrolledPanelsReview
import ClientGUIShortcuts
@ -149,7 +150,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
aboutinfo = wx.AboutDialogInfo()
aboutinfo.SetIcon( wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus.ico' ), wx.BITMAP_TYPE_ICO ) )
aboutinfo.SetIcon( wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus_32_non-transparent.png' ), wx.BITMAP_TYPE_PNG ) )
aboutinfo.SetName( 'hydrus client' )
aboutinfo.SetVersion( str( HC.SOFTWARE_VERSION ) + ', using network version ' + str( HC.NETWORK_VERSION ) )
@ -1347,6 +1348,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuItem( self, menu, 'review bandwidth usage', 'See where you are consuming data.', self._ReviewBandwidth )
ClientGUIMenus.AppendMenuItem( self, menu, 'manage network rules (under construction)', 'Configure how the client talks to the network.', self._ManageNetworkRules )
ClientGUIMenus.AppendSeparator( menu )
@ -1686,6 +1688,31 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
with ClientGUIDialogsManage.DialogManageImportFolders( self ) as dlg: dlg.ShowModal()
def _ManageNetworkRules( self ):
title = 'manage network rules'
with ClientGUITopLevelWindows.DialogEdit( self, title ) as dlg:
# eventually make this a proper management panel with several notebook pages or something
domain_manager = self._controller.network_engine.domain_manager
network_contexts_to_custom_header_dicts = domain_manager.GetNetworkContextsToCustomHeaderDicts()
panel = ClientGUIScrolledPanelsEdit.EditNetworkContextCustomHeadersPanel( dlg, network_contexts_to_custom_header_dicts )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
network_contexts_to_custom_header_dicts = panel.GetValue()
domain_manager.SetNetworkContextsToCustomHeaderDicts( network_contexts_to_custom_header_dicts )
def _ManageOptions( self ):
title = 'manage options'

View File

@ -291,6 +291,16 @@ class BetterCheckListBox( wx.CheckListBox ):
class BetterChoice( wx.Choice ):
def Append( self, display_string, client_data ):
wx.Choice.Append( self, display_string, client_data )
if self.GetCount() == 1:
self.Select( 0 )
def GetChoice( self ):
selection = self.GetSelection()
@ -1505,6 +1515,52 @@ class MenuButton( BetterButton ):
self._menu_items = menu_items
class NetworkContextButton( BetterButton ):
def __init__( self, parent, network_context ):
BetterButton.__init__( self, parent, network_context.ToUnicode(), self._Edit )
self._network_context = network_context
def _Edit( self ):
import ClientGUITopLevelWindows
import ClientGUIScrolledPanelsEdit
with ClientGUITopLevelWindows.DialogEdit( self, 'edit network context' ) as dlg:
panel = ClientGUIScrolledPanelsEdit.EditNetworkContextPanel( dlg, self._network_context )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
self._network_context = panel.GetValue()
self._Update()
def _Update( self ):
self.SetLabelText( self._network_context.ToUnicode() )
def GetValue( self ):
return self._network_context
def SetValue( self, network_context ):
self._network_context = network_context
self._Update()
class NoneableSpinCtrl( wx.Panel ):
def __init__( self, parent, message = '', none_phrase = 'no limit', min = 0, max = 1000000, unit = None, multiplier = 1, num_dimensions = 1 ):

View File

@ -134,7 +134,7 @@ class Dialog( wx.Dialog ):
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_FRAMEBK ) )
self.SetIcon( wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus.ico' ), wx.BITMAP_TYPE_ICO ) )
self.SetIcon( wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus_32_non-transparent.png' ), wx.BITMAP_TYPE_PNG ) )
self.Bind( wx.EVT_BUTTON, self.EventDialogButton )

View File

@ -2292,6 +2292,10 @@ class MediaPanelThumbnails( MediaPanel ):
( thumbnail_span_width, thumbnail_span_height ) = self._GetThumbnailSpanDimensions()
new_options = HG.client_controller.GetNewOptions()
percent_visible = new_options.GetInteger( 'thumbnail_visibility_scroll_percent' ) / float( 100 )
if y < start_y * y_unit:
y_to_scroll_to = y / y_unit
@ -2300,7 +2304,7 @@ class MediaPanelThumbnails( MediaPanel ):
wx.PostEvent( self, wx.ScrollWinEvent( wx.wxEVT_SCROLLWIN_THUMBRELEASE, pos = y_to_scroll_to ) )
elif y > ( start_y * y_unit ) + height - ( thumbnail_span_height * 0.90 ):
elif y > ( start_y * y_unit ) + height - ( thumbnail_span_height * percent_visible ):
y_to_scroll_to = ( y - height ) / y_unit
@ -2480,7 +2484,12 @@ class MediaPanelThumbnails( MediaPanel ):
drop_source.SetData( data_object )
drop_source.DoDragDrop( flags )
result = drop_source.DoDragDrop( flags )
if result not in ( wx.DragError, wx.DragNone ):
self._SetFocussedMedia( None )

View File

@ -2065,6 +2065,13 @@ class PagesNotebook( wx.Notebook ):
follow_dropped_page = not shift_down
new_options = HG.client_controller.GetNewOptions()
if new_options.GetBoolean( 'reverse_page_shift_drag_behaviour' ):
follow_dropped_page = not follow_dropped_page
self._MovePage( page, dest_notebook, insertion_tab_index, follow_dropped_page )
@ -2131,7 +2138,7 @@ class PagesNotebook( wx.Notebook ):
if page.GetPageKey() == page_key:
if page.GetName() != page_key:
if page.GetName() != name:
page.SetName( name )

View File

@ -181,6 +181,11 @@ class PopupMessage( PopupWindow ):
self._job_key.SetVariable( 'popup_yes_no_answer', False )
self._job_key.Delete()
self._yes.Hide()
self._no.Hide()
def _ProcessText( self, text ):
@ -202,6 +207,11 @@ class PopupMessage( PopupWindow ):
self._job_key.SetVariable( 'popup_yes_no_answer', True )
self._job_key.Delete()
self._yes.Hide()
self._no.Hide()
def Cancel( self ):

View File

@ -15,9 +15,11 @@ import ClientGUIMenus
import ClientGUIScrolledPanels
import ClientGUISeedCache
import ClientGUITopLevelWindows
import ClientNetworking
import ClientNetworkingDomain
import ClientParsing
import ClientTags
import collections
import HydrusConstants as HC
import HydrusData
import HydrusExceptions
@ -985,6 +987,364 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
return ( self._mime, media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
class EditNetworkContextPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, network_context ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._context_type = ClientGUICommon.BetterChoice( self )
for ct in ( CC.NETWORK_CONTEXT_GLOBAL, CC.NETWORK_CONTEXT_DOMAIN, CC.NETWORK_CONTEXT_HYDRUS, CC.NETWORK_CONTEXT_DOWNLOADER, CC.NETWORK_CONTEXT_DOWNLOADER_QUERY, CC.NETWORK_CONTEXT_SUBSCRIPTION, CC.NETWORK_CONTEXT_THREAD_WATCHER_THREAD ):
self._context_type.Append( CC.network_context_type_string_lookup[ ct ], ct )
self._context_type_info = ClientGUICommon.BetterStaticText( self )
self._context_data_text = wx.TextCtrl( self )
self._context_data_services = ClientGUICommon.BetterChoice( self )
for service in HG.client_controller.services_manager.GetServices( HC.REPOSITORIES ):
self._context_data_services.Append( service.GetName(), service.GetServiceKey() )
self._context_data_downloaders = ClientGUICommon.BetterChoice( self )
self._context_data_downloaders.Append( 'downloaders are not ready yet!', '' )
self._context_data_subscriptions = ClientGUICommon.BetterChoice( self )
self._context_data_none = wx.CheckBox( self, label = 'No specific data--acts as default.' )
names = HG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION )
for name in names:
self._context_data_subscriptions.Append( name, name )
#
self._context_type.SelectClientData( network_context.context_type )
self._Update()
context_type = network_context.context_type
if network_context.context_data is None:
self._context_data_none.SetValue( True )
else:
if context_type == CC.NETWORK_CONTEXT_DOMAIN:
self._context_data_text.SetValue( network_context.context_data )
elif context_type == CC.NETWORK_CONTEXT_HYDRUS:
self._context_data_services.SelectClientData( network_context.context_data )
elif context_type == CC.NETWORK_CONTEXT_DOWNLOADER:
pass
#self._context_data_downloaders.SelectClientData( network_context.context_data )
elif context_type == CC.NETWORK_CONTEXT_SUBSCRIPTION:
self._context_data_subscriptions.SelectClientData( network_context.context_data )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._context_type, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._context_type_info, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._context_data_text, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._context_data_services, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._context_data_downloaders, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._context_data_subscriptions, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._context_data_none, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
#
self._context_type.Bind( wx.EVT_CHOICE, self.EventContextTypeChanged )
def _Update( self ):
self._context_type_info.SetLabelText( CC.network_context_type_description_lookup[ self._context_type.GetChoice() ] )
context_type = self._context_type.GetChoice()
self._context_data_text.Disable()
self._context_data_services.Disable()
self._context_data_downloaders.Disable()
self._context_data_subscriptions.Disable()
if context_type in ( CC.NETWORK_CONTEXT_GLOBAL, CC.NETWORK_CONTEXT_DOWNLOADER_QUERY, CC.NETWORK_CONTEXT_THREAD_WATCHER_THREAD ):
self._context_data_none.SetValue( True )
else:
self._context_data_none.SetValue( False )
if context_type == CC.NETWORK_CONTEXT_DOMAIN:
self._context_data_text.Enable()
elif context_type == CC.NETWORK_CONTEXT_HYDRUS:
self._context_data_services.Enable()
elif context_type == CC.NETWORK_CONTEXT_DOWNLOADER:
self._context_data_downloaders.Enable()
elif context_type == CC.NETWORK_CONTEXT_SUBSCRIPTION:
self._context_data_subscriptions.Enable()
def EventContextTypeChanged( self, event ):
self._Update()
def GetValue( self ):
context_type = self._context_type.GetChoice()
if self._context_data_none.GetValue() == True:
context_data = None
else:
if context_type == CC.NETWORK_CONTEXT_DOMAIN:
context_data = self._context_data_text.GetValue()
elif context_type == CC.NETWORK_CONTEXT_HYDRUS:
context_data = self._context_data_services.GetChoice()
elif context_type == CC.NETWORK_CONTEXT_DOWNLOADER:
raise HydrusExceptions.VetoException( 'Downloaders do not work yet!' )
#context_data = self._context_data_downloaders.GetChoice()
elif context_type == CC.NETWORK_CONTEXT_SUBSCRIPTION:
context_data = self._context_data_subscriptions.GetChoice()
return ClientNetworking.NetworkContext( context_type, context_data )
class EditNetworkContextCustomHeadersPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, network_contexts_to_custom_header_dicts ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._list_ctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
self._list_ctrl = ClientGUIListCtrl.BetterListCtrl( self._list_ctrl_panel, 'network_contexts_custom_headers', 15, 40, [ ( 'context', 24 ), ( 'header', 30 ), ( 'approved?', 12 ), ( 'reason', -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.Sort( 0 )
#
for ( network_context, custom_header_dict ) in network_contexts_to_custom_header_dicts.items():
for ( key, ( value, approved, reason ) ) in custom_header_dict.items():
data = ( network_context, ( key, value ), approved, reason )
self._list_ctrl.AddDatas( ( data, ) )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._list_ctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
def _Add( self ):
network_context = ClientNetworking.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, 'hostname.com' )
key = 'Authorization'
value = 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='
approved = ClientNetworkingDomain.VALID_APPROVED
reason = 'EXAMPLE REASON: HTTP header login--needed for access.'
with ClientGUITopLevelWindows.DialogEdit( self, 'edit header' ) as dlg:
panel = self._EditPanel( dlg, network_context, key, value, approved, reason )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
data = ( network_context, ( key, value ), approved, reason )
self._list_ctrl.AddDatas( ( data, ) )
def _ConvertDataToListCtrlTuples( self, data ):
( network_context, ( key, value ), approved, reason ) = data
pretty_network_context = network_context.ToUnicode()
pretty_key_value = key + ': ' + value
pretty_approved = ClientNetworkingDomain.valid_str_lookup[ approved ]
pretty_reason = reason
display_tuple = ( pretty_network_context, pretty_key_value, pretty_approved, pretty_reason )
sort_tuple = ( pretty_network_context, ( key, value ), pretty_approved, reason )
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 data in self._list_ctrl.GetData( only_selected = True ):
( network_context, ( key, value ), approved, reason ) = data
with ClientGUITopLevelWindows.DialogEdit( self, 'edit header' ) as dlg:
panel = self._EditPanel( dlg, network_context, key, value, approved, reason )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
self._list_ctrl.DeleteDatas( ( data, ) )
( network_context, key, value, approved, reason ) = panel.GetValue()
new_data = ( network_context, ( key, value ), approved, reason )
self._list_ctrl.AddDatas( ( new_data, ) )
else:
break
def GetValue( self ):
network_contexts_to_custom_header_dicts = collections.defaultdict( dict )
for ( network_context, ( key, value ), approved, reason ) in self._list_ctrl.GetData():
network_contexts_to_custom_header_dicts[ network_context ][ key ] = ( value, approved, reason )
return network_contexts_to_custom_header_dicts
class _EditPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, network_context, key, value, approved, reason ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._network_context = ClientGUICommon.NetworkContextButton( self, network_context )
self._key = wx.TextCtrl( self )
self._value = wx.TextCtrl( self )
self._approved = ClientGUICommon.BetterChoice( self )
for a in ( ClientNetworkingDomain.VALID_APPROVED, ClientNetworkingDomain.VALID_DENIED, ClientNetworkingDomain.VALID_UNKNOWN ):
self._approved.Append( ClientNetworkingDomain.valid_str_lookup[ a ], a )
self._reason = wx.TextCtrl( self )
width = ClientData.ConvertTextToPixelWidth( self._reason, 60 )
self._reason.SetMinSize( ( width, -1 ) )
#
self._key.SetValue( key )
self._value.SetValue( value )
self._approved.SelectClientData( approved )
self._reason.SetValue( reason )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._network_context, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._key, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._value, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._approved, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._reason, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
def GetValue( self ):
network_context = self._network_context.GetValue()
key = self._key.GetValue()
value = self._value.GetValue()
approved = self._approved.GetChoice()
reason = self._reason.GetValue()
return ( network_context, key, value, approved, reason )
class EditNoneableIntegerPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, value, message = '', none_phrase = 'no limit', min = 0, max = 1000000, unit = None, multiplier = 1, num_dimensions = 1 ):

View File

@ -2498,6 +2498,9 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._page_file_count_display.Append( CC.page_file_count_display_string_lookup[ display_type ], display_type )
self._reverse_page_shift_drag_behaviour = wx.CheckBox( self )
self._reverse_page_shift_drag_behaviour.SetToolTipString( 'By default, holding down shift when you drop off a page tab means the client will not \'chase\' the page tab. This makes this behaviour default, with shift-drop meaning to chase.' )
self._always_embed_autocompletes = wx.CheckBox( self )
self._hide_preview = wx.CheckBox( self )
@ -2505,6 +2508,9 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._show_thumbnail_title_banner = wx.CheckBox( self )
self._show_thumbnail_page = 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.' )
self._discord_dnd_fix = wx.CheckBox( self )
self._discord_dnd_fix.SetToolTipString( 'This makes small file drag-and-drops a little laggier in exchange for discord support.' )
@ -2553,6 +2559,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._page_file_count_display.SelectClientData( self._new_options.GetInteger( 'page_file_count_display' ) )
self._reverse_page_shift_drag_behaviour.SetValue( self._new_options.GetBoolean( 'reverse_page_shift_drag_behaviour' ) )
self._always_embed_autocompletes.SetValue( HC.options[ 'always_embed_autocompletes' ] )
self._hide_preview.SetValue( HC.options[ 'hide_preview' ] )
@ -2561,6 +2569,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._show_thumbnail_page.SetValue( self._new_options.GetBoolean( 'show_thumbnail_page' ) )
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' ) )
self._always_show_hover_windows.SetValue( self._new_options.GetBoolean( 'always_show_hover_windows' ) )
@ -2591,10 +2601,12 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
rows.append( ( 'Confirm sending more than one file to archive or inbox: ', self._confirm_archive ) )
rows.append( ( 'Max characters to display in a page name: ', self._max_page_name_chars ) )
rows.append( ( 'Show page file count after its name: ', self._page_file_count_display ) )
rows.append( ( 'Reverse page tab shift-drag behaviour: ', self._reverse_page_shift_drag_behaviour ) )
rows.append( ( 'Always embed autocomplete dropdown results window: ', self._always_embed_autocompletes ) )
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( ( '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 ) )
rows.append( ( 'BUGFIX: Hide the popup message manager when the main gui is minimised: ', self._hide_message_manager_on_gui_iconise ) )
@ -2680,11 +2692,15 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._new_options.SetInteger( 'page_file_count_display', self._page_file_count_display.GetChoice() )
self._new_options.SetBoolean( 'reverse_page_shift_drag_behaviour', self._reverse_page_shift_drag_behaviour.GetValue() )
HG.client_controller.pub( 'main_gui_title', title )
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.SetInteger( 'thumbnail_visibility_scroll_percent', self._thumbnail_visibility_scroll_percent.GetValue() )
self._new_options.SetBoolean( 'discord_dnd_fix', self._discord_dnd_fix.GetValue() )
self._new_options.SetBoolean( 'always_show_hover_windows', self._always_show_hover_windows.GetValue() )
self._new_options.SetBoolean( 'hide_message_manager_on_gui_iconise', self._hide_message_manager_on_gui_iconise.GetValue() )

View File

@ -294,7 +294,7 @@ class NewDialog( wx.Dialog ):
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_FRAMEBK ) )
self.SetIcon( wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus.ico' ), wx.BITMAP_TYPE_ICO ) )
self.SetIcon( wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus_32_non-transparent.png' ), wx.BITMAP_TYPE_PNG ) )
self.Bind( wx.EVT_BUTTON, self.EventDialogButton )
@ -619,7 +619,7 @@ class Frame( wx.Frame ):
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_FRAMEBK ) )
self.SetIcon( wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus.ico' ), wx.BITMAP_TYPE_ICO ) )
self.SetIcon( wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus_32_non-transparent.png' ), wx.BITMAP_TYPE_PNG ) )
self.Bind( wx.EVT_MENU_CLOSE, self.EventMenuClose )
self.Bind( wx.EVT_MENU_HIGHLIGHT_ALL, self.EventMenuHighlight )

View File

@ -160,7 +160,7 @@ def RequestsGet( url, params = None, stream = False, headers = None ):
headers = {}
headers[ 'User-Agent' ] = 'hydrus/' + str( HC.NETWORK_VERSION )
headers[ 'User-Agent' ] = 'hydrus client'
response = requests.get( url, params = params, stream = stream, headers = headers )
@ -198,7 +198,7 @@ def RequestsPost( url, data = None, files = None, headers = None ):
headers = {}
headers[ 'User-Agent' ] = 'hydrus/' + str( HC.NETWORK_VERSION )
headers[ 'User-Agent' ] = 'hydrus client'
response = requests.post( url, data = data, files = files )
@ -668,7 +668,7 @@ class HTTPConnection( object ):
if 'User-Agent' not in request_headers:
request_headers[ 'User-Agent' ] = 'hydrus/' + str( HC.NETWORK_VERSION )
request_headers[ 'User-Agent' ] = 'hydrus client'
if 'Accept' not in request_headers:
@ -1670,9 +1670,9 @@ class NetworkEngine( object ):
if self._current_validation_process is None:
validation_process = job.GenerateValidationProcess
validation_process = job.GenerateValidationPopupProcess()
self.controller.CallToThread( validation_process )
self.controller.CallToThread( validation_process.Start )
self._current_validation_process = validation_process
@ -1964,11 +1964,11 @@ class NetworkJob( object ):
data = self._body
files = self._files
headers = {}
headers = self.engine.domain_manager.GetHeaders( self._network_contexts )
if self._referral_url is not None:
headers = { 'referer' : self._referral_url }
headers[ 'referer' ] = self._referral_url
for ( key, value ) in self._additional_headers.items():
@ -2818,8 +2818,6 @@ class NetworkSessionManager( HydrusSerialisable.SerialisableBase ):
session = requests.Session()
session.headers.update( { 'User-Agent' : 'hydrus/' + str( HC.NETWORK_VERSION ) } )
if network_context.context_type == CC.NETWORK_CONTEXT_HYDRUS:
session.verify = False

View File

@ -7,6 +7,7 @@ import HydrusGlobals as HG
import HydrusData
import HydrusExceptions
import HydrusSerialisable
import os
import threading
import time
import urlparse
@ -41,17 +42,16 @@ def ConvertURLIntoDomain( url ):
VALID_DENIED = 0
VALID_APPROVED = 1
VALID_UNKNOWN = 2
# this should do network_contexts->user-agent as well, with some kind of approval system in place
# approval needs a new queue in the network engine. this will eventually test downloader validity and so on. failable at that stage
# user-agent info should be exportable/importable on the ui as well
# eventually extend this to do urlmatch->downloader_key, I think.
# hence we'll be able to do some kind of dnd_url->new thread watcher page
# hide urls on media viewer based on domain
# decide whether we want to add this to the dirtyobjects loop, and it which case, if anything is appropriate to store in the db separately
# hence making this a serialisableobject itself.
valid_str_lookup = {}
valid_str_lookup[ VALID_DENIED ] = 'denied'
valid_str_lookup[ VALID_APPROVED ] = 'approved'
valid_str_lookup[ VALID_UNKNOWN ] = 'unknown'
class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER
SERIALISABLE_VERSION = 1
def __init__( self ):
@ -61,7 +61,7 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
self.engine = None
self._url_matches = HydrusSerialisable.SerialisableList()
self._network_contexts_to_custom_headers = {}
self._network_contexts_to_custom_header_dicts = collections.defaultdict( dict )
self._domains_to_url_matches = collections.defaultdict( list )
@ -72,6 +72,14 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
self._RecalcCache()
def _GetSerialisableInfo( self ):
serialisable_url_matches = self._url_matches.GetSerialisableTuple()
serialisable_network_contexts_to_custom_header_dicts = [ ( network_context.GetSerialisableTuple(), custom_header_dict.items() ) for ( network_context, custom_header_dict ) in self._network_contexts_to_custom_header_dicts.items() ]
return ( serialisable_url_matches, serialisable_network_contexts_to_custom_header_dicts )
def _GetURLMatch( self, url ):
domain = ConvertURLIntoDomain( url )
@ -99,6 +107,23 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
return None
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( serialisable_url_matches, serialisable_network_contexts_to_custom_header_dicts ) = serialisable_info
self._url_matches = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_url_matches )
self._network_contexts_to_custom_header_dicts = collections.defaultdict( dict )
for ( serialisable_network_context, custom_header_dict_items ) in serialisable_network_contexts_to_custom_header_dicts:
network_context = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_network_context )
custom_header_dict = dict( custom_header_dict_items )
self._network_contexts_to_custom_header_dicts[ network_context ] = custom_header_dict
def _RecalcCache( self ):
self._domains_to_url_matches = collections.defaultdict( list )
@ -123,29 +148,32 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
return True
def GenerateValidationProcess( self, network_contexts ):
# generate a process that will, when threadcalled maybe with .Start() , ask the user, one after another, all the key-value pairs
# Should (network context) apply "(key)" header "(value)"?
# Reason given is: "You need this to make it work lol."
# once all the yes/nos are set, update db, reinitialise domain manager, set IsDone to true.
pass
def GetCustomHeaders( self, network_contexts ):
keys_to_values = {}
def GenerateValidationPopupProcess( self, network_contexts ):
with self._lock:
pass
header_tuples = []
# good order is global = least powerful, which I _think_ is how these come.
# e.g. a site User-Agent should overwrite a global default
for network_context in network_contexts:
if network_context in self._network_contexts_to_custom_header_dicts:
custom_header_dict = self._network_contexts_to_custom_header_dicts[ network_context ]
for ( key, ( value, approved, reason ) ) in custom_header_dict.items():
if approved == VALID_UNKNOWN:
header_tuples.append( ( network_context, key, value, reason ) )
process = DomainValidationPopupProcess( self, header_tuples )
return process
return keys_to_values
def GetDownloader( self, url ):
@ -161,17 +189,59 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
def GetHeaders( self, network_contexts ):
with self._lock:
headers = {}
for network_context in network_contexts:
if network_context in self._network_contexts_to_custom_header_dicts:
custom_header_dict = self._network_contexts_to_custom_header_dicts[ network_context ]
for ( key, ( value, approved, reason ) ) in custom_header_dict.items():
if approved == VALID_APPROVED:
headers[ key ] = value
return headers
def GetNetworkContextsToCustomHeaderDicts( self ):
with self._lock:
return dict( self._network_contexts_to_custom_header_dicts )
def IsDirty( self ):
with self._lock:
return self._dirty
def IsValid( self, network_contexts ):
# for now, let's say that denied headers are simply not added, not that they invalidate a query
for network_context in network_contexts:
if network_context in self._network_contexts_to_custom_headers:
if network_context in self._network_contexts_to_custom_header_dicts:
custom_headers = self._network_contexts_to_custom_headers[ network_context ]
custom_header_dict = self._network_contexts_to_custom_header_dicts[ network_context ]
for ( key, value, approved, reason ) in custom_headers:
for ( value, approved, reason ) in custom_header_dict.values():
if approved == VALID_UNKNOWN:
@ -216,15 +286,35 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
with self._lock:
custom_headers = self._network_contexts_to_custom_headers[ network_context ]
if network_context in self._network_contexts_to_custom_header_dicts:
custom_header_dict = self._network_contexts_to_custom_header_dicts[ network_context ]
if key in custom_header_dict:
( value, old_approved, reason ) = custom_header_dict[ key ]
custom_header_dict[ key ] = ( value, approved, reason )
self._SetDirty()
def SetNetworkContextsToCustomHeaderDicts( self, network_contexts_to_custom_header_dicts ):
with self._lock:
self._network_contexts_to_custom_header_dicts = network_contexts_to_custom_header_dicts
self._SetDirty()
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER ] = NetworkDomainManager
class DomainValidationProcess( object ):
class DomainValidationPopupProcess( object ):
def __init__( self, domain_manager, header_tuples ):
@ -246,16 +336,21 @@ class DomainValidationProcess( object ):
results = []
for ( network_context, key, value, approval_reason ) in self._header_tuples:
for ( network_context, key, value, reason ) in self._header_tuples:
job_key = ClientThreading.JobKey()
# generate question
question = 'intro text ' + approval_reason
question = 'For the network context ' + network_context.ToUnicode() + ', can the client set this header?'
question += os.linesep * 2
question += key + ': ' + value
question += os.linesep * 2
question += reason
job_key.SetVariable( 'popup_yes_no_question', question )
# pub it
HG.client_controller.pub( 'message', job_key )
result = job_key.GetIfHasVariable( 'popup_yes_no_answer' )
@ -268,6 +363,8 @@ class DomainValidationProcess( object ):
time.sleep( 0.25 )
result = job_key.GetIfHasVariable( 'popup_yes_no_answer' )
if result:
@ -287,10 +384,6 @@ class DomainValidationProcess( object ):
# make this serialisable--maybe with name as the name of a named serialisable
# __hash__ for name? not sure
# maybe all serialisable should return __hash__ of ( type, name ) if they don't already
# that might lead to problems elsewhere, so careful
class URLMatch( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_URL_MATCH

View File

@ -49,7 +49,7 @@ options = {}
# Misc
NETWORK_VERSION = 18
SOFTWARE_VERSION = 276
SOFTWARE_VERSION = 277
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -15,6 +15,18 @@ import HydrusGlobals as HG
import HydrusPaths
import warnings
if not hasattr( PILImage, 'DecompressionBombWarning' ):
# super old versions don't have this, so let's just make a stub, wew
class DBW_stub( Exception ):
pass
PILImage.DecompressionBombWarning = DBW_stub
warnings.simplefilter( 'ignore', PILImage.DecompressionBombWarning )
def ConvertToPngIfBmp( path ):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
static/hydrus_32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B