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.
|