2747 lines
98 KiB
Python
2747 lines
98 KiB
Python
import ClientConstants as CC
|
|
import ClientDefaults
|
|
import ClientDownloading
|
|
import ClientFiles
|
|
import ClientNetworking
|
|
import ClientThreading
|
|
import collections
|
|
import datetime
|
|
import HydrusConstants as HC
|
|
import HydrusExceptions
|
|
import HydrusFileHandling
|
|
import HydrusPaths
|
|
import HydrusSerialisable
|
|
import HydrusTags
|
|
import threading
|
|
import traceback
|
|
import os
|
|
import random
|
|
import requests
|
|
import shutil
|
|
import sqlite3
|
|
import stat
|
|
import sys
|
|
import time
|
|
import urllib
|
|
import wx
|
|
import yaml
|
|
import HydrusData
|
|
import HydrusGlobals
|
|
import HydrusThreading
|
|
|
|
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
|
|
|
|
|
|
job_key = ClientThreading.JobKey()
|
|
|
|
if etype == HydrusExceptions.ShutdownException:
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
if etype == wx.PyDeadObjectError:
|
|
|
|
HydrusData.Print( 'Got a PyDeadObjectError, which can probably be ignored, but here it is anyway:' )
|
|
HydrusData.Print( trace )
|
|
|
|
return
|
|
|
|
|
|
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 )
|
|
|
|
HydrusGlobals.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 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 = HydrusGlobals.client_controller.GetServicesManager().GetService( service_key ).GetName()
|
|
|
|
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 ] )
|
|
|
|
num_files += len( content_update.GetHashes() )
|
|
|
|
|
|
|
|
s = ', '.join( locations ) + '->' + ', '.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 range( 65, 91 ): key = chr( key + 32 ) # + 32 for converting ascii A -> a
|
|
elif key in range( 97, 123 ): key = chr( key )
|
|
else: key = CC.wxk_code_string_lookup[ key ]
|
|
|
|
return ( modifier, key )
|
|
|
|
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 GenerateService( service_key, service_type, name, info ):
|
|
|
|
if service_type in HC.REPOSITORIES:
|
|
|
|
cl = ServiceRepository
|
|
|
|
elif service_type in HC.RESTRICTED_SERVICES:
|
|
|
|
cl = ServiceRestricted
|
|
|
|
elif service_type == HC.IPFS:
|
|
|
|
cl = ServiceIPFS
|
|
|
|
elif service_type in HC.REMOTE_SERVICES:
|
|
|
|
cl = ServiceRemote
|
|
|
|
elif service_type == HC.LOCAL_BOORU:
|
|
|
|
cl = ServiceLocalBooru
|
|
|
|
else:
|
|
|
|
cl = Service
|
|
|
|
|
|
return cl( service_key, service_type, name, info )
|
|
|
|
def GetMediasTagCount( pool, tag_service_key = CC.COMBINED_TAG_SERVICE_KEY, collapse_siblings = False ):
|
|
|
|
siblings_manager = HydrusGlobals.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.CURRENT ] )
|
|
deleted_tags_to_count.update( statuses_to_tags[ HC.DELETED ] )
|
|
pending_tags_to_count.update( statuses_to_tags[ HC.PENDING ] )
|
|
petitioned_tags_to_count.update( statuses_to_tags[ HC.PETITIONED ] )
|
|
|
|
|
|
return ( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count )
|
|
|
|
def GetSortChoices( add_namespaces_and_ratings = True ):
|
|
|
|
sort_choices = list( CC.SORT_CHOICES )
|
|
|
|
if add_namespaces_and_ratings:
|
|
|
|
sort_choices.extend( HC.options[ 'sort_by' ] )
|
|
|
|
ratings_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
|
|
|
|
for ratings_service in ratings_services:
|
|
|
|
sort_choices.append( ( 'rating_descend', ratings_service ) )
|
|
sort_choices.append( ( 'rating_ascend', ratings_service ) )
|
|
|
|
|
|
|
|
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 ):
|
|
|
|
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
|
|
|
|
|
|
|
|
return master_predicate_dict.values()
|
|
|
|
def ShowExceptionClient( e ):
|
|
|
|
( etype, value, tb ) = sys.exc_info()
|
|
|
|
if etype is None:
|
|
|
|
etype = type( e )
|
|
value = HydrusData.ToUnicode( e )
|
|
|
|
trace = ''.join( traceback.format_stack() )
|
|
|
|
else:
|
|
|
|
trace = ''.join( traceback.format_exception( etype, value, tb ) )
|
|
|
|
|
|
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 etype == wx.PyDeadObjectError:
|
|
|
|
HydrusData.Print( 'Got a PyDeadObjectError, which can probably be ignored, but here it is anyway:' )
|
|
HydrusData.Print( trace )
|
|
|
|
return
|
|
|
|
|
|
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 )
|
|
|
|
HydrusGlobals.client_controller.pub( 'message', job_key )
|
|
|
|
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 )
|
|
|
|
HydrusGlobals.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 ):
|
|
|
|
if ':' in tag:
|
|
|
|
( namespace, subtag ) = tag.split( ':', 1 )
|
|
|
|
if namespace == '':
|
|
|
|
return ( '{', subtag )
|
|
|
|
else:
|
|
|
|
return ( namespace, subtag )
|
|
|
|
|
|
else:
|
|
|
|
return ( '{', tag ) # '{' is above 'z' in ascii, so this works for most situations
|
|
|
|
|
|
|
|
else:
|
|
|
|
key = None
|
|
|
|
|
|
tags.sort( key = key, reverse = reverse )
|
|
|
|
def WaitPolitely( page_key = None ):
|
|
|
|
if page_key is not None:
|
|
|
|
HydrusGlobals.client_controller.pub( 'waiting_politely', page_key, True )
|
|
|
|
|
|
time.sleep( HC.options[ 'website_download_polite_wait' ] )
|
|
|
|
if page_key is not None:
|
|
|
|
HydrusGlobals.client_controller.pub( 'waiting_politely', page_key, False )
|
|
|
|
|
|
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_VERSION = 2
|
|
|
|
def __init__( self, db_dir = None ):
|
|
|
|
HydrusSerialisable.SerialisableBase.__init__( self )
|
|
|
|
if db_dir is None:
|
|
|
|
db_dir = os.path.join( HC.BASE_DIR, 'db' )
|
|
|
|
|
|
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' ][ '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' ][ 'waiting_politely_text' ] = 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' ][ 'show_related_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' ] = True
|
|
|
|
#
|
|
|
|
self._dictionary[ 'integers' ] = {}
|
|
|
|
self._dictionary[ 'integers' ][ 'video_buffer_size_mb' ] = 96
|
|
|
|
self._dictionary[ 'integers' ][ 'related_tags_width' ] = 150
|
|
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[ 'keys' ] = {}
|
|
|
|
self._dictionary[ 'keys' ][ 'default_tag_service_search_page' ] = CC.COMBINED_TAG_SERVICE_KEY.encode( 'hex' )
|
|
|
|
#
|
|
|
|
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' ][ 'suggested_tags_width' ] = None
|
|
|
|
self._dictionary[ 'noneable_integers' ][ 'num_recent_tags' ] = None
|
|
|
|
self._dictionary[ 'noneable_integers' ][ 'maintenance_vacuum_period_days' ] = 30
|
|
|
|
#
|
|
|
|
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_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' ][ 'main_gui' ] = ( True, True, ( 640, 480 ), ( 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_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[ '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 ) )
|
|
|
|
jpg_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 )
|
|
png_zoom_info = ( CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, True, CC.ZOOM_NEAREST, 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, jpg_zoom_info )
|
|
self._dictionary[ 'media_view' ][ HC.IMAGE_PNG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, png_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.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[ 'suggested_tags' ] = HydrusSerialisable.SerialisableDictionary()
|
|
|
|
self._dictionary[ 'suggested_tags' ][ 'favourites' ] = {}
|
|
|
|
|
|
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 )
|
|
|
|
|
|
|
|
def ClearDefaultImportTagOptions( 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 GetDefaultImportTagOptions( self, gallery_identifier = None ):
|
|
|
|
with self._lock:
|
|
|
|
default_import_tag_options = self._dictionary[ 'default_import_tag_options' ]
|
|
|
|
if gallery_identifier is None:
|
|
|
|
return default_import_tag_options
|
|
|
|
else:
|
|
|
|
if gallery_identifier in default_import_tag_options:
|
|
|
|
import_tag_options = default_import_tag_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_import_tag_options = None
|
|
|
|
site_type = gallery_identifier.GetSiteType()
|
|
|
|
if site_type == HC.SITE_TYPE_BOORU and default_booru_gallery_identifier in default_import_tag_options:
|
|
|
|
guidance_import_tag_options = default_import_tag_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_import_tag_options:
|
|
|
|
guidance_import_tag_options = default_import_tag_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_import_tag_options:
|
|
|
|
guidance_import_tag_options = default_import_tag_options[ default_pixiv_gallery_identifier ]
|
|
|
|
elif default_gallery_identifier in default_import_tag_options:
|
|
|
|
guidance_import_tag_options = default_import_tag_options[ default_gallery_identifier ]
|
|
|
|
|
|
service_keys_to_namespaces = {}
|
|
service_keys_to_explicit_tags = {}
|
|
|
|
if guidance_import_tag_options is not None:
|
|
|
|
( namespaces, search_value ) = ClientDefaults.GetDefaultNamespacesAndSearchValue( gallery_identifier )
|
|
|
|
guidance_service_keys_to_namespaces = guidance_import_tag_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_import_tag_options.GetServiceKeysToExplicitTags()
|
|
|
|
|
|
import_tag_options = ImportTagOptions( service_keys_to_namespaces = service_keys_to_namespaces, service_keys_to_explicit_tags = service_keys_to_explicit_tags )
|
|
|
|
|
|
return import_tag_options
|
|
|
|
|
|
|
|
|
|
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 GetMediaShowAction( self, mime ):
|
|
|
|
with self._lock:
|
|
|
|
( media_show_action, preview_show_action, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
|
|
|
|
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 GetNoneableInteger( self, name ):
|
|
|
|
with self._lock:
|
|
|
|
return self._dictionary[ 'noneable_integers' ][ name ]
|
|
|
|
|
|
|
|
def GetPreviewShowAction( self, mime ):
|
|
|
|
with self._lock:
|
|
|
|
( media_show_action, preview_show_action, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
|
|
|
|
return preview_show_action
|
|
|
|
|
|
|
|
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 SetBoolean( self, name, value ):
|
|
|
|
with self._lock:
|
|
|
|
self._dictionary[ 'booleans' ][ name ] = value
|
|
|
|
|
|
|
|
def SetClientFilesLocationsToIdealWeights( self, locations_to_weights, resized_thumbnail_override, full_size_thumbnail_override ):
|
|
|
|
with self._lock:
|
|
|
|
portable_locations_and_weights = [ ( HydrusPaths.ConvertAbsPathToPortablePath( location ), float( weight ) ) for ( location, weight ) in locations_to_weights.items() ]
|
|
|
|
self._dictionary[ 'client_files_locations_ideal_weights' ] = portable_locations_and_weights
|
|
|
|
if resized_thumbnail_override is not None:
|
|
|
|
resized_thumbnail_override = HydrusPaths.ConvertAbsPathToPortablePath( resized_thumbnail_override )
|
|
|
|
|
|
self._dictionary[ 'client_files_locations_resized_thumbnail_override' ] = resized_thumbnail_override
|
|
self._dictionary[ 'client_files_locations_full_size_thumbnail_override' ] = full_size_thumbnail_override
|
|
|
|
|
|
|
|
def SetDefaultImportTagOptions( self, gallery_identifier, import_tag_options ):
|
|
|
|
with self._lock:
|
|
|
|
self._dictionary[ 'default_import_tag_options' ][ gallery_identifier ] = import_tag_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 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 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 SetNoneableInteger( self, name, value ):
|
|
|
|
with self._lock:
|
|
|
|
self._dictionary[ 'noneable_integers' ][ 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 CommandHandler( object ):
|
|
|
|
def __init__( self, action_cache ):
|
|
|
|
self._action_cache = action_cache
|
|
self._commands_to_callables = {}
|
|
|
|
|
|
def ProcessMenuEvent( self, menu_event ):
|
|
|
|
event_id = menu_event.GetId()
|
|
|
|
action = self._action_cache.GetAction( event_id )
|
|
|
|
if action is not None:
|
|
|
|
( command, data ) = action
|
|
|
|
if command in self._commands_to_callables:
|
|
|
|
callable = self._commands_to_callables[ command ]
|
|
|
|
if data is None:
|
|
|
|
wx.CallAfter( callable )
|
|
|
|
else:
|
|
|
|
wx.CallAfter( callable, data )
|
|
|
|
|
|
|
|
|
|
|
|
def SetCallable( self, command, callable ):
|
|
|
|
self._commands_to_callables[ command ] = callable
|
|
|
|
|
|
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 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 ):
|
|
|
|
( hash, inbox, size, mime, width, height, duration, num_frames, num_words, tags_manager, locations_manager, ratings_manager ) = media_result.ToTuple()
|
|
|
|
if CC.RESTRICTION_MIN_RESOLUTION in self._restrictions:
|
|
|
|
( min_width, min_height ) = self._restrictions[ CC.RESTRICTION_MIN_RESOLUTION ]
|
|
|
|
if width < min_width or height < min_height: return False
|
|
|
|
|
|
if CC.RESTRICTION_MAX_RESOLUTION in self._restrictions:
|
|
|
|
( max_width, max_height ) = self._restrictions[ CC.RESTRICTION_MAX_RESOLUTION ]
|
|
|
|
if width > max_width or height > max_height: return False
|
|
|
|
|
|
if CC.RESTRICTION_MAX_FILE_SIZE in self._restrictions and size > self._restrictions[ CC.RESTRICTION_MAX_FILE_SIZE ]: return False
|
|
|
|
if CC.RESTRICTION_ALLOWED_MIMES in self._restrictions and mime not in self._restrictions[ CC.RESTRICTION_ALLOWED_MIMES ]: return False
|
|
|
|
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 ImportFileOptions( HydrusSerialisable.SerialisableBase ):
|
|
|
|
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FILE_OPTIONS
|
|
SERIALISABLE_VERSION = 1
|
|
|
|
def __init__( self, automatic_archive = None, exclude_deleted = None, min_size = None, min_resolution = None ):
|
|
|
|
HydrusSerialisable.SerialisableBase.__init__( self )
|
|
|
|
self._automatic_archive = automatic_archive
|
|
self._exclude_deleted = exclude_deleted
|
|
self._min_size = min_size
|
|
self._min_resolution = min_resolution
|
|
|
|
|
|
def _GetSerialisableInfo( self ):
|
|
|
|
return ( self._automatic_archive, self._exclude_deleted, self._min_size, self._min_resolution )
|
|
|
|
|
|
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
|
|
|
( self._automatic_archive, self._exclude_deleted, self._min_size, self._min_resolution ) = serialisable_info
|
|
|
|
|
|
def FileIsValid( self, size, resolution = None ):
|
|
|
|
if self._min_size is not None and size < self._min_size:
|
|
|
|
return False
|
|
|
|
|
|
if resolution is not None and self._min_resolution is not None:
|
|
|
|
( x, y ) = resolution
|
|
|
|
( min_x, min_y ) = self._min_resolution
|
|
|
|
if x < min_x or y < min_y:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
def GetAutomaticArchive( self ):
|
|
|
|
return self._automatic_archive
|
|
|
|
|
|
def GetExcludeDeleted( self ):
|
|
|
|
return self._exclude_deleted
|
|
|
|
|
|
def ToTuple( self ):
|
|
|
|
return ( self._automatic_archive, self._exclude_deleted, self._min_size, self._min_resolution )
|
|
|
|
|
|
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FILE_OPTIONS ] = ImportFileOptions
|
|
|
|
class ImportTagOptions( HydrusSerialisable.SerialisableBase ):
|
|
|
|
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_TAG_OPTIONS
|
|
SERIALISABLE_VERSION = 2
|
|
|
|
def __init__( self, service_keys_to_namespaces = None, service_keys_to_explicit_tags = None ):
|
|
|
|
HydrusSerialisable.SerialisableBase.__init__( self )
|
|
|
|
if service_keys_to_namespaces is None:
|
|
|
|
service_keys_to_namespaces = {}
|
|
|
|
|
|
if service_keys_to_explicit_tags is None:
|
|
|
|
service_keys_to_explicit_tags = {}
|
|
|
|
|
|
self._service_keys_to_namespaces = service_keys_to_namespaces
|
|
self._service_keys_to_explicit_tags = service_keys_to_explicit_tags
|
|
|
|
|
|
def _GetSerialisableInfo( self ):
|
|
|
|
safe_service_keys_to_namespaces = { service_key.encode( 'hex' ) : list( namespaces ) for ( service_key, namespaces ) in self._service_keys_to_namespaces.items() }
|
|
safe_service_keys_to_explicit_tags = { service_key.encode( 'hex' ) : list( tags ) for ( service_key, tags ) in self._service_keys_to_explicit_tags.items() }
|
|
|
|
return ( safe_service_keys_to_namespaces, safe_service_keys_to_explicit_tags )
|
|
|
|
|
|
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
|
|
|
( safe_service_keys_to_namespaces, safe_service_keys_to_explicit_tags ) = serialisable_info
|
|
|
|
self._service_keys_to_namespaces = { service_key.decode( 'hex' ) : set( namespaces ) for ( service_key, namespaces ) in safe_service_keys_to_namespaces.items() }
|
|
self._service_keys_to_explicit_tags = { service_key.decode( 'hex' ) : set( tags ) for ( service_key, tags ) in safe_service_keys_to_explicit_tags.items() }
|
|
|
|
|
|
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
|
|
|
|
if version == 1:
|
|
|
|
safe_service_keys_to_namespaces = old_serialisable_info
|
|
|
|
safe_service_keys_to_explicit_tags = {}
|
|
|
|
new_serialisable_info = ( safe_service_keys_to_namespaces, safe_service_keys_to_explicit_tags )
|
|
|
|
return ( 2, new_serialisable_info )
|
|
|
|
|
|
|
|
def GetServiceKeysToExplicitTags( self ):
|
|
|
|
return dict( self._service_keys_to_explicit_tags )
|
|
|
|
|
|
def GetServiceKeysToNamespaces( self ):
|
|
|
|
return dict( self._service_keys_to_namespaces )
|
|
|
|
|
|
def GetServiceKeysToContentUpdates( self, hash, tags ):
|
|
|
|
tags = [ tag for tag in tags if tag is not None ]
|
|
|
|
service_keys_to_tags = collections.defaultdict( set )
|
|
|
|
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
|
|
parents_manager = HydrusGlobals.client_controller.GetManager( 'tag_parents' )
|
|
|
|
for ( service_key, namespaces ) in self._service_keys_to_namespaces.items():
|
|
|
|
tags_to_add_here = []
|
|
|
|
if len( namespaces ) > 0:
|
|
|
|
for namespace in namespaces:
|
|
|
|
if namespace == '': tags_to_add_here.extend( [ tag for tag in tags if not ':' in tag ] )
|
|
else: tags_to_add_here.extend( [ tag for tag in tags if tag.startswith( namespace + ':' ) ] )
|
|
|
|
|
|
|
|
tags_to_add_here = HydrusTags.CleanTags( tags_to_add_here )
|
|
|
|
if len( tags_to_add_here ) > 0:
|
|
|
|
tags_to_add_here = siblings_manager.CollapseTags( service_key, tags_to_add_here )
|
|
tags_to_add_here = parents_manager.ExpandTags( service_key, tags_to_add_here )
|
|
|
|
service_keys_to_tags[ service_key ].update( tags_to_add_here )
|
|
|
|
|
|
|
|
for ( service_key, explicit_tags ) in self._service_keys_to_explicit_tags.items():
|
|
|
|
tags_to_add_here = HydrusTags.CleanTags( explicit_tags )
|
|
|
|
if len( tags_to_add_here ) > 0:
|
|
|
|
tags_to_add_here = siblings_manager.CollapseTags( service_key, tags_to_add_here )
|
|
tags_to_add_here = parents_manager.ExpandTags( service_key, tags_to_add_here )
|
|
|
|
service_keys_to_tags[ service_key ].update( tags_to_add_here )
|
|
|
|
|
|
|
|
service_keys_to_content_updates = ConvertServiceKeysToTagsToServiceKeysToContentUpdates( { hash }, service_keys_to_tags )
|
|
|
|
return service_keys_to_content_updates
|
|
|
|
|
|
def ShouldFetchTags( self ):
|
|
|
|
i_am_interested_in_namespaces = len( self._service_keys_to_namespaces ) > 0
|
|
|
|
return i_am_interested_in_namespaces
|
|
|
|
|
|
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_TAG_OPTIONS ] = ImportTagOptions
|
|
|
|
class Service( object ):
|
|
|
|
def __init__( self, service_key, service_type, name, info ):
|
|
|
|
self._service_key = service_key
|
|
self._service_type = service_type
|
|
self._name = name
|
|
self._info = info
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
HydrusGlobals.client_controller.sub( self, 'ProcessServiceUpdates', 'service_updates_data' )
|
|
|
|
|
|
def __hash__( self ): return self._service_key.__hash__()
|
|
|
|
def _ProcessServiceUpdate( self, service_update ):
|
|
|
|
pass
|
|
|
|
|
|
def GetInfo( self, key = None ):
|
|
|
|
if key is None:
|
|
|
|
return self._info
|
|
|
|
else:
|
|
|
|
return self._info[ key ]
|
|
|
|
|
|
|
|
def GetServiceKey( self ): return self._service_key
|
|
|
|
def GetName( self ): return self._name
|
|
|
|
def GetServiceType( self ): return self._service_type
|
|
|
|
def ProcessServiceUpdates( self, service_keys_to_service_updates ):
|
|
|
|
for ( service_key, service_updates ) in service_keys_to_service_updates.items():
|
|
|
|
for service_update in service_updates:
|
|
|
|
if service_key == self._service_key:
|
|
|
|
self._ProcessServiceUpdate( service_update )
|
|
|
|
|
|
|
|
|
|
|
|
def ToTuple( self ): return ( self._service_key, self._service_type, self._name, self._info )
|
|
|
|
class ServiceLocalBooru( Service ):
|
|
|
|
def _ProcessServiceUpdate( self, service_update ):
|
|
|
|
Service._ProcessServiceUpdate( self, service_update )
|
|
|
|
( action, row ) = service_update.ToTuple()
|
|
|
|
if action == HC.SERVICE_UPDATE_REQUEST_MADE:
|
|
|
|
num_bytes = row
|
|
|
|
self._info[ 'used_monthly_data' ] += num_bytes
|
|
self._info[ 'used_monthly_requests' ] += 1
|
|
|
|
|
|
|
|
class ServiceRemote( Service ):
|
|
|
|
def GetCredentials( self ):
|
|
|
|
host = self._info[ 'host' ]
|
|
port = self._info[ 'port' ]
|
|
|
|
credentials = Credentials( host, port )
|
|
|
|
return credentials
|
|
|
|
|
|
def _GetErrorWaitPeriod( self ):
|
|
|
|
return 3600 * 4
|
|
|
|
|
|
def _ProcessServiceUpdate( self, service_update ):
|
|
|
|
Service._ProcessServiceUpdate( self, service_update )
|
|
|
|
( action, row ) = service_update.ToTuple()
|
|
|
|
if action == HC.SERVICE_UPDATE_ERROR:
|
|
|
|
self._info[ 'last_error' ] = HydrusData.GetNow()
|
|
|
|
elif action == HC.SERVICE_UPDATE_RESET:
|
|
|
|
self._info[ 'last_error' ] = 0
|
|
|
|
|
|
|
|
def GetRecentErrorPending( self ):
|
|
|
|
return HydrusData.ConvertTimestampToPrettyPending( self._info[ 'last_error' ] + self._GetErrorWaitPeriod() )
|
|
|
|
|
|
def HasRecentError( self ):
|
|
|
|
return not HydrusData.TimeHasPassed( self._info[ 'last_error' ] + self._GetErrorWaitPeriod() )
|
|
|
|
|
|
def SetCredentials( self, credentials ):
|
|
|
|
( host, port ) = credentials.GetAddress()
|
|
|
|
self._info[ 'host' ] = host
|
|
self._info[ 'port' ] = port
|
|
|
|
|
|
class ServiceRestricted( ServiceRemote ):
|
|
|
|
def _GetErrorWaitPeriod( self ):
|
|
|
|
if 'account' in self._info and self._info[ 'account' ].HasPermission( HC.GENERAL_ADMIN ):
|
|
|
|
return 900
|
|
|
|
else:
|
|
|
|
return ServiceRemote._GetErrorWaitPeriod( self )
|
|
|
|
|
|
|
|
def _ProcessServiceUpdate( self, service_update ):
|
|
|
|
ServiceRemote._ProcessServiceUpdate( self, service_update )
|
|
|
|
( action, row ) = service_update.ToTuple()
|
|
|
|
if action == HC.SERVICE_UPDATE_ACCOUNT:
|
|
|
|
account = row
|
|
|
|
self._info[ 'account' ] = account
|
|
self._info[ 'last_error' ] = 0
|
|
|
|
elif action == HC.SERVICE_UPDATE_REQUEST_MADE:
|
|
|
|
num_bytes = row
|
|
|
|
self._info[ 'account' ].RequestMade( num_bytes )
|
|
|
|
|
|
|
|
def _RecordHydrusBandwidth( self, method, command, data_used ):
|
|
|
|
if ( self._service_type, method, command ) in HC.BANDWIDTH_CONSUMING_REQUESTS:
|
|
|
|
HydrusGlobals.client_controller.pub( 'service_updates_delayed', { self._service_key : [ HydrusData.ServiceUpdate( HC.SERVICE_UPDATE_REQUEST_MADE, data_used ) ] } )
|
|
|
|
|
|
|
|
def CanDownload( self ):
|
|
|
|
return self._info[ 'account' ].HasPermission( HC.GET_DATA ) and not self.HasRecentError()
|
|
|
|
|
|
def CanUpload( self ): return self._info[ 'account' ].HasPermission( HC.POST_DATA ) and not self.HasRecentError()
|
|
|
|
def GetCredentials( self ):
|
|
|
|
host = self._info[ 'host' ]
|
|
port = self._info[ 'port' ]
|
|
|
|
if 'access_key' in self._info:
|
|
|
|
access_key = self._info[ 'access_key' ]
|
|
|
|
else:
|
|
|
|
access_key = None
|
|
|
|
|
|
credentials = Credentials( host, port, access_key )
|
|
|
|
return credentials
|
|
|
|
|
|
def IsInitialised( self ):
|
|
|
|
if self._service_type == HC.SERVER_ADMIN:
|
|
|
|
return 'access_key' in self._info
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def Request( self, method, command, request_args = None, request_headers = None, report_hooks = None, temp_path = None, return_cookies = False ):
|
|
|
|
if request_args is None: request_args = {}
|
|
if request_headers is None: request_headers = {}
|
|
if report_hooks is None: report_hooks = []
|
|
|
|
try:
|
|
|
|
credentials = self.GetCredentials()
|
|
|
|
if command in ( 'access_key', 'init', '' ):
|
|
|
|
pass
|
|
|
|
elif command in ( 'session_key', 'access_key_verification' ):
|
|
|
|
ClientNetworking.AddHydrusCredentialsToHeaders( credentials, request_headers )
|
|
|
|
else:
|
|
|
|
ClientNetworking.AddHydrusSessionKeyToHeaders( self._service_key, request_headers )
|
|
|
|
|
|
path = '/' + command
|
|
|
|
if method == HC.GET:
|
|
|
|
query = ClientNetworking.ConvertHydrusGETArgsToQuery( request_args )
|
|
|
|
body = ''
|
|
|
|
elif method == HC.POST:
|
|
|
|
query = ''
|
|
|
|
if command == 'file':
|
|
|
|
content_type = HC.APPLICATION_OCTET_STREAM
|
|
|
|
body = request_args[ 'file' ]
|
|
|
|
del request_args[ 'file' ]
|
|
|
|
else:
|
|
|
|
if isinstance( request_args, HydrusSerialisable.SerialisableDictionary ):
|
|
|
|
content_type = HC.APPLICATION_JSON
|
|
|
|
body = request_args.DumpToNetworkString()
|
|
|
|
else:
|
|
|
|
content_type = HC.APPLICATION_YAML
|
|
|
|
body = yaml.safe_dump( request_args )
|
|
|
|
|
|
|
|
request_headers[ 'Content-Type' ] = HC.mime_string_lookup[ content_type ]
|
|
|
|
|
|
if query != '':
|
|
|
|
path_and_query = path + '?' + query
|
|
|
|
else:
|
|
|
|
path_and_query = path
|
|
|
|
|
|
( host, port ) = credentials.GetAddress()
|
|
|
|
url = 'http://' + host + ':' + str( port ) + path_and_query
|
|
|
|
( response, size_of_response, response_headers, cookies ) = HydrusGlobals.client_controller.DoHTTP( method, url, request_headers, body, report_hooks = report_hooks, temp_path = temp_path, return_everything = True )
|
|
|
|
ClientNetworking.CheckHydrusVersion( self._service_key, self._service_type, response_headers )
|
|
|
|
if method == HC.GET:
|
|
|
|
data_used = size_of_response
|
|
|
|
elif method == HC.POST:
|
|
|
|
data_used = len( body )
|
|
|
|
|
|
self._RecordHydrusBandwidth( method, command, data_used )
|
|
|
|
if return_cookies:
|
|
|
|
return ( response, cookies )
|
|
|
|
else:
|
|
|
|
return response
|
|
|
|
|
|
except Exception as e:
|
|
|
|
if not isinstance( e, HydrusExceptions.ServerBusyException ):
|
|
|
|
if isinstance( e, HydrusExceptions.SessionException ):
|
|
|
|
session_manager = HydrusGlobals.client_controller.GetClientSessionManager()
|
|
|
|
session_manager.DeleteSessionKey( self._service_key )
|
|
|
|
|
|
HydrusGlobals.client_controller.Write( 'service_updates', { self._service_key : [ HydrusData.ServiceUpdate( HC.SERVICE_UPDATE_ERROR, HydrusData.ToUnicode( e ) ) ] } )
|
|
|
|
if isinstance( e, HydrusExceptions.PermissionException ):
|
|
|
|
if 'account' in self._info:
|
|
|
|
account_key = self._info[ 'account' ].GetAccountKey()
|
|
|
|
unknown_account = HydrusData.GetUnknownAccount( account_key )
|
|
|
|
else: unknown_account = HydrusData.GetUnknownAccount()
|
|
|
|
HydrusGlobals.client_controller.Write( 'service_updates', { self._service_key : [ HydrusData.ServiceUpdate( HC.SERVICE_UPDATE_ACCOUNT, unknown_account ) ] } )
|
|
|
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
def SetCredentials( self, credentials ):
|
|
|
|
ServiceRemote.SetCredentials( self, credentials )
|
|
|
|
if credentials.HasAccessKey():
|
|
|
|
self._info[ 'access_key' ] = credentials.GetAccessKey()
|
|
|
|
|
|
|
|
class ServiceRepository( ServiceRestricted ):
|
|
|
|
def _ProcessServiceUpdate( self, service_update ):
|
|
|
|
ServiceRestricted._ProcessServiceUpdate( self, service_update )
|
|
|
|
( action, row ) = service_update.ToTuple()
|
|
|
|
if action == HC.SERVICE_UPDATE_RESET:
|
|
|
|
if 'next_processing_timestamp' in self._info:
|
|
|
|
self._info[ 'next_processing_timestamp' ] = 0
|
|
|
|
|
|
elif action == HC.SERVICE_UPDATE_NEXT_DOWNLOAD_TIMESTAMP:
|
|
|
|
next_download_timestamp = row
|
|
|
|
if next_download_timestamp > self._info[ 'next_download_timestamp' ]:
|
|
|
|
if self._info[ 'first_timestamp' ] is None: self._info[ 'first_timestamp' ] = next_download_timestamp
|
|
|
|
self._info[ 'next_download_timestamp' ] = next_download_timestamp
|
|
|
|
|
|
elif action == HC.SERVICE_UPDATE_NEXT_PROCESSING_TIMESTAMP:
|
|
|
|
next_processing_timestamp = row
|
|
|
|
if next_processing_timestamp > self._info[ 'next_processing_timestamp' ]:
|
|
|
|
self._info[ 'next_processing_timestamp' ] = next_processing_timestamp
|
|
|
|
|
|
elif action == HC.SERVICE_UPDATE_PAUSE:
|
|
|
|
self._info[ 'paused' ] = True
|
|
|
|
|
|
|
|
def _ReportSyncProcessingError( self, path, error_text ):
|
|
|
|
text = 'While synchronising ' + self._name + ', the expected update file ' + path + ', ' + error_text + '.'
|
|
text += os.linesep * 2
|
|
text += 'The service has been indefinitely paused.'
|
|
text += os.linesep * 2
|
|
text += 'This is a serious error. Unless you know what went wrong, you should contact the developer.'
|
|
|
|
HydrusData.ShowText( text )
|
|
|
|
service_updates = [ HydrusData.ServiceUpdate( HC.SERVICE_UPDATE_PAUSE ) ]
|
|
|
|
service_keys_to_service_updates = { self._service_key : service_updates }
|
|
|
|
self.ProcessServiceUpdates( service_keys_to_service_updates )
|
|
|
|
HydrusGlobals.client_controller.Write( 'service_updates', service_keys_to_service_updates )
|
|
|
|
|
|
def CanDownloadUpdate( self ):
|
|
|
|
work_to_do = self.IsUpdateDueForDownload()
|
|
|
|
return work_to_do and self.CanDownload() and not self.IsPaused()
|
|
|
|
|
|
def CanProcessUpdate( self ):
|
|
|
|
work_to_do = self.IsUpdateDueForProcessing()
|
|
|
|
it_is_time = HydrusData.TimeHasPassed( self._info[ 'next_processing_timestamp' ] + HC.UPDATE_DURATION + HC.options[ 'processing_phase' ] )
|
|
|
|
return work_to_do and it_is_time and not self.IsPaused()
|
|
|
|
|
|
def GetTimestamps( self ): return ( self._info[ 'first_timestamp' ], self._info[ 'next_download_timestamp' ], self._info[ 'next_processing_timestamp' ] )
|
|
|
|
def GetUpdateStatus( self ):
|
|
|
|
account = self._info[ 'account' ]
|
|
|
|
now = HydrusData.GetNow()
|
|
first_timestamp = self._info[ 'first_timestamp' ]
|
|
next_download_timestamp = self._info[ 'next_download_timestamp' ]
|
|
next_processing_timestamp = self._info[ 'next_processing_timestamp' ]
|
|
|
|
if first_timestamp is None:
|
|
|
|
num_updates = 0
|
|
num_updates_downloaded = 0
|
|
num_updates_processed = 0
|
|
|
|
else:
|
|
|
|
num_updates = ( now - first_timestamp ) / HC.UPDATE_DURATION
|
|
num_updates_downloaded = ( next_download_timestamp - first_timestamp ) / HC.UPDATE_DURATION
|
|
num_updates_processed = max( 0, ( next_processing_timestamp - first_timestamp ) / HC.UPDATE_DURATION )
|
|
|
|
|
|
downloaded_text = 'downloaded ' + HydrusData.ConvertValueRangeToPrettyString( num_updates_downloaded, num_updates )
|
|
processed_text = 'processed ' + HydrusData.ConvertValueRangeToPrettyString( num_updates_processed, num_updates )
|
|
|
|
if self.IsPaused() or not self._info[ 'account' ].HasPermission( HC.GET_DATA ): status = 'updates on hold'
|
|
else:
|
|
|
|
if self.CanDownloadUpdate(): status = 'downloaded up to ' + HydrusData.ConvertTimestampToPrettySync( self._info[ 'next_download_timestamp' ] )
|
|
elif self.CanProcessUpdate(): status = 'processed up to ' + HydrusData.ConvertTimestampToPrettySync( self._info[ 'next_processing_timestamp' ] )
|
|
elif self.HasRecentError(): status = 'due to a previous error, update is delayed - next check ' + self.GetRecentErrorPending()
|
|
else:
|
|
|
|
if HydrusData.TimeHasPassed( self._info[ 'next_download_timestamp' ] + HC.UPDATE_DURATION ):
|
|
|
|
status = 'next update will be downloaded soon'
|
|
|
|
else:
|
|
|
|
status = 'fully synchronised - next update ' + HydrusData.ConvertTimestampToPrettyPending( self._info[ 'next_download_timestamp' ] + HC.UPDATE_DURATION + 1800 )
|
|
|
|
|
|
|
|
|
|
return downloaded_text + ' - ' + processed_text + ' - ' + status
|
|
|
|
|
|
def IsPaused( self ):
|
|
|
|
return self._info[ 'paused' ]
|
|
|
|
|
|
def IsUpdateDueForDownload( self ):
|
|
|
|
return HydrusData.TimeHasPassed( self._info[ 'next_download_timestamp' ] + HC.UPDATE_DURATION + 1800 )
|
|
|
|
|
|
def IsUpdateDueForProcessing( self ):
|
|
|
|
return self._info[ 'next_download_timestamp' ] > self._info[ 'next_processing_timestamp' ]
|
|
|
|
|
|
def Sync( self, only_when_idle = False, stop_time = None ):
|
|
|
|
if self.IsPaused():
|
|
|
|
return
|
|
|
|
|
|
if not self.CanDownloadUpdate() and not self.CanProcessUpdate():
|
|
|
|
return
|
|
|
|
|
|
job_key = ClientThreading.JobKey( pausable = False, cancellable = True, only_when_idle = only_when_idle, stop_time = stop_time )
|
|
|
|
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
|
|
|
if should_quit:
|
|
|
|
return
|
|
|
|
|
|
num_updates_downloaded = 0
|
|
num_updates_processed = 0
|
|
total_content_weight_processed = 0
|
|
|
|
try:
|
|
|
|
options = HydrusGlobals.client_controller.GetOptions()
|
|
|
|
HydrusGlobals.client_controller.pub( 'splash_set_title_text', self._name )
|
|
job_key.SetVariable( 'popup_title', 'repository synchronisation - ' + self._name )
|
|
|
|
HydrusGlobals.client_controller.pub( 'message', job_key )
|
|
|
|
try:
|
|
|
|
while self.CanDownloadUpdate():
|
|
|
|
if options[ 'pause_repo_sync' ]:
|
|
|
|
break
|
|
|
|
|
|
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
|
|
|
if should_quit:
|
|
|
|
break
|
|
|
|
|
|
if self._info[ 'first_timestamp' ] is None:
|
|
|
|
gauge_range = None
|
|
gauge_value = 0
|
|
|
|
update_index_string = 'initial update: '
|
|
|
|
else:
|
|
|
|
gauge_range = ( HydrusData.GetNow() - self._info[ 'first_timestamp' ] ) / HC.UPDATE_DURATION
|
|
gauge_value = ( ( self._info[ 'next_download_timestamp' ] - self._info[ 'first_timestamp' ] ) / HC.UPDATE_DURATION ) + 1
|
|
|
|
update_index_string = 'update ' + HydrusData.ConvertValueRangeToPrettyString( gauge_value, gauge_range ) + ': '
|
|
|
|
|
|
subupdate_index_string = 'service update: '
|
|
|
|
HydrusGlobals.client_controller.pub( 'splash_set_title_text', self._name + ' - ' + update_index_string + subupdate_index_string )
|
|
HydrusGlobals.client_controller.pub( 'splash_set_status_text', 'downloading' )
|
|
job_key.SetVariable( 'popup_text_1', update_index_string + subupdate_index_string + 'downloading and parsing' )
|
|
job_key.SetVariable( 'popup_gauge_1', ( gauge_value, gauge_range ) )
|
|
|
|
service_update_package = self.Request( HC.GET, 'service_update_package', { 'begin' : self._info[ 'next_download_timestamp' ] } )
|
|
|
|
begin = service_update_package.GetBegin()
|
|
|
|
subindex_count = service_update_package.GetSubindexCount()
|
|
|
|
for subindex in range( subindex_count ):
|
|
|
|
path = ClientFiles.GetExpectedContentUpdatePackagePath( self._service_key, begin, subindex )
|
|
|
|
if os.path.exists( path ):
|
|
|
|
size = os.path.getsize( path )
|
|
|
|
if size == 0:
|
|
|
|
os.remove( path )
|
|
|
|
|
|
|
|
if not os.path.exists( path ):
|
|
|
|
subupdate_index_string = 'content update ' + HydrusData.ConvertValueRangeToPrettyString( subindex + 1, subindex_count ) + ': '
|
|
|
|
HydrusGlobals.client_controller.pub( 'splash_set_title_text', self._name + ' - ' + update_index_string + subupdate_index_string )
|
|
job_key.SetVariable( 'popup_text_1', update_index_string + subupdate_index_string + 'downloading and parsing' )
|
|
|
|
content_update_package = self.Request( HC.GET, 'content_update_package', { 'begin' : begin, 'subindex' : subindex } )
|
|
|
|
obj_string = content_update_package.DumpToNetworkString()
|
|
|
|
job_key.SetVariable( 'popup_text_1', update_index_string + subupdate_index_string + 'saving to disk' )
|
|
|
|
with open( path, 'wb' ) as f: f.write( obj_string )
|
|
|
|
|
|
|
|
job_key.SetVariable( 'popup_text_1', update_index_string + 'committing' )
|
|
|
|
path = ClientFiles.GetExpectedServiceUpdatePackagePath( self._service_key, begin )
|
|
|
|
obj_string = service_update_package.DumpToNetworkString()
|
|
|
|
with open( path, 'wb' ) as f: f.write( obj_string )
|
|
|
|
service_updates = [ HydrusData.ServiceUpdate( HC.SERVICE_UPDATE_NEXT_DOWNLOAD_TIMESTAMP, service_update_package.GetNextBegin() ) ]
|
|
|
|
service_keys_to_service_updates = { self._service_key : service_updates }
|
|
|
|
self.ProcessServiceUpdates( service_keys_to_service_updates )
|
|
|
|
HydrusGlobals.client_controller.Write( 'service_updates', service_keys_to_service_updates )
|
|
|
|
HydrusGlobals.client_controller.WaitUntilPubSubsEmpty()
|
|
|
|
num_updates_downloaded += 1
|
|
|
|
|
|
except HydrusExceptions.ServerBusyException:
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'Server was too busy to respond for now, will continue with processing.' )
|
|
|
|
time.sleep( 3 )
|
|
|
|
except Exception as e:
|
|
|
|
if 'Could not connect' in str( e ):
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'Could not connect to service, will continue with processing.' )
|
|
|
|
time.sleep( 5 )
|
|
|
|
else:
|
|
|
|
raise
|
|
|
|
|
|
|
|
if self._service_type == HC.TAG_REPOSITORY and self.CanProcessUpdate():
|
|
|
|
HydrusGlobals.client_controller.pub( 'splash_set_status_text', 'preparing disk cache' )
|
|
job_key.SetVariable( 'popup_text_1', 'preparing disk cache' )
|
|
|
|
loaded_into_disk_cache = False
|
|
|
|
while not loaded_into_disk_cache:
|
|
|
|
if options[ 'pause_repo_sync' ]:
|
|
|
|
break
|
|
|
|
|
|
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
|
|
|
if should_quit:
|
|
|
|
break
|
|
|
|
|
|
stop_time = HydrusData.GetNow() + 15
|
|
|
|
loaded_into_disk_cache = HydrusGlobals.client_controller.Read( 'load_into_disk_cache', stop_time = stop_time )
|
|
|
|
|
|
|
|
while self.CanProcessUpdate():
|
|
|
|
if options[ 'pause_repo_sync' ]:
|
|
|
|
break
|
|
|
|
|
|
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
|
|
|
if should_quit:
|
|
|
|
break
|
|
|
|
|
|
gauge_range = ( ( HydrusData.GetNow() - self._info[ 'first_timestamp' ] ) / HC.UPDATE_DURATION )
|
|
|
|
gauge_value = ( ( self._info[ 'next_processing_timestamp' ] - self._info[ 'first_timestamp' ] ) / HC.UPDATE_DURATION ) + 1
|
|
|
|
update_index_string = 'update ' + HydrusData.ConvertValueRangeToPrettyString( gauge_value, gauge_range ) + ': '
|
|
|
|
subupdate_index_string = 'service update: '
|
|
|
|
HydrusGlobals.client_controller.pub( 'splash_set_title_text', self._name + ' - ' + update_index_string + subupdate_index_string )
|
|
HydrusGlobals.client_controller.pub( 'splash_set_status_text', 'processing' )
|
|
job_key.SetVariable( 'popup_text_1', update_index_string + subupdate_index_string + 'loading from disk' )
|
|
job_key.SetVariable( 'popup_gauge_1', ( gauge_value, gauge_range ) )
|
|
|
|
path = ClientFiles.GetExpectedServiceUpdatePackagePath( self._service_key, self._info[ 'next_processing_timestamp' ] )
|
|
|
|
if not os.path.exists( path ):
|
|
|
|
self._ReportSyncProcessingError( path, 'was missing' )
|
|
|
|
return
|
|
|
|
|
|
with open( path, 'rb' ) as f: obj_string = f.read()
|
|
|
|
try:
|
|
|
|
service_update_package = HydrusSerialisable.CreateFromNetworkString( obj_string )
|
|
|
|
if not isinstance( service_update_package, HydrusData.ServerToClientServiceUpdatePackage ):
|
|
|
|
raise Exception()
|
|
|
|
|
|
except:
|
|
|
|
self._ReportSyncProcessingError( path, 'did not parse' )
|
|
|
|
return
|
|
|
|
|
|
subindex_count = service_update_package.GetSubindexCount()
|
|
|
|
processing_went_ok = True
|
|
|
|
for subindex in range( subindex_count ):
|
|
|
|
should_break = False
|
|
|
|
if options[ 'pause_repo_sync' ]:
|
|
|
|
should_break = True
|
|
|
|
|
|
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
|
|
|
if should_quit or should_break:
|
|
|
|
processing_went_ok = False
|
|
|
|
break
|
|
|
|
|
|
subupdate_index_string = 'content update ' + HydrusData.ConvertValueRangeToPrettyString( subindex + 1, subindex_count ) + ': '
|
|
|
|
path = ClientFiles.GetExpectedContentUpdatePackagePath( self._service_key, self._info[ 'next_processing_timestamp' ], subindex )
|
|
|
|
if not os.path.exists( path ):
|
|
|
|
self._ReportSyncProcessingError( path, 'was missing' )
|
|
|
|
return
|
|
|
|
|
|
job_key.SetVariable( 'popup_text_1', update_index_string + subupdate_index_string + 'loading from disk' )
|
|
|
|
with open( path, 'rb' ) as f: obj_string = f.read()
|
|
|
|
job_key.SetVariable( 'popup_text_1', update_index_string + subupdate_index_string + 'parsing' )
|
|
|
|
try:
|
|
|
|
content_update_package = HydrusSerialisable.CreateFromNetworkString( obj_string )
|
|
|
|
if not isinstance( content_update_package, HydrusData.ServerToClientContentUpdatePackage ):
|
|
|
|
raise Exception()
|
|
|
|
|
|
except:
|
|
|
|
self._ReportSyncProcessingError( path, 'did not parse' )
|
|
|
|
return
|
|
|
|
|
|
HydrusGlobals.client_controller.pub( 'splash_set_title_text', self._name + ' - ' + update_index_string + subupdate_index_string )
|
|
job_key.SetVariable( 'popup_text_1', update_index_string + subupdate_index_string + 'processing' )
|
|
|
|
( did_it_all, c_u_p_weight_processed ) = HydrusGlobals.client_controller.WriteSynchronous( 'content_update_package', self._service_key, content_update_package, job_key )
|
|
|
|
total_content_weight_processed += c_u_p_weight_processed
|
|
|
|
if not did_it_all:
|
|
|
|
processing_went_ok = False
|
|
|
|
break
|
|
|
|
|
|
HydrusGlobals.client_controller.WaitUntilPubSubsEmpty()
|
|
|
|
time.sleep( 1 )
|
|
|
|
|
|
if options[ 'pause_repo_sync' ]:
|
|
|
|
break
|
|
|
|
|
|
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
|
|
|
if should_quit:
|
|
|
|
break
|
|
|
|
|
|
if processing_went_ok:
|
|
|
|
job_key.SetVariable( 'popup_text_2', 'committing service updates' )
|
|
|
|
service_updates = [ service_update for service_update in service_update_package.IterateServiceUpdates() ]
|
|
|
|
service_updates.append( HydrusData.ServiceUpdate( HC.SERVICE_UPDATE_NEXT_PROCESSING_TIMESTAMP, service_update_package.GetNextBegin() ) )
|
|
|
|
service_keys_to_service_updates = { self._service_key : service_updates }
|
|
|
|
self.ProcessServiceUpdates( service_keys_to_service_updates )
|
|
|
|
HydrusGlobals.client_controller.Write( 'service_updates', service_keys_to_service_updates )
|
|
|
|
HydrusGlobals.client_controller.WaitUntilPubSubsEmpty()
|
|
|
|
|
|
job_key.SetVariable( 'popup_gauge_2', ( 0, 1 ) )
|
|
job_key.SetVariable( 'popup_text_2', '' )
|
|
|
|
num_updates_processed += 1
|
|
|
|
time.sleep( 0.1 )
|
|
|
|
|
|
job_key.DeleteVariable( 'popup_gauge_1' )
|
|
job_key.DeleteVariable( 'popup_text_2' )
|
|
job_key.DeleteVariable( 'popup_gauge_2' )
|
|
|
|
self.SyncThumbnails( job_key )
|
|
|
|
HydrusGlobals.client_controller.pub( 'splash_set_status_text', '' )
|
|
|
|
job_key.SetVariable( 'popup_title', 'repository synchronisation - ' + self._name + ' - finished' )
|
|
|
|
updates_text = HydrusData.ConvertIntToPrettyString( num_updates_downloaded ) + ' updates downloaded, ' + HydrusData.ConvertIntToPrettyString( num_updates_processed ) + ' updates processed'
|
|
|
|
if self._service_type == HC.TAG_REPOSITORY: content_text = HydrusData.ConvertIntToPrettyString( total_content_weight_processed ) + ' mappings added'
|
|
elif self._service_type == HC.FILE_REPOSITORY: content_text = HydrusData.ConvertIntToPrettyString( total_content_weight_processed ) + ' files added'
|
|
|
|
job_key.SetVariable( 'popup_text_1', updates_text + ', and ' + content_text )
|
|
|
|
HydrusData.Print( job_key.ToString() )
|
|
|
|
time.sleep( 3 )
|
|
|
|
job_key.Delete()
|
|
|
|
except Exception as e:
|
|
|
|
job_key.Cancel()
|
|
|
|
HydrusData.Print( traceback.format_exc() )
|
|
|
|
HydrusData.ShowText( 'Failed to update ' + self._name + ':' )
|
|
|
|
HydrusData.ShowException( e )
|
|
|
|
time.sleep( 3 )
|
|
|
|
finally:
|
|
|
|
HydrusGlobals.client_controller.pub( 'notify_new_pending' )
|
|
HydrusGlobals.client_controller.pub( 'notify_new_siblings_data' )
|
|
HydrusGlobals.client_controller.pub( 'notify_new_siblings_gui' )
|
|
HydrusGlobals.client_controller.pub( 'notify_new_parents' )
|
|
|
|
|
|
|
|
def SyncThumbnails( self, job_key ):
|
|
|
|
synced = not ( self.IsUpdateDueForDownload() or self.IsUpdateDueForProcessing() )
|
|
|
|
if self._service_type == HC.FILE_REPOSITORY and synced and self.CanDownload():
|
|
|
|
options = HydrusGlobals.client_controller.GetOptions()
|
|
|
|
HydrusGlobals.client_controller.pub( 'splash_set_status_text', 'reviewing service thumbnails' )
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'reviewing service thumbnails' )
|
|
|
|
remote_thumbnail_hashes_i_should_have = HydrusGlobals.client_controller.Read( 'remote_thumbnail_hashes_i_should_have', self._service_key )
|
|
|
|
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
|
|
|
|
thumbnail_hashes_i_need = set()
|
|
|
|
for hash in remote_thumbnail_hashes_i_should_have:
|
|
|
|
if not client_files_manager.HaveFullSizeThumbnail( hash ):
|
|
|
|
thumbnail_hashes_i_need.add( hash )
|
|
|
|
|
|
|
|
if len( thumbnail_hashes_i_need ) > 0:
|
|
|
|
thumbnails = []
|
|
|
|
for ( i, hash ) in enumerate( thumbnail_hashes_i_need ):
|
|
|
|
if options[ 'pause_repo_sync' ]:
|
|
|
|
break
|
|
|
|
|
|
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
|
|
|
if should_quit:
|
|
|
|
break
|
|
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'downloading thumbnail ' + HydrusData.ConvertValueRangeToPrettyString( i, len( thumbnail_hashes_i_need ) ) )
|
|
job_key.SetVariable( 'popup_gauge_1', ( i, len( thumbnail_hashes_i_need ) ) )
|
|
|
|
request_args = { 'hash' : hash.encode( 'hex' ) }
|
|
|
|
thumbnail = self.Request( HC.GET, 'thumbnail', request_args = request_args )
|
|
|
|
client_files_manager.AddFullSizeThumbnail( hash, thumbnail )
|
|
|
|
|
|
job_key.DeleteVariable( 'popup_gauge_1' )
|
|
|
|
|
|
|
|
|
|
class ServiceIPFS( ServiceRemote ):
|
|
|
|
def _ConvertMultihashToURLTree( self, name, size, multihash ):
|
|
|
|
api_base_url = self._GetAPIBaseURL()
|
|
|
|
links_url = api_base_url + 'object/links/' + multihash
|
|
|
|
response = ClientNetworking.RequestsGet( links_url )
|
|
|
|
links_json = response.json()
|
|
|
|
is_directory = False
|
|
|
|
if 'Links' in links_json:
|
|
|
|
for link in links_json[ 'Links' ]:
|
|
|
|
if link[ 'Name' ] != '':
|
|
|
|
is_directory = True
|
|
|
|
|
|
|
|
|
|
if is_directory:
|
|
|
|
children = []
|
|
|
|
for link in links_json[ 'Links' ]:
|
|
|
|
subname = link[ 'Name' ]
|
|
subsize = link[ 'Size' ]
|
|
submultihash = link[ 'Hash' ]
|
|
|
|
children.append( self._ConvertMultihashToURLTree( subname, subsize, submultihash ) )
|
|
|
|
|
|
if size is None:
|
|
|
|
size = sum( ( subsize for ( type_gumpf, subname, subsize, submultihash ) in children ) )
|
|
|
|
|
|
return ( 'directory', name, size, children )
|
|
|
|
else:
|
|
|
|
url = api_base_url + 'cat/' + multihash
|
|
|
|
return ( 'file', name, size, url )
|
|
|
|
|
|
|
|
def _GetAPIBaseURL( self ):
|
|
|
|
credentials = self.GetCredentials()
|
|
|
|
( host, port ) = credentials.GetAddress()
|
|
|
|
api_base_url = 'http://' + host + ':' + str( port ) + '/api/v0/'
|
|
|
|
return api_base_url
|
|
|
|
|
|
def GetDaemonVersion( self ):
|
|
|
|
api_base_url = self._GetAPIBaseURL()
|
|
|
|
url = api_base_url + 'version'
|
|
|
|
response = ClientNetworking.RequestsGet( url )
|
|
|
|
j = response.json()
|
|
|
|
return j[ 'Version' ]
|
|
|
|
|
|
def ImportFile( self, multihash ):
|
|
|
|
def on_wx_select_tree( job_key, url_tree ):
|
|
|
|
import ClientGUIDialogs
|
|
|
|
with ClientGUIDialogs.DialogSelectFromURLTree( None, url_tree ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
urls = dlg.GetURLs()
|
|
|
|
if len( urls ) > 0:
|
|
|
|
HydrusGlobals.client_controller.CallToThread( ClientDownloading.THREADDownloadURLs, job_key, urls, multihash )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def off_wx():
|
|
|
|
job_key = ClientThreading.JobKey( pausable = True, cancellable = True )
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'Looking up multihash information' )
|
|
|
|
HydrusGlobals.client_controller.pub( 'message', job_key )
|
|
|
|
url_tree = self._ConvertMultihashToURLTree( multihash, None, multihash )
|
|
|
|
if url_tree[0] == 'file':
|
|
|
|
url = url_tree[3]
|
|
|
|
HydrusGlobals.client_controller.CallToThread( ClientDownloading.THREADDownloadURL, job_key, url, multihash )
|
|
|
|
else:
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'Waiting for user selection' )
|
|
|
|
wx.CallAfter( on_wx_select_tree, job_key, url_tree )
|
|
|
|
|
|
|
|
HydrusGlobals.client_controller.CallToThread( off_wx )
|
|
|
|
|
|
def PinDirectory( self, hashes, note ):
|
|
|
|
job_key = ClientThreading.JobKey( pausable = True, cancellable = True )
|
|
|
|
job_key.SetVariable( 'popup_title', 'creating ipfs directory on ' + self._name )
|
|
|
|
HydrusGlobals.client_controller.pub( 'message', job_key )
|
|
|
|
try:
|
|
|
|
file_info = []
|
|
|
|
hashes = list( hashes )
|
|
|
|
hashes.sort()
|
|
|
|
for ( i, hash ) in enumerate( hashes ):
|
|
|
|
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
|
|
|
if should_quit:
|
|
|
|
return
|
|
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'pinning files: ' + HydrusData.ConvertValueRangeToPrettyString( i + 1, len( hashes ) ) )
|
|
job_key.SetVariable( 'popup_gauge_1', ( i + 1, len( hashes ) ) )
|
|
|
|
( media_result, ) = HydrusGlobals.client_controller.Read( 'media_results', ( hash, ) )
|
|
|
|
mime = media_result.GetMime()
|
|
|
|
result = HydrusGlobals.client_controller.Read( 'service_filenames', self._service_key, { hash } )
|
|
|
|
if len( result ) == 0:
|
|
|
|
multihash = self.PinFile( hash, mime )
|
|
|
|
else:
|
|
|
|
( multihash, ) = result
|
|
|
|
|
|
file_info.append( ( hash, mime, multihash ) )
|
|
|
|
|
|
api_base_url = self._GetAPIBaseURL()
|
|
|
|
url = api_base_url + 'object/new?arg=unixfs-dir'
|
|
|
|
response = ClientNetworking.RequestsGet( url )
|
|
|
|
for ( i, ( hash, mime, multihash ) ) in enumerate( file_info ):
|
|
|
|
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
|
|
|
if should_quit:
|
|
|
|
return
|
|
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'creating directory: ' + HydrusData.ConvertValueRangeToPrettyString( i + 1, len( file_info ) ) )
|
|
job_key.SetVariable( 'popup_gauge_1', ( i + 1, len( file_info ) ) )
|
|
|
|
object_multihash = response.json()[ 'Hash' ]
|
|
|
|
filename = hash.encode( 'hex' ) + HC.mime_ext_lookup[ mime ]
|
|
|
|
url = api_base_url + 'object/patch/add-link?arg=' + object_multihash + '&arg=' + filename + '&arg=' + multihash
|
|
|
|
response = ClientNetworking.RequestsGet( url )
|
|
|
|
|
|
directory_multihash = response.json()[ 'Hash' ]
|
|
|
|
url = api_base_url + 'pin/add?arg=' + directory_multihash
|
|
|
|
response = ClientNetworking.RequestsGet( url )
|
|
|
|
content_update_row = ( hashes, directory_multihash, note )
|
|
|
|
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_DIRECTORIES, HC.CONTENT_UPDATE_ADD, content_update_row ) ]
|
|
|
|
HydrusGlobals.client_controller.WriteSynchronous( 'content_updates', { self._service_key : content_updates } )
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'done!' )
|
|
job_key.DeleteVariable( 'popup_gauge_1' )
|
|
|
|
multihash_prefix = self._info[ 'multihash_prefix' ]
|
|
|
|
text = multihash_prefix + directory_multihash
|
|
|
|
job_key.SetVariable( 'popup_clipboard', ( 'copy multihash to clipboard', text ) )
|
|
|
|
job_key.Finish()
|
|
|
|
return directory_multihash
|
|
|
|
except Exception as e:
|
|
|
|
HydrusData.ShowException( e )
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'error' )
|
|
job_key.DeleteVariable( 'popup_gauge_1' )
|
|
|
|
job_key.Cancel()
|
|
|
|
|
|
|
|
def PinFile( self, hash, mime ):
|
|
|
|
mime_string = HC.mime_string_lookup[ mime ]
|
|
|
|
api_base_url = self._GetAPIBaseURL()
|
|
|
|
url = api_base_url + 'add'
|
|
|
|
client_files_manager = HydrusGlobals.client_controller.GetClientFilesManager()
|
|
|
|
path = client_files_manager.GetFilePath( hash, mime )
|
|
|
|
files = { 'path' : ( hash.encode( 'hex' ), open( path, 'rb' ), mime_string ) }
|
|
|
|
response = ClientNetworking.RequestsPost( url, files = files )
|
|
|
|
j = response.json()
|
|
|
|
multihash = j[ 'Hash' ]
|
|
|
|
content_update_row = ( hash, multihash )
|
|
|
|
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_ADD, content_update_row ) ]
|
|
|
|
HydrusGlobals.client_controller.WriteSynchronous( 'content_updates', { self._service_key : content_updates } )
|
|
|
|
return multihash
|
|
|
|
|
|
def UnpinDirectory( self, multihash ):
|
|
|
|
api_base_url = self._GetAPIBaseURL()
|
|
|
|
url = api_base_url + 'pin/rm/' + multihash
|
|
|
|
ClientNetworking.RequestsGet( url )
|
|
|
|
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_DIRECTORIES, HC.CONTENT_UPDATE_DELETE, multihash ) ]
|
|
|
|
HydrusGlobals.client_controller.WriteSynchronous( 'content_updates', { self._service_key : content_updates } )
|
|
|
|
|
|
def UnpinFile( self, hash, multihash ):
|
|
|
|
api_base_url = self._GetAPIBaseURL()
|
|
|
|
url = api_base_url + 'pin/rm/' + multihash
|
|
|
|
try:
|
|
|
|
ClientNetworking.RequestsGet( url )
|
|
|
|
except HydrusExceptions.NetworkException as e:
|
|
|
|
if 'not pinned' not in str( e ):
|
|
|
|
raise
|
|
|
|
|
|
|
|
content_updates = [ HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, { hash } ) ]
|
|
|
|
HydrusGlobals.client_controller.WriteSynchronous( 'content_updates', { self._service_key : content_updates } )
|
|
|
|
|
|
class Shortcuts( HydrusSerialisable.SerialisableBaseNamed ):
|
|
|
|
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS
|
|
SERIALISABLE_VERSION = 1
|
|
|
|
def __init__( self, name ):
|
|
|
|
HydrusSerialisable.SerialisableBaseNamed.__init__( self, name )
|
|
|
|
self._mouse_actions = {}
|
|
self._keyboard_actions = {}
|
|
|
|
|
|
def _ConvertActionToSerialisableAction( self, action ):
|
|
|
|
( service_key, data ) = action
|
|
|
|
if service_key is None:
|
|
|
|
return [ service_key, data ]
|
|
|
|
else:
|
|
|
|
serialisable_service_key = service_key.encode( 'hex' )
|
|
|
|
return [ serialisable_service_key, data ]
|
|
|
|
|
|
|
|
def _ConvertSerialisableActionToAction( self, serialisable_action ):
|
|
|
|
( serialisable_service_key, data ) = serialisable_action
|
|
|
|
if serialisable_service_key is None:
|
|
|
|
return ( serialisable_service_key, data ) # important to return tuple, as serialisable_action is likely a list
|
|
|
|
else:
|
|
|
|
service_key = serialisable_service_key.decode( 'hex' )
|
|
|
|
return ( service_key, data )
|
|
|
|
|
|
|
|
def _GetSerialisableInfo( self ):
|
|
|
|
serialisable_mouse_actions = []
|
|
|
|
for ( ( modifier, mouse_button ), action ) in self._mouse_actions.items():
|
|
|
|
serialisable_action = self._ConvertActionToSerialisableAction( action )
|
|
|
|
serialisable_mouse_actions.append( ( modifier, mouse_button, serialisable_action ) )
|
|
|
|
|
|
serialisable_keyboard_actions = []
|
|
|
|
for ( ( modifier, key ), action ) in self._keyboard_actions.items():
|
|
|
|
serialisable_action = self._ConvertActionToSerialisableAction( action )
|
|
|
|
serialisable_keyboard_actions.append( ( modifier, key, serialisable_action ) )
|
|
|
|
|
|
return ( serialisable_mouse_actions, serialisable_keyboard_actions )
|
|
|
|
|
|
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
|
|
|
( serialisable_mouse_actions, serialisable_keyboard_actions ) = serialisable_info
|
|
|
|
self._mouse_actions = {}
|
|
|
|
for ( modifier, mouse_button, serialisable_action ) in serialisable_mouse_actions:
|
|
|
|
action = self._ConvertSerialisableActionToAction( serialisable_action )
|
|
|
|
self._mouse_actions[ ( modifier, mouse_button ) ] = action
|
|
|
|
|
|
self._keyboard_actions = {}
|
|
|
|
for ( modifier, key, serialisable_action ) in serialisable_keyboard_actions:
|
|
|
|
action = self._ConvertSerialisableActionToAction( serialisable_action )
|
|
|
|
self._keyboard_actions[ ( modifier, key ) ] = action
|
|
|
|
|
|
|
|
def ClearActions( self ):
|
|
|
|
self._mouse_actions = {}
|
|
self._keyboard_actions = {}
|
|
|
|
|
|
def DeleteKeyboardAction( self, modifier, key ):
|
|
|
|
if ( modifier, key ) in self._keyboard_actions:
|
|
|
|
del self._keyboard_actions[ ( modifier, key ) ]
|
|
|
|
|
|
|
|
def DeleteMouseAction( self, modifier, mouse_button ):
|
|
|
|
if ( modifier, mouse_button ) in self._mouse_actions:
|
|
|
|
del self._mouse_actions[ ( modifier, mouse_button ) ]
|
|
|
|
|
|
|
|
def GetKeyboardAction( self, modifier, key ):
|
|
|
|
if ( modifier, key ) in self._keyboard_actions:
|
|
|
|
return self._keyboard_actions[ ( modifier, key ) ]
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def GetMouseAction( self, modifier, mouse_button ):
|
|
|
|
if ( modifier, mouse_button ) in self._mouse_actions:
|
|
|
|
return self._mouse_actions[ ( modifier, mouse_button ) ]
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def IterateKeyboardShortcuts( self ):
|
|
|
|
for ( ( modifier, key ), action ) in self._keyboard_actions.items(): yield ( ( modifier, key ), action )
|
|
|
|
|
|
def IterateMouseShortcuts( self ):
|
|
|
|
for ( ( modifier, mouse_button ), action ) in self._mouse_actions.items(): yield ( ( modifier, mouse_button ), action )
|
|
|
|
|
|
def SetKeyboardAction( self, modifier, key, action ):
|
|
|
|
self._keyboard_actions[ ( modifier, key ) ] = action
|
|
|
|
|
|
def SetMouseAction( self, modifier, mouse_button, action ):
|
|
|
|
self._mouse_actions[ ( modifier, mouse_button ) ] = action
|
|
|
|
|
|
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS ] = Shortcuts
|
|
|
|
def GetShortcutFromEvent( 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 )
|
|
|