SF.net SVN: fclient: [23] trunk/fclient/fclient_lib/fcp/fcp2_0.py
Status: Pre-Alpha
Brought to you by:
jurner
From: <jU...@us...> - 2007-10-31 10:00:18
|
Revision: 23 http://fclient.svn.sourceforge.net/fclient/?rev=23&view=rev Author: jUrner Date: 2007-10-31 03:00:15 -0700 (Wed, 31 Oct 2007) Log Message: ----------- refactored the code and added some more protocol methods and events Modified Paths: -------------- trunk/fclient/fclient_lib/fcp/fcp2_0.py Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py =================================================================== --- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-30 15:11:02 UTC (rev 22) +++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-31 10:00:15 UTC (rev 23) @@ -1,10 +1,9 @@ -'''Freenet client protocol 2.0 implementation +"""Freenet client protocol 2.0 implementation @newfield event, events - Sample code:: client = FcpClient() @@ -12,45 +11,8 @@ if nodeHello is not None: # do whatever - +""" -Most method calls can be made either synchron or asynchron:: - - peers = client.peerList(synchron=True) - for peer in peers: - # do whatever - - -To get informed about asynchron events you should connect the relevant events the client provides:: - - # connect to one single event - client.EventListNextPeer += MyCallback - - # connect to multiple events at once - client += ( - (client.EventListPeers, MyCallback1), - (client.EventEndListPeers, MyCallback2), - ) - - # each callback is called with the event as first parameter, followed by additional parameters, - # depending on the event triggered. - def MyListNextPeerCallback(event, peer): - print peer - - client.peerList(synchron=False) - - - # when event notifications are no longer required, you should always make shure to disconnect from them - client.EventListNextPeer -= MyCallback - client -= ( - (client.EventListPeers, MyCallback1), - (client.EventEndListPeers, MyCallback2), - ) - - - -''' - import atexit import base64 import logging @@ -92,192 +54,9 @@ DefaultFcpPort = 9481 SocketTimeout = 0.1 - -class IdentifierPrefix: - """Special purpose identifier prefixes""" - - FileInfo = 'FileInfo::' - - -class Verbosity: - Debug = logging.DEBUG - Info = logging.INFO - Warning = logging.WARNING - - -class Priorities: - """All priorities supported by the client""" - - Maximum = '0' - Interactive = '1' - SemiInteractive = '2' - Updatable = '3' - Bulk = '4' - Prefetch = '5' - Minimum = '6' - - PriorityMin = Minimum - PriorityDefault = Bulk - - -#TODO: no idea how fcp handles strings as in <Peer volatile.status=CONNECTED> -# all I could find in the sources where these constants as in PEER_NODE_STATUS_CONNECTED -# in --> freenet/node/PeerManager.java -class PeerNodeStatus: - Connected = 1 - RoutingBackedOff = 2 - TooNew = 3 - TooOld = 4 - Disconnected = 5 - NeverConnected = 6 - Disabled = 7 - Bursting = 8 - Listening = 9 - ListenOnly = 10 - ClockProblem = 11 - ConnError = 12 - Disconnecting = 13 - - - -class PeerNoteType: - """All known peer note types""" - Private = '1' - - -#************************************************************************************ -# exceptions -#************************************************************************************ -class FetchError(Exception): - """All fetch errors supported by the client""" - - def __init__(self, msg): - """ - @param msg: (Message) GetFailed message or its parameters dict - """ - self.value = '%s (%s, %s)' % ( - msg.get('CodeDescription', 'Unknown error') , - msg['Code'], - msg.get('ExtraDescription', '...'), - ) - def __str__(self): return self.value - - MaxArchiveRecursionExceeded = '1' - UnknownSplitfileMetadata = '2' - UnknownMetadata = '3' - InvalidMetadata = '4' - ArchiveFailure = '5' - BlockDecodeError = '6' - MaxMetadataLevelsExceeded = '7' - MaxArchiveRestartsExceeded = '8' - MaxRecursionLevelExceeded = '9' - NotAnArchve = '10' - TooManyMetastrings = '11' - BucketError = '12' - DataNotFound = '13' - RouteNotFound = '14' - RejectedOverload = '15' - TooManyRedirects = '16' - InternalError = '17' - TransferFailed = '18' - SplitfileError = '19' - InvalidUri = '20' - TooBig = '21' - MetadataTooBig = '22' - TooManyBlocks = '23' - NotEnoughMetastrings = '24' - Canceled = '25' - ArchiveRestart = '26' - PermanentRedirect = '27' - NotAllDataFound = '28' - - -class InsertError(Exception): - """All insert errors supported by the client""" - - def __init__(self, msg): - """ - @param msg: (Message) PutFailed message or its parameters dict - """ - self.value = '%s (%s, %s)' % ( - msg.get('CodeDescription', 'Unknown error') , - msg['Code'], - msg.get('ExtraDescription', '...'), - ) - def __str__(self): return self.value - - InvalidUri = '1' - BucketError = '2' - InternalError = '3' - RejectedOverload = '4' - RouteNotFound = '5' - FatalErrorInBlocks = '6' - TooManyRetriesInBlock = '7' - RouteReallyNotFound = '8' - Collision = '9' - Canceled = '10' - - -class ProtocolError(Exception): - """All protocol errors supported by the client""" - - def __init__(self, msg): - """ - @param msg: (Message) ProtocolError message or its parameters dict - """ - self.value = '%s (%s, %s)' % ( - msg.get('CodeDescription', 'Unknown error') , - msg['Code'], - msg.get('ExtraDescription', '...'), - ) - def __str__(self): return self.value - - ClientHelloMustBeFirst = '1' - NoLateClientHellos = '2' - MessageParseError = '3' - UriParseError = '4' - MissingField = '5' - ErrorParsingNumber = '6' - InvalidMessage = '7' - InvalidField = '8' - FileNotFound = '9' - DiskTargetExists = '10' - SameDirectoryExpected = '11' - CouldNotCreateFile = '12' - CouldNotWriteFile = '13' - CouldNotRenameFile = '14' - NoSuchIdentifier = '15' - NotSupported = '16' - InternalError = '17' - ShuttingDown = '18' - NoSuchNodeIdentifier = '19' # Unused since 995 - UrlParseError = '20' - ReferenceParseError = '21' - FileParseError = '22' - NotAFile = '23' - AccessDenied = '24' - DDADenied = '25' - CouldNotReadFile = '26' - ReferenceSignature = '27' - CanNotPeerWithSelf = '28' - PeerExists = '29' - OpennetDisabled = '30' - DarknetPeerOnly = '31' - -class SocketError(Exception): pass -class FcpError(Exception): pass #********************************************************************** -# functions +# helpers #********************************************************************** -def newIdentifier(prefix=None): - """Returns a new unique identifier - @return: (str) uuid - """ - if prefix: - return prefix + str(uuid.uuid4()) - return str(uuid.uuid4()) - - def saveReadFile(fpath): """Reads contents of a file in the savest manner possible @param fpath: file to write @@ -330,358 +109,14 @@ fp.close() return written -def startFreenet(cmdline): - """Starts freenet - @param cmdline: commandline to start freenet (like '/freenet/run.sh start' or 'c:\freenet\start.bat') - @return: (string) whatever freenet returns - """ - #TODO: on windows it may be necessary to hide the command window - p = subprocess.Popen( - args=cmdline, - shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - stdout, stderr = p.communicate() - return stdout - #********************************************************************** # classes #********************************************************************** -class FcpUri(object): - """Wrapper class for freenet uris""" - - - KeySSK = 'SSK@' - KeyKSK = 'KSK@' - KeyCHK = 'CHK@' - KeyUSK = 'USK@' - KeySVK = 'SVK@' - KeyUnknown = '' - KeysAll = (KeySSK, KeyKSK, KeyCHK, KeyUSK, KeySVK) - - ReUriPattern = re.compile('(%s.*?)(?= |\Z)' % '.*?|'.join(KeysAll), re.I) - ReKeyPattern = re.compile('(%s)' % '|'.join(KeysAll), re.I) - - - def __init__(self, uri): - """ - @param uri: uri to wrap - @param cvar ReUriPattern: pattern matching a freenet uri - @param cvar ReKeyPattern: pattern matching the key type of a freenet uri - - @note: any dfecorations prefixing the freenet part of the uri uri are stripped if possible - - - >>> uri = FcpUri('freenet:SSK@foo/bar') - >>> str(uri) - 'SSK@foo/bar' - >>> uri.keyType() == FcpUri.KeySSK - True - >>> uri.split() - ('SSK@foo', 'bar') - >>> uri.fileName() - 'bar' - - >>> uri = FcpUri('http://SSK@foo/bar') - >>> str(uri) - 'SSK@foo/bar' - - # uris not containing freenet keys are left unchanged - >>> uri = FcpUri('http://foo/bar') - >>> str(uri) - 'http://foo/bar' - >>> uri.keyType() == FcpUri.KeyUnknown - True - >>> uri.split() - ('http://foo/bar', '') - >>> uri.fileName() - 'http://foo/bar' - - """ - self._uri = uri - - result = self.ReUriPattern.search(uri) - if result is not None: - self._uri = result.group(0) - - def __str__(self): - return str(self._uri) - - def __unicode__(self): - return unicode(self._uri) - - def keyType(self): - """Retuns the key type of the uri - @return: one of the Key* consts - """ - result = self.ReKeyPattern.search(self._uri) - if result is not None: - return result.group(0).upper() - return self.KeyUnknown - - def split(self): - """Splits the uri - @return: tuple(freenet-key, file-name) - """ - if self.keyType() != self.KeyUnknown: - head, sep, tail = self._uri.partition('/') - return head, tail - return self._uri, '' - - def fileName(self): - """Returns the filename part of the uri - @return: str - """ - head, tail = self.split() - if tail: - return tail - return self._uri - -class Message(object): - """Class wrapping a freenet message""" - - __slots__ = ('name', 'data', 'params') - - # client messages - ClientHello = 'ClientHello' - ListPeer = 'ListPeer' # (since 1045) - ListPeers = 'ListPeers' - ListPeerNotes = 'ListPeerNotes' - AddPeer = 'AddPeer' - ModifyPeer = 'ModifyPeer' - ModifyPeerNote = 'ModifyPeerNote' - RemovePeer = 'RemovePeer' - GetNode = 'GetNode' - GetConfig = 'GetConfig' # (since 1027) - ModifyConfig = 'ModifyConfig' # (since 1027) - TestDDARequest = 'TestDDARequest' # (since 1027) - TestDDAResponse = 'TestDDAResponse' # (since 1027) - GenerateSSK = 'GenerateSSK' - ClientPut = 'ClientPut' - ClientPutDiskDir = 'ClientPutDiskDir' - ClientPutComplexDir = 'ClientPutComplexDir' - ClientGet = 'ClientGet' - SubscribeUSK = 'SubscribeUSK' - WatchGlobal = 'WatchGlobal' - GetRequestStatus = 'GetRequestStatus' - ListPersistentRequests = 'ListPersistentRequests' - RemovePersistentRequest = 'RemovePersistentRequest' - ModifyPersistentRequest = 'ModifyPersistentRequest' - Shutdown = 'Shutdown' - - # node messages - NodeHello = 'NodeHello' - CloseConnectionDuplicateClientName = 'CloseConnectionDuplicateClientName' - Peer = 'Peer' - PeerNote = 'PeerNote' - EndListPeers = 'EndListPeers' - EndListPeerNotes = 'EndListPeerNotes' - PeerRemoved = 'PeerRemoved' - NodeData = 'NodeData' - ConfigData = 'ConfigData' # (since 1027) - TestDDAReply = 'TestDDAReply' # (since 1027) - TestDDAComplete = 'TestDDAComplete' # (since 1027) - SSKKeypair = 'SSKKeypair' - PersistentGet = 'PersistentGet' - PersistentPut = 'PersistentPut' - PersistentPutDir = 'PersistentPutDir' - URIGenerated = 'URIGenerated' - PutSuccessful = 'PutSuccessful' - PutFetchable = 'PutFetchable' - DataFound = 'DataFound' - AllData = 'AllData' - StartedCompression = 'StartedCompression' - FinishedCompression = 'FinishedCompression' - SimpleProgress = 'SimpleProgress' - EndListPersistentRequests = 'EndListPersistentRequests' - PersistentRequestRemoved = 'PersistentRequestRemoved' # (since 1016) - PersistentRequestModified = 'PersistentRequestModified' # (since 1016) - PutFailed = 'PutFailed' - GetFailed = 'GetFailed' - ProtocolError = 'ProtocolError' - IdentifierCollision = 'IdentifierCollision' - UnknownNodeIdentifier = 'UnknownNodeIdentifier' - UnknownPeerNoteType = 'UnknownPeerNoteType' - SubscribedUSKUpdate = 'SubscribedUSKUpdate' - - # client messages (internal use only) - ClientSocketTimeout = 0 - ClientSocketDied = 1 - - - def __init__(self, name, data=None, **params): - """ - @param name: messge name - @param data: data associated to the messge (not yet implemented) - @param params: {field-name: value, ...} of parameters of the message - @note: all params can be accessed as attributes of the class - """ - self.data = data - self.name = name - self.params = params - - - @classmethod - def bytesFromSocket(clss, socketObj, n): - """Reads n bytes from socket - @param socketObj: socket to read bytes from - @param n: (int) number of bytes to read - @return: (tuple) error-message or None, bytes read or None if an error occured - or no bytes could be read - """ - error = p = None - try: - p = socketObj.recv(n) - if not p: - p = None - raise socket.error('Socket shut down by node') - except socket.timeout, d: # no new messages in queue - error = clss(clss.ClientSocketTimeout) - except socket.error, d: - error = clss(clss.ClientSocketDied, Exception=socket.error, Details=d) - return error, p - - - @classmethod - def fromSocket(clss, socketObj): - """Reads a message from a socket - @param socketObj: socket to read a message from - @return: L{Message} next message from the socket. If the socket dies - unexpectedly a L{ClientSocketDied} message is returned containing the parameters - 'Exception' and 'Details'. If the socket times out a L{MessageClientSocketTimout} - message is returned. - """ - - msg = clss(None) - buf = [] - - #TODO: to buffer or not to buffer? - while True: - - # get next line from socket - error, p = clss.bytesFromSocket(socketObj, 1) - if error: - return error - - if p != '\n': - buf.append(p) - continue - #TODO: check if '\r\n' is allowed in freenet client protocol - else: - if buf[-1] == '\r': - del buf[-1] - - line = ''.join(buf) - buf = [] - if line == 'EndMessage': - break - - # first line == message name - if msg.name is None: - msg.name = line - - # get data member - elif line == 'Data': - remaining = int(msg.params['DataLength']) - msg.data = '' - while remaining > 0: - error, p = clss.bytesFromSocket(socketObj, remaining) - if error: - return error - remaining -= len(p) - msg.data += p - break - - # get next paramater - else: - head, sep, tail = line.partition('=') - msg.params[head] = tail - # TODO: errorchek params? - #if not sep: pass - - return msg - - - def get(self, name, default=None): - """Returns the message parameter 'name' or 'default' """ - return self.params.get(name, default) - - - def __getitem__(self, name): - """Returns the message parameter 'name' """ - return self.params[name] - - - def __setitem__(self, name, value): - """Sets the message parameter 'name' to 'value' """ - self.params[name] = value - - - def pprint(self): - """Returns the message as nicely formated human readable string""" - out = ['', '>>' + self.name, ] - for param, value in self.params.items(): - out.append('>> %s=%s' % (param, value)) - out.append('>>EndMessage') - return '\n'.join(out) - - - def send(self, socketObj): - """Dumps the message to a socket - @param socketObj: socket to dump the message to - """ - socketObj.sendall(self.toString()) - - - def toString(self): - """Returns the message as formated string ready to be send""" - - #TODO: just a guess, so maybe remove this check - if isinstance(self.name, (int, long)): - raise ValueError('You can not send client internal messages to the node') - out = [self.name, ] - for param, value in self.params.items(): - out.append('%s=%s' % (param, value)) - if self.data: - assert 'DataLength' in self.params, 'DataLength member required' - n = None - try: - n = int(self['DataLength']) - except ValueError: pass - assert n is not None, 'DataLength member must be an integer' - assert n == len(self.data), 'DataLength member must corrospond to lenght of data' - out.append('Data') - out.append(self.data) - else: - out.append('EndMessage\n') - return '\n'.join(out) #************************************************************************** # fcp client #************************************************************************** -class LogMessages: - """Message strings used for log infos""" - Connecting = 'Connecting to node...' - Connected = 'Connected to node' - ConnectionRetry = 'Connecting to node failed... retrying' - ConnectingFailed = 'Connecting to node failed' - - ClientClose = 'Closing client' - - MessageSend = 'SendMessage' - MessageReceived = 'ReceivedMessage' - - JobStart = 'Starting job: ' - JobStop = 'Stopping job: ' - JobsCompleted = 'All jobs completed' - - - KeyboardInterrupt = 'Keyboard interrupt' - SocketDied = 'Socket died' - - #TODO: no idea what happens on reconnect if socket died. What about running jobs? #TODO: name as specified in NodeHello seems to be usable to keep jobs alive. Have to test this. #TODO: do not mix directories as identifiers with identifiers (might lead to collisions) @@ -692,6 +127,10 @@ _events_ = ( + # config related events + 'EventConfigData', + 'EventNodeData', + #Peer related events 'EventListPeers', 'EventEndListPeers', @@ -707,18 +146,18 @@ 'EventSocketDied', # get / put related events + 'EventTestDDAComplete', 'EventIdentifierCollision', - 'EventFileInfo', - 'EventFileInfoProgress', + 'EventClientGetInfo', + 'EventClientGetInfoProgress', 'EventDataFound', 'EventGetFailed', 'EventSimpleProgress', 'EventPersistentRequestModified', 'EventPersistentRequestRemoved', - - + # others 'EventSSKKeypair', @@ -727,26 +166,513 @@ Version = '2.0' FcpTrue = 'true' FcpFalse = 'false' + class FetchError(Exception): + """All fetch errors supported by the client""" + + def __init__(self, msg): + """ + @param msg: (Message) GetFailed message or its parameters dict + """ + self.value = '%s (%s, %s)' % ( + msg.get('CodeDescription', 'Unknown error') , + msg['Code'], + msg.get('ExtraDescription', '...'), + ) + def __str__(self): return self.value + + MaxArchiveRecursionExceeded = '1' + UnknownSplitfileMetadata = '2' + UnknownMetadata = '3' + InvalidMetadata = '4' + ArchiveFailure = '5' + BlockDecodeError = '6' + MaxMetadataLevelsExceeded = '7' + MaxArchiveRestartsExceeded = '8' + MaxRecursionLevelExceeded = '9' + NotAnArchve = '10' + TooManyMetastrings = '11' + BucketError = '12' + DataNotFound = '13' + RouteNotFound = '14' + RejectedOverload = '15' + TooManyRedirects = '16' + InternalError = '17' + TransferFailed = '18' + SplitfileError = '19' + InvalidUri = '20' + TooBig = '21' + MetadataTooBig = '22' + TooManyBlocks = '23' + NotEnoughMetastrings = '24' + Canceled = '25' + ArchiveRestart = '26' + PermanentRedirect = '27' + NotAllDataFound = '28' + + class FcpError(Exception): pass + class FcpUri(object): + """Wrapper class for freenet uris""" + + + KeySSK = 'SSK@' + KeyKSK = 'KSK@' + KeyCHK = 'CHK@' + KeyUSK = 'USK@' + KeySVK = 'SVK@' + KeyUnknown = '' + KeysAll = (KeySSK, KeyKSK, KeyCHK, KeyUSK, KeySVK) + + ReUriPattern = re.compile('(%s.*?)(?= |\Z)' % '.*?|'.join(KeysAll), re.I) + ReKeyPattern = re.compile('(%s)' % '|'.join(KeysAll), re.I) + + + def __init__(self, uri): + """ + @param uri: uri to wrap + @param cvar ReUriPattern: pattern matching a freenet uri + @param cvar ReKeyPattern: pattern matching the key type of a freenet uri + + @note: any dfecorations prefixing the freenet part of the uri uri are stripped if possible + + + >>> uri = FcpUri('freenet:SSK@foo/bar') + >>> str(uri) + 'SSK@foo/bar' + >>> uri.keyType() == FcpUri.KeySSK + True + >>> uri.split() + ('SSK@foo', 'bar') + >>> uri.fileName() + 'bar' + + >>> uri = FcpUri('http://SSK@foo/bar') + >>> str(uri) + 'SSK@foo/bar' + + # uris not containing freenet keys are left unchanged + >>> uri = FcpUri('http://foo/bar') + >>> str(uri) + 'http://foo/bar' + >>> uri.keyType() == FcpUri.KeyUnknown + True + >>> uri.split() + ('http://foo/bar', '') + >>> uri.fileName() + 'http://foo/bar' + + """ + self._uri = uri + + result = self.ReUriPattern.search(uri) + if result is not None: + self._uri = result.group(0) + + def __str__(self): + return str(self._uri) + + def __unicode__(self): + return unicode(self._uri) + + def keyType(self): + """Retuns the key type of the uri + @return: one of the Key* consts + """ + result = self.ReKeyPattern.search(self._uri) + if result is not None: + return result.group(0).upper() + return self.KeyUnknown + + def split(self): + """Splits the uri + @return: tuple(freenet-key, file-name) + """ + if self.keyType() != self.KeyUnknown: + head, sep, tail = self._uri.partition('/') + return head, tail + return self._uri, '' + + def fileName(self): + """Returns the filename part of the uri + @return: str + """ + head, tail = self.split() + if tail: + return tail + return self._uri + + class IdentifierPrefix: + """Special purpose identifier prefixes""" + ClientGetInfo = 'ClientGetInfo::' + + class InsertError(Exception): + """All insert errors supported by the client""" + + def __init__(self, msg): + """ + @param msg: (Message) PutFailed message or its parameters dict + """ + self.value = '%s (%s, %s)' % ( + msg.get('CodeDescription', 'Unknown error') , + msg['Code'], + msg.get('ExtraDescription', '...'), + ) + def __str__(self): return self.value + + InvalidUri = '1' + BucketError = '2' + InternalError = '3' + RejectedOverload = '4' + RouteNotFound = '5' + FatalErrorInBlocks = '6' + TooManyRetriesInBlock = '7' + RouteReallyNotFound = '8' + Collision = '9' + Canceled = '10' + + class LogMessages: + """Message strings used for log infos""" + Connecting = 'Connecting to node...' + Connected = 'Connected to node' + ConnectionRetry = 'Connecting to node failed... retrying' + ConnectingFailed = 'Connecting to node failed' + + ClientClose = 'Closing client' + + MessageSend = 'SendMessage' + MessageReceived = 'ReceivedMessage' + + JobStart = 'Starting job: ' + JobStop = 'Stopping job: ' + JobsCompleted = 'All jobs completed' + + KeyboardInterrupt = 'Keyboard interrupt' + SocketDied = 'Socket died' + + class Message(object): + """Class wrapping a freenet message""" + + __slots__ = ('name', 'data', 'params') + + # client messages + ClientHello = 'ClientHello' + ListPeer = 'ListPeer' # (since 1045) + ListPeers = 'ListPeers' + ListPeerNotes = 'ListPeerNotes' + AddPeer = 'AddPeer' + ModifyPeer = 'ModifyPeer' + ModifyPeerNote = 'ModifyPeerNote' + RemovePeer = 'RemovePeer' + GetNode = 'GetNode' + GetConfig = 'GetConfig' # (since 1027) + ModifyConfig = 'ModifyConfig' # (since 1027) + TestDDARequest = 'TestDDARequest' # (since 1027) + TestDDAResponse = 'TestDDAResponse' # (since 1027) + GenerateSSK = 'GenerateSSK' + ClientPut = 'ClientPut' + ClientPutDiskDir = 'ClientPutDiskDir' + ClientPutComplexDir = 'ClientPutComplexDir' + ClientGet = 'ClientGet' + SubscribeUSK = 'SubscribeUSK' + WatchGlobal = 'WatchGlobal' + GetRequestStatus = 'GetRequestStatus' + ListPersistentRequests = 'ListPersistentRequests' + RemovePersistentRequest = 'RemovePersistentRequest' + ModifyPersistentRequest = 'ModifyPersistentRequest' + Shutdown = 'Shutdown' + + # node messages + NodeHello = 'NodeHello' + CloseConnectionDuplicateClientName = 'CloseConnectionDuplicateClientName' + Peer = 'Peer' + PeerNote = 'PeerNote' + EndListPeers = 'EndListPeers' + EndListPeerNotes = 'EndListPeerNotes' + PeerRemoved = 'PeerRemoved' + NodeData = 'NodeData' + ConfigData = 'ConfigData' # (since 1027) + TestDDAReply = 'TestDDAReply' # (since 1027) + TestDDAComplete = 'TestDDAComplete' # (since 1027) + SSKKeypair = 'SSKKeypair' + PersistentGet = 'PersistentGet' + PersistentPut = 'PersistentPut' + PersistentPutDir = 'PersistentPutDir' + URIGenerated = 'URIGenerated' + PutSuccessful = 'PutSuccessful' + PutFetchable = 'PutFetchable' + DataFound = 'DataFound' + AllData = 'AllData' + StartedCompression = 'StartedCompression' + FinishedCompression = 'FinishedCompression' + SimpleProgress = 'SimpleProgress' + EndListPersistentRequests = 'EndListPersistentRequests' + PersistentRequestRemoved = 'PersistentRequestRemoved' # (since 1016) + PersistentRequestModified = 'PersistentRequestModified' # (since 1016) + PutFailed = 'PutFailed' + GetFailed = 'GetFailed' + ProtocolError = 'ProtocolError' + IdentifierCollision = 'IdentifierCollision' + UnknownNodeIdentifier = 'UnknownNodeIdentifier' + UnknownPeerNoteType = 'UnknownPeerNoteType' + SubscribedUSKUpdate = 'SubscribedUSKUpdate' + + # client messages (internal use only) + ClientSocketTimeout = 0 + ClientSocketDied = 1 + + + def __init__(self, name, data=None, **params): + """ + @param name: messge name + @param data: data associated to the messge (not yet implemented) + @param params: {field-name: value, ...} of parameters of the message + @note: all params can be accessed as attributes of the class + """ + self.data = data + self.name = name + self.params = params + + + @classmethod + def bytesFromSocket(clss, socketObj, n): + """Reads n bytes from socket + @param socketObj: socket to read bytes from + @param n: (int) number of bytes to read + @return: (tuple) error-message or None, bytes read or None if an error occured + or no bytes could be read + """ + error = p = None + try: + p = socketObj.recv(n) + if not p: + p = None + raise socket.error('Socket shut down by node') + except socket.timeout, d: # no new messages in queue + error = clss(clss.ClientSocketTimeout) + except socket.error, d: + error = clss(clss.ClientSocketDied, Exception=socket.error, Details=d) + return error, p + + + @classmethod + def fromSocket(clss, socketObj): + """Reads a message from a socket + @param socketObj: socket to read a message from + @return: L{Message} next message from the socket. If the socket dies + unexpectedly a L{ClientSocketDied} message is returned containing the parameters + 'Exception' and 'Details'. If the socket times out a L{MessageClientSocketTimout} + message is returned. + """ + + msg = clss(None) + buf = [] + + #TODO: to buffer or not to buffer? + while True: + + # get next line from socket + error, p = clss.bytesFromSocket(socketObj, 1) + if error: + return error + + if p != '\n': + buf.append(p) + continue + #TODO: check if '\r\n' is allowed in freenet client protocol + else: + if buf[-1] == '\r': + del buf[-1] + + line = ''.join(buf) + buf = [] + if line == 'EndMessage': + break + + # first line == message name + if msg.name is None: + msg.name = line + + # get data member + elif line == 'Data': + remaining = int(msg.params['DataLength']) + msg.data = '' + while remaining > 0: + error, p = clss.bytesFromSocket(socketObj, remaining) + if error: + return error + remaining -= len(p) + msg.data += p + break + + # get next paramater + else: + head, sep, tail = line.partition('=') + msg.params[head] = tail + # TODO: errorchek params? + #if not sep: pass + + return msg + + + def get(self, name, default=None): + """Returns the message parameter 'name' or 'default' """ + return self.params.get(name, default) + + + def __getitem__(self, name): + """Returns the message parameter 'name' """ + return self.params[name] + + + def __setitem__(self, name, value): + """Sets the message parameter 'name' to 'value' """ + self.params[name] = value + + + def pprint(self): + """Returns the message as nicely formated human readable string""" + out = ['', '>>' + self.name, ] + for param, value in self.params.items(): + out.append('>> %s=%s' % (param, value)) + out.append('>>EndMessage') + return '\n'.join(out) + + + def send(self, socketObj): + """Dumps the message to a socket + @param socketObj: socket to dump the message to + """ + socketObj.sendall(self.toString()) + + + def toString(self): + """Returns the message as formated string ready to be send""" + + #TODO: just a guess, so maybe remove this check + if isinstance(self.name, (int, long)): + raise ValueError('You can not send client internal messages to the node') + out = [self.name, ] + for param, value in self.params.items(): + out.append('%s=%s' % (param, value)) + if self.data: + assert 'DataLength' in self.params, 'DataLength member required' + n = None + try: + n = int(self['DataLength']) + except ValueError: pass + assert n is not None, 'DataLength member must be an integer' + assert n == len(self.data), 'DataLength member must corrospond to lenght of data' + out.append('Data') + out.append(self.data) + else: + out.append('EndMessage\n') + return '\n'.join(out) + + + + #TODO: no idea how fcp handles strings as in <Peer volatile.status=CONNECTED> + # all I could find in the sources where these constants as in PEER_NODE_STATUS_CONNECTED + # in --> freenet/node/PeerManager.java + class PeerNodeStatus: + Connected = 1 + RoutingBackedOff = 2 + TooNew = 3 + TooOld = 4 + Disconnected = 5 + NeverConnected = 6 + Disabled = 7 + Bursting = 8 + Listening = 9 + ListenOnly = 10 + ClockProblem = 11 + ConnError = 12 + Disconnecting = 13 + + class PeerNoteType: + """All known peer note types""" + Private = '1' + + class Priorities: + """All priorities supported by the client""" + Maximum = '0' + Interactive = '1' + SemiInteractive = '2' + Updatable = '3' + Bulk = '4' + Prefetch = '5' + Minimum = '6' + + PriorityMin = Minimum + PriorityDefault = Bulk + + class ProtocolError(Exception): + """All protocol errors supported by the client""" + + def __init__(self, msg): + """ + @param msg: (Message) ProtocolError message or its parameters dict + """ + self.value = '%s (%s, %s)' % ( + msg.get('CodeDescription', 'Unknown error') , + msg['Code'], + msg.get('ExtraDescription', '...'), + ) + def __str__(self): return self.value + + ClientHelloMustBeFirst = '1' + NoLateClientHellos = '2' + MessageParseError = '3' + UriParseError = '4' + MissingField = '5' + ErrorParsingNumber = '6' + InvalidMessage = '7' + InvalidField = '8' + FileNotFound = '9' + DiskTargetExists = '10' + SameDirectoryExpected = '11' + CouldNotCreateFile = '12' + CouldNotWriteFile = '13' + CouldNotRenameFile = '14' + NoSuchIdentifier = '15' + NotSupported = '16' + InternalError = '17' + ShuttingDown = '18' + NoSuchNodeIdentifier = '19' # Unused since 995 + UrlParseError = '20' + ReferenceParseError = '21' + FileParseError = '22' + NotAFile = '23' + AccessDenied = '24' + DDADenied = '25' + CouldNotReadFile = '26' + ReferenceSignature = '27' + CanNotPeerWithSelf = '28' + PeerExists = '29' + OpennetDisabled = '30' + DarknetPeerOnly = '31' + + class SocketError(Exception): pass + class Verbosity: + Debug = logging.DEBUG + Info = logging.INFO + Warning = logging.WARNING + + def __init__(self, name='', connectionName=None, verbosity=Verbosity.Warning, - logMessages=LogMessages ): """ @param name: name of the client instance or '' (for debugging) @param conectionName: name of the connection @param verbosity: verbosity level for debugging - @param logMessages: LogMessages class containing message strings """ self._connectionName = connectionName self._ddaTmpFiles = [] self._log = logging.getLogger(name) - self._logMessages = logMessages - self._lock = thread.allocate_lock() self._socket = None self.setVerbosity(verbosity) @@ -756,7 +682,7 @@ """Closes the client @note: make shure to call close() when done with the client """ - self._log.info(self._logMessages.ClientClose) + self._log.info(self.LogMessages.ClientClose) if self._socket is not None: self._socket.close() self._socket = None @@ -764,8 +690,8 @@ # clean left over tmp files for fpath in self._ddaTmpFiles: saveRemoveFile(fpath) + - #TODO: an iterator would be nice to enshure Guis stay responsitive in the call def connect(self, host=DefaultFcpHost, port=DefaultFcpPort, repeat=20, timeout=0.5): """Establishes the connection to a freenet node @@ -776,7 +702,7 @@ @return: (Message) NodeHello if successful,None otherwise """ self._clientHello = None - self._log.info(self._logMessages.Connecting) + self._log.info(self.LogMessages.Connecting) # poll untill freenet responds timeElapsed = 0 @@ -792,7 +718,7 @@ except Exception, d: pass else: - self._log.info(self._logMessages.Connected) + self._log.info(self.LogMessages.Connected) # send ClientHello and wait for NodeHello #NOTE: thought I could leave ClientHelloing up to the caller @@ -800,26 +726,26 @@ # as expected when not doing so, the node disconnects. # So take it over here. self.sendMessage( - Message.ClientHello, - Name=self._connectionName if self._connectionName is not None else newIdentifier(), + self.Message.ClientHello, + Name=self._connectionName if self._connectionName is not None else self.newIdentifier(), ExpectedVersion=self.Version, ) while timeElapsed <= repeat: msg = self.next() - if msg.name == Message.ClientSocketTimeout: + if msg.name == self.Message.ClientSocketTimeout: timeElapsed += SocketTimeout - elif msg.name == Message.NodeHello: + elif msg.name == self.Message.NodeHello: return msg.params else: break break # continue polling - self._log.info(self._logMessages.ConnectionRetry) + self._log.info(self.LogMessages.ConnectionRetry) timeElapsed += timeout time.sleep(timeout) - self._log.info(self._logMessages.ConnectingFailed) + self._log.info(self.LogMessages.ConnectingFailed) return None @@ -829,18 +755,18 @@ @return: True if the message was handled, False otherwise """ - if msg.name == Message.ClientSocketTimeout: + if msg.name == self.Message.ClientSocketTimeout: return True - self._log.debug(self._logMessages.MessageReceived + msg.pprint()) + self._log.debug(self.LogMessages.MessageReceived + msg.pprint()) - if msg.name == Message.ProtocolError: + if msg.name == self.Message.ProtocolError: code = msg['Code'] - if code == ProtocolError.ShuttingDown: + if code == self.ProtocolError.ShuttingDown: self.close() self.EventShutdown(msg.params) return True - raise ProtocolError(msg) + raise self.ProtocolError(msg) #################################################### ## @@ -851,7 +777,7 @@ ## Have to handle this! ## #################################################### - elif msg.name == Message.TestDDAReply: + elif msg.name == self.Message.TestDDAReply: fpathWrite = msg.params.get('WriteFilename', None) fpathRead = msg.params.get('ReadFilename', None) readContent = '' @@ -868,38 +794,51 @@ readContent = '' self.sendMessage( - Message.TestDDAResponse, + self.Message.TestDDAResponse, Directory=msg['Directory'], ReadContent=readContent, ) return True - - - elif msg.name == Message.TestDDAComplete: + + elif msg.name == self.Message.TestDDAComplete: # clean tmp files for fpath in self._ddaTmpFiles: saveRemoveFile(fpath) self._ddaTmpFiles = [] + self.EventTestDDAComplete(msg.params) return True #################################################### ## + ## Config related messages + ## + #################################################### + elif msg.name == self.Message.ConfigData: + self.EventConfigData(msg.params) + return True + + elif msg.name == self.Message.NodeData: + self.EventNodeData(msg.params) + return True + + #################################################### + ## ## Peer related messages ## #################################################### - elif msg.name == Message.EndListPeers: + elif msg.name == self.Message.EndListPeers: self.EventEndListPeers(msg.params) return True - elif msg.name == Message.EndListPeerNotes: + elif msg.name == self.Message.EndListPeerNotes: self.EventEndListPeerNotes(msg.params) return True - elif msg.name == Message.Peer: + elif msg.name == self.Message.Peer: self.EventPeer(msg.params) return True - elif msg.name == Message.PeerNote: + elif msg.name == self.Message.PeerNote: note = msg.get('NoteText', '') if note: note = base64.decodestring(note) @@ -907,11 +846,11 @@ self.EventPeerNote(msg.params, note) return True - elif msg.name == Message.PeerRemoved: + elif msg.name == self.Message.PeerRemoved: self.EventPeerRemoved(msg.params) return True - elif msg.name == Message.UnknownNodeIdentifier: + elif msg.name == self.Message.UnknownNodeIdentifier: self.EventUnknownIdentifier(msg.params) return True @@ -920,49 +859,44 @@ ## Get related messages ## #################################################### - elif msg.name == Message.DataFound: - if msg['Identifier'].startswith(IdentifierPrefix.FileInfo): - self.EventFileInfo(msg.params) + elif msg.name == self.Message.DataFound: + if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo): + self.EventClientGetInfo(msg.params) return True - #TODO: self.EventDataFound(msg.params) return True - - elif msg.name == Message.GetFailed: + elif msg.name == self.Message.GetFailed: code = msg['Code'] - if code == FetchError.TooBig: - if msg['Identifier'].startswith(IdentifierPrefix.FileInfo): + if code == self.FetchError.TooBig: + if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo): params = { 'Metadata.ContentType': msg.get('ExpectedMetadata.ContentType', ''), 'DataLength': msg.get('ExpectedDataLength', '') } - self.EventFileInfo(params) + self.EventClientGetInfo(params) return True self.EventGetFailed(msg.params) return True - - - elif msg.name == Message.SimpleProgress: - if msg['Identifier'].startswith(IdentifierPrefix.FileInfo): - self.EventFileInfoProgress(msg.params) + + elif msg.name == self.Message.SimpleProgress: + if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo): + self.EventClientGetInfoProgress(msg.params) else: self.EventSimpleProgress(msg.params) return True - - elif msg.name == Message.IdentifierCollision: + elif msg.name == self.Message.IdentifierCollision: self.EventIdentifierCollision(msg.params) return True - - elif msg.name == Message.PersistentRequestModified: + elif msg.name == self.Message.PersistentRequestModified: self.EventPersistentRequestModified(msg.params) return True - elif msg.name == Message.PersistentRequestRemoved: + elif msg.name == self.Message.PersistentRequestRemoved: self.EventPersistentRequestRemoved(msg.params) return True @@ -971,15 +905,41 @@ ## Others ## #################################################### - elif msg.name == Message.SSKKeypair: + elif msg.name == self.Message.SSKKeypair: self.EventSSKKeypair(msg.params) return True + - - - ## default + ## default ## return False + + def setVerbosity(self, verbosity): + """Sets the verbosity level of the client + @note: see L{Verbosity} + """ + self._log.setLevel(verbosity) + + + def startFreenet(self, cmdline): + """Starts freenet + @param cmdline: commandline to start freenet (like '/freenet/run.sh start' or 'c:\freenet\start.bat') + @return: (string) whatever freenet returns + """ + #TODO: on windows it may be necessary to hide the command window + p = subprocess.Popen( + args=cmdline, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + stdout, stderr = p.communicate() + return stdout + + + def verbosity(self): + """Returns the current verbosity level of the client""" + return self._log.level ######################################################### ## ## @@ -989,8 +949,8 @@ """Pumps the next message waiting @note: use this method instead of run() to run the client step by step """ - msg = Message.fromSocket(self._socket) - if msg.name == Message.ClientSocketDied: + msg = self.Message.fromSocket(self._socket) + if msg.name == self.Message.ClientSocketDied: self.EventSocketDied(msg['Exception'], msg['Details']) raise SocketError(msg['Details']) self.handleMessage(msg) @@ -1007,7 +967,7 @@ If an error handler is passed to the client it is called emidiately before the error is raised. """ - return self.sendMessageEx(Message(name, data=data, **params)) + return self.sendMessageEx(self.Message(name, data=data, **params)) def sendMessageEx(self, msg): @@ -1018,26 +978,16 @@ If an error handler is passed to the client it is called emidiately before the error is raised. """ - self._log.debug(self._logMessages.MessageSend + msg.pprint()) + self._log.debug(self.LogMessages.MessageSend + msg.pprint()) try: msg.send(self._socket) except socket.error, d: - self._log.info(self._logMessages.SocketDied) + self._log.info(self.LogMessages.SocketDied) self.close() self.EventSocketDied(socket.error, d) raise SocketError(d) return msg - - - def setLogMessages(self, logMessages): - """""" - self._logMessages = logMessages - - - def setVerbosity(self, verbosity): - """""" - self._log.setLevel(verbosity) - + ######################################################### ## ## @@ -1049,15 +999,56 @@ @return: (str) 'true' or 'false' """ return self.FcpTrue if pythonBool else self.FcpFalse - + + def newIdentifier(self, prefix=None): + """Returns a new unique identifier + @return: (str) uuid + """ + if prefix: + return prefix + str(uuid.uuid4()) + return str(uuid.uuid4()) + + def pythonBool(self, fcpBool): """Converts a fcp bool to a python bool @param pythonBool: 'true' or 'false' @return: (bool) True or False """ return fcpBool == self.FcpTrue + + ######################################################## + ## + ## Config related methods + ## + ######################################################## + #TODO: WithDefault never returns defaults + def getConfig(self): + """ + @event: ConfigData(event, params) + """ + self.sendMessage( + self.Message.GetConfig, + WithCurrent=self.FcpTrue, + WithDefault=self.FcpTrue, + WithExpertFlag=self.FcpTrue, + WithForceWriteFlag=self.FcpTrue, + WithShortDescription=self.FcpTrue, + WithLongDescription=self.FcpTrue, + ) + + def getNode(self): + """ + @event: NodeData(event, params) + """ + self.sendMessage( + self.Message.GetNode, + WithPrivate==self.FcpTrue, + WithVlatile==self.FcpTrue, + GiveOpennetRef==self.FcpTrue, + ) + ######################################################## ## ## Peer related methods @@ -1065,28 +1056,35 @@ ######################################################## def listPeer(self, identifier): self.jobClient.sendMessage( - Message.ListPeer, + self.Message.ListPeer, NodeIdentifier=identifier, ) def listPeerNotes(self, identifier): + """Lists all text notes associated to a peer + @param identifier: peer as returned in a call to L{peerList} + @event: EventListPeerNotes(event). + @event: EventListPeerNote(event, note). + @event: EventEndListPeerNotes(event). """ - @param identifier: identifier of the peer to list notes for - """ self.sendMessage( - Message.ListPeerNotes, + self.Message.ListPeerNotes, NodeIdentifier=identifier ) def listPeers(self, withMetaData=True, withVolantile=True): - """ + """Lists all peers of the node @param withMetaData: include meta data for each peer? @param withVolantile: include volantile data for each peer? + + @event: EventListPeers(event). + @event: EvenPeer(event, peer). + @event: EventEndListPeers(event). """ self.sendMessage( - Message.ListPeers, + self.Message.ListPeers, WithMetadata=self.fcpBool(withMetaData), WithVolatile=self.fcpBool(withVolantile), ) @@ -1094,7 +1092,7 @@ def modifyPeer(self, identifier, allowLocalAddresses=None, isDisabled=None, isListenOnly=None): msg = Message( - Message.ModifyPeer, + self.Message.ModifyPeer, NodeIdentifier=identifier, ) if allowLocalAddresses is not None: @@ -1109,17 +1107,17 @@ def modifyPeerNote(self, identifier, note): self.sendMessage( - Message.ModifyPeerNote, + self.Message.ModifyPeerNote, NodeIdentifier=identifier, #NOTE: currently fcp supports only this one type - PeerNoteType=PeerNoteType.Private, + PeerNoteType=self.PeerNoteType.Private, NoteText=note ) def removePeer(self, identifier): self.sendMessage( - Message.RemovePeer, + self.Message.RemovePeer, NodeIdentifier=identifier, ) @@ -1128,18 +1126,46 @@ ## get / put related methods ## ########################################################## - def fileInfo(self, uri, **params): + #TODO: not complete yet + def clientGetFile(self, uri, filename): + """ + """ + identifier = self.new_identifier() + msg = self.Message( + self.Message.ClientGet, + IgnoreDS='false', + DSOnly='false', + URI=uri, + Identifier=identifier, + Verbosity='1', + ReturnType='disk', + #MaxSize=client_get_info['Size'], + #MaxTempSize=client_get_info['Size'], + #MaxRetries='-1', + #PriorityClass='4', + Persistence='forever', + #ClientToken=identifier, + Global='false', + #BinaryBlob='false', + Filename=filename, + ) + self.sendMessageEx(msg) + + return identifier + + + def clientGetInfo(self, uri, **params): """Requests info about a file @param uri: uri of the file to request info about - @event: FileInfo(event, params). If success, params will contain a key 'Metadata.ContentType' + @event: clientGetInfo(event, params). If success, params will contain a key 'Metadata.ContentType' and 'DataLength'. Both may be '' (empty string) - @event: FileInfoProgress(event, params). Triggered instead of EventSimpleProgress + @event: clientGetInfoProgress(event, params). Triggered instead of EventSimpleProgress @note: for other events see: L{clientGet} - @return: (str) identifier of the request + @return: (str) request identifier """ - identifier = IdentifierPrefix.FileInfo + newIdentifier() + identifier = self.IdentifierPrefix.ClientGetInfo + self.newIdentifier() self.sendMessage( - Message.ClientGet, + self.Message.ClientGet, Identifier=identifier, URI=uri, # suggested by Mathew Toseland to use about 32k for mimeType requests @@ -1152,199 +1178,152 @@ return identifier - ######################################################### - ## how to tackle TestDDA? - ## - ## best idea hear so far is to wait for ProtocolError 25 Test DDA denied (or PersistantGet) - ## and reinsert the job if necessary after TestDDA completion. - ## - ## Problem is, how to wait for a message without flooding the caller. Basic idea of the client - ## is to enshure a Gui can stay responsitive by letting the caller decide when to process the - ## next message. So waiting would require to buffer messages and watch messages carefuly - ## as they flood in. - ## - ## If we do not wait, the caller may flood us with download requests, I fear, faster than - ## the node and we are able to go get the error and through the TestDDA drill. Have to - ## do some tests to see how the node reacts. - ## - ## easiest approach would be to let the caller test a directory explicitely when HE thinks - ## it might be necessary. But then this code will hang around forever with an already - ## assigned bug report [https://bugs.freenetproject.org/view.php?id=1753] suggesting - ## much easier processing to test DDA (DDA Challenge) - ## - ## - ## so.. maybe best is to lurker around a while and keep an eye on the tracker - ## - ## - ## below is just some old boilerplate code.. to be removed sooner or later - ######################################################### - def testWriteAccess(self, directory): - canRead, canWrite = False, False - result = self._jobs.get('RegisteredDirectories', None) - if result is not None: - canRead, canWrite = result - if not canWrite: - job = JobTestDDA(directory, read=canRead, write=True) - self.addJob(job, synchron=True) - canWrite = job.jobResult[1] - self._jobs['RegisteredDirectories'] = (canRead, canWrite) - return canWrite - - def testReadAccess(self, directory): - canRead, canWrite = False, False - result = self._jobs.get('RegisteredDirectories', None) - if result is not None: - canRead, canWrite = result - if not canRead: - job = JobTestDDA(directory, read=True, write=canWrite) - self.addJob(job, synchron=True) - canRead = job.jobResult[0] - self._jobs['RegisteredDirectories'] = (canRead, canWrite) - return canRead - - - def downloadFile(self, directory, job): - if not os.path.isdir(directory): - raise IOError('No such directory') - - self._jobs['PendingJobs'].append(job) - try: - result = self.testWriteAccess(directory) - if result: - self.addJob(job) - finally: - self._jobs['PendingJobs'].remove(job) - return result + def testDDA(self, directory, wantReadDirectory=None, wantWriteDirectory=None): + """Tests a directory for read / write access + @param directory: directory to test + @param read: if not Note, test directory for read access + @param write: if not Note, test directory for write access + @event: TestDDAComplete(event, params) is triggered on test completion - - def uploadFile(self, directory, job): - if not os.path.isdir(directory): - raise IOError('No such directory') - - self._jobs['PendingJobs'].append(job) - try: - result = self.testReadAccess(directory) - if result: - self.addJob(job) - finally: - self._jobs['PendingJobs'].remove(job) - return result - - - ################################################# + @note: you have to test a directory if it can bew written to before downloading files ito it + and a directory for read access before uploading content from it + @note: the node does not like both parameters being False and will respond with a protocol error in this + case. Take care of that. + """ + msg = self.Message( + self.Message.TestDDARequest, + Directory=directory, + ) + if wantReadDirectory is not None: + msg['WantReadDirectory'] = self.fcpBool(wantReadDirectory) + if wantWriteDirectory is not None: + msg['WantWriteDirectory'] = self.fcpBool(wantWriteDirectory) + self.sendMessageEx(msg) + ########################################################## ## - ## public methods + ## others ## - ################################################# - def peerList(self, synchron=False): - """Lists all peers of the node - @param synchron: if True, waits untill the call is completed, if False returns emidiately - @return: (list) of peers in a synchron, always None in an asynchron call - - @event: EventListPeers(event). - @event: EventListNextPeer(event, peer). - @event: EventEndListPeers(event). + ########################################################## + def generateSSK(self): """ - job = JobListPeers(self) - self.jobAdd(job, synchron=synchron) - return job.jobResult - - - def peerNotes(self, peer, synchron=False): - """Lists all text notes associated to a peer - @param peer: peer as returned in a call to L{peerList} - @param synchron: if True, waits untill the call is completed, if False returns emidiately - @return: (list) of notes in a synchron, always None in an asynchron call - - @event: EventListPeerNotes(event). - @event: EventListNextPeerNote(event, note). - @event: EventEndListPeerNotes(event). + @event: SSKKeypair(event, params), triggered when the request is complete + @return: identifier of the request """ - if self.pythonBool(peer['opennet']): # opennet peers do not have any notes associated - return [] - job = JobListPeerNotes(self, peer['identity']) - self.jobAdd(job, synchron=synchron) - return job.jobResult - + identifier = self.newIdentifier() + self.sendMessage( + self.Message.GenerateSSK, + Identifier=identifier + ) + return identifier + #***************************************************************************** # #***************************************************************************** if __name__ == '__main__': - c = FcpClient(name='test', verbosity=Verbosity.Warning) + c = FcpClient(name='test', verbosity=FcpClient.Verbosity.Debug) nodeHello = c.connect() if nodeHello is not None: + - - - def foo(): - job1 = JobClientHello(c) - c.jobAdd(job1) - - c.run() - print '---------------------------' - print job1.jobResult - print '---------------------------' + def testLateClientHello(): + c.sendMessage( + c.Message.ClientHello, + Name=c.newIdentifier(), + ExpectedVersion=c.Version, + ) + for i in xrange(2): + c.next() + # should raise - #foo() + #testLateClientHello() - #ModifyPeer not ok + + def testGetConfig(): + + def getBuddyValue(params, settingName, buddyPrefix): + buddyName = buddyPrefix + '.' + settingName + value = params.get(buddyName, '') + return (buddyPrefix, value) + + def cb(event, params): + + settings = [ i for i in params if i.startswith('current.')] + settings.sort() + for setting in settings: + + configTree, sep, settingName = setting.partition('.') + value = params[setting] + print '%s=%s' % (settingName, value) + print '%s=%s' % getBuddyValue(params, settingName, 'expertFlag') + print '%s=%s' % getBuddyValue(params, settingName, 'forceWriteFlag') + print '%s=%s' % getBuddyValue(params, settingName, 'shortDescription') + prefix, value = getBuddyValue(params, settingName, 'longDescription') + value = value.replace('. ', '.\n') + value = value.replace('? ', '.\n') + print '%s=%s' % (prefix, value) + print + + c.EventConfigData += cb + oldVerbosity = c.verbosity() + ##c.setVerbosity(c.Verbosity.Warning) + + print '\n>> Requesting config\n' + c.getConfig() + for i in xrange(1): + c.next() + + c.setVerbosity(oldVerbosity) + + #testGetConfig() - #RemovePeer not ok - #ModifyPeerNote ok - #ListPeer not ok + def testGenerateSSK(): + def cb(event, params): + print params + + c.EventSSKKeypair += cb + c.generateSSK() + for i in xrange(1): + c.next() + #testGenerateSSK() - - def foo(): - job = JobListPeer(c, '123456') - c.jobAdd(job, synchron=True) - print job.jobResult - #foo() - - - def foo(): - job = JobGenerateSSK(c) - c.jobAdd(job, synchron=True) - print job.jobResult - #foo() - - - - def foo(): + def testTestDDA(): + def cb(event, params): + print params + + c.EventTestDDAComplete += cb d = os.path.dirname(os.path.abspath(__file__)) - job2 = JobTestDDA(c, d) - c.jobAdd(job2) - c.run() - print '---------------------------' - print job2.jobResult - print '---------------------------' - #foo() + c.testDDA(d, True, True) + for i in xrange(4): + c.next() + + #testTestDDA() - def foo(): + + def testListPeerNotes(): def cb(event, params): - #print params.get('opennet', 'true'), c.pythonBool(params.get('Opennet', 'true')), params['identity'] if params['opennet'] == c.FcpFalse: c.listPeerNotes(params['identity']) c.EventPeer += cb - - c.listPeers() - for i in xrange(80): + for i in xrange(100): c.next() - #foo() + #testListPeerNotes() - def foo(): - #job = JobGetFileInfo(c, 'USK@IK8rVHfjCz1i1IZwgyafsPNDGODCk~EtBgsKWHmPKoQ,FeDoFZ8YeUsU0vo1-ZI~GRhZjeyXyaGhn-xQkIoGpak,AQACAAE/FreeMulET/15/FreeMulET-0.6.29-lib-jar.zip') - identifier = c.fileInfo('CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg') + def testClientGetInfo(): + def cb(event, params): + print params + + c.EventClientGetInfo += cb + identifier = c.clientGetInfo('CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg') for i in xrange(20): c.next() - - #foo() + #testClientGetInfo() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |