diff --git a/client.pyw b/client.pyw
index 51032f80..6213e124 100755
--- a/client.pyw
+++ b/client.pyw
@@ -49,4 +49,4 @@ HC.shutdown = True
reactor.callFromThread( reactor.stop )
-HC.pubsub.pubimmediate( 'shutdown' )
+HC.pubsub.WXpubimmediate( 'shutdown' )
diff --git a/help/changelog.html b/help/changelog.html
index 48083e62..984b5d68 100755
--- a/help/changelog.html
+++ b/help/changelog.html
@@ -8,6 +8,22 @@
changelog
+ version 91
+
+ - encrypted instant messaging framework started
+ - encrypted instant messaging test added
+ - encrypted instant messaging trust framework started
+ - started an AMP framework for encrypted instant messaging
+ - tag A/C exact match parameter added
+ - tag A/C exact match parameter test added
+ - tag A/C logic reorganised and improved
+ - tag A/C now gives exact matches for queries shorter than the A/C threshold
+ - tag A/C threshold default set to 2
+ - fixed an important architectural bug in pubsub
+ - generally cleaned up pubsub code
+ - 'popup messages sometimes not updating' problem is fixed
+ - a bit of initialisation cleanup to make startup behaviour more reliable
+
version 91
- improved how accounts are identified in the server
diff --git a/include/ClientConstants.py b/include/ClientConstants.py
index e790d8fb..51316831 100755
--- a/include/ClientConstants.py
+++ b/include/ClientConstants.py
@@ -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 = {}
diff --git a/include/ClientController.py b/include/ClientController.py
index f4b5c17d..52cdf147 100755
--- a/include/ClientController.py
+++ b/include/ClientController.py
@@ -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 )
diff --git a/include/ClientDB.py b/include/ClientDB.py
index 4a70ed91..b5f51bf0 100755
--- a/include/ClientDB.py
+++ b/include/ClientDB.py
@@ -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()
diff --git a/include/ClientGUI.py b/include/ClientGUI.py
index 10f8e021..0e9b0d68 100755
--- a/include/ClientGUI.py
+++ b/include/ClientGUI.py
@@ -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 ):
diff --git a/include/ClientGUICommon.py b/include/ClientGUICommon.py
index f5dc1302..4be3b9cd 100755
--- a/include/ClientGUICommon.py
+++ b/include/ClientGUICommon.py
@@ -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:
diff --git a/include/HydrusConstants.py b/include/HydrusConstants.py
index 2d55df3e..1293e228 100755
--- a/include/HydrusConstants.py
+++ b/include/HydrusConstants.py
@@ -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 )
diff --git a/include/HydrusEncryption.py b/include/HydrusEncryption.py
index 31cac886..8a746cab 100644
--- a/include/HydrusEncryption.py
+++ b/include/HydrusEncryption.py
@@ -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]
-
\ No newline at end of file
+
+# 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.
\ No newline at end of file
diff --git a/include/HydrusPubSub.py b/include/HydrusPubSub.py
index e50032cb..a20ba938 100755
--- a/include/HydrusPubSub.py
+++ b/include/HydrusPubSub.py
@@ -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 )
+
+
\ No newline at end of file
diff --git a/include/HydrusServerAMP.py b/include/HydrusServerAMP.py
new file mode 100644
index 00000000..a27246a3
--- /dev/null
+++ b/include/HydrusServerAMP.py
@@ -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 )
\ No newline at end of file
diff --git a/include/ServerController.py b/include/ServerController.py
index f25f9672..60d30251 100755
--- a/include/ServerController.py
+++ b/include/ServerController.py
@@ -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 ):
diff --git a/include/TestDB.py b/include/TestDB.py
index 260ffab0..38c56e19 100644
--- a/include/TestDB.py
+++ b/include/TestDB.py
@@ -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 ):
diff --git a/include/TestHydrusEncryption.py b/include/TestHydrusEncryption.py
new file mode 100644
index 00000000..3bf9c2db
--- /dev/null
+++ b/include/TestHydrusEncryption.py
@@ -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' )
+
diff --git a/include/TestServer.py b/include/TestServer.py
index 2f8d4955..7602ece0 100644
--- a/include/TestServer.py
+++ b/include/TestServer.py
@@ -14,7 +14,6 @@ import threading
import unittest
from twisted.internet import reactor
-
class TestServer( unittest.TestCase ):
@classmethod
diff --git a/server.pyw b/server.pyw
index a81f9b1f..9ce5389e 100755
--- a/server.pyw
+++ b/server.pyw
@@ -49,4 +49,4 @@ HC.shutdown = True
reactor.callFromThread( reactor.stop )
-HC.pubsub.pubimmediate( 'shutdown' )
+HC.pubsub.WXpubimmediate( 'shutdown' )
diff --git a/test.py b/test.py
index 7e8a5652..f63014f3 100644
--- a/test.py
+++ b/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' )