SF.net SVN: fclient: [538] trunk/fclient/src/fclient
Status: Pre-Alpha
Brought to you by:
jurner
|
From: <jU...@us...> - 2008-07-07 21:31:51
|
Revision: 538
http://fclient.svn.sourceforge.net/fclient/?rev=538&view=rev
Author: jUrner
Date: 2008-07-07 14:32:00 -0700 (Mon, 07 Jul 2008)
Log Message:
-----------
added lib
Added Paths:
-----------
trunk/fclient/src/fclient/lib/
trunk/fclient/src/fclient/lib/__init__.py
trunk/fclient/src/fclient/lib/fcp2/
trunk/fclient/src/fclient/lib/fcp2/LICENCE.MIT
trunk/fclient/src/fclient/lib/fcp2/README
trunk/fclient/src/fclient/lib/fcp2/__init__.py
trunk/fclient/src/fclient/lib/fcp2/client.py
trunk/fclient/src/fclient/lib/fcp2/config.py
trunk/fclient/src/fclient/lib/fcp2/consts.py
trunk/fclient/src/fclient/lib/fcp2/events.py
trunk/fclient/src/fclient/lib/fcp2/iohandler.py
trunk/fclient/src/fclient/lib/fcp2/key.py
trunk/fclient/src/fclient/lib/fcp2/lib/
trunk/fclient/src/fclient/lib/fcp2/lib/__init__.py
trunk/fclient/src/fclient/lib/fcp2/lib/events.py
trunk/fclient/src/fclient/lib/fcp2/lib/namespace.py
trunk/fclient/src/fclient/lib/fcp2/lib/node.py
trunk/fclient/src/fclient/lib/fcp2/lib/numbers.py
trunk/fclient/src/fclient/lib/fcp2/lib/tools.py
trunk/fclient/src/fclient/lib/fcp2/message.py
trunk/fclient/src/fclient/lib/fcp2/scripts/
trunk/fclient/src/fclient/lib/fcp2/scripts/__init__.py
trunk/fclient/src/fclient/lib/fcp2/scripts/gen_docs.py
trunk/fclient/src/fclient/lib/fcp2/scripts/gen_messagecheatsheet.py
trunk/fclient/src/fclient/lib/fcp2/test/
trunk/fclient/src/fclient/lib/fcp2/test/__init__.py
trunk/fclient/src/fclient/lib/fcp2/test/dummy_io.py
trunk/fclient/src/fclient/lib/fcp2/test/test_all.py
trunk/fclient/src/fclient/lib/fcp2/test/test_client.py
trunk/fclient/src/fclient/lib/fcp2/test/test_config.py
trunk/fclient/src/fclient/lib/fcp2/test/test_iohandler.py
trunk/fclient/src/fclient/lib/fcp2/test/test_key.py
trunk/fclient/src/fclient/lib/fcp2/test/test_message.py
trunk/fclient/src/fclient/lib/fcp2/test/test_types.py
trunk/fclient/src/fclient/lib/fcp2/types.py
trunk/fclient/src/fclient/lib/qt4ex/
trunk/fclient/src/fclient/lib/qt4ex/LICENCE.MIT
trunk/fclient/src/fclient/lib/qt4ex/README
trunk/fclient/src/fclient/lib/qt4ex/__init__.py
trunk/fclient/src/fclient/lib/qt4ex/assistant.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/
trunk/fclient/src/fclient/lib/qt4ex/ctrls/__init__.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/areatips.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/checkarraywrap.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/colorbutton.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/compactpatwrap.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/dragtool.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/editboxwrap.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/labelwrap.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/mrumenu.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/tablewidget.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/toolbarwrap.py
trunk/fclient/src/fclient/lib/qt4ex/ctrls/treewidgetwrap.py
trunk/fclient/src/fclient/lib/qt4ex/dlgs/
trunk/fclient/src/fclient/lib/qt4ex/dlgs/__init__.py
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgabout/
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgabout/DlgAbout.ui
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgabout/Ui_DlgAbout.py
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgabout/__init__.py
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgfindreplace/
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgfindreplace/DlgFindReplace.ui
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgfindreplace/Ui_DlgFindReplace.py
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgfindreplace/__init__.py
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgpreferences/
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgpreferences/DlgPreferencesTree.ui
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgpreferences/Ui_DlgPreferencesTree.py
trunk/fclient/src/fclient/lib/qt4ex/dlgs/dlgpreferences/__init__.py
trunk/fclient/src/fclient/lib/qt4ex/lang/
trunk/fclient/src/fclient/lib/qt4ex/lang/qt4ex_de.ts
trunk/fclient/src/fclient/lib/qt4ex/lang/qt4ex_en.ts
trunk/fclient/src/fclient/lib/qt4ex/language.py
trunk/fclient/src/fclient/lib/qt4ex/qt4ex.pro
trunk/fclient/src/fclient/lib/qt4ex/qtools.py
trunk/fclient/src/fclient/lib/qt4ex/res/
trunk/fclient/src/fclient/lib/qt4ex/res/language/
trunk/fclient/src/fclient/lib/qt4ex/res/language/LangCodes-ISO 639-1.txt
trunk/fclient/src/fclient/lib/qt4ex/resources.py
trunk/fclient/src/fclient/lib/qt4ex/scripts/
trunk/fclient/src/fclient/lib/qt4ex/scripts/__init__.py
trunk/fclient/src/fclient/lib/qt4ex/scripts/manifest.py
trunk/fclient/src/fclient/lib/qt4ex/scripts/pylupdate.py
trunk/fclient/src/fclient/lib/qt4ex/scripts/qtpro.py
trunk/fclient/src/fclient/lib/qt4ex/settingsbase.py
Added: trunk/fclient/src/fclient/lib/__init__.py
===================================================================
--- trunk/fclient/src/fclient/lib/__init__.py (rev 0)
+++ trunk/fclient/src/fclient/lib/__init__.py 2008-07-07 21:32:00 UTC (rev 538)
@@ -0,0 +1 @@
+
Added: trunk/fclient/src/fclient/lib/fcp2/LICENCE.MIT
===================================================================
--- trunk/fclient/src/fclient/lib/fcp2/LICENCE.MIT (rev 0)
+++ trunk/fclient/src/fclient/lib/fcp2/LICENCE.MIT 2008-07-07 21:32:00 UTC (rev 538)
@@ -0,0 +1,20 @@
+ Fcp2 - a python wraper library for the freenet client protocol version 2. 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/fclient/src/fclient/lib/fcp2/README
===================================================================
--- trunk/fclient/src/fclient/lib/fcp2/README (rev 0)
+++ trunk/fclient/src/fclient/lib/fcp2/README 2008-07-07 21:32:00 UTC (rev 538)
@@ -0,0 +1,30 @@
+Fcp - a python wraper library for the freenet client protocol version 2
+
+homepage: [http://fclient.sourceforge.net]
+freenet: [http://www.freenetproject.org]
+
+
+requirements: python >= 2.5
+
+note: for one or the other reason the package is designed to be dropped into
+any project or to be used directly from the folder it resides in. no need to install it
+to 'lib/site-packages'. if you want to do so, make shure to remove any prior
+fcp2 package and copy it to site-packages by hand.
+
+
+
+Version history:
+
+*******************************************************************
+0.0.1
+*******************************************************************
+(07.06.08) first alpha release for testing
+
+news:
+
+ x.
+
+bugfixes:
+
+ x.
+
Added: trunk/fclient/src/fclient/lib/fcp2/__init__.py
===================================================================
--- trunk/fclient/src/fclient/lib/fcp2/__init__.py (rev 0)
+++ trunk/fclient/src/fclient/lib/fcp2/__init__.py 2008-07-07 21:32:00 UTC (rev 538)
@@ -0,0 +1,50 @@
+"""Python wrapper for the freenet client protocol version-2
+
+See: [http://www.freenetproject.org] and [http://wiki.freenetproject.org/FreenetFCPSpec2Point0]
+
+@requires: python >= 2.5
+"""
+from __future__ import absolute_import
+if __name__ == '__main__': # see --> http://bugs.python.org/issue1510172 . works only current dir and below
+ import os; __path__ = [os.path.dirname(__file__)]
+
+__author__ = 'Juergen Urner'
+__copyright__ = '(c) 2008 - Juergen Urner'
+__email__ = 'jue...@ar...'
+__licence__ = 'Mit'
+__version__ = '0.0.1'
+
+from .client import Client
+from .config import (Config, ConfigDataType, ConfigItem, ConfigKeySep, ConfigValueClass)
+from .consts import (ConstByteAmountPostfix, ConstConnectReason, ConstDebugVerbosity, ConstDisconnectReason,
+ ConstFetchError, ConstFilenameCollision, ConstInsertError, ConstKeyType, ConstLogMessages,
+ ConstLogger, ConstMessage, ConstPeerNodeStatus, ConstPeerNoteType, ConstPersistence,
+ ConstPriority, ConstProtocolError, ConstPutDirType, ConstRequestModified, ConstRequestStatus,
+ ConstRequestType, ConstReturnType, ConstTimeDeltaPostfix, ConstUploadFrom, ConstVerbosity,
+ Error, ErrorIOBroken, ErrorIOClosed, ErrorIOConnectFailed, ErrorIOTimeout, ErrorMessageParse,
+ FcpFalse, FcpTrue)
+from .key import (Key, KeyCHK, KeyKSK, KeySSK, KeyTypesAll, KeyUSK, TypeKey, base64UrlsaveDecode, keyNormkey)
+from .message import (MessagesAll, MsgAddPeer, MsgAllData, MsgClientDisconnected, MsgClientGet, MsgClientHello,
+ MsgClientPut, MsgClientPutComplexDir, MsgClientPutDiskDir, MsgClientSocketDied,
+ MsgClientSocketTimeout, MsgCloseConnectionDuplicateClientName, MsgConfigData, MsgDataFound,
+ MsgEndListPeerNotes, MsgEndListPeers, MsgEndListPersistentRequests, MsgFCPPluginMessage,
+ MsgFCPPluginReply, MsgFinishedCompression, MsgGenerateSSK, MsgGetConfig, MsgGetFailed,
+ MsgGetNode, MsgGetPluginInfo, MsgGetRequestStatus, MsgIdentifierCollision, MsgListPeer,
+ MsgListPeerNotes, MsgListPeers, MsgListPersistentRequests, MsgModifyConfig, MsgModifyPeer,
+ MsgModifyPeerNote, MsgModifyPersistentRequest, MsgNodeData, MsgNodeHello, MsgPeer, MsgPeerNote,
+ MsgPeerRemoved, MsgPersistentGet, MsgPersistentPut, MsgPersistentPutDir, MsgPersistentRequestModified,
+ MsgPersistentRequestRemoved, MsgPluginInfo, MsgProtocolError, MsgPutFailed, MsgPutFetchable,
+ MsgPutSuccessful, MsgRemovePeer, MsgRemoveRequest, MsgSSKKeypair, MsgShutdown, MsgSimpleProgress,
+ MsgStartedCompression, MsgSubscribeUSK, MsgSubscribedUSK, MsgSubscribedUSKUpdate,
+ MsgTestDDAComplete, MsgTestDDAReply, MsgTestDDARequest, MsgTestDDAResponse, MsgURIGenerated,
+ MsgUnknownNodeIdentifier, MsgUnknownPeerNoteType, MsgWatchGlobal, PersistentParamsSep,
+ newMessageClass)
+from .types import (Type, TypeBase64EncodedString, TypeBool, TypeByteAmount, TypeChoiceFProxyCss, TypeChoiceLoggerPriority,
+ TypeChoiceNodeDownloadAllowedDirs, TypeChoiceNodeUploadAllowedDirs, TypeChoicePriorityPolicy,
+ TypeChoiceSSLVersion, TypeDirname, TypeFilename, TypeFloat, TypeIP, TypeIPList, TypeIPort, TypeInt,
+ TypeIntWithBounds, TypeInt_GetFailed_ExpectedDataLenght, TypePercent, TypeString, TypeStringList, TypeTime,
+ TypeTimeDelta, TypeUri)
+#****************************************************************************************
+#
+#****************************************************************************************
+
Added: trunk/fclient/src/fclient/lib/fcp2/client.py
===================================================================
--- trunk/fclient/src/fclient/lib/fcp2/client.py (rev 0)
+++ trunk/fclient/src/fclient/lib/fcp2/client.py 2008-07-07 21:32:00 UTC (rev 538)
@@ -0,0 +1,2176 @@
+"""Fcp2 client implementation
+
+Compatibility: >= Freenet 0.7 Build #1107
+
+
+@newfield event: Event, Events
+@newfield requestparam: RequestParam, RequestParams
+
+@note: The client implementation never uses or watches the global queue. No implementation
+should ever do so. Global is evil.
+@note: the client is not thread save.
+
+
+Sample code. Connect to the freenet node::
+ client = FcpClient()
+ nodeHello = client.connect()
+ if nodeHello is None:
+ pass
+ # something went wrong ..could not connect to the freenet node
+ else:
+ pass
+ # everything went well ..we are connected now
+
+Request data associated to a freenet key::
+ myKey = client.key.key('CHK@ABCDE.......')
+ myRequestIdentifier = client.getData(myKey)
+ myRequest = c.getRequest(myIdentifier)
+ client.run()
+ print myRequest.data
+
+Usually you would connect handlers to client events to do processing or handle errors::
+ def handleSuccess(event, request):
+ print 'Here is the data:', request.data
+
+ def handleFailure(event, request):
+ print 'Too bad, something went wrong'
+
+ client.events.RequestCompleted += handleSuccess
+ client.events.RequestFailed += handleFailure
+ myKey = client.key.key('CHK@ABCDE.......')
+ client.getData(myKey)
+ c.run()
+
+
+Instead of calling run() you may run the client step by step::
+ myKey = client.key.key('CHK@ABCDE.......')
+ client.getData(myKey)
+ for i in xrange(50):
+ client.next()
+
+
+You may disconnect event handlers aswell::
+
+ client.events.RequestCompleted -= handleSuccess
+ client.events.RequestFailed -= handleFailure
+
+
+Multiple event handlers may be connected / disconnected at once::
+
+ client.events += (
+ (client.events.RequestCompleted, handleSuccess),
+ (client.events.RequestFailed, handleFailure)
+ )
+
+
+"""
+
+#Bug reports filed and open:
+#--------------------------------------------------------------------------------------------------------------------------------------------
+# [0001931: Send EndListPersistentRequests following client connect]
+#
+# PendingRequests currently get lost if a.) the node goes down b.) if the client goes down unexpectedly.
+# This affects IdentifierCollision + FilenameCollision + ClientPut when a SSK needs to be created first
+#
+# we can handle this case none short of maintaining a file keeping messages. But still there is the
+# problem of knowing when the node has actually registered a request. The node does not send
+# an EndListPersistentRequests on connect so it is impossible to tell when or if to restore one of
+# the pending requests we stored.
+#
+#FIX: None
+#---------------------------------------------------------------------------------------------------------------------------------------------
+# [0001893: CloseConnectionDuplicateClientName bug or feature?]
+#
+# CloseConnectionDuplicateClientName
+# currently fcp takes down a our connection if another client (...) uses the same connection name.
+#
+#FIX: None
+#----------------------------------------------------------------------------------------------------------------------------------------------
+# [0001888: explicite connection locale]
+#
+# Many strings are already translated by freenet, but there is no way to tell the node wich language
+# to use to talk to a client. Maybe a good idea, maybe not.
+#
+#FIX: None
+#-----------------------------------------------------------------------------------------------------------------------------------------------
+# [0001781: unregister directories registered via TestDDARequest]
+#
+# With current state of the art DDA handling it is not possiblr to unregister directories (may pile
+# up in memory over time).
+#
+#FIX: None
+#------------------------------------------------------------------------------------------------------------------------------------------------
+# [0002019: Socket dies if first message is not ClientHello]
+#
+# minor one
+#
+#FIX: None
+#-------------------------------------------------------------------------------------------------------------------------------------------------
+# [0002015: Drop the global queue]
+#
+# this one is somewhat related to [0001931: Send EndListPersistentRequests following client connect]
+#
+# We never use or watch the global queue. It is to dangerous. But problems remain when it comes
+# to restoring persistent requests. Shure these are our requests? Worst case is a client with a colliding
+# connection name flooding our client with an unknown number of left overs.
+#
+#FIX: None (that is, this case is handled as savely as possible - except from possible slowdowns - but no
+# guarantee that no unknown request may slip through)
+#-------------------------------------------------------------------------------------------------------------------------------------------------
+# [0001894: HandleCollision field in ClientGet]
+#
+# minor one. When downloading a file, filename collisions may occur. Fcp does not handle these very well
+# It checks if the tempfile (filename ?) can be created newly when the request is started. IIRC In the final
+# rename of the tempfile to filename no check is done and filename will get overwritten.
+#
+#FIX: we handle collisions in the client as savely as possible. But no guarantee either when a colliding file
+# (...) finds his way into the download directory while downloading another.
+#------------------------------------------------------------------------------------------------------------------------------------------------
+# [0002083: RemovePersistentRequest ignores unknown requests]
+#
+# minor one, but related to it a major one: you can not change the Priority of requests with
+# Persistence=conncetion
+#
+#FIX: workaround for the "related" part
+#------------------------------------------------------------------------------------------------------------------------------------------------
+ # [0002200 - handle persistent and non-peristent request uniformly]
+#
+# removbe request is now handled uniformly in Fcp. modify request not yet.
+#
+#-------------------------------------------------------------------------------------------------------------------------------------------------
+# [0002202: drop global persistents ]
+#
+# suggested dropping of global persistents. pretty dangerous and unhandy imo.
+#
+#FIX: at least some are implemented in the client
+#--------------------------------------------------------------------------------------------------------------------------------------------------
+
+
+
+# Todos
+#------------------------------------------------------------------------------------------------------------------------------------------------
+# clean up
+#
+# x. move saveWriteFile and friends to a separate module
+#
+#------------------------------------------------------------------------------------------------------------------------------------------------
+# Fcp types vs. Python types
+#
+# x. Fcp seems to use kibibytes. Autoconvert to kilobytes?
+# x. time intervals
+#
+#------------------------------------------------------------------------------------------------------------------------------------------------
+# logging
+#
+# x. should uris (...) always be visible in log output? Certainly in memory. Maybe a specialized
+# "save" logger could be useful (like for user feedback).
+#
+#------------------------------------------------------------------------------------------------------------------------------------------------
+# runtime
+#
+# x. if the socket dies the client disconnects automatically, clearing all requests.
+# No idea how to handle this. Would require at least an EndListPersistentRequest
+# from the node to check wich requests arrived at the node ..and an auto resend
+# requests the node does not know about.
+#
+#------------------------------------------------------------------------------------------------------------------------------------------------
+# request status
+#
+# x. have to set a dedicated flag when a request is about to be modified or removed
+# Fcp gets confused if we disconnect emidiately after sending a modify or remove request
+# Curretnly the RequestStatus.Completed flag is removed and later set again to get some
+# control over the process.
+#
+# TODO: check if this is a bug in Fcp
+# NOTE: seems to be fixed in [build 1112], fixes removed
+#-------------------------------------------------------------------------------------------------------------------------------------------------
+
+
+
+# reminders to self
+#------------------------------------------------------------------------------------------------------------------------------------------------
+# Key()
+#
+# we do not allow passing strings as uris. putting a CHK gets in the way here. would have to add another
+# special case to handle 'CHK@myfilename' on input. so for uniformity reasons keys are enforced.
+#------------------------------------------------------------------------------------------------------------------------------------------------
+# clientGet('USK@.../whatever/-1')
+#
+# will trigger a FetchError(PermanentRedirect) with the highest available version as RedirectURI
+# we don't handle this automatically, cos it could be the desired result. maybe implement a flag
+# someday to handle this
+#
+#------------------------------------------------------------------------------------------------------------------------------------------------
+from __future__ import absolute_import
+if __name__ == '__main__': # see --> http://bugs.python.org/issue1510172 . works only current dir and below
+ import os; __path__ = [os.path.dirname(__file__)]
+
+
+import os, sys
+
+import atexit
+import copy
+import logging
+import random
+import subprocess
+import time
+
+
+from . import consts
+from . import config
+from . import events
+from . import message
+from . import iohandler
+from . import types
+from . import key
+
+from .lib import namespace
+from .lib import tools
+
+
+__all__ = ['Client', ]
+#*************************************************************************************************
+#
+#*************************************************************************************************
+class Client(object):
+ """
+ @cvar ExpectedFcpVersion: (float) minimum expected Freenet client protocol version
+ @cvar ExpectedNodeBuild: (int) minimum expected node build
+ @cvar DefaultFcpHost: (str) default Fcp host
+ @cvar DefaultFcpPort: (int) default Fcp port
+ @cvar MaxSizeKeyInfo: (bytes) maximum request size for key info requests
+ @cvar inimumRunTime: (seconds) minimum runtime when the L{run} method is called.
+ Required to make shure persistent requests can be taken up from the node.
+
+ @ivar events: events the client supports
+
+
+ @todo: cvar MaxSizeKeyInfo. 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 (?!)
+
+ """
+ ExpectedFcpVersion = 2.0
+ ExpectedNodeBuild = 1153
+ DefaultFcpHost = os.environ.get('FCP_HOST', '127.0.0.1')
+ DefaultFcpPort = int(os.environ.get('FCP_PORT', '9481'))
+ MaxSizeKeyInfo = 32768
+ MinimumRunTime = 1 # FIX: 0001931
+
+ #consts = consts
+ #config = config
+ #message = message
+ #types = types
+ #key = key
+
+
+ def __init__(self,
+ connectionName=None,
+ debugVerbosity=None,
+ ):
+ """
+ @param connectionName: name of the connection or None to use an arbitrary connection name
+ @param debugVerbosity: verbosity level for debugging. Default is L{consts.ConstDebugVerbosity.Warning}
+
+ """
+ self._connectionName = self.setConnectionName(connectionName)
+ self._ddaTests = [] # currently running DDA tests (request0, ... requestN)
+ self._nodeHelloMessage = None
+ self._requests = {} # currently running requests (requestIdentifier --> request)
+
+ self.events = events.Events()
+ self.ioHandler = iohandler.IOHandler()
+
+ for event in self.events:
+ event += self._captureEvent
+ self.setDebugVerbosity(consts.ConstDebugVerbosity.Warning if debugVerbosity is None else debugVerbosity)
+ atexit.register(self.close)
+
+ ###############################################################
+ ##
+ ## private methods
+ ##
+ ###############################################################
+ def _captureEvent(self, event, request):
+ if event == self.events.Idle:
+ consts.ConstLogger.Event.log(consts.ConstDebugVerbosity.Chatty, consts.ConstLogMessages.EventTriggered + event.name)
+ else:
+ consts.ConstLogger.Event.info(consts.ConstLogMessages.EventTriggered + event.name)
+
+
+ def _close(self, msg):
+ """Closes the client
+ @param msg: message to pass to the ClientDisconnected event or None to not inform listeners
+
+ @todo: complain if the client is already closed?
+ @todo: trigger ClientDisconnected() if the client is already closed? should be yes. otherwise
+ we'd have to distinguish between intentional and unintentional closing like on a broken io
+ """
+ consts.ConstLogger.Client.info(consts.ConstLogMessages.Closing)
+
+ # clean left over DDA test tmp files
+ for initialRequest in self._ddaTests:
+ if initialRequest['TestDDA'].get('TmpFile', None) is not None:
+ tools.saveRemoveFile(initialRequest['TestDDA']['TmpFile'])
+
+ self._ddaTests = []
+ self._requests = {}
+
+ if msg is not None:
+ self.events.ClientDisconnected(msg)
+ if self.ioHandler.isOpen():
+ self.ioHandler.close()
+
+
+ def _finalizeRequest(self, msg, request, event):
+ """Finalzes a request
+ @param msg: message that is the reason for finalizing
+ @param request: request to finalize
+ @param event: event to trigger or None
+
+ @note: this method sets the requests L{consts.ConstRequestStatus.RemovedFromQueue} and
+ L{consts.ConstRequestStatus.Completed} flags accordingly
+ @note: Fcp removes Get / Put requests with Persistence == connection emidiately
+ from its queue. Same goes all requests on ProtocolError. We inform the caller
+ that the request has been completed and remove it fom our queue if necessary.
+ Non Get / Put requests will be removed in any case.
+ """
+ removeRequest = msg == message.MsgProtocolError or msg == message.MsgPersistentRequestRemoved # TODO: PersistentRequestRemoved???
+ if not removeRequest:
+ #NOTE: non Get / Put related requests do not have a Persistence param
+ removeRequest = request.params.get('Persistence', consts.ConstPersistence.Connection) == consts.ConstPersistence.Connection
+ if removeRequest:
+ request['RequestStatus'] |= consts.ConstRequestStatus.RemovedFromQueue
+
+ request['RequestStatus'] |= consts.ConstRequestStatus.Completed
+ if event is not None:
+ event(request)
+
+ if removeRequest:
+ del self._requests[request['Identifier']]
+
+
+ def _registerRequest(self,
+ msg,
+ requestType,
+ userData=None,
+ identifier=None,
+ initTime=None,
+ persistentUserData='',
+ filenameCollision=consts.ConstFilenameCollision.HandleNever,
+ ):
+ """Registers a request
+ @param msg: message to register
+ @param requestType: (L{consts.ConstRequestType}) type of request to register
+ @param filenameCollision: (L{consts.ConstFilenameCollision}) how to handle filename collisions.
+ Default is L{consts.ConstFilenameCollision.HandleNever}
+ @param identifier: (str) identifier of the request or None to create a new one
+ @param initTime: (int) init time of the request or None to set it to now
+ @param persistentUserData: (str) anyuser defined persistent data
+ @param userData: (any) any user defined non persistent data
+
+ @return: (str) identifer of therequest
+ @note: the identifier returned is unique to the client but may not be unique to the node
+ """
+ identifier = self.newIdentifier(identifiers=self._requests) if identifier is None else identifier
+
+ # equip requests with some additional params
+
+ #TODO: keep an eye on additional params, they may collide with Fcp parameters
+ msg['Identifier'] = self.newIdentifier(identifiers=self._requests) if identifier is None else identifier
+ msg['RequestType'] = requestType
+ msg['InitTime'] = time.time() if initTime is None else initTime
+ if requestType & consts.ConstRequestType.MaskGet:
+ msg['FilenameCollision'] = filenameCollision
+ msg['UserData'] = userData
+ msg['PersistentUserData'] = persistentUserData
+ msg['ClientToken'] = ''
+ msg.updatePersistentParams()
+
+ elif requestType & consts.ConstRequestType.MaskPut:
+ msg['UserData'] = userData
+ msg['PersistentUserData'] = persistentUserData
+ msg['ClientToken'] = ''
+ msg.updatePersistentParams()
+
+ elif requestType & consts.ConstRequestType.MaskGenerateKeypair:
+ pass
+ elif requestType & consts.ConstRequestType.SubscribeUSK:
+ msg['UserData'] = userData
+ elif requestType & consts.ConstRequestType.PluginInfo:
+ pass
+ else:
+ raise ValueError('Can not register request: ' + msg.name)
+
+ self._requests[identifier] = msg
+
+ ###############################################################
+ ##
+ ## connection related methods
+ ##
+ ###############################################################
+ def close(self):
+ """Closes the client
+ @note: make shure to call close() when done with the client
+ """
+ msg = message.MsgClientDisconnected(
+ DisconnectReason=consts.ConstDisconnectReason.Close,
+ )
+ self._close(msg)
+
+
+ def closeNode(self):
+ """Shuts down the freenet node"""
+ self.sendMessage(message.MsgShutdown())
+
+
+ def connect(self, host=DefaultFcpHost, port=DefaultFcpPort, duration=20, timeout=0.5):
+ """Connects to the 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
+
+ @return: (L{message.MsgNodeHello}) or None if no connection could be established
+ """
+ nodeHello = None
+ for nodeHello in self.iterConnect(host=host, port=port, duration=duration, timeout=timeout):
+ pass
+ return nodeHello
+
+
+ def isOpen(self):
+ """Checks if the clients connection is open
+ @return: (bool) True if so, False otherwise
+ """
+ return self.ioHandler.isOpen()
+
+
+ def iterConnect(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
+
+ @return: (L{message.MsgNodeHello}) if successful, None otherwise for the next iteration
+
+ @event: ClientConnected(event, message) is triggered as soon as the client is connected
+ """
+
+ #TODO: we have to yield a few round here to make NodeHello injection work in unittests
+ # no idea exactly how many...
+ if self.ioHandler.isOpen():
+ disconnectMsg = message.MsgClientDisconnected(
+ DisconnectReason=consts.ConstDisconnectReason.Reconnect,
+ )
+ self._close(disconnectMsg)
+
+ disconnectReason = consts.ConstDisconnectReason.IOConnectFailed
+ t0 = time.time()
+ for result in self.ioHandler.iterConnect(duration=duration, timeout=timeout, host=host, port=port):
+ yield None
+
+ # try to get handshake
+ timeElapsed = time.time() - t0
+ if result:
+ self.sendMessage(
+ message.MsgClientHello(
+ Name=self._connectionName,
+ ExpectedVersion=self.ExpectedFcpVersion,
+ )
+ )
+
+ while timeElapsed <= duration:
+ yield None
+ msg = self.next(dispatch=False)
+ if msg == message.MsgClientSocketTimeout:
+ disconnectReason = consts.ConstDisconnectReason.NoNodeHello
+ timeElapsed += max(self.ioHandler.io.Timeout, 0.1)
+ yield None
+ elif msg == message.MsgNodeHello:
+ self._nodeHelloMessage = msg
+ # check if version is ok
+ if self.versionCheckNodeHello(msg):
+ self.events.ClientConnected(msg)
+ yield self._nodeHelloMessage
+ raise StopIteration
+ else:
+ disconnectReason = consts.ConstDisconnectReason.VersionMissmatch
+ break
+ else:
+ disconnectReason = consts.ConstDisconnectReason.UnknownNodeHello
+ break
+
+ disconnectMsg = message.MsgClientDisconnected(
+ DisconnectReason=disconnectReason,
+ )
+ self._close(disconnectMsg)
+ raise StopIteration
+
+
+ def getConnectionName(self):
+ """Returns the connection name used by the client
+ @return: (str) connection name
+ """
+ return self._connectionName
+
+
+ def setConnectionName(self, connectionName=None):
+ """Sets the connection name to be used by the client
+ @param connectionName: (str) connection name or None to use an arbitrary connection name
+ @return: (str) connection name
+ """
+ self._connectionName = self.newIdentifier() if connectionName is None else connectionName
+ return self._connectionName
+
+
+ def getDebugVerbosity(self):
+ """Returns the current verbosity level of the client
+ @return: L{consts.ConstDebugVerbosity}
+ """
+ return consts.ConstLogger.Client.getEffectiveLevel()
+
+
+ def setDebugVerbosity(self, debugVerbosity):
+ """Sets the verbosity level of the client
+ @param debugVerbosity: L{consts.ConstDebugVerbosity}
+ """
+ consts.ConstLogger.Client.setLevel(debugVerbosity)
+
+
+ def newIdentifier(self, identifiers=None):
+ """Creates a new identifier to be used as request identifer or whatever
+ @param identifiers: if desired any iterable containing identifiers to enshure the identifier is unique within the iterable
+ @return: (str) identifier
+
+ """
+ identifier = str(hex(random.getrandbits(128)))
+ if identifiers is not None:
+ while identifier in identifiers:
+ identifier = str(hex(random.getrandbits(128)))
+ return identifier
+
+
+ def startNode(self, cmdline):
+ """Starts the freenet node
+ @param cmdline: commandline to start freenet (like '/freenet/run.sh start' or 'c:\freenet\start.bat')
+ @return: (str) whatever freenet returns
+
+ @todo: on windows it may be necessary to hide the command window
+ """
+ p = subprocess.Popen(
+ args=cmdline,
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ stdout, stderr = p.communicate()
+ return stdout
+
+
+ def versionCheckNodeHello(self, nodeHelloMessage):
+ """Performa a version check of the client against the specified NodeHello message
+ @return: (bool) True if version is ok, False otherwise
+ @note: this implementation checks for FCPVersion == L{ExpectedFcpVersion}
+ and a Build >= L{ExpectedNodeBuild}
+ """
+ if nodeHelloMessage['FCPVersion'] == self.ExpectedFcpVersion:
+ if nodeHelloMessage['Build'] >= self.ExpectedNodeBuild:
+ return True
+ return False
+
+ #########################################################
+ ##
+ ## 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
+ """
+
+ CancelPersistentRequests = 0 # for testing... if True, cancels all PersistentRequests
+
+ # 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 == message.MsgIdentifierCollision:
+ if initialRequest is None:
+ self.events.IdentifierCollision(msg)
+ return False
+
+ # resend request with new identifier
+ newIdentifier = self.newIdentifier(identifiers=self._requests)
+ self._requests[newIdentifier] = initialRequest
+ del self._requests[requestIdentifier]
+ initialRequest['Identifier'] = newIdentifier
+ initialRequest['Modified'] = {consts.ConstRequestModified.Identifier: requestIdentifier}
+ self.events.RequestModified(initialRequest)
+ self.sendMessage(initialRequest)
+ return True
+
+
+ elif msg == message.MsgProtocolError:
+ code = msg['Code']
+ if code == consts.ConstProtocolError.ShuttingDown:
+ disconnectMsg = message.MsgClientDisconnected(
+ DisconnectReason=consts.ConstDisconnectReason.NodeClosing,
+ )
+ self._close(disconnectMsg)
+ return True
+
+
+ if requestIdentifier is None:
+ self.events.ProtocolError(msg)
+ return True
+
+ if initialRequest is None:
+ self.events.ProtocolError(msg)
+ return False
+
+ # handle DDA errors
+ elif code == consts.ConstProtocolError.DDADenied:
+ ddaRequestMsg = message.MsgTestDDARequest()
+ if initialRequest == message.MsgClientGet:
+ ddaRequestMsg['WantWriteDirectory'] = True
+ directory = os.path.dirname(initialRequest['Filename'])
+ else:
+ ddaRequestMsg['WantReadDirectory'] = True
+ directory = os.path.dirname(initialRequest['Filename'])
+ ddaRequestMsg['Directory'] = directory
+
+ # add params required for testing
+ initialRequest['TestDDA'] = {
+ 'Directory': directory,
+ 'Replied': False,
+ 'TmpFile': None,
+ 'WantWrite': ddaRequestMsg.get('WantWriteDirectory', False),
+ 'ErrorMsg': msg,
+ }
+ self._ddaTests.append(initialRequest)
+ self.sendMessage(ddaRequestMsg)
+ return True
+
+
+ # handle filename collisions
+ elif code == consts.ConstProtocolError.DiskTargetExists:
+ handleCollision = initialRequest.get('FilenameCollision', consts.ConstFilenameCollision.HandleNever)
+ collisionHandled = bool(handleCollision & consts.ConstFilenameCollision.CollisionHandled)
+
+ # rename filename
+ if handleCollision & consts.ConstFilenameCollision.HandleRename:
+ filename = initialRequest['Filename']
+ initialRequest['FilenameCollision'] |= consts.ConstFilenameCollision.CollisionHandled
+ newFilename = namespace.unique_filename(filename, extensions=1, ispostfixed=collisionHandled)
+ initialRequest['Filename'] = newFilename
+ initialRequest['Modified'] = {consts.ConstRequestModified.Filename: filename}
+ self.sendMessage(initialRequest)
+ self.events.RequestModified(initialRequest)
+ return True
+
+ # don't handle
+ else:
+ initialRequest['FilenameCollision'] &= ~consts.ConstFilenameCollision.CollisionHandled
+
+
+ # handle plugin related request failures
+ elif code == consts.ConstProtocolError.NoSuchPlugin or code == consts.ConstProtocolError.AccessDenied:
+ if initialRequest == message.MsgGetPluginInfo:
+ initialRequest['ErrorMessage'] = msg
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Error
+ self._finalizeRequest(msg, initialRequest, self.events.PluginInfoFailed)
+ return True
+
+ # only requests should get through to here
+
+ # NOTE: Fcp already removed the request
+ initialRequest['ErrorMessage'] = msg
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Error
+ self._finalizeRequest(msg, initialRequest, self.events.RequestFailed)
+ 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 == message.MsgTestDDAReply:
+ directory = msg['Directory']
+
+ # find message that triggered the call
+ for initialRequest in self._ddaTests:
+ if initialRequest['TestDDA']['Directory'] == directory:
+ if not initialRequest['TestDDA']['Replied']:
+ initialRequest['TestDDA']['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 = tools.saveReadFile(fpathRead)
+ if readContent is None:
+ readContent = ''
+
+ # perform write test if necessary
+ fpathWrite = msg.params.get('WriteFilename', None)
+ if fpathWrite is not None:
+ written = tools.saveWriteFile(fpathWrite, msg['ContentToWrite'])
+ if not written:
+ tools.saveRemoveFile(fpathWrite)
+ else:
+ initialRequest['TestDDA']['TmpFile'] = fpathWrite
+
+ self.sendMessage(
+ message.MsgTestDDAResponse(
+ Directory=msg['Directory'],
+ ReadContent=readContent,
+ )
+ )
+ return True
+
+
+ elif msg == message.MsgTestDDAComplete:
+ # clean up tmp file
+ directory = msg['Directory']
+
+ # find message that triggered the call
+ for initialRequest in self._ddaTests:
+ if initialRequest['TestDDA']['Directory'] == directory:
+ if initialRequest['TestDDA']['Replied']:
+ break
+ else:
+ # fell through
+ raise ValueError('No initial message found in TestDDAComplete')
+
+ # remove test and clean tmp data
+ #TODO: reset TestDDA params or not?
+ self._ddaTests.remove(initialRequest)
+ if initialRequest['TestDDA']['TmpFile'] is not None:
+ tools.saveRemoveFile(initialRequest['TestDDA']['TmpFile'])
+ wantWrite = initialRequest.params['TestDDA']['WantWrite']
+
+ # check if test was sucessful
+ testFailed = False
+ if wantWrite:
+ testFailed = not msg.params.get('WriteDirectoryAllowed', False)
+ else:
+ testFailed = not msg.params.get('ReadDirectoryAllowed', False)
+
+ if testFailed:
+ initialRequest['RequestStatus'] = consts.ConstRequestStatus.Error
+ initialRequest['ErrorMessage'] = initialRequest['TestDDA']['ErrorMsg']
+ self._finalizeRequest(msg, initialRequest, self.events.RequestFailed)
+ return True
+
+ # else: resend message
+ self.sendMessage(initialRequest)
+ return True
+
+ ####################################################
+ ##
+ ## config related
+ ##
+ ####################################################
+ elif msg == message.MsgConfigData:
+ self.events.ConfigData(msg)
+ return True
+
+ elif msg == message.MsgNodeData:
+ self.events.NodeData(msg)
+ return True
+
+ ####################################################
+ ##
+ ## get / put related
+ ##
+ ####################################################
+ elif msg == message.MsgAllData:
+ if initialRequest is None:
+ return False
+
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Success
+ initialRequest.data = msg.data
+ self._finalizeRequest(msg, initialRequest, self.events.RequestCompleted)
+ return True
+
+ elif msg == message.MsgDataFound:
+ if initialRequest is None:
+ return False
+
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Success
+ initialRequest['MetadataContentType'] = msg.get('Metadata.ContentType', '')
+ initialRequest['MetadataSize'] = msg.get('DataLength', '')
+
+ # except from GetData all requests are complete here. Next GetData will run through AllData...
+
+ # For GetData with persistence != connection the node sends no All Data message
+ # whatever that is good for ..fix this here to get all GetData request to complete on
+ # All Data.
+ if initialRequest['RequestType'] == consts.ConstRequestType.GetData:
+ if initialRequest['Persistence'] != consts.ConstPersistence.Connection:
+ self.sendMessage(
+ message.MsgGetRequestStatus(
+ Identifier=initialRequest['Identifier'],
+ Global=False,
+ OnlyData=True
+ )
+ )
+ else:
+ self._finalizeRequest(msg, initialRequest, self.events.RequestCompleted)
+
+ return True
+
+
+ elif msg == message.MsgGetFailed:
+ if initialRequest is None:
+ return False
+
+ code = msg['Code']
+ if code == consts.ConstFetchError.Canceled:
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Removed | \
+ consts.ConstRequestStatus.Completed | \
+ consts.ConstRequestStatus.RemovedFromQueue
+ del self._requests[requestIdentifier]
+ self.events.RequestRemoved(initialRequest)
+ return True
+
+ # check if it is one of our requests for key information
+ if code == consts.ConstFetchError.TooBig and initialRequest['RequestType'] == consts.ConstRequestType.GetKeyInfo:
+ initialRequest['MetadataContentType'] = msg.get('ExpectedMetadata.ContentType', '')
+ initialRequest['DataLength'] = msg.get('ExpectedDataLength', -1)
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Success
+ self._finalizeRequest(msg, initialRequest, self.events.RequestCompleted)
+ else:
+ initialRequest['ErrorMessage'] = msg
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Error
+ self._finalizeRequest(msg, initialRequest, self.events.RequestFailed)
+ return True
+
+
+ elif msg == message.MsgPersistentGet or msg == message.MsgPersistentPut or msg == message.MsgPersistentPutDir:
+
+ # unknown request... try to restore it
+ if initialRequest is None:
+ if CancelPersistentRequests:
+ self.sendMessage(
+ message.MsgRemoveRequest(
+ Identifier=msg['Identifier'],
+ Global=msg['Global'],
+ )
+ )
+ return True
+
+ requestType = msg['RequestType']
+ if msg == message.MsgPersistentGet:
+ initialRequest = message.MsgClientGet()
+ elif msg == message.MsgPersistentPut:
+ initialRequest = message.MsgClientPut()
+ else:
+ #NOTE: PutDiskDir is just a subtype of PutComplexDir. check PutDirType param to tell appart
+ initialRequest = message.MsgClientPutComplexDir()
+
+ initialRequest.params.update(msg.params)
+ self._requests[initialRequest['Identifier']] = initialRequest
+
+ #FIX: remove Started param from PersistentGet / Put, not interesting
+ if 'Started' in initialRequest.params:
+ del initialRequest.params['Started']
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Restored
+ self.events.RequestRestored(initialRequest)
+ return True
+
+ #TODO: ignore handshake?
+ return False
+
+
+ elif msg == message.MsgPersistentRequestModified:
+ if initialRequest is None:
+ return False
+
+ modified = {}
+
+ # check if PersistentUserData has changed
+ clientToken = msg.get('ClientToken', None)
+ if clientToken is not None:
+ modified[consts.ConstRequestModified.PersistentUserData] = 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()
+ #
+ # hmm ..thinking again, this would only work if we could make shure
+ # PersistentUserData can only be modified through modifyRequest().
+ # So no way.
+
+ #TODO: ??? try ...except
+ initialRequest._restoreParams(msg.params)
+
+ # check if PriorityClass has changed
+ priorityClass = msg.get('PriorityClass', None)
+ if priorityClass is not None:
+ modified[consts.ConstRequestModified.PriorityClass] = None
+ initialRequest['PriorityClass'] = priorityClass
+
+ initialRequest['Modified'] = modified
+ self.events.RequestModified(initialRequest)
+ return True
+
+ #NOTE: fcp sends a GetFailed / PutFailed if the request was still running (code=canceled)
+ elif msg == message.MsgPersistentRequestRemoved:
+ if initialRequest is None:
+ return False
+
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Removed | consts.ConstRequestStatus.Completed | consts.ConstRequestStatus.RemovedFromQueue
+ del self._requests[requestIdentifier]
+ self.events.RequestRemoved(initialRequest)
+ return True
+
+ elif msg == message.MsgSimpleProgress:
+ if initialRequest is None:
+ return False
+
+ initialRequest['ProgressTotal'] = msg['Total']
+ initialRequest['ProgressRequired'] = msg['Required']
+ initialRequest['ProgressFailed'] = msg['Failed']
+ initialRequest['ProgressFatalyFailed'] = msg['FatallyFailed']
+ initialRequest['ProgressSucceeded'] = msg['Succeeded']
+ self.events.RequestProgress(initialRequest)
+ return True
+
+ ## put related
+
+ elif msg == message.MsgPutFailed:
+ if initialRequest is None:
+ return False
+
+ code = msg['Code']
+ if code == consts.ConstInsertError.Canceled:
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Removed | consts.ConstRequestStatus.Completed | consts.ConstRequestStatus.RemovedFromQueue
+ del self._requests[requestIdentifier]
+ self.events.RequestRemoved(initialRequest)
+ return True
+
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Error
+ initialRequest['ErrorMessage'] = msg
+ self._finalizeRequest(msg, initialRequest, self.events.RequestFailed)
+ return True
+
+
+ elif msg == message.MsgPutFetchable:
+ if initialRequest is None:
+ # something went wrong
+ return False
+
+ self.events.RequestFetchable(initialRequest)
+ return True
+
+
+ elif msg == message.MsgPutSuccessful:
+ if initialRequest is None:
+ return False
+ # TODO: StartupTime and CompletionTime are passed, but
+ # as long as no corrosponding params are passed in DataFound
+ # we ignore them
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Success
+ initialRequest['URI'] = msg['URI']
+ self._finalizeRequest(msg, initialRequest, self.events.RequestCompleted)
+ return True
+
+
+ elif msg == message.MsgURIGenerated:
+ if initialRequest is None:
+ return False
+ initialRequest['URI'] = msg['URI']
+ return True
+
+ elif msg == message.MsgFinishedCompression:
+ if initialRequest is None:
+ return False
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Compressed
+ self.events.RequestCompressionCompleted(initialRequest)
+ return True
+
+ elif msg == message.MsgStartedCompression:
+ if initialRequest is None:
+ return False
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Compressing
+ self.events.RequestCompressionStarted(initialRequest)
+ return True
+
+ ####################################################
+ ##
+ ## Peer related messages
+ ##
+ ####################################################
+ elif msg == message.MsgEndListPeers:
+ self.events.EndListPeers(msg)
+ return True
+
+ elif msg == message.MsgEndListPeerNotes:
+ self.events.EndListPeerNotes(msg.params)
+ return True
+
+ elif msg == message.MsgPeer:
+ self.events.Peer(msg)
+ return True
+
+ elif msg == message.MsgPeerNote:
+ self.events.PeerNote(msg)
+ return True
+
+ elif msg == message.MsgPeerRemoved:
+ self.events.PeerRemoved(msg)
+ return True
+
+ elif msg == message.MsgUnknownNodeIdentifier:
+ self.events.PeerUnknown(msg)
+ return True
+
+ elif msg == message.MsgUnknownPeerNoteType:
+ self.events.PeerNoteTypeUnknown(msg)
+ return True
+ ####################################################
+ ##
+ ## plugins
+ ##
+ ####################################################
+ elif msg == message.MsgPluginInfo:
+ if initialRequest is None:
+ return False
+
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Success
+ self._finalizeRequest(msg, initialRequest, self.events.PluginInfo)
+ return True
+
+ elif msg == message.MsgFCPPluginReply:
+ self.events.PluginMessage(msg)
+ return True
+
+ ####################################################
+ ##
+ ## others
+ ##
+ ####################################################
+ elif msg == message.MsgCloseConnectionDuplicateClientName:
+ disconnectMsg = message.MsgClientDisconnected(
+ DisconnectReason=consts.ConstDisconnectReason.DuplicateClientName,
+ )
+ self._close(disconnectMsg)
+ return True
+
+
+ elif msg == message.MsgSSKKeypair:
+ if initialRequest is None:
+ return False
+
+ insertURI = msg['InsertURI']
+ requestURI = msg['RequestURI']
+
+ if initialRequest['RequestType'] == consts.ConstRequestType.GenerateUSKKeypair:
+ insertURI = key.KeyUSK(insertURI.keyData, docName=insertURI.docName)
+ insertURI = key.KeyUSK(insertURI.keyData, docName=insertURI.docName)
+ requestURI = key.KeyUSK(requestURI.keyData, docName=requestURI.docName)
+ initialRequest['InsertURI'] = insertURI
+ initialRequest['RequestURI'] = requestURI
+ initialRequest['RequestStatus'] |= consts.ConstRequestStatus.Success
+ self._finalizeRequest(msg, initialRequest, self.events.KeypairGenerated)
+ return True
+
+ elif msg == message.MsgSubscribedUSKUpdate:
+ if initialRequest is None:
+ return False
+
+ initialRequest['Edition'] =msg['Edition']
+ self.events.USKUpdated(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 to run the client step by step. If you want to run the
+ client unconditionally use L{run}
+ """
+
+ #FIX: [0002083] we may run into persistents of other clients by accident. the client can not handle them.
+ # so remove them, no matter what....
+ def hackyInvalidMessageCallback(msg, requests=self._requests):
+ if msg == message.MsgPersistentGet or msg == message.MsgPersistentPut or msg == message.MsgPersistentPutDir:
+ requestIdentifier = msg['Identifier']
+ if requestIdentifier not in requests:
+ msgRemove = message.MsgRemoveRequest(Identifier=msg['Identifier'], Global=msg['Global'])
+ self.sendMessage(msgRemove)
+
+ try:
+ msg = self.ioHandler.readMessage(hackyInvalidMessageCallback=hackyInvalidMessageCallback)
+ except consts.ErrorIOTimeout, details:
+ msg = message.MsgClientSocketTimeout()
+ if dispatch:
+ self.events.Idle(msg)
+ except consts.ErrorIOBroken, details:
+ msg = message.MsgClientDisconnected(
+ DisconnectReason=consts.ConstDisconnectReason.ConnectionDied,
+ )
+ self._close(msg)
+ raise consts.ErrorIOBroken(details)
+ else:
+ if dispatch:
+ self.handleMessage(msg)
+ return msg
+
+
+ def run(self):
+ """Runs the client unconditionally untill all requests have completed
+ @note: a KeyboardInterrupt will stop the client
+ """
+
+ #FIX: 0001931
+ # poll a few times to see if there are persistent requests waiting
+ t0 = time.time()
+ while time.time() - t0 <= self.MinimumRunTime:
+ try:
+ msg = self.next()
+ except KeyboardInterrupt:
+ consts.ConstLogger.Client.info(consts.ConstLogMessages.KeyboardInterrupt)
+ return
+ if msg == message.MsgClientSocketDied:
+ return
+
+ #n = 0
+ while True:
+ #n += 1
+ #if n > 50: break
+
+ # check if we have running requests. Assert False
+ haveRunningRequests = False
+ for request in self._requests.values():
+ if not request['RequestStatus'] & consts.ConstRequestStatus.Completed:
+ haveRunningRequests = True
+ break
+
+ if not haveRunningRequests:
+ consts.ConstLogger.Client.info(consts.ConstLogMessages.AllRequestsCompleted)
+ break
+
+ try:
+ msg = self.next()
+ except KeyboardInterrupt:
+ sconsts.ConstLogger.Client.info(consts.ConstLogMessages.KeyboardInterrupt)
+ break
+
+ if msg == message.MsgClientSocketDied:
+ break
+
+
+ def sendMessage(self, msg):
+ """Sends a message to freenet
+ @param msg: (L{message._MessageBase}) message to send
+ @return: always None
+
+ @note: If an error occurs the client is closed
+ @note: you can use this method to send a message to the node, bypassing all
+ track keeping methods of the client
+ """
+
+ # Reminder to self:
+ #
+ # if socket dies on sendall there is no way to determine if and how much data was send
+ # ...so assume data was send
+ try:
+ self.ioHandler.sendMessage(msg)
+ except consts.ErrorIOBroken, details:
+ errorMsg = message.MsgClientDisconnected(
+ DisconnectReason=consts.ConstDisconnectReason.ConnectionDied,
+ )
+ self._close(errorMsg)
+ raise consts.ErrorIOBroken(details)
+
+ #########################################################
+ ##
+ ## config related methods
+ ##
+ #########################################################
+ def getConfig(self,
+ withCurrent=True,
+ withDefaults=True,
+ withExpertFlag=True,
+ withForceWriteFlag=True,
+ withSortOrder=True,
+ withShortDescription=True,
+ withLongDescription=True,
+ withDataTypes=True,
+ ):
+ """
+ @event: ConfigData(event, msg)
+ """
+ self.sendMessage(
+ message.MsgGetConfig(
+ WithSortOrder=withSortOrder,
+ WithCurrent=withCurrent,
+ WithDefaults=withDefaults,
+ WithExpertFlag=withExpertFlag,
+ WithForceWriteFlag=withForceWriteFlag,
+ WithShortDescription=withShortDescription,
+ WithLongDescription=withLongDescription,
+ WithDataTypes=withDataTypes,
+ )
+ )
+
+
+ def modifyConfig(self, params):
+ """Modifies node configuration values
+ @param params: (dict) containing parameters to modify
+ """
+ msg = message.MsgModifyConfig()
+ msg.params = params
+ self.sendMessage(msg)
+
+ ########################################################
+ ##
+ ## ClientGet related methods
+ ##
+ ########################################################
+ def clientGet(self,
+ uri,
+ requestType,
+ userData,
+ persistentUserData,
+ filenameCollision,
+ **messageParams
+ ):
+ """Requests a key from the node
+ @param uri: (L{key._KeyBase}) key to request
+ @param requestType: (L{consts.ConstRequestType}) sub type of the message
+ @param userData: any non persistent data to associate to the request or None
+ @param persistentUserData: any string to associate to the request as persistent data or None
+ @param filenameCollision: what to do if the disk target alreaady exists. One of the FilenameCollision.* consts
+ @param messageParams: keyword arguments to pass along with the ClientGet message (uppercase first letter!!).
+ If the value of a keyword is None, it is ignored.
+
+ @return: (str) request identifier
+ """
+ msg = message.MsgClientGet(URI=uri)
+ for paramName, value in messageParams.items():
+ if value is not None:
+ msg[paramName] = value
+
+ self._registerRequest(
+ msg,
+ requestType,
+ filenameCollision=filenameCollision,
+ persistentUserData=persistentUserData,
+ userData=userData,
+ )
+ self.sendMessage(msg)
+ return msg['Identifier']
+
+
+ def getData(self,
+ uri,
+
+ allowedMimeTypes=None,
+ binaryBlob=False,
+ dsOnly=False,
+ ignoreDS=False,
+ maxRetries=None,
+ maxSize=None,
+ persistence=consts.ConstPersistence.Connection,
+ priorityClass=consts.ConstPriority.Medium,
+
+ userData=None,
+ persistentUserData='',
+ ):
+ """Requests datae from the node
+
+ @param uri: (L{key._KeyBase}) key to request
+
+ @param allowedMimeTypes: (str) list of allowed mime types
+ @param binaryBlob: (bool) if True, the file is retrieved as binary blob file
+ @param dsOnly: (bool) if True, retrieves the file from the local data store only
+ @param ignoreDS: (bool) if True, 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: (L{consts.ConstPersistence}) persistence of the request
+ @param priorityClass: (L{consts.ConstPriority}) priority of the request
+ @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
+
+ @event: L{events.Events.RequestCompleted} triggered as soon as the request is complete
+ @event: L{events.Events.RequestFailed} triggered if the request failes
+ @event: L{events.Events.RequestModified} trigggered if the request identifi...
[truncated message content] |