hydrus/include/ClientGUIDialogsManage.py

2881 lines
106 KiB
Python

import ClientCaches
import ClientConstants as CC
import ClientData
import ClientDefaults
import ClientDownloading
import ClientDragDrop
import ClientExporting
import ClientFiles
import ClientGUIACDropdown
import ClientGUICommon
import ClientGUIListBoxes
import ClientGUIListCtrl
import ClientGUIDialogs
import ClientGUIImport
import ClientGUIOptionsPanels
import ClientGUIPredicates
import ClientGUIScrolledPanelsEdit
import ClientGUIShortcuts
import ClientGUIFileSeedCache
import ClientGUITime
import ClientGUITopLevelWindows
import ClientImporting
import ClientImportLocal
import ClientImportOptions
import ClientMedia
import ClientRatings
import ClientSearch
import ClientServices
import ClientThreading
import collections
import HydrusConstants as HC
import HydrusData
import HydrusExceptions
import HydrusGlobals as HG
import HydrusNATPunch
import HydrusNetwork
import HydrusPaths
import HydrusSerialisable
import HydrusTagArchive
import HydrusTags
import HydrusText
import itertools
import os
import random
import re
import string
import time
import traceback
import wx
import yaml
# Option Enums
ID_NULL = wx.NewId()
ID_TIMER_UPDATE = wx.NewId()
'''class DialogManageContacts( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
def InitialiseControls():
self._contacts = ClientGUICommon.ListBook( self )
self._contacts.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventContactChanging )
self._contacts.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventContactChanged )
self._add_contact_address = wx.Button( self, label = 'add by contact address' )
self._add_contact_address.Bind( wx.EVT_BUTTON, self.EventAddByContactAddress )
self._add_contact_address.SetForegroundColour( ( 0, 128, 0 ) )
self._add_manually = wx.Button( self, label = 'add manually' )
self._add_manually.Bind( wx.EVT_BUTTON, self.EventAddManually )
self._add_manually.SetForegroundColour( ( 0, 128, 0 ) )
self._remove = wx.Button( self, label = 'remove' )
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
self._export = wx.Button( self, label = 'export' )
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
self._edit_log = []
( identities, contacts, deletable_names ) = HG.client_controller.Read( 'identities_and_contacts' )
self._deletable_names = deletable_names
for identity in identities:
name = identity.GetName()
page_info = ( self._Panel, ( self._contacts, identity ), { 'is_identity' : True } )
self._contacts.AddPage( page_info, ' identity - ' + name )
for contact in contacts:
name = contact.GetName()
page_info = ( self._Panel, ( self._contacts, contact ), { 'is_identity' : False } )
self._contacts.AddPage( page_info, name )
def ArrangeControls():
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
add_remove_hbox.Add( self._add_manually, CC.FLAGS_VCENTER )
add_remove_hbox.Add( self._add_contact_address, CC.FLAGS_VCENTER )
add_remove_hbox.Add( self._remove, CC.FLAGS_VCENTER )
add_remove_hbox.Add( self._export, CC.FLAGS_VCENTER )
ok_hbox = wx.BoxSizer( wx.HORIZONTAL )
ok_hbox.Add( self._ok, CC.FLAGS_VCENTER )
ok_hbox.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._contacts, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( add_remove_hbox, CC.FLAGS_SMALL_INDENT )
vbox.Add( ok_hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage contacts' )
InitialiseControls()
PopulateControls()
ArrangeControls()
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 980, y ) )
self.SetDropTarget( ClientDragDrop.FileDropTarget( filenames_callable = self.Import ) )
self.EventContactChanged( None )
wx.CallAfter( self._ok.SetFocus )
def _CheckCurrentContactIsValid( self ):
contact_panel = self._contacts.GetCurrentPage()
if contact_panel is not None:
contact = contact_panel.GetContact()
old_name = self._contacts.GetCurrentName()
name = contact.GetName()
if name != old_name and ' identity - ' + name != old_name:
if self._contacts.NameExists( name ) or self._contacts.NameExists( ' identity - ' + name ) or name == 'Anonymous': raise Exception( 'That name is already in use!' )
if old_name.startswith( ' identity - ' ): self._contacts.RenamePage( old_name, ' identity - ' + name )
else: self._contacts.RenamePage( old_name, name )
def EventAddByContactAddress( self, event ):
try: self._CheckCurrentContactIsValid()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
return
with ClientGUIDialogs.DialogTextEntry( self, 'Enter contact\'s address in the form contact_key@hostname:port.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
contact_address = dlg.GetValue()
try:
( contact_key_encoded, address ) = contact_address.split( '@' )
contact_key = contact_key_encoded.decode( 'hex' )
( host, port ) = address.split( ':' )
port = int( port )
except: raise Exception( 'Could not parse the address!' )
name = contact_key_encoded
contact = ClientConstantsMessages.Contact( None, name, host, port )
try:
connection = contact.GetConnection()
public_key = connection.Get( 'public_key', contact_key = contact_key.encode( 'hex' ) )
except: raise Exception( 'Could not fetch the contact\'s public key from the address:' + os.linesep + traceback.format_exc() )
contact = ClientConstantsMessages.Contact( public_key, name, host, port )
self._edit_log.append( ( HC.ADD, contact ) )
page = self._Panel( self._contacts, contact, is_identity = False )
self._deletable_names.add( name )
self._contacts.AddPage( page, name, select = True )
def EventAddManually( self, event ):
try: self._CheckCurrentContactIsValid()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
return
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new contact\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
name = dlg.GetValue()
if self._contacts.NameExists( name ) or self._contacts.NameExists( ' identity - ' + name ) or name == 'Anonymous': raise Exception( 'That name is already in use!' )
if name == '': raise Exception( 'Please enter a nickname for the service.' )
public_key = None
host = 'hostname'
port = 45871
contact = ClientConstantsMessages.Contact( public_key, name, host, port )
self._edit_log.append( ( HC.ADD, contact ) )
page = self._Panel( self._contacts, contact, is_identity = False )
self._deletable_names.add( name )
self._contacts.AddPage( page, name, select = True )
def EventContactChanged( self, event ):
contact_panel = self._contacts.GetCurrentPage()
if contact_panel is not None:
old_name = contact_panel.GetOriginalName()
if old_name in self._deletable_names: self._remove.Enable()
else: self._remove.Disable()
def EventContactChanging( self, event ):
try: self._CheckCurrentContactIsValid()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
event.Veto()
def EventExport( self, event ):
contact_panel = self._contacts.GetCurrentPage()
if contact_panel is not None:
name = self._contacts.GetCurrentName()
contact = contact_panel.GetContact()
try:
with wx.FileDialog( self, 'select where to export contact', defaultFile = name + '.yaml', style = wx.FD_SAVE ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = HydrusData.ToUnicode( dlg.GetPath() )
with open( path, 'wb' ) as f: f.write( yaml.safe_dump( contact ) )
except:
with wx.FileDialog( self, 'select where to export contact', defaultFile = 'contact.yaml', style = wx.FD_SAVE ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = HydrusData.ToUnicode( dlg.GetPath() )
with open( path, 'wb' ) as f: f.write( yaml.safe_dump( contact ) )
def EventOK( self, event ):
try: self._CheckCurrentContactIsValid()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
return
for ( name, page ) in self._contacts.GetNamesToActivePages().items():
if page.HasChanges(): self._edit_log.append( ( HC.EDIT, ( page.GetOriginalName(), page.GetContact() ) ) )
try:
if len( self._edit_log ) > 0: HG.client_controller.Write( 'update_contacts', self._edit_log )
finally: self.EndModal( wx.ID_OK )
# this isn't used yet!
def EventRemove( self, event ):
contact_panel = self._contacts.GetCurrentPage()
if contact_panel is not None:
name = contact_panel.GetOriginalName()
self._edit_log.append( ( HC.DELETE, name ) )
self._contacts.DeleteCurrentPage()
self._deletable_names.discard( name )
def Import( self, paths ):
try: self._CheckCurrentContactIsValid()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
return
for path in paths:
try:
with open( path, 'rb' ) as f: file = f.read()
obj = yaml.safe_load( file )
if type( obj ) == ClientConstantsMessages.Contact:
contact = obj
name = contact.GetName()
if self._contacts.NameExists( name ) or self._contacts.NameExists( ' identities - ' + name ) or name == 'Anonymous':
message = 'There already exists a contact or identity with the name ' + name + '. Do you want to overwrite, or make a new contact?'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Please choose what to do.', yes_label = 'overwrite', no_label = 'make new' ) as dlg:
if True:
name_to_page_dict = self._contacts.GetNamesToActivePages()
if name in name_to_page_dict: page = name_to_page_dict[ name ]
else: page = name_to_page_dict[ ' identities - ' + name ]
page.Update( contact )
else:
while self._contacts.NameExists( name ) or self._contacts.NameExists( ' identities - ' + name ) or name == 'Anonymous': name = name + str( random.randint( 0, 9 ) )
( public_key, old_name, host, port ) = contact.GetInfo()
new_contact = ClientConstantsMessages.Contact( public_key, name, host, port )
self._edit_log.append( ( HC.ADD, contact ) )
self._deletable_names.add( name )
page = self._Panel( self._contacts, contact, False )
self._contacts.AddPage( page, name, select = True )
else:
( public_key, old_name, host, port ) = contact.GetInfo()
new_contact = ClientConstantsMessages.Contact( public_key, name, host, port )
self._edit_log.append( ( HC.ADD, contact ) )
self._deletable_names.add( name )
page = self._Panel( self._contacts, contact, False )
self._contacts.AddPage( page, name, select = True )
except:
wx.MessageBox( traceback.format_exc() )
class _Panel( wx.Panel ):
def __init__( self, parent, contact, is_identity ):
wx.Panel.__init__( self, parent )
self._contact = contact
self._is_identity = is_identity
( public_key, name, host, port ) = contact.GetInfo()
contact_key = contact.GetContactKey()
def InitialiseControls():
self._contact_panel = ClientGUICommon.StaticBox( self, 'contact' )
self._name = wx.TextCtrl( self._contact_panel )
self._contact_address = wx.TextCtrl( self._contact_panel )
self._public_key = ClientGUICommon.SaneMultilineTextCtrl( self._contact_panel )
def PopulateControls():
self._name.SetValue( name )
contact_address = host + ':' + str( port )
if contact_key is not None: contact_address = contact_key.encode( 'hex' ) + '@' + contact_address
self._contact_address.SetValue( contact_address )
if public_key is not None: self._public_key.SetValue( public_key )
def ArrangeControls():
gridbox = wx.FlexGridSizer( 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.Add( wx.StaticText( self._contact_panel, label = 'name' ), CC.FLAGS_VCENTER )
gridbox.Add( self._name, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( wx.StaticText( self._contact_panel, label = 'contact address' ), CC.FLAGS_VCENTER )
gridbox.Add( self._contact_address, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( wx.StaticText( self._contact_panel, label = 'public key' ), CC.FLAGS_VCENTER )
gridbox.Add( self._public_key, CC.FLAGS_EXPAND_BOTH_WAYS )
self._contact_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._contact_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
InitialiseControls()
PopulateControls()
ArrangeControls()
def _GetInfo( self ):
public_key = self._public_key.GetValue()
if public_key == '': public_key = None
name = self._name.GetValue()
contact_address = self._contact_address.GetValue()
try:
if '@' in contact_address: ( contact_key, address ) = contact_address.split( '@' )
else: address = contact_address
( host, port ) = address.split( ':' )
try: port = int( port )
except:
port = 45871
wx.MessageBox( 'Could not parse the port!' )
except:
host = 'hostname'
port = 45871
wx.MessageBox( 'Could not parse the contact\'s address!' )
return [ public_key, name, host, port ]
def GetContact( self ):
[ public_key, name, host, port ] = self._GetInfo()
return ClientConstantsMessages.Contact( public_key, name, host, port )
def GetOriginalName( self ): return self._contact.GetName()
def HasChanges( self ):
[ my_public_key, my_name, my_host, my_port ] = self._GetInfo()
[ public_key, name, host, port ] = self._contact.GetInfo()
if my_public_key != public_key: return True
if my_name != name: return True
if my_host != host: return True
if my_port != port: return True
return False
def Update( self, contact ):
( public_key, name, host, port ) = contact.GetInfo()
contact_key = contact.GetContactKey()
self._name.SetValue( name )
contact_address = host + ':' + str( port )
if contact_key is not None: contact_address = contact_key.encode( 'hex' ) + '@' + contact_address
self._contact_address.SetValue( contact_address )
if public_key is None: public_key = ''
self._public_key.SetValue( public_key )
'''
class DialogManageExportFolders( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage export folders' )
columns = [ ( 'name', 20 ), ( 'path', -1 ), ( 'type', 12 ), ( 'query', 16 ), ( 'period', 10 ), ( 'phrase', 20 ) ]
self._export_folders = ClientGUIListCtrl.BetterListCtrl( self, 'export_folders', 6, 40, columns, self._ConvertExportFolderToListCtrlTuples, use_simple_delete = True, activation_callback = self.Edit )
export_folders = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_EXPORT_FOLDER )
self._export_folders.AddDatas( export_folders )
self._add_button = wx.Button( self, label = 'add' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
self._edit_button = wx.Button( self, label = 'edit' )
self._edit_button.Bind( wx.EVT_BUTTON, self.EventEdit )
self._delete_button = wx.Button( self, label = 'delete' )
self._delete_button.Bind( wx.EVT_BUTTON, self.EventDelete )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
file_buttons = wx.BoxSizer( wx.HORIZONTAL )
file_buttons.Add( self._add_button, CC.FLAGS_VCENTER )
file_buttons.Add( self._edit_button, CC.FLAGS_VCENTER )
file_buttons.Add( self._delete_button, CC.FLAGS_VCENTER )
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.Add( self._ok, CC.FLAGS_VCENTER )
buttons.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Here you can set the client to regularly export a certain query to a particular location.'
vbox.Add( ClientGUICommon.BetterStaticText( self, intro ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._export_folders, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( file_buttons, CC.FLAGS_BUTTON_SIZER )
vbox.Add( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
if x < 780: x = 780
if y < 480: y = 480
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def _AddFolder( self ):
new_options = HG.client_controller.new_options
phrase = new_options.GetString( 'export_phrase' )
name = 'export folder'
path = ''
export_type = HC.EXPORT_FOLDER_TYPE_REGULAR
file_search_context = ClientSearch.FileSearchContext( file_service_key = CC.LOCAL_FILE_SERVICE_KEY )
period = 15 * 60
export_folder = ClientExporting.ExportFolder( name, path, export_type = export_type, file_search_context = file_search_context, period = period, phrase = phrase )
with DialogManageExportFoldersEdit( self, export_folder ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
export_folder = dlg.GetInfo()
export_folder.SetNonDupeName( self._GetExistingNames() )
self._export_folders.AddDatas( ( export_folder, ) )
def _ConvertExportFolderToListCtrlTuples( self, export_folder ):
( name, path, export_type, file_search_context, period, phrase ) = export_folder.ToTuple()
if export_type == HC.EXPORT_FOLDER_TYPE_REGULAR:
pretty_export_type = 'regular'
elif export_type == HC.EXPORT_FOLDER_TYPE_SYNCHRONISE:
pretty_export_type = 'synchronise'
pretty_file_search_context = ', '.join( predicate.GetUnicode( with_count = False ) for predicate in file_search_context.GetPredicates() )
pretty_period = HydrusData.TimeDeltaToPrettyTimeDelta( period )
pretty_phrase = phrase
display_tuple = ( name, path, pretty_export_type, pretty_file_search_context, pretty_period, pretty_phrase )
sort_tuple = ( name, path, pretty_export_type, pretty_file_search_context, period, phrase )
return ( display_tuple, sort_tuple )
def _GetExistingNames( self ):
existing_names = { export_folder.GetName() for export_folder in self._export_folders.GetData() }
return existing_names
def Edit( self ):
export_folders = self._export_folders.GetData( only_selected = True )
for export_folder in export_folders:
with DialogManageExportFoldersEdit( self, export_folder ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
self._export_folders.DeleteDatas( ( export_folder, ) )
export_folder = dlg.GetInfo()
export_folder.SetNonDupeName( self._GetExistingNames() )
self._export_folders.AddDatas( ( export_folder, ) )
else:
return
def EventAdd( self, event ):
self._AddFolder()
def EventDelete( self, event ):
self.Delete()
def EventEdit( self, event ):
self.Edit()
def EventOK( self, event ):
existing_db_names = set( HG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_EXPORT_FOLDER ) )
export_folders = self._export_folders.GetData()
good_names = set()
for export_folder in export_folders:
HG.client_controller.Write( 'serialisable', export_folder )
good_names.add( export_folder.GetName() )
names_to_delete = existing_db_names - good_names
for name in names_to_delete:
HG.client_controller.Write( 'delete_serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_EXPORT_FOLDER, name )
HG.client_controller.pub( 'notify_new_export_folders' )
self.EndModal( wx.ID_OK )
class DialogManageExportFoldersEdit( ClientGUIDialogs.Dialog ):
def __init__( self, parent, export_folder ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'edit export folder' )
self._export_folder = export_folder
( name, path, export_type, file_search_context, period, phrase ) = self._export_folder.ToTuple()
self._path_box = ClientGUICommon.StaticBox( self, 'name and location' )
self._name = wx.TextCtrl( self._path_box)
self._path = wx.DirPickerCtrl( self._path_box, style = wx.DIRP_USE_TEXTCTRL )
#
self._type_box = ClientGUICommon.StaticBox( self, 'type of export' )
self._type = ClientGUICommon.BetterChoice( self._type_box )
self._type.Append( 'regular', HC.EXPORT_FOLDER_TYPE_REGULAR )
self._type.Append( 'synchronise', HC.EXPORT_FOLDER_TYPE_SYNCHRONISE )
#
self._query_box = ClientGUICommon.StaticBox( self, 'query to export' )
self._page_key = 'export folders placeholder'
predicates = file_search_context.GetPredicates()
self._predicates_box = ClientGUIListBoxes.ListBoxTagsActiveSearchPredicates( self._query_box, self._page_key, predicates )
self._searchbox = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._query_box, self._page_key, file_search_context )
#
self._period_box = ClientGUICommon.StaticBox( self, 'export period' )
self._period = ClientGUITime.TimeDeltaButton( self._period_box, min = 3 * 60, days = True, hours = True, minutes = True )
#
self._phrase_box = ClientGUICommon.StaticBox( self, 'filenames' )
self._pattern = wx.TextCtrl( self._phrase_box )
self._examples = ClientGUICommon.ExportPatternButton( self._phrase_box )
#
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._name.SetValue( name )
self._path.SetPath( path )
self._type.SelectClientData( export_type )
self._period.SetValue( period )
self._pattern.SetValue( phrase )
#
rows = []
rows.append( ( 'name: ', self._name ) )
rows.append( ( 'folder path: ', self._path ) )
gridbox = ClientGUICommon.WrapInGrid( self._path_box, rows )
self._path_box.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
text = '''regular - try to export the files to the directory, overwriting if the filesize if different
synchronise - try to export the files to the directory, overwriting if the filesize if different, and delete anything else in the directory
If you select synchronise, be careful!'''
st = ClientGUICommon.BetterStaticText( self._type_box, label = text )
st.SetWrapWidth( 440 )
self._type_box.Add( st, CC.FLAGS_EXPAND_PERPENDICULAR )
self._type_box.Add( self._type, CC.FLAGS_EXPAND_PERPENDICULAR )
self._query_box.Add( self._predicates_box, CC.FLAGS_EXPAND_BOTH_WAYS )
self._query_box.Add( self._searchbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self._period_box.Add( self._period, CC.FLAGS_EXPAND_PERPENDICULAR )
phrase_hbox = wx.BoxSizer( wx.HORIZONTAL )
phrase_hbox.Add( self._pattern, CC.FLAGS_EXPAND_BOTH_WAYS )
phrase_hbox.Add( self._examples, CC.FLAGS_VCENTER )
self._phrase_box.Add( phrase_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.Add( self._ok, CC.FLAGS_VCENTER )
buttons.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._path_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._type_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._query_box, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( self._period_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._phrase_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 480, y ) )
wx.CallAfter( self._ok.SetFocus )
def EventOK( self, event ):
if self._path.GetPath() in ( '', None ):
wx.MessageBox( 'You must enter a folder path to export to!' )
return
phrase = self._pattern.GetValue()
try:
ClientExporting.ParseExportPhrase( phrase )
except:
wx.MessageBox( 'Could not parse that export phrase!' )
return
self.EndModal( wx.ID_OK )
def GetInfo( self ):
name = self._name.GetValue()
path = HydrusData.ToUnicode( self._path.GetPath() )
export_type = self._type.GetChoice()
file_search_context = self._searchbox.GetFileSearchContext()
predicates = self._predicates_box.GetPredicates()
file_search_context.SetPredicates( predicates )
period = self._period.GetValue()
phrase = self._pattern.GetValue()
export_folder = ClientExporting.ExportFolder( name, path, export_type, file_search_context, period, phrase )
return export_folder
'''
class DialogManageImageboards( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
def InitialiseControls():
self._sites = ClientGUICommon.ListBook( self )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
self._add.SetForegroundColour( ( 0, 128, 0 ) )
self._remove = wx.Button( self, label = 'remove' )
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
self._export = wx.Button( self, label = 'export' )
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
self._names_to_delete = []
sites = HG.client_controller.Read( 'imageboards' )
for ( name, imageboards ) in sites.items():
self._sites.AddPageArgs( name, name, self._Panel, ( self._sites, imageboards ), {} )
def ArrangeControls():
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
add_remove_hbox.Add( self._add, CC.FLAGS_VCENTER )
add_remove_hbox.Add( self._remove, CC.FLAGS_VCENTER )
add_remove_hbox.Add( self._export, CC.FLAGS_VCENTER )
ok_hbox = wx.BoxSizer( wx.HORIZONTAL )
ok_hbox.Add( self._ok, CC.FLAGS_VCENTER )
ok_hbox.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._sites, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( add_remove_hbox, CC.FLAGS_SMALL_INDENT )
vbox.Add( ok_hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage imageboards' )
InitialiseControls()
PopulateControls()
ArrangeControls()
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 980, y ) )
self.SetDropTarget( ClientDragDrop.FileDropTarget( filenames_callable = self.Import ) )
wx.CallAfter( self._ok.SetFocus )
def EventAdd( self, event ):
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new site\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
try:
name = dlg.GetValue()
if self._sites.KeyExists( name ): raise HydrusExceptions.NameException( 'That name is already in use!' )
if name == '': raise HydrusExceptions.NameException( 'Please enter a nickname for the service.' )
page = self._Panel( self._sites, [], is_new = True )
self._sites.AddPage( name, name, page, select = True )
except HydrusExceptions.NameException as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
self.EventAdd( event )
def EventExport( self, event ):
site_panel = self._sites.GetCurrentPage()
if site_panel is not None:
name = self._sites.GetCurrentKey()
imageboards = site_panel.GetImageboards()
dict = { name : imageboards }
with wx.FileDialog( self, 'select where to export site', defaultFile = 'site.yaml', style = wx.FD_SAVE ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = HydrusData.ToUnicode( dlg.GetPath() )
with open( path, 'wb' ) as f: f.write( yaml.safe_dump( dict ) )
def EventOK( self, event ):
try:
for name in self._names_to_delete:
HG.client_controller.Write( 'delete_imageboard', name )
for page in self._sites.GetActivePages():
if page.HasChanges():
imageboards = page.GetImageboards()
name = 'this is old code'
HG.client_controller.Write( 'imageboard', name, imageboards )
finally: self.EndModal( wx.ID_OK )
def EventRemove( self, event ):
site_panel = self._sites.GetCurrentPage()
if site_panel is not None:
name = self._sites.GetCurrentKey()
self._names_to_delete.append( name )
self._sites.DeleteCurrentPage()
def Import( self, paths ):
for path in paths:
try:
with open( path, 'rb' ) as f: file = f.read()
thing = yaml.safe_load( file )
if isinstance( thing, dict ):
( name, imageboards ) = thing.items()[0]
if not self._sites.KeyExists( name ):
page = self._Panel( self._sites, [], is_new = True )
self._sites.AddPage( name, name, page, select = True )
page = self._sites.GetPage( name )
for imageboard in imageboards:
if isinstance( imageboard, ClientData.Imageboard ): page.UpdateImageboard( imageboard )
elif isinstance( thing, ClientData.Imageboard ):
imageboard = thing
page = self._sites.GetCurrentPage()
page.UpdateImageboard( imageboard )
except:
wx.MessageBox( traceback.format_exc() )
class _Panel( wx.Panel ):
def __init__( self, parent, imageboards, is_new = False ):
def InitialiseControls():
self._site_panel = ClientGUICommon.StaticBox( self, 'site' )
self._imageboards = ClientGUICommon.ListBook( self._site_panel )
self._add = wx.Button( self._site_panel, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
self._add.SetForegroundColour( ( 0, 128, 0 ) )
self._remove = wx.Button( self._site_panel, label = 'remove' )
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
self._export = wx.Button( self._site_panel, label = 'export' )
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
def PopulateControls():
for imageboard in imageboards:
name = imageboard.GetName()
self._imageboards.AddPageArgs( name, name, self._Panel, ( self._imageboards, imageboard ), {} )
def ArrangeControls():
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
add_remove_hbox.Add( self._add, CC.FLAGS_VCENTER )
add_remove_hbox.Add( self._remove, CC.FLAGS_VCENTER )
add_remove_hbox.Add( self._export, CC.FLAGS_VCENTER )
self._site_panel.Add( self._imageboards, CC.FLAGS_EXPAND_BOTH_WAYS )
self._site_panel.Add( add_remove_hbox, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._site_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
wx.Panel.__init__( self, parent )
self._original_imageboards = imageboards
self._has_changes = False
self._is_new = is_new
InitialiseControls()
PopulateControls()
ArrangeControls()
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 980, y ) )
def EventAdd( self, event ):
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new imageboard\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
try:
name = dlg.GetValue()
if self._imageboards.KeyExists( name ): raise HydrusExceptions.NameException()
if name == '': raise Exception( 'Please enter a nickname for the service.' )
imageboard = ClientData.Imageboard( name, '', 60, [], {} )
page = self._Panel( self._imageboards, imageboard, is_new = True )
self._imageboards.AddPage( name, name, page, select = True )
self._has_changes = True
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
self.EventAdd( event )
def EventExport( self, event ):
imageboard_panel = self._imageboards.GetCurrentPage()
if imageboard_panel is not None:
imageboard = imageboard_panel.GetImageboard()
with wx.FileDialog( self, 'select where to export imageboard', defaultFile = 'imageboard.yaml', style = wx.FD_SAVE ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = HydrusData.ToUnicode( dlg.GetPath() )
with open( path, 'wb' ) as f: f.write( yaml.safe_dump( imageboard ) )
def EventRemove( self, event ):
imageboard_panel = self._imageboards.GetCurrentPage()
if imageboard_panel is not None:
name = self._imageboards.GetCurrentKey()
self._imageboards.DeleteCurrentPage()
self._has_changes = True
def GetImageboards( self ):
names_to_imageboards = { imageboard.GetName() : imageboard for imageboard in self._original_imageboards if self._imageboards.KeyExists( imageboard.GetName() ) }
for page in self._imageboards.GetActivePages():
imageboard = page.GetImageboard()
names_to_imageboards[ imageboard.GetName() ] = imageboard
return names_to_imageboards.values()
def HasChanges( self ):
if self._is_new: return True
return self._has_changes or True in ( page.HasChanges() for page in self._imageboards.GetActivePages() )
def UpdateImageboard( self, imageboard ):
name = imageboard.GetName()
if not self._imageboards.KeyExists( name ):
new_imageboard = ClientData.Imageboard( name, '', 60, [], {} )
page = self._Panel( self._imageboards, new_imageboard, is_new = True )
self._imageboards.AddPage( name, name, page, select = True )
page = self._imageboards.GetPage( name )
page.Update( imageboard )
class _Panel( wx.Panel ):
def __init__( self, parent, imageboard, is_new = False ):
wx.Panel.__init__( self, parent )
self._imageboard = imageboard
self._is_new = is_new
( post_url, flood_time, form_fields, restrictions ) = self._imageboard.GetBoardInfo()
def InitialiseControls():
self._imageboard_panel = ClientGUICommon.StaticBox( self, 'imageboard' )
#
self._basic_info_panel = ClientGUICommon.StaticBox( self._imageboard_panel, 'basic info' )
self._post_url = wx.TextCtrl( self._basic_info_panel )
self._flood_time = wx.SpinCtrl( self._basic_info_panel, min = 5, max = 1200 )
#
self._form_fields_panel = ClientGUICommon.StaticBox( self._imageboard_panel, 'form fields' )
self._form_fields = ClientGUICommon.SaneListCtrl( self._form_fields_panel, 350, [ ( 'name', 120 ), ( 'type', 120 ), ( 'default', -1 ), ( 'editable', 120 ) ], delete_key_callback = self.Delete )
self._add = wx.Button( self._form_fields_panel, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
self._edit = wx.Button( self._form_fields_panel, label = 'edit' )
self._edit.Bind( wx.EVT_BUTTON, self.EventEdit )
self._delete = wx.Button( self._form_fields_panel, label = 'delete' )
self._delete.Bind( wx.EVT_BUTTON, self.EventDelete )
#
self._restrictions_panel = ClientGUICommon.StaticBox( self._imageboard_panel, 'restrictions' )
self._min_resolution = ClientGUICommon.NoneableSpinCtrl( self._restrictions_panel, 'min resolution', num_dimensions = 2 )
self._max_resolution = ClientGUICommon.NoneableSpinCtrl( self._restrictions_panel, 'max resolution', num_dimensions = 2 )
self._max_file_size = ClientGUICommon.NoneableSpinCtrl( self._restrictions_panel, 'max file size (KB)', multiplier = 1024 )
self._allowed_mimes_panel = ClientGUICommon.StaticBox( self._restrictions_panel, 'allowed mimes' )
self._mimes = wx.ListBox( self._allowed_mimes_panel )
self._mime_choice = wx.Choice( self._allowed_mimes_panel )
self._add_mime = wx.Button( self._allowed_mimes_panel, label = 'add' )
self._add_mime.Bind( wx.EVT_BUTTON, self.EventAddMime )
self._remove_mime = wx.Button( self._allowed_mimes_panel, label = 'remove' )
self._remove_mime.Bind( wx.EVT_BUTTON, self.EventRemoveMime )
def PopulateControls():
#
self._post_url.SetValue( post_url )
self._flood_time.SetRange( 5, 1200 )
self._flood_time.SetValue( flood_time )
#
for ( name, field_type, default, editable ) in form_fields:
self._form_fields.Append( ( name, CC.field_string_lookup[ field_type ], HydrusData.ToUnicode( default ), HydrusData.ToUnicode( editable ) ), ( name, field_type, default, editable ) )
#
if CC.RESTRICTION_MIN_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MIN_RESOLUTION ]
else: value = None
self._min_resolution.SetValue( value )
if CC.RESTRICTION_MAX_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MAX_RESOLUTION ]
else: value = None
self._max_resolution.SetValue( value )
if CC.RESTRICTION_MAX_FILE_SIZE in restrictions: value = restrictions[ CC.RESTRICTION_MAX_FILE_SIZE ]
else: value = None
self._max_file_size.SetValue( value )
if CC.RESTRICTION_ALLOWED_MIMES in restrictions: mimes = restrictions[ CC.RESTRICTION_ALLOWED_MIMES ]
else: mimes = []
for mime in mimes: self._mimes.Append( HC.mime_string_lookup[ mime ], mime )
for mime in HC.ALLOWED_MIMES: self._mime_choice.Append( HC.mime_string_lookup[ mime ], mime )
self._mime_choice.SetSelection( 0 )
def ArrangeControls():
gridbox = wx.FlexGridSizer( 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.Add( wx.StaticText( self._basic_info_panel, label = 'POST URL' ), CC.FLAGS_VCENTER )
gridbox.Add( self._post_url, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( wx.StaticText( self._basic_info_panel, label = 'flood time' ), CC.FLAGS_VCENTER )
gridbox.Add( self._flood_time, CC.FLAGS_EXPAND_BOTH_WAYS )
self._basic_info_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
h_b_box = wx.BoxSizer( wx.HORIZONTAL )
h_b_box.Add( self._add, CC.FLAGS_VCENTER )
h_b_box.Add( self._edit, CC.FLAGS_VCENTER )
h_b_box.Add( self._delete, CC.FLAGS_VCENTER )
self._form_fields_panel.Add( self._form_fields, CC.FLAGS_EXPAND_BOTH_WAYS )
self._form_fields_panel.Add( h_b_box, CC.FLAGS_BUTTON_SIZER )
#
mime_buttons_box = wx.BoxSizer( wx.HORIZONTAL )
mime_buttons_box.Add( self._mime_choice, CC.FLAGS_VCENTER )
mime_buttons_box.Add( self._add_mime, CC.FLAGS_VCENTER )
mime_buttons_box.Add( self._remove_mime, CC.FLAGS_VCENTER )
self._allowed_mimes_panel.Add( self._mimes, CC.FLAGS_EXPAND_BOTH_WAYS )
self._allowed_mimes_panel.Add( mime_buttons_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._restrictions_panel.Add( self._min_resolution, CC.FLAGS_EXPAND_PERPENDICULAR )
self._restrictions_panel.Add( self._max_resolution, CC.FLAGS_EXPAND_PERPENDICULAR )
self._restrictions_panel.Add( self._max_file_size, CC.FLAGS_EXPAND_PERPENDICULAR )
self._restrictions_panel.Add( self._allowed_mimes_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
#
self._imageboard_panel.Add( self._basic_info_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self._imageboard_panel.Add( self._form_fields_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self._imageboard_panel.Add( self._restrictions_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._imageboard_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
InitialiseControls()
PopulateControls()
ArrangeControls()
def _GetInfo( self ):
imageboard_name = self._imageboard.GetName()
post_url = self._post_url.GetValue()
flood_time = self._flood_time.GetValue()
form_fields = self._form_fields.GetClientData()
restrictions = {}
value = self._min_resolution.GetValue()
if value is not None: restrictions[ CC.RESTRICTION_MIN_RESOLUTION ] = value
value = self._max_resolution.GetValue()
if value is not None: restrictions[ CC.RESTRICTION_MAX_RESOLUTION ] = value
value = self._max_file_size.GetValue()
if value is not None: restrictions[ CC.RESTRICTION_MAX_FILE_SIZE ] = value
mimes = [ self._mimes.GetClientData( i ) for i in range( self._mimes.GetCount() ) ]
if len( mimes ) > 0: restrictions[ CC.RESTRICTION_ALLOWED_MIMES ] = mimes
return ( imageboard_name, post_url, flood_time, form_fields, restrictions )
def Delete( self ): self._form_fields.RemoveAllSelected()
def EventAdd( self, event ):
with ClientGUIDialogs.DialogInputNewFormField( self ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( name, field_type, default, editable ) = dlg.GetFormField()
if name in [ form_field[0] for form_field in self._form_fields.GetClientData() ]:
wx.MessageBox( 'There is already a field named ' + name )
self.EventAdd( event )
return
self._form_fields.Append( ( name, CC.field_string_lookup[ field_type ], HydrusData.ToUnicode( default ), HydrusData.ToUnicode( editable ) ), ( name, field_type, default, editable ) )
def EventAddMime( self, event ):
selection = self._mime_choice.GetSelection()
if selection != wx.NOT_FOUND:
mime = self._mime_choice.GetClientData( selection )
existing_mimes = [ self._mimes.GetClientData( i ) for i in range( self._mimes.GetCount() ) ]
if mime not in existing_mimes: self._mimes.Append( HC.mime_string_lookup[ mime ], mime )
def EventDelete( self, event ): self.Delete()
def EventRemoveMime( self, event ):
selection = self._mimes.GetSelection()
if selection != wx.NOT_FOUND: self._mimes.Delete( selection )
def EventEdit( self, event ):
indices = self._form_fields.GetAllSelected()
for index in indices:
( name, field_type, default, editable ) = self._form_fields.GetClientData( index )
form_field = ( name, field_type, default, editable )
with ClientGUIDialogs.DialogInputNewFormField( self, form_field ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
old_name = name
( name, field_type, default, editable ) = dlg.GetFormField()
if old_name != name:
if name in [ form_field[0] for form_field in self._form_fields.GetClientData() ]: raise Exception( 'You already have a form field called ' + name + '; delete or edit that one first' )
self._form_fields.UpdateRow( index, ( name, CC.field_string_lookup[ field_type ], HydrusData.ToUnicode( default ), HydrusData.ToUnicode( editable ) ), ( name, field_type, default, editable ) )
def GetImageboard( self ):
( name, post_url, flood_time, form_fields, restrictions ) = self._GetInfo()
return ClientData.Imageboard( name, post_url, flood_time, form_fields, restrictions )
def HasChanges( self ):
if self._is_new: return True
( my_name, my_post_url, my_flood_time, my_form_fields, my_restrictions ) = self._GetInfo()
( post_url, flood_time, form_fields, restrictions ) = self._imageboard.GetBoardInfo()
if post_url != my_post_url: return True
if flood_time != my_flood_time: return True
if set( [ tuple( item ) for item in form_fields ] ) != set( [ tuple( item ) for item in my_form_fields ] ): return True
if restrictions != my_restrictions: return True
return False
def Update( self, imageboard ):
( post_url, flood_time, form_fields, restrictions ) = imageboard.GetBoardInfo()
self._post_url.SetValue( post_url )
self._flood_time.SetValue( flood_time )
self._form_fields.ClearAll()
self._form_fields.InsertColumn( 0, 'name', width = 120 )
self._form_fields.InsertColumn( 1, 'type', width = 120 )
self._form_fields.InsertColumn( 2, 'default' )
self._form_fields.InsertColumn( 3, 'editable', width = 120 )
self._form_fields.setResizeColumn( 3 ) # default
for ( name, field_type, default, editable ) in form_fields:
self._form_fields.Append( ( name, CC.field_string_lookup[ field_type ], HydrusData.ToUnicode( default ), HydrusData.ToUnicode( editable ) ), ( name, field_type, default, editable ) )
if CC.RESTRICTION_MIN_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MIN_RESOLUTION ]
else: value = None
self._min_resolution.SetValue( value )
if CC.RESTRICTION_MAX_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MAX_RESOLUTION ]
else: value = None
self._max_resolution.SetValue( value )
if CC.RESTRICTION_MAX_FILE_SIZE in restrictions: value = restrictions[ CC.RESTRICTION_MAX_FILE_SIZE ]
else: value = None
self._max_file_size.SetValue( value )
self._mimes.Clear()
if CC.RESTRICTION_ALLOWED_MIMES in restrictions: mimes = restrictions[ CC.RESTRICTION_ALLOWED_MIMES ]
else: mimes = []
for mime in mimes: self._mimes.Append( HC.mime_string_lookup[ mime ], mime )
'''
class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage import folders' )
import_folders_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
columns = [ ( 'name', 24 ), ( 'path', -1 ), ( 'paused', 8 ), ( 'check period', 24 ) ]
self._import_folders = ClientGUIListCtrl.BetterListCtrl( import_folders_panel, 'import_folders', 8, 36, columns, self._ConvertImportFolderToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit )
import_folders_panel.SetListCtrl( self._import_folders )
import_folders_panel.AddButton( 'add', self._Add )
import_folders_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
import_folders_panel.AddDeleteButton()
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
import_folders = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FOLDER )
self._import_folders.SetData( import_folders )
self._import_folders.Sort()
#
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.Add( self._ok, CC.FLAGS_VCENTER )
buttons.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Here you can set the client to regularly check certain folders for new files to import.'
vbox.Add( ClientGUICommon.BetterStaticText( self, intro ), CC.FLAGS_EXPAND_PERPENDICULAR )
warning = 'WARNING: Import folders check (and potentially move/delete!) the contents of all subdirectories as well as the base directory!'
warning_st = ClientGUICommon.BetterStaticText( self, warning )
warning_st.SetForegroundColour( ( 128, 0, 0 ) )
vbox.Add( warning_st, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( import_folders_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
#
( x, y ) = self.GetEffectiveMinSize()
if x < 780: x = 780
if y < 480: y = 480
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def _Add( self ):
import_folder = ClientImportLocal.ImportFolder( 'import folder' )
with DialogManageImportFoldersEdit( self, import_folder ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
import_folder = dlg.GetInfo()
import_folder.SetNonDupeName( self._GetExistingNames() )
self._import_folders.AddDatas( ( import_folder, ) )
self._import_folders.Sort()
def _ConvertImportFolderToListCtrlTuples( self, import_folder ):
( name, path, paused, check_regularly, check_period ) = import_folder.ToListBoxTuple()
if paused:
pretty_paused = 'yes'
else:
pretty_paused = ''
if not check_regularly:
pretty_check_period = 'not checking regularly'
else:
pretty_check_period = HydrusData.TimeDeltaToPrettyTimeDelta( check_period )
sort_tuple = ( name, path, paused, check_period )
display_tuple = ( name, path, pretty_paused, pretty_check_period )
return ( display_tuple, sort_tuple )
def _Edit( self ):
import_folders = self._import_folders.GetData( only_selected = True )
for import_folder in import_folders:
with DialogManageImportFoldersEdit( self, import_folder ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
self._import_folders.DeleteDatas( ( import_folder, ) )
edited_import_folder = dlg.GetInfo()
edited_import_folder.SetNonDupeName( self._GetExistingNames() )
self._import_folders.AddDatas( ( edited_import_folder, ) )
self._import_folders.Sort()
def _GetExistingNames( self ):
import_folders = self._import_folders.GetData()
names = { import_folder.GetName() for import_folder in import_folders }
return names
def EventOK( self, event ):
existing_db_names = set( HG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FOLDER ) )
good_names = set()
import_folders = self._import_folders.GetData()
for import_folder in import_folders:
good_names.add( import_folder.GetName() )
HG.client_controller.Write( 'serialisable', import_folder )
names_to_delete = existing_db_names.difference( good_names )
for name in names_to_delete:
HG.client_controller.Write( 'delete_serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FOLDER, name )
HG.client_controller.pub( 'notify_new_import_folders' )
self.EndModal( wx.ID_OK )
class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
def __init__( self, parent, import_folder ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'edit import folder' )
self._import_folder = import_folder
( name, path, mimes, file_import_options, tag_import_options, tag_service_keys_to_filename_tagging_options, actions, action_locations, period, check_regularly, paused, check_now, show_working_popup, publish_files_to_popup_button, publish_files_to_page ) = self._import_folder.ToTuple()
self._panel = wx.ScrolledWindow( self )
self._folder_box = ClientGUICommon.StaticBox( self._panel, 'folder options' )
self._name = wx.TextCtrl( self._folder_box )
self._path = wx.DirPickerCtrl( self._folder_box, style = wx.DIRP_USE_TEXTCTRL )
self._check_regularly = wx.CheckBox( self._folder_box )
self._period = ClientGUITime.TimeDeltaButton( self._folder_box, min = 3 * 60, days = True, hours = True, minutes = True )
self._paused = wx.CheckBox( self._folder_box )
self._check_now = wx.CheckBox( self._folder_box )
self._show_working_popup = wx.CheckBox( self._folder_box )
self._publish_files_to_popup_button = wx.CheckBox( self._folder_box )
self._publish_files_to_page = wx.CheckBox( self._folder_box )
self._file_seed_cache_button = ClientGUIFileSeedCache.FileSeedCacheButton( self._folder_box, HG.client_controller, self._import_folder.GetFileSeedCache, file_seed_cache_set_callable = self._import_folder.SetFileSeedCache )
#
self._file_box = ClientGUICommon.StaticBox( self._panel, 'file options' )
self._mimes = ClientGUIOptionsPanels.OptionsPanelMimes( self._file_box, HC.ALLOWED_MIMES )
def create_choice():
choice = ClientGUICommon.BetterChoice( self._file_box )
for if_id in ( CC.IMPORT_FOLDER_DELETE, CC.IMPORT_FOLDER_IGNORE, CC.IMPORT_FOLDER_MOVE ):
choice.Append( CC.import_folder_string_lookup[ if_id ], if_id )
choice.Bind( wx.EVT_CHOICE, self.EventCheckLocations )
return choice
self._action_successful = create_choice()
self._location_successful = wx.DirPickerCtrl( self._file_box, style = wx.DIRP_USE_TEXTCTRL )
self._action_redundant = create_choice()
self._location_redundant = wx.DirPickerCtrl( self._file_box, style = wx.DIRP_USE_TEXTCTRL )
self._action_deleted = create_choice()
self._location_deleted = wx.DirPickerCtrl( self._file_box, style = wx.DIRP_USE_TEXTCTRL )
self._action_failed = create_choice()
self._location_failed = wx.DirPickerCtrl( self._file_box, style = wx.DIRP_USE_TEXTCTRL )
show_downloader_options = False
self._file_import_options = ClientGUIImport.FileImportOptionsButton( self._file_box, file_import_options, show_downloader_options )
#
self._tag_box = ClientGUICommon.StaticBox( self._panel, 'tag options' )
self._tag_import_options = ClientGUIImport.TagImportOptionsButton( self._tag_box, tag_import_options, show_downloader_options )
self._filename_tagging_options_box = ClientGUICommon.StaticBox( self._tag_box, 'filename tagging' )
filename_tagging_options_panel = ClientGUIListCtrl.BetterListCtrlPanel( self._filename_tagging_options_box )
columns = [ ( 'filename tagging options services', -1 ) ]
self._filename_tagging_options = ClientGUIListCtrl.BetterListCtrl( filename_tagging_options_panel, 'filename_tagging_options', 5, 25, columns, self._ConvertFilenameTaggingOptionsToListCtrlTuples, use_simple_delete = True, activation_callback = self._EditFilenameTaggingOptions )
filename_tagging_options_panel.SetListCtrl( self._filename_tagging_options )
filename_tagging_options_panel.AddButton( 'add', self._AddFilenameTaggingOptions )
filename_tagging_options_panel.AddButton( 'edit', self._EditFilenameTaggingOptions, enabled_only_on_selection = True )
filename_tagging_options_panel.AddDeleteButton()
services_manager = HG.client_controller.services_manager
#
self._ok = wx.Button( self, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._name.SetValue( name )
self._path.SetPath( path )
self._check_regularly.SetValue( check_regularly )
self._period.SetValue( period )
self._paused.SetValue( paused )
self._show_working_popup.SetValue( show_working_popup )
self._publish_files_to_popup_button.SetValue( publish_files_to_popup_button )
self._publish_files_to_page.SetValue( publish_files_to_page )
self._mimes.SetValue( mimes )
self._action_successful.SelectClientData( actions[ CC.STATUS_SUCCESSFUL_AND_NEW ] )
if CC.STATUS_SUCCESSFUL_AND_NEW in action_locations:
self._location_successful.SetPath( action_locations[ CC.STATUS_SUCCESSFUL_AND_NEW ] )
self._action_redundant.SelectClientData( actions[ CC.STATUS_SUCCESSFUL_BUT_REDUNDANT ] )
if CC.STATUS_SUCCESSFUL_BUT_REDUNDANT in action_locations:
self._location_redundant.SetPath( action_locations[ CC.STATUS_SUCCESSFUL_BUT_REDUNDANT ] )
self._action_deleted.SelectClientData( actions[ CC.STATUS_DELETED ] )
if CC.STATUS_DELETED in action_locations:
self._location_deleted.SetPath( action_locations[ CC.STATUS_DELETED ] )
self._action_failed.SelectClientData( actions[ CC.STATUS_ERROR ] )
if CC.STATUS_ERROR in action_locations:
self._location_failed.SetPath( action_locations[ CC.STATUS_ERROR ] )
good_tag_service_keys_to_filename_tagging_options = { service_key : filename_tagging_options for ( service_key, filename_tagging_options ) in tag_service_keys_to_filename_tagging_options.items() if HG.client_controller.services_manager.ServiceExists( service_key ) }
self._filename_tagging_options.AddDatas( good_tag_service_keys_to_filename_tagging_options.items() )
self._filename_tagging_options.Sort()
#
rows = []
rows.append( ( 'name: ', self._name ) )
rows.append( ( 'folder path: ', self._path ) )
rows.append( ( 'currently paused (if set, will not ever do any work): ', self._paused ) )
rows.append( ( 'check regularly?: ', self._check_regularly ) )
rows.append( ( 'check period: ', self._period ) )
rows.append( ( 'check on manage dialog ok: ', self._check_now ) )
rows.append( ( 'show a popup while working: ', self._show_working_popup ) )
rows.append( ( 'publish new files to a popup button: ', self._publish_files_to_popup_button ) )
rows.append( ( 'publish new files to a page: ', self._publish_files_to_page ) )
rows.append( ( 'review currently cached import paths: ', self._file_seed_cache_button ) )
gridbox = ClientGUICommon.WrapInGrid( self._folder_box, rows )
self._folder_box.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
rows = []
rows.append( ( 'mimes to import: ', self._mimes ) )
mimes_gridbox = ClientGUICommon.WrapInGrid( self._file_box, rows, expand_text = True )
gridbox = wx.FlexGridSizer( 3 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.Add( wx.StaticText( self._file_box, label = 'when a file imports successfully: '), CC.FLAGS_VCENTER )
gridbox.Add( self._action_successful, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._location_successful, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( wx.StaticText( self._file_box, label = 'when a file is already in the db: '), CC.FLAGS_VCENTER )
gridbox.Add( self._action_redundant, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._location_redundant, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( wx.StaticText( self._file_box, label = 'when a file has previously been deleted from the db: '), CC.FLAGS_VCENTER )
gridbox.Add( self._action_deleted, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._location_deleted, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( wx.StaticText( self._file_box, label = 'when a file fails to import: '), CC.FLAGS_VCENTER )
gridbox.Add( self._action_failed, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._location_failed, CC.FLAGS_EXPAND_BOTH_WAYS )
self._file_box.Add( mimes_gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._file_box.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._file_box.Add( self._file_import_options, CC.FLAGS_EXPAND_PERPENDICULAR )
#
self._filename_tagging_options_box.Add( filename_tagging_options_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self._tag_box.Add( self._tag_import_options, CC.FLAGS_EXPAND_PERPENDICULAR )
self._tag_box.Add( self._filename_tagging_options_box, CC.FLAGS_EXPAND_BOTH_WAYS )
#
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.Add( self._ok, CC.FLAGS_VCENTER )
buttons.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._folder_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._file_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._panel.SetSizer( vbox )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
( max_x, max_y ) = ClientGUITopLevelWindows.GetDisplaySize( self )
x = min( x + 25, max_x )
y = min( y + 25, max_y )
self._panel.SetScrollRate( 20, 20 )
self.SetInitialSize( ( x, y ) )
self._CheckLocations()
self._check_regularly.Bind( wx.EVT_CHECKBOX, self.EventCheckRegularly )
self._UpdateCheckRegularly()
wx.CallAfter( self._ok.SetFocus )
def _AddFilenameTaggingOptions( self ):
service_key = ClientGUIDialogs.SelectServiceKey( HC.TAG_SERVICES )
if service_key is None:
return
existing_service_keys = { service_key for ( service_key, filename_tagging_options ) in self._filename_tagging_options.GetData() }
if service_key in existing_service_keys:
wx.MessageBox( 'You already have an entry for that service key! Please try editing it instead!' )
return
with ClientGUITopLevelWindows.DialogEdit( self, 'edit filename tagging options' ) as dlg:
filename_tagging_options = ClientImportOptions.FilenameTaggingOptions()
panel = ClientGUIImport.EditFilenameTaggingOptionPanel( dlg, service_key, filename_tagging_options )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
filename_tagging_options = panel.GetValue()
self._filename_tagging_options.AddDatas( [ ( service_key, filename_tagging_options ) ] )
self._filename_tagging_options.Sort()
def _CheckLocations( self ):
if self._action_successful.GetChoice() == CC.IMPORT_FOLDER_MOVE:
self._location_successful.Enable()
else:
self._location_successful.Disable()
if self._action_redundant.GetChoice() == CC.IMPORT_FOLDER_MOVE:
self._location_redundant.Enable()
else:
self._location_redundant.Disable()
if self._action_deleted.GetChoice() == CC.IMPORT_FOLDER_MOVE:
self._location_deleted.Enable()
else:
self._location_deleted.Disable()
if self._action_failed.GetChoice() == CC.IMPORT_FOLDER_MOVE:
self._location_failed.Enable()
else:
self._location_failed.Disable()
def _ConvertFilenameTaggingOptionsToListCtrlTuples( self, data ):
( service_key, filename_tagging_options ) = data
name = HG.client_controller.services_manager.GetName( service_key )
display_tuple = ( name, )
sort_tuple = ( name, )
return ( display_tuple, sort_tuple )
def _EditFilenameTaggingOptions( self ):
selected_data = self._filename_tagging_options.GetData( only_selected = True )
for data in selected_data:
( service_key, filename_tagging_options ) = data
with ClientGUITopLevelWindows.DialogEdit( self, 'edit filename tagging options' ) as dlg:
panel = ClientGUIImport.EditFilenameTaggingOptionPanel( dlg, service_key, filename_tagging_options )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
self._filename_tagging_options.DeleteDatas( ( data, ) )
filename_tagging_options = panel.GetValue()
self._filename_tagging_options.AddDatas( [ ( service_key, filename_tagging_options ) ] )
else:
break
def _UpdateCheckRegularly( self ):
if self._check_regularly.GetValue():
self._period.Enable()
else:
self._period.Disable()
def EventCheckRegularly( self, event ):
self._UpdateCheckRegularly()
def EventCheckLocations( self, event ):
self._CheckLocations()
def EventOK( self, event ):
path = self._path.GetPath()
if path in ( '', None ):
wx.MessageBox( 'You must enter a path to import from!' )
return
if not os.path.exists( path ):
wx.MessageBox( 'The path you have entered--"' + path + '"--does not exist! The dialog will not force you to correct it, but this import folder will do no work as long as the location is missing!' )
if HC.BASE_DIR.startswith( path ) or HG.client_controller.GetDBDir().startswith( path ):
wx.MessageBox( 'You cannot set an import path that includes your install or database directory!' )
return
if self._action_successful.GetChoice() == CC.IMPORT_FOLDER_MOVE:
path = self._location_successful.GetPath()
if path in ( '', None ):
wx.MessageBox( 'You must enter a path for your successful file move location!' )
return
if not os.path.exists( path ):
wx.MessageBox( 'The path you have entered for your successful file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )
if self._action_redundant.GetChoice() == CC.IMPORT_FOLDER_MOVE:
path = self._location_redundant.GetPath()
if path in ( '', None ):
wx.MessageBox( 'You must enter a path for your redundant file move location!' )
return
if not os.path.exists( path ):
wx.MessageBox( 'The path you have entered for your redundant file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )
if self._action_deleted.GetChoice() == CC.IMPORT_FOLDER_MOVE:
path = self._location_deleted.GetPath()
if path in ( '', None ):
wx.MessageBox( 'You must enter a path for your deleted file move location!' )
return
if not os.path.exists( path ):
wx.MessageBox( 'The path you have entered for your deleted file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )
if self._action_failed.GetChoice() == CC.IMPORT_FOLDER_MOVE:
path = self._location_failed.GetPath()
if path in ( '', None ):
wx.MessageBox( 'You must enter a path for your failed file move location!' )
return
if not os.path.exists( path ):
wx.MessageBox( 'The path you have entered for your failed file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )
self.EndModal( wx.ID_OK )
def GetInfo( self ):
name = self._name.GetValue()
path = HydrusData.ToUnicode( self._path.GetPath() )
mimes = self._mimes.GetValue()
file_import_options = self._file_import_options.GetValue()
tag_import_options = self._tag_import_options.GetValue()
actions = {}
action_locations = {}
actions[ CC.STATUS_SUCCESSFUL_AND_NEW ] = self._action_successful.GetChoice()
if actions[ CC.STATUS_SUCCESSFUL_AND_NEW ] == CC.IMPORT_FOLDER_MOVE:
action_locations[ CC.STATUS_SUCCESSFUL_AND_NEW ] = HydrusData.ToUnicode( self._location_successful.GetPath() )
actions[ CC.STATUS_SUCCESSFUL_BUT_REDUNDANT ] = self._action_redundant.GetChoice()
if actions[ CC.STATUS_SUCCESSFUL_BUT_REDUNDANT ] == CC.IMPORT_FOLDER_MOVE:
action_locations[ CC.STATUS_SUCCESSFUL_BUT_REDUNDANT ] = HydrusData.ToUnicode( self._location_redundant.GetPath() )
actions[ CC.STATUS_DELETED ] = self._action_deleted.GetChoice()
if actions[ CC.STATUS_DELETED] == CC.IMPORT_FOLDER_MOVE:
action_locations[ CC.STATUS_DELETED ] = HydrusData.ToUnicode( self._location_deleted.GetPath() )
actions[ CC.STATUS_ERROR ] = self._action_failed.GetChoice()
if actions[ CC.STATUS_ERROR ] == CC.IMPORT_FOLDER_MOVE:
action_locations[ CC.STATUS_ERROR ] = HydrusData.ToUnicode( self._location_failed.GetPath() )
period = self._period.GetValue()
check_regularly = self._check_regularly.GetValue()
paused = self._paused.GetValue()
check_now = self._check_now.GetValue()
show_working_popup = self._show_working_popup.GetValue()
publish_files_to_popup_button = self._publish_files_to_popup_button.GetValue()
publish_files_to_page = self._publish_files_to_page.GetValue()
tag_service_keys_to_filename_tagging_options = dict( self._filename_tagging_options.GetData() )
self._import_folder.SetTuple( name, path, mimes, file_import_options, tag_import_options, tag_service_keys_to_filename_tagging_options, actions, action_locations, period, check_regularly, paused, check_now, show_working_popup, publish_files_to_popup_button, publish_files_to_page )
return self._import_folder
class DialogManageRatings( ClientGUIDialogs.Dialog ):
def __init__( self, parent, media ):
self._hashes = set()
for m in media: self._hashes.update( m.GetHashes() )
( remember, position ) = HC.options[ 'rating_dialog_position' ]
if remember and position is not None:
my_position = 'custom'
wx.CallAfter( self.SetPosition, position )
else:
my_position = 'topleft'
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage ratings for ' + HydrusData.ToHumanInt( len( self._hashes ) ) + ' files', position = my_position )
#
like_services = HG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_LIKE, ), randomised = False )
numerical_services = HG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_NUMERICAL, ), randomised = False )
self._panels = []
if len( like_services ) > 0:
self._panels.append( self._LikePanel( self, like_services, media ) )
if len( numerical_services ) > 0:
self._panels.append( self._NumericalPanel( self, numerical_services, media ) )
self._apply = wx.Button( self, id = wx.ID_OK, label = 'apply' )
self._apply.Bind( wx.EVT_BUTTON, self.EventOK )
self._apply.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
buttonbox = wx.BoxSizer( wx.HORIZONTAL )
buttonbox.Add( self._apply, CC.FLAGS_VCENTER )
buttonbox.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
for panel in self._panels:
vbox.Add( panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( buttonbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
#
self._my_shortcut_handler = ClientGUIShortcuts.ShortcutsHandler( self, [ 'media' ] )
def EventOK( self, event ):
try:
service_keys_to_content_updates = {}
for panel in self._panels:
sub_service_keys_to_content_updates = panel.GetContentUpdates()
service_keys_to_content_updates.update( sub_service_keys_to_content_updates )
if len( service_keys_to_content_updates ) > 0:
HG.client_controller.Write( 'content_updates', service_keys_to_content_updates )
( remember, position ) = HC.options[ 'rating_dialog_position' ]
current_position = self.GetPosition()
if remember and position != current_position:
HC.options[ 'rating_dialog_position' ] = ( remember, current_position )
HG.client_controller.Write( 'save_options', HC.options )
finally:
self.EndModal( wx.ID_OK )
def ProcessApplicationCommand( self, command ):
command_processed = True
command_type = command.GetCommandType()
data = command.GetData()
if command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
action = data
if action == 'manage_file_ratings':
self.EventOK( None )
else:
command_processed = False
else:
command_processed = False
return command_processed
class _LikePanel( wx.Panel ):
def __init__( self, parent, services, media ):
wx.Panel.__init__( self, parent )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_FRAMEBK ) )
self._services = services
self._media = media
self._service_keys_to_controls = {}
self._service_keys_to_original_ratings_states = {}
rows = []
for service in self._services:
name = service.GetName()
service_key = service.GetServiceKey()
rating_state = ClientRatings.GetLikeStateFromMedia( self._media, service_key )
control = ClientGUICommon.RatingLikeDialog( self, service_key )
control.SetRatingState( rating_state )
self._service_keys_to_controls[ service_key ] = control
self._service_keys_to_original_ratings_states[ service_key ] = rating_state
rows.append( ( name + ': ', control ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows, expand_text = True )
self.SetSizer( gridbox )
def GetContentUpdates( self ):
service_keys_to_content_updates = {}
hashes = { hash for hash in itertools.chain.from_iterable( ( media.GetHashes() for media in self._media ) ) }
for ( service_key, control ) in self._service_keys_to_controls.items():
original_rating_state = self._service_keys_to_original_ratings_states[ service_key ]
rating_state = control.GetRatingState()
if rating_state != original_rating_state:
if rating_state == ClientRatings.LIKE: rating = 1
elif rating_state == ClientRatings.DISLIKE: rating = 0
else: rating = None
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, hashes ) )
service_keys_to_content_updates[ service_key ] = ( content_update, )
return service_keys_to_content_updates
class _NumericalPanel( wx.Panel ):
def __init__( self, parent, services, media ):
wx.Panel.__init__( self, parent )
self._services = services
self._media = media
self._service_keys_to_controls = {}
self._service_keys_to_original_ratings_states = {}
rows = []
for service in self._services:
name = service.GetName()
service_key = service.GetServiceKey()
( rating_state, rating ) = ClientRatings.GetNumericalStateFromMedia( self._media, service_key )
control = ClientGUICommon.RatingNumericalDialog( self, service_key )
if rating_state != ClientRatings.SET:
control.SetRatingState( rating_state )
else:
control.SetRating( rating )
self._service_keys_to_controls[ service_key ] = control
self._service_keys_to_original_ratings_states[ service_key ] = ( rating_state, rating )
rows.append( ( name + ': ', control ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows, expand_text = True )
self.SetSizer( gridbox )
def GetContentUpdates( self ):
service_keys_to_content_updates = {}
hashes = { hash for hash in itertools.chain.from_iterable( ( media.GetHashes() for media in self._media ) ) }
for ( service_key, control ) in self._service_keys_to_controls.items():
( original_rating_state, original_rating ) = self._service_keys_to_original_ratings_states[ service_key ]
rating_state = control.GetRatingState()
if rating_state == ClientRatings.NULL:
rating = None
else:
rating = control.GetRating()
if rating != original_rating:
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, hashes ) )
service_keys_to_content_updates[ service_key ] = ( content_update, )
return service_keys_to_content_updates
class DialogManageUPnP( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
title = 'manage local upnp'
ClientGUIDialogs.Dialog.__init__( self, parent, title )
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
self._status_st = ClientGUICommon.BetterStaticText( self )
self._mappings_list_ctrl = ClientGUIListCtrl.SaneListCtrl( self, 480, [ ( 'description', -1 ), ( 'internal ip', 100 ), ( 'internal port', 80 ), ( 'external ip', 100 ), ( 'external port', 80 ), ( 'protocol', 80 ), ( 'lease', 80 ) ], delete_key_callback = self.RemoveMappings, activation_callback = self.EditMappings )
self._add_custom = wx.Button( self, label = 'add custom mapping' )
self._add_custom.Bind( wx.EVT_BUTTON, self.EventAddCustomMapping )
self._edit = wx.Button( self, label = 'edit mapping' )
self._edit.Bind( wx.EVT_BUTTON, self.EventEditMapping )
self._remove = wx.Button( self, label = 'remove mapping' )
self._remove.Bind( wx.EVT_BUTTON, self.EventRemoveMapping )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
#
edit_buttons = wx.BoxSizer( wx.HORIZONTAL )
edit_buttons.Add( self._add_custom, CC.FLAGS_VCENTER )
edit_buttons.Add( self._edit, CC.FLAGS_VCENTER )
edit_buttons.Add( self._remove, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._status_st, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._mappings_list_ctrl, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( edit_buttons, CC.FLAGS_BUTTON_SIZER )
vbox.Add( self._ok, CC.FLAGS_LONE_BUTTON )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 760 )
self.SetInitialSize( ( x, y ) )
#
self._RefreshMappings()
def _RefreshMappings( self ):
def THREADdo_it():
def wx_code( mappings ):
if not self:
return
self._mappings = mappings
for mapping in self._mappings:
self._mappings_list_ctrl.Append( mapping, mapping )
self._status_st.SetLabelText( '' )
try:
mappings = HydrusNATPunch.GetUPnPMappings()
except Exception as e:
HydrusData.ShowException( e )
wx.CallAfter( wx.MessageBox, 'Could not load mappings:' + os.linesep * 2 + str( e ) )
return
wx.CallAfter( wx_code, mappings )
self._status_st.SetLabelText( 'Refreshing mappings--please wait...' )
self._mappings_list_ctrl.DeleteAllItems()
HG.client_controller.CallToThread( THREADdo_it )
def EditMappings( self ):
do_refresh = False
for index in self._mappings_list_ctrl.GetAllSelected():
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = self._mappings_list_ctrl.GetClientData( index )
with ClientGUIDialogs.DialogInputUPnPMapping( self, external_port, protocol, internal_port, description, duration ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( external_port, protocol, internal_port, description, duration ) = dlg.GetInfo()
HydrusNATPunch.RemoveUPnPMapping( external_port, protocol )
internal_client = HydrusNATPunch.GetLocalIP()
HydrusNATPunch.AddUPnPMapping( internal_client, internal_port, external_port, protocol, description, duration = duration )
do_refresh = True
if do_refresh:
self._RefreshMappings()
def EventAddCustomMapping( self, event ):
do_refresh = False
external_port = HC.DEFAULT_SERVICE_PORT
protocol = 'TCP'
internal_port = HC.DEFAULT_SERVICE_PORT
description = 'hydrus service'
duration = 0
with ClientGUIDialogs.DialogInputUPnPMapping( self, external_port, protocol, internal_port, description, duration ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( external_port, protocol, internal_port, description, duration ) = dlg.GetInfo()
for ( existing_description, existing_internal_ip, existing_internal_port, existing_external_ip, existing_external_port, existing_protocol, existing_lease ) in self._mappings:
if external_port == existing_external_port and protocol == existing_protocol:
wx.MessageBox( 'That external port already exists!' )
return
internal_client = HydrusNATPunch.GetLocalIP()
HydrusNATPunch.AddUPnPMapping( internal_client, internal_port, external_port, protocol, description, duration = duration )
do_refresh = True
if do_refresh:
self._RefreshMappings()
def EventEditMapping( self, event ):
self.EditMappings()
def EventOK( self, event ):
self.EndModal( wx.ID_OK )
def EventRemoveMapping( self, event ):
self.RemoveMappings()
def RemoveMappings( self ):
do_refresh = False
for index in self._mappings_list_ctrl.GetAllSelected():
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = self._mappings_list_ctrl.GetClientData( index )
HydrusNATPunch.RemoveUPnPMapping( external_port, protocol )
do_refresh = True
if do_refresh:
self._RefreshMappings()