Version 299

This commit is contained in:
Hydrus Network Developer 2018-03-21 19:03:33 -05:00
parent 76fe7117c7
commit 2f106cc97d
28 changed files with 1201 additions and 374 deletions

View File

@ -8,6 +8,34 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 299</h3></li>
<ul>
<li>wrote ui to review and even edit session cookies by network context. it is still a bit rough but will help with future development.</li>
<li>added a 'open_selection_in_new_page' shortcut to the 'media' shortcut set that will work on the thumbnail view</li>
<li>added a 'export_files' shortcut to the 'media' shortcut set that will work on the thumbnail view</li>
<li>fudged manage siblings logic to not do the borked 'hey, that sibling already exists' as soon as you type the old sibling--it will now auto-petition any existing siblings when you click 'add' with a good automated petition reason that makes sense to the janitor</li>
<li>manage siblings now also only shows rows appropriate to the current selection like parents does. it gets a new 'notes' column to specify conflicts that will be auto-petitioned as above</li>
<li>manage siblings and parents now have a laggy 'show all pairs' checkbox to let you quickly review everything like you used to</li>
<li>fixed a database-level bug that meant petitioned and pending the same left-hand sibling tag (like petition a->b, pend a->c) would sometimes not both save together</li>
<li>when you middle-click or right-click->open a new page on a selection of tags/search predicates, the new page will now be named after the tags</li>
<li>if subscriptions hit their periodic file limit, they will now give a little popup message describing what happened and possible causes and actions the user can take</li>
<li>added a 'make a modal popup in five seconds' action to help->debug</li>
<li>modal popups will now hide/show other child frames (like review services) rather than minimise/restore, as this latter action can raise the entire progam to the front</li>
<li>added a 'make a parentless text control dialog' debug entry to test some key event catching</li>
<li>added a 'layout all tlws' debug entry to help->debug that'll hopefully help figure out some child window sizing/position issues</li>
<li>added a 'shortcut report mode', which will report caught shortcut keys and their matched commands, if any</li>
<li>fixed a little shortcut catching bug in the main gui</li>
<li>finished adding the new bytes control</li>
<li>updated ffmpeg for windows</li>
<li>improved sankaku default bandwidth rules to stop a subscription bandwidth rules mismatch that could sometimes make for subscription delays--existing users may like to add a 2GB/day rule for sankakucomplex.com</li>
<li>improved some service and account error handling to better propagate the exact problem up the exception handling chain</li>
<li>the clientside 'update A actually had hash B' repository sync error is now dealt with in a less severe way, and the bad update file is saved to disk with a request for it to be forwarded to hydrus dev for further investigation</li>
<li>file parsing is now more resistant to invalid negative values for properties like width, height, and duration</li>
<li>improved some focus code that may have been affecting linux stability</li>
<li>improved a deviant art url-not-found error</li>
<li>improved a wx version boot test</li>
<li>misc fixes</li>
</ul>
<li><h3>version 298</h3></li>
<ul>
<li>wrote a new 'bytescontrol' and related noneablebytescontrol that allows for a uniform way to choose a bytes size value with a wider range--and deployed it all over the place</li>

View File

@ -336,7 +336,7 @@ SHORTCUTS_RESERVED_NAMES = [ 'archive_delete_filter', 'duplicate_filter', 'media
# shortcut commands
SHORTCUTS_MEDIA_ACTIONS = [ 'manage_file_tags', 'manage_file_ratings', 'manage_file_urls', 'manage_file_notes', 'archive_file', 'inbox_file', 'delete_file', 'remove_file_from_view', 'open_file_in_external_program', 'launch_the_archive_delete_filter', 'copy_bmp', 'copy_file', 'copy_path', 'copy_sha256_hash', 'get_similar_to_exact', 'get_similar_to_very_similar', 'get_similar_to_similar', 'get_similar_to_speculative' ]
SHORTCUTS_MEDIA_ACTIONS = [ 'manage_file_tags', 'manage_file_ratings', 'manage_file_urls', 'manage_file_notes', 'archive_file', 'inbox_file', 'delete_file', 'export_files', 'remove_file_from_view', 'open_file_in_external_program', 'open_selection_in_new_page', 'launch_the_archive_delete_filter', 'copy_bmp', 'copy_file', 'copy_path', 'copy_sha256_hash', 'get_similar_to_exact', 'get_similar_to_very_similar', 'get_similar_to_similar', 'get_similar_to_speculative' ]
SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ 'move_animation_to_previous_frame', 'move_animation_to_next_frame', 'switch_between_fullscreen_borderless_and_regular_framed_window', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'zoom_in', 'zoom_out', 'switch_between_100_percent_and_canvas_zoom', 'flip_darkmode' ]
SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS = [ 'view_next', 'view_first', 'view_last', 'view_previous' ]
SHORTCUTS_MAIN_GUI_ACTIONS = [ 'refresh', 'new_page', 'synchronised_wait_switch', 'set_media_focus', 'show_hide_splitters', 'set_search_focus', 'unclose_page', 'close_page', 'redo', 'undo', 'flip_darkmode', 'check_all_import_folders' ]

View File

@ -1,3 +1,16 @@
import os
import wx
wx_first_num = int( wx.__version__[0] )
if wx_first_num < 4:
wx_error = 'Unfortunately, hydrus now requires the new Phoenix (4.x) version of wx.'
wx_error += os.linesep * 2
wx_error += 'Please check the \'running from source\' page in the html help for more details.'
raise Exception( wx_error )
import ClientCaches
import ClientData
import ClientDaemons
@ -26,28 +39,15 @@ import ClientGUIDialogs
import ClientGUIScrolledPanelsManagement
import ClientGUITopLevelWindows
import gc
import os
import psutil
import threading
import time
import traceback
import wx
if not HG.twisted_is_broke:
from twisted.internet import reactor, defer
wx_first_num = int( wx.__version__[0] )
if wx_first_num < 4:
wx_error = 'Unfortunately, hydrus now requires the new Phoenix (4.x) version of wx.'
wx_error += os.linesep * 2
wx_error += 'The good news is that you can get the new version via pip. If you still need the old version of wx, Phoenix works a lot better with virtual environments.'
raise Exception( wx_error )
class Controller( HydrusController.HydrusController ):
def __init__( self, db_dir, no_daemons, no_wal ):

View File

@ -7547,7 +7547,7 @@ class DB( HydrusDB.HydrusDB ):
reason_id = self._GetTextId( reason )
self._c.execute( 'DELETE FROM tag_sibling_petitions WHERE service_id = ? AND bad_tag_id = ?;', ( service_id, bad_tag_id ) )
self._c.execute( 'DELETE FROM tag_sibling_petitions WHERE service_id = ? AND bad_tag_id = ? AND good_tag_id = ?;', ( service_id, bad_tag_id, good_tag_id ) )
self._c.execute( 'INSERT OR IGNORE INTO tag_sibling_petitions ( service_id, bad_tag_id, good_tag_id, reason_id, status ) VALUES ( ?, ?, ?, ?, ? );', ( service_id, bad_tag_id, good_tag_id, reason_id, new_status ) )

View File

@ -90,6 +90,8 @@ def SetDefaultBandwidthManagerRules( bandwidth_manager ):
rules.AddRule( HC.BANDWIDTH_TYPE_REQUESTS, 4, 1 )
rules.AddRule( HC.BANDWIDTH_TYPE_DATA, 86400, 2 * GB ) # keep this in there so subs can know better when to stop running (the files come from a subdomain, which causes a pain for bandwidth calcs)
bandwidth_manager.SetRules( ClientNetworking.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, 'sankakucomplex.com' ), rules )
def SetDefaultDomainManagerData( domain_manager ):

View File

@ -1067,6 +1067,8 @@ class GalleryDeviantArt( Gallery ):
def _ParseImagePage( self, html, referral_url ):
img_url = None
soup = GetSoup( html )
download_button = soup.find( 'a', class_ = 'dev-page-download' )
@ -1127,6 +1129,11 @@ class GalleryDeviantArt( Gallery ):
img_url = download_button[ 'href' ]
if img_url is None:
raise HydrusExceptions.ParseException( 'Could not find a download link--maybe this work was text?' )
return img_url

View File

@ -13,6 +13,7 @@ import ClientGUIMenus
import ClientGUIPages
import ClientGUIParsing
import ClientGUIPopupMessages
import ClientGUIScrolledPanels
import ClientGUIScrolledPanelsEdit
import ClientGUIScrolledPanelsManagement
import ClientGUIScrolledPanelsReview
@ -88,7 +89,9 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
self._statusbar_thread_updater = ClientGUICommon.ThreadToGUIUpdater( self._statusbar, self.RefreshStatusBar )
self._focus_holder = wx.Window( self, size = ( 0, 0 ) )
self._focus_holder = wx.Window( self )
self._focus_holder.SetSize( ( 0, 0 ) )
self._closed_pages = []
self._closed_page_keys = set()
@ -757,6 +760,55 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
def _DebugMakeDelayedModalPopup( self ):
def do_it( controller ):
time.sleep( 5 )
job_key = ClientThreading.JobKey( cancellable = True )
job_key.SetVariable( 'popup_title', 'debug modal job' )
controller.pub( 'modal_message', job_key )
for i in range( 5 ):
if job_key.IsCancelled():
break
job_key.SetVariable( 'popup_text_1', 'Will auto-dismiss in ' + HydrusData.ConvertTimeDeltaToPrettyString( 5 - i ) + '.' )
job_key.SetVariable( 'popup_gauge_1', ( i, 5 ) )
time.sleep( 1 )
job_key.Delete()
self._controller.CallToThread( do_it, self._controller )
def _DebugMakeParentlessTextCtrl( self ):
with wx.Dialog( None, title = 'parentless debug dialog' ) as dlg:
control = wx.TextCtrl( dlg )
control.SetValue( 'debug test input' )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( control, CC.FLAGS_EXPAND_BOTH_WAYS )
dlg.SetSizer( vbox )
dlg.ShowModal()
def _DebugMakeSomePopups( self ):
for i in range( 1, 7 ):
@ -983,6 +1035,20 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
def _ForceLayoutAllTLWs( self ):
tlws = wx.GetTopLevelWindows()
for tlw in tlws:
HydrusData.ShowText( 'TLW ' + repr( tlw ) + ': pre size/pos - ' + repr( tuple( tlw.GetSize() ) ) + ' ' + repr( tuple( tlw.GetPosition() ) ) )
tlw.Layout()
HydrusData.ShowText( 'TLW ' + repr( tlw ) + ': post size/pos - ' + repr( tuple( tlw.GetSize() ) ) + ' ' + repr( tuple( tlw.GetPosition() ) ) )
def _GenerateMenuInfo( self, name ):
menu = wx.Menu()
@ -1495,6 +1561,12 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
ClientGUIMenus.AppendMenuLabel( menu, '(This section is under construction)' )
ClientGUIMenus.AppendMenuItem( self, menu, 'review session cookies', 'Review and edit which cookies you have for which network contexts.', self._ReviewNetworkSessions )
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuLabel( menu, '(This section is under construction)' )
# this will be the easy-mode 'export ability to download from blahbooru' that'll bundle it all into a nice package with a neat png.
# need a name for this that isn't 'downloader', or maybe it should be, and I should rename downloaders below to 'gallery query generator' or whatever.
@ -1716,12 +1788,16 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
ClientGUIMenus.AppendMenuCheckItem( self, report_modes, 'gui report mode', 'Have the gui report inside information, where supported.', HG.gui_report_mode, self._SwitchBoolean, 'gui_report_mode' )
ClientGUIMenus.AppendMenuCheckItem( self, report_modes, 'hover window report mode', 'Have the hover windows report their show/hide logic.', HG.hover_window_report_mode, self._SwitchBoolean, 'hover_window_report_mode' )
ClientGUIMenus.AppendMenuCheckItem( self, report_modes, 'network report mode', 'Have the network engine report new jobs.', HG.network_report_mode, self._SwitchBoolean, 'network_report_mode' )
ClientGUIMenus.AppendMenuCheckItem( self, report_modes, 'shortcut report mode', 'Have the new shortcut system report what shortcuts it catches and whether it matches an action.', HG.shortcut_report_mode, self._SwitchBoolean, 'shortcut_report_mode' )
ClientGUIMenus.AppendMenu( debug, report_modes, 'report modes' )
ClientGUIMenus.AppendMenuItem( self, debug, 'make some popups', 'Throw some varied popups at the message manager, just to check it is working.', self._DebugMakeSomePopups )
ClientGUIMenus.AppendMenuItem( self, debug, 'make a popup in five seconds', 'Throw a delayed popup at the message manager, giving you time to minimise or otherwise alter the client before it arrives.', self._controller.CallLater, 5, HydrusData.ShowText, 'This is a delayed popup message.' )
ClientGUIMenus.AppendMenuItem( self, debug, 'make a modal popup in five seconds', 'Throw up a delayed modal popup to test with. It will stay alive for five seconds.', self._DebugMakeDelayedModalPopup )
ClientGUIMenus.AppendMenuItem( self, debug, 'make a parentless text ctrl dialog', 'Make a parentless text control in a dialog to test some character event catching.', self._DebugMakeParentlessTextCtrl )
ClientGUIMenus.AppendMenuItem( self, debug, 'force a gui layout now', 'Tell the gui to relayout--useful to test some gui bootup layout issues.', self.Layout )
ClientGUIMenus.AppendMenuItem( self, debug, 'force a layout for all tlws now', 'Tell all frames to relayout--useful to test some layout issues.', self._ForceLayoutAllTLWs )
ClientGUIMenus.AppendMenuItem( self, debug, 'flush log', 'Command the log to write any buffered contents to hard drive.', HydrusData.DebugPrint, 'Flushing log' )
ClientGUIMenus.AppendMenuItem( self, debug, 'print garbage', 'Print some information about the python garbage to the log.', self._DebugPrintGarbage )
ClientGUIMenus.AppendMenuItem( self, debug, 'show scheduled jobs', 'Print some information about the currently scheduled jobs log.', self._DebugShowScheduledJobs )
@ -2625,6 +2701,15 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
frame.SetPanel( panel )
def _ReviewNetworkSessions( self ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, 'review network sessions' )
panel = ClientGUIScrolledPanelsReview.ReviewNetworkSessionsPanel( frame, self._controller.network_engine.session_manager )
frame.SetPanel( panel )
def _ReviewServices( self ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, self._controller.PrepStringForDisplay( 'Review Services' ), 'review_services' )
@ -2882,6 +2967,10 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
HG.network_report_mode = not HG.network_report_mode
elif name == 'shortcut_report_mode':
HG.shortcut_report_mode = not HG.shortcut_report_mode
elif name == 'pubsub_profile_mode':
HG.pubsub_profile_mode = not HG.pubsub_profile_mode
@ -3915,7 +4004,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._notebook.ChooseNewPageForDeepestNotebook()
if action == 'close_page':
elif action == 'close_page':
self._notebook.CloseCurrentPage()

View File

@ -1797,11 +1797,13 @@ class MenuButton( BetterButton ):
class NetworkContextButton( BetterButton ):
def __init__( self, parent, network_context ):
def __init__( self, parent, network_context, limited_types = None, allow_default = True ):
BetterButton.__init__( self, parent, network_context.ToUnicode(), self._Edit )
self._network_context = network_context
self._limited_types = limited_types
self._allow_default = allow_default
def _Edit( self ):
@ -1811,7 +1813,7 @@ class NetworkContextButton( BetterButton ):
with ClientGUITopLevelWindows.DialogEdit( self, 'edit network context' ) as dlg:
panel = ClientGUIScrolledPanelsEdit.EditNetworkContextPanel( dlg, self._network_context )
panel = ClientGUIScrolledPanelsEdit.EditNetworkContextPanel( dlg, self._network_context, limited_types = self._limited_types, allow_default = self._allow_default )
dlg.SetPanel( panel )
@ -1855,11 +1857,19 @@ class NoneableSpinCtrl( wx.Panel ):
self._checkbox.Bind( wx.EVT_CHECKBOX, self.EventCheckBox )
self._checkbox.SetLabelText( none_phrase )
self._one = wx.SpinCtrl( self, min = min, max = max, size = ( 60, -1 ) )
self._one = wx.SpinCtrl( self, min = min, max = max )
width = ClientData.ConvertTextToPixelWidth( self._one, len( str( max ) ) + 2 )
self._one.SetInitialSize( ( width, -1 ) )
if num_dimensions == 2:
self._two = wx.SpinCtrl( self, initial = 0, min = min, max = max, size = ( 60, -1 ) )
self._two = wx.SpinCtrl( self, initial = 0, min = min, max = max )
width = ClientData.ConvertTextToPixelWidth( self._two, len( str( max ) ) + 2 )
self._two.SetInitialSize( ( width, -1 ) )
hbox = wx.BoxSizer( wx.HORIZONTAL )

View File

@ -237,7 +237,11 @@ class BytesControl( wx.Panel ):
wx.Panel.__init__( self, parent )
self._spin = wx.SpinCtrl( self, min = 0, max = 1048576, size = ( 60, -1 ) )
self._spin = wx.SpinCtrl( self, min = 0, max = 1048576 )
width = ClientData.ConvertTextToPixelWidth( self._spin, 9 )
self._spin.SetSize( ( width, -1 ) )
self._unit = ClientGUICommon.BetterChoice( self )
@ -349,6 +353,16 @@ class NoneableBytesControl( wx.Panel ):
def SetToolTip( self, text ):
wx.Panel.SetToolTip( self, text )
for c in self.GetChildren():
c.SetToolTip( text )
def SetValue( self, value ):
if value is None:

View File

@ -3648,6 +3648,8 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
self._pairs_to_reasons = {}
self._show_all = wx.CheckBox( self )
self._tag_parents = ClientGUIListCtrl.BetterListCtrl( self, 'tag_parents', 30, 25, [ ( '', 4 ), ( 'child', 25 ), ( 'parent', -1 ) ], self._ConvertPairToListCtrlTuples, delete_key_callback = self._ListCtrlActivated, activation_callback = self._ListCtrlActivated )
self._tag_parents.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
self._tag_parents.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
@ -3690,6 +3692,7 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
vbox.Add( self._status_st, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._count_st, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( ClientGUICommon.WrapInText( self._show_all, self, 'show all pairs' ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._tag_parents, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( self._add, CC.FLAGS_LONE_BUTTON )
vbox.Add( tags_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
@ -3700,6 +3703,7 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
#
self.Bind( ClientGUIListBoxes.EVT_LIST_BOX, self.EventListBoxChanged )
self._show_all.Bind( wx.EVT_CHECKBOX, self.EventShowAll )
HG.client_controller.CallToThread( self.THREADInitialise, tags, self._service_key )
@ -4022,6 +4026,8 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
all_pairs = set()
show_all = self._show_all.GetValue()
for ( status, pairs ) in self._current_statuses_to_pairs.items():
if status == HC.CONTENT_STATUS_DELETED:
@ -4029,10 +4035,9 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
continue
if len( pertinent_tags ) == 0:
if status == HC.CONTENT_STATUS_CURRENT:
if status == HC.CONTENT_STATUS_CURRENT and not show_all:
continue
@ -4049,7 +4054,7 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
( a, b ) = pair
if a in pertinent_tags or b in pertinent_tags:
if a in pertinent_tags or b in pertinent_tags or show_all:
all_pairs.add( pair )
@ -4095,7 +4100,10 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
children = self._children.GetTags()
parents = self._parents.GetTags()
for parent in parents: self._AddPairs( children, parent )
for parent in parents:
self._AddPairs( children, parent )
self._children.SetTags( [] )
self._parents.SetTags( [] )
@ -4115,6 +4123,11 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
self._UpdateListCtrlData()
def EventShowAll( self, event ):
self._UpdateListCtrlData()
def GetContentUpdates( self ):
# we make it manually here because of the mass pending tags done (but not undone on a rescind) on a pending pair!
@ -4178,23 +4191,7 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
self._child_input.Enable()
self._parent_input.Enable()
'''
all_pairs = set()
for ( status, pairs ) in self._original_statuses_to_pairs.items():
if status == HC.CONTENT_STATUS_DELETED:
continue
all_pairs.update( pairs )
self._tag_parents.AddDatas( all_pairs )
self._tag_parents.Sort( 2 )
'''
if tags is None:
self._UpdateListCtrlData()
@ -4270,7 +4267,7 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self.SetSizer( vbox )
self.SetInitialSize( ( 550, 780 ) )
self.SetInitialSize( ( 850, 780 ) )
def _SetSearchFocus( self ):
@ -4354,10 +4351,14 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._current_new = None
self._tag_siblings = ClientGUIListCtrl.BetterListCtrl( self, 'tag_siblings', 30, 25, [ ( '', 4 ), ( 'old', 25 ), ( 'new', -1 ) ], self._ConvertPairToListCtrlTuples, delete_key_callback = self._ListCtrlActivated, activation_callback = self._ListCtrlActivated )
self._show_all = wx.CheckBox( self )
self._tag_siblings = ClientGUIListCtrl.BetterListCtrl( self, 'tag_siblings', 30, 40, [ ( '', 4 ), ( 'old', 25 ), ( 'new', 25 ), ( 'note', -1 ) ], self._ConvertPairToListCtrlTuples, delete_key_callback = self._ListCtrlActivated, activation_callback = self._ListCtrlActivated )
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
self._tag_siblings.Sort( 2 )
self._old_siblings = ClientGUIListBoxes.ListBoxTagsStringsAddRemove( self, self._service_key, show_sibling_text = False )
self._new_sibling = ClientGUICommon.BetterStaticText( self )
@ -4398,6 +4399,7 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
vbox.Add( self._status_st, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._count_st, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( ClientGUICommon.WrapInText( self._show_all, self, 'show all pairs' ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._tag_siblings, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( self._add, CC.FLAGS_LONE_BUTTON )
vbox.Add( text_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
@ -4407,10 +4409,13 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
#
self._show_all.Bind( wx.EVT_CHECKBOX, self.EventShowAll )
self.Bind( ClientGUIListBoxes.EVT_LIST_BOX, self.EventListBoxChanged )
HG.client_controller.CallToThread( self.THREADInitialise, tags, self._service_key )
def _AddPairs( self, olds, new ):
def _AddPairs( self, olds, new, remove_only = False, default_reason = None ):
new_pairs = []
current_pairs = []
@ -4427,27 +4432,32 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
elif pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]:
petitioned_pairs.append( pair )
if not remove_only:
petitioned_pairs.append( pair )
elif pair in self._original_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ]:
current_pairs.append( pair )
elif self._CanAdd( pair ):
elif not remove_only and self._CanAdd( pair ):
new_pairs.append( pair )
affected_pairs = []
if len( new_pairs ) > 0:
do_it = True
if self._service_key != CC.LOCAL_TAG_SERVICE_KEY:
if self._service.HasPermission( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.PERMISSION_ACTION_OVERRULE ):
if default_reason is not None:
reason = default_reason
elif self._service.HasPermission( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.PERMISSION_ACTION_OVERRULE ):
reason = 'admin'
@ -4470,7 +4480,10 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
reason = dlg.GetValue()
else: do_it = False
else:
do_it = False
@ -4484,8 +4497,6 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ].update( new_pairs )
affected_pairs.extend( new_pairs )
else:
@ -4495,60 +4506,58 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
if self._service_key != CC.LOCAL_TAG_SERVICE_KEY:
if len( current_pairs ) > 10:
if default_reason is not None:
pair_strings = 'The many pairs you entered.'
reason = default_reason
elif self._service.HasPermission( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.PERMISSION_ACTION_OVERRULE ):
reason = 'admin'
else:
pair_strings = os.linesep.join( ( old + '->' + new for ( old, new ) in current_pairs ) )
if len( current_pairs ) > 1: message = 'The pairs:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'Already exist.'
else: message = 'The pair ' + pair_strings + ' already exists.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'petition it', no_label = 'do nothing' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
if len( pending_pairs ) > 10:
if self._service.HasPermission( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.PERMISSION_ACTION_OVERRULE ):
reason = 'admin'
else:
message = 'Enter a reason for this pair to be removed. A janitor will review your petition.'
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
else: do_it = False
if do_it:
for pair in current_pairs: self._pairs_to_reasons[ pair ] = reason
pair_strings = 'The many pairs you entered.'
else:
do_it = False
pair_strings = os.linesep.join( ( old + '->' + new for ( old, new ) in pending_pairs ) )
message = 'Enter a reason for:'
message += os.linesep * 2
message += pair_strings
message += os.linesep * 2
message += 'to be removed. You will see the delete as soon as you upload, but a janitor will review your petition to decide if all users should receive it as well.'
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
reason = dlg.GetValue()
else:
do_it = False
if do_it:
for pair in current_pairs:
self._pairs_to_reasons[ pair ] = reason
if do_it:
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ].update( current_pairs )
affected_pairs.extend( current_pairs )
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ].update( current_pairs )
if len( pending_pairs ) > 0:
if len( pending_pairs ) > 10:
pair_strings = 'The many pairs you entered.'
@ -4558,8 +4567,14 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
pair_strings = os.linesep.join( ( old + '->' + new for ( old, new ) in pending_pairs ) )
if len( pending_pairs ) > 1: message = 'The pairs:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'Are pending.'
else: message = 'The pair ' + pair_strings + ' is pending.'
if len( pending_pairs ) > 1:
message = 'The pairs:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'Are pending.'
else:
message = 'The pair ' + pair_strings + ' is pending.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'rescind the pend', no_label = 'do nothing' ) as dlg:
@ -4567,8 +4582,6 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ].difference_update( pending_pairs )
affected_pairs.extend( pending_pairs )
@ -4592,40 +4605,10 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ].difference_update( petitioned_pairs )
affected_pairs.extend( petitioned_pairs )
if len( affected_pairs ) > 0:
def in_current( pair ):
for status in ( HC.CONTENT_STATUS_CURRENT, HC.CONTENT_STATUS_PENDING, HC.CONTENT_STATUS_PETITIONED ):
if pair in self._current_statuses_to_pairs[ status ]:
return True
return False
affected_pairs = [ ( self._tag_siblings.HasData( pair ), in_current( pair ), pair ) for pair in affected_pairs ]
to_add = [ pair for ( exists, current, pair ) in affected_pairs if not exists ]
to_update = [ pair for ( exists, current, pair ) in affected_pairs if exists and current ]
to_delete = [ pair for ( exists, current, pair ) in affected_pairs if exists and not current ]
self._tag_siblings.AddDatas( to_add )
self._tag_siblings.UpdateDatas( to_update )
self._tag_siblings.DeleteDatas( to_delete )
self._tag_siblings.Sort()
def _CanAdd( self, potential_pair ):
@ -4687,6 +4670,8 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._UpdateListCtrlData()
def _ConvertPairToListCtrlTuples( self, pair ):
@ -4709,67 +4694,98 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
pretty_status = sign
display_tuple = ( pretty_status, old, new )
sort_tuple = ( status, old, new )
existing_olds = self._old_siblings.GetTags()
note = ''
if old in existing_olds:
if status == HC.CONTENT_STATUS_PENDING:
note = 'CONFLICT: Will be rescinded on add.'
elif status == HC.CONTENT_STATUS_CURRENT:
note = 'CONFLICT: Will be petitioned/deleted on add.'
display_tuple = ( pretty_status, old, new, note )
sort_tuple = ( status, old, new, note )
return ( display_tuple, sort_tuple )
def _SetButtonStatus( self ):
if self._current_new is None or len( self._old_siblings.GetTags() ) == 0: self._add.Disable()
else: self._add.Enable()
if self._current_new is None or len( self._old_siblings.GetTags() ) == 0:
self._add.Disable()
else:
self._add.Enable()
def _UpdateListCtrlData( self ):
olds = self._old_siblings.GetTags()
pertinent_tags = set( olds )
if self._current_new is not None:
pertinent_tags.add( self._current_new )
self._tag_siblings.DeleteDatas( self._tag_siblings.GetData() )
all_pairs = set()
show_all = self._show_all.GetValue()
for ( status, pairs ) in self._current_statuses_to_pairs.items():
if status == HC.CONTENT_STATUS_DELETED:
continue
if len( pertinent_tags ) == 0:
if status == HC.CONTENT_STATUS_CURRENT and not show_all:
continue
# show all pending/petitioned
all_pairs.update( pairs )
else:
# show all appropriate
for pair in pairs:
( a, b ) = pair
if a in pertinent_tags or b in pertinent_tags or show_all:
all_pairs.add( pair )
self._tag_siblings.AddDatas( all_pairs )
self._tag_siblings.Sort()
def EnterOlds( self, olds ):
potential_olds = olds
olds = set()
for potential_old in potential_olds:
do_it = True
current_pairs = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ].union( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ] ).difference( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ] )
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
while potential_old in current_olds:
olds_to_news = dict( current_pairs )
conflicting_new = olds_to_news[ potential_old ]
message = 'There already is a relationship set for ' + potential_old + '! It goes to ' + conflicting_new + '.'
message += os.linesep * 2
message += 'You cannot have two siblings for the same original term.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'I want to overwrite the existing record', no_label = 'do nothing' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._AddPairs( [ potential_old ], conflicting_new )
else:
do_it = False
break
current_pairs = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ].union( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ] ).difference( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ] )
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
if do_it:
olds.add( potential_old )
if self._current_new in olds:
self.SetNew( set() )
@ -4777,6 +4793,8 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._old_siblings.EnterTags( olds )
self._UpdateListCtrlData()
self._SetButtonStatus()
@ -4784,13 +4802,38 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
if self._current_new is not None and len( self._old_siblings.GetTags() ) > 0:
# let's eliminate conflicts first
olds = self._old_siblings.GetTags()
current_pairs = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ].union( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ] ).difference( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ] )
olds_to_news = dict( current_pairs )
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
for old in olds:
if old in current_olds:
conflicting_new = olds_to_news[ old ]
if conflicting_new != self._current_new:
self._AddPairs( [ old ], conflicting_new, remove_only = True, default_reason = 'AUTO-PETITION TO REASSIGN TO: ' + self._current_new )
#
self._AddPairs( olds, self._current_new )
self._old_siblings.SetTags( set() )
self.SetNew( set() )
self._UpdateListCtrlData()
self._SetButtonStatus()
@ -4800,6 +4843,16 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._SetButtonStatus()
def EventListBoxChanged( self, event ):
self._UpdateListCtrlData()
def EventShowAll( self, event ):
self._UpdateListCtrlData()
def GetContentUpdates( self ):
# we make it manually here because of the mass pending tags done (but not undone on a rescind) on a pending pair!
@ -4811,8 +4864,15 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
if self._service_key == CC.LOCAL_TAG_SERVICE_KEY:
for pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]: content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_ADD, pair ) )
for pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]: content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_DELETE, pair ) )
for pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]:
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_ADD, pair ) )
for pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]:
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_DELETE, pair ) )
else:
@ -4861,6 +4921,8 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._current_new = new
self._UpdateListCtrlData()
self._SetButtonStatus()
@ -4894,23 +4956,12 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._old_input.Enable()
self._new_input.Enable()
all_pairs = set()
for ( status, pairs ) in self._original_statuses_to_pairs.items():
if tags is None:
if status == HC.CONTENT_STATUS_DELETED:
continue
self._UpdateListCtrlData()
all_pairs.update( pairs )
self._tag_siblings.AddDatas( all_pairs )
self._tag_siblings.Sort( 2 )
if tags is not None:
else:
self.EnterOlds( tags )

View File

@ -1187,7 +1187,13 @@ class ListBoxTags( ListBox ):
if len( predicates ) > 0:
HG.client_controller.pub( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY, initial_predicates = predicates )
s = [ predicate.GetUnicode() for predicate in predicates ]
s.sort()
page_name = ', '.join( s )
HG.client_controller.pub( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY, initial_predicates = predicates, page_name = page_name )

View File

@ -1586,7 +1586,7 @@ class ManagementPanelImporterGallery( ManagementPanelImporter ):
if page_key == self._page_key:
self._query_input.SetFocus()
wx.CallAfter( self._query_input.SetFocus )
@ -1978,7 +1978,10 @@ class ManagementPanelImporterPageOfImages( ManagementPanelImporter ):
def SetSearchFocus( self, page_key ):
if page_key == self._page_key: self._page_url_input.SetFocus()
if page_key == self._page_key:
wx.CallAfter( self._page_url_input.SetFocus )
def Start( self ):
@ -2298,7 +2301,7 @@ class ManagementPanelImporterThreadWatcher( ManagementPanelImporter ):
if page_key == self._page_key and self._thread_input.IsEditable():
self._thread_input.SetFocus()
wx.CallAfter( self._thread_input.SetFocus )
@ -2463,7 +2466,7 @@ class ManagementPanelImporterURLs( ManagementPanelImporter ):
if page_key == self._page_key:
self._url_input.SetFocus()
wx.CallAfter( self._url_input.SetFocus )
@ -3262,8 +3265,10 @@ class ManagementPanelQuery( ManagementPanel ):
if page_key == self._page_key:
try: self._searchbox.SetFocus() # there's a chance this doesn't exist!
except: self._controller.pub( 'set_media_focus' )
if self._search_enabled:
wx.CallAfter( self._searchbox.SetFocus )

View File

@ -497,6 +497,54 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
def _ExportFiles( self ):
if len( self._selected_media ) > 0:
flat_media = []
for media in self._sorted_media:
if media in self._selected_media:
if media.IsCollection():
flat_media.extend( media.GetFlatMedia() )
else:
flat_media.append( media )
with ClientGUIDialogs.DialogSetupExport( None, flat_media ) as dlg:
dlg.ShowModal()
self.SetFocus()
def _ExportTags( self ):
if len( self._selected_media ) > 0:
services = HG.client_controller.services_manager.GetServices( ( HC.LOCAL_TAG, HC.TAG_REPOSITORY, HC.COMBINED_TAG ) )
service_keys = [ service.GetServiceKey() for service in services ]
service_key = ClientGUIDialogs.SelectServiceKey( service_keys = service_keys )
hashes = self._GetSelectedHashes()
if service_key is not None:
ClientTags.ExportToHTA( self, service_key, hashes )
def _FullScreen( self, first_media = None ):
@ -1664,6 +1712,10 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
self._CopyHashesToClipboard( 'sha256' )
elif action == 'export_files':
self._ExportFiles()
elif action == 'manage_file_ratings':
self._ManageRatings()
@ -1716,6 +1768,10 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
self._OpenExternally()
elif action == 'open_selection_in_new_page':
self._ShowSelectionInNewPage()
elif action == 'launch_the_archive_delete_filter':
self._ArchiveDeleteFilter()
@ -2019,46 +2075,6 @@ class MediaPanelThumbnails( MediaPanel ):
HG.client_controller.GetCache( 'thumbnail' ).Waterfall( self._page_key, thumbnails_to_render_later )
def _ExportFiles( self ):
if len( self._selected_media ) > 0:
flat_media = []
for media in self._sorted_media:
if media in self._selected_media:
if media.IsCollection(): flat_media.extend( media.GetFlatMedia() )
else: flat_media.append( media )
with ClientGUIDialogs.DialogSetupExport( None, flat_media ) as dlg: dlg.ShowModal()
self.SetFocus()
def _ExportTags( self ):
if len( self._selected_media ) > 0:
services = HG.client_controller.services_manager.GetServices( ( HC.LOCAL_TAG, HC.TAG_REPOSITORY, HC.COMBINED_TAG ) )
service_keys = [ service.GetServiceKey() for service in services ]
service_key = ClientGUIDialogs.SelectServiceKey( service_keys = service_keys )
hashes = self._GetSelectedHashes()
if service_key is not None:
ClientTags.ExportToHTA( self, service_key, hashes )
def _FadeThumbnails( self, thumbnails ):
if len( thumbnails ) == 0:

View File

@ -653,7 +653,7 @@ class Page( wx.SplitterWindow ):
def SetMediaFocus( self ):
self._media_panel.SetFocus()
wx.CallAfter( self._media_panel.SetFocus )
def SetMediaResults( self, media_results ):

View File

@ -1000,16 +1000,16 @@ class PopupMessageDialogPanel( ClientGUIScrolledPanels.ReviewPanelVetoable ):
self.SetSizer( vbox )
self._windows_minimised = []
self._windows_hidden = []
self._MinimiseOtherWindows()
self._HideOtherWindows()
self._message_pubbed = False
self._update_job = HG.client_controller.CallRepeatingWXSafe( self, 0.5, 0.25, self.REPEATINGUpdate )
def _MinimiseOtherWindows( self ):
def _HideOtherWindows( self ):
for tlw in wx.GetTopLevelWindows():
@ -1035,9 +1035,9 @@ class PopupMessageDialogPanel( ClientGUIScrolledPanels.ReviewPanelVetoable ):
continue
tlw.Iconize()
tlw.Hide()
self._windows_minimised.append( tlw )
self._windows_hidden.append( tlw )
@ -1055,12 +1055,12 @@ class PopupMessageDialogPanel( ClientGUIScrolledPanels.ReviewPanelVetoable ):
def _RestoreOtherWindows( self ):
for tlw in self._windows_minimised:
for tlw in self._windows_hidden:
tlw.Restore()
tlw.Show()
self._windows_minimised = []
self._windows_hidden = []
def _Update( self ):

View File

@ -211,6 +211,97 @@ class EditChooseMultiple( ClientGUIScrolledPanels.EditPanel ):
return datas
class EditCookiePanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, name, value, domain, path, expires ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._name = wx.TextCtrl( self )
self._value = wx.TextCtrl( self )
self._domain = wx.TextCtrl( self )
self._path = wx.TextCtrl( self )
expires_panel = ClientGUICommon.StaticBox( self, 'expires' )
self._expires_st = ClientGUICommon.BetterStaticText( expires_panel )
self._expires_st_utc = ClientGUICommon.BetterStaticText( expires_panel )
self._expires_time_delta = ClientGUITime.TimeDeltaButton( expires_panel, min = 1200, days = True, hours = True, minutes = True )
#
self._name.SetValue( name )
self._value.SetValue( value )
self._domain.SetValue( domain )
self._path.SetValue( path )
self._expires = expires
self._expires_time_delta.SetValue( 30 * 86400 )
#
rows = []
rows.append( ( 'Actual expires as UTC Timestamp: ', self._expires_st_utc ) )
rows.append( ( 'Set expires as a delta from now: ', self._expires_time_delta ) )
gridbox = ClientGUICommon.WrapInGrid( expires_panel, rows )
expires_panel.Add( self._expires_st, CC.FLAGS_EXPAND_PERPENDICULAR )
expires_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox = wx.BoxSizer( wx.VERTICAL )
rows = []
rows.append( ( 'name: ', self._name ) )
rows.append( ( 'value: ', self._value ) )
rows.append( ( 'domain: ', self._domain ) )
rows.append( ( 'path: ', self._path ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( expires_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
#
self._UpdateExpiresText()
self._expires_time_delta.Bind( ClientGUITime.EVT_TIME_DELTA, self.EventTimeDelta )
def _UpdateExpiresText( self ):
self._expires_st.SetLabelText( HydrusData.ConvertTimestampToPrettyExpires( self._expires ) )
self._expires_st_utc.SetLabelText( str( self._expires ) )
def EventTimeDelta( self, event ):
time_delta = self._expires_time_delta.GetValue()
expires = HydrusData.GetNow() + time_delta
self._expires = expires
self._UpdateExpiresText()
def GetValue( self ):
name = self._name.GetValue()
value = self._value.GetValue()
domain = self._domain.GetValue()
path = self._path.GetValue()
expires = self._expires
return ( name, value, domain, path, expires )
class EditDomainManagerInfoPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, url_matches, network_contexts_to_custom_header_dicts ):
@ -1147,13 +1238,18 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
class EditNetworkContextPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, network_context ):
def __init__( self, parent, network_context, limited_types = None, allow_default = True ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
if limited_types is None:
limited_types = ( 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 = 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 ):
for ct in limited_types:
self._context_type.Append( CC.network_context_type_string_lookup[ ct ], ct )
@ -1177,6 +1273,11 @@ class EditNetworkContextPanel( ClientGUIScrolledPanels.EditPanel ):
self._context_data_none = wx.CheckBox( self, label = 'No specific data--acts as default.' )
if not allow_default:
self._context_data_none.Hide()
names = HG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION )
for name in names:
@ -1453,7 +1554,7 @@ class EditNetworkContextCustomHeadersPanel( ClientGUIScrolledPanels.EditPanel ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._network_context = ClientGUICommon.NetworkContextButton( self, network_context )
self._network_context = ClientGUICommon.NetworkContextButton( self, network_context, allow_default = False )
self._key = wx.TextCtrl( self )
self._value = wx.TextCtrl( self )
@ -1795,7 +1896,7 @@ class EditServersideService( ClientGUIScrolledPanels.EditPanel ):
ClientGUICommon.StaticBox.__init__( self, parent, 'file repository' )
self._log_uploader_ips = wx.CheckBox( self )
self._max_storage = ClientGUICommon.NoneableSpinCtrl( self, unit = 'MB', multiplier = 1024 * 1024 )
self._max_storage = ClientGUIControls.NoneableBytesControl( self, initial_value = 5 * 1024 * 1024 * 1024 )
#

View File

@ -2780,8 +2780,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
rows.append( ( 'Prefer system FFMPEG: ', self._use_system_ffmpeg ) )
rows.append( ( 'Media zooms: ', self._media_zooms ) )
rows.append( ( 'WINDOWS ONLY: Hide and anchor mouse cursor on slow canvas drags: ', self._anchor_and_hide_canvas_drags ) )
rows.append( ( 'BUGFIX: Load images with PIL: ', self._load_images_with_pil ) )
rows.append( ( 'BUGFIX: Disable OpenCV for gifs: ', self._disable_cv_for_gifs ) )
rows.append( ( 'BUGFIX: Load images with PIL (slower): ', self._load_images_with_pil ) )
rows.append( ( 'BUGFIX: Load gifs with PIL instead of OpenCV (slower, bad transparency): ', self._disable_cv_for_gifs ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
@ -3100,11 +3100,11 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
help_hbox = ClientGUICommon.WrapInText( disk_cache_help_button, disk_panel, 'help for this panel -->', wx.Colour( 0, 0, 255 ) )
self._disk_cache_init_period = ClientGUICommon.NoneableSpinCtrl( disk_panel, 'run disk cache on boot for this long', unit = 's', none_phrase = 'do not run', min = 1, max = 120 )
self._disk_cache_init_period.SetToolTip( 'When the client boots, it can speed up operation by reading the front of the database into memory. This sets the max number of seconds it can spend doing that.' )
self._disk_cache_init_period = ClientGUICommon.NoneableSpinCtrl( disk_panel, unit = 's', none_phrase = 'do not run', min = 1, max = 120 )
self._disk_cache_init_period.SetToolTip( 'When the client boots, it can speed up operation (particularly loading your session pages) by reading the front of its database into memory. This sets the max number of seconds it can spend doing that.' )
self._disk_cache_maintenance_mb = ClientGUICommon.NoneableSpinCtrl( disk_panel, 'disk cache maintenance', unit = 'MB', none_phrase = 'do not keep db cached', min = 32, max = 65536 )
self._disk_cache_maintenance_mb.SetToolTip( 'The client can regularly check the front of its database is cached in memory. This represents how many megabytes it will ensure are cached.' )
self._disk_cache_maintenance = ClientGUIControls.NoneableBytesControl( disk_panel, initial_value = 256 * 1024 * 1024, none_label = 'do not keep db cached' )
self._disk_cache_maintenance.SetToolTip( 'The client can regularly ensure the front of its database is cached in your OS\'s disk cache. This represents how many megabytes it will ensure are cached in memory.' )
#
@ -3163,7 +3163,19 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
#
self._disk_cache_init_period.SetValue( self._new_options.GetNoneableInteger( 'disk_cache_init_period' ) )
self._disk_cache_maintenance_mb.SetValue( self._new_options.GetNoneableInteger( 'disk_cache_maintenance_mb' ) )
disk_cache_maintenance_mb = self._new_options.GetNoneableInteger( 'disk_cache_maintenance_mb' )
if disk_cache_maintenance_mb is None:
disk_cache_maintenance = disk_cache_maintenance_mb
else:
disk_cache_maintenance = disk_cache_maintenance_mb * 1024 * 1024
self._disk_cache_maintenance.SetValue( disk_cache_maintenance )
( thumbnail_width, thumbnail_height ) = HC.options[ 'thumbnail_dimensions' ]
@ -3193,11 +3205,17 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
#
rows = []
rows.append( ( 'run disk cache on boot for this long: ', self._disk_cache_init_period ) )
rows.append( ( 'regularly ensure this much of the db is in OS\'s disk cache: ', self._disk_cache_maintenance ) )
gridbox = ClientGUICommon.WrapInGrid( disk_panel, rows )
vbox = wx.BoxSizer( wx.VERTICAL )
disk_panel.Add( help_hbox, CC.FLAGS_BUTTON_SIZER )
disk_panel.Add( self._disk_cache_init_period, CC.FLAGS_EXPAND_PERPENDICULAR )
disk_panel.Add( self._disk_cache_maintenance_mb, CC.FLAGS_EXPAND_PERPENDICULAR )
disk_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( disk_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -3237,7 +3255,9 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
text += os.linesep
text += 'If you have a lot of memory, you can set a generous potential video buffer to compensate.'
text += os.linesep
text += 'If the video buffer can hold an entire video, it only needs to be rendered once and will loop smoothly.'
text += 'If the video buffer can hold an entire video, it only needs to be rendered once and will play and loop very smoothly.'
text += os.linesep
text += 'PROTIP: Do not go crazy here.'
buffer_panel.Add( wx.StaticText( buffer_panel, label = text ), CC.FLAGS_VCENTER )
@ -3299,7 +3319,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
def _ShowDiskCacheHelp( self ):
message = 'The hydrus database runs best on a drive with fast random access latency. Important and heavy read and write operations can function up to 100 times faster when started raw from an SSD rather than an HDD.'
message = 'The hydrus database runs best on a drive with fast random access latency. Certain important operations can function up to 100 times faster when started raw from an SSD rather than an HDD.'
message += os.linesep * 2
message += 'To get around this, the client populates a pre-boot and ongoing disk cache. By contiguously frontloading the database into memory, the most important functions do not need to wait on your disk for most of their work.'
message += os.linesep * 2
@ -3307,7 +3327,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
message += os.linesep * 2
message += 'If you run the database from an SSD, you can reduce or entirely eliminate these values, as the benefit is not so stark. 2s and 256MB is fine.'
message += os.linesep * 2
message += 'Unless you are testing, do not go crazy with this stuff. You can set 8192MB if you like, but there are diminishing returns.'
message += 'Unless you are testing, do not go crazy with this stuff. You can set 8192MB if you like, but there are diminishing (and potentially negative) returns.'
wx.MessageBox( message )
@ -3354,7 +3374,19 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
def UpdateOptions( self ):
self._new_options.SetNoneableInteger( 'disk_cache_init_period', self._disk_cache_init_period.GetValue() )
self._new_options.SetNoneableInteger( 'disk_cache_maintenance_mb', self._disk_cache_maintenance_mb.GetValue() )
disk_cache_maintenance = self._disk_cache_maintenance.GetValue()
if disk_cache_maintenance is None:
disk_cache_maintenance_mb = disk_cache_maintenance
else:
disk_cache_maintenance_mb = disk_cache_maintenance // ( 1024 * 1024 )
self._new_options.SetNoneableInteger( 'disk_cache_maintenance_mb', disk_cache_maintenance_mb )
new_thumbnail_dimensions = [ self._thumbnail_width.GetValue(), self._thumbnail_height.GetValue() ]

View File

@ -2,6 +2,7 @@ import ClientConstants as CC
import ClientData
import ClientDefaults
import ClientGUICommon
import ClientGUIControls
import ClientGUIDialogs
import ClientGUIFrames
import ClientGUIListCtrl
@ -15,6 +16,7 @@ import ClientNetworking
import ClientTags
import ClientThreading
import collections
import cookielib
import HydrusConstants as HC
import HydrusData
import HydrusGlobals as HG
@ -565,7 +567,9 @@ class ReviewAllBandwidthPanel( ClientGUIScrolledPanels.ReviewPanel ):
for network_context in self._bandwidths.GetData( only_selected = True ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self._controller.GetGUI(), 'review bandwidth for ' + network_context.ToUnicode() )
parent = self.GetTopLevelParent().GetParent()
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( parent, 'review bandwidth for ' + network_context.ToUnicode() )
panel = ReviewNetworkContextBandwidthPanel( frame, self._controller, network_context )
@ -992,6 +996,325 @@ class ReviewServicesPanel( ClientGUIScrolledPanels.ReviewPanel ):
self._InitialiseServices()
class ReviewNetworkSessionsPanel( ClientGUIScrolledPanels.ReviewPanel ):
def __init__( self, parent, session_manager ):
ClientGUIScrolledPanels.ReviewPanel.__init__( self, parent )
self._session_manager = session_manager
listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
self._listctrl = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, 'review_network_sessions', 32, 34, [ ( 'network context', -1 ), ( 'cookies', 9 ), ( 'expires', 28 ) ], self._ConvertNetworkContextToListCtrlTuple, delete_key_callback = self._Clear, activation_callback = self._Review )
self._listctrl.Sort()
listctrl_panel.SetListCtrl( self._listctrl )
listctrl_panel.AddButton( 'create new', self._Add )
listctrl_panel.AddButton( 'review', self._Review, enabled_only_on_selection = True )
listctrl_panel.AddButton( 'clear', self._Clear, enabled_only_on_selection = True )
listctrl_panel.AddSeparator()
listctrl_panel.AddButton( 'refresh', self._Update )
#
self._Update()
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
def _Add( self ):
with ClientGUITopLevelWindows.DialogEdit( self, 'enter new network context' ) as dlg:
network_context = ClientNetworking.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, 'example.com' )
panel = ClientGUIScrolledPanelsEdit.EditNetworkContextPanel( dlg, network_context, limited_types = ( CC.NETWORK_CONTEXT_DOMAIN, CC.NETWORK_CONTEXT_HYDRUS ), allow_default = False )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
network_context = panel.GetValue()
self._AddNetworkContext( network_context )
self._Update()
def _AddNetworkContext( self, network_context ):
# this establishes a bare session
self._session_manager.GetSession( network_context )
def _Clear( self ):
for network_context in self._listctrl.GetData( only_selected = True ):
self._session_manager.ClearSession( network_context )
self._AddNetworkContext( network_context )
self._Update()
def _ConvertNetworkContextToListCtrlTuple( self, network_context ):
session = self._session_manager.GetSession( network_context )
pretty_network_context = network_context.ToUnicode()
number_of_cookies = len( session.cookies )
pretty_number_of_cookies = HydrusData.ConvertIntToPrettyString( number_of_cookies )
expires_numbers = [ c.expires for c in session.cookies if c.expires is not None ]
if len( expires_numbers ) == 0:
if number_of_cookies > 0:
expiry = 0
pretty_expiry = 'session'
else:
expiry = -1
pretty_expiry = ''
else:
expiry = max( expires_numbers )
pretty_expiry = HydrusData.ConvertTimestampToPrettyExpires( expiry )
display_tuple = ( pretty_network_context, pretty_number_of_cookies, pretty_expiry )
sort_tuple = ( pretty_network_context, number_of_cookies, expiry )
return ( display_tuple, sort_tuple )
def _Review( self ):
for network_context in self._listctrl.GetData( only_selected = True ):
parent = self.GetTopLevelParent().GetParent()
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( parent, 'review session for ' + network_context.ToUnicode() )
panel = ReviewNetworkSessionPanel( frame, self._session_manager, network_context )
frame.SetPanel( panel )
def _Update( self ):
network_contexts = [ network_context for network_context in self._session_manager.GetNetworkContexts() if network_context.context_type in ( CC.NETWORK_CONTEXT_DOMAIN, CC.NETWORK_CONTEXT_HYDRUS ) ]
self._listctrl.SetData( network_contexts )
class ReviewNetworkSessionPanel( ClientGUIScrolledPanels.ReviewPanel ):
def __init__( self, parent, session_manager, network_context ):
ClientGUIScrolledPanels.ReviewPanel.__init__( self, parent )
self._session_manager = session_manager
self._network_context = network_context
self._session = self._session_manager.GetSession( self._network_context )
self._description = ClientGUICommon.BetterStaticText( self, network_context.ToUnicode() )
listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
self._listctrl = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, 'review_network_session', 8, 18, [ ( 'name', -1 ), ( 'value', 32 ), ( 'domain', 20 ), ( 'path', 8 ), ( 'expires', 28 ) ], self._ConvertCookieToListCtrlTuple, delete_key_callback = self._Delete, activation_callback = self._Edit )
self._listctrl.Sort()
listctrl_panel.SetListCtrl( self._listctrl )
listctrl_panel.AddButton( 'add', self._Add )
listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
listctrl_panel.AddButton( 'delete', self._Delete, enabled_only_on_selection = True )
listctrl_panel.AddSeparator()
listctrl_panel.AddButton( 'refresh', self._Update )
#
self._Update()
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._description, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
def _Add( self ):
with ClientGUITopLevelWindows.DialogEdit( self, 'edit cookie' ) as dlg:
name = 'name'
value = '123'
if self._network_context.context_type == CC.NETWORK_CONTEXT_DOMAIN:
domain = '.' + self._network_context.context_data
else:
domain = 'service domain'
path = '/'
expires = HydrusData.GetNow() + 30 * 86400
panel = ClientGUIScrolledPanelsEdit.EditCookiePanel( dlg, name, value, domain, path, expires )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
( name, value, domain, path, expires ) = panel.GetValue()
self._SetCookie( name, value, domain, path, expires )
self._Update()
def _ConvertCookieToListCtrlTuple( self, cookie ):
name = cookie.name
pretty_name = name
value = cookie.value
pretty_value = value
domain = cookie.domain
pretty_domain = domain
path = cookie.path
pretty_path = path
expiry = cookie.expires
if expiry is None:
expiry = -1
pretty_expiry = 'session'
else:
pretty_expiry = HydrusData.ConvertTimestampToPrettyExpires( expiry )
display_tuple = ( pretty_name, pretty_value, pretty_domain, pretty_path, pretty_expiry )
sort_tuple = ( name, value, domain, path, expiry )
return ( display_tuple, sort_tuple )
def _Delete( self ):
with ClientGUIDialogs.DialogYesNo( self, 'Delete all selected cookies?' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
for cookie in self._listctrl.GetData( only_selected = True ):
domain = cookie.domain
path = cookie.path
name = cookie.name
self._session.cookies.clear( domain, path, name )
self._Update()
def _Edit( self ):
for cookie in self._listctrl.GetData( only_selected = True ):
with ClientGUITopLevelWindows.DialogEdit( self, 'edit cookie' ) as dlg:
name = cookie.name
value = cookie.value
domain = cookie.domain
path = cookie.path
expires = cookie.expires
panel = ClientGUIScrolledPanelsEdit.EditCookiePanel( dlg, name, value, domain, path, expires )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
( name, value, domain, path, expires ) = panel.GetValue()
self._SetCookie( name, value, domain, path, expires )
else:
break
self._Update()
def _SetCookie( self, name, value, domain, path, expires ):
version = 0
port = None
port_specified = False
domain_specified = True
domain_initial_dot = domain.startswith( '.' )
path_specified = True
secure = False
discard = False
comment = None
comment_url = None
rest = {}
cookie = cookielib.Cookie( version, name, value, port, port_specified, domain, domain_specified, domain_initial_dot, path, path_specified, secure, expires, discard, comment, comment_url, rest )
self._session.cookies.set_cookie( cookie )
def _Update( self ):
self._session = self._session_manager.GetSession( self._network_context )
cookies = list( self._session.cookies )
self._listctrl.SetData( cookies )
class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ):
RESIZED_RATIO = 0.012

View File

@ -1,6 +1,7 @@
import ClientData
import ClientGUICommon
import HydrusConstants as HC
import HydrusData
import HydrusGlobals as HG
import wx
@ -74,17 +75,49 @@ class ShortcutsHandler( object ):
shortcut_processed = True
if HG.shortcut_report_mode:
message = 'Shortcut "' + shortcut.ToString() + '" matched to command "' + command.ToString() + '" on ' + repr( self._parent ) + '.'
if command_processed:
message += ' It was processed.'
else:
message += ' It was not processed.'
HydrusData.ShowText( message )
return shortcut_processed
def EventCharHook( self, event ):
if IShouldCatchCharHook( self._parent ):
shortcut = ClientData.ConvertKeyEventToShortcut( event )
if shortcut is not None:
shortcut = ClientData.ConvertKeyEventToShortcut( event )
if HG.shortcut_report_mode:
message = 'Key shortcut "' + shortcut.ToString() + '" passing through ' + repr( self._parent ) + '.'
if IShouldCatchCharHook( self._parent ):
message += ' I am in a state to catch it.'
else:
message += ' I am not in a state to catch it.'
HydrusData.ShowText( message )
if shortcut is not None:
if IShouldCatchCharHook( self._parent ):
shortcut_processed = self._ProcessShortcut( shortcut )

View File

@ -36,7 +36,10 @@ def EfficientlyResizeNumpyImage( numpy_image, ( target_x, target_y ) ):
( im_y, im_x, depth ) = numpy_image.shape
if target_x >= im_x and target_y >= im_y: return numpy_image
if target_x >= im_x and target_y >= im_y:
return numpy_image
# this seems to slow things down a lot, at least for cv!
#if im_x > 2 * target_x and im_y > 2 * target_y: result = cv2.resize( numpy_image, ( 2 * target_x, 2 * target_y ), interpolation = cv2.INTER_NEAREST )
@ -47,7 +50,10 @@ def EfficientlyThumbnailNumpyImage( numpy_image, ( target_x, target_y ) ):
( im_y, im_x, depth ) = numpy_image.shape
if target_x >= im_x and target_y >= im_y: return numpy_image
if target_x >= im_x and target_y >= im_y:
return numpy_image
( target_x, target_y ) = HydrusImageHandling.GetThumbnailResolution( ( im_x, im_y ), ( target_x, target_y ) )

View File

@ -4259,6 +4259,17 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
return HydrusData.TimeHasPassed( self._no_work_until )
def _ShowHitPeriodicFileLimitMessage( self, query_text ):
message = 'When syncing, the query "' + query_text + '" for subscription "' + self._name + '" hit its periodic file limit!'
message += os.linesep * 2
message += 'This may be because the query has not run in a while--so the backlog of files has built up--or that the site has changed how it presents file urls on its gallery pages (and so the subscription thinks it is seeing new files when it truly is not).'
message += os.linesep * 2
message += 'If the former is true, you might want to fill in the gap with a manual download page, but if the latter is true, the maintainer for the download parser (hydrus dev or whoever), would be interested in knowing this information so they can roll out a fix.'
HydrusData.ShowText( message )
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
@ -4678,6 +4689,8 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
if self._periodic_file_limit is not None and total_new_urls + 1 > self._periodic_file_limit:
self._ShowHitPeriodicFileLimitMessage( query_text )
break
@ -4768,6 +4781,8 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
if self._periodic_file_limit is not None and total_new_urls + 1 > self._periodic_file_limit:
self._ShowHitPeriodicFileLimitMessage( query_text )
keep_checking = False
break

View File

@ -1974,6 +1974,23 @@ class NetworkSessionManager( HydrusSerialisable.SerialisableBase ):
self._network_contexts_to_session_timeouts = {}
def _CleanSessionCookies( self, network_context, session ):
if network_context not in self._network_contexts_to_session_timeouts:
self._network_contexts_to_session_timeouts[ network_context ] = 0
if HydrusData.TimeHasPassed( self._network_contexts_to_session_timeouts[ network_context ] ):
session.cookies.clear_session_cookies()
self._network_contexts_to_session_timeouts[ network_context ] = HydrusData.GetNow() + self.SESSION_TIMEOUT
session.cookies.clear_expired_cookies()
def _GenerateSession( self, network_context ):
session = requests.Session()
@ -2026,6 +2043,14 @@ class NetworkSessionManager( HydrusSerialisable.SerialisableBase ):
def GetNetworkContexts( self ):
with self._lock:
return self._network_contexts_to_sessions.keys()
def GetSession( self, network_context ):
with self._lock:
@ -2047,17 +2072,7 @@ class NetworkSessionManager( HydrusSerialisable.SerialisableBase ):
#
if network_context not in self._network_contexts_to_session_timeouts:
self._network_contexts_to_session_timeouts[ network_context ] = 0
if HydrusData.TimeHasPassed( self._network_contexts_to_session_timeouts[ network_context ] ):
session.cookies.clear_session_cookies()
self._network_contexts_to_session_timeouts[ network_context ] = HydrusData.GetNow() + self.SESSION_TIMEOUT
self._CleanSessionCookies( network_context, session )
#

View File

@ -130,9 +130,18 @@ class NetworkLoginManager( HydrusSerialisable.SerialisableBase ):
service = services_manager.GetService( service_key )
if not service.IsFunctional( ignore_account = True ):
try:
raise HydrusExceptions.LoginException( 'Service has had a recent error or is otherwise not functional! You might like to try refreshing its account in \'review services\'.' )
service.CheckFunctional( ignore_account = True )
except Exception as e:
message = 'Service has had a recent error or is otherwise not functional! Specific error was:'
message += os.linesep * 2
message += HydrusData.ToUnicode( e )
message += 'You might like to try refreshing its account in \'review services\'.'
raise HydrusExceptions.LoginException( message )

View File

@ -148,9 +148,9 @@ class Service( object ):
def __hash__( self ): return self._service_key.__hash__()
def _GetFunctionalStatus( self ):
def _CheckFunctional( self ):
return ( True, 'service is functional' )
pass
def _GetSerialisableDictionary( self ):
@ -170,6 +170,14 @@ class Service( object ):
HG.client_controller.pub( 'service_updated', self )
def CheckFunctional( self ):
with self._lock:
self._CheckFunctional()
def Duplicate( self ):
with self._lock:
@ -220,15 +228,15 @@ class Service( object ):
with self._lock:
( functional, status ) = self._GetFunctionalStatus()
if not functional:
try:
return 'service not functional: ' + status
self._CheckFunctional()
else:
return 'service is functional'
return status
except Exception as e:
return HydrusData.ToUnicode( e )
@ -242,9 +250,16 @@ class Service( object ):
with self._lock:
( functional, status ) = self._GetFunctionalStatus()
return functional
try:
self._CheckFunctional()
return True
except:
return False
@ -273,14 +288,14 @@ class Service( object ):
class ServiceLocalBooru( Service ):
def _GetFunctionalStatus( self ):
def _CheckFunctional( self ):
if not self._bandwidth_rules.CanStartRequest( self._bandwidth_tracker ):
return ( False, 'bandwidth exceeded' )
raise HydrusExceptions.BandwidthException( 'bandwidth exceeded' )
return Service._GetFunctionalStatus( self )
Service._CheckFunctional( self )
def _GetSerialisableDictionary( self ):
@ -490,11 +505,11 @@ class ServiceRemote( Service ):
return 3600 * 4
def _GetFunctionalStatus( self ):
def _CheckFunctional( self ):
if not HydrusData.TimeHasPassed( self._no_requests_until ):
return ( False, self._no_requests_reason + ' - next request ' + HydrusData.ConvertTimestampToPrettyPending( self._no_requests_until ) )
raise HydrusExceptions.PermissionException( self._no_requests_reason + ' - next request ' + HydrusData.ConvertTimestampToPrettyPending( self._no_requests_until ) )
example_nj = ClientNetworking.NetworkJobHydrus( self._service_key, 'GET', self._GetBaseURL() )
@ -503,10 +518,10 @@ class ServiceRemote( Service ):
if not can_start:
return ( False, 'bandwidth exceeded' )
raise HydrusExceptions.BandwidthException( 'bandwidth exceeded' )
return Service._GetFunctionalStatus( self )
Service._CheckFunctional( self )
def _GetSerialisableDictionary( self ):
@ -619,19 +634,19 @@ class ServiceRestricted( ServiceRemote ):
def _GetFunctionalStatus( self, ignore_account = False ):
def _CheckFunctional( self, ignore_account = False ):
if not self._credentials.HasAccessKey():
return ( False, 'this service has no access key set' )
raise HydrusExceptions.PermissionException( 'this service has no access key set' )
if not ignore_account and not self._account.IsFunctional():
if not ignore_account:
return ( False, 'account problem: ' + self._account.GetStatusString() )
self._account.CheckFunctional()
return ServiceRemote._GetFunctionalStatus( self )
ServiceRemote._CheckFunctional( self )
def _GetSerialisableDictionary( self ):
@ -652,6 +667,14 @@ class ServiceRestricted( ServiceRemote ):
self._next_account_sync = dictionary[ 'next_account_sync' ]
def CheckFunctional( self, ignore_account = False ):
with self._lock:
self._CheckFunctional( ignore_account = ignore_account )
def GetAccount( self ):
with self._lock:
@ -687,9 +710,16 @@ class ServiceRestricted( ServiceRemote ):
with self._lock:
( functional, status ) = self._GetFunctionalStatus( ignore_account = ignore_account )
return functional
try:
self._CheckFunctional( ignore_account = ignore_account )
return True
except:
return False
@ -937,9 +967,16 @@ class ServiceRepository( ServiceRestricted ):
return False
( result, reason ) = self._GetFunctionalStatus()
try:
self._CheckFunctional()
except:
return False
return result
return True
def _GetSerialisableDictionary( self ):
@ -1147,16 +1184,28 @@ class ServiceRepository( ServiceRestricted ):
if update_network_string_hash != update_hash:
# this is the weird update problem, seems to be network related
# throwing a whole hullabaloo about it only caused problems, as the real fix was 'unpause it, try again'
with self._lock:
self._paused = True
self._DealWithFundamentalNetworkError()
self._DelayFutureRequests( 'had an unusual update response' )
message = 'Update ' + update_hash.encode( 'hex' ) + ' downloaded from the ' + self._name + ' repository had hash ' + update_network_string_hash.encode( 'hex' ) + '! This is a serious error!'
filename = 'should be ' + update_network_string_hash.encode( 'hex' ) + '.wew'
path = os.path.join( HG.client_controller.db_dir, filename )
with open( path, 'wb' ) as f:
f.write( update_network_string )
message = 'Update ' + update_hash.encode( 'hex' ) + ' downloaded from the ' + self._name + ' repository had hash ' + update_network_string_hash.encode( 'hex' ) + '!'
message += os.linesep * 2
message += 'The repository has been paused for now. Please look into what could be wrong and report this to the hydrus dev.'
message += 'This is an unusual network error that hydrus dev is trying to pin down. The bad file has been written to ' + path + '--please inform hydrus dev of what has happened and send him that file!'
message += os.linesep * 2
message += 'Your repository will try again later, which usually fixes this problem.'
HydrusData.ShowText( message )

View File

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

View File

@ -223,6 +223,31 @@ def GetFileInfo( path, mime = None ):
duration = int( duration_in_s * 1000 )
if width is not None and width < 0:
width *= -1
if height is not None and height < 0:
width *= -1
if duration is not None and duration < 0:
duration *= -1
if num_frames is not None and num_frames < 0:
num_frames *= -1
if num_words is not None and num_words < 0:
num_words *= -1
return ( size, mime, width, height, duration, num_frames, num_words )
def GetHashFromPath( path ):

View File

@ -15,6 +15,7 @@ callto_report_mode = False
db_report_mode = False
db_profile_mode = False
gui_report_mode = False
shortcut_report_mode = False
hover_window_report_mode = False
menu_profile_mode = False
network_report_mode = False

View File

@ -356,6 +356,34 @@ class Account( object ):
return self.__repr__()
def _CheckFunctional( self ):
if self._created == 0:
raise HydrusExceptions.PermissionException( 'account is unsynced' )
if self._account_type.HasPermission( HC.CONTENT_TYPE_SERVICES, HC.PERMISSION_ACTION_OVERRULE ):
return # admins can do anything
if self._IsBanned():
raise HydrusExceptions.PermissionException( 'This account is banned: ' + self._GetBannedString() )
if self._IsExpired():
raise HydrusExceptions.PermissionException( 'This account is expired: ' + self._GetExpiresString() )
if not self._account_type.BandwidthOK( self._bandwidth_tracker ):
raise HydrusExceptions.PermissionException( 'account has exceeded bandwidth' )
def _GetBannedString( self ):
if self._banned_info is None:
@ -375,36 +403,6 @@ class Account( object ):
return HydrusData.ConvertTimestampToPrettyExpires( self._expires )
def _GetFunctionalStatus( self ):
if self._created == 0:
return ( False, 'account is unsynced' )
if self._account_type.HasPermission( HC.CONTENT_TYPE_SERVICES, HC.PERMISSION_ACTION_OVERRULE ):
return ( True, 'admin: can do anything' )
if self._IsBanned():
return ( False, self._GetBannedString() )
if self._IsExpired():
return ( False, self._GetExpiresString() )
if not self._account_type.BandwidthOK( self._bandwidth_tracker ):
return ( False, 'account has exceeded bandwidth' )
return ( True, 'account is functional' )
def _IsBanned( self ):
if self._banned_info is None:
@ -464,22 +462,7 @@ class Account( object ):
with self._lock:
if self._IsBanned():
banned_string = self._GetBannedString()
raise HydrusExceptions.PermissionException( 'This account is banned: ' + banned_string )
if self._IsExpired():
raise HydrusExceptions.PermissionException( 'This account has expired.' )
if not self._account_type.BandwidthOK( self._bandwidth_tracker ):
raise HydrusExceptions.PermissionException( 'This account has no remaining bandwidth.' )
self._CheckFunctional()
@ -545,15 +528,15 @@ class Account( object ):
with self._lock:
( functional, status ) = self._GetFunctionalStatus()
if functional:
try:
return status
self._CheckFunctional()
else:
return 'account is functional'
return 'account not functional: ' + status
except Exception as e:
return HydrusData.ToUnicode( e )
@ -578,9 +561,16 @@ class Account( object ):
with self._lock:
( functional, status ) = self._GetFunctionalStatus()
return functional
try:
self._CheckFunctional()
return True
except:
return False