Thread: SF.net SVN: fclient: [4] trunk
Status: Pre-Alpha
Brought to you by:
jurner
|
From: <ju...@us...> - 2007-10-15 17:42:21
|
Revision: 4
http://fclient.svn.sourceforge.net/fclient/?rev=4&view=rev
Author: jurner
Date: 2007-10-15 10:42:24 -0700 (Mon, 15 Oct 2007)
Log Message:
-----------
started implementing fcp20 client
Added Paths:
-----------
trunk/fclient/
trunk/fclient/__init__.py
trunk/fclient/fclient_lib/
trunk/fclient/fclient_lib/__init__.py
trunk/fclient/fclient_lib/fcp/
trunk/fclient/fclient_lib/fcp/__init__.py
trunk/fclient/fclient_lib/fcp/fcp20.py
Added: trunk/fclient/__init__.py
===================================================================
--- trunk/fclient/__init__.py (rev 0)
+++ trunk/fclient/__init__.py 2007-10-15 17:42:24 UTC (rev 4)
@@ -0,0 +1 @@
+
Added: trunk/fclient/fclient_lib/__init__.py
===================================================================
--- trunk/fclient/fclient_lib/__init__.py (rev 0)
+++ trunk/fclient/fclient_lib/__init__.py 2007-10-15 17:42:24 UTC (rev 4)
@@ -0,0 +1 @@
+
Added: trunk/fclient/fclient_lib/fcp/__init__.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/__init__.py (rev 0)
+++ trunk/fclient/fclient_lib/fcp/__init__.py 2007-10-15 17:42:24 UTC (rev 4)
@@ -0,0 +1 @@
+
Added: trunk/fclient/fclient_lib/fcp/fcp20.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp20.py (rev 0)
+++ trunk/fclient/fclient_lib/fcp/fcp20.py 2007-10-15 17:42:24 UTC (rev 4)
@@ -0,0 +1,715 @@
+
+import os
+import socket
+import time
+import thread
+import uuid
+#**************************************************************
+# consts
+#**************************************************************
+DefaultFcpHost = os.environ.get('FCP_HOST', '127.0.0.1').strip()
+DefaultFcpPort = 9481
+try:
+ DefaultFcpPort = int(os.environ.get('FCP_PORT', '').strip())
+except: pass
+SocketTimeout = 0.1
+KeyTypes = ('SSK@', 'KSK@', 'CHK@', 'USK@', 'SVK@')
+
+class JobIdentifiers:
+ # fixed job identifiers
+ # note that the client can only handle one job of these at a time
+ ClientHello = 'ClientHello'
+ ListPeers = 'ListPeers'
+
+class Messages:
+
+ # 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'
+
+
+class Priorities:
+ Maximum = 0
+ Interactive = 1
+ SemiInteractive = 2
+ Updatable = 3
+ Bulk = 4
+ Prefetch = 5
+ Minimum = 6
+
+ PriorityMin = Minimum
+ PriorityDefault = Bulk
+
+
+# errors
+
+class FetchErrors:
+ 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 InsertErrors:
+ InvalidUri = '1'
+ BucketError = '2'
+ InternalError = '3'
+ RejectedOverload = '4'
+ RouteNotFound = '5'
+ FatalErrorInBlocks = '6'
+ TooManyRetriesInBlock = '7'
+ RouteReallyNotFound = '8'
+ Collision = '9'
+ Canceled = '10'
+
+
+class ProtocolErrors:
+ 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'
+ DarknetOnly = '31'
+
+#**********************************************************************
+# functions
+#**********************************************************************
+def newIdentifier():
+ return str(uuid.uuid4())
+
+def saveReadFile(fpath):
+ """Reads contents of a file in the savest manner possible
+ @param fpath: file to write
+ @return: contents of the file if successful, None otherwise
+ """
+ read = None
+ try:
+ fp = open(fpath, 'rb')
+ except: pass
+ else:
+ try:
+ read = fp.read()
+ except: pass
+ fp.close()
+ return read
+
+
+def saveWriteFile(fpath, data):
+ """Writes data to a file i the savest manner possible
+ @param fpath: file to write
+ @param data: data to write to file
+ @return: True if successful, False otherwise
+ """
+ written = False
+ try:
+ fp = open(fpath, 'wb')
+ except: pass
+ else:
+ try:
+ fp.write(data)
+ written = True
+ except:
+ fp.Close()
+ else:
+ fp.close()
+ return written
+
+def fcpBool(pythonBool):
+ """Converts a python bool to a fcp bool
+ @param pythonBool: (bool)
+ @return: (str) 'true' or 'false'
+ """
+ if pythonBool:
+ return 'true'
+ return 'false'
+
+def pythonBool(fcpBool):
+ """Converts a fcp bool to a python bool
+ @param pythonBool: 'true' or 'false'
+ @return: (bool) True or False
+ """
+ return fcpBool == 'true'
+
+#**********************************************************************
+# classes
+#**********************************************************************
+class FcpSocketError(Exception): pass
+class Message(object):
+ """Class wrapping a freenet message"""
+
+ Name = 'UMessage'
+
+
+ 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
+
+
+ def toString(self):
+ """Returns a string with the formated message, ready to be send"""
+
+ # TODO: "Data" not yet implemented
+ out = [self.name, ]
+ for param, value in self.params.items():
+ out.append('%s=%s' % (param, value))
+ out.append('EndMessage\n')
+ return '\n'.join(out)
+
+
+ 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')
+ out.append('')
+ return '\n'.join(out)
+
+ def __getitem__(self, name):
+ """Returns the message parameter 'name' """
+ return self.params[name]
+
+ def get(self, name, default=None):
+ """Returns the message parameter 'name' or 'default' """
+ return self.params.get(name, default)
+
+ def __setitem__(self, name, value):
+ """Sets the message parameter 'name' to 'value' """
+ self.params[name] = value
+
+
+class MessageSocketTimeout(Message):
+
+ Name = 'USocketTimeout'
+
+ def __init__(self):
+ Message.__init__(self, self.Name)
+
+#**************************************************************************
+# jobs
+#**************************************************************************
+#TODO: do somrthing that this class does not lock the queue
+class JobBase(object):
+ """Base class for jobs"""
+
+ _fcp_auto_remove_ = True
+
+ def __init__(self, fcpClient, identifier, message):
+
+ self.fcpClient = fcpClient
+ self.fcpIdentifier = identifier
+ self.fcpMessage = message
+ self.fcpResult = None
+ self.fcpTime = 0
+
+ def start(self):
+ self.fcpTime = time.time()
+ self.fcpClient.sendMessageEx(self.fcpMessage)
+
+ def stop(self, result):
+ self.fcpTime = time.time() - self.fcpTime
+ self.fcpResult = result
+
+
+class JobNodeHello(JobBase):
+
+ _fcp_auto_remove_ = True
+
+ def __init__(self, fcpClient, expectedVersion='2.0'):
+ message = Message(
+ Messages.ClientHello,
+ Name=newIdentifier(),
+ ExpectedVersion=expectedVersion,
+ )
+ JobBase.__init__(self, fcpClient, JobIdentifiers.ClientHello, message)
+
+
+
+class JobListPeers(JobBase):
+
+ _fcp_auto_remove_ = True
+
+ def __init__(self, fcpClient, withMetaData=False, withVolantile=False):
+ message = Message(
+ Messages.ListPeers,
+ WithMetadata='true' if withMetaData else 'false',
+ WithVolatile='true' if withVolantile else 'false',
+ )
+ JobBase.__init__(self, fcpClient, JobIdentifiers.ListPeers, message)
+
+
+ def handlePeer(self,msg):
+ pass
+
+
+
+class JobFileInfo(JobBase):
+
+ _fcp_auto_remove_ = False
+
+ def __init__(self, fcpClient, uri, **params):
+ """
+ @param fcpClient: FcpClient() instance
+ @param uri: uri of the file to retrieve info for
+ @param params: additional parameters:
+ IgnoreDS='true' / 'false'
+ DSOnly='true' / 'false'
+ MaxRetries=-1 ...N
+ PriorityClass=Priority*
+
+ """
+ identifier = newIdentifier()
+ message = Message(
+ Messages.ClientGet,
+ Identifier=identifier,
+ URI=uri,
+ MaxSize='ase0',
+ ReturnType='none',
+ Verbosity='1',
+ **params
+ )
+ JobBase.__init__(self, fcpClient, identifier, message)
+
+
+ def handleProgress(self, msg):
+ pass
+
+
+ def stop(self, msg):
+ JobBase.stop(self, msg)
+ error = result = None
+ if msg.name == Messages.GetFailed:
+ if msg['Code'] == FetchErrors.TooBig:
+ result = (
+ msg.get('ExpectedMetadata.ContentType', ''),
+ msg.get('ExpectedDataLength', '')
+ )
+ else:
+ error, result = msg['Code'], msg
+
+ elif msg.name == Messages.DataFound:
+ result = (
+ msg.get('Metadata.ContentType', ''),
+ msg.get('DataLength', '')
+ )
+
+ elif msg.name == Messages.ProtocolError:
+ error, result = msg['Code'], msg
+
+ else:
+ raise ValueError('Unhandled message: %s' % msg.name)
+
+ self.fcpResult = error, result
+
+
+#TODO: handle case where directories are registered multiple times
+class JobTestDDA(JobBase):
+
+ _fcp_auto_remove_ = False
+
+ def __init__(self, fcpClient, directory, read=True, write=True):
+ message = Message(
+ Messages.TestDDARequest,
+ Directory=directory,
+ WantReadDirectory=fcpBool(read),
+ WantWriteDirectory=fcpBool(write),
+ )
+ JobBase.__init__(self, fcpClient, directory, message)
+ self.fcpTmpFile = None
+
+
+ def handleTestDDAReply(self, msg):
+ fpathWrite = msg.params.get('WriteFilename', None)
+ fpathRead = msg.params.get('ReadFilename', None)
+ readContent = ''
+ if fpathWrite is not None:
+ written = saveWriteFile(fpathWrite, msg['ContentToWrite'])
+ if not written:
+ if os.path.isfile(fpathWrite):
+ os.remove(fpathWrite)
+ else:
+ self.fcpTmpFile = fpathWrite
+
+ if fpathRead is not None:
+ readContent = saveReadFile(fpathRead)
+ if readContent is None:
+ readContent = ''
+
+ self.fcpClient.sendMessage(
+ Messages.TestDDAResponse,
+ Directory=msg['Directory'],
+ ReadContent=readContent,
+ )
+
+ def stop(self, msg):
+ JobBase.stop(self, msg)
+ if self.fcpTmpFile is not None:
+ if os.path.isfile(self.fcpTmpFile):
+ os.remove(self.fcpTmpFile)
+
+#**************************************************************************
+# fcp client
+#**************************************************************************
+class FcpClient(object):
+
+ def __init__(self):
+
+ self._isConnected = False
+ self._jobs = {
+ 'all': {},
+ 'pending': [],
+ 'running': [],
+ 'complete': [],
+ }
+ self._lock = thread.allocate_lock()
+ self._socket = None
+
+
+
+ def close(self):
+ if self._socket is not None:
+ self._socket.close()
+ self._socket = None
+
+
+ def connect(self, host=DefaultFcpHost, port=DefaultFcpPort, repeat=20, timeout=0.5):
+ # poll untill freenet responds
+ time_elapsed = 0
+ while time_elapsed <= repeat:
+
+ # try to Connect socket
+ self.close()
+ self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._socket.settimeout(SocketTimeout)
+ try:
+ self._socket.connect((host, port))
+ except Exception, d:
+ pass
+ else:
+ #self._isConnected = True
+ return True
+
+ # continue polling
+ time_elapsed += timeout
+ time.sleep(timeout)
+
+ return False
+
+
+ #def __nonzero__(self):
+ # return self._isConnected
+
+ def addJob(self, job):
+ self._lock.acquire(True)
+ try:
+ if job.fcpIdentifier in self._jobs['all']:
+ raise ValueError('Duplicate job: %r' % job.identifier)
+ self._jobs['all'][job.fcpIdentifier] = job
+ self._jobs['running'].append(job)
+ finally:
+ self._lock.release()
+ job.start()
+
+ def finishJob(self, identifier, msg):
+ self._lock.acquire(True)
+ try:
+ job = self._jobs['all'].get(identifier, None)
+ if job is not None:
+ self._jobs['running'].remove(job)
+ if job._fcp_auto_remove_:
+ del self._jobs['all'][identifier]
+ else:
+ self._jobs['complete'].append(job)
+ finally:
+ self._lock.release()
+
+ if job is None:
+ raise ValueError('No such job: %r' % identifier)
+ job.stop(msg)
+
+
+ def notifyJob(self, identifier, handler, msg):
+ self._lock.acquire(True)
+ try:
+ job = self._jobs['all'].get(identifier, None)
+ finally:
+ self._lock.release()
+ if job is None:
+ raise ValueError('No such job: %r' % identifier)
+ getattr(job, handler)(msg)
+
+
+ def run(self):
+
+ # TODO:
+ # x. push pending jobs
+ # x. on error stop this thingy
+
+ n = 0
+ while True:
+ if not self._lock.acquire(False):
+ continue
+
+ try:
+ if not self._jobs['pending'] and not self._jobs['running']:
+ break
+ finally:
+ self._lock.release()
+
+ msg = self.readMessage()
+ self.handleMessage(msg)
+
+
+ n += 1
+ if n > 50: break
+
+
+ def next(self):
+ msg = self.readMessage()
+ self.handleMessage(msg)
+
+
+ def handleMessage(self, msg):
+
+ print msg.pprint()
+
+ if msg.name == Messages.NodeHello:
+ #connectionIdentifier = msg['ConnectionIdentifier']
+ self.finishJob(JobIdentifiers.ClientHello, msg)
+
+ elif msg.name == Messages.ProtocolError:
+ code = msg['Code']
+
+ if code == ProtocolErrors.NoLateClientHellos:
+ self.finishJob(JobIdentifiers.ClientHello, msg)
+
+ else:
+ identifier = msg.get('Identifier', None)
+ if identifier is None:
+ pass # raise ???
+ else:
+ self.finishJob(identifier, msg)
+
+ elif msg.name == Messages.Peer:
+ self.notifyJob(JobIdentifiers.ListPeers, 'handlePeer', msg)
+
+ elif msg.name == Messages.EndListPeers:
+ self.finishJob(IdentifierListPeers, msg)
+
+ elif msg.name == Messages.GetFailed:
+ self.finishJob(msg['Identifier'], msg)
+
+ elif msg.name == Messages.SimpleProgress:
+ self.notifyJob(msg['Identifier'], 'handleProgress', msg)
+
+ elif msg.name == Messages.TestDDAReply:
+ self.notifyJob(msg['Directory'], 'handleTestDDAReply', msg)
+
+ elif msg.name == Messages.TestDDAComplete:
+ self.finishJob(msg['Directory'], msg)
+
+ elif msg.name == Messages.IdentifierCollision:
+ pass
+
+
+ def readMessage(self):
+ """Reads the next message directly from the socket and dispatches it
+ @return: valid or invalid Message()
+ """
+ msg = Message(None)
+ buf = []
+ while True:
+
+ try:
+ p = self._socket.recv(1)
+ if not p: raise ValueError('Socket is dead')
+ except socket.timeout, d: # no new messages in queue
+ msg = MessageSocketTimeout()
+ break
+ except Exception, d:
+ raise FcpSocketError(d) #!!
+
+ if p == '\r': # ignore
+ continue
+
+ if p != '\n':
+ buf.append(p)
+ continue
+
+ line = ''.join(buf)
+ if line in ('End', "EndMessage"):
+ break
+ buf = []
+
+ if msg.name is None:
+ msg.name = line
+ elif line == 'Data':
+ n = int(msg.params['DataLength'])
+ try:
+ msg.data = self._socket.recv(n)
+ except Exception, d:
+ raise FcpSocketError(d) #!!
+
+ else:
+ head, sep, tail = line.partition('=')
+ msg.params[head] = tail
+ if not sep:
+ # TODO: chek for invalid messages or not
+ pass
+
+ return msg
+
+
+
+ def sendMessage(self, name, data=None, **params):
+ """Sends a message to freenet
+ @param name: name of the message to send
+ @param data: data to atatch to the message
+ @param params: {para-name: param-calue, ...} of parameters to pass along
+ with the message (see freenet protocol)
+ """
+ return self.sendMessageEx(Message(name, data=data, **params))
+
+
+ def sendMessageEx(self, msg):
+ """Sends a message to freenet
+ @param msg: (Message) message to send
+ @return: Message
+ """
+ #self.log.info('SendMessage\n' + msg.pprint())
+ rawMsg = msg.toString()
+ try:
+ self._socket.sendall(rawMsg)
+ except Exception, d:
+ raise FcpSocketError(d)
+ #TODO: allow for an error handler to handle
+ return msg
+
+#*****************************************************************************
+#
+#*****************************************************************************
+if __name__ == '__main__':
+ c = FcpClient()
+ if c.connect():
+ job1 = JobNodeHello(c)
+ c.addJob(job1)
+
+ c.run()
+ print '---------------------------'
+ print job1.fcpResult.pprint()
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <ju...@us...> - 2008-01-27 02:16:50
|
Revision: 79
http://fclient.svn.sourceforge.net/fclient/?rev=79&view=rev
Author: jurner
Date: 2008-01-26 18:16:50 -0800 (Sat, 26 Jan 2008)
Log Message:
-----------
added sanbox with fcp rewrite
Added Paths:
-----------
trunk/sandbox/
trunk/sandbox/fcp/
trunk/sandbox/fcp/001.py
trunk/sandbox/fcp/LICENCE.MIT
trunk/sandbox/fcp/README
trunk/sandbox/fcp/__init__.py
trunk/sandbox/fcp/boards/
trunk/sandbox/fcp/boards/__init__.py
trunk/sandbox/fcp/boards/frost.py
trunk/sandbox/fcp/fcp2_0_client.py
trunk/sandbox/fcp/fcp2_0_config.py
trunk/sandbox/fcp/fcp2_0_consts.py
trunk/sandbox/fcp/fcp2_0_message.py
trunk/sandbox/fcp/fcp2_0_params.py
trunk/sandbox/fcp/fcp2_0_uri.py
trunk/sandbox/fcp/fcp_lib/
trunk/sandbox/fcp/fcp_lib/__init__.py
trunk/sandbox/fcp/fcp_lib/events.py
trunk/sandbox/fcp/fcp_lib/namespace.py
trunk/sandbox/fcp/fcp_lib/numbers.py
trunk/sandbox/fcp/fcp_lib/uuid.py
trunk/sandbox/fcp/oo1.py
trunk/sandbox/fcp/test_fcp/
trunk/sandbox/fcp/test_fcp/__init__.py
trunk/sandbox/fcp/test_fcp/dummy_socket.py
trunk/sandbox/fcp/test_fcp/dummy_socket.pyc
trunk/sandbox/fcp/test_fcp/test_fcp2_0_client.py
trunk/sandbox/fcp/test_fcp/test_fcp2_0_config.py
trunk/sandbox/fcp/test_fcp/test_fcp2_0_message.py
Added: trunk/sandbox/fcp/001.py
===================================================================
--- trunk/sandbox/fcp/001.py (rev 0)
+++ trunk/sandbox/fcp/001.py 2008-01-27 02:16:50 UTC (rev 79)
@@ -0,0 +1,107 @@
+
+import random
+import uuid
+#*********************************************************************************************
+#
+#*********************************************************************************************
+def validateFcpBool(value):
+ if value in ('true', 'false'):
+ return value
+ return None
+
+def validateFloat(value):
+ try:
+ return float(value)
+ except ValueError:
+ return None
+
+def validateInt(value):
+ try:
+ return int(value)
+ except ValueError:
+ return None
+
+def validateUuid(value):
+ result = uuid.UUID_EXACT_MATCH_PAT.match(value)
+ if result:
+ return result.group(0)
+ return None
+
+#*********************************************************************************************
+#
+#*********************************************************************************************
+FcParamsSep = '\x01'
+
+FcParams = (
+ ('FcSubType', validateInt),
+ ('FcInitTime', validateFloat), # can not take it from uuid cos requests may be resend multiple times
+ ('FcHandleCollisions', validateFcpBool),
+ ('FcCollisionHandled', validateFcpBool),
+ ('FcRequestIdentifier', validateUuid),
+ ('FcRandomBytes', validateInt),
+ )
+
+
+ISubType = 0
+IRequestIdentifier = 1
+IInitTime = 2
+IHandleCollisions = 3
+ICollisionHandled = 4
+IRandomBytes = 5
+
+def paramsFromRequest(msg):
+ """
+
+ >>> params = [1, 123.456, 'false', 'false', uuid.uuid_time(), 123456789]
+ >>> identifier = FcParamsSep.join( [str(i) for i in params] )
+ >>> result = paramsFromRequest({'Identifier': identifier})
+ >>> result == params
+ True
+
+ """
+ identifier = msg.get('Identifier', None)
+ if identifier is None:
+ return None
+
+ params = identifier.split(FcParamsSep)
+ if len(params) != len(FcParams):
+ return None
+
+ for i, (paramName, paramValidator) in enumerate(FcParams):
+ result = paramValidator(params[i])
+ if result is None:
+ return None
+ params[i] = result
+
+ return params
+
+def identifierFromRequest(msg):
+ """
+
+ >>> msg = {'FcSubType':1, 'FcInitTime':1.234, 'FcHandleCollisions':'false', 'FcCollisionHandled':'flase', 'FcRequestIdentifier':uuid.uuid_time(), 'FcRandomBytes':1234567879}
+ >>> identifierFromRequest(msg)
+
+ """
+
+
+ params = []
+ for paramName, paramValidator in FcParams:
+ params.append(msg[paramName])
+
+ params[-1] = random.getrandbits(32) # add some random bits to be able to
+ # handle IdentifierCollisions(FcRequestIdentifier
+ # remains the same, identifier kown to node changes)
+
+ return FcParamsSep.join( [str(i) for i in params] )
+
+
+s = '1\x011.234\x01false\x01flase\x01{fc0125cc-c76f-11dc-9099-fce464f183f6}\x011234567879'
+print len(s)
+#*********************************************************************************
+#
+#*********************************************************************************
+if __name__ == '__main__2':
+ import doctest
+ doctest.testmod()
+
+
Added: trunk/sandbox/fcp/LICENCE.MIT
===================================================================
--- trunk/sandbox/fcp/LICENCE.MIT (rev 0)
+++ trunk/sandbox/fcp/LICENCE.MIT 2008-01-27 02:16:50 UTC (rev 79)
@@ -0,0 +1,20 @@
+ Fcp - a python wraper library for the freenet client protocol. See: [http://www.freenetproject.org]
+
+Copyright (c) 2008 J\xFCrgen Urner
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+and associated documentation files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
+OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
Added: trunk/sandbox/fcp/README
===================================================================
--- trunk/sandbox/fcp/README (rev 0)
+++ trunk/sandbox/fcp/README 2008-01-27 02:16:50 UTC (rev 79)
@@ -0,0 +1,18 @@
+Fcp - a python wraper library for the freenet client protocol. See: [http://www.freenetproject.org]
+
+
+
+
+Version history:
+
+*******************************************************************
+0.1.0
+*******************************************************************
+news:
+
+ x.
+
+bugfixes:
+
+ x.
+
Added: trunk/sandbox/fcp/__init__.py
===================================================================
--- trunk/sandbox/fcp/__init__.py (rev 0)
+++ trunk/sandbox/fcp/__init__.py 2008-01-27 02:16:50 UTC (rev 79)
@@ -0,0 +1,11 @@
+"""Python wrapper for the freenet client protocol
+
+See: [http://www.freenetproject.org]
+"""
+
+
+__author__ = 'Juergen Urner'
+__copyright__ = '(c) 2008 - Juergen Urner'
+__emeil__ = 'jue...@go...'
+__licence__ = 'Mit'
+__version__ = '0.1'
\ No newline at end of file
Added: trunk/sandbox/fcp/boards/__init__.py
===================================================================
--- trunk/sandbox/fcp/boards/__init__.py (rev 0)
+++ trunk/sandbox/fcp/boards/__init__.py 2008-01-27 02:16:50 UTC (rev 79)
@@ -0,0 +1 @@
+
Added: trunk/sandbox/fcp/boards/frost.py
===================================================================
--- trunk/sandbox/fcp/boards/frost.py (rev 0)
+++ trunk/sandbox/fcp/boards/frost.py 2008-01-27 02:16:50 UTC (rev 79)
@@ -0,0 +1,99 @@
+
+import os, sys, time
+
+#--> rel import hack
+class SysPathHack(object):
+ def __init__(self, n):
+ fpath = os.path.abspath(__file__)
+ for i in xrange(n): fpath = os.path.dirname(fpath)
+ sys.path.insert(0, fpath)
+ def __del__(self): sys.path.pop(0)
+hack = SysPathHack(2)
+
+from fcp2_0_client import FcpClient
+
+
+del hack
+#<-- rel import hack
+#**********************************************************************
+#
+#**********************************************************************
+#c = FcpClient()
+#c.connect()
+
+
+
+#KSK@frost|message|news|*currentDate*-*boardName*-*slot*.xml
+
+class FrostDate(object):
+
+ def __init__(self, year, month, day):
+ self.year = year
+ self.month = month
+ self.day = day
+
+ @classmethod
+ def now(clss):
+ t = time.gmtime(time.time())
+ instance = clss(t.tm_year, t.tm_mon, t.tm_mday)
+ return instance
+
+ def toString(self):
+ return '%s.%s.%s' % (self.year, self.month, self.day)
+
+
+
+
+class FrostBoard(object):
+
+ def __init__(self):
+
+ self.fcpClient = FcpClient(debugVerbosity=FcpClient.DebugVerbosity.Debug)
+ for nodeHello in self.fcpClient.connect():
+ pass
+ self.fcpClient.events.RequestCompleted += self.handleRequestCompleted
+
+
+ def handleRequestCompleted(self, event, request):
+
+ self.fcpClient.removeRequest(request['Identifier'])
+ print request['FcData']
+
+
+
+ def readBoard(self, boardName, section, startDate=None):
+
+ slot = -1
+ while True:
+ slot += 1
+ if slot > 30: break
+
+ ksk = 'KSK@frost|message|%s|%s-%s-%s.xml' % (section, startDate.toString(), boardName, slot)
+ print ksk
+
+ self.fcpClient.getData(
+ uri=ksk,
+ maxSize='33000'
+ )
+
+ for i in xrange(300):
+ self.fcpClient.next()
+
+
+
+
+board = FrostBoard()
+
+
+t = FrostDate.now()
+print t.toString()
+
+board.readBoard('de.freenet', 'news', FrostDate.now())
+
+
+
+
+
+
+
+
Added: trunk/sandbox/fcp/fcp2_0_client.py
===================================================================
--- trunk/sandbox/fcp/fcp2_0_client.py (rev 0)
+++ trunk/sandbox/fcp/fcp2_0_client.py 2008-01-27 02:16:50 UTC (rev 79)
@@ -0,0 +1,1510 @@
+"""Freenet client protocol 2.0 implementation
+
+Compatibility: >= Freenet 0.7 Build #1084
+
+
+@newfield event, events
+
+
+@note: The client implementation never uses or watches the global queue. No (in words N-O)
+implementation should ever do so. Global is evil... so avoid it. Already filed a bug report regarding this.
+
+"""
+
+import atexit
+import base64
+import cPickle
+import logging
+import os
+import random
+import socket
+import subprocess
+import sys
+import time
+
+
+#--> rel import hack
+class SysPathHack(object):
+ def __init__(self, n):
+ fpath = os.path.abspath(__file__)
+ for i in xrange(n): fpath = os.path.dirname(fpath)
+ sys.path.insert(0, fpath)
+ def __del__(self): sys.path.pop(0)
+hack = SysPathHack(3)
+
+
+from fcp_lib import events, namespace, uuid
+
+
+del hack
+#<-- rel import hack
+
+import fcp2_0_message
+import fcp2_0_consts as consts
+
+
+logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
+#**********************************************************************
+# helpers
+#**********************************************************************
+def saveReadFile(fpath):
+ """Reads contents of a file in the savest manner possible
+ @param fpath: file to write
+ @return: contents of the file if successful, None otherwise
+ """
+ read = None
+ try:
+ fp = open(fpath, 'rb')
+ except: pass
+ else:
+ try:
+ read = fp.read()
+ except: pass
+ fp.close()
+ return read
+
+def saveRemoveFile(fpath):
+ """Savely removes a file
+ @param fpath: filepath of the file to remove or None
+ @return: True if the file was removed, False otherwise
+ """
+ if fpath is not None:
+ if os.path.isfile(fpath):
+ try:
+ os.remove(fpath)
+ except Exception, d:
+ pass
+ else:
+ return True
+ return False
+
+
+def saveWriteFile(fpath, data):
+ """Writes data to a file i the savest manner possible
+ @param fpath: file to write
+ @param data: data to write to file
+ @return: True if successful, False otherwise
+ """
+ written = False
+ try:
+ fp = open(fpath, 'wb')
+ except: pass
+ else:
+ try:
+ fp.write(data)
+ written = True
+ except:
+ fp.Close()
+ else:
+ fp.close()
+ return written
+
+#*************************************************************************************************
+#
+#*************************************************************************************************
+class FcpClient(object):
+
+ DefaultFcpHost = os.environ.get('FCP_HOST', '127.0.0.1').strip()
+ try:
+ DefaultFcpPort = int(os.environ.get('FCP_PORT', '').strip())
+ except ValueError:
+ DefaultFcpPort = 9481
+
+ #TODO: check if required
+ # suggested by Mathew Toseland to use about 32k for mimeType requests
+ # basic sizes of keys are: 1k for SSks and 32k for CHKs
+ # without MaxSize DataFound will have DataLength set to 0 (?!)
+ MaxSizeKeyInfo = '32000'
+ SocketTimeout = 0.1
+ Version = '2.0'
+
+ from fcp2_0_config import Config
+ from fcp2_0_consts import (
+ ConnectReason,
+ DebugVerbosity,
+ DisconnectReason,
+ FcpTrue,
+ FcpFalse,
+ FetchError,
+ FilenameCollision,
+ InsertError,
+ LogMessages,
+ PeerNodeStatus,
+ PeerNoteType,
+ Persistence,
+ Priority,
+ ProtocolError,
+ ReturnType,
+ Verbosity,
+ )
+ from fcp2_0_message import Message
+ import fcp2_0_params as FcParams
+ from fcp2_0_uri import Uri
+
+
+
+ class Events(events.Events):
+ """All events the client supports"""
+ _events_ = (
+
+ 'Idle',
+
+ 'ClientConnected',
+ 'ClientDisconnected',
+
+ 'RequestCompleted', # the request is not removed neither from node nor client
+ 'RequestFailed', # the request is already removed from node and client
+ 'RequestModified',
+ 'RequestProgress',
+ 'RequestRestored',
+ 'RequestStarted',
+
+
+ # config related events
+ 'ConfigData',
+ 'NodeData',
+
+
+ #Peer related events
+ 'EndListPeers',
+ 'Peer',
+ 'PeerRemoved',
+ 'UnknownNodeIdentifier',
+ 'EndListPeerNotes',
+ 'PeerNote',
+
+
+ ###############################
+
+ 'ProtocolError',
+ 'PersistentGet',
+
+ # others
+ 'SSKKeypair',
+
+ )
+
+
+
+ def __init__(self,
+ connectionName=None,
+ debugVerbosity=None,
+ ):
+ """
+ @param conectionName: name of the connection
+ @param debugVerbosity: verbosity level for debugging. Default is L{Verbosity.Warning}
+
+ @ivar events: events the client supports
+ """
+ self._connectionName = connectionName
+ self._ddaTests = []
+ self._identifierMapping = {} # mapping from identifiers to FcRequestIdentifiers
+ self._requests = {}
+
+ self._log = logging.getLogger(self.__class__.__name__)
+ self._socket = None
+
+ self.events = self.Events()
+
+ self.setDebugVerbosity(self.DebugVerbosity.Warning if debugVerbosity is None else debugVerbosity)
+ atexit.register(self.close)
+
+
+ ###############################################################
+ ##
+ ## private methods
+ ##
+ ###############################################################
+ def _addFcParamsToRequest(self,
+ msg,
+ userData,
+ msgSubType,
+ initTime,
+ persistentUserData,
+ filenameCollision,
+ ):
+
+
+ # add additional params to msg
+ msg.params.update({
+
+ # persistent params that will go into identifier
+ 'FcSubType': msgSubType, # identifies sub message types
+ 'FcInitTime': self.fcpTime(initTime), # when was the request started?
+ 'FcFilenameCollision': filenameCollision, # handle fielanem collisions?
+ 'FcPersistentUserData': persistentUserData, # any user defined persistent data
+
+ # non persistent params
+ 'FcStatus': self.Message.StatusPending,
+ 'FcErrorMessage': None, # did an error occur?
+ 'FcUserData': userData, # any user defined runtime data here
+
+ # params for AllData
+ 'FcData': '', # if data was requested via requestData you will find it here
+
+ # params for SSKKeypair
+ 'FcInsertUri': None,
+ 'FcRequestUri': None,
+
+ # params from DataFound
+ 'FcMetadataContentType': '', # contecnt type
+ 'FcDataLength': '', # content size
+
+ # params from PersistentRequestModified
+ 'FcModified': {},
+
+
+ # params for SimpleProgress
+ 'FcProgressTotal': '0',
+ 'FcProgressRequired': '0',
+ 'FcProgressFailed': '0',
+ 'FcProgressFatalyFailed': '0',
+ 'FcProgressSucceeeded': '0',
+
+ })
+ return msg
+
+
+ def _registerRequest(self,
+ msg,
+ userData,
+ msgSubType,
+ initTime,
+ persistentUserData,
+ filenameCollision=consts.FilenameCollision.HandleNever,
+ ):
+ """Registers a message
+ @param msg: message to register for track keeping
+ @param msgSubType: one of the message sub type consts
+ @param requestIdentifier: (str)
+ @param userData: (str)
+ @param initTime: (python time)
+ @param handleCollisions: (bool)
+
+ @return: (str) uuid
+ @note: the identifier returned is unique to the client but may not be unique to the node
+ """
+
+ # we store FcRequestIdentifier and additional params in ClientToken
+ identifier = self.FcParams.newUuid()
+
+ # add additional params to msg
+ msg = self._addFcParamsToRequest(
+ msg,
+ userData,
+ msgSubType,
+ initTime,
+ persistentUserData,
+ filenameCollision,
+ )
+
+ msg['ClientToken'] = self.FcParams.messageToParams(msg)
+ msg['Identifier'] = identifier
+ self._requests[identifier] = msg
+
+
+
+ def _restorePersistentRequestFromNode(self, msg):
+ """Preps a message from node to be restored
+ @return: the restored message if everything went well, None otherwise
+ """
+ fcParams = self.FcParams.paramsFromRequest(msg)
+ if fcParams is None:
+ return None
+
+ # have to re-adjust initTime to python time
+ fcParams[self.FcParams.IInitTime] = self.pythonTime(fcParams[self.FcParams.IInitTime])
+
+ # add additional params to msg
+ msg = self._addFcParamsToRequest(
+ msg,
+ None, # userData,
+ fcParams[self.FcParams.ISubType],
+ fcParams[self.FcParams.IInitTime],
+ fcParams[self.FcParams.IPersistentUserData],
+ fcParams[self.FcParams.IFilenameCollision],
+ )
+
+ # fix some Fcp inconsistencies ClientGet vs. PersistentGet
+ if msg.name == self.Message.MessagePersistentGet:
+ del msg.params['Started']
+ if 'PersistenceType' in msg.params:
+ msg['Persistence'] = msg.params.pop('PersistenceType')
+
+ return msg
+
+
+
+ ###############################################################
+ ##
+ ## Fcp <--> Python mappings
+ ##
+ ###############################################################
+ def fcpBool(self, pythonBool):
+ """Converts a python bool to a fcp bool
+ @param pythonBool: (bool)
+ @return: (str) 'true' or 'false'
+ """
+ return self.FcpTrue if pythonBool else self.FcpFalse
+
+ def fcpTime(self, pythonTime):
+ """Converts a python time value to a fcp time value
+ @param fcpTime: (int, str) time to convert
+ @raise ValueError: if the python time could not be converted
+ @return: (int) fcp time
+ """
+ return pythonTime * 1000
+
+ 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
+
+ def pythonTime(self, fcpTime):
+ """Converts a fcp time value to a python time value
+ @param fcpTime: (int, str) time to convert
+ @raise ValueError: if the fcp time could not be converted
+ @return: (int) python time
+ """
+ return int(fcpTime) / 1000
+
+ ###############################################################
+ ##
+ ## connection related methods
+ ##
+ ###############################################################
+ def close(self):
+ """Closes the client
+ @note: make shure to call close() when done with the client
+ """
+ self._log.info(self.LogMessages.ClientClose)
+ if self._socket is not None:
+ self._socket.close()
+ self._socket = None
+
+ # clean left over tmp files
+ for initialRequest in self._ddaTests:
+ if initialRequest['FcTestDDA']['TmpFile'] is not None:
+ saveRemoveFile(initialRequest['FcTestDDA']['TmpFile'])
+
+
+ def connect(self, host=DefaultFcpHost, port=DefaultFcpPort, duration=20, timeout=0.5):
+ """Iterator to stablish a connection to a freenet node
+ @param host: (str) host of th node
+ @param port: (int) port of the node
+ @param duration: (int) how many seconds try to connect before giving up
+ @param timeout: (int) how much time to wait before another attempt to connect
+ @event: Connected(event, params). Triggered as soon as the client is connected. Params
+ will be the parameters of the NodeHello message.
+ @return: (Message) NodeHello if successful, None otherwise for the next iteration
+ """
+ self._log.info(self.LogMessages.Connecting)
+
+ # poll untill freenet responds
+ timeElapsed = 0
+ while timeElapsed <= duration:
+
+ # try to Connect socket
+ if self._socket is not None:
+ self.close()
+ self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._socket.settimeout(self.SocketTimeout)
+ try:
+ self._socket.connect((host, port))
+ except socket.error, d:
+ yield None
+ else:
+ self._log.info(self.LogMessages.Connected)
+
+ # send ClientHello and wait for NodeHello
+ #NOTE: thought I could leave ClientHelloing up to the caller
+ # but instad of responding with ClientHelloMustBeFirst
+ # as expected when not doing so, the node disconnects.
+ # So take it over here.
+ self.sendMessage(
+ self.Message.MessageClientHello,
+ Name=self._connectionName if self._connectionName is not None else uuid.uuid_time(),
+ ExpectedVersion=self.Version,
+ )
+ while timeElapsed <= duration:
+ msg = self.next(dispatch=False)
+ if msg.name == self.Message.MessageClientSocketTimeout:
+ timeElapsed += self.SocketTimeout
+ yield None
+ elif msg.name == self.Message.MessageNodeHello:
+ self._log.debug(self.LogMessages.MessageReceived + msg.pprint())
+ self.events.ClientConnected(msg)
+ yield msg
+ raise StopIteration
+ else:
+ self._log.debug(self.LogMessages.MessageReceived + msg.pprint())
+ break
+ break
+
+ # continue polling
+ self._log.info(self.LogMessages.ConnectionRetry)
+ timeElapsed += timeout
+ time.sleep(timeout)
+
+ msg = self.Message(
+ self.Message.MessageClientDisconnected,
+ DisconnectReason=self.DisconnectReason.ConnectingFailed
+ )
+ self.events.ClientDisconnected(msg)
+ self._log.info(self.LogMessages.ConnectingFailed)
+ self.close()
+ raise StopIteration
+
+
+ def setDebugVerbosity(self, debugVerbosity):
+ """Sets the verbosity level of the client
+ @note: see L{Verbosity}
+ """
+ self._log.setLevel(debugVerbosity)
+
+
+ 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
+
+ #########################################################
+ ##
+ ## runtime related methods
+ ##
+ #########################################################
+ def handleMessage(self, msg):
+ """Handles a message from the freenet node
+ @param msg: (Message) to handle
+ @return: True if the message was handled, False otherwise
+ """
+ if msg.name == self.Message.MessageClientSocketTimeout:
+ return True
+ self._log.debug(self.LogMessages.MessageReceived + msg.pprint())
+
+ # check if we have a corrosponding initial message
+ fcRequestIdentifier = None
+ initialRequest = None
+
+ # check if we have an initial request corrosponding to msg
+ requestIdentifier = msg.get('Identifier', None)
+ initialRequest = None if requestIdentifier is None else self._requests.get(requestIdentifier, None)
+ ####################################################
+ ##
+ ## errors
+ ##
+ ####################################################
+ if msg.name == self.Message.MessageIdentifierCollision:
+ if initialRequest is not None:
+ # resend request with new identifier
+
+ newIdentifier = self.FcParams.newUuid(uuids=self._requests)
+ self._requests[newIdentifier] = initialRequest
+ del self._requests[requestIdentifier]
+ initialRequest['Identifier'] = newIdentifier
+ initialRequest['FcModified'] = {self.Message.ModifiedRequestIdentifier: requestIdentifier}
+ self.events.RequestModified(initialRequest)
+ self.sendMessageEx(initialRequest)
+ return True
+
+
+ elif msg.name == self.Message.MessageProtocolError:
+ code = msg['Code']
+ if requestIdentifier is None:
+ #TODO: check how to handle this
+ raise self.ProtocolError(msg)
+
+ if initialRequest is None:
+ #TODO: check how to handle this
+ raise self.ProtocolError(msg)
+
+
+ if code == self.ProtocolError.ShuttingDown:
+ self.close()
+ msg = self.Message(
+ self.Message.MessageClientDisconnected,
+ DisconnectReason=DisconnectReason.Shutdown,
+ )
+ self.events.ClientDisconnect(msg)
+ return True
+
+
+ # handle DDA errors
+ elif code == self.ProtocolError.DDADenied:
+ ddaRequestMsg = self.Message(self.Message.MessageTestDDARequest)
+ if initialRequest.name == self.Message.MessageClientGet:
+ ddaRequestMsg['WantWriteDirectory'] = self.FcpTrue
+ directory = os.path.dirname(initialRequest['Filename'])
+ else:
+
+ #TODO: determine directory for other cases
+ raise RuntimeError(NotImplemented)
+
+ ddaRequestMsg['WantReadDirectory'] = self.FcpTrue
+ directory = None
+ ddaRequestMsg['Directory'] = directory
+
+
+ # add params required for testing
+ initialRequest['FcTestDDA'] = {
+ 'Directory': directory,
+ 'Replied': False,
+ 'TmpFile': None,
+ 'WantWrite': self.pythonBool(ddaRequestMsg.get('WantWriteDirectory', self.FcpFalse)),
+ 'ErrorMsg': msg,
+ }
+ self._ddaTests.append(initialRequest)
+ self.sendMessageEx(ddaRequestMsg)
+ return True
+
+
+ # handle filename collisions
+ elif code == self.ProtocolError.DiskTargetExists:
+ handleCollision = initialRequest.get('FcFilenameCollision', self.FilenameCollision.HandleNever)
+ collisionHandled = bool(handleCollision & self.FilenameCollision.CollisionHandled)
+
+ # rename filename
+ if handleCollision & self.FilenameCollision.HandleRename:
+ filename = initialRequest['Filename']
+ initialRequest['FcFilenameCollision'] |= self.FilenameCollision.CollisionHandled
+ newFilename = namespace.unique_filename(filename, extensions=1, ispostfixed=collisionHandled)
+ initialRequest['Filename'] = newFilename
+ initialRequest['FcModified'] = {self.Message.ModifiedRequestFilename: filename}
+ self.sendMessageEx(initialRequest)
+ self.events.RequestModified(initialRequest)
+ return True
+
+ # don't handle
+ else:
+ initialRequest['FcFilenameCollision'] &= ~self.FilenameCollision.CollisionHandled
+
+
+ # only requests should get through to here
+
+ # NOTE: Fcp already removed the request
+ del self._requests[requestIdentifier]
+ initialRequest['FcErrorMessage'] = msg
+ initialRequest['FcStatus'] = self.Message.StatusError | self.Message.StatusRemoved
+ self.events.RequestFailed(initialRequest)
+ return True
+
+
+ ####################################################
+ ##
+ ## TestDDA
+ ##
+ ## We assume that a DDATest messages do not get mixed up. 1st TestDDARequest is first to
+ ## enter TestDDAReply and TestDDAComplete. Hopefuly the freenet devels will rework the
+ ## TestDDA drill.
+ ##
+ ####################################################
+ elif msg.name == self.Message.MessageTestDDAReply:
+ directory = msg['Directory']
+
+ # find message that triggered the call
+ for initialRequest in self._ddaTests:
+ if initialRequest['FcTestDDA']['Directory'] == directory:
+ if not initialRequest['FcTestDDA']['Replied']:
+ initialRequest['FcTestDDA']['Replied'] = True
+ break
+ else:
+ # fell through
+ raise ValueError('No inital message found in TestDDAReply')
+
+ # perform read test if necessary
+ fpathRead = msg.params.get('ReadFilename', None)
+ readContent = ''
+ if fpathRead is not None:
+ readContent = saveReadFile(fpathRead)
+ if readContent is None:
+ readContent = ''
+
+ # perform write test if necessary
+ fpathWrite = msg.params.get('WriteFilename', None)
+ if fpathWrite is not None:
+ written = saveWriteFile(fpathWrite, msg['ContentToWrite'])
+ if not written:
+ saveRemoveFile(fpathWrite)
+ else:
+ initialRequest['FcTestDDA']['TmpFile'] = fpathWrite
+
+ self.sendMessage(
+ self.Message.MessageTestDDAResponse,
+ Directory=msg['Directory'],
+ ReadContent=readContent,
+ )
+ return True
+
+
+ elif msg.name == self.Message.MessageTestDDAComplete:
+ # clean up tmp file
+ directory = msg['Directory']
+
+ # find message that triggered the call
+ for initialRequest in self._ddaTests:
+ if initialRequest['FcTestDDA']['Directory'] == directory:
+ if initialRequest['FcTestDDA']['Replied']:
+ break
+ else:
+ # fell through
+ raise ValueError('No initial message found in TestDDAComplete')
+
+ # remove test and clean tmp data
+ self._ddaTests.remove(initialRequest)
+ if initialRequest['FcTestDDA']['TmpFile'] is not None:
+ saveRemoveFile(initialRequest['FcTestDDA']['TmpFile'])
+ wantWrite = initialRequest.params['FcTestDDA']['WantWrite']
+ errorMsg = initialRequest['FcTestDDA']['ErrorMsg']
+ del initialRequest.params['FcTestDDA']
+
+ # check if test was sucessful
+ testFailed = False
+ if wantWrite:
+ testFailed = not self.pythonBool(msg.params.get('WriteDirectoryAllowed', self.FcpFalse) )
+ else:
+ testFailed = not self.pythonBool(msg.params.get('ReadDirectoryAllowed', self.FcpFalse) )
+
+ if testFailed:
+
+ #TODO: c...
[truncated message content] |
|
From: <ju...@us...> - 2008-02-16 11:00:48
|
Revision: 219
http://fclient.svn.sourceforge.net/fclient/?rev=219&view=rev
Author: jurner
Date: 2008-02-16 03:00:47 -0800 (Sat, 16 Feb 2008)
Log Message:
-----------
added sf web page
Added Paths:
-----------
trunk/web/
trunk/web/default.css
trunk/web/download-fclient.html
trunk/web/download-fcp.html
trunk/web/index.html
trunk/web/intro.html
trunk/web/more-fclient.html
Added: trunk/web/default.css
===================================================================
--- trunk/web/default.css (rev 0)
+++ trunk/web/default.css 2008-02-16 11:00:47 UTC (rev 219)
@@ -0,0 +1,97 @@
+/* bit of a hack to get tables (...) to display at 100% hight
+See: http://apptools.com/examples/tableheight.php
+*/
+html,body, #fillsViewport{
+ margin:0;
+ padding:0;
+ height:100%;
+ border:none
+ }
+
+div.body{
+ border: solid;
+ border-width: 0.1em;
+ border-color: #C7E9D3;
+ height: auto;
+ }
+
+td.frame{
+ border: solid;
+ border-width: 0.1em;
+ border-color: #C7E9D3;
+ }
+div.navHeader{
+ background-color: green;
+ font-size: xx-large;
+ font-weight: bolder;
+ }
+div.topic{
+ margin-top: 10;
+ }
+a{
+ text-decoration: none;
+ }
+.navRef{
+ color: #64CB8C;
+ }
+
+.hilight{
+ font-weight: bold;
+ background-color: #F0F0F0;
+}
+.bottom_padding{
+ padding-bottom: 20em;
+ }
+
+
+/* pymarkup */
+table.py_table{
+}
+/* pre */
+pre.py_code{
+ font:xx-small Georgia,Serif;
+ padding-left: 0.5em;
+ background: #F0F0F0;
+ border: solid 1px;
+}
+pre.py_lineno{
+ font:xx-small Georgia,Serif;
+ color: gray;
+ padding-right: 0.1em;
+ border-right: solid 1px gray;
+ background: #F0F0F0;
+}
+/* source code colors */
+span.py_blockcomment1{
+ color: #008000;
+}
+span.py_blockcomment2{
+ color: #008000;
+}
+span.py_comment{
+ color: #008000;
+}
+span.py_string1{
+ color: #FF00FF;
+}
+span.py_string2{
+ color: #FF00FF;
+}
+span.py_keyword{
+ color: #0000FF;
+ font: bold;
+}
+span.py_operator{
+ color: #008080;
+}
+span.py_bool{
+ color: #008080;
+}
+span.py-number{
+ color: #0080C0;
+}
+span.py_exception{
+ color: #FF0000;
+}
+span.py_plain{
+}
\ No newline at end of file
Added: trunk/web/download-fclient.html
===================================================================
--- trunk/web/download-fclient.html (rev 0)
+++ trunk/web/download-fclient.html 2008-02-16 11:00:47 UTC (rev 219)
@@ -0,0 +1,22 @@
+
+<html>
+ <head>
+ <link rel="StyleSheet" href="default.css" type="text/css" media="screen">
+ </head>
+ <body>
+
+ <div class="navHeader">
+ <a class="navRef" href="intro.html" target="mainFrame">fclient</a><span class="navRef">::download-fclient</span>
+ </div>
+
+
+ <div class="topic">
+ <br>
+ <br>
+ Nothing to see here, move along
+ </div>
+
+
+ <div class="bottom_padding"></div>
+ </body>
+</html>
Added: trunk/web/download-fcp.html
===================================================================
--- trunk/web/download-fcp.html (rev 0)
+++ trunk/web/download-fcp.html 2008-02-16 11:00:47 UTC (rev 219)
@@ -0,0 +1,21 @@
+<html>
+ <head>
+ <link rel="StyleSheet" href="default.css" type="text/css" media="screen">
+ </head>
+ <body>
+
+ <div class="navHeader">
+ <a class="navRef" href="intro.html" target="mainFrame">fclient</a><span class="navRef">::download-fcp</span>
+ </div>
+
+
+ <div class="topic">
+ <br>
+ <br>
+ Nothing to see here, move along
+ </div>
+
+ <div class="bottom_padding"></div>
+ </body>
+</html>
+
Added: trunk/web/index.html
===================================================================
--- trunk/web/index.html (rev 0)
+++ trunk/web/index.html 2008-02-16 11:00:47 UTC (rev 219)
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>fclient</title>
+ <link rel="StyleSheet" href="default.css" type="text/css" media="screen">
+ </head>
+ <body>
+
+
+ <table id="fillsViewport" width="100%" border="0" cellspacing="0" cellpadding="0">
+
+ <!-- header -->
+ <tr>
+ <td class="frame" colspan="999">
+ Python versus freenet
+ <td>
+ </tr>
+ <!-- end header -->
+
+ <tr>
+ <td colspan="999" height="100%">
+ <iframe name="mainFrame" src="intro.html" title="" frameborder="0" width="100%" height="100%">
+
+ Your browser does not seem to support iframes. Some links for quick navigation
+ <br>
+ <a href="intro.html">Intro</a><br>
+ <a href="fclient-more.html">fclient</a><br>
+ <a href="fcp-more.html">fcp</a><br>
+
+ </iframe>
+ </td>
+ </tr>
+
+
+ <!-- footer -->
+ <tr>
+
+ <td class="frame" width="100%" valign="bottom">
+ [<a href="http://www.freenetproject.org">Freenet</a>]
+ <!-- [<a href="http://epydoc.sourceforge.net/">Epydoc</a>] -->
+ </td>
+ <td class="frame" valign="bottom">
+ <a href="http://sourceforge.net/donate/index.php?group_id=206970"><img src="http://images.sourceforge.net/images/project-support.jpg" width="88" height="32" border="0" alt="Support This Project" /> </a>
+ </td>
+ <td class="frame" valign="bottom">
+ <a href="http://sourceforge.net"><img src="http://sflogo.sourceforge.net/sflogo.php?group_id=206970&type=4" width="125" height="37" border="0" alt="SourceForge.net Logo" /></a>
+ </td>
+
+ </tr>
+
+ <!-- end footer -->
+
+ </table>
+
+ </body>
+</html>
\ No newline at end of file
Added: trunk/web/intro.html
===================================================================
--- trunk/web/intro.html (rev 0)
+++ trunk/web/intro.html 2008-02-16 11:00:47 UTC (rev 219)
@@ -0,0 +1,29 @@
+<html>
+ <head>
+ <link rel="StyleSheet" href="default.css" type="text/css" media="screen">
+ </head>
+ <body>
+ <div class="navHeader">
+ <a href="" class="navRef">fclient</a>
+ </div>
+
+
+ <div class="topic">
+ <b>fclient:</b> Gui frontend for freenet written in Qt and python. Yet to come
+
+ <br>
+ [<a href="more-fclient.html" target="mainFrame">..More</a>] [<a href="download-fclient.html" target="mainFrame">Download</a>] [<a href="screenshots-fclient.html" target="mainFrame">Screenshots</a>]
+ <hr>
+ </div>
+
+
+ <div class="topic">
+ <b>fcp:</b> high level wrapper for the freenet client protocol written in python. Automatic
+ conversions from Fcp to python types, access to node and peer configurations and more
+ <br>
+ [<a href="more-fcp.html" target="mainFrame">..More</a>] [<a href="download-fcp.html" target="mainFrame">Download</a>] [<a href="screenshots-fcp.html" target="mainFrame">Screenshots</a>]
+ </div>
+
+
+ </body>
+</html>
\ No newline at end of file
Added: trunk/web/more-fclient.html
===================================================================
--- trunk/web/more-fclient.html (rev 0)
+++ trunk/web/more-fclient.html 2008-02-16 11:00:47 UTC (rev 219)
@@ -0,0 +1,20 @@
+<html>
+ <head>
+ <link rel="StyleSheet" href="default.css" type="text/css" media="screen">
+ </head>
+ <body>
+
+ <div class="navHeader">
+ <a class="navRef" href="intro.html" target="mainFrame">fclient</a><span class="navRef">::more-fclient</span>
+ </div>
+
+
+ <div class="topic">
+ <br>
+ <br>
+ Nothing to see here, move along
+ </div>
+
+
+ </body>
+</html>
\ No newline at end of file
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2008-06-30 12:14:30
|
Revision: 443
http://fclient.svn.sourceforge.net/fclient/?rev=443&view=rev
Author: jUrner
Date: 2008-06-30 05:14:37 -0700 (Mon, 30 Jun 2008)
Log Message:
-----------
...
Added Paths:
-----------
trunk/fcp2/
trunk/fcp2/src/
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2008-06-30 12:15:31
|
Revision: 444
http://fclient.svn.sourceforge.net/fclient/?rev=444&view=rev
Author: jUrner
Date: 2008-06-30 05:15:39 -0700 (Mon, 30 Jun 2008)
Log Message:
-----------
move fcp2 from sandbox to trunk
Added Paths:
-----------
trunk/fcp2/src/fcp2/
Removed Paths:
-------------
trunk/sandbox/fcp2/
Copied: trunk/fcp2/src/fcp2 (from rev 443, trunk/sandbox/fcp2)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2008-07-06 05:38:32
|
Revision: 514
http://fclient.svn.sourceforge.net/fclient/?rev=514&view=rev
Author: jUrner
Date: 2008-07-05 22:38:42 -0700 (Sat, 05 Jul 2008)
Log Message:
-----------
....
Added Paths:
-----------
trunk/fclient/
trunk/fclient/src/
trunk/fclient/src/fclient/
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|