Version 108

This commit is contained in:
Hydrus 2014-03-19 15:27:07 -05:00
parent 3339e5e0be
commit c14d8f4008
11 changed files with 708 additions and 532 deletions

View File

@ -8,6 +8,19 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 108</h3></li>
<ul>
<li>added 'database->delete orphan files' for manual firing of this maintenance routine</li>
<li>improved redirect location parsing--should fix the booru 'Could not connect to None!' errors</li>
<li>fixed 4chan thread downloader's erroneous 'still working' warning when trying to close while paused</li>
<li>added multiple additions to manage tag parents dialog</li>
<li>added multiple additions to manage tag siblings dialog</li>
<li>generic improvements to both dialogs</li>
<li>manage tags dialog now shows all the selection's tags combined, not just the tag intersection</li>
<li>multiple improvements to manage tags dialog</li>
<li>reworked some tagsbox classes to be more flexible</li>
<li>improved dialogsetupexport's use of tagsbox</li>
</ul>
<li><h3>version 107</h3></li>
<ul>
<li>converted 'namespace blacklists' to more general 'tag censorship'</li>

View File

@ -1538,6 +1538,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
c.execute( 'REPLACE INTO shutdown_timestamps ( shutdown_type, timestamp ) VALUES ( ?, ? );', ( CC.SHUTDOWN_TIMESTAMP_DELETE_ORPHANS, HC.GetNow() ) )
HC.ShowText( 'orphaned files deleted' )
def _DeletePending( self, c, service_identifier ):

View File

@ -570,6 +570,16 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
HC.pubsub.pub( 'notify_new_undo' )
def _DeleteOrphans( self ):
message = 'This will iterate through the client\'s file store, deleting anything that is no longer needed. It happens automatically every few days, but you can force it here. If you have a lot of files, it will take a few minutes. A popup message will appear when it is done.'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_YES: HC.app.Write( 'delete_orphans' )
def _DeletePending( self, service_identifier ):
with ClientGUIDialogs.DialogYesNo( self, 'Are you sure you want to delete the pending data for ' + service_identifier.GetName() + '?' ) as dlg:
@ -781,6 +791,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'restore_database' ), p( 'Restore Database Backup' ), p( 'Restore the database from an external location.' ) )
menu.AppendSeparator()
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'vacuum_db' ), p( '&Vacuum' ), p( 'Rebuild the Database.' ) )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete_orphans' ), p( '&Delete Orphan Files' ), p( 'Go through the client\'s file store, deleting any files that are no longer needed.' ) )
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'regenerate_thumbnails' ), p( '&Regenerate All Thumbnails' ), p( 'Delete all thumbnails and regenerate from original files.' ) )
menu.AppendSeparator()
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'clear_caches' ), p( '&Clear Caches' ), p( 'Fully clear the fullscreen, preview and thumbnail caches.' ) )
@ -1628,7 +1639,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
def _VacuumDatabase( self ):
message = 'This will rebuild the database, rewriting all indices and tables to be contiguous, optimising most operations. If you have a large database, it will take some time. A popup message will appear when it is done.'
message = 'This will rebuild the database, rewriting all indices and tables to be contiguous and optimising most operations. It happens automatically every few days, but you can force it here. If you have a large database, it will take a few minutes. A popup message will appear when it is done.'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
@ -1783,6 +1794,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
HC.pubsub.pub( 'notify_new_sessions' )
elif command == 'delete_orphans': self._DeleteOrphans()
elif command == 'delete_pending': self._DeletePending( data )
elif command == 'exit': self.EventExit( event )
elif command == 'fetch_ip': self._FetchIP( data )

View File

@ -4137,34 +4137,29 @@ class TagsBoxColourOptions( TagsBox ):
class TagsBoxCPP( TagsBox ):
class TagsBoxCounts( TagsBox ):
has_counts = True
def __init__( self, parent, page_key ):
def __init__( self, parent ):
TagsBox.__init__( self, parent, min_height = 200 )
self._sort = HC.options[ 'default_tag_sort' ]
self._page_key = page_key
self._tag_service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER
self._last_media = None
self._tag_service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER
self._current_tags_to_count = collections.Counter()
self._deleted_tags_to_count = collections.Counter()
self._pending_tags_to_count = collections.Counter()
self._petitioned_tags_to_count = collections.Counter()
HC.pubsub.sub( self, 'SetTagsByMedia', 'new_tags_selection' )
HC.pubsub.sub( self, 'ChangeTagRepository', 'change_tag_repository' )
def _Activate( self, s, term ):
predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', term ), None )
HC.pubsub.pub( 'add_predicate', self._page_key, predicate )
self._show_current = True
self._show_deleted = False
self._show_pending = True
self._show_petitioned = True
def _GetAllTagsForClipboard( self, with_counts = False ):
@ -4177,11 +4172,12 @@ class TagsBoxCPP( TagsBox ):
siblings_manager = HC.app.GetManager( 'tag_siblings' )
all_current = ( tag for tag in self._current_tags_to_count if self._current_tags_to_count[ tag ] > 0 )
all_pending = ( tag for tag in self._pending_tags_to_count if self._pending_tags_to_count[ tag ] > 0 )
all_petitioned = ( tag for tag in self._petitioned_tags_to_count if self._petitioned_tags_to_count[ tag ] > 0 )
all_tags = set()
all_tags = set( itertools.chain( all_current, all_pending, all_petitioned ) )
if self._show_current: all_tags.update( ( tag for ( tag, count ) in self._current_tags_to_count.items() if count > 0 ) )
if self._show_deleted: all_tags.update( ( tag for ( tag, count ) in self._deleted_tags_to_count.items() if count > 0 ) )
if self._show_pending: all_tags.update( ( tag for ( tag, count ) in self._pending_tags_to_count.items() if count > 0 ) )
if self._show_petitioned: all_tags.update( ( tag for ( tag, count ) in self._petitioned_tags_to_count.items() if count > 0 ) )
self._ordered_strings = []
self._strings_to_terms = {}
@ -4190,9 +4186,10 @@ class TagsBoxCPP( TagsBox ):
tag_string = tag
if tag in self._current_tags_to_count: tag_string += ' (' + HC.ConvertIntToPrettyString( self._current_tags_to_count[ tag ] ) + ')'
if tag in self._pending_tags_to_count: tag_string += ' (+' + HC.ConvertIntToPrettyString( self._pending_tags_to_count[ tag ] ) + ')'
if tag in self._petitioned_tags_to_count: tag_string += ' (-' + HC.ConvertIntToPrettyString( self._petitioned_tags_to_count[ tag ] ) + ')'
if self._show_current and tag in self._current_tags_to_count: tag_string += ' (' + HC.ConvertIntToPrettyString( self._current_tags_to_count[ tag ] ) + ')'
if self._show_pending and tag in self._pending_tags_to_count: tag_string += ' (+' + HC.ConvertIntToPrettyString( self._pending_tags_to_count[ tag ] ) + ')'
if self._show_petitioned and tag in self._petitioned_tags_to_count: tag_string += ' (-' + HC.ConvertIntToPrettyString( self._petitioned_tags_to_count[ tag ] ) + ')'
if self._show_deleted and tag in self._deleted_tags_to_count: tag_string += ' (X' + HC.ConvertIntToPrettyString( self._deleted_tags_to_count[ tag ] ) + ')'
sibling = siblings_manager.GetSibling( tag )
@ -4225,14 +4222,11 @@ class TagsBoxCPP( TagsBox ):
self._TextsHaveChanged()
def ChangeTagRepository( self, page_key, service_identifier ):
def ChangeTagRepository( self, service_identifier ):
if page_key == self._page_key:
self._tag_service_identifier = service_identifier
if self._last_media is not None: self.SetTagsByMedia( self._page_key, self._last_media )
self._tag_service_identifier = service_identifier
if self._last_media is not None: self.SetTagsByMedia( self._last_media )
def SetSort( self, sort ):
@ -4242,70 +4236,120 @@ class TagsBoxCPP( TagsBox ):
self._SortTags()
def SetTags( self, current_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ):
def SetTags( self, current_tags_to_count = collections.Counter(), deleted_tags_to_count = collections.Counter(), pending_tags_to_count = collections.Counter(), petitioned_tags_to_count = collections.Counter() ):
siblings_manager = HC.app.GetManager( 'tag_siblings' )
current_tags_to_count = siblings_manager.CollapseTagsToCount( current_tags_to_count )
self._current_tags_to_count = current_tags_to_count
self._deleted_tags_to_count = deleted_tags_to_count
self._pending_tags_to_count = pending_tags_to_count
self._petitioned_tags_to_count = petitioned_tags_to_count
self._RecalcStrings()
def SetTagsByMedia( self, page_key, media, force_reload = False ):
def SetShow( self, type, value ):
if page_key == self._page_key:
media = set( media )
if force_reload:
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = CC.GetMediasTagCount( media, self._tag_service_identifier )
self.SetTags( current_tags_to_count, pending_tags_to_count, petitioned_tags_to_count )
else:
if self._last_media is None: ( removees, adds ) = ( set(), media )
else:
removees = self._last_media.difference( media )
adds = media.difference( self._last_media )
siblings_manager = HC.app.GetManager( 'tag_siblings' )
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = CC.GetMediasTagCount( removees, self._tag_service_identifier )
current_tags_to_count = siblings_manager.CollapseTagsToCount( current_tags_to_count )
self._current_tags_to_count.subtract( current_tags_to_count )
self._pending_tags_to_count.subtract( pending_tags_to_count )
self._petitioned_tags_to_count.subtract( petitioned_tags_to_count )
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = CC.GetMediasTagCount( adds, self._tag_service_identifier )
current_tags_to_count = siblings_manager.CollapseTagsToCount( current_tags_to_count )
self._current_tags_to_count.update( current_tags_to_count )
self._pending_tags_to_count.update( pending_tags_to_count )
self._petitioned_tags_to_count.update( petitioned_tags_to_count )
self._last_media = media
self._RecalcStrings()
if type == 'current': self._show_current = value
elif type == 'deleted': self._show_deleted = value
elif type == 'pending': self._show_pending = value
elif type == 'petitioned': self._show_petitioned = value
self._RecalcStrings()
class TagsBoxCPPWithSorter( StaticBox ):
def SetTagsByMedia( self, media, force_reload = False ):
media = set( media )
if force_reload:
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = CC.GetMediasTagCount( media, self._tag_service_identifier )
self.SetTags( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count )
else:
if self._last_media is None: ( removees, adds ) = ( set(), media )
else:
removees = self._last_media.difference( media )
adds = media.difference( self._last_media )
siblings_manager = HC.app.GetManager( 'tag_siblings' )
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = CC.GetMediasTagCount( removees, self._tag_service_identifier )
current_tags_to_count = siblings_manager.CollapseTagsToCount( current_tags_to_count )
self._current_tags_to_count.subtract( current_tags_to_count )
self._deleted_tags_to_count.subtract( deleted_tags_to_count )
self._pending_tags_to_count.subtract( pending_tags_to_count )
self._petitioned_tags_to_count.subtract( petitioned_tags_to_count )
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = CC.GetMediasTagCount( adds, self._tag_service_identifier )
current_tags_to_count = siblings_manager.CollapseTagsToCount( current_tags_to_count )
self._current_tags_to_count.update( current_tags_to_count )
self._deleted_tags_to_count.update( deleted_tags_to_count )
self._pending_tags_to_count.update( pending_tags_to_count )
self._petitioned_tags_to_count.update( petitioned_tags_to_count )
self._last_media = media
self._RecalcStrings()
class TagsBoxCPP( TagsBoxCounts ):
def __init__( self, parent, page_key ):
StaticBox.__init__( self, parent, 'selection tags' )
TagsBoxCounts.__init__( self, parent )
self._page_key = page_key
HC.pubsub.sub( self, 'SetTagsByMediaPubsub', 'new_tags_selection' )
HC.pubsub.sub( self, 'ChangeTagRepositoryPubsub', 'change_tag_repository' )
def _Activate( self, s, term ):
predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', term ), None )
HC.pubsub.pub( 'add_predicate', self._page_key, predicate )
def ChangeTagRepositoryPubsub( self, page_key, service_identifier ):
if page_key == self._page_key: self.ChangeTagRepository( service_identifier )
def SetTagsByMediaPubsub( self, page_key, media, force_reload = False ):
if page_key == self._page_key: self.SetTagsByMedia( media, force_reload = force_reload )
class TagsBoxCountsSimple( TagsBoxCounts ):
def __init__( self, parent, callable ):
TagsBoxCounts.__init__( self, parent )
self._callable = callable
def _Activate( self, s, term ): self._callable( term )
class TagsBoxCountsSorter( StaticBox ):
def __init__( self, parent, title ):
StaticBox.__init__( self, parent, title )
self._sorter = wx.Choice( self )
@ -4321,12 +4365,11 @@ class TagsBoxCPPWithSorter( StaticBox ):
self._sorter.Bind( wx.EVT_CHOICE, self.EventSort )
self._tags_box = TagsBoxCPP( self, page_key )
self.AddF( self._sorter, FLAGS_EXPAND_PERPENDICULAR )
self.AddF( self._tags_box, FLAGS_EXPAND_BOTH_WAYS )
def ChangeTagRepository( self, service_identifier ): self._tags_box.ChangeTagRepository( service_identifier )
def EventSort( self, event ):
selection = self._sorter.GetSelection()
@ -4339,6 +4382,15 @@ class TagsBoxCPPWithSorter( StaticBox ):
def SetTagsBox( self, tags_box ):
self._tags_box = tags_box
self.AddF( self._tags_box, FLAGS_EXPAND_BOTH_WAYS )
def SetTagsByMedia( self, media, force_reload = False ): self._tags_box.SetTagsByMedia( media, force_reload = force_reload )
class TagsBoxFlat( TagsBox ):
def __init__( self, parent, removed_callable ):
@ -4394,7 +4446,7 @@ class TagsBoxFlat( TagsBox ):
def AddTag( self, tag, parents ):
def AddTag( self, tag, parents = [] ):
if tag in self._tags: self._tags.discard( tag )
else:
@ -4411,7 +4463,9 @@ class TagsBoxFlat( TagsBox ):
def SetTags( self, tags ):
self._tags = tags
self._tags = set()
for tag in tags: self._tags.add( tag )
self._RecalcTags()

View File

@ -281,6 +281,77 @@ class DialogAdvancedContentUpdate( Dialog ):
self._specific_tag.SetLabel( tag )
class DialogButtonChoice( Dialog ):
def __init__( self, parent, intro, choices ):
def InitialiseControls():
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
self._buttons = []
self._ids_to_data = {}
i = 0
for ( text, data ) in choices:
self._buttons.append( wx.Button( self, label = text, id = i ) )
self._ids_to_data[ i ] = data
i += 1
def PopulateControls():
pass
def ArrangeControls():
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
for button in self._buttons: vbox.AddF( button, FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
Dialog.__init__( self, parent, 'choose what to do', position = 'center' )
InitialiseControls()
PopulateControls()
ArrangeControls()
self.Bind( wx.EVT_BUTTON, self.EventButton )
wx.CallAfter( self._buttons[0].SetFocus )
def EventButton( self, event ):
id = event.GetId()
if id == wx.ID_CANCEL: self.EndModal( wx.ID_CANCEL )
else:
self._data = self._ids_to_data[ id ]
self.EndModal( wx.ID_OK )
def GetData( self ): return self._data
class DialogChooseNewServiceMethod( Dialog ):
def __init__( self, parent ):
@ -4940,7 +5011,12 @@ class DialogSetupExport( Dialog ):
def InitialiseControls():
self._tags_box = ClientGUICommon.TagsBoxCPPWithSorter( self, self._page_key )
self._tags_box = ClientGUICommon.TagsBoxCountsSorter( self, 'files\' tags' )
t = ClientGUICommon.TagsBoxCounts( self._tags_box )
self._tags_box.SetTagsBox( t )
self._tags_box.SetMinSize( ( 220, 300 ) )
self._paths = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'number', 60 ), ( 'mime', 70 ), ( 'expected path', -1 ) ] )
@ -5054,8 +5130,6 @@ class DialogSetupExport( Dialog ):
Dialog.__init__( self, parent, 'setup export' )
self._page_key = os.urandom( 32 )
InitialiseControls()
PopulateControls()
@ -5218,7 +5292,7 @@ class DialogSetupExport( Dialog ):
all_media = [ media for ( ( ordering_index, media ), mime, old_path ) in [ self._paths.GetClientData( index ) for index in indices ] ]
HC.pubsub.pub( 'new_tags_selection', self._page_key, all_media )
self._tags_box.SetTagsByMedia( all_media )
class DialogYesNo( Dialog ):

View File

@ -7,6 +7,7 @@ import ClientConstants as CC
import ClientConstantsMessages
import ClientGUICommon
import ClientGUIDialogs
import ClientGUIMixins
import collections
import HydrusNATPunch
import itertools
@ -5761,19 +5762,19 @@ class DialogManageTagCensorship( ClientGUIDialogs.Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Here you can set which tags or classes of tags you do not want to see.'
intro += os.linesep
intro += "Input ':' for all namespaced tags, and '' for all unnamespaced tags."
intro += os.linesep
intro += 'You may have to refresh your current queries to see any changes.'
intro = "Here you can set which tags or classes of tags you do not want to see. Input something like 'series:' to censor an entire namespace, or ':' for all namespaced tags, and '' for all unnamespaced tags. You may have to refresh your current queries to see any changes."
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
st = wx.StaticText( self, label = intro )
st.Wrap( 350 )
vbox.AddF( st, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tag_services, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
self.SetInitialSize( ( 350, 480 ) )
self.SetInitialSize( ( -1, 480 ) )
ClientGUIDialogs.Dialog.__init__( self, parent, 'tag censorship' )
@ -6038,11 +6039,13 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
self._tag_parents.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
self._tag_parents.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
self._child_text = wx.StaticText( self )
self._parent_text = wx.StaticText( self )
removed_callable = lambda tag: 1
self._child_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetChild, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._parent_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetParent, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._children = ClientGUICommon.TagsBoxFlat( self, removed_callable )
self._parents = ClientGUICommon.TagsBoxFlat( self, removed_callable )
self._child_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddChild, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._parent_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddParent, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAddButton )
@ -6060,15 +6063,17 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
self._tag_parents.SortListItems( 2 )
if tag is not None: self.SetChild( tag )
if tag is not None: self.AddChild( tag )
def ArrangeControls():
text_box = wx.BoxSizer( wx.HORIZONTAL )
intro = 'Files with a tag on the left will also be given the tag on the right.'
text_box.AddF( self._child_text, FLAGS_EXPAND_BOTH_WAYS )
text_box.AddF( self._parent_text, FLAGS_EXPAND_BOTH_WAYS )
tags_box = wx.BoxSizer( wx.HORIZONTAL )
tags_box.AddF( self._children, FLAGS_EXPAND_BOTH_WAYS )
tags_box.AddF( self._parents, FLAGS_EXPAND_BOTH_WAYS )
input_box = wx.BoxSizer( wx.HORIZONTAL )
@ -6077,9 +6082,10 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tag_parents, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._add, FLAGS_LONE_BUTTON )
vbox.AddF( text_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( tags_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( input_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.SetSizer( vbox )
@ -6104,9 +6110,6 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
self._pairs_to_reasons = {}
self._current_parent = None
self._current_child = None
InitialiseControls()
PopulateControls()
@ -6245,7 +6248,7 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
if HydrusTags.LoopInSimpleChildrenToParents( simple_children_to_parents, potential_child, potential_parent ):
wx.MessageBox( 'Adding that pair would create a loop!' )
wx.MessageBox( 'Adding ' + potential_child + '->' + potential_parent + ' would create a loop!' )
return False
@ -6256,10 +6259,34 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
def _SetButtonStatus( self ):
if self._current_parent is None or self._current_child is None: self._add.Disable()
if len( self._children.GetTags() ) == 0 or len( self._parents.GetTags() ) == 0: self._add.Disable()
else: self._add.Enable()
def AddChild( self, tag, parents = [] ):
if tag is not None:
if tag in self._parents.GetTags(): self._parents.AddTag( tag )
self._children.AddTag( tag )
self._SetButtonStatus()
def AddParent( self, tag, parents = [] ):
if tag is not None:
if tag in self._children.GetTags(): self._children.AddTag( tag )
self._parents.AddTag( tag )
self._SetButtonStatus()
def EventActivated( self, event ):
all_selected = self._tag_parents.GetAllSelected()
@ -6276,13 +6303,15 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
def EventAddButton( self, event ):
if self._current_child is not None and self._current_parent is not None:
self._AddPair( self._current_child, self._current_parent )
self.SetChild( None )
self.SetParent( None )
children = self._children.GetTags()
parents = self._parents.GetTags()
for ( child, parent ) in itertools.product( children, parents ): self._AddPair( child, parent )
self._children.SetTags( [] )
self._parents.SetTags( [] )
self._SetButtonStatus()
def EventItemSelected( self, event ):
@ -6325,33 +6354,9 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
return ( self._service_identifier, content_updates )
def SetChild( self, tag, parents = [] ):
if tag is not None and tag == self._current_parent: self.SetParent( None )
self._current_child = tag
if tag is None: self._child_text.SetLabel( '' )
else: self._child_text.SetLabel( tag )
self._SetButtonStatus()
def SetParent( self, tag, parents = [] ):
if tag is not None and tag == self._current_child: self.SetChild( None )
self._current_parent = tag
if tag is None: self._parent_text.SetLabel( '' )
else: self._parent_text.SetLabel( tag )
self._SetButtonStatus()
def SetTagBoxFocus( self ):
if self._current_child is None: self._child_input.SetFocus()
if len( self._children.GetTags() ) == 0: self._child_input.SetFocus()
else: self._parent_input.SetFocus()
@ -6496,10 +6501,12 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
self._old_text = wx.StaticText( self )
self._new_text = wx.StaticText( self )
removed_callable = lambda tags: 1
self._old_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetOld, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._old_siblings = ClientGUICommon.TagsBoxFlat( self, removed_callable )
self._new_sibling = wx.StaticText( self )
self._old_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddOld, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._new_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetNew, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._add = wx.Button( self, label = 'add' )
@ -6518,15 +6525,23 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._tag_siblings.SortListItems( 2 )
if tag is not None: self.SetOld( tag )
if tag is not None: self.AddOld( tag )
def ArrangeControls():
intro = 'Tags on the left will be replaced by those on the right.'
new_sibling_box = wx.BoxSizer( wx.VERTICAL )
new_sibling_box.AddF( ( 10, 10 ), FLAGS_EXPAND_BOTH_WAYS )
new_sibling_box.AddF( self._new_sibling, FLAGS_EXPAND_PERPENDICULAR )
new_sibling_box.AddF( ( 10, 10 ), FLAGS_EXPAND_BOTH_WAYS )
text_box = wx.BoxSizer( wx.HORIZONTAL )
text_box.AddF( self._old_text, FLAGS_EXPAND_BOTH_WAYS )
text_box.AddF( self._new_text, FLAGS_EXPAND_BOTH_WAYS )
text_box.AddF( self._old_siblings, FLAGS_EXPAND_BOTH_WAYS )
text_box.AddF( new_sibling_box, FLAGS_EXPAND_SIZER_BOTH_WAYS )
input_box = wx.BoxSizer( wx.HORIZONTAL )
@ -6535,6 +6550,7 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tag_siblings, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._add, FLAGS_LONE_BUTTON )
vbox.AddF( text_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
@ -6562,7 +6578,6 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
self._pairs_to_reasons = {}
self._current_old = None
self._current_new = None
InitialiseControls()
@ -6571,8 +6586,6 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
ArrangeControls()
if tag is not None: self.SetOld( tag )
def _AddPair( self, old, new ):
@ -6718,7 +6731,7 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
if next_new == potential_old:
wx.MessageBox( 'Adding that pair would create a loop!' )
wx.MessageBox( 'Adding ' + potential_old + '->' + potential_new + ' would create a loop!' )
return False
@ -6730,10 +6743,56 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
def _SetButtonStatus( self ):
if self._current_new is None or self._current_old is None: self._add.Disable()
if self._current_new is None or len( self._old_siblings.GetTags() ) == 0: self._add.Disable()
else: self._add.Enable()
def AddOld( self, old, parents = [] ):
if old is not None:
current_pairs = self._current_statuses_to_pairs[ HC.CURRENT ].union( self._current_statuses_to_pairs[ HC.PENDING ] )
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
# test for ambiguity
while old in current_olds:
olds_to_news = dict( current_pairs )
new = olds_to_news[ old ]
message = 'There already is a relationship set for ' + old + '! It goes to ' + new + '.'
with ClientGUIDialogs.DialogYesNo( self, message, yes_label = 'I want to overwrite it', no_label = 'do nothing' ) as dlg:
if self._service_identifier != HC.LOCAL_TAG_SERVICE_IDENTIFIER:
if dlg.ShowModal() != wx.ID_YES: return
self._AddPair( old, new )
current_pairs = self._current_statuses_to_pairs[ HC.CURRENT ].union( self._current_statuses_to_pairs[ HC.PENDING ] )
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
#
if old is not None:
if old == self._current_new: self.SetNew( None )
self._old_siblings.AddTag( old )
self._SetButtonStatus()
def EventActivated( self, event ):
all_selected = self._tag_siblings.GetAllSelected()
@ -6750,13 +6809,15 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
def EventAddButton( self, event ):
if self._current_old is not None and self._current_new is not None:
if self._current_new is not None and len( self._old_siblings.GetTags() ) > 0:
self._AddPair( self._current_old, self._current_new )
for old in self._old_siblings.GetTags(): self._AddPair( old, self._current_new )
self.SetOld( None )
self._old_siblings.SetTags( [] )
self.SetNew( None )
self._SetButtonStatus()
def EventItemSelected( self, event ):
@ -6803,65 +6864,22 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
def SetNew( self, new, parents = [] ):
if new is not None and new == self._current_old: self.SetOld( None )
if new is None: self._new_sibling.SetLabel( '' )
else:
if new in self._old_siblings.GetTags(): self._old_siblings.AddTag( new )
self._new_sibling.SetLabel( new )
self._current_new = new
if new is None: self._new_text.SetLabel( '' )
else: self._new_text.SetLabel( new )
self._SetButtonStatus()
def SetOld( self, old, parents = [] ):
if old is not None:
current_pairs = self._current_statuses_to_pairs[ HC.CURRENT ].union( self._current_statuses_to_pairs[ HC.PENDING ] )
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
# test for ambiguity
while old in current_olds:
olds_to_news = dict( current_pairs )
new = olds_to_news[ old ]
message = 'There already is a relationship set for ' + old + '! It goes to ' + new + '.'
with ClientGUIDialogs.DialogYesNo( self, message, yes_label = 'I want to overwrite it', no_label = 'do nothing' ) as dlg:
if self._service_identifier != HC.LOCAL_TAG_SERVICE_IDENTIFIER:
if dlg.ShowModal() != wx.ID_YES: return
self._AddPair( old, new )
current_pairs = self._current_statuses_to_pairs[ HC.CURRENT ].union( self._current_statuses_to_pairs[ HC.PENDING ] )
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
#
if old is not None and old == self._current_new: self.SetNew( None )
self._current_old = old
if old is None: self._old_text.SetLabel( '' )
else: self._old_text.SetLabel( old )
self._SetButtonStatus()
def SetTagBoxFocus( self ):
if self._current_old is None: self._old_input.SetFocus()
if len( self._old_siblings.GetTags() ) == 0: self._old_input.SetFocus()
else: self._new_input.SetFocus()
@ -7150,7 +7168,16 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
def InitialiseControls():
self._tags_box = ClientGUICommon.TagsBoxManageWithShowDeleted( self, self.AddTag, self._current_tags, self._deleted_tags, self._pending_tags, self._petitioned_tags )
self._tags_box_sorter = ClientGUICommon.TagsBoxCountsSorter( self, 'tags' )
self._tags_box = ClientGUICommon.TagsBoxCountsSimple( self._tags_box_sorter, self.AddTag )
self._tags_box_sorter.SetTagsBox( self._tags_box )
self._show_deleted_checkbox = wx.CheckBox( self._tags_box_sorter, label = 'show deleted' )
self._show_deleted_checkbox.Bind( wx.EVT_CHECKBOX, self.EventShowDeleted )
self._tags_box_sorter.AddF( self._show_deleted_checkbox, FLAGS_LONE_BUTTON )
self._add_tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddTag, self._file_service_identifier, self._tag_service_identifier )
@ -7166,7 +7193,9 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
def PopulateControls():
pass
self._tags_box.ChangeTagRepository( self._tag_service_identifier )
self._tags_box.SetTagsByMedia( self._media )
def ArrangeControls():
@ -7187,7 +7216,7 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._tags_box, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._tags_box_sorter, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._add_tag_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( copy_paste_hbox, FLAGS_BUTTON_SIZERS )
vbox.AddF( self._modify_mappers, FLAGS_BUTTON_SIZERS )
@ -7213,12 +7242,14 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
self._account = service.GetAccount()
tags_managers = [ m.GetTagsManager() for m in media ]
hashes = set( itertools.chain.from_iterable( ( m.GetHashes() for m in media ) ) )
( self._current_tags, self._deleted_tags, self._pending_tags, self._petitioned_tags ) = CC.IntersectTags( tags_managers, tag_service_identifier )
media_results = HC.app.Read( 'media_results', self._file_service_identifier, hashes )
self._current_tags.sort()
self._pending_tags.sort()
# this should now be a nice clean copy of the original media
self._media = [ ClientGUIMixins.MediaSingleton( media_result ) for media_result in media_results ]
tags_managers = [ m.GetTagsManager() for m in self._media ]
InitialiseControls()
@ -7229,100 +7260,74 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
def _AddTag( self, tag, only_add = False ):
tag_managers = [ m.GetTagsManager() for m in self._media ]
num_files = len( self._media )
num_current = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetCurrent( self._tag_service_identifier ) ] )
choices = []
if self._i_am_local_tag_service:
if tag in self._pending_tags:
if only_add: return
self._pending_tags.remove( tag )
self._tags_box.RescindPend( tag )
elif tag in self._petitioned_tags:
self._petitioned_tags.remove( tag )
self._tags_box.RescindPetition( tag )
elif tag in self._current_tags:
if only_add: return
self._petitioned_tags.append( tag )
self._tags_box.PetitionTag( tag )
else:
self._pending_tags.append( tag )
self._tags_box.PendTag( tag )
self._content_updates = []
self._content_updates.extend( [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( tag, self._hashes ) ) for tag in self._pending_tags ] )
self._content_updates.extend( [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_DELETE, ( tag, self._hashes ) ) for tag in self._petitioned_tags ] )
if num_current < num_files: choices.append( ( 'add ' + tag, HC.CONTENT_UPDATE_ADD ) )
if num_current > 0: choices.append( ( 'delete ' + tag, HC.CONTENT_UPDATE_DELETE ) )
else:
if tag in self._pending_tags:
num_pending = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetPending( self._tag_service_identifier ) ] )
num_petitioned = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetPetitioned( self._tag_service_identifier ) ] )
if num_current + num_pending < num_files: choices.append( ( 'pend ' + tag, HC.CONTENT_UPDATE_PENDING ) )
if num_current > num_petitioned: choices.append( ( 'petition ' + tag, HC.CONTENT_UPDATE_PETITION ) )
if num_pending > 0: choices.append( ( 'rescind pending ' + tag, HC.CONTENT_UPDATE_RESCIND_PENDING ) )
if num_petitioned > 0: choices.append( ( 'rescind petitioned ' + tag, HC.CONTENT_UPDATE_RESCIND_PETITION ) )
if len( choices ) > 1:
intro = 'What would you like to do?'
with ClientGUIDialogs.DialogButtonChoice( self, intro, choices ) as dlg:
if only_add: return
self._pending_tags.remove( tag )
self._tags_box.RescindPend( tag )
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_RESCIND_PENDING, ( tag, self._hashes ) ) )
elif tag in self._petitioned_tags:
self._petitioned_tags.remove( tag )
self._tags_box.RescindPetition( tag )
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_RESCIND_PETITION, ( tag, self._hashes ) ) )
elif tag in self._current_tags:
if only_add: return
if self._account.HasPermission( HC.RESOLVE_PETITIONS ):
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_PETITION, ( tag, self._hashes, 'admin' ) ) )
self._petitioned_tags.append( tag )
self._tags_box.PetitionTag( tag )
elif self._account.HasPermission( HC.POST_PETITIONS ):
message = 'Enter a reason for this tag to be removed. A janitor will review your petition.'
with wx.TextEntryDialog( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_PETITION, ( tag, self._hashes, dlg.GetValue() ) ) )
self._petitioned_tags.append( tag )
self._tags_box.PetitionTag( tag )
else:
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_PENDING, ( tag, self._hashes ) ) )
self._pending_tags.append( tag )
self._tags_box.PendTag( tag )
if dlg.ShowModal() == wx.ID_OK: choice = dlg.GetData()
else: return
else: [ ( text, choice ) ] = choices
if choice == HC.CONTENT_UPDATE_ADD: media_to_affect = ( m for m in self._media if tag not in m.GetTagsManager().GetCurrent( self._tag_service_identifier ) )
elif choice == HC.CONTENT_UPDATE_DELETE: media_to_affect = ( m for m in self._media if tag in m.GetTagsManager().GetCurrent( self._tag_service_identifier ) )
elif choice == HC.CONTENT_UPDATE_PENDING: media_to_affect = ( m for m in self._media if tag not in m.GetTagsManager().GetCurrent( self._tag_service_identifier ) and tag not in m.GetTagsManager().GetPending( self._tag_service_identifier ) )
elif choice == HC.CONTENT_UPDATE_PETITION: media_to_affect = ( m for m in self._media if tag in m.GetTagsManager().GetCurrent( self._tag_service_identifier ) and tag not in m.GetTagsManager().GetPetitioned( self._tag_service_identifier ) )
elif choice == HC.CONTENT_UPDATE_RESCIND_PENDING: media_to_affect = ( m for m in self._media if tag in m.GetTagsManager().GetPending( self._tag_service_identifier ) )
elif choice == HC.CONTENT_UPDATE_RESCIND_PETITION: media_to_affect = ( m for m in self._media if tag in m.GetTagsManager().GetPetitioned( self._tag_service_identifier ) )
hashes = set( itertools.chain.from_iterable( ( m.GetHashes() for m in media_to_affect ) ) )
if choice == HC.CONTENT_UPDATE_PETITION:
if self._account.HasPermission( HC.RESOLVE_PETITIONS ): reason = 'admin'
else:
message = 'Enter a reason for this tag to be removed. A janitor will review your petition.'
with wx.TextEntryDialog( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
else: return
content_update = HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, choice, ( tag, hashes, reason ) )
else: content_update = HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, choice, ( tag, hashes ) )
for m in self._media: m.GetMediaResult().ProcessContentUpdate( self._tag_service_identifier, content_update )
self._content_updates.append( content_update )
self._tags_box.SetTagsByMedia( self._media, force_reload = True )
def AddTag( self, tag, parents = [] ):
@ -7340,7 +7345,9 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
if wx.TheClipboard.Open():
tags = self._current_tags + self._pending_tags
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = CC.GetMediasTagCount( self._media, self._tag_service_identifier )
tags = set( current_tags_to_count.keys() ).union( pending_tags_to_count.keys() )
text = yaml.safe_dump( tags )
@ -7357,7 +7364,7 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
tag = self._tags_box.GetSelectedTag()
if tag is not None and tag in self._current_tags or tag in self._petitioned_tags:
if tag is not None:
subject_identifiers = [ HC.AccountIdentifier( hash = hash, tag = tag ) for hash in self._hashes ]
@ -7381,20 +7388,16 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
tags = yaml.safe_load( text )
tags = [ tag for tag in tags if tag not in self._current_tags and tag not in self._pending_tags ]
for tag in tags: self.AddTag( tag )
for tag in tags: self._AddTag( tag, only_add = True )
except: wx.MessageBox( 'I could not understand what was in the clipboard' )
else: wx.MessageBox( 'I could not get permission to access the clipboard.' )
def EventTagsBoxAction( self, event ):
def EventShowDeleted( self, event ):
tag = self._tags_box.GetSelectedTag()
if tag is not None: self.AddTag( tag )
self._tags_box.SetShow( 'deleted', self._show_deleted_checkbox.GetValue() )
def GetContentUpdates( self ): return ( self._tag_service_identifier, self._content_updates )

View File

@ -375,7 +375,11 @@ class ManagementPanel( wx.lib.scrolledpanel.ScrolledPanel ):
def _MakeCurrentSelectionTagsBox( self, sizer ):
tags_box = ClientGUICommon.TagsBoxCPPWithSorter( self, self._page_key )
tags_box = ClientGUICommon.TagsBoxCountsSorter( self, 'selection tags' )
t = ClientGUICommon.TagsBoxCPP( tags_box, self._page_key )
tags_box.SetTagsBox( t )
sizer.AddF( tags_box, FLAGS_EXPAND_BOTH_WAYS )
@ -1786,6 +1790,19 @@ class ManagementPanelImportThreadWatcher( ManagementPanelImport ):
if page_key == self._page_key: self._thread_input.SetFocus()
def TestAbleToClose( self ):
import_queue_position_job_key = self._import_controller.GetJobKey( 'import_queue' )
if import_queue_position_job_key.IsWorking() and not import_queue_position_job_key.IsPaused():
with ClientGUIDialogs.DialogYesNo( self, 'This page is still importing. Are you sure you want to close it?' ) as dlg:
if dlg.ShowModal() == wx.ID_NO: raise Exception()
class ManagementPanelPetitions( ManagementPanel ):
def __init__( self, parent, page, page_key, file_service_identifier, petition_service_identifier, starting_from_session = False ):
@ -2017,8 +2034,8 @@ class ManagementPanelQuery( ManagementPanel ):
HC.pubsub.sub( self, 'AddMediaResultsFromQuery', 'add_media_results_from_query' )
HC.pubsub.sub( self, 'AddPredicate', 'add_predicate' )
HC.pubsub.sub( self, 'ChangeFileRepository', 'change_file_repository' )
HC.pubsub.sub( self, 'ChangeTagRepository', 'change_tag_repository' )
HC.pubsub.sub( self, 'ChangeFileRepositoryPubsub', 'change_file_repository' )
HC.pubsub.sub( self, 'ChangeTagRepositoryPubsub', 'change_tag_repository' )
HC.pubsub.sub( self, 'IncludeCurrent', 'notify_include_current' )
HC.pubsub.sub( self, 'IncludePending', 'notify_include_pending' )
HC.pubsub.sub( self, 'SearchImmediately', 'notify_search_immediately' )
@ -2094,7 +2111,7 @@ class ManagementPanelQuery( ManagementPanel ):
def ChangeFileRepository( self, page_key, service_identifier ):
def ChangeFileRepositoryPubsub( self, page_key, service_identifier ):
if page_key == self._page_key:
@ -2104,7 +2121,7 @@ class ManagementPanelQuery( ManagementPanel ):
def ChangeTagRepository( self, page_key, service_identifier ):
def ChangeTagRepositoryPubsub( self, page_key, service_identifier ):
if page_key == self._page_key:

View File

@ -439,13 +439,9 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ):
if len( self._selected_media ) > 0:
try:
with ClientGUIDialogsManage.DialogManageTags( None, self._file_service_identifier, self._selected_media ) as dlg: dlg.ShowModal()
self.SetFocus()
except: wx.MessageBox( traceback.format_exc() )
with ClientGUIDialogsManage.DialogManageTags( None, self._file_service_identifier, self._selected_media ) as dlg: dlg.ShowModal()
self.SetFocus()

View File

@ -48,7 +48,7 @@ TEMP_DIR = BASE_DIR + os.path.sep + 'temp'
# Misc
NETWORK_VERSION = 13
SOFTWARE_VERSION = 107
SOFTWARE_VERSION = 108
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -1304,217 +1304,6 @@ class ImportArgsGeneratorURLs( ImportArgsGenerator ):
else: return ( status, None )
class ImportQueueGenerator():
def __init__( self, job_key, item ):
self._job_key = job_key
self._item = item
def __call__( self ):
queue = self._item
self._job_key.SetVariable( 'queue', queue )
self._job_key.Finish()
class ImportQueueGeneratorGallery( ImportQueueGenerator ):
def __init__( self, job_key, item, downloaders_factory ):
ImportQueueGenerator.__init__( self, job_key, item )
self._downloaders_factory = downloaders_factory
def __call__( self ):
try:
raw_query = self._item
downloaders = list( self._downloaders_factory( raw_query ) )
downloaders[0].SetupGallerySearch() # for now this is cookie-based for hf, so only have to do it on one
total_urls_found = 0
while True:
downloaders_to_remove = []
for downloader in downloaders:
if self._job_key.IsPaused():
self._job_key.SetVariable( 'status', 'paused after ' + HC.u( total_urls_found ) + ' urls' )
self._job_key.WaitOnPause()
if self._job_key.IsCancelled(): break
self._job_key.SetVariable( 'status', 'found ' + HC.u( total_urls_found ) + ' urls' )
page_of_url_args = downloader.GetAnotherPage()
total_urls_found += len( page_of_url_args )
if len( page_of_url_args ) == 0: downloaders_to_remove.append( downloader )
else:
queue = self._job_key.GetVariable( 'queue' )
queue = list( queue )
queue.extend( page_of_url_args )
self._job_key.SetVariable( 'queue', queue )
for downloader in downloaders_to_remove: downloaders.remove( downloader )
if len( downloaders ) == 0: break
if self._job_key.IsPaused():
self._job_key.SetVariable( 'status', 'paused after ' + HC.u( total_urls_found ) + ' urls' )
self._job_key.WaitOnPause()
if self._job_key.IsCancelled(): break
self._job_key.SetVariable( 'status', '' )
except Exception as e:
self._job_key.SetVariable( 'status', HC.u( e ) )
HC.ShowException( e )
time.sleep( 2 )
finally: self._job_key.Finish()
class ImportQueueGeneratorURLs( ImportQueueGenerator ):
def __call__( self ):
try:
url = self._item
self._job_key.SetVariable( 'status', 'Connecting to address' )
try: html = HC.http.Request( HC.GET, url )
except: raise Exception( 'Could not download that url' )
self._job_key.SetVariable( 'status', 'parsing html' )
try: urls = ParsePageForURLs( html, url )
except: raise Exception( 'Could not parse that URL\'s html' )
queue = urls
self._job_key.SetVariable( 'queue', queue )
except Exception as e:
self._job_key.SetVariable( 'status', HC.u( e ) )
HC.ShowException( e )
time.sleep( 2 )
finally: self._job_key.Finish()
class ImportQueueGeneratorThread( ImportQueueGenerator ):
def __call__( self ):
try:
( board, thread_id ) = self._item
last_thread_check = 0
image_infos_already_added = set()
while True:
if self._job_key.IsPaused():
self._job_key.SetVariable( 'status', 'paused' )
self._job_key.WaitOnPause()
if self._job_key.IsCancelled(): break
thread_time = self._job_key.GetVariable( 'thread_time' )
if thread_time < 30: thread_time = 30
next_thread_check = last_thread_check + thread_time
if next_thread_check < HC.GetNow():
self._job_key.SetVariable( 'status', 'checking thread' )
url = 'http://api.4chan.org/' + board + '/res/' + thread_id + '.json'
try:
raw_json = HC.http.Request( HC.GET, url )
json_dict = json.loads( raw_json )
posts_list = json_dict[ 'posts' ]
image_infos = [ ( post[ 'md5' ].decode( 'base64' ), board, HC.u( post[ 'tim' ] ), post[ 'ext' ], post[ 'filename' ] ) for post in posts_list if 'md5' in post ]
image_infos_i_can_add = [ image_info for image_info in image_infos if image_info not in image_infos_already_added ]
image_infos_already_added.update( image_infos_i_can_add )
if len( image_infos_i_can_add ) > 0:
queue = self._job_key.GetVariable( 'queue' )
queue = list( queue )
queue.extend( image_infos_i_can_add )
self._job_key.SetVariable( 'queue', queue )
except HydrusExceptions.NotFoundException: raise Exception( 'Thread 404' )
last_thread_check = HC.GetNow()
else: self._job_key.SetVariable( 'status', 'rechecking thread ' + HC.ConvertTimestampToPrettyPending( next_thread_check ) )
except Exception as e:
self._job_key.SetVariable( 'status', HC.u( e ) )
HC.ShowException( e )
time.sleep( 2 )
finally: self._job_key.Finish()
class ImportController():
def __init__( self, import_args_generator_factory, import_queue_generator_factory, page_key = None ):
@ -1748,6 +1537,216 @@ class ImportController():
threading.Thread( target = self.MainLoop ).start()
class ImportQueueGenerator():
def __init__( self, job_key, item ):
self._job_key = job_key
self._item = item
def __call__( self ):
queue = self._item
self._job_key.SetVariable( 'queue', queue )
self._job_key.Finish()
class ImportQueueGeneratorGallery( ImportQueueGenerator ):
def __init__( self, job_key, item, downloaders_factory ):
ImportQueueGenerator.__init__( self, job_key, item )
self._downloaders_factory = downloaders_factory
def __call__( self ):
try:
raw_query = self._item
downloaders = list( self._downloaders_factory( raw_query ) )
downloaders[0].SetupGallerySearch() # for now this is cookie-based for hf, so only have to do it on one
total_urls_found = 0
while True:
downloaders_to_remove = []
for downloader in downloaders:
if self._job_key.IsPaused():
self._job_key.SetVariable( 'status', 'paused after ' + HC.u( total_urls_found ) + ' urls' )
self._job_key.WaitOnPause()
if self._job_key.IsCancelled(): break
self._job_key.SetVariable( 'status', 'found ' + HC.u( total_urls_found ) + ' urls' )
page_of_url_args = downloader.GetAnotherPage()
total_urls_found += len( page_of_url_args )
if len( page_of_url_args ) == 0: downloaders_to_remove.append( downloader )
else:
queue = self._job_key.GetVariable( 'queue' )
queue = list( queue )
queue.extend( page_of_url_args )
self._job_key.SetVariable( 'queue', queue )
for downloader in downloaders_to_remove: downloaders.remove( downloader )
if len( downloaders ) == 0: break
if self._job_key.IsPaused():
self._job_key.SetVariable( 'status', 'paused after ' + HC.u( total_urls_found ) + ' urls' )
self._job_key.WaitOnPause()
if self._job_key.IsCancelled(): break
self._job_key.SetVariable( 'status', '' )
except Exception as e:
self._job_key.SetVariable( 'status', HC.u( e ) )
HC.ShowException( e )
time.sleep( 2 )
finally: self._job_key.Finish()
class ImportQueueGeneratorURLs( ImportQueueGenerator ):
def __call__( self ):
try:
url = self._item
self._job_key.SetVariable( 'status', 'Connecting to address' )
try: html = HC.http.Request( HC.GET, url )
except: raise Exception( 'Could not download that url' )
self._job_key.SetVariable( 'status', 'parsing html' )
try: urls = ParsePageForURLs( html, url )
except: raise Exception( 'Could not parse that URL\'s html' )
queue = urls
self._job_key.SetVariable( 'queue', queue )
except Exception as e:
self._job_key.SetVariable( 'status', HC.u( e ) )
HC.ShowException( e )
time.sleep( 2 )
finally: self._job_key.Finish()
class ImportQueueGeneratorThread( ImportQueueGenerator ):
def __call__( self ):
try:
( board, thread_id ) = self._item
last_thread_check = 0
image_infos_already_added = set()
while True:
if self._job_key.IsPaused():
self._job_key.SetVariable( 'status', 'paused' )
self._job_key.WaitOnPause()
if self._job_key.IsCancelled(): break
thread_time = self._job_key.GetVariable( 'thread_time' )
if thread_time < 30: thread_time = 30
next_thread_check = last_thread_check + thread_time
if next_thread_check < HC.GetNow():
self._job_key.SetVariable( 'status', 'checking thread' )
url = 'http://api.4chan.org/' + board + '/res/' + thread_id + '.json'
try:
raw_json = HC.http.Request( HC.GET, url )
json_dict = json.loads( raw_json )
posts_list = json_dict[ 'posts' ]
image_infos = [ ( post[ 'md5' ].decode( 'base64' ), board, HC.u( post[ 'tim' ] ), post[ 'ext' ], post[ 'filename' ] ) for post in posts_list if 'md5' in post ]
image_infos_i_can_add = [ image_info for image_info in image_infos if image_info not in image_infos_already_added ]
image_infos_already_added.update( image_infos_i_can_add )
if len( image_infos_i_can_add ) > 0:
queue = self._job_key.GetVariable( 'queue' )
queue = list( queue )
queue.extend( image_infos_i_can_add )
self._job_key.SetVariable( 'queue', queue )
except HydrusExceptions.NotFoundException: raise Exception( 'Thread 404' )
last_thread_check = HC.GetNow()
else: self._job_key.SetVariable( 'status', 'rechecking thread ' + HC.ConvertTimestampToPrettyPending( next_thread_check ) )
except Exception as e:
self._job_key.SetVariable( 'status', HC.u( e ) )
HC.ShowException( e )
time.sleep( 2 )
finally: self._job_key.Finish()
def THREADDownloadURL( message, url, url_string ):
try:

View File

@ -98,14 +98,18 @@ def ParseURL( url ):
parse_result = urlparse.urlparse( url )
( scheme, host, port ) = ( parse_result.scheme, parse_result.hostname, parse_result.port )
scheme = parse_result.scheme
hostname = parse_result.hostname
port = parse_result.port
parse_result = urlparse.urlparse( url )
location = ( parse_result.scheme, parse_result.hostname, parse_result.port )
if hostname is None: location = None
else: location = ( scheme, hostname, port )
path = parse_result.path
# this happens when parsing 'index.html' rather than 'hostname/index.html' or '/index.html'
if not path.startswith( '/' ): path = '/' + path
query = parse_result.query
except: raise Exception( 'Could not parse that URL' )
@ -143,6 +147,8 @@ class HTTPConnectionManager():
( new_location, new_path, new_query ) = ParseURL( new_url )
if new_location is None: new_location = location
if new_query != '': new_path_and_query = new_path + '?' + new_query
else: new_path_and_query = new_path