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: check if Fcp removed the request + #TODO: check if errorMsg gives reasonable feedback + + del self._request[fcRequestIdentifier] + initialRequest['FcStatus'] = self.Message.StatusError | self.Message.StatusRemoved + initialRequest['FcErrorMessage'] = errorMsg + self.events.ProtocolError(initialRequest) + return True + + + # resend message + self.sendMessageEx(initialRequest) + return True + + + + #################################################### + ## + ## config related + ## + #################################################### + elif msg.name == self.Message.MessageConfigData: + self.events.ConfigData(msg) + return True + + elif msg.name == self.Message.MessageNodeData: + self.events.NodeData(msg) + return True + + + #################################################### + ## + ## get / put related + ## + #################################################### + elif msg.name == self.Message.MessageAllData: + if initialRequest is None: + return False + + initialRequest['FcData'] = msg.data + self.events.RequestCompleted(initialRequest) + return True + + elif msg.name == self.Message.MessageDataFound: + + if initialRequest is None: + # something is going wrong + return False + + initialRequest['FcStatus'] = self.Message.StatusComplete + initialRequest['FcMetadataContentType'] = msg.get('Metadata.ContentType', '') + initialRequest['FcDataLength'] = msg.get('DataLength', '') + self.events.RequestCompleted(initialRequest) + return True + + + + elif msg.name == self.Message.MessageGetFailed: + code = msg['Code'] + if initialRequest is None: + # something is going wrong + return False + + # check if it is one of our requests for key information + if code == self.FetchError.TooBig and initialRequest['FcSubType'] == self.Message.SubTypeGetKeyInfo: + initialRequest['FcStatus'] = self.Message.StatusComplete + initialRequest['FcMetadataContentType'] = msg.get('ExpectedMetadata.ContentType', '') + initialRequest['FcDataLength'] = msg.get('ExpectedDataLength', '') + initialRequest['FcProgressCompleted'] = self.FcpTrue + #TODO: check if Fcp removed the request + + self.events.RequestCompleted(initialRequest) + else: + + #TODO: check if Fcp removed the request + + initialRequest['FcErrorMessage'] = msg + initialRequest['FcStatus'] = self.Message.StatusError + self.events.RequestFailed(initialRequest) + return True + + + + elif msg.name == self.Message.MessagePersistentGet: + + # NOTE: + # Fcp does no good job in handling persistent requests and identifiers. Already dropped some + # notes and reports regarding this. See freenet-tech mailing list [Fcp notes and issues] + + CancelPersistentRequests = 0 # for testing... if True, cancels all persistent requests + + # unknown request... try to restore it + if initialRequest is None: + restoredRequest = self._restorePersistentRequestFromNode(msg) + + # not one of our requests... so cancel it + if restoredRequest is None or CancelPersistentRequests: + self.sendMessage( + self.Message.MessageRemovePersistentRequest, + Identifier=msg['Identifier'], + Global=msg['Global'], + ) + return True + + restoredRequest.name = self.Message.MessageClientGet + self._requests[requestIdentifier] = restoredRequest + restoredRequest['FcStatus'] = self.Message.StatusStarted + self.events.RequestRestored(restoredRequest) + return True + + # known request... filter out multiple PersistentGets + if initialRequest['FcStatus'] == self.Message.StatusPending: + initialRequest['FcStatus'] = self.Message.StatusStarted + + #TODO: update initialRequest with params from PersistentGet? + + self.events.RequestStarted(initialRequest) + return True + + return True + + + elif msg.name == self.Message.MessagePersistentRequestModified: + if initialRequest is None: + return False + + modified = {} + + # check if PersistentUserData has changed + params = self.FcParams.paramsFromRequest(initialRequest) + if params is not None: + clientToken = msg.get('ClientToken', None) + if clientToken is not None: + + #TODO: its more or less a guess that PersistentUserData has changed + # ...as long as no other param is changed at runtime we are ok + # otherwise we would have to set flags to indicate wich member + # of ClientToken changed. See --> modifyRequest() + modified[self.Message.ModifiedRequestPersistentUserData] = None + for i, fcParam in enumerate(self.FcParams.FcParams): + initialRequest[fcParam] = params[i] + + # check if PriorityClass has changed + priorityClass = msg.get('PriorityClass', None) + if priorityClass is not None: + modified[self.Message.ModifiedRequestPriorityClass] = None + initialRequest['PriorityClass'] = priorityClass + + initialRequest['FcModified'] = modified + self.events.RequestModified(initialRequest) + return True + + + elif msg.name == self.Message.MessagePersistentRequestRemoved: + if initialRequest is None: + return False + + del self._requests[requestIdentifier] + return True + + + elif msg.name == self.Message.MessageSimpleProgress: + if initialRequest is None: + # something went wrong + return False + + initialRequest['FcProgressTotal'] = msg['Total'] + initialRequest['FcProgressRequired'] = msg['Required'] + initialRequest['FcProgressFailed'] = msg['Failed'] + initialRequest['FcProgressFatalyFailed'] = msg['FatallyFailed'] + initialRequest['FcProgressSucceeeded'] = msg['Succeeded'] + self.events.RequestProgress(initialRequest) + return True + + #################################################### + ## + ## Peer related messages + ## + #################################################### + elif msg.name == self.Message.MessageEndListPeers: + self.events.EndListPeers(msg) + return True + + elif msg.name == self.Message.MessageEndListPeerNotes: + self.events.EndListPeerNotes(msg.params) + return True + + elif msg.name == self.Message.MessagePeer: + self.events.Peer(msg) + return True + + elif msg.name == self.Message.MessagePeerNote: + note = msg.get('NoteText', '') + if note: + note = base64.decodestring(note) + msg['NoteText'] = note + self.events.PeerNote(msg) + return True + + elif msg.name == self.Message.MessagePeerRemoved: + self.events.PeerRemoved(msg) + return True + + elif msg.name == self.Message.MessageUnknownNodeIdentifier: + self.events.UnknownNodeIdentifier(msg) + return True + + #################################################### + ## + ## others + ## + #################################################### + elif msg.name == self.Message.MessageSSKKeypair: + if initialRequest is None: + return False + + #TODO: maybe we need a mapping from SSKKeypair to pending request + + initialRequest['Uri'] = msg['InsertUri'] + initialRequest['FcRequestUri'] = msg['RequestUri'] + + self.sendMessageEx(initialRequest) + return True + + + + # default + return False + + + def next(self, dispatch=True): + """Pumps the next message waiting + @param dispatch: if True the message is dispatched to L{handleMessage} + @note: use this method instead of run() to run the client step by step + """ + msg = self.Message.fromSocket(self._socket) + if msg.name == self.Message.MessageClientSocketDied: + if dispatch: + msg['DisconnectReason'] = self.DisconnectReason.SocketDied + self.events.ClientDisconnected(msg) + raise socket.error(msg['Details']) + + elif msg.name == self.Message.MessageClientSocketTimeout: + if dispatch: + self.events.Idle(msg) + + else: + if dispatch: + self.handleMessage(msg) + 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) + @raise SocketError: if the socket connection to the node dies unexpectedly + If an error handler is passed to the client it is called emidiately before the error + is raised. + """ + return self.sendMessageEx(self.Message(name, data=data, **params)) + + + def sendMessageEx(self, msg): + """Sends a message to freenet + @param msg: (Message) message to send + @return: Message + @raise SocketError: if the socket connection to the node dies unexpectedly. + 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()) + try: + msg.send(self._socket) + except socket.error, d: + self._log.info(self.LogMessages.SocketDied) + self.close() + + errorMsg = self.Message( + self.Message.MessageClientSocketDied, + DisconnectReason=self.DisconnectReason.SocketDied, + Exception=socket.error, + Details=d + ) + self.events.ClientDisconnected(errorMsg) + raise socket.error(d) + return msg + + ######################################################### + ## + ## config related methods + ## + ######################################################### + #TODO: WithDefault never returns defaults + def getConfig(self, + withCurrent=consts.FcpTrue, + withDefaults=consts.FcpTrue, + withExpertFlag=consts.FcpTrue, + withForceWriteFlag=consts.FcpTrue, + withSortOrder=consts.FcpTrue, + withShortDescription=consts.FcpTrue, + withLongDescription=consts.FcpTrue, + ): + """ + @event: ConfigData(event, msg) + """ + self.sendMessage( + self.Message.MessageGetConfig, + WithSortOrder=withSortOrder, + WithCurrent=withCurrent, + WithDefaults=withDefaults, + WithExpertFlag=withExpertFlag, + WithForceWriteFlag=withForceWriteFlag, + WithShortDescription=withShortDescription, + WithLongDescription=withLongDescription, + ) + + def getNode(self, + withPrivate=True, + withVolatile=True, + giveOpennetRef=True, + ): + """ + @event: NodeData(event, msg) + """ + self.sendMessage( + self.Message.MessageGetNode, + WithPrivate=self.fcpBool(withPrivate), + WithVolatile=self.fcpBool(withVolatile), + GiveOpennetRef=self.fcpBool(giveOpennetRef), + ) + + + ######################################################## + ## + ## ClientGet related methods + ## + ######################################################## + def getData(self, + uri, + + allowedMimeTypes=None, + binaryBlob=consts.FcpFalse, + dsOnly=consts.FcpFalse, + ignoreDS=consts.FcpFalse, + maxRetries=None, + maxSize=None, + persistence=consts.Persistence.Connection, + priorityClass=consts.Priority.Medium, + + userData=None, + persistentUserData='', + ): + """Requests a file from the node + + @param uri: uri of the file to request (may contain prefixes like 'freenet:' or 'http://') + + @param allowedMimeTypes: (str) list of allowed mime types + @param binaryBlob: if FcpTrue, the file is retrieved as binary blob file + @param dsOnly: if FcpTrue, retrieves the file from the local data store only + @param ignoreDs: If FcpTrue, ignores the local data store + @param maxRetries: (int) maximum number of retries or -1 to retry forver or None to leave it to the node to decide + @param maxSize: (int) maximum size of the file in bytes or None to set no limited + @param persistence: persistence of the request as one of the L{consts.Persistence} constants + @param priorityClass: priority of the request as one of the L{consts.Priority} consts + + @param userData: any non persistent data to associate to the request + @param persistentUserData: any string to associate to the request as persistent data + + @return: (str) request identifier + @note: if a filename collision is handled a RequestFilenameChanged event is triggered + """ + + msg = self.Message( + self.Message.MessageClientGet, + + BinaryBlob=binaryBlob, + Global=self.FcpFalse, + DSOnly=dsOnly, + + Identifier=None, + IgnoreDS=ignoreDS, + Persistence=persistence, + PriorityClass=priorityClass, + + ReturnType=self.ReturnType.Direct, + URI=self.Uri(uri).uri, + Verbosity=self.Verbosity.ReportProgress, + + #MaxTempSize=whatever, + #TempFilename=whatever + ) + if allowedMimeTypes is not None: + msg['AllowedMimeTypes'] = allowedMimeTypes + if maxRetries is not None: + msg['MaxRetries'] = maxRetries + if maxSize is not None: + msg['MaxSize'] = maxSize + + + self._registerRequest( + msg, + userData, + self.Message.SubTypeGetData, + time.time(), + persistentUserData, + ) + self.sendMessageEx(msg) + return msg['Identifier'] + + + def getFile(self, + uri, + filename, + + allowedMimeTypes=None, + binaryBlob=consts.FcpFalse, + dsOnly=consts.FcpFalse, + ignoreDS=consts.FcpFalse, + maxRetries=None, + maxSize=None, + persistence=consts.Persistence.Connection, + priorityClass=consts.Priority.Medium, + + userData=None, + persistentUserData='', + filenameCollision=consts.FilenameCollision.HandleNever, + ): + """Requests a file from the node + + @param uri: uri of the file to request (may contain prefixes like 'freenet:' or 'http://') + @param filename: (full path) filename to store the file to + + @param allowedMimeTypes: (str) list of allowed mime types + @param binaryBlob: if FcpTrue, the file is retrieved as binary blob file + @param dsOnly: if FcpTrue, retrieves the file from the local data store only + @param ignoreDs: If FcpTrue, ignores the local data store + @param maxRetries: (int) maximum number of retries or -1 to retry forver or None to leave it to the node to decide + @param maxSize: (int) maximum size of the file in bytes or None to set no limited + @param persistence: persistence of the request as one of the L{consts.Persistence} constants + @param priorityClass: priority of the request as one of the L{consts.Priority} consts + + @param filenameCollision: what to do if the disk target alreaady exists. One of the FilenameCollision.* consts + @param userData: any non persistent data to associate to the request + @param persistentUserData: any string to associate to the request as persistent data + + @return: (str) request identifier + @note: if a filename collision is handled a RequestFilenameChanged event is triggered + """ + + msg = self.Message( + self.Message.MessageClientGet, + + BinaryBlob=binaryBlob, + Filename=filename, + Global=self.FcpFalse, + DSOnly=dsOnly, + + Identifier=None, + IgnoreDS=ignoreDS, + Persistence=persistence, + PriorityClass=priorityClass, + + ReturnType=self.ReturnType.Disk, + URI=self.Uri(uri).uri, + Verbosity=self.Verbosity.ReportProgress, + + #MaxTempSize=whatever, + #TempFilename=whatever + ) + if allowedMimeTypes is not None: + msg['AllowedMimeTypes'] = allowedMimeTypes + if maxRetries is not None: + msg['MaxRetries'] = maxRetries + if maxSize is not None: + msg['MaxSize'] = maxSize + + + self._registerRequest( + msg, + userData, + self.Message.SubTypeGetFile, + time.time(), + persistentUserData, + filenameCollision, + ) + self.sendMessageEx(msg) + return msg['Identifier'] + + + def getKeyInfo(self, + uri, + + dsOnly=consts.FcpFalse, + ignoreDS=consts.FcpFalse, + persistence=consts.Persistence.Connection, + priorityClass=consts.Priority.Medium, + + userData=None, + persistentUserData='', + ): + """Requests info about a key + + @param uri: uri of the file to request (may contain prefixes like 'freenet:' or 'http://') + + @param dsOnly: if FcpTrue, retrieves the file from the local data store only + @param ignoreDs: If FcpTrue, ignores the local data store + @param persistence: persistence of the request as one of the L{consts.Persistence} constants + @param priorityClass: priority of the request as one of the L{consts.Priority} consts + @param userData: any non persistent data to associate to the request + @param persistentUserData: any string to associate to the request as persistent data + + @return: (str) request identifier + """ + + # how to retrieve meta info about a key? + # ...idea is to provoke a GetFailed (TooBig) + + msg = self.Message( + self.Message.MessageClientGet, + + DSOnly=dsOnly, + Global=self.FcpFalse, + Identifier=None, + IgnoreDS=ignoreDS, + + MaxSize=self.MaxSizeKeyInfo, + + Persistence=persistence, + PriorityClass=priorityClass, + ReturnType=self.ReturnType.Nothing, + + URI=self.Uri(uri).uri, + Verbosity=self.Verbosity.ReportProgress, + ) + + self._registerRequest( + msg, + userData, + self.Message.SubTypeGetKeyInfo, + time.time(), + persistentUserData, + ) + self.sendMessageEx(msg) + return msg['Identifier'] + + ######################################################## + ## + ## ClientPut related methods + ## + ######################################################## + def put(self): + pass + + + + + ######################################################## + ## + ## request related methods + ## + ######################################################## + def getRequest(self, identifier): + """Returns a (initial) message, given its identifier + @param identifier: identifier of the message + @return: L{Message} + """ + return self._requests[identifier] + + + def getRequests(self): + """Returns all (initial) messages, currently known to the client + @return: list(messages) + """ + return self._requests + + + def modifyRequest(self, requestIdentifier, persistentUserData=None, priorityClass=None): + """Modifies a request + @param identifier: identifier of the request to modify + @param clientToken: new client token or None + @param priorityClass: new priority or None + + @note: a RequestModified event is triggered as soon as the request has actually been modified + """ + initialRequest = self._requests[requestIdentifier] + msg = self.Message( + self.Message.MessageModifyPersistentRequest, + Identifier=initialRequest['Identifier'], + Global=self.FcpFalse, + ) + if persistentUserData is not None: + initialRequest['FcPersistentUserData'] = persistentUserData + msg['ClientToken'] = self.FcParams.messageToParams(initialRequest) + if priorityClass is not None: + msg['PriorityClass'] = priorityClass + self.sendMessageEx(msg) + + + def removeRequest(self, requestIdentifier): + """Removes a request + @param identifier: (str) identifier of the request to remove + + @note: a RequestRemoved event is triggered as soon as the request has actually been removed + """ + initialRequest = self._requests[requestIdentifier] + initialRequest['FcStatus'] = self.Message.StatusRemoved + self.sendMessage( + self.Message.MessageRemovePersistentRequest, + Global=self.FcpFalse, + Identifier=requestIdentifier, + ) + + #TODO: check how Fcp responds when the identifier is unknwon or something else goes + # werong. Maybe a ProtocolError.NoSuchIdentifier ??? + + ######################################################## + ## + ## Peer related methods + ## + ######################################################## + def listPeer(self, identity): + self.jobClient.sendMessage( + self.Message.MessageListPeer, + NodeIdentifier=identity, + ) + + + def listPeerNotes(self, identity): + """Lists all text notes associated to a peer + @param identifier: peer as returned in a call to L{peerList} + @event: ListPeerNote(event, params) + @event: EndListPeerNotes(event, params) + @note: listPeerNotes() is only available for darknet nodes + """ + self.sendMessage( + self.Message.MessageListPeerNotes, + NodeIdentifier=identity + ) + + + 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: Peer(event, peer). + @event: EndListPeers(event, params). + """ + self.sendMessage( + self.Message.MessageListPeers, + WithMetadata=self.fcpBool(withMetaData), + WithVolatile=self.fcpBool(withVolantile), + ) + + + def modifyPeer(self, identity, allowLocalAddresses=None, isDisabled=None, isListenOnly=None): + msg = Message( + self.Message.MessageModifyPeer, + NodeIdentifier=identity, + ) + if allowLocalAddresses is not None: + msg['AllowLocalAddresses'] = self.fcpBool(allowLocalAddresses) + if isDisabled is not None: + msg['isDisabled'] = self.fcpBool(isDisabled) + if isListenOnly is not None: + msg['isListenOnly'] = self.fcpBool(isListenOnly) + self.jobClient.sendMessageEx(msg) + self.sendMessageEx(msg) + + + def modifyPeerNote(self, identity, note): + self.sendMessage( + self.Message.MessageModifyPeerNote, + NodeIdentifier=identity, + #NOTE: currently fcp supports only this one type + PeerNoteType=self.PeerNoteType.Private, + NoteText=note + ) + + + def removePeer(self, identity): + self.sendMessage( + self.Message.MessageRemovePeer, + NodeIdentifier=identity, + ) + + ########################################################## + ## + ## others + ## + ########################################################## + def generateSSK(self): + """ + @event: SSKKeypair(event, params), triggered when the request is complete + @return: identifier of the request + """ + while True: + identifier = uuid.uuid_time() + if identifier not in self._requests: + break + self.sendMessage( + self.Message.MessageGenerateSSK, + Identifier=identifier, + ) + return identifier + + +#***************************************************************************** +# +#***************************************************************************** +if __name__ == '__main__': + c = FcpClient( + connectionName='test', + debugVerbosity=FcpClient.DebugVerbosity.Debug + ) + + for nodeHello in c.connect(): pass + if nodeHello is not None: + + #for i in xrange(5): + # c.next() + + + def testGetData(): + def cb(event, request): + print request['FcData'] + c.events.RequestCompleted += cb + + identifier = c.getData( + 'CHK@q4~2soHTd9SOINIoXmg~dn7LNUAOYzN1tHNHT3j4c9E,gcVRtoglEhgqN-DJolXPqJ4yX1f~1gBGh89HNWlFMWQ,AAIC--8/snow_002%20%2810%29.jpg', + #binaryBlob=c.FcpTrue, + ) + + for i in xrange(50): + c.next() + + c.removeRequest(identifier) + for i in xrange(5): + c.next() + + #testGetData() + + + + def testGetFile(): + filename = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test.jpg') + + identifier = c.getFile( + 'CHK@q4~2soHTd9SOINIoXmg~dn7LNUAOYzN1tHNHT3j4c9E,gcVRtoglEhgqN-DJolXPqJ4yX1f~1gBGh89HNWlFMWQ,AAIC--8/snow_002%20%2810%29.jpg', + filename, + filenameCollision=c.FilenameCollision.HandleRename, + persistence=consts.Persistence.Forever, + ) + + for i in xrange(50): + c.next() + + #c.removeRequest(identifier) + #for i in xrange(5): + # c.next() + + testGetFile() + + + + def testGetKeyInfo(): + identifier = c.getKeyInfo( + 'CHK@q4~2soHTd9SOINIoXmg~dn7LNUAOYzN1tHNHT3j4c9E,gcVRtoglEhgqN-DJolXPqJ4yX1f~1gBGh89HNWlFMWQ,AAIC--8/snow_002%20%2810%29.jpg', + ) + + for i in xrange(50): + c.next() + c.removeRequest(identifier) + for i in xrange(5): + c.next() + + #testgetKeyInfo() + + + + def testConfigData(): + + from fcp2_0_config import Config + + def cb(event, msg): + root=Config(configDataMsg=msg) + for node in root.walk(): + print node.key() + + c.events.ConfigData += cb + c.getConfig() + for i in xrange(10): + c.next() + + #testConfigData() + + + + def testNodeData(): + + def cb(event, msg): + pass + + + c.events.NodeData += cb + c.getNode() + for i in xrange(10): + c.next() + + #testNodeData() + + + def testGenerateSSK(): + + #def cb(event, msg): + # pass + + + #c.events.NodeData += cb + c.generateSSK() + for i in xrange(10): + c.next() + + #testGenerateSSK() + + + \ No newline at end of file Added: trunk/sandbox/fcp/fcp2_0_config.py =================================================================== --- trunk/sandbox/fcp/fcp2_0_config.py (rev 0) +++ trunk/sandbox/fcp/fcp2_0_config.py 2008-01-27 02:16:50 UTC (rev 79) @@ -0,0 +1,335 @@ +"""Sketch for fcp config tree""" + +#**************************************************************************************** +# +#**************************************************************************************** +class ConfigItem(object): + + def __init__(self, parent, name): + self.parent = parent + self.name = name + self.children = {} + self.values = {} + + def key(self): + out = [] + parent = self + while parent is not None: + if parent.name is not None: + out.append(parent.name) + parent = parent.parent + out.reverse() + return '.'.join(out) + + +#**************************************************************************************** +# +#**************************************************************************************** +class Config(object): + + """Class representing fcp config tree + + """ + #{'current': {'valueType': (8, None), 'allowedHosts': 'true'}, 'default': {'valueType': (8, None), 'allowedHosts': 'true'}} + + ValueClassCurrent = 'current' + ValueClassDefault = 'default' + ValueClassExpertFlag = 'expertFlag' + ValueClassForceWriteFlag = 'forceWriteFlag' + ValueClassShortDescription = 'shortDescription' + ValueClassLongDescription = 'longDescription' + ValueClassSortOrder = 'sortOrder' + ValueClassType = 'valueType' + + ValueClassesFcp = ( + ValueClassDefault, + ValueClassExpertFlag, + ValueClassForceWriteFlag, + ValueClassShortDescription, + ValueClassLongDescription, + ) + ValueClassAll = ValueClassesFcp + (ValueClassType, ) + + + ValueTypeUnknown = 0 + ValueTypeBool = 1 + ValueTypeInt = 2 # params: LowerLimit, UpperLimit + ValueTypeBytes = 3 # may have params: LowerLimit, UpperLimit + ValueTypeFilename = 4 + ValueTypeDirname = 5 + ValueTypePort = 6 + ValueTypeIP = 7 + ValueTypeIPList = 8 + ValueTypeChoice = 9 + ValueTypeChoiceDirname = 10 + ValueTypePercent = 11 + ValueTypeString = 12 + ValueTypeUri = 13 + ValueTypeStringList = 14 + + + class ChoiceFProxyCss: + Clean = 'Clean' + Boxed = 'boxed' + GrayAndBlue = 'grayandblue' + Sky = 'sky' + + ChoicesAll = (Clean, Boxed, GrayAndBlue, Sky) + ChoicesAllowMultiple = False + + class ChoiceLoggerPriority: + Error = 'ERROR' + Normal = 'NORMAL' + Minor = 'MINOR' + Debug = 'DEBUG' + + ChoicesAll = (Error, Normal, Minor, Debug) + ChoicesAllowMultiple = False + + class ChoiceNodeDownloadAllowedDirs: + All = 'all' + Downloads = 'downloads' + Nowhere = '' + + ChoicesAll = (All, Downloads, Nowhere) + ChoicesAllowMultiple = False + + class ChoiceNodeUploadAllowedDirs: + All = 'all' + Nowhere = '' + ChoicesAll = (All, Nowhere) + ChoicesAllowMultiple = False + + class ChoicePriorityPolicy: + Hard = 'HARD' + Soft = 'SOFT' + + ChoicesAll = (Hard, Soft) + ChoicesAllowMultiple = False + + + class ChoiceSSLVersion: + Stl = 'STL' + SslV3 = 'SSLV3' + TlsV1 = 'TLSv1' + + ChoicesAll = (Stl, SslV3, TlsV1) + ChoicesAllowMultiple = False + + + KeyConsole = 'console' + KeyFcp = 'fcp' + KeyFproxy = 'fproxy' + KeyLogger = 'logger' + KeyNode = 'node' + KeyNodeLoad = 'node.load' + KeyNodeOpennet = 'node.opennet' + KeyNodeScheduler = 'node.scheduler' + KeyNodeUpdaterer = 'node.updater' + KeyPluginmanager = 'pluginmanager' + KeyPluginmanager2 = 'pluginmanager2' + KeySSL = 'ssl' + KeyToadletSymlinker = 'toadletsymlinker' + + Keys = { + + 'console.allowedHosts': (ValueTypeIPList, None), # host names, single IPs CIDR-maskip IPs likee 192.168.0.0/24 + 'console.bindTo': (ValueTypeIPList, None), + 'console.directEnabled': (ValueTypeBool, None), + 'console.enabled': (ValueTypeBool, None), + 'console.port': (ValueTypePort, None), + 'console.ssl': (ValueTypeBool, None), + + + 'fcp.allowedHosts': (ValueTypeIPList, None), + 'fcp.allowedHostsFullAccess': (ValueTypeIPList, None), + 'fcp.assumeDownloadDDAIsAllowed': (ValueTypeBool, None), + 'fcp.assumeUploadDDAIsAllowed': (ValueTypeBool, None), + 'fcp.bindTo': (ValueTypeIP, None), + 'fcp.enabled': (ValueTypeBool, None), + 'fcp.persistentDownloadsEnabled': (ValueTypeBool, None), + 'fcp.persistentDownloadsFile': (ValueTypeFilename, None), + 'fcp.persistentDownloadsInterval': (ValueTypeInt, (0, None)), + 'fcp.port': (ValueTypePort, None), + 'fcp.ssl': (ValueTypeBool, None), + + + 'fproxy.CSSOverride': (ValueTypeBool, None), + 'fproxy.advancedModeEnabled': (ValueTypeBool, None), + 'fproxy.allowedHosts': (ValueTypeIPList, None), + 'fproxy.allowedHostsFullAccess': (ValueTypeIPList, None), + 'fproxy.bindTo': (ValueTypeIPList, None), + 'fproxy.css': (ValueTypeChoice, ChoiceFProxyCss), + 'fproxy.doRobots': (ValueTypeBool, None), + 'fproxy.enabled': (ValueTypeBool, None), + 'fproxy.javascriptEnabled': (ValueTypeBool, None), + 'fproxy.port': (ValueTypePort, None), + 'fproxy.showPanicButton': (ValueTypeBool, None), + 'fproxy.ssl': (ValueTypeBool, None), + + + 'logger.dirname': (ValueTypeDirname, None), + 'logger.enabled': (ValueTypeBool, None), + 'logger.interval': (ValueTypeUnknown, None), # ??? 1HOUR ?? + 'logger.maxCachedBytes': (ValueTypeBytes, None), + 'logger.maxCachedLines': (ValueTypeInt, (0, None)), # ??? + 'logger.maxZippedLogsSize': (ValueTypeBytes, None), # ??? + 'logger.priority': (ValueTypeChoice, ChoiceLoggerPriority), + 'logger.priorityDetail': (ValueTypeUnknown, None), # ???? Detailed priority thresholds + + + 'node.alwaysAllowLocalAddresses': (ValueTypeBool, None), + 'node.assumeNATed': (ValueTypeBool, None), + 'node.bindTo': (ValueTypeIP, None), + 'nod... [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. |