hydrus/include/ClientData.py

2985 lines
104 KiB
Python

import ClientConstants as CC
import ClientDefaults
import ClientDownloading
import ClientThreading
import collections
import HydrusConstants as HC
import HydrusData
import HydrusExceptions
import HydrusGlobals as HG
import HydrusPaths
import HydrusSerialisable
import HydrusTags
import threading
import traceback
import os
import sqlite3
import sys
import time
import wx
import wx.lib.colourutils
import yaml
def AddPaddingToDimensions( dimensions, padding ):
( x, y ) = dimensions
return ( x + padding, y + padding )
def CatchExceptionClient( etype, value, tb ):
try:
trace_list = traceback.format_tb( tb )
trace = ''.join( trace_list )
pretty_value = HydrusData.ToUnicode( value )
if os.linesep in pretty_value:
( first_line, anything_else ) = pretty_value.split( os.linesep, 1 )
trace = trace + os.linesep + anything_else
else:
first_line = pretty_value
trace = HydrusData.ToUnicode( trace )
job_key = ClientThreading.JobKey()
if etype == HydrusExceptions.ShutdownException:
return
else:
try: job_key.SetVariable( 'popup_title', HydrusData.ToUnicode( etype.__name__ ) )
except: job_key.SetVariable( 'popup_title', HydrusData.ToUnicode( etype ) )
job_key.SetVariable( 'popup_text_1', first_line )
job_key.SetVariable( 'popup_traceback', trace )
text = job_key.ToString()
HydrusData.Print( 'Uncaught exception:' )
HydrusData.DebugPrint( text )
HG.client_controller.pub( 'message', job_key )
except:
text = 'Encountered an error I could not parse:'
text += os.linesep
text += HydrusData.ToUnicode( ( etype, value, tb ) )
try: text += traceback.format_exc()
except: pass
HydrusData.ShowText( text )
time.sleep( 1 )
def ColourIsBright( colour ):
( r, g, b, a ) = colour.Get()
brightness_estimate = ( r + g + b ) // 3
it_is_bright = brightness_estimate > 127
return it_is_bright
def ColourIsGreyish( colour ):
( r, g, b, a ) = colour.Get()
greyish = r // 16 == g // 16 and g // 16 == b // 16
return greyish
def ConvertHTTPSToHTTP( url ):
if url.startswith( 'http://' ):
return url
elif url.startswith( 'https://' ):
http_url = 'http://' + url[8:]
return http_url
else:
raise Exception( 'Given a url that did not have a scheme!' )
def ConvertHTTPToHTTPS( url ):
if url.startswith( 'https://' ):
return url
elif url.startswith( 'http://' ):
https_url = 'https://' + url[7:]
return https_url
else:
raise Exception( 'Given a url that did not have a scheme!' )
def ConvertServiceKeysToContentUpdatesToPrettyString( service_keys_to_content_updates ):
num_files = 0
actions = set()
locations = set()
extra_words = ''
for ( service_key, content_updates ) in service_keys_to_content_updates.items():
if len( content_updates ) > 0:
name = HG.client_controller.services_manager.GetName( service_key )
locations.add( name )
for content_update in content_updates:
( data_type, action, row ) = content_update.ToTuple()
if data_type == HC.CONTENT_TYPE_MAPPINGS:
extra_words = ' tags for'
actions.add( HC.content_update_string_lookup[ action ] )
if action in ( HC.CONTENT_UPDATE_ARCHIVE, HC.CONTENT_UPDATE_INBOX ):
locations = set()
num_files += len( content_update.GetHashes() )
s = ''
if len( locations ) > 0:
s += ', '.join( locations ) + '->'
s += ', '.join( actions ) + extra_words + ' ' + HydrusData.ConvertIntToPrettyString( num_files ) + ' files'
return s
def ConvertServiceKeysToTagsToServiceKeysToContentUpdates( hashes, service_keys_to_tags ):
service_keys_to_content_updates = {}
for ( service_key, tags ) in service_keys_to_tags.items():
if service_key == CC.LOCAL_TAG_SERVICE_KEY:
action = HC.CONTENT_UPDATE_ADD
else:
action = HC.CONTENT_UPDATE_PEND
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, action, ( tag, hashes ) ) for tag in tags ]
service_keys_to_content_updates[ service_key ] = content_updates
return service_keys_to_content_updates
def ConvertShortcutToPrettyShortcut( modifier, key ):
if modifier == wx.ACCEL_NORMAL:
modifier = ''
elif modifier == wx.ACCEL_ALT:
modifier = 'alt'
elif modifier == wx.ACCEL_CTRL:
modifier = 'ctrl'
elif modifier == wx.ACCEL_SHIFT:
modifier = 'shift'
if key in CC.wxk_code_string_lookup:
key = CC.wxk_code_string_lookup[ key ]
elif OrdIsAlphaUpper( key ):
key = chr( key + 32 ) # + 32 for converting ascii A -> a
elif OrdIsSensibleASCII( key ):
key = chr( key )
else:
key = 'unknown key'
return ( modifier, key )
def ConvertTagSliceToString( tag_slice ):
if tag_slice == '':
return 'unnamespaced tags'
elif tag_slice == ':':
return 'namespaced tags'
elif tag_slice.count( ':' ) == 1 and tag_slice.endswith( ':' ):
namespace = tag_slice[ : -1 ]
return '\'' + namespace + '\' tags'
else:
return tag_slice
def ConvertTextToPixels( window, ( char_cols, char_rows ) ):
dialog_units = ( char_cols * 4, char_rows * 8 )
return tuple( window.ConvertDialogToPixels( dialog_units ) ) # convert from _Point_ to a tuple that size methods can deal with
def ConvertTextToPixelWidth( window, char_cols ):
( width, height ) = ConvertTextToPixels( window, ( char_cols, 1 ) )
return width
def ConvertZoomToPercentage( zoom ):
zoom_percent = zoom * 100
pretty_zoom = '%.2f' % zoom_percent + '%'
if pretty_zoom.endswith( '00%' ):
pretty_zoom = '%i' % zoom_percent + '%'
return pretty_zoom
def DeletePath( path ):
if HC.options[ 'delete_to_recycle_bin' ] == True:
HydrusPaths.RecyclePath( path )
else:
HydrusPaths.DeletePath( path )
def GetDifferentLighterDarkerColour( colour, intensity = 3 ):
( r, g, b, a ) = colour.Get()
if ColourIsGreyish( colour ):
if ColourIsBright( colour ):
colour = wx.Colour( int( g * ( 1 - 0.05 * intensity ) ), b, r )
else:
colour = wx.Colour( int( g * ( 1 + 0.05 * intensity ) ) / 2, b, r )
else:
colour = wx.Colour( g, b, r )
return GetLighterDarkerColour( colour, intensity )
def GetLighterDarkerColour( colour, intensity = 3 ):
if intensity is None or intensity == 0:
return colour
if ColourIsBright( colour ):
return wx.lib.colourutils.AdjustColour( colour, -5 * intensity )
else:
( r, g, b, a ) = colour.Get()
( r, g, b ) = [ max( value, 32 ) for value in ( r, g, b ) ]
colour = wx.Colour( r, g, b )
return wx.lib.colourutils.AdjustColour( colour, 5 * intensity )
def GetMediasTagCount( pool, tag_service_key = CC.COMBINED_TAG_SERVICE_KEY, collapse_siblings = False ):
siblings_manager = HG.client_controller.GetManager( 'tag_siblings' )
tags_managers = []
for media in pool:
if media.IsCollection():
tags_managers.extend( media.GetSingletonsTagsManagers() )
else:
tags_managers.append( media.GetTagsManager() )
current_tags_to_count = collections.Counter()
deleted_tags_to_count = collections.Counter()
pending_tags_to_count = collections.Counter()
petitioned_tags_to_count = collections.Counter()
for tags_manager in tags_managers:
statuses_to_tags = tags_manager.GetStatusesToTags( tag_service_key )
# combined is already collapsed
if tag_service_key != CC.COMBINED_TAG_SERVICE_KEY and collapse_siblings:
statuses_to_tags = siblings_manager.CollapseStatusesToTags( tag_service_key, statuses_to_tags )
current_tags_to_count.update( statuses_to_tags[ HC.CONTENT_STATUS_CURRENT ] )
deleted_tags_to_count.update( statuses_to_tags[ HC.CONTENT_STATUS_DELETED ] )
pending_tags_to_count.update( statuses_to_tags[ HC.CONTENT_STATUS_PENDING ] )
petitioned_tags_to_count.update( statuses_to_tags[ HC.CONTENT_STATUS_PETITIONED ] )
return ( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count )
def GetSearchURLs( url ):
search_urls = [ url ]
if url.startswith( 'http://' ):
search_urls.append( ConvertHTTPToHTTPS( url ) )
elif url.startswith( 'https://' ):
search_urls.append( ConvertHTTPSToHTTP( url ) )
return search_urls
def GetSortTypeChoices():
sort_choices = list( CC.SORT_CHOICES )
for ( namespaces_text, namespaces_list ) in HC.options[ 'sort_by' ]:
sort_choices.append( ( namespaces_text, tuple( namespaces_list ) ) )
service_keys = HG.client_controller.services_manager.GetServiceKeys( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
for service_key in service_keys:
sort_choices.append( ( 'rating', service_key ) )
return sort_choices
def MergeCounts( min_a, max_a, min_b, max_b ):
# 100-None and 100-None returns 100-200
# 1-None and 4-5 returns 5-6
# 1-2, and 5-7 returns 6, 9
if min_a == 0:
( min_answer, max_answer ) = ( min_b, max_b )
elif min_b == 0:
( min_answer, max_answer ) = ( min_a, max_a )
else:
if max_a is None:
max_a = min_a
if max_b is None:
max_b = min_b
min_answer = max( min_a, min_b )
max_answer = max_a + max_b
return ( min_answer, max_answer )
def MergePredicates( predicates, add_namespaceless = False ):
master_predicate_dict = {}
for predicate in predicates:
# this works because predicate.__hash__ exists
if predicate in master_predicate_dict:
master_predicate_dict[ predicate ].AddCounts( predicate )
else:
master_predicate_dict[ predicate ] = predicate
if add_namespaceless:
# we want to include the count for namespaced tags in the namespaceless version when:
# there exists more than one instance of the subtag with different namespaces, including '', that has nonzero count
unnamespaced_predicate_dict = {}
subtag_nonzero_instance_counter = collections.Counter()
for predicate in master_predicate_dict.values():
if predicate.HasNonZeroCount():
unnamespaced_predicate = predicate.GetUnnamespacedCopy()
subtag_nonzero_instance_counter[ unnamespaced_predicate ] += 1
if unnamespaced_predicate in unnamespaced_predicate_dict:
unnamespaced_predicate_dict[ unnamespaced_predicate ].AddCounts( unnamespaced_predicate )
else:
unnamespaced_predicate_dict[ unnamespaced_predicate ] = unnamespaced_predicate
for ( unnamespaced_predicate, count ) in subtag_nonzero_instance_counter.items():
# if there were indeed several instances of this subtag, overwrte the master dict's instance with our new count total
if count > 1:
master_predicate_dict[ unnamespaced_predicate ] = unnamespaced_predicate_dict[ unnamespaced_predicate ]
return master_predicate_dict.values()
def OrdIsSensibleASCII( o ):
return 32 <= o and o <= 127
def OrdIsAlphaLower( o ):
return 97 <= o and o <= 122
def OrdIsAlphaUpper( o ):
return 65 <= o and o <= 90
def OrdIsAlpha( o ):
return OrdIsAlphaLower( o ) or OrdIsAlphaUpper( o )
def OrdIsNumber( o ):
return 48 <= o and o <= 57
def ReportShutdownException():
text = 'A serious error occured while trying to exit the program. Its traceback may be shown next. It should have also been written to client.log. You may need to quit the program from task manager.'
HydrusData.DebugPrint( text )
HydrusData.DebugPrint( traceback.format_exc() )
wx.CallAfter( wx.MessageBox, traceback.format_exc() )
wx.CallAfter( wx.MessageBox, text )
def ShowExceptionClient( e, do_wait = True ):
( etype, value, tb ) = sys.exc_info()
if etype is None:
etype = type( e )
value = HydrusData.ToUnicode( e )
trace = 'No error trace--here is the stack:' + os.linesep + ''.join( traceback.format_stack() )
else:
trace = ''.join( traceback.format_exception( etype, value, tb ) )
trace = HydrusData.ToUnicode( trace )
pretty_value = HydrusData.ToUnicode( value )
if os.linesep in pretty_value:
( first_line, anything_else ) = HydrusData.ToUnicode( value ).split( os.linesep, 1 )
trace = trace + os.linesep + anything_else
else:
first_line = pretty_value
job_key = ClientThreading.JobKey()
if isinstance( e, HydrusExceptions.ShutdownException ):
return
else:
if hasattr( etype, '__name__' ): title = HydrusData.ToUnicode( etype.__name__ )
else: title = HydrusData.ToUnicode( etype )
job_key.SetVariable( 'popup_title', title )
job_key.SetVariable( 'popup_text_1', first_line )
job_key.SetVariable( 'popup_traceback', trace )
text = job_key.ToString()
HydrusData.Print( 'Exception:' )
HydrusData.DebugPrint( text )
HG.client_controller.pub( 'message', job_key )
if do_wait:
time.sleep( 1 )
def ShowTextClient( text ):
job_key = ClientThreading.JobKey()
job_key.SetVariable( 'popup_text_1', HydrusData.ToUnicode( text ) )
text = job_key.ToString()
HydrusData.Print( text )
HG.client_controller.pub( 'message', job_key )
def SortTagsList( tags, sort_type ):
if sort_type in ( CC.SORT_BY_LEXICOGRAPHIC_DESC, CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_DESC ):
reverse = True
else:
reverse = False
if sort_type in ( CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_ASC, CC.SORT_BY_LEXICOGRAPHIC_NAMESPACE_DESC ):
def key( tag ):
# '{' is above 'z' in ascii, so this works for most situations
( namespace, subtag ) = HydrusTags.SplitTag( tag )
if namespace == '':
return ( '{', subtag )
else:
return ( namespace, subtag )
else:
key = None
tags.sort( key = key, reverse = reverse )
class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_APPLICATION_COMMAND
SERIALISABLE_NAME = 'Application Command'
SERIALISABLE_VERSION = 1
def __init__( self, command_type = None, data = None ):
if command_type is None:
command_type = CC.APPLICATION_COMMAND_TYPE_SIMPLE
if data is None:
data = 'archive_file'
HydrusSerialisable.SerialisableBase.__init__( self )
self._command_type = command_type
self._data = data
def __cmp__( self, other ):
return cmp( self.ToString(), other.ToString() )
def __repr__( self ):
return self.ToString()
def _GetSerialisableInfo( self ):
if self._command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
serialisable_data = self._data
elif self._command_type == CC.APPLICATION_COMMAND_TYPE_CONTENT:
( service_key, content_type, action, value ) = self._data
serialisable_data = ( service_key.encode( 'hex' ), content_type, action, value )
return ( self._command_type, serialisable_data )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( self._command_type, serialisable_data ) = serialisable_info
if self._command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
self._data = serialisable_data
elif self._command_type == CC.APPLICATION_COMMAND_TYPE_CONTENT:
( serialisable_service_key, content_type, action, value ) = serialisable_data
self._data = ( serialisable_service_key.decode( 'hex' ), content_type, action, value )
def GetCommandType( self ):
return self._command_type
def GetData( self ):
return self._data
def ToString( self ):
if self._command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
return self._data
elif self._command_type == CC.APPLICATION_COMMAND_TYPE_CONTENT:
( service_key, content_type, action, value ) = self._data
components = []
components.append( HC.content_update_string_lookup[ action ] )
components.append( HC.content_type_string_lookup[ content_type ] )
components.append( '"' + HydrusData.ToUnicode( value ) + '"' )
components.append( 'for' )
services_manager = HG.client_controller.services_manager
if services_manager.ServiceExists( service_key ):
service = services_manager.GetService( service_key )
components.append( service.GetName() )
else:
components.append( 'unknown service!' )
return ' '.join( components )
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_APPLICATION_COMMAND ] = ApplicationCommand
class Booru( HydrusData.HydrusYAMLBase ):
yaml_tag = u'!Booru'
def __init__( self, name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ):
self._name = name
self._search_url = search_url
self._search_separator = search_separator
self._advance_by_page_num = advance_by_page_num
self._thumb_classname = thumb_classname
self._image_id = image_id
self._image_data = image_data
self._tag_classnames_to_namespaces = tag_classnames_to_namespaces
def GetData( self ): return ( self._search_url, self._search_separator, self._advance_by_page_num, self._thumb_classname, self._image_id, self._image_data, self._tag_classnames_to_namespaces )
def GetGalleryParsingInfo( self ): return ( self._search_url, self._advance_by_page_num, self._search_separator, self._thumb_classname )
def GetName( self ): return self._name
def GetNamespaces( self ): return self._tag_classnames_to_namespaces.values()
sqlite3.register_adapter( Booru, yaml.safe_dump )
class ClientOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS
SERIALISABLE_NAME = 'Client Options'
SERIALISABLE_VERSION = 3
def __init__( self, db_dir = None ):
HydrusSerialisable.SerialisableBase.__init__( self )
if db_dir is None:
db_dir = HC.DEFAULT_DB_DIR
self._dictionary = HydrusSerialisable.SerialisableDictionary()
self._lock = threading.Lock()
self._InitialiseDefaults( db_dir )
def _GetSerialisableInfo( self ):
with self._lock:
serialisable_info = self._dictionary.GetSerialisableTuple()
return serialisable_info
def _InitialiseDefaults( self, db_dir ):
self._dictionary[ 'booleans' ] = {}
self._dictionary[ 'booleans' ][ 'advanced_mode' ] = False
self._dictionary[ 'booleans' ][ 'always_show_hover_windows' ] = False
self._dictionary[ 'booleans' ][ 'apply_all_parents_to_all_services' ] = False
self._dictionary[ 'booleans' ][ 'apply_all_siblings_to_all_services' ] = False
self._dictionary[ 'booleans' ][ 'filter_inbox_and_archive_predicates' ] = False
self._dictionary[ 'booleans' ][ 'do_not_import_decompression_bombs' ] = True
self._dictionary[ 'booleans' ][ 'discord_dnd_fix' ] = False
self._dictionary[ 'booleans' ][ 'show_thumbnail_title_banner' ] = True
self._dictionary[ 'booleans' ][ 'show_thumbnail_page' ] = True
self._dictionary[ 'booleans' ][ 'disable_cv_for_gifs' ] = False
self._dictionary[ 'booleans' ][ 'add_parents_on_manage_tags' ] = True
self._dictionary[ 'booleans' ][ 'replace_siblings_on_manage_tags' ] = True
self._dictionary[ 'booleans' ][ 'get_tags_if_url_known_and_file_redundant' ] = False
self._dictionary[ 'booleans' ][ 'permit_watchers_to_name_their_pages' ] = True
self._dictionary[ 'booleans' ][ 'show_related_tags' ] = False
self._dictionary[ 'booleans' ][ 'show_file_lookup_script_tags' ] = False
self._dictionary[ 'booleans' ][ 'hide_message_manager_on_gui_iconise' ] = HC.PLATFORM_OSX
self._dictionary[ 'booleans' ][ 'hide_message_manager_on_gui_deactive' ] = False
self._dictionary[ 'booleans' ][ 'load_images_with_pil' ] = False
self._dictionary[ 'booleans' ][ 'use_system_ffmpeg' ] = False
self._dictionary[ 'booleans' ][ 'maintain_similar_files_duplicate_pairs_during_idle' ] = False
self._dictionary[ 'booleans' ][ 'show_namespaces' ] = True
self._dictionary[ 'booleans' ][ 'verify_regular_https' ] = True
self._dictionary[ 'booleans' ][ 'reverse_page_shift_drag_behaviour' ] = False
self._dictionary[ 'booleans' ][ 'anchor_and_hide_canvas_drags' ] = HC.PLATFORM_WINDOWS
self._dictionary[ 'booleans' ][ 'thumbnail_fill' ] = False
self._dictionary[ 'booleans' ][ 'process_subs_in_random_order' ] = True
self._dictionary[ 'booleans' ][ 'ac_select_first_with_count' ] = False
#
self._dictionary[ 'colours' ] = HydrusSerialisable.SerialisableDictionary()
self._dictionary[ 'colours' ][ 'default' ] = HydrusSerialisable.SerialisableDictionary()
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BACKGROUND ] = ( 255, 255, 255 )
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BACKGROUND_SELECTED ] = ( 217, 242, 255 ) # light blue
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BACKGROUND_REMOTE ] = ( 32, 32, 36 ) # 50% Payne's Gray
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BACKGROUND_REMOTE_SELECTED ] = ( 64, 64, 72 ) # Payne's Gray
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BORDER ] = ( 223, 227, 230 ) # light grey
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BORDER_SELECTED ] = ( 1, 17, 26 ) # dark grey
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BORDER_REMOTE ] = ( 248, 208, 204 ) # 25% Vermillion, 75% White
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BORDER_REMOTE_SELECTED ] = ( 227, 66, 52 ) # Vermillion, lol
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMBGRID_BACKGROUND ] = ( 255, 255, 255 )
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_AUTOCOMPLETE_BACKGROUND ] = ( 235, 248, 255 ) # very light blue
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_MEDIA_BACKGROUND ] = ( 255, 255, 255 )
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_MEDIA_TEXT ] = ( 0, 0, 0 )
self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_TAGS_BOX ] = ( 255, 255, 255 )
self._dictionary[ 'colours' ][ 'darkmode' ] = HydrusSerialisable.SerialisableDictionary()
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BACKGROUND ] = ( 64, 64, 72 ) # Payne's Gray
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BACKGROUND_SELECTED ] = ( 112, 128, 144 ) # Slate Gray
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BACKGROUND_REMOTE ] = ( 64, 13, 2 ) # Black Bean
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BACKGROUND_REMOTE_SELECTED ] = ( 171, 39, 79 ) # Amaranth Purple
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BORDER ] = ( 145, 163, 176 ) # Cadet Grey
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BORDER_SELECTED ] = ( 223, 227, 230 ) # light grey
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BORDER_REMOTE ] = ( 248, 208, 204 ) # 25% Vermillion, 75% White
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BORDER_REMOTE_SELECTED ] = ( 227, 66, 52 ) # Vermillion, lol
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMBGRID_BACKGROUND ] = ( 0, 0, 0 )
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_AUTOCOMPLETE_BACKGROUND ] = ( 83, 98, 103 ) # Gunmetal
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_MEDIA_BACKGROUND ] = ( 0, 0, 0 )
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_MEDIA_TEXT ] = ( 112, 128, 144 ) # Slate Gray
self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_TAGS_BOX ] = ( 0, 0, 0 )
self._dictionary[ 'duplicate_action_options' ] = HydrusSerialisable.SerialisableDictionary()
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_BETTER ] = DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE, TagCensor() ) ], [], True, True )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_SAME_QUALITY ] = DuplicateActionOptions( [ ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE, TagCensor() ) ], [], False, True )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_ALTERNATE ] = DuplicateActionOptions( [], [], False )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_NOT_DUPLICATE ] = DuplicateActionOptions( [], [], False )
#
self._dictionary[ 'integers' ] = {}
self._dictionary[ 'integers' ][ 'video_buffer_size_mb' ] = 96
self._dictionary[ 'integers' ][ 'related_tags_search_1_duration_ms' ] = 250
self._dictionary[ 'integers' ][ 'related_tags_search_2_duration_ms' ] = 2000
self._dictionary[ 'integers' ][ 'related_tags_search_3_duration_ms' ] = 6000
self._dictionary[ 'integers' ][ 'suggested_tags_width' ] = 300
self._dictionary[ 'integers' ][ 'similar_files_duplicate_pairs_search_distance' ] = 0
self._dictionary[ 'integers' ][ 'default_new_page_goes' ] = CC.NEW_PAGE_GOES_FAR_RIGHT
self._dictionary[ 'integers' ][ 'max_page_name_chars' ] = 20
self._dictionary[ 'integers' ][ 'page_file_count_display' ] = CC.PAGE_FILE_COUNT_DISPLAY_ALL
self._dictionary[ 'integers' ][ 'network_timeout' ] = 10
self._dictionary[ 'integers' ][ 'thumbnail_visibility_scroll_percent' ] = 75
self._dictionary[ 'integers' ][ 'total_pages_warning' ] = 165
self._dictionary[ 'integers' ][ 'last_session_save_period_minutes' ] = 5
#
self._dictionary[ 'keys' ] = {}
self._dictionary[ 'keys' ][ 'default_tag_service_search_page' ] = CC.COMBINED_TAG_SERVICE_KEY.encode( 'hex' )
self._dictionary[ 'key_list' ] = {}
self._dictionary[ 'key_list' ][ 'default_neighbouring_txt_tag_service_keys' ] = []
#
self._dictionary[ 'noneable_integers' ] = {}
self._dictionary[ 'noneable_integers' ][ 'forced_search_limit' ] = None
self._dictionary[ 'noneable_integers' ][ 'disk_cache_maintenance_mb' ] = 256
self._dictionary[ 'noneable_integers' ][ 'disk_cache_init_period' ] = 4
self._dictionary[ 'noneable_integers' ][ 'num_recent_tags' ] = 20
self._dictionary[ 'noneable_integers' ][ 'maintenance_vacuum_period_days' ] = 30
self._dictionary[ 'noneable_integers' ][ 'duplicate_background_switch_intensity' ] = 3
#
self._dictionary[ 'noneable_strings' ] = {}
self._dictionary[ 'noneable_strings' ][ 'favourite_file_lookup_script' ] = 'gelbooru md5'
self._dictionary[ 'noneable_strings' ][ 'suggested_tags_layout' ] = 'notebook'
self._dictionary[ 'noneable_strings' ][ 'backup_path' ] = None
self._dictionary[ 'noneable_strings' ][ 'thread_watcher_not_found_page_string' ] = '[404]'
self._dictionary[ 'noneable_strings' ][ 'thread_watcher_dead_page_string' ] = '[DEAD]'
self._dictionary[ 'noneable_strings' ][ 'thread_watcher_paused_page_string' ] = u'\u23F8'
self._dictionary[ 'strings' ] = {}
self._dictionary[ 'strings' ][ 'main_gui_title' ] = 'hydrus client'
self._dictionary[ 'strings' ][ 'namespace_connector' ] = ':'
self._dictionary[ 'strings' ][ 'export_phrase' ] = '{hash}'
self._dictionary[ 'strings' ][ 'current_colourset' ] = 'default'
self._dictionary[ 'string_list' ] = {}
self._dictionary[ 'string_list' ][ 'default_media_viewer_custom_shortcuts' ] = []
#
client_files_default = os.path.join( db_dir, 'client_files' )
self._dictionary[ 'client_files_locations_ideal_weights' ] = [ ( HydrusPaths.ConvertAbsPathToPortablePath( client_files_default ), 1.0 ) ]
self._dictionary[ 'client_files_locations_resized_thumbnail_override' ] = None
self._dictionary[ 'client_files_locations_full_size_thumbnail_override' ] = None
#
self._dictionary[ 'default_file_import_options' ] = HydrusSerialisable.SerialisableDictionary()
automatic_archive = False
exclude_deleted = True
min_size = None
min_resolution = None
present_new_files = True
present_already_in_inbox_files = False
present_archived_files = False
import ClientImporting
quiet_file_import_options = ClientImporting.FileImportOptions( automatic_archive = automatic_archive, exclude_deleted = exclude_deleted, present_new_files = present_new_files, present_already_in_inbox_files = present_already_in_inbox_files, present_archived_files = present_archived_files, min_size = min_size, min_resolution = min_resolution )
self._dictionary[ 'default_file_import_options' ][ 'quiet' ] = quiet_file_import_options
present_new_files = True
present_already_in_inbox_files = True
present_archived_files = True
loud_file_import_options = ClientImporting.FileImportOptions( automatic_archive = automatic_archive, exclude_deleted = exclude_deleted, present_new_files = present_new_files, present_already_in_inbox_files = present_already_in_inbox_files, present_archived_files = present_archived_files, min_size = min_size, min_resolution = min_resolution )
self._dictionary[ 'default_file_import_options' ][ 'loud' ] = loud_file_import_options
#
self._dictionary[ 'default_import_tag_options' ] = HydrusSerialisable.SerialisableDictionary()
#
self._dictionary[ 'frame_locations' ] = {}
# remember size, remember position, last_size, last_pos, default gravity, default position, maximised, fullscreen
self._dictionary[ 'frame_locations' ][ 'file_import_status' ] = ( True, True, None, None, ( -1, -1 ), 'topleft', False, False )
self._dictionary[ 'frame_locations' ][ 'local_import_filename_tagging' ] = ( True, False, None, None, ( -1, -1 ), 'topleft', False, False )
self._dictionary[ 'frame_locations' ][ 'main_gui' ] = ( True, True, ( 800, 600 ), ( 20, 20 ), ( -1, -1 ), 'topleft', True, False )
self._dictionary[ 'frame_locations' ][ 'manage_options_dialog' ] = ( False, False, None, None, ( -1, -1 ), 'topleft', False, False )
self._dictionary[ 'frame_locations' ][ 'manage_subscriptions_dialog' ] = ( True, True, None, None, ( 1, -1 ), 'topleft', False, False )
self._dictionary[ 'frame_locations' ][ 'manage_tags_dialog' ] = ( False, False, None, None, ( -1, 1 ), 'topleft', False, False )
self._dictionary[ 'frame_locations' ][ 'manage_tags_frame' ] = ( False, False, None, None, ( -1, 1 ), 'topleft', False, False )
self._dictionary[ 'frame_locations' ][ 'media_viewer' ] = ( True, True, ( 640, 480 ), ( 70, 70 ), ( -1, -1 ), 'topleft', True, True )
self._dictionary[ 'frame_locations' ][ 'regular_dialog' ] = ( False, False, None, None, ( -1, -1 ), 'topleft', False, False )
self._dictionary[ 'frame_locations' ][ 'review_services' ] = ( False, True, None, None, ( -1, -1 ), 'topleft', False, False )
self._dictionary[ 'frame_locations' ][ 'deeply_nested_dialog' ] = ( False, False, None, None, ( -1, -1 ), 'topleft', False, False )
#
self._dictionary[ 'media_launch' ] = HydrusSerialisable.SerialisableDictionary() # integer keys, so got to be cleverer dict
for mime in HC.SEARCHABLE_MIMES:
self._dictionary[ 'media_launch' ][ mime ] = None
#
self._dictionary[ 'media_view' ] = HydrusSerialisable.SerialisableDictionary() # integer keys, so got to be cleverer dict
# media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
image_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
gif_zoom_info = ( CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, True, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
flash_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR )
video_zoom_info = ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, True, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
null_zoom_info = ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR )
self._dictionary[ 'media_view' ][ HC.IMAGE_JPEG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
self._dictionary[ 'media_view' ][ HC.IMAGE_PNG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
self._dictionary[ 'media_view' ][ HC.IMAGE_APNG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, gif_zoom_info )
self._dictionary[ 'media_view' ][ HC.IMAGE_GIF ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, gif_zoom_info )
if HC.PLATFORM_WINDOWS:
self._dictionary[ 'media_view' ][ HC.APPLICATION_FLASH ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, flash_zoom_info )
else:
self._dictionary[ 'media_view' ][ HC.APPLICATION_FLASH ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_PDF ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_ZIP ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_7Z ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_RAR ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_CONTENT ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_AVI ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_FLV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MOV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MP4 ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MPEG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_MKV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_WEBM ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.VIDEO_WMV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
self._dictionary[ 'media_view' ][ HC.AUDIO_MP3 ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.AUDIO_OGG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.AUDIO_FLAC ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_view' ][ HC.AUDIO_WMA ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
self._dictionary[ 'media_zooms' ] = [ 0.01, 0.05, 0.1, 0.15, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.5, 2.0, 3.0, 5.0, 10.0, 20.0 ]
#
self._dictionary[ 'misc' ] = HydrusSerialisable.SerialisableDictionary()
self._dictionary[ 'misc' ][ 'default_thread_watcher_options' ] = CheckerOptions( intended_files_per_check = 4, never_faster_than = 300, never_slower_than = 86400, death_file_velocity = ( 1, 86400 ) )
#
self._dictionary[ 'suggested_tags' ] = HydrusSerialisable.SerialisableDictionary()
self._dictionary[ 'suggested_tags' ][ 'favourites' ] = {}
#
import ClientMedia
self._dictionary[ 'default_sort' ] = ClientMedia.MediaSort( ( 'system', CC.SORT_FILES_BY_FILESIZE ), CC.SORT_ASC )
self._dictionary[ 'fallback_sort' ] = ClientMedia.MediaSort( ( 'system', CC.SORT_FILES_BY_IMPORT_TIME ), CC.SORT_ASC )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_info )
for ( key, value ) in loaded_dictionary.items():
if isinstance( value, dict ):
self._dictionary[ key ].update( value )
else:
self._dictionary[ key ] = value
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( old_serialisable_info )
if 'media_view' in loaded_dictionary:
mimes = loaded_dictionary[ 'media_view' ].keys()
for mime in mimes:
if mime in self._dictionary[ 'media_view' ]:
( default_media_show_action, default_preview_show_action, default_zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
( media_show_action, preview_show_action, zoom_in_to_fit, exact_zooms_only, scale_up_quality, scale_down_quality ) = loaded_dictionary[ 'media_view' ][ mime ]
loaded_dictionary[ 'media_view' ][ mime ] = ( media_show_action, preview_show_action, default_zoom_info )
else:
# while devving this, I discovered some u'20' stringified keys had snuck in and hung around. let's nuke them here, in case anyone else got similar
del loaded_dictionary[ 'media_view' ][ mime ]
new_serialisable_info = loaded_dictionary.GetSerialisableTuple()
return ( 2, new_serialisable_info )
if version == 2:
# as db_dir is now moveable, let's move portable base from base_dir to db_dir
def update_portable_path( p ):
if p is None:
return p
p = os.path.normpath( p ) # collapses .. stuff and converts / to \\ for windows only
if os.path.isabs( p ):
a_p = p
else:
a_p = os.path.normpath( os.path.join( HC.BASE_DIR, p ) )
if not HC.PLATFORM_WINDOWS and not os.path.exists( a_p ):
a_p = a_p.replace( '\\', '/' )
try:
db_dir = HG.controller.GetDBDir()
p = os.path.relpath( a_p, db_dir )
if p.startswith( '..' ):
p = a_p
except:
p = a_p
if HC.PLATFORM_WINDOWS:
p = p.replace( '\\', '/' ) # store seps as /, to maintain multiplatform uniformity
return p
loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( old_serialisable_info )
if 'client_files_locations_ideal_weights' in loaded_dictionary:
updated_cfliw = []
for ( old_portable_path, weight ) in loaded_dictionary[ 'client_files_locations_ideal_weights' ]:
new_portable_path = update_portable_path( old_portable_path )
updated_cfliw.append( ( new_portable_path, weight ) )
loaded_dictionary[ 'client_files_locations_ideal_weights' ] = updated_cfliw
if 'client_files_locations_resized_thumbnail_override' in loaded_dictionary:
loaded_dictionary[ 'client_files_locations_resized_thumbnail_override' ] = update_portable_path( loaded_dictionary[ 'client_files_locations_resized_thumbnail_override' ] )
if 'client_files_locations_full_size_thumbnail_override' in loaded_dictionary:
loaded_dictionary[ 'client_files_locations_full_size_thumbnail_override' ] = update_portable_path( loaded_dictionary[ 'client_files_locations_full_size_thumbnail_override' ] )
new_serialisable_info = loaded_dictionary.GetSerialisableTuple()
return ( 3, new_serialisable_info )
def ClearDefaultTagImportOptions( self ):
with self._lock:
self._dictionary[ 'default_import_tag_options' ] = HydrusSerialisable.SerialisableDictionary()
def GetBoolean( self, name ):
with self._lock:
return self._dictionary[ 'booleans' ][ name ]
def GetClientFilesLocationsToIdealWeights( self ):
with self._lock:
paths_to_weights = {}
for ( portable_path, weight ) in self._dictionary[ 'client_files_locations_ideal_weights' ]:
abs_path = HydrusPaths.ConvertPortablePathToAbsPath( portable_path )
paths_to_weights[ abs_path ] = weight
resized_thumbnail_override = self._dictionary[ 'client_files_locations_resized_thumbnail_override' ]
if resized_thumbnail_override is not None:
resized_thumbnail_override = HydrusPaths.ConvertPortablePathToAbsPath( resized_thumbnail_override )
full_size_thumbnail_override = self._dictionary[ 'client_files_locations_full_size_thumbnail_override' ]
if full_size_thumbnail_override is not None:
full_size_thumbnail_override = HydrusPaths.ConvertPortablePathToAbsPath( full_size_thumbnail_override )
return ( paths_to_weights, resized_thumbnail_override, full_size_thumbnail_override )
def GetColour( self, colour_type, colourset = None ):
with self._lock:
if colourset is None:
colourset = self._dictionary[ 'strings' ][ 'current_colourset' ]
( r, g, b ) = self._dictionary[ 'colours' ][ colourset ][ colour_type ]
return wx.Colour( r, g, b )
def GetDefaultFileImportOptions( self, options_type ):
with self._lock:
return self._dictionary[ 'default_file_import_options' ][ options_type ]
def GetDefaultTagImportOptions( self, gallery_identifier = None ):
with self._lock:
default_tag_import_options = self._dictionary[ 'default_import_tag_options' ]
if gallery_identifier is None:
return default_tag_import_options
else:
if gallery_identifier in default_tag_import_options:
tag_import_options = default_tag_import_options[ gallery_identifier ]
else:
default_booru_gallery_identifier = ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_BOORU )
default_hentai_foundry_gallery_identifier = ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_HENTAI_FOUNDRY )
default_pixiv_gallery_identifier = ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_PIXIV )
default_gallery_identifier = ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_DEFAULT )
guidance_tag_import_options = None
site_type = gallery_identifier.GetSiteType()
if site_type == HC.SITE_TYPE_THREAD_WATCHER:
import ClientImporting
return ClientImporting.TagImportOptions() # if nothing set, do nothing in this special case
if site_type == HC.SITE_TYPE_BOORU and default_booru_gallery_identifier in default_tag_import_options:
guidance_tag_import_options = default_tag_import_options[ default_booru_gallery_identifier ]
elif site_type in ( HC.SITE_TYPE_HENTAI_FOUNDRY_ARTIST, HC.SITE_TYPE_HENTAI_FOUNDRY_TAGS ) and default_hentai_foundry_gallery_identifier in default_tag_import_options:
guidance_tag_import_options = default_tag_import_options[ default_hentai_foundry_gallery_identifier ]
elif site_type in ( HC.SITE_TYPE_PIXIV_ARTIST_ID, HC.SITE_TYPE_PIXIV_TAG ) and default_pixiv_gallery_identifier in default_tag_import_options:
guidance_tag_import_options = default_tag_import_options[ default_pixiv_gallery_identifier ]
elif default_gallery_identifier in default_tag_import_options:
guidance_tag_import_options = default_tag_import_options[ default_gallery_identifier ]
service_keys_to_namespaces = {}
service_keys_to_explicit_tags = {}
if guidance_tag_import_options is not None:
( namespaces, search_value ) = ClientDefaults.GetDefaultNamespacesAndSearchValue( gallery_identifier )
guidance_service_keys_to_namespaces = guidance_tag_import_options.GetServiceKeysToNamespaces()
for ( service_key, guidance_namespaces ) in guidance_service_keys_to_namespaces.items():
if 'all namespaces' in guidance_namespaces:
service_keys_to_namespaces[ service_key ] = namespaces
else:
service_keys_to_namespaces[ service_key ] = [ namespace for namespace in namespaces if namespace in guidance_namespaces ]
service_keys_to_explicit_tags = guidance_tag_import_options.GetServiceKeysToExplicitTags()
import ClientImporting
tag_import_options = ClientImporting.TagImportOptions( service_keys_to_namespaces = service_keys_to_namespaces, service_keys_to_explicit_tags = service_keys_to_explicit_tags )
return tag_import_options
def GetDefaultThreadCheckerOptions( self ):
with self._lock:
return self._dictionary[ 'misc' ][ 'default_thread_watcher_options' ]
def GetDefaultSort( self ):
with self._lock:
return self._dictionary[ 'default_sort' ]
def GetDuplicateActionOptions( self, duplicate_type ):
with self._lock:
return self._dictionary[ 'duplicate_action_options' ][ duplicate_type ]
def GetFallbackSort( self ):
with self._lock:
return self._dictionary[ 'fallback_sort' ]
def GetFrameLocation( self, frame_key ):
with self._lock:
return self._dictionary[ 'frame_locations' ][ frame_key ]
def GetFrameLocations( self ):
with self._lock:
return self._dictionary[ 'frame_locations' ].items()
def GetInteger( self, name ):
with self._lock:
return self._dictionary[ 'integers' ][ name ]
def GetKey( self, name ):
with self._lock:
return self._dictionary[ 'keys' ][ name ].decode( 'hex' )
def GetKeyList( self, name ):
with self._lock:
return [ key.decode( 'hex' ) for key in self._dictionary[ 'key_list' ][ name ] ]
def GetMediaShowAction( self, mime ):
with self._lock:
( media_show_action, preview_show_action, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
if media_show_action not in CC.media_viewer_capabilities[ mime ]:
return CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON
return media_show_action
def GetMediaViewOptions( self, mime ):
with self._lock:
return self._dictionary[ 'media_view' ][ mime ]
def GetMediaZoomOptions( self, mime ):
with self._lock:
( media_show_action, preview_show_action, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
return zoom_info
def GetMediaZooms( self ):
with self._lock:
return list( self._dictionary[ 'media_zooms' ] )
def GetMediaZoomQuality( self, mime ):
with self._lock:
( media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) ) = self._dictionary[ 'media_view' ][ mime ]
return ( scale_up_quality, scale_down_quality )
def GetMimeLaunch( self, mime ):
with self._lock:
if mime not in self._dictionary[ 'media_launch' ]:
self._dictionary[ 'media_launch' ][ mime ] = None
return self._dictionary[ 'media_launch' ][ mime ]
def GetNoneableInteger( self, name ):
with self._lock:
return self._dictionary[ 'noneable_integers' ][ name ]
def GetNoneableString( self, name ):
with self._lock:
return self._dictionary[ 'noneable_strings' ][ name ]
def GetPreviewShowAction( self, mime ):
with self._lock:
( media_show_action, preview_show_action, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
if preview_show_action not in CC.media_viewer_capabilities[ mime ]:
return CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON
return preview_show_action
def GetString( self, name ):
with self._lock:
return self._dictionary[ 'strings' ][ name ]
def GetStringList( self, name ):
with self._lock:
return self._dictionary[ 'string_list' ][ name ]
def GetSuggestedTagsFavourites( self, service_key ):
with self._lock:
service_key_hex = service_key.encode( 'hex' )
stf = self._dictionary[ 'suggested_tags' ][ 'favourites' ]
if service_key_hex in stf:
return set( stf[ service_key_hex ] )
else:
return set()
def InvertBoolean( self, name ):
with self._lock:
self._dictionary[ 'booleans' ][ name ] = not self._dictionary[ 'booleans' ][ name ]
def RemoveClientFilesLocation( self, location ):
with self._lock:
if len( self._dictionary[ 'client_files_locations_ideal_weights' ] ) < 2:
raise Exception( 'Cannot remove any more files locations!' )
portable_location = HydrusPaths.ConvertAbsPathToPortablePath( location )
self._dictionary[ 'client_files_locations_ideal_weights' ] = [ ( l, w ) for ( l, w ) in self._dictionary[ 'client_files_locations_ideal_weights' ] if l != portable_location ]
def SetBoolean( self, name, value ):
with self._lock:
self._dictionary[ 'booleans' ][ name ] = value
def SetClientFilesLocation( self, location, weight ):
with self._lock:
portable_location = HydrusPaths.ConvertAbsPathToPortablePath( location )
weight = float( weight )
self._dictionary[ 'client_files_locations_ideal_weights' ] = [ ( l, w ) for ( l, w ) in self._dictionary[ 'client_files_locations_ideal_weights' ] if l != portable_location ]
self._dictionary[ 'client_files_locations_ideal_weights' ].append( ( portable_location, weight ) )
def SetColour( self, colour_type, colourset, colour ):
with self._lock:
if isinstance( colour, wx.Colour ):
( r, g, b, a ) = colour.Get()
else:
( r, g, b ) = colour
self._dictionary[ 'colours' ][ colourset ][ colour_type ] = ( r, g, b )
def SetDefaultTagImportOptions( self, gallery_identifier, tag_import_options ):
with self._lock:
self._dictionary[ 'default_import_tag_options' ][ gallery_identifier ] = tag_import_options
def SetDefaultThreadCheckerOptions( self, checker_options ):
with self._lock:
self._dictionary[ 'misc' ][ 'default_thread_watcher_options' ] = checker_options
def SetDefaultSort( self, media_sort ):
with self._lock:
self._dictionary[ 'default_sort' ] = media_sort
def SetDuplicateActionOptions( self, duplicate_type, duplicate_action_options ):
with self._lock:
self._dictionary[ 'duplicate_action_options' ][ duplicate_type ] = duplicate_action_options
def SetFallbackSort( self, media_sort ):
with self._lock:
self._dictionary[ 'fallback_sort' ] = media_sort
def SetDefaultFileImportOptions( self, options_type, file_import_options ):
with self._lock:
self._dictionary[ 'default_file_import_options' ][ options_type ] = file_import_options
def SetFrameLocation( self, frame_key, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ):
with self._lock:
self._dictionary[ 'frame_locations' ][ frame_key ] = ( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen )
def SetFullsizeThumbnailOverride( self, full_size_thumbnail_override ):
with self._lock:
self._dictionary[ 'client_files_locations_full_size_thumbnail_override' ] = full_size_thumbnail_override
def SetInteger( self, name, value ):
with self._lock:
self._dictionary[ 'integers' ][ name ] = value
def SetKey( self, name, value ):
with self._lock:
self._dictionary[ 'keys' ][ name ] = value.encode( 'hex' )
def SetKeyList( self, name, value ):
with self._lock:
self._dictionary[ 'key_list' ][ name ] = [ key.encode( 'hex' ) for key in value ]
def SetMediaViewOptions( self, mime, value_tuple ):
with self._lock:
self._dictionary[ 'media_view' ][ mime ] = value_tuple
def SetMediaZooms( self, zooms ):
with self._lock:
self._dictionary[ 'media_zooms' ] = zooms
def SetMimeLaunch( self, mime, launch_path ):
with self._lock:
self._dictionary[ 'media_launch' ][ mime ] = launch_path
def SetNoneableInteger( self, name, value ):
with self._lock:
self._dictionary[ 'noneable_integers' ][ name ] = value
def SetNoneableString( self, name, value ):
with self._lock:
self._dictionary[ 'noneable_strings' ][ name ] = value
def SetResizedThumbnailOverride( self, resized_thumbnail_override ):
with self._lock:
self._dictionary[ 'client_files_locations_resized_thumbnail_override' ] = resized_thumbnail_override
def SetString( self, name, value ):
with self._lock:
it_changed = False
if value is not None and value != '':
if self._dictionary[ 'strings' ][ name ] != value:
self._dictionary[ 'strings' ][ name ] = value
it_changed = True
if name == 'current_colourset' and it_changed:
HG.client_controller.pub( 'notify_new_colourset' )
def SetStringList( self, name, value ):
with self._lock:
self._dictionary[ 'string_list' ][ name ] = value
def SetSuggestedTagsFavourites( self, service_key, tags ):
with self._lock:
service_key_hex = service_key.encode( 'hex' )
self._dictionary[ 'suggested_tags' ][ 'favourites' ][ service_key_hex ] = list( tags )
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS ] = ClientOptions
class Credentials( HydrusData.HydrusYAMLBase ):
yaml_tag = u'!Credentials'
def __init__( self, host, port, access_key = None ):
HydrusData.HydrusYAMLBase.__init__( self )
if host == 'localhost':
host = '127.0.0.1'
self._host = host
self._port = port
self._access_key = access_key
def __eq__( self, other ): return self.__hash__() == other.__hash__()
def __hash__( self ): return ( self._host, self._port, self._access_key ).__hash__()
def __ne__( self, other ): return self.__hash__() != other.__hash__()
def __repr__( self ): return 'Credentials: ' + HydrusData.ToUnicode( ( self._host, self._port, self._access_key.encode( 'hex' ) ) )
def GetAccessKey( self ): return self._access_key
def GetAddress( self ): return ( self._host, self._port )
def GetConnectionString( self ):
connection_string = ''
if self.HasAccessKey(): connection_string += self._access_key.encode( 'hex' ) + '@'
connection_string += self._host + ':' + str( self._port )
return connection_string
def HasAccessKey( self ): return self._access_key is not None and self._access_key is not ''
def SetAccessKey( self, access_key ): self._access_key = access_key
class DuplicateActionOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_DUPLICATE_ACTION_OPTIONS
SERIALISABLE_NAME = 'Duplicate Action Options'
SERIALISABLE_VERSION = 2
def __init__( self, tag_service_actions = None, rating_service_actions = None, delete_second_file = False, sync_archive = False, delete_both_files = False ):
if tag_service_actions is None:
tag_service_actions = []
if rating_service_actions is None:
rating_service_actions = []
HydrusSerialisable.SerialisableBase.__init__( self )
self._tag_service_actions = tag_service_actions
self._rating_service_actions = rating_service_actions
self._delete_second_file = delete_second_file
self._sync_archive = sync_archive
self._delete_both_files = delete_both_files
def _GetSerialisableInfo( self ):
if HG.client_controller.IsBooted():
services_manager = HG.client_controller.services_manager
self._tag_service_actions = [ ( service_key, action, tag_censor ) for ( service_key, action, tag_censor ) in self._tag_service_actions if services_manager.ServiceExists( service_key ) and services_manager.GetServiceType( service_key ) in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ) ]
self._rating_service_actions = [ ( service_key, action ) for ( service_key, action ) in self._rating_service_actions if services_manager.ServiceExists( service_key ) and services_manager.GetServiceType( service_key ) in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) ]
serialisable_tag_service_actions = [ ( service_key.encode( 'hex' ), action, tag_censor.GetSerialisableTuple() ) for ( service_key, action, tag_censor ) in self._tag_service_actions ]
serialisable_rating_service_actions = [ ( service_key.encode( 'hex' ), action ) for ( service_key, action ) in self._rating_service_actions ]
return ( serialisable_tag_service_actions, serialisable_rating_service_actions, self._delete_second_file, self._sync_archive, self._delete_both_files )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( serialisable_tag_service_actions, serialisable_rating_service_actions, self._delete_second_file, self._sync_archive, self._delete_both_files ) = serialisable_info
self._tag_service_actions = [ ( serialisable_service_key.decode( 'hex' ), action, HydrusSerialisable.CreateFromSerialisableTuple( serialisable_tag_censor ) ) for ( serialisable_service_key, action, serialisable_tag_censor ) in serialisable_tag_service_actions ]
self._rating_service_actions = [ ( serialisable_service_key.decode( 'hex' ), action ) for ( serialisable_service_key, action ) in serialisable_rating_service_actions ]
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
( serialisable_service_actions, delete_second_file ) = old_serialisable_info
tag_service_actions = []
rating_service_actions = []
# As the client isn't booted when this is loaded in options, there isn't a good way to figure out tag from rating
# So, let's just dupe and purge later on, in serialisation
for ( service_key_encoded, action ) in serialisable_service_actions:
service_key = service_key_encoded.decode( 'hex' )
tag_censor = TagCensor()
tag_service_actions.append( ( service_key, action, tag_censor ) )
rating_service_actions.append( ( service_key, action ) )
serialisable_tag_service_actions = [ ( service_key.encode( 'hex' ), action, tag_censor.GetSerialisableTuple() ) for ( service_key, action, tag_censor ) in tag_service_actions ]
serialisable_rating_service_actions = [ ( service_key.encode( 'hex' ), action ) for ( service_key, action ) in rating_service_actions ]
sync_archive = delete_second_file
delete_both_files = False
new_serialisable_info = ( serialisable_tag_service_actions, serialisable_rating_service_actions, delete_second_file, sync_archive, delete_both_files )
return ( 2, new_serialisable_info )
def GetDeletedHashes( self, first_media, second_media ):
first_hashes = first_media.GetHashes()
second_hashes = second_media.GetHashes()
if self._delete_second_file:
return second_hashes
elif self._delete_both_files:
return first_hashes.union( second_hashes )
else:
return set()
def SetTuple( self, tag_service_actions, rating_service_actions, delete_second_file, sync_archive, delete_both_files ):
self._tag_service_actions = tag_service_actions
self._rating_service_actions = rating_service_actions
self._delete_second_file = delete_second_file
self._sync_archive = sync_archive
self._delete_both_files = delete_both_files
def ToTuple( self ):
return ( self._tag_service_actions, self._rating_service_actions, self._delete_second_file, self._sync_archive, self._delete_both_files )
def ProcessPairIntoContentUpdates( self, first_media, second_media ):
list_of_service_keys_to_content_updates = []
first_hashes = first_media.GetHashes()
second_hashes = second_media.GetHashes()
#
service_keys_to_content_updates = {}
services_manager = HG.client_controller.services_manager
for ( service_key, action, tag_censor ) in self._tag_service_actions:
content_updates = []
try:
service = services_manager.GetService( service_key )
except HydrusExceptions.DataMissing:
continue
service_type = service.GetServiceType()
if service_type == HC.LOCAL_TAG:
add_content_action = HC.CONTENT_UPDATE_ADD
elif service_type == HC.TAG_REPOSITORY:
add_content_action = HC.CONTENT_UPDATE_PEND
first_current_tags = first_media.GetTagsManager().GetCurrent( service_key )
second_current_tags = second_media.GetTagsManager().GetCurrent( service_key )
first_current_tags = tag_censor.Censor( first_current_tags )
second_current_tags = tag_censor.Censor( second_current_tags )
if action == HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE:
first_needs = second_current_tags.difference( first_current_tags )
second_needs = first_current_tags.difference( second_current_tags )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, add_content_action, ( tag, first_hashes ) ) for tag in first_needs ) )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, add_content_action, ( tag, second_hashes ) ) for tag in second_needs ) )
elif action == HC.CONTENT_MERGE_ACTION_COPY:
first_needs = second_current_tags.difference( first_current_tags )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, add_content_action, ( tag, first_hashes ) ) for tag in first_needs ) )
elif service_type == HC.LOCAL_TAG and action == HC.CONTENT_MERGE_ACTION_MOVE:
first_needs = second_current_tags.difference( first_current_tags )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, add_content_action, ( tag, first_hashes ) ) for tag in first_needs ) )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_DELETE, ( tag, second_hashes ) ) for tag in second_current_tags ) )
if len( content_updates ) > 0:
service_keys_to_content_updates[ service_key ] = content_updates
for ( service_key, action ) in self._rating_service_actions:
content_updates = []
try:
service = services_manager.GetService( service_key )
except HydrusExceptions.DataMissing:
continue
first_current_value = first_media.GetRatingsManager().GetRating( service_key )
second_current_value = second_media.GetRatingsManager().GetRating( service_key )
if action == HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE:
if first_current_value == second_current_value:
continue
if first_current_value is None and second_current_value is not None:
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( second_current_value, first_hashes ) ) )
elif first_current_value is not None and second_current_value is None:
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( first_current_value, second_hashes ) ) )
elif action == HC.CONTENT_MERGE_ACTION_COPY:
if first_current_value == second_current_value:
continue
if first_current_value is None and second_current_value is not None:
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( second_current_value, first_hashes ) ) )
elif action == HC.CONTENT_MERGE_ACTION_MOVE:
if second_current_value is not None:
if first_current_value is None:
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( second_current_value, first_hashes ) ) )
content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( None, second_hashes ) ) )
if len( content_updates ) > 0:
service_keys_to_content_updates[ service_key ] = content_updates
if len( service_keys_to_content_updates ) > 0:
list_of_service_keys_to_content_updates.append( service_keys_to_content_updates )
#
service_keys_to_content_updates = {}
if self._sync_archive:
if first_media.HasInbox() and second_media.HasArchive():
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, first_hashes )
service_keys_to_content_updates[ CC.COMBINED_LOCAL_FILE_SERVICE_KEY ] = [ content_update ]
elif first_media.HasArchive() and second_media.HasInbox():
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, second_hashes )
service_keys_to_content_updates[ CC.COMBINED_LOCAL_FILE_SERVICE_KEY ] = [ content_update ]
if len( service_keys_to_content_updates ) > 0:
list_of_service_keys_to_content_updates.append( service_keys_to_content_updates )
#
service_keys_to_content_updates = {}
deletee_media = []
if self._delete_second_file or self._delete_both_files:
if self._delete_both_files:
deletee_media.append( first_media )
deletee_media.append( second_media )
for media in deletee_media:
current_locations = media.GetLocationsManager().GetCurrent()
if CC.LOCAL_FILE_SERVICE_KEY in current_locations:
deletee_service_key = CC.LOCAL_FILE_SERVICE_KEY
elif CC.TRASH_SERVICE_KEY in current_locations:
deletee_service_key = CC.TRASH_SERVICE_KEY
else:
deletee_service_key = None
if deletee_service_key is not None:
if deletee_service_key not in service_keys_to_content_updates:
service_keys_to_content_updates[ deletee_service_key ] = []
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, media.GetHashes() )
service_keys_to_content_updates[ deletee_service_key ].append( content_update )
if len( service_keys_to_content_updates ) > 0:
list_of_service_keys_to_content_updates.append( service_keys_to_content_updates )
#
return list_of_service_keys_to_content_updates
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_DUPLICATE_ACTION_OPTIONS ] = DuplicateActionOptions
class Imageboard( HydrusData.HydrusYAMLBase ):
yaml_tag = u'!Imageboard'
def __init__( self, name, post_url, flood_time, form_fields, restrictions ):
self._name = name
self._post_url = post_url
self._flood_time = flood_time
self._form_fields = form_fields
self._restrictions = restrictions
def IsOKToPost( self, media_result ):
# deleted old code due to deprecation
return True
def GetBoardInfo( self ): return ( self._post_url, self._flood_time, self._form_fields, self._restrictions )
def GetName( self ): return self._name
sqlite3.register_adapter( Imageboard, yaml.safe_dump )
class Shortcut( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT
SERIALISABLE_NAME = 'Shortcut'
SERIALISABLE_VERSION = 1
def __init__( self, shortcut_type = None, shortcut_key = None, modifiers = None ):
if shortcut_type is None:
shortcut_type = CC.SHORTCUT_TYPE_KEYBOARD
if shortcut_key is None:
shortcut_key = wx.WXK_F7
if modifiers is None:
modifiers = []
modifiers.sort()
HydrusSerialisable.SerialisableBase.__init__( self )
self._shortcut_type = shortcut_type
self._shortcut_key = shortcut_key
self._modifiers = modifiers
def __cmp__( self, other ):
return cmp( self.ToString(), other.ToString() )
def __eq__( self, other ):
return self.__hash__() == other.__hash__()
def __hash__( self ):
return ( self._shortcut_type, self._shortcut_key, tuple( self._modifiers ) ).__hash__()
def __repr__( self ):
return 'Shortcut: ' + self.ToString()
def _GetSerialisableInfo( self ):
return ( self._shortcut_type, self._shortcut_key, self._modifiers )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( self._shortcut_type, self._shortcut_key, self._modifiers ) = serialisable_info
def GetShortcutType( self ):
return self._shortcut_type
def ToString( self ):
components = []
if CC.SHORTCUT_MODIFIER_CTRL in self._modifiers:
components.append( 'ctrl' )
if CC.SHORTCUT_MODIFIER_ALT in self._modifiers:
components.append( 'alt' )
if CC.SHORTCUT_MODIFIER_SHIFT in self._modifiers:
components.append( 'shift' )
if self._shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD:
if self._shortcut_key in CC.wxk_code_string_lookup:
components.append( CC.wxk_code_string_lookup[ self._shortcut_key ] )
elif OrdIsAlphaUpper( self._shortcut_key ):
components.append( chr( self._shortcut_key + 32 ) ) # + 32 for converting ascii A -> a
elif OrdIsSensibleASCII( self._shortcut_key ):
components.append( chr( self._shortcut_key ) )
else:
components.append( 'unknown key' )
elif self._shortcut_type == CC.SHORTCUT_TYPE_MOUSE:
components.append( CC.shortcut_mouse_string_lookup[ self._shortcut_key ] )
return '+'.join( components )
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT ] = Shortcut
class Shortcuts( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS
SERIALISABLE_NAME = 'Shortcuts'
SERIALISABLE_VERSION = 2
def __init__( self, name ):
HydrusSerialisable.SerialisableBaseNamed.__init__( self, name )
self._shortcuts_to_commands = {}
def __iter__( self ):
for ( shortcut, command ) in self._shortcuts_to_commands.items():
yield ( shortcut, command )
def __len__( self ):
return len( self._shortcuts_to_commands )
def _GetSerialisableInfo( self ):
return [ ( shortcut.GetSerialisableTuple(), command.GetSerialisableTuple() ) for ( shortcut, command ) in self._shortcuts_to_commands.items() ]
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
for ( serialisable_shortcut, serialisable_command ) in serialisable_info:
shortcut = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_shortcut )
command = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_command )
self._shortcuts_to_commands[ shortcut ] = command
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
( serialisable_mouse_actions, serialisable_keyboard_actions ) = old_serialisable_info
shortcuts_to_commands = {}
# this never stored mouse actions, so skip
services_manager = HG.client_controller.services_manager
for ( modifier, key, ( serialisable_service_key, data ) ) in serialisable_keyboard_actions:
if modifier not in CC.shortcut_wx_to_hydrus_lookup:
modifiers = []
else:
modifiers = [ CC.shortcut_wx_to_hydrus_lookup[ modifier ] ]
shortcut = Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, key, modifiers )
if serialisable_service_key is None:
command = ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, data )
else:
service_key = serialisable_service_key.decode( 'hex' )
if not services_manager.ServiceExists( service_key ):
continue
action = HC.CONTENT_UPDATE_FLIP
value = data
service = services_manager.GetService( service_key )
service_type = service.GetServiceType()
if service_type in HC.TAG_SERVICES:
content_type = HC.CONTENT_TYPE_MAPPINGS
elif service_type in HC.RATINGS_SERVICES:
content_type = HC.CONTENT_TYPE_RATINGS
else:
continue
command = ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( service_key, content_type, action, value ) )
shortcuts_to_commands[ shortcut ] = command
new_serialisable_info = ( ( shortcut.GetSerialisableTuple(), command.GetSerialisableTuple() ) for ( shortcut, command ) in shortcuts_to_commands.items() )
return ( 2, new_serialisable_info )
def GetCommand( self, shortcut ):
if shortcut in self._shortcuts_to_commands:
return self._shortcuts_to_commands[ shortcut ]
else:
return None
def SetCommand( self, shortcut, command ):
self._shortcuts_to_commands[ shortcut ] = command
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS ] = Shortcuts
def ConvertKeyEventToShortcut( event ):
key = event.KeyCode
if OrdIsSensibleASCII( key ) or key in CC.wxk_code_string_lookup.keys():
modifiers = []
if event.AltDown():
modifiers.append( CC.SHORTCUT_MODIFIER_ALT )
if event.CmdDown():
modifiers.append( CC.SHORTCUT_MODIFIER_CTRL )
if event.ShiftDown():
modifiers.append( CC.SHORTCUT_MODIFIER_SHIFT )
shortcut = Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, key, modifiers )
if HG.gui_report_mode:
HydrusData.ShowText( 'key event caught: ' + repr( shortcut ) )
return shortcut
return None
def ConvertKeyEventToSimpleTuple( event ):
modifier = wx.ACCEL_NORMAL
if event.AltDown(): modifier = wx.ACCEL_ALT
elif event.CmdDown(): modifier = wx.ACCEL_CTRL
elif event.ShiftDown(): modifier = wx.ACCEL_SHIFT
key = event.KeyCode
return ( modifier, key )
def ConvertMouseEventToShortcut( event ):
key = None
if event.LeftDown() or event.LeftDClick():
key = CC.SHORTCUT_MOUSE_LEFT
elif event.MiddleDown() or event.MiddleDClick():
key = CC.SHORTCUT_MOUSE_MIDDLE
elif event.RightDown() or event.RightDClick():
key = CC.SHORTCUT_MOUSE_RIGHT
elif event.GetWheelRotation() > 0:
key = CC.SHORTCUT_MOUSE_SCROLL_UP
elif event.GetWheelRotation() < 0:
key = CC.SHORTCUT_MOUSE_SCROLL_DOWN
if key is not None:
modifiers = []
if event.AltDown():
modifiers.append( CC.SHORTCUT_MODIFIER_ALT )
if event.CmdDown():
modifiers.append( CC.SHORTCUT_MODIFIER_CTRL )
if event.ShiftDown():
modifiers.append( CC.SHORTCUT_MODIFIER_SHIFT )
shortcut = Shortcut( CC.SHORTCUT_TYPE_MOUSE, key, modifiers )
if HG.gui_report_mode:
HydrusData.ShowText( 'mouse event caught: ' + repr( shortcut ) )
return shortcut
return None
class TagCensor( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_TAG_CENSOR
SERIALISABLE_NAME = 'Tag Censorship Rules'
SERIALISABLE_VERSION = 1
def __init__( self ):
HydrusSerialisable.SerialisableBase.__init__( self )
self._lock = threading.Lock()
self._tag_slices_to_rules = {}
def __eq__( self, other ):
return self._tag_slices_to_rules == other._tag_slices_to_rules
def _GetRulesForTag( self, tag ):
rules = []
tag_slices = []
( namespace, subtag ) = HydrusTags.SplitTag( tag )
tag_slices.append( tag )
if namespace != '':
tag_slices.append( namespace + ':' )
tag_slices.append( ':' )
else:
tag_slices.append( '' )
for tag_slice in tag_slices:
if tag_slice in self._tag_slices_to_rules:
rule = self._tag_slices_to_rules[ tag_slice ]
rules.append( rule )
return rules
def _GetSerialisableInfo( self ):
return self._tag_slices_to_rules.items()
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
self._tag_slices_to_rules = dict( serialisable_info )
def _TagOK( self, tag ):
rules = self._GetRulesForTag( tag )
if CC.CENSOR_WHITELIST in rules: # There is an exception for this tag
return True
elif CC.CENSOR_BLACKLIST in rules: # There is a rule against this tag
return False
else: # There are no rules for this tag
return True
def Censor( self, tags ):
with self._lock:
return { tag for tag in tags if self._TagOK( tag ) }
def GetTagSlicesToRules( self ):
with self._lock:
return dict( self._tag_slices_to_rules )
def SetRule( self, tag_slice, rule ):
with self._lock:
self._tag_slices_to_rules[ tag_slice ] = rule
def ToCensoredString( self ):
blacklist = []
whitelist = []
for ( tag_slice, rule ) in self._tag_slices_to_rules.items():
if rule == CC.CENSOR_BLACKLIST:
blacklist.append( tag_slice )
elif rule == CC.CENSOR_WHITELIST:
whitelist.append( tag_slice )
blacklist.sort()
whitelist.sort()
if len( blacklist ) == 0:
return 'all tags allowed'
else:
if set( blacklist ) == { '', ':' }:
text = 'no tags allowed'
else:
text = 'censoring ' + ', '.join( ( ConvertTagSliceToString( tag_slice ) for tag_slice in blacklist ) )
if len( whitelist ) > 0:
text += ' except ' + ', '.join( ( ConvertTagSliceToString( tag_slice ) for tag_slice in whitelist ) )
return text
def ToPermittedString( self ):
blacklist = []
whitelist = []
for ( tag_slice, rule ) in self._tag_slices_to_rules.items():
if rule == CC.CENSOR_BLACKLIST:
blacklist.append( tag_slice )
elif rule == CC.CENSOR_WHITELIST:
whitelist.append( tag_slice )
blacklist.sort()
whitelist.sort()
if len( blacklist ) == 0:
return 'all tags'
else:
if set( blacklist ) == { '', ':' }:
text = 'no tags'
if len( whitelist ) > 0:
text += ' except ' + ', '.join( ( ConvertTagSliceToString( tag_slice ) for tag_slice in whitelist ) )
else:
text = 'all tags except ' + ', '.join( ( ConvertTagSliceToString( tag_slice ) for tag_slice in blacklist ) )
if len( whitelist ) > 0:
text += ' (except ' + ', '.join( ( ConvertTagSliceToString( tag_slice ) for tag_slice in whitelist ) ) + ')'
text += ' permitted'
return text
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_TAG_CENSOR ] = TagCensor
class CheckerOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CHECKER_OPTIONS
SERIALISABLE_NAME = 'Checker Timing Options'
SERIALISABLE_VERSION = 1
def __init__( self, intended_files_per_check = 8, never_faster_than = 300, never_slower_than = 86400, death_file_velocity = ( 1, 86400 ) ):
HydrusSerialisable.SerialisableBase.__init__( self )
self._intended_files_per_check = intended_files_per_check
self._never_faster_than = never_faster_than
self._never_slower_than = never_slower_than
self._death_file_velocity = death_file_velocity
def _GetCurrentFilesVelocity( self, seed_cache, last_check_time ):
( death_files_found, death_time_delta ) = self._death_file_velocity
since = last_check_time - death_time_delta
current_files_found = seed_cache.GetNumNewFilesSince( since )
# when a thread is only 30mins old (i.e. first file was posted 30 mins ago), we don't want to calculate based on a longer delete time delta
# we want next check to be like 30mins from now, not 12 hours
# so we'll say "5 files in 30 mins" rather than "5 files in 24 hours"
earliest_source_time = seed_cache.GetEarliestSourceTime()
if earliest_source_time is None:
current_time_delta = death_time_delta
else:
early_time_delta = max( last_check_time - earliest_source_time, 30 )
current_time_delta = min( early_time_delta, death_time_delta )
return ( current_files_found, current_time_delta )
def _GetSerialisableInfo( self ):
return ( self._intended_files_per_check, self._never_faster_than, self._never_slower_than, self._death_file_velocity )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( self._intended_files_per_check, self._never_faster_than, self._never_slower_than, self._death_file_velocity ) = serialisable_info
def GetNextCheckTime( self, seed_cache, last_check_time ):
if len( seed_cache ) == 0:
if last_check_time == 0:
return 0 # haven't checked yet, so should check immediately
else:
return HydrusData.GetNow() + self._never_slower_than
else:
( current_files_found, current_time_delta ) = self._GetCurrentFilesVelocity( seed_cache, last_check_time )
if current_files_found == 0:
# this shouldn't typically matter, since a dead checker won't care about next check time
# so let's just have a nice safe value in case this is ever asked legit
check_period = self._never_slower_than
else:
approx_time_per_file = current_time_delta / current_files_found
ideal_check_period = self._intended_files_per_check * approx_time_per_file
# if a thread produced lots of files and then stopped completely for whatever reason, we don't want to keep checking fast
# so, we set a lower limit of time since last file upload, neatly doubling our check period in these situations
latest_source_time = seed_cache.GetLatestSourceTime()
time_since_latest_file = max( last_check_time - latest_source_time, 30 )
never_faster_than = max( self._never_faster_than, time_since_latest_file )
check_period = min( max( never_faster_than, ideal_check_period ), self._never_slower_than )
return last_check_time + check_period
def GetRawCurrentVelocity( self, seed_cache, last_check_time ):
return self._GetCurrentFilesVelocity( seed_cache, last_check_time )
def GetPrettyCurrentVelocity( self, seed_cache, last_check_time, no_prefix = False ):
if len( seed_cache ) == 0:
if last_check_time == 0:
pretty_current_velocity = 'no files yet'
else:
pretty_current_velocity = 'no files, unable to determine velocity'
else:
if no_prefix:
pretty_current_velocity = ''
else:
pretty_current_velocity = 'at last check, found '
( current_files_found, current_time_delta ) = self._GetCurrentFilesVelocity( seed_cache, last_check_time )
pretty_current_velocity += HydrusData.ConvertIntToPrettyString( current_files_found ) + ' files in previous ' + HydrusData.ConvertTimeDeltaToPrettyString( current_time_delta )
return pretty_current_velocity
def IsDead( self, seed_cache, last_check_time ):
if len( seed_cache ) == 0 and last_check_time == 0:
return False
else:
( current_files_found, current_time_delta ) = self._GetCurrentFilesVelocity( seed_cache, last_check_time )
( death_files_found, deleted_time_delta ) = self._death_file_velocity
current_file_velocity_float = current_files_found / float( current_time_delta )
death_file_velocity_float = death_files_found / float( deleted_time_delta )
return current_file_velocity_float < death_file_velocity_float
def ToTuple( self ):
return ( self._intended_files_per_check, self._never_faster_than, self._never_slower_than, self._death_file_velocity )
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_CHECKER_OPTIONS ] = CheckerOptions