Version 92
This commit is contained in:
parent
6f980b67ae
commit
29e67fdf19
|
@ -49,4 +49,4 @@ HC.shutdown = True
|
|||
|
||||
reactor.callFromThread( reactor.stop )
|
||||
|
||||
HC.pubsub.pubimmediate( 'shutdown' )
|
||||
HC.pubsub.WXpubimmediate( 'shutdown' )
|
||||
|
|
|
@ -8,6 +8,22 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 91</h3></li>
|
||||
<ul>
|
||||
<li>encrypted instant messaging framework started</li>
|
||||
<li>encrypted instant messaging test added</li>
|
||||
<li>encrypted instant messaging trust framework started</li>
|
||||
<li>started an AMP framework for encrypted instant messaging</li>
|
||||
<li>tag A/C exact match parameter added</li>
|
||||
<li>tag A/C exact match parameter test added</li>
|
||||
<li>tag A/C logic reorganised and improved</li>
|
||||
<li>tag A/C now gives exact matches for queries shorter than the A/C threshold</li>
|
||||
<li>tag A/C threshold default set to 2</li>
|
||||
<li>fixed an important architectural bug in pubsub</li>
|
||||
<li>generally cleaned up pubsub code</li>
|
||||
<li>'popup messages sometimes not updating' problem is fixed</li>
|
||||
<li>a bit of initialisation cleanup to make startup behaviour more reliable</li>
|
||||
</ul>
|
||||
<li><h3>version 91</h3></li>
|
||||
<ul>
|
||||
<li>improved how accounts are identified in the server</li>
|
||||
|
|
|
@ -190,7 +190,7 @@ CLIENT_DEFAULT_OPTIONS[ 'preview_cache_size' ] = 25 * 1048576
|
|||
CLIENT_DEFAULT_OPTIONS[ 'fullscreen_cache_size' ] = 200 * 1048576
|
||||
CLIENT_DEFAULT_OPTIONS[ 'thumbnail_dimensions' ] = [ 150, 125 ]
|
||||
CLIENT_DEFAULT_OPTIONS[ 'password' ] = None
|
||||
CLIENT_DEFAULT_OPTIONS[ 'num_autocomplete_chars' ] = 1
|
||||
CLIENT_DEFAULT_OPTIONS[ 'num_autocomplete_chars' ] = 2
|
||||
CLIENT_DEFAULT_OPTIONS[ 'gui_capitalisation' ] = False
|
||||
|
||||
system_predicates = {}
|
||||
|
|
|
@ -109,24 +109,10 @@ class Controller( wx.App ):
|
|||
|
||||
def EventPubSub( self, event ):
|
||||
|
||||
pubsubs_queue = HC.pubsub.GetQueue()
|
||||
|
||||
( callable, args, kwargs ) = pubsubs_queue.get()
|
||||
|
||||
HC.busy_doing_pubsub = True
|
||||
|
||||
try: callable( *args, **kwargs )
|
||||
except wx._core.PyDeadObjectError: pass
|
||||
except TypeError as e:
|
||||
|
||||
if '_wxPyDeadObject' not in str( e ): raise
|
||||
|
||||
finally:
|
||||
|
||||
pubsubs_queue.task_done()
|
||||
|
||||
HC.busy_doing_pubsub = False
|
||||
|
||||
try: HC.pubsub.WXProcessQueueItem()
|
||||
finally: HC.busy_doing_pubsub = False
|
||||
|
||||
|
||||
def GetFullscreenImageCache( self ): return self._fullscreen_image_cache
|
||||
|
@ -169,7 +155,7 @@ class Controller( wx.App ):
|
|||
|
||||
|
||||
def OnInit( self ):
|
||||
|
||||
|
||||
HC.app = self
|
||||
|
||||
self._local_service = None
|
||||
|
@ -289,7 +275,7 @@ class Controller( wx.App ):
|
|||
self.SetSplashText( 'starting daemons' )
|
||||
|
||||
if HC.is_first_start: self._gui.DoFirstStart()
|
||||
if HC.is_db_updated: wx.CallAfter( HC.pubsub.pub, 'message', HC.Message( HC.MESSAGE_TYPE_TEXT, 'The client has updated to version ' + HC.u( HC.SOFTWARE_VERSION ) + '!' ) )
|
||||
if HC.is_db_updated: wx.CallLater( 0, HC.pubsub.pub, 'message', HC.Message( HC.MESSAGE_TYPE_TEXT, 'The client has updated to version ' + HC.u( HC.SOFTWARE_VERSION ) + '!' ) )
|
||||
|
||||
self.RestartServer()
|
||||
self._db.StartDaemons()
|
||||
|
@ -471,12 +457,10 @@ class Controller( wx.App ):
|
|||
|
||||
def WaitUntilGoodTimeToUseGUIThread( self ):
|
||||
|
||||
pubsubs_queue = HC.pubsub.GetQueue()
|
||||
|
||||
while True:
|
||||
|
||||
if HC.shutdown: raise Exception( 'Client shutting down!' )
|
||||
elif pubsubs_queue.qsize() == 0 and not HC.busy_doing_pubsub: return
|
||||
elif HC.pubsub.NotBusy() and not HC.busy_doing_pubsub: return
|
||||
else: time.sleep( 0.0001 )
|
||||
|
||||
|
||||
|
|
|
@ -1601,7 +1601,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
else: return result
|
||||
|
||||
|
||||
def _GetAutocompleteTags( self, c, tag_service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER, file_service_identifier = HC.COMBINED_FILE_SERVICE_IDENTIFIER, half_complete_tag = '', include_current = True, include_pending = True, collapse = True ):
|
||||
def _GetAutocompleteTags( self, c, tag_service_identifier = HC.COMBINED_TAG_SERVICE_IDENTIFIER, file_service_identifier = HC.COMBINED_FILE_SERVICE_IDENTIFIER, tag = '', half_complete_tag = '', include_current = True, include_pending = True, collapse = True ):
|
||||
|
||||
tag_service_id = self._GetServiceId( c, tag_service_identifier )
|
||||
file_service_id = self._GetServiceId( c, file_service_identifier )
|
||||
|
@ -1676,6 +1676,12 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
predicates_phrase = 'tag_id IN ' + HC.SplayListForDB( possible_tag_ids )
|
||||
|
||||
|
||||
elif len( tag ) > 0:
|
||||
|
||||
( namespace_id, tag_id ) = self._GetNamespaceIdTagId( c, tag )
|
||||
|
||||
predicates_phrase = 'namespace_id = ' + HC.u( namespace_id ) + ' AND tag_id = ' + HC.u( tag_id )
|
||||
|
||||
else:
|
||||
|
||||
predicates_phrase = '1 = 1'
|
||||
|
@ -1689,7 +1695,9 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
siblings_manager = HC.app.GetTagSiblingsManager()
|
||||
|
||||
all_associated_sibling_tags = siblings_manager.GetAutocompleteSiblings( half_complete_tag )
|
||||
if len( half_complete_tag ) > 0: all_associated_sibling_tags = siblings_manager.GetAutocompleteSiblings( half_complete_tag )
|
||||
elif len( tag ) > 0: all_associated_sibling_tags = siblings_manager.GetAllSiblings( tag )
|
||||
else: all_associated_sibling_tags = siblings_manager.GetAutocompleteSiblings( '' )
|
||||
|
||||
sibling_results = [ self._GetNamespaceIdTagId( c, sibling_tag ) for sibling_tag in all_associated_sibling_tags ]
|
||||
|
||||
|
@ -1700,7 +1708,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
cache_results = []
|
||||
|
||||
if len( half_complete_tag ) > 0:
|
||||
if len( half_complete_tag ) > 0 or len( tag ) > 0:
|
||||
|
||||
for ( namespace_id, tag_ids ) in HC.BuildKeyToListDict( results ).items(): cache_results.extend( c.execute( 'SELECT namespace_id, tag_id, current_count, pending_count FROM autocomplete_tags_cache WHERE tag_service_id = ? AND file_service_id = ? AND namespace_id = ? AND tag_id IN ' + HC.SplayListForDB( tag_ids ) + ';', ( tag_service_id, file_service_id, namespace_id ) ).fetchall() )
|
||||
|
||||
|
@ -4927,6 +4935,15 @@ class DB( ServiceDB ):
|
|||
c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
|
||||
|
||||
|
||||
if version < 92:
|
||||
|
||||
( HC.options, ) = c.execute( 'SELECT options FROM options;' ).fetchone()
|
||||
|
||||
HC.options[ 'num_autocomplete_chars' ] = 2
|
||||
|
||||
c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
|
||||
|
||||
|
||||
unknown_account = HC.GetUnknownAccount()
|
||||
|
||||
unknown_account.MakeStale()
|
||||
|
|
|
@ -113,7 +113,10 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
self.Show( True )
|
||||
|
||||
wx.CallAfter( self._NewPageQuery, HC.LOCAL_FILE_SERVICE_IDENTIFIER )
|
||||
# as we are in oninit, callafter and calllater( 0 ) are different
|
||||
# later waits until the mainloop is running, I think.
|
||||
# after seems to execute synchronously
|
||||
wx.CallLater( 0, self._NewPageQuery, HC.LOCAL_FILE_SERVICE_IDENTIFIER )
|
||||
|
||||
|
||||
def _THREADUploadPending( self, service_identifier ):
|
||||
|
|
|
@ -609,46 +609,63 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
|
|||
half_complete_tag = search_text
|
||||
|
||||
|
||||
if len( half_complete_tag ) >= num_first_letters:
|
||||
if half_complete_tag == '': matches = [] # a query like 'namespace:'
|
||||
else:
|
||||
|
||||
if must_do_a_search or self._first_letters == '' or not half_complete_tag.startswith( self._first_letters ):
|
||||
media = self._media_callable()
|
||||
|
||||
# if synchro not on, then can't rely on current media as being accurate for current preds, so search db normally
|
||||
if media is None or not self._synchronised.IsOn():
|
||||
|
||||
self._first_letters = half_complete_tag
|
||||
|
||||
media = self._media_callable()
|
||||
|
||||
# if synchro not on, then can't rely on current media as being accurate for current preds, so search db normally
|
||||
if media is None or not self._synchronised.IsOn(): self._cached_results = HC.app.Read( 'autocomplete_tags', file_service_identifier = self._file_service_identifier, tag_service_identifier = self._tag_service_identifier, half_complete_tag = search_text, include_current = self._include_current, include_pending = self._include_pending )
|
||||
if len( search_text ) < num_first_letters:
|
||||
|
||||
results = HC.app.Read( 'autocomplete_tags', file_service_identifier = self._file_service_identifier, tag_service_identifier = self._tag_service_identifier, tag = search_text, include_current = self._include_current, include_pending = self._include_pending )
|
||||
|
||||
matches = results.GetMatches( half_complete_tag )
|
||||
|
||||
else:
|
||||
|
||||
tags_managers = []
|
||||
|
||||
for m in media:
|
||||
if must_do_a_search or self._first_letters == '' or not half_complete_tag.startswith( self._first_letters ):
|
||||
|
||||
if m.IsCollection(): tags_managers.extend( m.GetSingletonsTagsManagers() )
|
||||
else: tags_managers.append( m.GetTagsManager() )
|
||||
self._first_letters = half_complete_tag
|
||||
|
||||
self._cached_results = HC.app.Read( 'autocomplete_tags', file_service_identifier = self._file_service_identifier, tag_service_identifier = self._tag_service_identifier, half_complete_tag = search_text, include_current = self._include_current, include_pending = self._include_pending )
|
||||
|
||||
|
||||
lists_of_tags = []
|
||||
|
||||
if self._include_current: lists_of_tags += [ list( tags_manager.GetCurrent( self._tag_service_identifier ) ) for tags_manager in tags_managers ]
|
||||
if self._include_pending: lists_of_tags += [ list( tags_manager.GetPending( self._tag_service_identifier ) ) for tags_manager in tags_managers ]
|
||||
|
||||
all_tags_flat_iterable = itertools.chain.from_iterable( lists_of_tags )
|
||||
|
||||
all_tags_flat = [ tag for tag in all_tags_flat_iterable if HC.SearchEntryMatchesTag( half_complete_tag, tag ) ]
|
||||
|
||||
if self._current_namespace != '': all_tags_flat = [ tag for tag in all_tags_flat if tag.startswith( self._current_namespace + ':' ) ]
|
||||
|
||||
tags_to_count = collections.Counter( all_tags_flat )
|
||||
|
||||
self._cached_results = CC.AutocompleteMatchesPredicates( self._tag_service_identifier, [ HC.Predicate( HC.PREDICATE_TYPE_TAG, ( operator, tag ), count ) for ( tag, count ) in tags_to_count.items() ] )
|
||||
matches = self._cached_results.GetMatches( half_complete_tag )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
# it is possible that media will change between calls to this, so don't cache it
|
||||
# it's also quick as hell, so who cares
|
||||
|
||||
tags_managers = []
|
||||
|
||||
for m in media:
|
||||
|
||||
if m.IsCollection(): tags_managers.extend( m.GetSingletonsTagsManagers() )
|
||||
else: tags_managers.append( m.GetTagsManager() )
|
||||
|
||||
|
||||
lists_of_tags = []
|
||||
|
||||
if self._include_current: lists_of_tags += [ list( tags_manager.GetCurrent( self._tag_service_identifier ) ) for tags_manager in tags_managers ]
|
||||
if self._include_pending: lists_of_tags += [ list( tags_manager.GetPending( self._tag_service_identifier ) ) for tags_manager in tags_managers ]
|
||||
|
||||
all_tags_flat_iterable = itertools.chain.from_iterable( lists_of_tags )
|
||||
|
||||
all_tags_flat = [ tag for tag in all_tags_flat_iterable if HC.SearchEntryMatchesTag( half_complete_tag, tag ) ]
|
||||
|
||||
if self._current_namespace != '': all_tags_flat = [ tag for tag in all_tags_flat if tag.startswith( self._current_namespace + ':' ) ]
|
||||
|
||||
tags_to_count = collections.Counter( all_tags_flat )
|
||||
|
||||
results = CC.AutocompleteMatchesPredicates( self._tag_service_identifier, [ HC.Predicate( HC.PREDICATE_TYPE_TAG, ( operator, tag ), count ) for ( tag, count ) in tags_to_count.items() ] )
|
||||
|
||||
matches = results.GetMatches( half_complete_tag )
|
||||
|
||||
|
||||
matches = self._cached_results.GetMatches( half_complete_tag )
|
||||
|
||||
else: matches = []
|
||||
|
||||
if self._current_namespace != '': matches.insert( 0, HC.Predicate( HC.PREDICATE_TYPE_NAMESPACE, ( operator, namespace ), None ) )
|
||||
|
||||
|
@ -782,7 +799,13 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
|
|||
half_complete_tag = search_text
|
||||
|
||||
|
||||
if len( half_complete_tag ) >= num_first_letters:
|
||||
if len( search_text ) < num_first_letters:
|
||||
|
||||
results = HC.app.Read( 'autocomplete_tags', file_service_identifier = self._file_service_identifier, tag_service_identifier = self._tag_service_identifier, tag = search_text, collapse = False )
|
||||
|
||||
matches = results.GetMatches( half_complete_tag )
|
||||
|
||||
else:
|
||||
|
||||
if must_do_a_search or self._first_letters == '' or not half_complete_tag.startswith( self._first_letters ):
|
||||
|
||||
|
@ -793,7 +816,6 @@ class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
|
|||
|
||||
matches = self._cached_results.GetMatches( half_complete_tag )
|
||||
|
||||
else: matches = []
|
||||
|
||||
# do the 'put whatever they typed in at the top, whether it has count or not'
|
||||
# now with sibling support!
|
||||
|
@ -2356,7 +2378,7 @@ class PopupMessageGauge( PopupMessage ):
|
|||
|
||||
|
||||
def SetInfo( self, job_key, range, value, message ):
|
||||
|
||||
print( value )
|
||||
if job_key == self._job_key:
|
||||
|
||||
if value is None:
|
||||
|
|
|
@ -39,7 +39,7 @@ TEMP_DIR = BASE_DIR + os.path.sep + 'temp'
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 11
|
||||
SOFTWARE_VERSION = 91
|
||||
SOFTWARE_VERSION = 92
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import Crypto.PublicKey.RSA
|
|||
import hashlib
|
||||
import HydrusConstants as HC
|
||||
import os
|
||||
import potr
|
||||
import time
|
||||
import traceback
|
||||
import wx
|
||||
|
@ -207,4 +208,49 @@ def UnpadAES( message ):
|
|||
index_of_correct_end = len( message ) - i
|
||||
|
||||
return message[:index_of_correct_end + 1]
|
||||
|
||||
|
||||
# I based this on the excellent article by Darrik L Mazey, here:
|
||||
# https://blog.darmasoft.net/2013/06/30/using-pure-python-otr.html
|
||||
|
||||
DEFAULT_POLICY_FLAGS = {}
|
||||
|
||||
DEFAULT_POLICY_FLAGS[ 'ALLOW_V1' ] = False
|
||||
DEFAULT_POLICY_FLAGS[ 'ALLOW_V2' ] = True
|
||||
DEFAULT_POLICY_FLAGS[ 'REQUIRE_ENCRYPTION' ] = True
|
||||
|
||||
GenerateOTRKey = potr.compatcrypto.generateDefaultKey
|
||||
def LoadOTRKey( stream ): return potr.crypt.PK.parsePrivateKey( stream )[0]
|
||||
def DumpOTRKey( key ): return key.serializePrivateKey()
|
||||
|
||||
class HydrusOTRContext( potr.context.Context ):
|
||||
|
||||
def getPolicy( self, key ):
|
||||
|
||||
if key in DEFAULT_POLICY_FLAGS: return DEFAULT_POLICY_FLAGS[ key ]
|
||||
else: return False
|
||||
|
||||
|
||||
def inject( self, msg, appdata = None ):
|
||||
|
||||
inject_catcher = appdata
|
||||
|
||||
inject_catcher.write( msg )
|
||||
|
||||
|
||||
class HydrusOTRAccount( potr.context.Account ):
|
||||
|
||||
def __init__( self, name, privkey, trusts ):
|
||||
|
||||
potr.context.Account.__init__( self, name, 'hydrus network otr', 1024, privkey )
|
||||
|
||||
self.trusts = trusts
|
||||
|
||||
|
||||
def saveTrusts( self ):
|
||||
|
||||
HC.app.Write( 'otr_trusts', self.name, self.trusts )
|
||||
|
||||
|
||||
# I need an accounts manager so there is only ever one copy of an account
|
||||
# it should fetch name, privkey and trusts from db on bootup
|
||||
# savettrusts should just spam to the db because it ain't needed that much.
|
|
@ -12,7 +12,8 @@ class HydrusPubSub():
|
|||
|
||||
def __init__( self ):
|
||||
|
||||
self._pubsubs = Queue.Queue()
|
||||
self._pubsubs = []
|
||||
self._callables = []
|
||||
|
||||
self._lock = threading.Lock()
|
||||
|
||||
|
@ -20,72 +21,97 @@ class HydrusPubSub():
|
|||
self._topics_to_method_names = {}
|
||||
|
||||
|
||||
def GetQueue( self ): return self._pubsubs
|
||||
def _GetCallables( self, topic ):
|
||||
|
||||
callables = []
|
||||
|
||||
if topic in self._topics_to_objects:
|
||||
|
||||
try:
|
||||
|
||||
objects = self._topics_to_objects[ topic ]
|
||||
|
||||
for object in objects:
|
||||
|
||||
method_names = self._topics_to_method_names[ topic ]
|
||||
|
||||
for method_name in method_names:
|
||||
|
||||
if hasattr( object, method_name ):
|
||||
|
||||
try:
|
||||
|
||||
callable = getattr( object, method_name )
|
||||
|
||||
callables.append( callable )
|
||||
|
||||
except wx.PyDeadObjectError: pass
|
||||
except TypeError as e:
|
||||
|
||||
if '_wxPyDeadObject' not in str( e ): raise
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
except: pass
|
||||
|
||||
|
||||
return callables
|
||||
|
||||
|
||||
def NotBusy( self ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
return len( self._pubsubs ) == 0
|
||||
|
||||
|
||||
|
||||
def WXProcessQueueItem( self ):
|
||||
|
||||
# we don't want to map a topic to its callables until the previous topic's callables have been fully executed
|
||||
# e.g. when we start a message with a pubsub, it'll take a while (in indepedant thread-time) for wx to create
|
||||
# the dialog and hence map the new callable to the topic. this was leading to messages not being updated
|
||||
# because the (short) processing thread finished and entirely pubsubbed before wx had a chance to boot the
|
||||
# message.
|
||||
|
||||
do_callable = False
|
||||
|
||||
with self._lock:
|
||||
|
||||
if len( self._callables ) > 0:
|
||||
|
||||
( callable, args, kwargs ) = self._callables.pop( 0 )
|
||||
|
||||
do_callable = True
|
||||
|
||||
else:
|
||||
|
||||
( topic, args, kwargs ) = self._pubsubs.pop( 0 )
|
||||
|
||||
callables = self._GetCallables( topic )
|
||||
|
||||
self._callables = [ ( callable, args, kwargs ) for callable in callables ]
|
||||
|
||||
for i in range( len( self._callables ) ): wx.PostEvent( HC.app, PubSubEvent() )
|
||||
|
||||
|
||||
|
||||
# do this _outside_ the lock, lol
|
||||
if do_callable: callable( *args, **kwargs )
|
||||
|
||||
|
||||
def pub( self, topic, *args, **kwargs ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
if topic in self._topics_to_objects:
|
||||
# this stops the pubsubs started at the beginning of the program screwing with the queue
|
||||
if HC.app.IsMainLoopRunning():
|
||||
|
||||
try:
|
||||
|
||||
objects = self._topics_to_objects[ topic ]
|
||||
|
||||
for object in objects:
|
||||
|
||||
method_names = self._topics_to_method_names[ topic ]
|
||||
|
||||
for method_name in method_names:
|
||||
|
||||
if hasattr( object, method_name ):
|
||||
|
||||
try:
|
||||
|
||||
self._pubsubs.put( ( getattr( object, method_name ), args, kwargs ) )
|
||||
|
||||
wx.PostEvent( HC.app, PubSubEvent() )
|
||||
|
||||
except wx.PyDeadObjectError: pass
|
||||
except Exception as e: raise e
|
||||
|
||||
|
||||
|
||||
|
||||
except: pass
|
||||
self._pubsubs.append( ( topic, args, kwargs ) )
|
||||
|
||||
|
||||
|
||||
|
||||
def pubimmediate( self, topic, *args, **kwargs ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
if topic in self._topics_to_objects:
|
||||
|
||||
try:
|
||||
|
||||
objects = self._topics_to_objects[ topic ]
|
||||
|
||||
for object in objects:
|
||||
|
||||
method_names = self._topics_to_method_names[ topic ]
|
||||
|
||||
for method_name in method_names:
|
||||
|
||||
if hasattr( object, method_name ):
|
||||
|
||||
try: getattr( object, method_name )( *args, **kwargs )
|
||||
except wx.PyDeadObjectError: pass
|
||||
except Exception as e: raise e
|
||||
|
||||
|
||||
|
||||
|
||||
except RuntimeError: pass # sometimes the set changes size during iteration, which is a bug I haven't tracked down
|
||||
except wx.PyDeadObjectError: pass
|
||||
except TypeError: pass
|
||||
except Exception as e: raise e
|
||||
wx.PostEvent( HC.app, PubSubEvent() )
|
||||
|
||||
|
||||
|
||||
|
@ -101,4 +127,14 @@ class HydrusPubSub():
|
|||
self._topics_to_method_names[ topic ].add( method_name )
|
||||
|
||||
|
||||
|
||||
def WXpubimmediate( self, topic, *args, **kwargs ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
callables = self._GetCallables( topic )
|
||||
|
||||
for callable in callables: callable( *args, **kwargs )
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
import BaseHTTPServer
|
||||
import ClientConstants as CC
|
||||
import Cookie
|
||||
import hashlib
|
||||
import httplib
|
||||
import HydrusAudioHandling
|
||||
import HydrusConstants as HC
|
||||
import HydrusDocumentHandling
|
||||
import HydrusExceptions
|
||||
import HydrusFileHandling
|
||||
import HydrusFlashHandling
|
||||
import HydrusImageHandling
|
||||
import HydrusServerResources
|
||||
import HydrusVideoHandling
|
||||
import os
|
||||
import random
|
||||
import ServerConstants as SC
|
||||
import SocketServer
|
||||
import traceback
|
||||
import urllib
|
||||
import wx
|
||||
import yaml
|
||||
from twisted.internet import reactor, defer
|
||||
from twisted.internet.threads import deferToThread
|
||||
from twisted.protocols import amp
|
||||
|
||||
class HydrusAMPCommand( amp.Command ):
|
||||
errors = {}
|
||||
errors[ HydrusExceptions.ForbiddenException ] = 'FORBIDDEN'
|
||||
errors[ HydrusExceptions.NetworkVersionException ] = 'NETWORK_VERSION'
|
||||
errors[ HydrusExceptions.NotFoundException ] = 'NOT_FOUND'
|
||||
errors[ HydrusExceptions.PermissionException ] = 'PERMISSION'
|
||||
errors[ HydrusExceptions.SessionException ] = 'SESSION'
|
||||
errors[ Exception ] = 'EXCEPTION'
|
||||
|
||||
# IMFile, for aes-encrypted file transfers, as negotiated over otr messages
|
||||
# file_transfer_id (so we can match up the correct aes key)
|
||||
# file (this is complicated -- AMP should be little things, right? I need to check max packet size.)
|
||||
# so, this should be blocks. a block_id and a block
|
||||
class IMLogin( HydrusAMPCommand ):
|
||||
arguments = [ ( 'session_key', amp.String() ) ]
|
||||
|
||||
class IMMessage( HydrusAMPCommand ):
|
||||
arguments = [ ( 'identifier_from', amp.String() ), ( 'identifier_to', amp.String() ), ( 'message', amp.String() ) ]
|
||||
|
||||
class IMSessionKey( HydrusAMPCommand ):
|
||||
arguments = [ ( 'access_key', amp.String() ) ]
|
||||
response = [ ( 'session_key', amp.String() ) ]
|
||||
|
||||
class MPublicKey( HydrusAMPCommand ):
|
||||
arguments = [ ( 'identifier', amp.String() ) ]
|
||||
response = [ ( 'public_key', amp.String() ) ]
|
||||
|
||||
class MessagingServiceProtocol( amp.AMP ):
|
||||
|
||||
def im_login( self, session_key ):
|
||||
|
||||
# check session_key.
|
||||
# if it is good, stick this connection on the login manager
|
||||
# else error
|
||||
|
||||
return {}
|
||||
|
||||
IMLogin.responder( im_login )
|
||||
|
||||
def im_message( self, identifier_from, identifier_to, message ):
|
||||
|
||||
# get connection for identifier_to from larger, failing appropriately
|
||||
# if we fail, we should probably log the _to out, right?
|
||||
|
||||
# connection.callRemote( IMMessage, identifier_from = identifier_from, identifier_to = identifier_to, message = message )
|
||||
# this returns a deferred, so set up a 'return {}' deferred.
|
||||
|
||||
return {}
|
||||
|
||||
IMMessage.responder( im_message )
|
||||
|
||||
def im_session_key( self, access_key ):
|
||||
|
||||
session_key = os.urandom( 32 )
|
||||
|
||||
return { 'session_key' : session_key }
|
||||
|
||||
IMSessionKey.responder( im_session_key )
|
||||
|
||||
def m_public_key( self, identifier ):
|
||||
|
||||
# this will not be useful until we have normal messaging sorted
|
||||
|
||||
public_key = 'public key'
|
||||
|
||||
return { 'public_key' : public_key }
|
||||
|
||||
MPublicKey.responder( m_public_key )
|
||||
|
||||
def connectionLost( self, reason ):
|
||||
|
||||
# delete this connection from the login stuffs.
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MessagingClientProtocol( amp.AMP ):
|
||||
|
||||
def im_message( self, identifier_from, identifier_to, message ):
|
||||
|
||||
# send these args on to the messaging manager, which will:
|
||||
# start a context, if needed
|
||||
# spawn a gui prompt/window to start a convo, if needed
|
||||
# queue the message through to the appropriate context
|
||||
# maybe the context should spam up to the ui, prob in a pubsub; whatever.
|
||||
|
||||
pass
|
||||
|
||||
IMMessage.responder( im_message )
|
|
@ -38,18 +38,7 @@ class Controller( wx.App ):
|
|||
|
||||
def EventExit( self, event ): self._tbicon.Destroy()
|
||||
|
||||
def EventPubSub( self, event ):
|
||||
|
||||
pubsubs_queue = HC.pubsub.GetQueue()
|
||||
|
||||
( callable, args, kwargs ) = pubsubs_queue.get()
|
||||
|
||||
try: callable( *args, **kwargs )
|
||||
except TypeError: pass
|
||||
except Exception as e: HC.ShowException( e )
|
||||
|
||||
pubsubs_queue.task_done()
|
||||
|
||||
def EventPubSub( self, event ): HC.pubsub.WXProcessQueueItem()
|
||||
|
||||
def OnInit( self ):
|
||||
|
||||
|
|
|
@ -155,6 +155,26 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
self.assertEqual( pred, read_pred )
|
||||
|
||||
#
|
||||
|
||||
result = self._read( 'autocomplete_tags', tag = 'car' )
|
||||
|
||||
pred = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( '+', 'car' ), 1 )
|
||||
|
||||
( read_pred, ) = result.GetMatches( 'car' )
|
||||
|
||||
self.assertEqual( read_pred.GetCount(), 1 )
|
||||
|
||||
self.assertEqual( pred, read_pred )
|
||||
|
||||
#
|
||||
|
||||
result = self._read( 'autocomplete_tags', tag = 'c' )
|
||||
|
||||
read_preds = result.GetMatches( 'c' )
|
||||
|
||||
self.assertEqual( read_preds, [] )
|
||||
|
||||
|
||||
def test_booru( self ):
|
||||
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
import ClientConstants as CC
|
||||
import ClientGUIDialogs
|
||||
import collections
|
||||
import HydrusConstants as HC
|
||||
import HydrusEncryption
|
||||
import os
|
||||
import potr
|
||||
import TestConstants
|
||||
import unittest
|
||||
import wx
|
||||
|
||||
class Catcher():
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
self._last_write = ''
|
||||
|
||||
|
||||
def GetLastWrite( self ):
|
||||
|
||||
l_w = self._last_write
|
||||
|
||||
self._last_write = ''
|
||||
|
||||
return l_w
|
||||
|
||||
|
||||
def write( self, data ): self._last_write = data
|
||||
|
||||
class TestIM( unittest.TestCase ):
|
||||
|
||||
def test_otr( self ):
|
||||
|
||||
alice = os.urandom( 32 ).encode( 'hex' )
|
||||
bob = os.urandom( 32 ).encode( 'hex' )
|
||||
|
||||
alice_privkey_hex = '0000000000808000000000000000944834d12b2ad788d34743102266aa9d87fc180577f977c2b201799a4149ca598819ff59591254cb312d1ad23d791a9355cd423c438cb0bc7000bb33377cf73be6fc900705c250d2bdba3287c8e545faf0653e44e66aefffda6e445947ff98cac7c02cb4911f9f527a6f25cf6b8aae4af2909b3c077b80bb00000014afb936c2487a867db906015d755f158e5bf38c1d00000080345d40c8fc329e254ef4be5efa7e1dc20484b982394d09fece366ef598db1a29f4b63160728de57058f405903ded01d6359242656f1e8c02a0b5c67f5d09496486f2f9f005abcec1470888bd7f31dbee8b0ce94b31ed36437dc2446b38829ba08927329bd1ecec0de1d2cd409f840ed2478cdf154a12f79815b29e75ea4a2e0f000000807731a186f55afcdebc34aba5a10130e5eafac0d0067c50f49be494a463271b34a657114c9b69c4fbe30302259feafe75f091b5c5670c7193e256bd7a5be2f3daee2d1a8bc4e04eec891cd6c4591edf40e5cbf8f3e1ca985a9b01d13768ea7160761af475b0097878376dbac6b1ce5b101fb1dd7da354e739791895caba52f14c000000146497dca1a62f1039a0ce8bfc99984de1cc5a9848'
|
||||
bob_privkey_hex = '00000000008080000000000000741dae82c8c9a55a7f2a5eb9e4db0b3e5990de5df5d7e2a0dab221a8e1e8b92d99f70387458088215836ed1c42c157640578da801120aa50c180c7d9b4e72205b863ecbd6f43e2efbca04d4c6b1b184fd57bda231445ad4a5e9b7ada27ddd9b24c2cfdba77858e76072b5e87a0a4eb91608ffea42ded252bd700000014ec380fdb62ad0248746142c58654403f665c9701000000806e1aaee6b00ee1a77927b5c7a28089eb9bc147e7688091aeeff7de7c3fa98498748d0744f328c230991e9d8031b704d9fc2a87206d62e2f3b1c30b3a370a237368b04dbe826978a232666be84db52c398700d8e2dbc4f5cabc8bd1270f429ea54247a087fdedfac723bf8b1aa4cfad664646a51d97f96a7dffaef0c24d90a5f5000000803dff456298b4fdc4a08599790341f274c8ea7685101cd2d42fb90a34034f71ca0b9b1f2074ec41e1282bd6a3b74d855c82fcea411485da83f784ca15deb3b5372b544ae84fa6f9a8cd470bc8ebd8e60135098e4a4b608d2aea395b2053311f0802a6db0836e25170ce8e5670579f63445688113b93f8597e88d28f03c020c77800000014a762254ce091c8abf6acd0945e32436abbc1b3f2'
|
||||
|
||||
alice_privkey = HydrusEncryption.LoadOTRKey( alice_privkey_hex.decode( 'hex' ) )
|
||||
bob_privkey = HydrusEncryption.LoadOTRKey( bob_privkey_hex.decode( 'hex' ) )
|
||||
|
||||
#alice_privkey = HydrusEncryption.GenerateOTRKey()
|
||||
#bob_privkey = HydrusEncryption.GenerateOTRKey()
|
||||
|
||||
alice_account = HydrusEncryption.HydrusOTRAccount( alice, alice_privkey, {} )
|
||||
bob_account = HydrusEncryption.HydrusOTRAccount( bob, bob_privkey, {} )
|
||||
|
||||
alice_context = HydrusEncryption.HydrusOTRContext( alice_account, bob )
|
||||
bob_context = HydrusEncryption.HydrusOTRContext( bob_account, alice )
|
||||
|
||||
catcher = Catcher()
|
||||
|
||||
#
|
||||
|
||||
self.assertEqual( alice_context.state, potr.context.STATE_PLAINTEXT )
|
||||
self.assertEqual( bob_context.state, potr.context.STATE_PLAINTEXT )
|
||||
|
||||
m = alice_context.sendMessage( potr.context.FRAGMENT_SEND_ALL, '' )
|
||||
|
||||
res = bob_context.receiveMessage( m, catcher )
|
||||
|
||||
m = catcher.GetLastWrite()
|
||||
|
||||
res = alice_context.receiveMessage( m, catcher )
|
||||
|
||||
m = catcher.GetLastWrite()
|
||||
|
||||
res = bob_context.receiveMessage( m, catcher )
|
||||
|
||||
m = catcher.GetLastWrite()
|
||||
|
||||
res = alice_context.receiveMessage( m, catcher )
|
||||
|
||||
m = catcher.GetLastWrite()
|
||||
|
||||
res = bob_context.receiveMessage( m, catcher )
|
||||
|
||||
self.assertEqual( alice_context.state, potr.context.STATE_ENCRYPTED )
|
||||
self.assertEqual( bob_context.state, potr.context.STATE_ENCRYPTED )
|
||||
|
||||
self.assertEqual( bob_privkey.getPublicPayload(), alice_context.getCurrentKey().getPublicPayload() )
|
||||
self.assertEqual( alice_privkey.getPublicPayload(), bob_context.getCurrentKey().getPublicPayload() )
|
||||
|
||||
#
|
||||
|
||||
self.assertEqual( alice_context.getCurrentTrust(), None )
|
||||
|
||||
alice_context.setCurrentTrust( 'verified' )
|
||||
|
||||
self.assertEqual( alice_context.getCurrentTrust(), 'verified' )
|
||||
|
||||
[ ( args, kwargs ) ] = HC.app.GetWrite( 'otr_trusts' )
|
||||
|
||||
self.assertEqual( args, ( alice, { bob : { alice_context.getCurrentKey().cfingerprint() : 'verified' } } ) )
|
||||
|
||||
self.assertEqual( bob_context.getCurrentTrust(), None )
|
||||
|
||||
bob_context.setCurrentTrust( 'verified' )
|
||||
|
||||
self.assertEqual( bob_context.getCurrentTrust(), 'verified' )
|
||||
|
||||
[ ( args, kwargs ) ] = HC.app.GetWrite( 'otr_trusts' )
|
||||
|
||||
self.assertEqual( args, ( bob, { alice : { bob_context.getCurrentKey().cfingerprint() : 'verified' } } ) )
|
||||
|
||||
#
|
||||
|
||||
m = alice_context.sendMessage( potr.context.FRAGMENT_SEND_ALL, 'hello bob', appdata = catcher )
|
||||
|
||||
m = catcher.GetLastWrite()
|
||||
|
||||
res = bob_context.receiveMessage( m, catcher )
|
||||
|
||||
( message, gumpf ) = res
|
||||
|
||||
self.assertEqual( message, 'hello bob' )
|
||||
|
||||
#
|
||||
|
||||
m = bob_context.sendMessage( potr.context.FRAGMENT_SEND_ALL, 'hello alice', appdata = catcher )
|
||||
|
||||
m = catcher.GetLastWrite()
|
||||
|
||||
res = alice_context.receiveMessage( m, catcher )
|
||||
|
||||
( message, gumpf ) = res
|
||||
|
||||
self.assertEqual( message, 'hello alice' )
|
||||
|
|
@ -14,7 +14,6 @@ import threading
|
|||
import unittest
|
||||
from twisted.internet import reactor
|
||||
|
||||
|
||||
class TestServer( unittest.TestCase ):
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -49,4 +49,4 @@ HC.shutdown = True
|
|||
|
||||
reactor.callFromThread( reactor.stop )
|
||||
|
||||
HC.pubsub.pubimmediate( 'shutdown' )
|
||||
HC.pubsub.WXpubimmediate( 'shutdown' )
|
||||
|
|
4
test.py
4
test.py
|
@ -9,6 +9,7 @@ from include import TestDialogs
|
|||
from include import TestDB
|
||||
from include import TestFunctions
|
||||
from include import TestHydrusDownloading
|
||||
from include import TestHydrusEncryption
|
||||
from include import TestHydrusSessions
|
||||
from include import TestHydrusTags
|
||||
from include import TestServer
|
||||
|
@ -58,6 +59,7 @@ class App( wx.App ):
|
|||
if run_all or only_run == 'daemons': suites.append( unittest.TestLoader().loadTestsFromModule( TestClientDaemons ) )
|
||||
if run_all or only_run == 'dialogs': suites.append( unittest.TestLoader().loadTestsFromModule( TestDialogs ) )
|
||||
if run_all or only_run == 'db': suites.append( unittest.TestLoader().loadTestsFromModule( TestDB ) )
|
||||
if run_all or only_run == 'encryption': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusEncryption ) )
|
||||
if run_all or only_run == 'functions': suites.append( unittest.TestLoader().loadTestsFromModule( TestFunctions ) )
|
||||
if run_all or only_run == 'downloading': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusDownloading ) )
|
||||
if run_all or only_run == 'sessions': suites.append( unittest.TestLoader().loadTestsFromModule( TestHydrusSessions ) )
|
||||
|
@ -136,4 +138,4 @@ if __name__ == '__main__':
|
|||
|
||||
HC.shutdown = True
|
||||
|
||||
HC.pubsub.pubimmediate( 'shutdown' )
|
||||
HC.pubsub.WXpubimmediate( 'shutdown' )
|
||||
|
|
Loading…
Reference in New Issue