fclient-commit Mailing List for fclient (Page 38)
Status: Pre-Alpha
Brought to you by:
jurner
You can subscribe to this list here.
| 2007 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(23) |
Nov
(54) |
Dec
|
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2008 |
Jan
(17) |
Feb
(209) |
Mar
(63) |
Apr
(31) |
May
(7) |
Jun
(39) |
Jul
(390) |
Aug
(122) |
Sep
(6) |
Oct
|
Nov
|
Dec
|
|
From: <jU...@us...> - 2007-11-02 16:01:26
|
Revision: 37
http://fclient.svn.sourceforge.net/fclient/?rev=37&view=rev
Author: jUrner
Date: 2007-11-02 09:01:29 -0700 (Fri, 02 Nov 2007)
Log Message:
-----------
some fixes
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-02 08:43:54 UTC (rev 36)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-02 16:01:29 UTC (rev 37)
@@ -144,7 +144,6 @@
'PeerRemoved',
'UnknownNodeIdentifier',
- 'ListPeerNotes',
'EndListPeerNotes',
'PeerNote',
@@ -443,7 +442,7 @@
"""Reads n bytes from socket
@param socketObj: socket to read bytes from
@param n: (int) number of bytes to read
- @return: (tuple) error-message or None, bytes read or None if an error occured
+ @return: (tuple) (error-message or None, bytes read or None) if an error occured
or no bytes could be read
"""
error = p = None
@@ -452,7 +451,7 @@
if not p:
p = None
raise socket.error('Socket shut down by node')
- except socket.timeout, d: # no new messages in queue
+ except socket.timeout, d: # nothing in the queue
error = clss(clss.ClientSocketTimeout)
except socket.error, d:
error = clss(clss.ClientSocketDied, Exception=socket.error, Details=d)
@@ -853,7 +852,7 @@
if note:
note = base64.decodestring(note)
msg['NoteText'] = note
- self.events.PeerNote(msg.params, note)
+ self.events.PeerNote(msg.params)
return True
elif msg.name == self.Message.PeerRemoved:
@@ -1094,9 +1093,9 @@
def listPeerNotes(self, identity):
"""Lists all text notes associated to a peer
@param identifier: peer as returned in a call to L{peerList}
- @event: ListPeerNotes(event).
- @event: ListPeerNote(event, note).
- @event: EndListPeerNotes(event).
+ @event: ListPeerNote(event, params)
+ @event: EndListPeerNotes(event, params)
+ @note: listPeerNotes() is only available for darknet nodes
"""
self.sendMessage(
self.Message.ListPeerNotes,
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-02 08:43:53
|
Revision: 36
http://fclient.svn.sourceforge.net/fclient/?rev=36&view=rev
Author: jUrner
Date: 2007-11-02 01:43:54 -0700 (Fri, 02 Nov 2007)
Log Message:
-----------
message has to be dispatched
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-02 08:30:13 UTC (rev 35)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-02 08:43:54 UTC (rev 36)
@@ -809,6 +809,7 @@
)
return True
+ #TODO: unconditionally clean up all tmp files? Looks like trouble..
elif msg.name == self.Message.TestDDAComplete:
# clean tmp files
for fpath in self._ddaTmpFiles:
@@ -1228,6 +1229,12 @@
# already file a bug report that (False, False) should be interpreted
# by the node to forgett a directory (free resources)
if not wantReadDirectory and not wantWriteDirectory:
+ msg = self.Message(
+ self.Message.TestDDAComplete,
+ ReadDirectoryAllowed=self.FcpFalse,
+ WriteDirectoryAllowed=self.FcpFalse,
+ )
+ self.handleMessage(msg)
return
msg = self.Message(
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-02 08:30:16
|
Revision: 35
http://fclient.svn.sourceforge.net/fclient/?rev=35&view=rev
Author: jUrner
Date: 2007-11-02 01:30:13 -0700 (Fri, 02 Nov 2007)
Log Message:
-----------
poll delays can now be set + adjustements to recent Fcp channges
Modified Paths:
--------------
trunk/fclient/config.py
Modified: trunk/fclient/config.py
===================================================================
--- trunk/fclient/config.py 2007-11-02 08:28:13 UTC (rev 34)
+++ trunk/fclient/config.py 2007-11-02 08:30:13 UTC (rev 35)
@@ -28,13 +28,20 @@
"DlgPreferences": _(DocDir, 'dialogs', 'preferences.html'),
}
+
#*****************************************************************************
#
#*****************************************************************************
class Config(object):
- def __init__(self, parent):
+ def __init__(self, parent, connectDelay=100, pollDelay=300):
"""
+ @param parent: parent widget
+ @param connectDelay: (miliseconds) frequency by qich a connection to the node is made
+ @param pollDelay: (miliseconds) frequency the node is queried for the next message. A low
+ value will make messages from the node come in quickly but make the gui unresponsive
+ to user interaction
+
@ivar assistant: app global user docs management
@ivar defaultDownloadsDir: default directory for downloads
@ivar fcpClient: app global FcpClient
@@ -49,12 +56,20 @@
#TODO: assistant can not handle locale dependend docs yet
self.assistant = assistant.Assistant(parent, profile=AssistantProfile, pages=DocPages)
self.defaultDownloadsDir = os.path.join(BaseDir, 'downloads')
- self.fcpClient = None
+
self.language = language.Language([LanguageDir, ], translationPrefixes=TranslationPrefixes)
- self.nodeHello = None
+
self.resources = resources.Resources([ResourceDir, ], )
self.settings = settingsbase.Settings(OrgName, AppName)
+ self.fcpClient = None
+ self.nodeHello = None
+
+ self.connectDelay = connectDelay
+ self.pollDelay = pollDelay
+
+
+
self._pollTimer = QtCore.QTimer()
self._connectMethod = None
self._connectTimer = QtCore.QTimer()
@@ -67,8 +82,8 @@
def connectFcpNode(self, eventConnectedHandler):
"""Establishes a connection to the freenet node
- @param eventConnectedHandler: handler to handle the EventClientConnected of the client
- @note: the clients EventClientConnected gets automatically connected in the call. So no
+ @param eventConnectedHandler: handler to handle the events.ClientConnected of the client
+ @note: the clients events.ClientConnected gets automatically connected in the call. So no
need to connect once more.
@note: make shure not to access the 'fcpClient' or 'nodeHello' attrs of the config before
the handler gets called.
@@ -78,20 +93,20 @@
from fclient_lib.fcp.fcp2_0 import FcpClient
self.fcpClient = FcpClient(
connectionName='',
- verbosity=FcpClient.Verbosity.Debug,
+ #verbosity=FcpClient.Verbosity.Debug,
)
- self.fcpClient.EventClientDisconnected += self.handleFcpClientDisconnected
+ self.fcpClient.events.ClientDisconnected += self.handleFcpClientDisconnected
QtCore.QObject.connect(
self._pollTimer,
QtCore.SIGNAL('timeout()'),
self.fcpClient.next
)
self._connectMethod = self.fcpClient.connect()
- self.fcpClient.EventClientConnected += eventConnectedHandler
- self._connectTimer.start(100)
+ self.fcpClient.events.ClientConnected += eventConnectedHandler
+ self._connectTimer.start(self.connectDelay)
else:
- cb(self.fcpClient.EventClientConnected, self.nodeHello)
+ cb(self.fcpClient.events.ClientConnected, self.nodeHello)
def handleFcpConnect(self):
@@ -101,7 +116,7 @@
result = self._connectMethod.next()
if result is not None:
self.nodeHello = result
- self._pollTimer.start(100)
+ self._pollTimer.start(self.pollDelay)
except StopIteration:
return
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-02 08:28:16
|
Revision: 34
http://fclient.svn.sourceforge.net/fclient/?rev=34&view=rev
Author: jUrner
Date: 2007-11-02 01:28:13 -0700 (Fri, 02 Nov 2007)
Log Message:
-----------
continued implementig peers widget
Modified Paths:
--------------
trunk/fclient/fclient_widgets/peer_widget.py
Modified: trunk/fclient/fclient_widgets/peer_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/peer_widget.py 2007-11-02 08:27:25 UTC (rev 33)
+++ trunk/fclient/fclient_widgets/peer_widget.py 2007-11-02 08:28:13 UTC (rev 34)
@@ -1,4 +1,4 @@
-"""Sketch for a widget handling node peers
+"""Sketch for a widget handling peer nodes
"""
@@ -29,20 +29,28 @@
#
#***************************************************************************************************
class PeerWidget(QtGui.QTreeWidget):
+ """
+ """
HeaderIndexStatus = 0
HeaderIndexName = 1
HeaderIndexLastConnected = 2
HeaderIndexNotes = 3
+
+ ShowPeersOpennet = 1
+ ShowPeersDarknet = 2
+ ShowPeersAll = ShowPeersOpennet | ShowPeersDarknet
def __init__(self, parent, cfg=None):
-
+ """
+ """
self._cfg = cfg
self._fcpClient = None
self._fcpEvents = None
self._isCreated = False
self._peers = {} # identity --> item
+ self._showPeersFlags = self.ShowPeersAll
QtGui.QWidget.__init__(self, parent)
@@ -66,23 +74,49 @@
header.setResizeMode(header.ResizeToContents)
header.setStretchLastSection(True)
+ self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.connect(
+ self,
+ QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'),
+ self.handleContextMenu
+ )
def showEvent(self, event):
+ """
+ """
if not self._isCreated:
self._isCreated = True
self._cfg.connectFcpNode(self.handleClientConnected)
-
+ #############################################################
+ ##
+ ## handlers for tree events
+ ##
+ #############################################################
+ def handleContextMenu(self, pt):
+ """
+ """
+ pt = self.viewport().mapToGlobal(pt)
+ m = QtGui.QMenu()
+ self.populateMenu(m)
+ m.exec_(pt)
+
+ #############################################################
+ ##
+ ## handlers for Fcp events
+ ##
+ #############################################################
def handleClientConnected(self, event, params):
+ """
+ """
self._fcpEvents = (
- #(self._cfg.fcpClient.EventClientConnected, self.handleClientConnected),
- (self._cfg.fcpClient.EventClientDisconnected, self.handleClientDisconnected),
+ (self._cfg.fcpClient.events.ClientDisconnected, self.handleClientDisconnected),
- (self._cfg.fcpClient.EventPeer, self.handlePeer),
- (self._cfg.fcpClient.EventEndListPeers, self.handleEndListPeers),
- (self._cfg.fcpClient.EventPeerRemoved, self.handlePeerRemoved),
- (self._cfg.fcpClient.EventUnknownNodeIdentifier, self.handleUnknownNodeIdentifier),
+ (self._cfg.fcpClient.events.Peer, self.handlePeer),
+ (self._cfg.fcpClient.events.EndListPeers, self.handleEndListPeers),
+ (self._cfg.fcpClient.events.PeerRemoved, self.handlePeerRemoved),
+ (self._cfg.fcpClient.events.UnknownNodeIdentifier, self.handleUnknownNodeIdentifier),
)
# take care to not connect twice
@@ -95,15 +129,16 @@
self._cfg.fcpClient.listPeers()
-
def handleClientDisconnected(self, event, params):
- pass
+ """
+ """
def handlePeer(self, event, params):
-
+ """
+ """
timeLastConnected = params.get('metadata.timeLastConnected', None)
- timeLastConnected = self.timeToTimeDelta(timeLastConnected)
+ timeLastConnected = self.formatTimeDelta(timeLastConnected)
identity = params['identity']
item = self._peers.get(identity, None)
@@ -117,48 +152,226 @@
]
item = QtGui.QTreeWidgetItem(self, itemStrings)
item.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(identity))
- self._peers[identity] = item
+ self._peers[identity] = (item, params)
# update item
else:
item.settext(self.HeaderIndexLastConnected, timeLastConnected)
-
-
+ self._peers[identity] = (item, params)
+
def handleEndListPeers(self, event, params):
- pass
+ """
+ """
-
def handlePeerRemoved(self, peer):
- pass
+ """
+ """
def handleUnknownNodeIdentifier(self, params):
- pass
+ """
+ """
-
-
- def timeToTimeDelta(self, t):
+ #######################################################
+ ##
+ ## methods
+ ##
+ #######################################################
+ def close(self):
+ """Closes the widget
+ @note: make shure to call close when done with the widget
+ """
+ # disconnect Fcp events
+ for event, observer in self._fcpEvents:
+ if not observer in event:
+ event -= observer
+
+
+ def formatTimeDelta(self, t):
+ """Formats a Fcp time as time delta relative to to now
+ @param t: (str) time to format
+ @return: (str) human readably formated time delta (something like 2.5m or 10.2d) or ''
+ """
+ result = ''
try:
t = self._cfg.fcpClient.pythonTime(t)
- except:
- t = self.trUtf8('Unknown')
+ except: pass
else:
- if t == 0:
- t = self.trUtf8('Never')
- else:
- t = numbers.format_time_delta(t, time.time())
- return t
+ if t > 0:
+ result = numbers.format_time_delta(t, time.time())
+ return result
-
-
+
def identityFromItem(self, item):
+ """Returns the peer identity an item is associated to
+ @param item: treeItem
+ @return: (str) peer identity
+ """
v = item.data(0, QtCore.Qt.UserRole)
return str(v.toString())
+ def peers(self):
+ """Returns all currently known peers
+ @return: (dict) identity --> (treeItem, peer)
+ """
+ return self._peers
+
+ def populateMenu(self, menu):
+ """Populates a menu with peer actions
+ @return: (list) of all actions added to the menu
+ """
+ actions = []
+
+ actions.append(ShowPeersMenu(menu, self))
+
+ return actions
+
+
+ def showPeers(self):
+ """Returns the current ShowPeers* flags set
+ @return: (int) flags
+ """
+ return self._showPeersFlags
+
+
+ def setShowPeers(self, flags):
+ """Shows or hides peers
+ @param flags: (int) one or more ShowPeers* flags
+ @return: always None
+ """
+ self.setUpdatesEnabled(False)
+
+ for item, peer in self.peers().values():
+ if peer['opennet'] == self._cfg.fcpClient.FcpTrue:
+ item.setHidden(not flags & self.ShowPeersOpennet)
+ else:
+ item.setHidden(not flags & self.ShowPeersDarknet)
+
+ self.setUpdatesEnabled(True)
+ self._showPeersFlags = flags
+
+#*************************************************************************************
+# menu actions
+#*************************************************************************************
+class ShowPeersMenu(QtGui.QMenu):
+ """
+ """
+
+ def __init__(self, menu, tree):
+ """
+ """
+ QtGui.QMenu.__init__(self, menu)
+ self.setTitle(self.trUtf8('Show peers'))
+ menu.addMenu(self)
+
+ self.tree = tree
+
+ self.group = QtGui.QActionGroup(self)
+ self.group.setExclusive(True)
+ showPeers = (
+ ('Darknet', self.trUtf8('Darknet'), ShowPeersDarknetAction),
+ ('Opennet', self.trUtf8('Opennet'), ShowPeersOpennetAction),
+ ('OpenAndDarknet', self.trUtf8('Open and darknet'), ShowOpenAndDarknetPeersAction),
+ )
+
+ for name, menuText, act in showPeers:
+ act = act(name, menuText, self.group, tree)
+ act.setCheckable(True)
+ self.group.addAction(act)
+ self.addAction(act)
+
+ self.connect(
+ self,
+ QtCore.SIGNAL('aboutToShow()'),
+ self.handleAboutToShow
+ )
+
+
+ def handleAboutToShow(self):
+ """
+ """
+ acts = { # flag --> actionName
+ self.tree.ShowPeersAll: 'OpenAndDarknet',
+ self.tree.ShowPeersOpennet: 'Opennet',
+ self.tree.ShowPeersDarknet:'Darknet',
+ }
+
+ flags = self.tree.showPeers()
+ for flag, actionName in acts.items():
+ if flags & flag == flags:
+
+ for act in self.group.actions():
+ if str(act.objectName()) == actionName:
+ act.setChecked(True)
+ break
+ else:
+ raise ValueError('No action [name] found ???')
+ break
+ else:
+ raise ValueError('No action [flag] found ???')
+
+
+class ShowPeersDarknetAction(QtGui.QAction):
+ """
+ """
+
+ def __init__(self, name, text, parent, tree):
+ """
+ """
+ QtGui.QAction.__init__(self, text, parent)
+ self.setObjectName(name)
+
+ #self.setShortcuts(
+ # [QtGui.QKeySequence(self.trUtf8('Ctrl+A'))]
+ # )
+ self.tree = tree
+ self.connect(
+ self,
+ QtCore.SIGNAL('triggered()'),
+ self.__call__
+ )
+
+
+ def __call__(self):
+ """
+ """
+ flags = self.tree.showPeers()
+ flags |= self.tree.ShowPeersDarknet
+ flags &= ~self.tree.ShowPeersOpennet
+ self.tree.setShowPeers(flags)
+
+
+class ShowPeersOpennetAction(ShowPeersDarknetAction):
+ """
+ """
+
+ def __call__(self):
+ """
+ """
+ flags = self.tree.showPeers()
+ flags &= ~self.tree.ShowPeersDarknet
+ flags |= self.tree.ShowPeersOpennet
+ self.tree.setShowPeers(flags)
+
+
+class ShowOpenAndDarknetPeersAction(ShowPeersDarknetAction):
+ """
+ """
+
+ def __call__(self):
+ """
+ """
+ flags = self.tree.showPeers()
+ flags |= self.tree.ShowPeersDarknet
+ flags |= self.tree.ShowPeersOpennet
+ self.tree.setShowPeers(flags)
+
+
+
'''########################################################
Sample Peer message
>> Peer
@@ -203,7 +416,14 @@
import sys
app = QtGui.QApplication(sys.argv)
- w = PeerWidget(None)
+ w = QtGui.QMainWindow()
+ peers = PeerWidget(None)
+ w.setCentralWidget(peers)
+
+ m = w.menuBar()
+ m1 = m.addMenu('Peers')
+ peers.populateMenu(m1)
+
w.show()
res = app.exec_()
sys.exit(res)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-02 08:27:30
|
Revision: 33
http://fclient.svn.sourceforge.net/fclient/?rev=33&view=rev
Author: jUrner
Date: 2007-11-02 01:27:25 -0700 (Fri, 02 Nov 2007)
Log Message:
-----------
combed over events + a minor adjustement in testDDA()
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-01 14:58:13 UTC (rev 32)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-02 08:27:25 UTC (rev 33)
@@ -114,56 +114,61 @@
#**************************************************************************
#TODO: name as specified in NodeHello seems to be usable to keep jobs alive. Have to test this.
#TODO: events should be FcpClient.event.PeerNote not FcpClient.EventPeerNote
-class FcpClient(events.Events):
+class FcpClient(object):
"""Fcp client implementation
"""
+
+ Version = '2.0'
+ FcpTrue = 'true'
+ FcpFalse = 'false'
+ class DisconnectReason:
+ """Reason for client disconnect"""
+ Shutdown = '1'
+ SocketDied = '2'
+
- _events_ = (
+ class Events(events.Events):
+ """All events the client supports"""
+ _events_ = (
- 'EventClientConnected',
- 'EventClientDisconnected',
+ 'ClientConnected',
+ 'ClientDisconnected',
# config related events
- 'EventConfigData',
- 'EventNodeData',
+ 'ConfigData',
+ 'NodeData',
#Peer related events
- 'EventEndListPeers',
- 'EventPeer',
- 'EventPeerRemoved',
- 'EventUnknownNodeIdentifier',
+ 'EndListPeers',
+ 'Peer',
+ 'PeerRemoved',
+ 'UnknownNodeIdentifier',
- 'EventListPeerNotes',
- 'EventEndListPeerNotes',
- 'EventPeerNote',
+ 'ListPeerNotes',
+ 'EndListPeerNotes',
+ 'PeerNote',
# get / put related events
- 'EventTestDDAComplete',
- 'EventIdentifierCollision',
+ 'TestDDAComplete',
+ 'IdentifierCollision',
- 'EventClientGetInfo',
- 'EventClientGetInfoProgress',
+ 'ClientGetInfo',
+ 'ClientGetInfoProgress',
- 'EventDataFound',
- 'EventGetFailed',
- 'EventSimpleProgress',
- 'EventPersistentRequestModified',
- 'EventPersistentRequestRemoved',
+ 'DataFound',
+ 'GetFailed',
+ 'SimpleProgress',
+ 'PersistentRequestModified',
+ 'PersistentRequestRemoved',
# others
- 'EventSSKKeypair',
+ 'SSKKeypair',
)
- Version = '2.0'
- FcpTrue = 'true'
- FcpFalse = 'false'
- class DisconnectReason:
- """Reason for client disconnect"""
- Shutdown = '1'
- SocketDied = '2'
+
class FetchError(Exception):
"""All fetch errors supported by the client"""
@@ -348,6 +353,7 @@
KeyboardInterrupt = 'Keyboard interrupt'
SocketDied = 'Socket died'
+ #TODO: maybe speed up lookup of message name lookup by implementing integer message names
class Message(object):
"""Class wrapping a freenet message"""
@@ -666,6 +672,8 @@
@param name: name of the client instance or '' (for debugging)
@param conectionName: name of the connection
@param verbosity: verbosity level for debugging
+
+ @ivar events: events the client supports
"""
self._connectionName = connectionName
@@ -673,6 +681,8 @@
self._log = logging.getLogger(name)
self._socket = None
+ self.events = self.Events()
+
self.setVerbosity(verbosity)
atexit.register(self.close)
@@ -697,7 +707,7 @@
@param port: (int) port of the node
@param repeat: (int) how many seconds try to connect before giving up
@param timeout: (int) how much time to wait before another attempt to connect
- @event: EventConnected(event, params). Triggered as soon as the client is connected. Params
+ @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
"""
@@ -736,7 +746,7 @@
timeElapsed += SocketTimeout
yield None
elif msg.name == self.Message.NodeHello:
- self.EventClientConnected(msg.params)
+ self.events.ClientConnected(msg.params)
yield msg.params
raise StopIteration
else:
@@ -751,9 +761,7 @@
self._log.info(self.LogMessages.ConnectingFailed)
raise StopIteration
-
-
-
+
def handleMessage(self, msg):
"""Handles a message from the freenet node
@param msg: (Message) to handle
@@ -768,7 +776,7 @@
code = msg['Code']
if code == self.ProtocolError.ShuttingDown:
self.close()
- self.EventClientDisconnect({'DisconnectReason': DisconnectReason.Shutdown})
+ self.events.ClientDisconnect({'DisconnectReason': DisconnectReason.Shutdown})
return True
raise self.ProtocolError(msg)
@@ -777,10 +785,6 @@
##
## TestDDA
##
- ## Note: if both, ReadDirectoryAllowed and WriteDirectoryAllowed are
- ## set to false, the node sends a ProtocolError (7, 'Invalid message').
- ## Have to handle this!
- ##
####################################################
elif msg.name == self.Message.TestDDAReply:
fpathWrite = msg.params.get('WriteFilename', None)
@@ -810,7 +814,7 @@
for fpath in self._ddaTmpFiles:
saveRemoveFile(fpath)
self._ddaTmpFiles = []
- self.EventTestDDAComplete(msg.params)
+ self.events.TestDDAComplete(msg.params)
return True
####################################################
@@ -819,11 +823,11 @@
##
####################################################
elif msg.name == self.Message.ConfigData:
- self.EventConfigData(msg.params)
+ self.events.ConfigData(msg.params)
return True
elif msg.name == self.Message.NodeData:
- self.EventNodeData(msg.params)
+ self.events.NodeData(msg.params)
return True
####################################################
@@ -832,15 +836,15 @@
##
####################################################
elif msg.name == self.Message.EndListPeers:
- self.EventEndListPeers(msg.params)
+ self.events.EndListPeers(msg.params)
return True
elif msg.name == self.Message.EndListPeerNotes:
- self.EventEndListPeerNotes(msg.params)
+ self.events.EndListPeerNotes(msg.params)
return True
elif msg.name == self.Message.Peer:
- self.EventPeer(msg.params)
+ self.events.Peer(msg.params)
return True
elif msg.name == self.Message.PeerNote:
@@ -848,28 +852,28 @@
if note:
note = base64.decodestring(note)
msg['NoteText'] = note
- self.EventPeerNote(msg.params, note)
+ self.events.PeerNote(msg.params, note)
return True
elif msg.name == self.Message.PeerRemoved:
- self.EventPeerRemoved(msg.params)
+ self.events.PeerRemoved(msg.params)
return True
elif msg.name == self.Message.UnknownNodeIdentifier:
- self.EventUnknownNodeIdentifier(msg.params)
+ self.events.UnknownNodeIdentifier(msg.params)
return True
####################################################
##
- ## Get related messages
+ ## ClientGet related messages
##
####################################################
elif msg.name == self.Message.DataFound:
if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo):
- self.EventClientGetInfo(msg.params)
+ self.events.ClientGetInfo(msg.params)
return True
- self.EventDataFound(msg.params)
+ self.events.DataFound(msg.params)
return True
elif msg.name == self.Message.GetFailed:
@@ -880,29 +884,29 @@
'Metadata.ContentType': msg.get('ExpectedMetadata.ContentType', ''),
'DataLength': msg.get('ExpectedDataLength', '')
}
- self.EventClientGetInfo(params)
+ self.events.ClientGetInfo(params)
return True
- self.EventGetFailed(msg.params)
+ self.events.GetFailed(msg.params)
return True
elif msg.name == self.Message.SimpleProgress:
if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo):
- self.EventClientGetInfoProgress(msg.params)
+ self.events.ClientGetInfoProgress(msg.params)
else:
- self.EventSimpleProgress(msg.params)
+ self.events.SimpleProgress(msg.params)
return True
elif msg.name == self.Message.IdentifierCollision:
- self.EventIdentifierCollision(msg.params)
+ self.events.IdentifierCollision(msg.params)
return True
elif msg.name == self.Message.PersistentRequestModified:
- self.EventPersistentRequestModified(msg.params)
+ self.events.PersistentRequestModified(msg.params)
return True
elif msg.name == self.Message.PersistentRequestRemoved:
- self.EventPersistentRequestRemoved(msg.params)
+ self.events.PersistentRequestRemoved(msg.params)
return True
####################################################
@@ -911,7 +915,7 @@
##
####################################################
elif msg.name == self.Message.SSKKeypair:
- self.EventSSKKeypair(msg.params)
+ self.events.SSKKeypair(msg.params)
return True
@@ -961,7 +965,7 @@
'Exception': msg['Exception'],
'Details': msg['Details']
}
- self.EventClientDisconnected(params)
+ self.events.ClientDisconnected(params)
raise self.SocketError(msg['Details'])
self.handleMessage(msg)
return msg
@@ -999,7 +1003,7 @@
'Exception': socket.error,
'Details': d
}
- self.EventClientDisconnected(params)
+ self.events.ClientDisconnected(params)
raise self.SocketError(d)
return msg
@@ -1089,9 +1093,9 @@
def listPeerNotes(self, identity):
"""Lists all text notes associated to a peer
@param identifier: peer as returned in a call to L{peerList}
- @event: EventListPeerNotes(event).
- @event: EventListPeerNote(event, note).
- @event: EventEndListPeerNotes(event).
+ @event: ListPeerNotes(event).
+ @event: ListPeerNote(event, note).
+ @event: EndListPeerNotes(event).
"""
self.sendMessage(
self.Message.ListPeerNotes,
@@ -1104,8 +1108,8 @@
@param withMetaData: include meta data for each peer?
@param withVolantile: include volantile data for each peer?
- @event: EvenPeer(event, peer).
- @event: EventEndListPeers(event, params).
+ @event: Peer(event, peer).
+ @event: EndListPeers(event, params).
"""
self.sendMessage(
self.Message.ListPeers,
@@ -1183,7 +1187,7 @@
@param uri: uri of the file to request info about
@event: clientGetInfo(event, params). If success, params will contain a key 'Metadata.ContentType'
and 'DataLength'. Both may be '' (empty string)
- @event: clientGetInfoProgress(event, params). Triggered instead of EventSimpleProgress
+ @event: GetInfoProgress(event, params). Triggered instead ofSimpleProgress
@note: for other events see: L{clientGet}
@return: (str) request identifier
"""
@@ -1201,7 +1205,7 @@
)
return identifier
-
+
def testDDA(self, directory, wantReadDirectory=None, wantWriteDirectory=None):
"""Tests a directory for read / write access
@param directory: directory to test
@@ -1213,7 +1217,19 @@
and a directory for read access before uploading content from it
@note: the node does not like both parameters being False and will respond with a protocol error in this
case. Take care of that.
+ @note: if a directory is no longer needed, best pratice is to free resources by calling
+ testDDA() with both parameters set to False.
"""
+
+ # if both, ReadDirectoryAllowed and WriteDirectoryAllowed are
+ # set to false, the node sends a ProtocolError (7, 'Invalid message')
+ # No idea what the error is good for... so simply ignore the request.
+ #
+ # already file a bug report that (False, False) should be interpreted
+ # by the node to forgett a directory (free resources)
+ if not wantReadDirectory and not wantWriteDirectory:
+ return
+
msg = self.Message(
self.Message.TestDDARequest,
Directory=directory,
@@ -1223,6 +1239,7 @@
if wantWriteDirectory is not None:
msg['WantWriteDirectory'] = self.fcpBool(wantWriteDirectory)
self.sendMessageEx(msg)
+
##########################################################
##
## others
@@ -1246,7 +1263,8 @@
#*****************************************************************************
if __name__ == '__main__':
c = FcpClient(name='test', verbosity=FcpClient.Verbosity.Debug)
- nodeHello = c.connect()
+
+ for nodeHello in c.connect(): pass
if nodeHello is not None:
@@ -1289,13 +1307,13 @@
print '%s=%s' % (prefix, value)
print
- c.EventConfigData += cb
+ c.events.ConfigData += cb
oldVerbosity = c.verbosity()
##c.setVerbosity(c.Verbosity.Warning)
print '\n>> Requesting config\n'
c.getConfig()
- for i in xrange(1):
+ for i in xrange(5):
c.next()
c.setVerbosity(oldVerbosity)
@@ -1308,7 +1326,7 @@
def cb(event, params):
print params
- c.EventSSKKeypair += cb
+ c.events.SSKKeypair += cb
c.generateSSK()
for i in xrange(1):
c.next()
@@ -1319,7 +1337,7 @@
def cb(event, params):
print params
- c.EventTestDDAComplete += cb
+ c.events.TestDDAComplete += cb
d = os.path.dirname(os.path.abspath(__file__))
c.testDDA(d, True, True)
for i in xrange(4):
@@ -1333,9 +1351,9 @@
if params['opennet'] == c.FcpFalse:
c.listPeerNotes(params['identity'])
- c.EventPeer += cb
+ c.events.Peer += cb
c.listPeers()
- for i in xrange(100):
+ for i in xrange(120):
c.next()
#testListPeerNotes()
@@ -1345,7 +1363,7 @@
def cb(event, params):
print params
- c.EventClientGetInfo += cb
+ c.events.ClientGetInfo += cb
identifier = c.clientGetInfo('CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg')
for i in xrange(20):
c.next()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-01 14:58:11
|
Revision: 32
http://fclient.svn.sourceforge.net/fclient/?rev=32&view=rev
Author: jUrner
Date: 2007-11-01 07:58:13 -0700 (Thu, 01 Nov 2007)
Log Message:
-----------
...
Added Paths:
-----------
trunk/fclient/doc/
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-01 14:58:00
|
Revision: 31
http://fclient.svn.sourceforge.net/fclient/?rev=31&view=rev
Author: jUrner
Date: 2007-11-01 07:58:04 -0700 (Thu, 01 Nov 2007)
Log Message:
-----------
...
Added Paths:
-----------
trunk/fclient/lang/
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-01 14:56:48
|
Revision: 30
http://fclient.svn.sourceforge.net/fclient/?rev=30&view=rev
Author: jUrner
Date: 2007-11-01 07:56:53 -0700 (Thu, 01 Nov 2007)
Log Message:
-----------
...
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/__init__.py
Modified: trunk/fclient/fclient_lib/fcp/__init__.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/__init__.py 2007-11-01 14:55:49 UTC (rev 29)
+++ trunk/fclient/fclient_lib/fcp/__init__.py 2007-11-01 14:56:53 UTC (rev 30)
@@ -1 +1 @@
-
+
\ 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...> - 2007-11-01 14:55:44
|
Revision: 29
http://fclient.svn.sourceforge.net/fclient/?rev=29&view=rev
Author: jUrner
Date: 2007-11-01 07:55:49 -0700 (Thu, 01 Nov 2007)
Log Message:
-----------
added __contains__() method to events
Modified Paths:
--------------
trunk/fclient/fclient_lib/pyex/events.py
Modified: trunk/fclient/fclient_lib/pyex/events.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/events.py 2007-11-01 14:55:22 UTC (rev 28)
+++ trunk/fclient/fclient_lib/pyex/events.py 2007-11-01 14:55:49 UTC (rev 29)
@@ -26,6 +26,12 @@
for o in self.observers:
o(self, *args, **kwargs)
+ def __contains__(self, observer):
+ """Checks if an observer is aleady registered
+ @return: bool
+ """
+ return observer in self.observers
+
def __iadd__(self, observer):
"""Adds an observer to the event
@note: the observer will be called with the event as first paraeter,
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-01 14:55:17
|
Revision: 28
http://fclient.svn.sourceforge.net/fclient/?rev=28&view=rev
Author: jUrner
Date: 2007-11-01 07:55:22 -0700 (Thu, 01 Nov 2007)
Log Message:
-----------
added global config module
Added Paths:
-----------
trunk/fclient/config.py
Added: trunk/fclient/config.py
===================================================================
--- trunk/fclient/config.py (rev 0)
+++ trunk/fclient/config.py 2007-11-01 14:55:22 UTC (rev 28)
@@ -0,0 +1,131 @@
+"""Some app wide globals
+
+"""
+
+import os
+import thread
+
+from PyQt4 import QtCore
+from fclient_lib.qt4ex import assistant, language, settingsbase, resources
+
+_ = os.path.join
+#***********************************************************************
+#
+#***********************************************************************
+OrgName = 'Juergen Urner'
+AppName = 'fclient'
+
+TranslationPrefixes = ('fclient_', )
+
+BaseDir = os.path.dirname(os.path.abspath(__file__))
+DocDir = os.path.join(BaseDir, 'doc')
+LanguageDir = os.path.join(BaseDir, 'lang')
+ResourceDir = os.path.join(BaseDir, 'res')
+
+AssistantProfile = _(DocDir, 'assistant.adp')
+DocPages = { #TODO: just sample code here
+ "Index": _(DocDir, 'index.html'),
+ "DlgPreferences": _(DocDir, 'dialogs', 'preferences.html'),
+ }
+
+#*****************************************************************************
+#
+#*****************************************************************************
+class Config(object):
+
+ def __init__(self, parent):
+ """
+ @ivar assistant: app global user docs management
+ @ivar defaultDownloadsDir: default directory for downloads
+ @ivar fcpClient: app global FcpClient
+ @ivar language: app global language management
+ @ivar nodeHello: NodeHello message as returned from the client as soon as a connection
+ is established
+ @ivar resources: app global resource management
+ @ivar settings: global settings
+
+ """
+
+ #TODO: assistant can not handle locale dependend docs yet
+ self.assistant = assistant.Assistant(parent, profile=AssistantProfile, pages=DocPages)
+ self.defaultDownloadsDir = os.path.join(BaseDir, 'downloads')
+ self.fcpClient = None
+ self.language = language.Language([LanguageDir, ], translationPrefixes=TranslationPrefixes)
+ self.nodeHello = None
+ self.resources = resources.Resources([ResourceDir, ], )
+ self.settings = settingsbase.Settings(OrgName, AppName)
+
+ self._pollTimer = QtCore.QTimer()
+ self._connectMethod = None
+ self._connectTimer = QtCore.QTimer()
+ QtCore.QObject.connect(
+ self._connectTimer,
+ QtCore.SIGNAL('timeout()'),
+ self.handleFcpConnect
+ )
+
+
+ def connectFcpNode(self, eventConnectedHandler):
+ """Establishes a connection to the freenet node
+ @param eventConnectedHandler: handler to handle the EventClientConnected of the client
+ @note: the clients EventClientConnected gets automatically connected in the call. So no
+ need to connect once more.
+ @note: make shure not to access the 'fcpClient' or 'nodeHello' attrs of the config before
+ the handler gets called.
+ """
+ if self.fcpClient is None:
+ # some magic here to determine the client version to use
+ from fclient_lib.fcp.fcp2_0 import FcpClient
+ self.fcpClient = FcpClient(
+ connectionName='',
+ verbosity=FcpClient.Verbosity.Debug,
+ )
+ self.fcpClient.EventClientDisconnected += self.handleFcpClientDisconnected
+ QtCore.QObject.connect(
+ self._pollTimer,
+ QtCore.SIGNAL('timeout()'),
+ self.fcpClient.next
+ )
+ self._connectMethod = self.fcpClient.connect()
+ self.fcpClient.EventClientConnected += eventConnectedHandler
+ self._connectTimer.start(100)
+
+ else:
+ cb(self.fcpClient.EventClientConnected, self.nodeHello)
+
+
+ def handleFcpConnect(self):
+ """Handles establishing of the node connection"""
+ self._connectTimer.stop()
+ try:
+ result = self._connectMethod.next()
+ if result is not None:
+ self.nodeHello = result
+ self._pollTimer.start(100)
+
+ except StopIteration:
+ return
+ self._connectTimer.start()
+
+
+ def handleFcpClientDisconnected(self, params):
+ """Handles client disconneting"""
+ self.fcpClient = None
+ self.nodeHello = None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-01 14:54:48
|
Revision: 27
http://fclient.svn.sourceforge.net/fclient/?rev=27&view=rev
Author: jUrner
Date: 2007-11-01 07:54:53 -0700 (Thu, 01 Nov 2007)
Log Message:
-----------
adapt to recent FcpClient changes
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/test_fcp2_0/test_message_object.py
Modified: trunk/fclient/fclient_lib/fcp/test_fcp2_0/test_message_object.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/test_fcp2_0/test_message_object.py 2007-11-01 14:54:34 UTC (rev 26)
+++ trunk/fclient/fclient_lib/fcp/test_fcp2_0/test_message_object.py 2007-11-01 14:54:53 UTC (rev 27)
@@ -13,7 +13,8 @@
sys.path.insert(0, parentdir(2, __file__))
-from fcp2_0 import Message
+from fcp2_0 import FcpClient
+Message = FcpClient.Message
sys.path.pop(0)
del parentdir
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-01 14:54:29
|
Revision: 26
http://fclient.svn.sourceforge.net/fclient/?rev=26&view=rev
Author: jUrner
Date: 2007-11-01 07:54:34 -0700 (Thu, 01 Nov 2007)
Log Message:
-----------
bit of refactoring
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-01 14:53:13 UTC (rev 25)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-11-01 14:54:34 UTC (rev 26)
@@ -109,41 +109,34 @@
fp.close()
return written
-#**********************************************************************
-# classes
-#**********************************************************************
-
-
#**************************************************************************
# fcp client
#**************************************************************************
-#TODO: no idea what happens on reconnect if socket died. What about running jobs?
#TODO: name as specified in NodeHello seems to be usable to keep jobs alive. Have to test this.
-#TODO: do not mix directories as identifiers with identifiers (might lead to collisions)
-#TODO: how to handle (ProtocolError code 18: Shutting down)?
+#TODO: events should be FcpClient.event.PeerNote not FcpClient.EventPeerNote
class FcpClient(events.Events):
"""Fcp client implementation
"""
_events_ = (
+ 'EventClientConnected',
+ 'EventClientDisconnected',
+
# config related events
'EventConfigData',
'EventNodeData',
#Peer related events
- 'EventListPeers',
'EventEndListPeers',
'EventPeer',
'EventPeerRemoved',
- 'EventUnknownIdentifier',
+ 'EventUnknownNodeIdentifier',
'EventListPeerNotes',
'EventEndListPeerNotes',
'EventPeerNote',
- 'EventShutdown',
- 'EventSocketDied',
# get / put related events
'EventTestDDAComplete',
@@ -166,6 +159,11 @@
Version = '2.0'
FcpTrue = 'true'
FcpFalse = 'false'
+ class DisconnectReason:
+ """Reason for client disconnect"""
+ Shutdown = '1'
+ SocketDied = '2'
+
class FetchError(Exception):
"""All fetch errors supported by the client"""
@@ -692,14 +690,16 @@
saveRemoveFile(fpath)
- #TODO: an iterator would be nice to enshure Guis stay responsitive in the call
+
def connect(self, host=DefaultFcpHost, port=DefaultFcpPort, repeat=20, timeout=0.5):
- """Establishes the connection to a freenet node
+ """Iterator to stablish a connection to a freenet node
@param host: (str) host of th node
@param port: (int) port of the node
@param repeat: (int) how many seconds try to connect before giving up
@param timeout: (int) how much time to wait before another attempt to connect
- @return: (Message) NodeHello if successful,None otherwise
+ @event: EventConnected(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._clientHello = None
self._log.info(self.LogMessages.Connecting)
@@ -716,7 +716,7 @@
try:
self._socket.connect((host, port))
except Exception, d:
- pass
+ yield None
else:
self._log.info(self.LogMessages.Connected)
@@ -733,9 +733,12 @@
while timeElapsed <= repeat:
msg = self.next()
if msg.name == self.Message.ClientSocketTimeout:
- timeElapsed += SocketTimeout
+ timeElapsed += SocketTimeout
+ yield None
elif msg.name == self.Message.NodeHello:
- return msg.params
+ self.EventClientConnected(msg.params)
+ yield msg.params
+ raise StopIteration
else:
break
break
@@ -746,9 +749,11 @@
time.sleep(timeout)
self._log.info(self.LogMessages.ConnectingFailed)
- return None
+ raise StopIteration
+
+
-
+
def handleMessage(self, msg):
"""Handles a message from the freenet node
@param msg: (Message) to handle
@@ -763,7 +768,7 @@
code = msg['Code']
if code == self.ProtocolError.ShuttingDown:
self.close()
- self.EventShutdown(msg.params)
+ self.EventClientDisconnect({'DisconnectReason': DisconnectReason.Shutdown})
return True
raise self.ProtocolError(msg)
@@ -851,7 +856,7 @@
return True
elif msg.name == self.Message.UnknownNodeIdentifier:
- self.EventUnknownIdentifier(msg.params)
+ self.EventUnknownNodeIdentifier(msg.params)
return True
####################################################
@@ -951,8 +956,13 @@
"""
msg = self.Message.fromSocket(self._socket)
if msg.name == self.Message.ClientSocketDied:
- self.EventSocketDied(msg['Exception'], msg['Details'])
- raise SocketError(msg['Details'])
+ params = {
+ 'DisconnectReason': DisconnectReason.SocketDied,
+ 'Exception': msg['Exception'],
+ 'Details': msg['Details']
+ }
+ self.EventClientDisconnected(params)
+ raise self.SocketError(msg['Details'])
self.handleMessage(msg)
return msg
@@ -984,8 +994,13 @@
except socket.error, d:
self._log.info(self.LogMessages.SocketDied)
self.close()
- self.EventSocketDied(socket.error, d)
- raise SocketError(d)
+ params = {
+ 'DisconnectReason': DisconnectReason.SocketDied,
+ 'Exception': socket.error,
+ 'Details': d
+ }
+ self.EventClientDisconnected(params)
+ raise self.SocketError(d)
return msg
#########################################################
@@ -1001,7 +1016,8 @@
return self.FcpTrue if pythonBool else self.FcpFalse
- def newIdentifier(self, prefix=None):
+ @classmethod
+ def newIdentifier(clss, prefix=None):
"""Returns a new unique identifier
@return: (str) uuid
"""
@@ -1009,7 +1025,7 @@
return prefix + str(uuid.uuid4())
return str(uuid.uuid4())
-
+
def pythonBool(self, fcpBool):
"""Converts a fcp bool to a python bool
@param pythonBool: 'true' or 'false'
@@ -1017,6 +1033,15 @@
"""
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
+ @return: (int) python time
+ """
+ fcpTime = int(fcpTime)
+ return fcpTime / 1000
+
########################################################
##
## Config related methods
@@ -1054,14 +1079,14 @@
## Peer related methods
##
########################################################
- def listPeer(self, identifier):
+ def listPeer(self, identity):
self.jobClient.sendMessage(
self.Message.ListPeer,
- NodeIdentifier=identifier,
+ NodeIdentifier=identity,
)
- def listPeerNotes(self, identifier):
+ def listPeerNotes(self, identity):
"""Lists all text notes associated to a peer
@param identifier: peer as returned in a call to L{peerList}
@event: EventListPeerNotes(event).
@@ -1070,7 +1095,7 @@
"""
self.sendMessage(
self.Message.ListPeerNotes,
- NodeIdentifier=identifier
+ NodeIdentifier=identity
)
@@ -1079,9 +1104,8 @@
@param withMetaData: include meta data for each peer?
@param withVolantile: include volantile data for each peer?
- @event: EventListPeers(event).
@event: EvenPeer(event, peer).
- @event: EventEndListPeers(event).
+ @event: EventEndListPeers(event, params).
"""
self.sendMessage(
self.Message.ListPeers,
@@ -1090,10 +1114,10 @@
)
- def modifyPeer(self, identifier, allowLocalAddresses=None, isDisabled=None, isListenOnly=None):
+ def modifyPeer(self, identity, allowLocalAddresses=None, isDisabled=None, isListenOnly=None):
msg = Message(
self.Message.ModifyPeer,
- NodeIdentifier=identifier,
+ NodeIdentifier=identity,
)
if allowLocalAddresses is not None:
msg['AllowLocalAddresses'] = self.fcpBool(allowLocalAddresses)
@@ -1105,20 +1129,20 @@
self.sendMessageEx(msg)
- def modifyPeerNote(self, identifier, note):
+ def modifyPeerNote(self, identity, note):
self.sendMessage(
self.Message.ModifyPeerNote,
- NodeIdentifier=identifier,
+ NodeIdentifier=identity,
#NOTE: currently fcp supports only this one type
PeerNoteType=self.PeerNoteType.Private,
NoteText=note
)
- def removePeer(self, identifier):
+ def removePeer(self, identity):
self.sendMessage(
self.Message.RemovePeer,
- NodeIdentifier=identifier,
+ NodeIdentifier=identity,
)
##########################################################
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-11-01 14:53:12
|
Revision: 25
http://fclient.svn.sourceforge.net/fclient/?rev=25&view=rev
Author: jUrner
Date: 2007-11-01 07:53:13 -0700 (Thu, 01 Nov 2007)
Log Message:
-----------
implemented a basic widget to play around with peers
Added Paths:
-----------
trunk/fclient/fclient_widgets/
trunk/fclient/fclient_widgets/__init__.py
trunk/fclient/fclient_widgets/peer_widget.py
Added: trunk/fclient/fclient_widgets/__init__.py
===================================================================
--- trunk/fclient/fclient_widgets/__init__.py (rev 0)
+++ trunk/fclient/fclient_widgets/__init__.py 2007-11-01 14:53:13 UTC (rev 25)
@@ -0,0 +1 @@
+
Added: trunk/fclient/fclient_widgets/peer_widget.py
===================================================================
--- trunk/fclient/fclient_widgets/peer_widget.py (rev 0)
+++ trunk/fclient/fclient_widgets/peer_widget.py 2007-11-01 14:53:13 UTC (rev 25)
@@ -0,0 +1,211 @@
+"""Sketch for a widget handling node peers
+
+"""
+
+import os, sys
+
+#--> rel import hack
+def parentdir(n, fpath):
+ fpath = os.path.abspath(fpath)
+ for i in xrange(n):
+ fpath = os.path.dirname(fpath)
+ return fpath
+sys.path.insert(0, parentdir(2, __file__))
+
+
+import config
+from fclient_lib import fcp
+from fclient_lib.pyex import numbers
+
+
+sys.path.pop(0)
+del parentdir
+#<-- rel import hack
+
+
+import time
+from PyQt4 import QtCore, QtGui
+#***************************************************************************************************
+#
+#***************************************************************************************************
+class PeerWidget(QtGui.QTreeWidget):
+
+ HeaderIndexStatus = 0
+ HeaderIndexName = 1
+ HeaderIndexLastConnected = 2
+ HeaderIndexNotes = 3
+
+
+ def __init__(self, parent, cfg=None):
+
+ self._cfg = cfg
+ self._fcpClient = None
+ self._fcpEvents = None
+ self._isCreated = False
+ self._peers = {} # identity --> item
+
+ QtGui.QWidget.__init__(self, parent)
+
+ # setup config
+ if self._cfg is None:
+ self._cfg = config.Config(self)
+
+ # setup tree
+ self.setUniformRowHeights(True)
+ headerLabels = [
+ self.trUtf8('Status'),
+ self.trUtf8('Name'),
+ self.trUtf8('LastConnected'),
+ self.trUtf8('Notes'),
+ ]
+ self.setHeaderLabels(headerLabels)
+ #self.setRootIsDecorated(False)
+ self.setColumnCount(len(headerLabels))
+
+ header = self.header()
+ header.setResizeMode(header.ResizeToContents)
+ header.setStretchLastSection(True)
+
+
+
+ def showEvent(self, event):
+ if not self._isCreated:
+ self._isCreated = True
+ self._cfg.connectFcpNode(self.handleClientConnected)
+
+
+ def handleClientConnected(self, event, params):
+ self._fcpEvents = (
+ #(self._cfg.fcpClient.EventClientConnected, self.handleClientConnected),
+ (self._cfg.fcpClient.EventClientDisconnected, self.handleClientDisconnected),
+
+ (self._cfg.fcpClient.EventPeer, self.handlePeer),
+ (self._cfg.fcpClient.EventEndListPeers, self.handleEndListPeers),
+ (self._cfg.fcpClient.EventPeerRemoved, self.handlePeerRemoved),
+ (self._cfg.fcpClient.EventUnknownNodeIdentifier, self.handleUnknownNodeIdentifier),
+ )
+
+ # take care to not connect twice
+ for event, observer in self._fcpEvents:
+ if not observer in event:
+ event += observer
+
+ self.clear()
+ self._peers = {}
+ self._cfg.fcpClient.listPeers()
+
+
+
+ def handleClientDisconnected(self, event, params):
+ pass
+
+
+ def handlePeer(self, event, params):
+
+ timeLastConnected = params.get('metadata.timeLastConnected', None)
+ timeLastConnected = self.timeToTimeDelta(timeLastConnected)
+
+ identity = params['identity']
+ item = self._peers.get(identity, None)
+ # add item if necessary
+ if item is None:
+ itemStrings = [
+ params.get('volatile.status', ''),
+ params.get('myName', ''),
+ timeLastConnected,
+ ''
+ ]
+ item = QtGui.QTreeWidgetItem(self, itemStrings)
+ item.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(identity))
+ self._peers[identity] = item
+
+ # update item
+ else:
+ item.settext(self.HeaderIndexLastConnected, timeLastConnected)
+
+
+
+ def handleEndListPeers(self, event, params):
+ pass
+
+
+
+ def handlePeerRemoved(self, peer):
+ pass
+
+
+ def handleUnknownNodeIdentifier(self, params):
+ pass
+
+
+
+ def timeToTimeDelta(self, t):
+ try:
+ t = self._cfg.fcpClient.pythonTime(t)
+ except:
+ t = self.trUtf8('Unknown')
+ else:
+ if t == 0:
+ t = self.trUtf8('Never')
+ else:
+ t = numbers.format_time_delta(t, time.time())
+ return t
+
+
+
+ def identityFromItem(self, item):
+ v = item.data(0, QtCore.Qt.UserRole)
+ return str(v.toString())
+
+
+
+'''########################################################
+Sample Peer message
+>> Peer
+>> lastGoodVersion=Fred,0.7,1.0,1069
+>> myName=Ismael
+>> auth.negTypes=1;2
+>> volatile.routingBackoffLength=1000
+>> physical.udp=91.3.73.112:8904;91.3.60.151:8904
+>> metadata.timeLastRoutable=1192973814473
+>> metadata.routableConnectionCheckCount=37063
+>> volatile.totalBytesIn=0
+>> metadata.detected.udp=91.3.118.59:8904
+>> version=Fred,0.7,1.0,1070
+>> location=0.6583927959165321
+>> volatile.percentTimeRoutableConnection=6.111216037557672
+>> volatile.averagePingTime=1.0
+>> volatile.overloadProbability=0.0
+>> volatile.idle=953190074
+>> volatile.routingBackoff=0
+>> opennet=false
+>> metadata.timeLastReceivedPacket=1192973814459
+>> metadata.hadRoutableConnectionCount=2265
+>> volatile.status=DISCONNECTED
+>> volatile.totalBytesOut=571619
+>> ark.number=28
+>> identity=VFe6SIPloo42YOs8woDvPtnboEl2i1qMR~1vQx5tPwI
+>> dsaGroup.q=ALFDNoq81R9Y1kQNVBc5kzmk0VvvCWosXY5t9E9S1tN5
+>> dsaGroup.p=AIYIrE9VNhM38qPjirGGT-PJjWZBHY0q-JxSYyDFQfZQeOhrx4SUpdc~SppnWD~UHymT7WyX28eV3YjwkVyc~--H5Tc83hPjx8qQc7kQbrMb~CJy7QBX~YSocKGfioO-pwfRZEDDguYtOJBHPqeenVDErGsfHTCxDDKgL2hYM8Ynj8Kes0OcUzOIVhShFSGbOAjJKjeg82XNXmG1hhdh2tnv8M4jJQ9ViEj425Mrh6O9jXovfPmcdYIr3C~3waHXjQvPgUiK4N5Saf~FOri48fK-PmwFZFc-YSgI9o2-70nVybSnBXlM96QkzU6x4CYFUuZ7-B~je0ofeLdX7xhehuk
+>> dsaPubKey.y=dmlrTkJrXzfi17AVinCIV9rJKQa9FkFV8ZjzJ~lyuEtWl8b7S-RqMocZEbtjDAWVV79yxCBD-dpmbz0X7EbnCUlDhlO-2kqo3eqDwMNf3CjU6qbSBmvzp2BkNYXt1EO6VYNVojbVyxv1mX~IGcXREXJrvIdxj64cJiESKZglJX0Rv2owCFvZBGCslEgqENPbKXT8QqIGkYUiSrDWGM-CVnHkjeBfZjIAvmcjTMMeADV6kyZ7yshG-pmxABL6Z2IAr69bM9VZ26acSV55TCZbuKdsVgC6B7G8ucm9WdKC2~tEdBns-0RZMfh4eOH969MTRXojLR5RpzbNFr-OeNRAvQ
+>> volatile.routingBackoffPercent=0.0
+>> metadata.timeLastConnected=1192973814473
+>> testnet=false
+>> dsaGroup.g=UaRatnDByf0QvTlaaAXTMzn1Z15LDTXe-J~gOqXCv0zpz83CVngSkb--bVRuZ9R65OFg~ATKcuw8VJJwn1~A9p5jRt2NPj2EM7bu72O85-mFdBhcav8WHJtTbXb4cxNzZaQkbPQUv~gEnuEeMTc80KZVjilQ7wlTIM6GIY~ZJVHMKSIkEU87YBRtIt1R~BJcnaDAKBJv~oXv1PS-6iwQRFMynMEmipfpqDXBTkqaQ8ahiGWA41rY8d4jDhrzIgjvkzfxkkcCpFFOldwW8w8MEecUoRLuhKnY1sm8nnTjNlYLtc1Okeq-ba0mvwygSAf4wxovwY6n1Fuqt8yZe1PDVg
+>> ark.pubURI=SSK@1VhLypie-suqO-7MrMEK7YZLvTHkXstKqhq3noy-PY4,RROa7KmxsjivsNKHIlEy9kHmM6VyVJ0lLRbcdwTQRK4,AQACAAE/ark
+>>EndMessage
+###########################################################'''
+
+#***************************************************************************************************
+#
+#***************************************************************************************************
+if __name__ == '__main__':
+ import sys
+
+ app = QtGui.QApplication(sys.argv)
+ w = PeerWidget(None)
+ w.show()
+ res = app.exec_()
+ sys.exit(res)
+
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-10-31 13:05:49
|
Revision: 24
http://fclient.svn.sourceforge.net/fclient/?rev=24&view=rev
Author: jUrner
Date: 2007-10-31 06:05:50 -0700 (Wed, 31 Oct 2007)
Log Message:
-----------
started a todo.txt, with some major tasks
Added Paths:
-----------
trunk/TODO.txt
Added: trunk/TODO.txt
===================================================================
--- trunk/TODO.txt (rev 0)
+++ trunk/TODO.txt 2007-10-31 13:05:50 UTC (rev 24)
@@ -0,0 +1,47 @@
+Major todos
+********************************************************************************************
+x. fist widgets to implement: up / downloads, browser, message board
+
+ Multipurpose up / downnload widget should be ready to go in relatively short time.
+ This widget is not only intendet to be used for well.. up / downloads, but should
+ serve as a base component for message board and browser. I like to see visual
+ feedback on every up / download happening in the Gui.
+
+
+x. message board format is quite unclear to me. There are some specs
+ available here:
+ [http://jtcfrost.svn.sourceforge.net/viewvc/jtcfrost/trunk/frost-wot/specs/]
+
+ Some java code from freenet-svn:
+ [http://freenet.googlecode.com/svn/trunk/freenet/src/freenet/frost/message/]
+
+ ..and maybe more.
+
+
+x. quite a lot of consts are undocumented. Consts used in config settings for example.
+ Or the string representations of consts describing the status of peers. The node
+ says hapily this peer is "CONNECTED". But couldn't find any info on the const, except
+ tracking it down to the PEER_NODE_STATUS_CONNECTED const as defined in
+ [http://freenet.googlecode.com/svn/trunk/freenet/node/PeerManager.java]
+
+
+x. a widget that can be used to visualize freenet index files. This thingy should give some header information
+ and allow the user to select any number of uris from an index for download. Already implemented a half
+ way working prototype. But quite unclear how to handle _massive_ indices reliably. Intersting
+ thing would be to allow for automized creation of index files from a bunch of files ready for
+ insertion (wizzard?).
+
+
+x. developer documentation is written in epydoc. Should be 20-30Mb all in all,
+ so.. to huge to ship alongside. A nice-to-have would be an option to allow the user
+ to generate a fresh set on the fly and integrate it in QAssistant. Job is to write
+ a module that can handle epydoc-output --> assistant-profile generation.
+
+ done a similar thing berfore to generate Chms from epydoc output. See
+ [https://frogpie.svn.sourceforge.net/svnroot/frogpie/trunk/sandbox/chm].
+ Shouldn't be too hard to polish this package and add the desired functionality.
+ Basically its about adjusting the package to support output writers. Alternative
+ is to flesh "chm_lib/epydoc_fs.py" out and base a standalone module on it.
+
+
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-10-31 10:00:18
|
Revision: 23
http://fclient.svn.sourceforge.net/fclient/?rev=23&view=rev
Author: jUrner
Date: 2007-10-31 03:00:15 -0700 (Wed, 31 Oct 2007)
Log Message:
-----------
refactored the code and added some more protocol methods and events
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-30 15:11:02 UTC (rev 22)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-31 10:00:15 UTC (rev 23)
@@ -1,10 +1,9 @@
-'''Freenet client protocol 2.0 implementation
+"""Freenet client protocol 2.0 implementation
@newfield event, events
-
Sample code::
client = FcpClient()
@@ -12,45 +11,8 @@
if nodeHello is not None:
# do whatever
-
+"""
-Most method calls can be made either synchron or asynchron::
-
- peers = client.peerList(synchron=True)
- for peer in peers:
- # do whatever
-
-
-To get informed about asynchron events you should connect the relevant events the client provides::
-
- # connect to one single event
- client.EventListNextPeer += MyCallback
-
- # connect to multiple events at once
- client += (
- (client.EventListPeers, MyCallback1),
- (client.EventEndListPeers, MyCallback2),
- )
-
- # each callback is called with the event as first parameter, followed by additional parameters,
- # depending on the event triggered.
- def MyListNextPeerCallback(event, peer):
- print peer
-
- client.peerList(synchron=False)
-
-
- # when event notifications are no longer required, you should always make shure to disconnect from them
- client.EventListNextPeer -= MyCallback
- client -= (
- (client.EventListPeers, MyCallback1),
- (client.EventEndListPeers, MyCallback2),
- )
-
-
-
-'''
-
import atexit
import base64
import logging
@@ -92,192 +54,9 @@
DefaultFcpPort = 9481
SocketTimeout = 0.1
-
-class IdentifierPrefix:
- """Special purpose identifier prefixes"""
-
- FileInfo = 'FileInfo::'
-
-
-class Verbosity:
- Debug = logging.DEBUG
- Info = logging.INFO
- Warning = logging.WARNING
-
-
-class Priorities:
- """All priorities supported by the client"""
-
- Maximum = '0'
- Interactive = '1'
- SemiInteractive = '2'
- Updatable = '3'
- Bulk = '4'
- Prefetch = '5'
- Minimum = '6'
-
- PriorityMin = Minimum
- PriorityDefault = Bulk
-
-
-#TODO: no idea how fcp handles strings as in <Peer volatile.status=CONNECTED>
-# all I could find in the sources where these constants as in PEER_NODE_STATUS_CONNECTED
-# in --> freenet/node/PeerManager.java
-class PeerNodeStatus:
- Connected = 1
- RoutingBackedOff = 2
- TooNew = 3
- TooOld = 4
- Disconnected = 5
- NeverConnected = 6
- Disabled = 7
- Bursting = 8
- Listening = 9
- ListenOnly = 10
- ClockProblem = 11
- ConnError = 12
- Disconnecting = 13
-
-
-
-class PeerNoteType:
- """All known peer note types"""
- Private = '1'
-
-
-#************************************************************************************
-# exceptions
-#************************************************************************************
-class FetchError(Exception):
- """All fetch errors supported by the client"""
-
- def __init__(self, msg):
- """
- @param msg: (Message) GetFailed message or its parameters dict
- """
- self.value = '%s (%s, %s)' % (
- msg.get('CodeDescription', 'Unknown error') ,
- msg['Code'],
- msg.get('ExtraDescription', '...'),
- )
- def __str__(self): return self.value
-
- MaxArchiveRecursionExceeded = '1'
- UnknownSplitfileMetadata = '2'
- UnknownMetadata = '3'
- InvalidMetadata = '4'
- ArchiveFailure = '5'
- BlockDecodeError = '6'
- MaxMetadataLevelsExceeded = '7'
- MaxArchiveRestartsExceeded = '8'
- MaxRecursionLevelExceeded = '9'
- NotAnArchve = '10'
- TooManyMetastrings = '11'
- BucketError = '12'
- DataNotFound = '13'
- RouteNotFound = '14'
- RejectedOverload = '15'
- TooManyRedirects = '16'
- InternalError = '17'
- TransferFailed = '18'
- SplitfileError = '19'
- InvalidUri = '20'
- TooBig = '21'
- MetadataTooBig = '22'
- TooManyBlocks = '23'
- NotEnoughMetastrings = '24'
- Canceled = '25'
- ArchiveRestart = '26'
- PermanentRedirect = '27'
- NotAllDataFound = '28'
-
-
-class InsertError(Exception):
- """All insert errors supported by the client"""
-
- def __init__(self, msg):
- """
- @param msg: (Message) PutFailed message or its parameters dict
- """
- self.value = '%s (%s, %s)' % (
- msg.get('CodeDescription', 'Unknown error') ,
- msg['Code'],
- msg.get('ExtraDescription', '...'),
- )
- def __str__(self): return self.value
-
- InvalidUri = '1'
- BucketError = '2'
- InternalError = '3'
- RejectedOverload = '4'
- RouteNotFound = '5'
- FatalErrorInBlocks = '6'
- TooManyRetriesInBlock = '7'
- RouteReallyNotFound = '8'
- Collision = '9'
- Canceled = '10'
-
-
-class ProtocolError(Exception):
- """All protocol errors supported by the client"""
-
- def __init__(self, msg):
- """
- @param msg: (Message) ProtocolError message or its parameters dict
- """
- self.value = '%s (%s, %s)' % (
- msg.get('CodeDescription', 'Unknown error') ,
- msg['Code'],
- msg.get('ExtraDescription', '...'),
- )
- def __str__(self): return self.value
-
- ClientHelloMustBeFirst = '1'
- NoLateClientHellos = '2'
- MessageParseError = '3'
- UriParseError = '4'
- MissingField = '5'
- ErrorParsingNumber = '6'
- InvalidMessage = '7'
- InvalidField = '8'
- FileNotFound = '9'
- DiskTargetExists = '10'
- SameDirectoryExpected = '11'
- CouldNotCreateFile = '12'
- CouldNotWriteFile = '13'
- CouldNotRenameFile = '14'
- NoSuchIdentifier = '15'
- NotSupported = '16'
- InternalError = '17'
- ShuttingDown = '18'
- NoSuchNodeIdentifier = '19' # Unused since 995
- UrlParseError = '20'
- ReferenceParseError = '21'
- FileParseError = '22'
- NotAFile = '23'
- AccessDenied = '24'
- DDADenied = '25'
- CouldNotReadFile = '26'
- ReferenceSignature = '27'
- CanNotPeerWithSelf = '28'
- PeerExists = '29'
- OpennetDisabled = '30'
- DarknetPeerOnly = '31'
-
-class SocketError(Exception): pass
-class FcpError(Exception): pass
#**********************************************************************
-# functions
+# helpers
#**********************************************************************
-def newIdentifier(prefix=None):
- """Returns a new unique identifier
- @return: (str) uuid
- """
- if prefix:
- return prefix + str(uuid.uuid4())
- return str(uuid.uuid4())
-
-
def saveReadFile(fpath):
"""Reads contents of a file in the savest manner possible
@param fpath: file to write
@@ -330,358 +109,14 @@
fp.close()
return written
-def startFreenet(cmdline):
- """Starts freenet
- @param cmdline: commandline to start freenet (like '/freenet/run.sh start' or 'c:\freenet\start.bat')
- @return: (string) whatever freenet returns
- """
- #TODO: on windows it may be necessary to hide the command window
- p = subprocess.Popen(
- args=cmdline,
- shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- stdout, stderr = p.communicate()
- return stdout
-
#**********************************************************************
# classes
#**********************************************************************
-class FcpUri(object):
- """Wrapper class for freenet uris"""
-
-
- KeySSK = 'SSK@'
- KeyKSK = 'KSK@'
- KeyCHK = 'CHK@'
- KeyUSK = 'USK@'
- KeySVK = 'SVK@'
- KeyUnknown = ''
- KeysAll = (KeySSK, KeyKSK, KeyCHK, KeyUSK, KeySVK)
-
- ReUriPattern = re.compile('(%s.*?)(?= |\Z)' % '.*?|'.join(KeysAll), re.I)
- ReKeyPattern = re.compile('(%s)' % '|'.join(KeysAll), re.I)
-
-
- def __init__(self, uri):
- """
- @param uri: uri to wrap
- @param cvar ReUriPattern: pattern matching a freenet uri
- @param cvar ReKeyPattern: pattern matching the key type of a freenet uri
-
- @note: any dfecorations prefixing the freenet part of the uri uri are stripped if possible
-
-
- >>> uri = FcpUri('freenet:SSK@foo/bar')
- >>> str(uri)
- 'SSK@foo/bar'
- >>> uri.keyType() == FcpUri.KeySSK
- True
- >>> uri.split()
- ('SSK@foo', 'bar')
- >>> uri.fileName()
- 'bar'
-
- >>> uri = FcpUri('http://SSK@foo/bar')
- >>> str(uri)
- 'SSK@foo/bar'
-
- # uris not containing freenet keys are left unchanged
- >>> uri = FcpUri('http://foo/bar')
- >>> str(uri)
- 'http://foo/bar'
- >>> uri.keyType() == FcpUri.KeyUnknown
- True
- >>> uri.split()
- ('http://foo/bar', '')
- >>> uri.fileName()
- 'http://foo/bar'
-
- """
- self._uri = uri
-
- result = self.ReUriPattern.search(uri)
- if result is not None:
- self._uri = result.group(0)
-
- def __str__(self):
- return str(self._uri)
-
- def __unicode__(self):
- return unicode(self._uri)
-
- def keyType(self):
- """Retuns the key type of the uri
- @return: one of the Key* consts
- """
- result = self.ReKeyPattern.search(self._uri)
- if result is not None:
- return result.group(0).upper()
- return self.KeyUnknown
-
- def split(self):
- """Splits the uri
- @return: tuple(freenet-key, file-name)
- """
- if self.keyType() != self.KeyUnknown:
- head, sep, tail = self._uri.partition('/')
- return head, tail
- return self._uri, ''
-
- def fileName(self):
- """Returns the filename part of the uri
- @return: str
- """
- head, tail = self.split()
- if tail:
- return tail
- return self._uri
-
-class Message(object):
- """Class wrapping a freenet message"""
-
- __slots__ = ('name', 'data', 'params')
-
- # client messages
- ClientHello = 'ClientHello'
- ListPeer = 'ListPeer' # (since 1045)
- ListPeers = 'ListPeers'
- ListPeerNotes = 'ListPeerNotes'
- AddPeer = 'AddPeer'
- ModifyPeer = 'ModifyPeer'
- ModifyPeerNote = 'ModifyPeerNote'
- RemovePeer = 'RemovePeer'
- GetNode = 'GetNode'
- GetConfig = 'GetConfig' # (since 1027)
- ModifyConfig = 'ModifyConfig' # (since 1027)
- TestDDARequest = 'TestDDARequest' # (since 1027)
- TestDDAResponse = 'TestDDAResponse' # (since 1027)
- GenerateSSK = 'GenerateSSK'
- ClientPut = 'ClientPut'
- ClientPutDiskDir = 'ClientPutDiskDir'
- ClientPutComplexDir = 'ClientPutComplexDir'
- ClientGet = 'ClientGet'
- SubscribeUSK = 'SubscribeUSK'
- WatchGlobal = 'WatchGlobal'
- GetRequestStatus = 'GetRequestStatus'
- ListPersistentRequests = 'ListPersistentRequests'
- RemovePersistentRequest = 'RemovePersistentRequest'
- ModifyPersistentRequest = 'ModifyPersistentRequest'
- Shutdown = 'Shutdown'
-
- # node messages
- NodeHello = 'NodeHello'
- CloseConnectionDuplicateClientName = 'CloseConnectionDuplicateClientName'
- Peer = 'Peer'
- PeerNote = 'PeerNote'
- EndListPeers = 'EndListPeers'
- EndListPeerNotes = 'EndListPeerNotes'
- PeerRemoved = 'PeerRemoved'
- NodeData = 'NodeData'
- ConfigData = 'ConfigData' # (since 1027)
- TestDDAReply = 'TestDDAReply' # (since 1027)
- TestDDAComplete = 'TestDDAComplete' # (since 1027)
- SSKKeypair = 'SSKKeypair'
- PersistentGet = 'PersistentGet'
- PersistentPut = 'PersistentPut'
- PersistentPutDir = 'PersistentPutDir'
- URIGenerated = 'URIGenerated'
- PutSuccessful = 'PutSuccessful'
- PutFetchable = 'PutFetchable'
- DataFound = 'DataFound'
- AllData = 'AllData'
- StartedCompression = 'StartedCompression'
- FinishedCompression = 'FinishedCompression'
- SimpleProgress = 'SimpleProgress'
- EndListPersistentRequests = 'EndListPersistentRequests'
- PersistentRequestRemoved = 'PersistentRequestRemoved' # (since 1016)
- PersistentRequestModified = 'PersistentRequestModified' # (since 1016)
- PutFailed = 'PutFailed'
- GetFailed = 'GetFailed'
- ProtocolError = 'ProtocolError'
- IdentifierCollision = 'IdentifierCollision'
- UnknownNodeIdentifier = 'UnknownNodeIdentifier'
- UnknownPeerNoteType = 'UnknownPeerNoteType'
- SubscribedUSKUpdate = 'SubscribedUSKUpdate'
-
- # client messages (internal use only)
- ClientSocketTimeout = 0
- ClientSocketDied = 1
-
-
- def __init__(self, name, data=None, **params):
- """
- @param name: messge name
- @param data: data associated to the messge (not yet implemented)
- @param params: {field-name: value, ...} of parameters of the message
- @note: all params can be accessed as attributes of the class
- """
- self.data = data
- self.name = name
- self.params = params
-
-
- @classmethod
- def bytesFromSocket(clss, socketObj, n):
- """Reads n bytes from socket
- @param socketObj: socket to read bytes from
- @param n: (int) number of bytes to read
- @return: (tuple) error-message or None, bytes read or None if an error occured
- or no bytes could be read
- """
- error = p = None
- try:
- p = socketObj.recv(n)
- if not p:
- p = None
- raise socket.error('Socket shut down by node')
- except socket.timeout, d: # no new messages in queue
- error = clss(clss.ClientSocketTimeout)
- except socket.error, d:
- error = clss(clss.ClientSocketDied, Exception=socket.error, Details=d)
- return error, p
-
-
- @classmethod
- def fromSocket(clss, socketObj):
- """Reads a message from a socket
- @param socketObj: socket to read a message from
- @return: L{Message} next message from the socket. If the socket dies
- unexpectedly a L{ClientSocketDied} message is returned containing the parameters
- 'Exception' and 'Details'. If the socket times out a L{MessageClientSocketTimout}
- message is returned.
- """
-
- msg = clss(None)
- buf = []
-
- #TODO: to buffer or not to buffer?
- while True:
-
- # get next line from socket
- error, p = clss.bytesFromSocket(socketObj, 1)
- if error:
- return error
-
- if p != '\n':
- buf.append(p)
- continue
- #TODO: check if '\r\n' is allowed in freenet client protocol
- else:
- if buf[-1] == '\r':
- del buf[-1]
-
- line = ''.join(buf)
- buf = []
- if line == 'EndMessage':
- break
-
- # first line == message name
- if msg.name is None:
- msg.name = line
-
- # get data member
- elif line == 'Data':
- remaining = int(msg.params['DataLength'])
- msg.data = ''
- while remaining > 0:
- error, p = clss.bytesFromSocket(socketObj, remaining)
- if error:
- return error
- remaining -= len(p)
- msg.data += p
- break
-
- # get next paramater
- else:
- head, sep, tail = line.partition('=')
- msg.params[head] = tail
- # TODO: errorchek params?
- #if not sep: pass
-
- return msg
-
-
- def get(self, name, default=None):
- """Returns the message parameter 'name' or 'default' """
- return self.params.get(name, default)
-
-
- def __getitem__(self, name):
- """Returns the message parameter 'name' """
- return self.params[name]
-
-
- def __setitem__(self, name, value):
- """Sets the message parameter 'name' to 'value' """
- self.params[name] = value
-
-
- def pprint(self):
- """Returns the message as nicely formated human readable string"""
- out = ['', '>>' + self.name, ]
- for param, value in self.params.items():
- out.append('>> %s=%s' % (param, value))
- out.append('>>EndMessage')
- return '\n'.join(out)
-
-
- def send(self, socketObj):
- """Dumps the message to a socket
- @param socketObj: socket to dump the message to
- """
- socketObj.sendall(self.toString())
-
-
- def toString(self):
- """Returns the message as formated string ready to be send"""
-
- #TODO: just a guess, so maybe remove this check
- if isinstance(self.name, (int, long)):
- raise ValueError('You can not send client internal messages to the node')
- out = [self.name, ]
- for param, value in self.params.items():
- out.append('%s=%s' % (param, value))
- if self.data:
- assert 'DataLength' in self.params, 'DataLength member required'
- n = None
- try:
- n = int(self['DataLength'])
- except ValueError: pass
- assert n is not None, 'DataLength member must be an integer'
- assert n == len(self.data), 'DataLength member must corrospond to lenght of data'
- out.append('Data')
- out.append(self.data)
- else:
- out.append('EndMessage\n')
- return '\n'.join(out)
#**************************************************************************
# fcp client
#**************************************************************************
-class LogMessages:
- """Message strings used for log infos"""
- Connecting = 'Connecting to node...'
- Connected = 'Connected to node'
- ConnectionRetry = 'Connecting to node failed... retrying'
- ConnectingFailed = 'Connecting to node failed'
-
- ClientClose = 'Closing client'
-
- MessageSend = 'SendMessage'
- MessageReceived = 'ReceivedMessage'
-
- JobStart = 'Starting job: '
- JobStop = 'Stopping job: '
- JobsCompleted = 'All jobs completed'
-
-
- KeyboardInterrupt = 'Keyboard interrupt'
- SocketDied = 'Socket died'
-
-
#TODO: no idea what happens on reconnect if socket died. What about running jobs?
#TODO: name as specified in NodeHello seems to be usable to keep jobs alive. Have to test this.
#TODO: do not mix directories as identifiers with identifiers (might lead to collisions)
@@ -692,6 +127,10 @@
_events_ = (
+ # config related events
+ 'EventConfigData',
+ 'EventNodeData',
+
#Peer related events
'EventListPeers',
'EventEndListPeers',
@@ -707,18 +146,18 @@
'EventSocketDied',
# get / put related events
+ 'EventTestDDAComplete',
'EventIdentifierCollision',
- 'EventFileInfo',
- 'EventFileInfoProgress',
+ 'EventClientGetInfo',
+ 'EventClientGetInfoProgress',
'EventDataFound',
'EventGetFailed',
'EventSimpleProgress',
'EventPersistentRequestModified',
'EventPersistentRequestRemoved',
-
-
+
# others
'EventSSKKeypair',
@@ -727,26 +166,513 @@
Version = '2.0'
FcpTrue = 'true'
FcpFalse = 'false'
+ class FetchError(Exception):
+ """All fetch errors supported by the client"""
+
+ def __init__(self, msg):
+ """
+ @param msg: (Message) GetFailed message or its parameters dict
+ """
+ self.value = '%s (%s, %s)' % (
+ msg.get('CodeDescription', 'Unknown error') ,
+ msg['Code'],
+ msg.get('ExtraDescription', '...'),
+ )
+ def __str__(self): return self.value
+
+ MaxArchiveRecursionExceeded = '1'
+ UnknownSplitfileMetadata = '2'
+ UnknownMetadata = '3'
+ InvalidMetadata = '4'
+ ArchiveFailure = '5'
+ BlockDecodeError = '6'
+ MaxMetadataLevelsExceeded = '7'
+ MaxArchiveRestartsExceeded = '8'
+ MaxRecursionLevelExceeded = '9'
+ NotAnArchve = '10'
+ TooManyMetastrings = '11'
+ BucketError = '12'
+ DataNotFound = '13'
+ RouteNotFound = '14'
+ RejectedOverload = '15'
+ TooManyRedirects = '16'
+ InternalError = '17'
+ TransferFailed = '18'
+ SplitfileError = '19'
+ InvalidUri = '20'
+ TooBig = '21'
+ MetadataTooBig = '22'
+ TooManyBlocks = '23'
+ NotEnoughMetastrings = '24'
+ Canceled = '25'
+ ArchiveRestart = '26'
+ PermanentRedirect = '27'
+ NotAllDataFound = '28'
+
+ class FcpError(Exception): pass
+ class FcpUri(object):
+ """Wrapper class for freenet uris"""
+
+
+ KeySSK = 'SSK@'
+ KeyKSK = 'KSK@'
+ KeyCHK = 'CHK@'
+ KeyUSK = 'USK@'
+ KeySVK = 'SVK@'
+ KeyUnknown = ''
+ KeysAll = (KeySSK, KeyKSK, KeyCHK, KeyUSK, KeySVK)
+
+ ReUriPattern = re.compile('(%s.*?)(?= |\Z)' % '.*?|'.join(KeysAll), re.I)
+ ReKeyPattern = re.compile('(%s)' % '|'.join(KeysAll), re.I)
+
+
+ def __init__(self, uri):
+ """
+ @param uri: uri to wrap
+ @param cvar ReUriPattern: pattern matching a freenet uri
+ @param cvar ReKeyPattern: pattern matching the key type of a freenet uri
+
+ @note: any dfecorations prefixing the freenet part of the uri uri are stripped if possible
+
+
+ >>> uri = FcpUri('freenet:SSK@foo/bar')
+ >>> str(uri)
+ 'SSK@foo/bar'
+ >>> uri.keyType() == FcpUri.KeySSK
+ True
+ >>> uri.split()
+ ('SSK@foo', 'bar')
+ >>> uri.fileName()
+ 'bar'
+
+ >>> uri = FcpUri('http://SSK@foo/bar')
+ >>> str(uri)
+ 'SSK@foo/bar'
+
+ # uris not containing freenet keys are left unchanged
+ >>> uri = FcpUri('http://foo/bar')
+ >>> str(uri)
+ 'http://foo/bar'
+ >>> uri.keyType() == FcpUri.KeyUnknown
+ True
+ >>> uri.split()
+ ('http://foo/bar', '')
+ >>> uri.fileName()
+ 'http://foo/bar'
+
+ """
+ self._uri = uri
+
+ result = self.ReUriPattern.search(uri)
+ if result is not None:
+ self._uri = result.group(0)
+
+ def __str__(self):
+ return str(self._uri)
+
+ def __unicode__(self):
+ return unicode(self._uri)
+
+ def keyType(self):
+ """Retuns the key type of the uri
+ @return: one of the Key* consts
+ """
+ result = self.ReKeyPattern.search(self._uri)
+ if result is not None:
+ return result.group(0).upper()
+ return self.KeyUnknown
+
+ def split(self):
+ """Splits the uri
+ @return: tuple(freenet-key, file-name)
+ """
+ if self.keyType() != self.KeyUnknown:
+ head, sep, tail = self._uri.partition('/')
+ return head, tail
+ return self._uri, ''
+
+ def fileName(self):
+ """Returns the filename part of the uri
+ @return: str
+ """
+ head, tail = self.split()
+ if tail:
+ return tail
+ return self._uri
+
+ class IdentifierPrefix:
+ """Special purpose identifier prefixes"""
+ ClientGetInfo = 'ClientGetInfo::'
+
+ class InsertError(Exception):
+ """All insert errors supported by the client"""
+
+ def __init__(self, msg):
+ """
+ @param msg: (Message) PutFailed message or its parameters dict
+ """
+ self.value = '%s (%s, %s)' % (
+ msg.get('CodeDescription', 'Unknown error') ,
+ msg['Code'],
+ msg.get('ExtraDescription', '...'),
+ )
+ def __str__(self): return self.value
+
+ InvalidUri = '1'
+ BucketError = '2'
+ InternalError = '3'
+ RejectedOverload = '4'
+ RouteNotFound = '5'
+ FatalErrorInBlocks = '6'
+ TooManyRetriesInBlock = '7'
+ RouteReallyNotFound = '8'
+ Collision = '9'
+ Canceled = '10'
+
+ class LogMessages:
+ """Message strings used for log infos"""
+ Connecting = 'Connecting to node...'
+ Connected = 'Connected to node'
+ ConnectionRetry = 'Connecting to node failed... retrying'
+ ConnectingFailed = 'Connecting to node failed'
+
+ ClientClose = 'Closing client'
+
+ MessageSend = 'SendMessage'
+ MessageReceived = 'ReceivedMessage'
+
+ JobStart = 'Starting job: '
+ JobStop = 'Stopping job: '
+ JobsCompleted = 'All jobs completed'
+
+ KeyboardInterrupt = 'Keyboard interrupt'
+ SocketDied = 'Socket died'
+
+ class Message(object):
+ """Class wrapping a freenet message"""
+
+ __slots__ = ('name', 'data', 'params')
+
+ # client messages
+ ClientHello = 'ClientHello'
+ ListPeer = 'ListPeer' # (since 1045)
+ ListPeers = 'ListPeers'
+ ListPeerNotes = 'ListPeerNotes'
+ AddPeer = 'AddPeer'
+ ModifyPeer = 'ModifyPeer'
+ ModifyPeerNote = 'ModifyPeerNote'
+ RemovePeer = 'RemovePeer'
+ GetNode = 'GetNode'
+ GetConfig = 'GetConfig' # (since 1027)
+ ModifyConfig = 'ModifyConfig' # (since 1027)
+ TestDDARequest = 'TestDDARequest' # (since 1027)
+ TestDDAResponse = 'TestDDAResponse' # (since 1027)
+ GenerateSSK = 'GenerateSSK'
+ ClientPut = 'ClientPut'
+ ClientPutDiskDir = 'ClientPutDiskDir'
+ ClientPutComplexDir = 'ClientPutComplexDir'
+ ClientGet = 'ClientGet'
+ SubscribeUSK = 'SubscribeUSK'
+ WatchGlobal = 'WatchGlobal'
+ GetRequestStatus = 'GetRequestStatus'
+ ListPersistentRequests = 'ListPersistentRequests'
+ RemovePersistentRequest = 'RemovePersistentRequest'
+ ModifyPersistentRequest = 'ModifyPersistentRequest'
+ Shutdown = 'Shutdown'
+
+ # node messages
+ NodeHello = 'NodeHello'
+ CloseConnectionDuplicateClientName = 'CloseConnectionDuplicateClientName'
+ Peer = 'Peer'
+ PeerNote = 'PeerNote'
+ EndListPeers = 'EndListPeers'
+ EndListPeerNotes = 'EndListPeerNotes'
+ PeerRemoved = 'PeerRemoved'
+ NodeData = 'NodeData'
+ ConfigData = 'ConfigData' # (since 1027)
+ TestDDAReply = 'TestDDAReply' # (since 1027)
+ TestDDAComplete = 'TestDDAComplete' # (since 1027)
+ SSKKeypair = 'SSKKeypair'
+ PersistentGet = 'PersistentGet'
+ PersistentPut = 'PersistentPut'
+ PersistentPutDir = 'PersistentPutDir'
+ URIGenerated = 'URIGenerated'
+ PutSuccessful = 'PutSuccessful'
+ PutFetchable = 'PutFetchable'
+ DataFound = 'DataFound'
+ AllData = 'AllData'
+ StartedCompression = 'StartedCompression'
+ FinishedCompression = 'FinishedCompression'
+ SimpleProgress = 'SimpleProgress'
+ EndListPersistentRequests = 'EndListPersistentRequests'
+ PersistentRequestRemoved = 'PersistentRequestRemoved' # (since 1016)
+ PersistentRequestModified = 'PersistentRequestModified' # (since 1016)
+ PutFailed = 'PutFailed'
+ GetFailed = 'GetFailed'
+ ProtocolError = 'ProtocolError'
+ IdentifierCollision = 'IdentifierCollision'
+ UnknownNodeIdentifier = 'UnknownNodeIdentifier'
+ UnknownPeerNoteType = 'UnknownPeerNoteType'
+ SubscribedUSKUpdate = 'SubscribedUSKUpdate'
+
+ # client messages (internal use only)
+ ClientSocketTimeout = 0
+ ClientSocketDied = 1
+
+
+ def __init__(self, name, data=None, **params):
+ """
+ @param name: messge name
+ @param data: data associated to the messge (not yet implemented)
+ @param params: {field-name: value, ...} of parameters of the message
+ @note: all params can be accessed as attributes of the class
+ """
+ self.data = data
+ self.name = name
+ self.params = params
+
+
+ @classmethod
+ def bytesFromSocket(clss, socketObj, n):
+ """Reads n bytes from socket
+ @param socketObj: socket to read bytes from
+ @param n: (int) number of bytes to read
+ @return: (tuple) error-message or None, bytes read or None if an error occured
+ or no bytes could be read
+ """
+ error = p = None
+ try:
+ p = socketObj.recv(n)
+ if not p:
+ p = None
+ raise socket.error('Socket shut down by node')
+ except socket.timeout, d: # no new messages in queue
+ error = clss(clss.ClientSocketTimeout)
+ except socket.error, d:
+ error = clss(clss.ClientSocketDied, Exception=socket.error, Details=d)
+ return error, p
+
+
+ @classmethod
+ def fromSocket(clss, socketObj):
+ """Reads a message from a socket
+ @param socketObj: socket to read a message from
+ @return: L{Message} next message from the socket. If the socket dies
+ unexpectedly a L{ClientSocketDied} message is returned containing the parameters
+ 'Exception' and 'Details'. If the socket times out a L{MessageClientSocketTimout}
+ message is returned.
+ """
+
+ msg = clss(None)
+ buf = []
+
+ #TODO: to buffer or not to buffer?
+ while True:
+
+ # get next line from socket
+ error, p = clss.bytesFromSocket(socketObj, 1)
+ if error:
+ return error
+
+ if p != '\n':
+ buf.append(p)
+ continue
+ #TODO: check if '\r\n' is allowed in freenet client protocol
+ else:
+ if buf[-1] == '\r':
+ del buf[-1]
+
+ line = ''.join(buf)
+ buf = []
+ if line == 'EndMessage':
+ break
+
+ # first line == message name
+ if msg.name is None:
+ msg.name = line
+
+ # get data member
+ elif line == 'Data':
+ remaining = int(msg.params['DataLength'])
+ msg.data = ''
+ while remaining > 0:
+ error, p = clss.bytesFromSocket(socketObj, remaining)
+ if error:
+ return error
+ remaining -= len(p)
+ msg.data += p
+ break
+
+ # get next paramater
+ else:
+ head, sep, tail = line.partition('=')
+ msg.params[head] = tail
+ # TODO: errorchek params?
+ #if not sep: pass
+
+ return msg
+
+
+ def get(self, name, default=None):
+ """Returns the message parameter 'name' or 'default' """
+ return self.params.get(name, default)
+
+
+ def __getitem__(self, name):
+ """Returns the message parameter 'name' """
+ return self.params[name]
+
+
+ def __setitem__(self, name, value):
+ """Sets the message parameter 'name' to 'value' """
+ self.params[name] = value
+
+
+ def pprint(self):
+ """Returns the message as nicely formated human readable string"""
+ out = ['', '>>' + self.name, ]
+ for param, value in self.params.items():
+ out.append('>> %s=%s' % (param, value))
+ out.append('>>EndMessage')
+ return '\n'.join(out)
+
+
+ def send(self, socketObj):
+ """Dumps the message to a socket
+ @param socketObj: socket to dump the message to
+ """
+ socketObj.sendall(self.toString())
+
+
+ def toString(self):
+ """Returns the message as formated string ready to be send"""
+
+ #TODO: just a guess, so maybe remove this check
+ if isinstance(self.name, (int, long)):
+ raise ValueError('You can not send client internal messages to the node')
+ out = [self.name, ]
+ for param, value in self.params.items():
+ out.append('%s=%s' % (param, value))
+ if self.data:
+ assert 'DataLength' in self.params, 'DataLength member required'
+ n = None
+ try:
+ n = int(self['DataLength'])
+ except ValueError: pass
+ assert n is not None, 'DataLength member must be an integer'
+ assert n == len(self.data), 'DataLength member must corrospond to lenght of data'
+ out.append('Data')
+ out.append(self.data)
+ else:
+ out.append('EndMessage\n')
+ return '\n'.join(out)
+
+
+
+ #TODO: no idea how fcp handles strings as in <Peer volatile.status=CONNECTED>
+ # all I could find in the sources where these constants as in PEER_NODE_STATUS_CONNECTED
+ # in --> freenet/node/PeerManager.java
+ class PeerNodeStatus:
+ Connected = 1
+ RoutingBackedOff = 2
+ TooNew = 3
+ TooOld = 4
+ Disconnected = 5
+ NeverConnected = 6
+ Disabled = 7
+ Bursting = 8
+ Listening = 9
+ ListenOnly = 10
+ ClockProblem = 11
+ ConnError = 12
+ Disconnecting = 13
+
+ class PeerNoteType:
+ """All known peer note types"""
+ Private = '1'
+
+ class Priorities:
+ """All priorities supported by the client"""
+ Maximum = '0'
+ Interactive = '1'
+ SemiInteractive = '2'
+ Updatable = '3'
+ Bulk = '4'
+ Prefetch = '5'
+ Minimum = '6'
+
+ PriorityMin = Minimum
+ PriorityDefault = Bulk
+
+ class ProtocolError(Exception):
+ """All protocol errors supported by the client"""
+
+ def __init__(self, msg):
+ """
+ @param msg: (Message) ProtocolError message or its parameters dict
+ """
+ self.value = '%s (%s, %s)' % (
+ msg.get('CodeDescription', 'Unknown error') ,
+ msg['Code'],
+ msg.get('ExtraDescription', '...'),
+ )
+ def __str__(self): return self.value
+
+ ClientHelloMustBeFirst = '1'
+ NoLateClientHellos = '2'
+ MessageParseError = '3'
+ UriParseError = '4'
+ MissingField = '5'
+ ErrorParsingNumber = '6'
+ InvalidMessage = '7'
+ InvalidField = '8'
+ FileNotFound = '9'
+ DiskTargetExists = '10'
+ SameDirectoryExpected = '11'
+ CouldNotCreateFile = '12'
+ CouldNotWriteFile = '13'
+ CouldNotRenameFile = '14'
+ NoSuchIdentifier = '15'
+ NotSupported = '16'
+ InternalError = '17'
+ ShuttingDown = '18'
+ NoSuchNodeIdentifier = '19' # Unused since 995
+ UrlParseError = '20'
+ ReferenceParseError = '21'
+ FileParseError = '22'
+ NotAFile = '23'
+ AccessDenied = '24'
+ DDADenied = '25'
+ CouldNotReadFile = '26'
+ ReferenceSignature = '27'
+ CanNotPeerWithSelf = '28'
+ PeerExists = '29'
+ OpennetDisabled = '30'
+ DarknetPeerOnly = '31'
+
+ class SocketError(Exception): pass
+ class Verbosity:
+ Debug = logging.DEBUG
+ Info = logging.INFO
+ Warning = logging.WARNING
+
+
def __init__(self,
name='',
connectionName=None,
verbosity=Verbosity.Warning,
- logMessages=LogMessages
):
"""
@param name: name of the client instance or '' (for debugging)
@param conectionName: name of the connection
@param verbosity: verbosity level for debugging
- @param logMessages: LogMessages class containing message strings
"""
self._connectionName = connectionName
self._ddaTmpFiles = []
self._log = logging.getLogger(name)
- self._logMessages = logMessages
- self._lock = thread.allocate_lock()
self._socket = None
self.setVerbosity(verbosity)
@@ -756,7 +682,7 @@
"""Closes the client
@note: make shure to call close() when done with the client
"""
- self._log.info(self._logMessages.ClientClose)
+ self._log.info(self.LogMessages.ClientClose)
if self._socket is not None:
self._socket.close()
self._socket = None
@@ -764,8 +690,8 @@
# clean left over tmp files
for fpath in self._ddaTmpFiles:
saveRemoveFile(fpath)
+
-
#TODO: an iterator would be nice to enshure Guis stay responsitive in the call
def connect(self, host=DefaultFcpHost, port=DefaultFcpPort, repeat=20, timeout=0.5):
"""Establishes the connection to a freenet node
@@ -776,7 +702,7 @@
@return: (Message) NodeHello if successful,None otherwise
"""
self._clientHello = None
- self._log.info(self._logMessages.Connecting)
+ self._log.info(self.LogMessages.Connecting)
# poll untill freenet responds
timeElapsed = 0
@@ -792,7 +718,7 @@
except Exception, d:
pass
else:
- self._log.info(self._logMessages.Connected)
+ self._log.info(self.LogMessages.Connected)
# send ClientHello and wait for NodeHello
#NOTE: thought I could leave ClientHelloing up to the caller
@@ -800,26 +726,26 @@
# as expected when not doing so, the node disconnects.
# So take it over here.
self.sendMessage(
- Message.ClientHello,
- Name=self._connectionName if self._connectionName is not None else newIdentifier(),
+ self.Message.ClientHello,
+ Name=self._connectionName if self._connectionName is not None else self.newIdentifier(),
ExpectedVersion=self.Version,
)
while timeElapsed <= repeat:
msg = self.next()
- if msg.name == Message.ClientSocketTimeout:
+ if msg.name == self.Message.ClientSocketTimeout:
timeElapsed += SocketTimeout
- elif msg.name == Message.NodeHello:
+ elif msg.name == self.Message.NodeHello:
return msg.params
else:
break
break
# continue polling
- self._log.info(self._logMessages.ConnectionRetry)
+ self._log.info(self.LogMessages.ConnectionRetry)
timeElapsed += timeout
time.sleep(timeout)
- self._log.info(self._logMessages.ConnectingFailed)
+ self._log.info(self.LogMessages.ConnectingFailed)
return None
@@ -829,18 +755,18 @@
@return: True if the message was handled, False otherwise
"""
- if msg.name == Message.ClientSocketTimeout:
+ if msg.name == self.Message.ClientSocketTimeout:
return True
- self._log.debug(self._logMessages.MessageReceived + msg.pprint())
+ self._log.debug(self.LogMessages.MessageReceived + msg.pprint())
- if msg.name == Message.ProtocolError:
+ if msg.name == self.Message.ProtocolError:
code = msg['Code']
- if code == ProtocolError.ShuttingDown:
+ if code == self.ProtocolError.ShuttingDown:
self.close()
self.EventShutdown(msg.params)
return True
- raise ProtocolError(msg)
+ raise self.ProtocolError(msg)
####################################################
##
@@ -851,7 +777,7 @@
## Have to handle this!
##
####################################################
- elif msg.name == Message.TestDDAReply:
+ elif msg.name == self.Message.TestDDAReply:
fpathWrite = msg.params.get('WriteFilename', None)
fpathRead = msg.params.get('ReadFilename', None)
readContent = ''
@@ -868,38 +794,51 @@
readContent = ''
self.sendMessage(
- Message.TestDDAResponse,
+ self.Message.TestDDAResponse,
Directory=msg['Directory'],
ReadContent=readContent,
)
return True
-
-
- elif msg.name == Message.TestDDAComplete:
+
+ elif msg.name == self.Message.TestDDAComplete:
# clean tmp files
for fpath in self._ddaTmpFiles:
saveRemoveFile(fpath)
self._ddaTmpFiles = []
+ self.EventTestDDAComplete(msg.params)
return True
####################################################
##
+ ## Config related messages
+ ##
+ ####################################################
+ elif msg.name == self.Message.ConfigData:
+ self.EventConfigData(msg.params)
+ return True
+
+ elif msg.name == self.Message.NodeData:
+ self.EventNodeData(msg.params)
+ return True
+
+ ####################################################
+ ##
## Peer related messages
##
####################################################
- elif msg.name == Message.EndListPeers:
+ elif msg.name == self.Message.EndListPeers:
self.EventEndListPeers(msg.params)
return True
- elif msg.name == Message.EndListPeerNotes:
+ elif msg.name == self.Message.EndListPeerNotes:
self.EventEndListPeerNotes(msg.params)
return True
- elif msg.name == Message.Peer:
+ elif msg.name == self.Message.Peer:
self.EventPeer(msg.params)
return True
- elif msg.name == Message.PeerNote:
+ elif msg.name == self.Message.PeerNote:
note = msg.get('NoteText', '')
if note:
note = base64.decodestring(note)
@@ -907,11 +846,11 @@
self.EventPeerNote(msg.params, note)
return True
- elif msg.name == Message.PeerRemoved:
+ elif msg.name == self.Message.PeerRemoved:
self.EventPeerRemoved(msg.params)
return True
- elif msg.name == Message.UnknownNodeIdentifier:
+ elif msg.name == self.Message.UnknownNodeIdentifier:
self.EventUnknownIdentifier(msg.params)
return True
@@ -920,49 +859,44 @@
## Get related messages
##
####################################################
- elif msg.name == Message.DataFound:
- if msg['Identifier'].startswith(IdentifierPrefix.FileInfo):
- self.EventFileInfo(msg.params)
+ elif msg.name == self.Message.DataFound:
+ if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo):
+ self.EventClientGetInfo(msg.params)
return True
- #TODO:
self.EventDataFound(msg.params)
return True
-
- elif msg.name == Message.GetFailed:
+ elif msg.name == self.Message.GetFailed:
code = msg['Code']
- if code == FetchError.TooBig:
- if msg['Identifier'].startswith(IdentifierPrefix.FileInfo):
+ if code == self.FetchError.TooBig:
+ if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo):
params = {
'Metadata.ContentType': msg.get('ExpectedMetadata.ContentType', ''),
'DataLength': msg.get('ExpectedDataLength', '')
}
- self.EventFileInfo(params)
+ self.EventClientGetInfo(params)
return True
self.EventGetFailed(msg.params)
return True
-
-
- elif msg.name == Message.SimpleProgress:
- if msg['Identifier'].startswith(IdentifierPrefix.FileInfo):
- self.EventFileInfoProgress(msg.params)
+
+ elif msg.name == self.Message.SimpleProgress:
+ if msg['Identifier'].startswith(self.IdentifierPrefix.ClientGetInfo):
+ self.EventClientGetInfoProgress(msg.params)
else:
self.EventSimpleProgress(msg.params)
return True
-
- elif msg.name == Message.IdentifierCollision:
+ elif msg.name == self.Message.IdentifierCollision:
self.EventIdentifierCollision(msg.params)
return True
-
- elif msg.name == Message.PersistentRequestModified:
+ elif msg.name == self.Message.PersistentRequestModified:
self.EventPersistentRequestModified(msg.params)
return True
- elif msg.name == Message.PersistentRequestRemoved:
+ elif msg.name == self.Message.PersistentRequestRemoved:
self.EventPersistentRequestRemoved(msg.params)
return True
@@ -971,15 +905,41 @@
## Others
##
####################################################
- elif msg.name == Message.SSKKeypair:
+ elif msg.name == self.Message.SSKKeypair:
self.EventSSKKeypair(msg.params)
return True
+
-
-
- ## default
+ ## default ##
return False
+
+ def setVerbosity(self, verbosity):
+ """Sets the verbosity level of the client
+ @note: see L{Verbosity}
+ """
+ self._log.setLevel(verbosity)
+
+
+ def startFreenet(self, cmdline):
+ """Starts freenet
+ @param cmdline: commandline to start freenet (like '/freenet/run.sh start' or 'c:\freenet\start.bat')
+ @return: (string) whatever freenet returns
+ """
+ #TODO: on windows it may be necessary to hide the command window
+ p = subprocess.Popen(
+ args=cmdline,
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ stdout, stderr = p.communicate()
+ return stdout
+
+
+ def verbosity(self):
+ """Returns the current verbosity level of the client"""
+ return self._log.level
#########################################################
##
##
@@ -989,8 +949,8 @@
"""Pumps the next message waiting
@note: use this method instead of run() to run the client step by step
"""
- msg = Message.fromSocket(self._socket)
- if msg.name == Message.ClientSocketDied:
+ msg = self.Message.fromSocket(self._socket)
+ if msg.name == self.Message.ClientSocketDied:
self.EventSocketDied(msg['Exception'], msg['Details'])
raise SocketError(msg['Details'])
self.handleMessage(msg)
@@ -1007,7 +967,7 @@
If an error handler is passed to the client it is called emidiately before the error
is raised.
"""
- return self.sendMessageEx(Message(name, data=data, **params))
+ return self.sendMessageEx(self.Message(name, data=data, **params))
def sendMessageEx(self, msg):
@@ -1018,26 +978,16 @@
If an error handler is passed to the client it is called emidiately before the error
is raised.
"""
- self._log.debug(self._logMessages.MessageSend + msg.pprint())
+ self._log.debug(self.LogMessages.MessageSend + msg.pprint())
try:
msg.send(self._socket)
except socket.error, d:
- self._log.info(self._logMessages.SocketDied)
+ self._log.info(self.LogMessages.SocketDied)
self.close()
self.EventSocketDied(socket.error, d)
raise SocketError(d)
return msg
-
-
- def setLogMessages(self, logMessages):
- """"""
- self._logMessages = logMessages
-
-
- def setVerbosity(self, verbosity):
- """"""
- self._log.setLevel(verbosity)
-
+
#########################################################
##
##
@@ -1049,15 +999,56 @@
@return: (str) 'true' or 'false'
"""
return self.FcpTrue if pythonBool else self.FcpFalse
-
+
+ def newIdentifier(self, prefix=None):
+ """Returns a new unique identifier
+ @return: (str) uuid
+ """
+ if prefix:
+ return prefix + str(uuid.uuid4())
+ return str(uuid.uuid4())
+
+
def pythonBool(self, fcpBool):
"""Converts a fcp bool to a python bool
@param pythonBool: 'true' or 'false'
@return: (bool) True or False
"""
return fcpBool == self.FcpTrue
+
+ ########################################################
+ ##
+ ## Config related methods
+ ##
+ ########################################################
+ #TODO: WithDefault never returns defaults
+ def getConfig(self):
+ """
+ @event: ConfigData(event, params)
+ """
+ self.sendMessage(
+ self.Message.GetConfig,
+ WithCurrent=self.FcpTrue,
+ WithDefault=self.FcpTrue,
+ WithExpertFlag=self.FcpTrue,
+ WithForceWriteFlag=self.FcpTrue,
+ WithShortDescription=self.FcpTrue,
+ WithLongDescription=self.FcpTrue,
+ )
+
+ def getNode(self):
+ """
+ @event: NodeData(event, params)
+ """
+ self.sendMessage(
+ self.Message.GetNode,
+ WithPrivate==self.FcpTrue,
+ WithVlatile==self.FcpTrue,
+ GiveOpennetRef==self.FcpTrue,
+ )
+
########################################################
##
## Peer related methods
@@ -1065,28 +1056,35 @@
########################################################
def listPeer(self, identifier):
self.jobClient.sendMessage(
- Message.ListPeer,
+ self.Message.ListPeer,
NodeIdentifier=identifier,
)
def listPeerNotes(self, identifier):
+ """Lists all text notes associated to a peer
+ @param identifier: peer as returned in a call to L{peerList}
+ @event: EventListPeerNotes(event).
+ @event: EventListPeerNote(event, note).
+ @event: EventEndListPeerNotes(event).
"""
- @param identifier: identifier of the peer to list notes for
- """
self.sendMessage(
- Message.ListPeerNotes,
+ self.Message.ListPeerNotes,
NodeIdentifier=identifier
)
def listPeers(self, withMetaData=True, withVolantile=True):
- """
+ """Lists all peers of the node
@param withMetaData: include meta data for each peer?
@param withVolantile: include volantile data for each peer?
+
+ @event: EventListPeers(event).
+ @event: EvenPeer(event, peer).
+ @event: EventEndListPeers(event).
"""
self.sendMessage(
- Message.ListPeers,
+ self.Message.ListPeers,
WithMetadata=self.fcpBool(withMetaData),
WithVolatile=self.fcpBool(withVolantile),
)
@@ -1094,7 +1092,7 @@
def modifyPeer(self, identifier, allowLocalAddresses=None, isDisabled=None, isListenOnly=None):
msg = Message(
- Message.ModifyPeer,
+ self.Message.ModifyPeer,
NodeIdentifier=identifier,
)
if allowLocalAddresses is not None:
@@ -1109,17 +1107,17 @@
def modifyPeerNote(self, identifier, note):
self.sendMessage(
- Message.ModifyPeerNote,
+ self.Message.ModifyPeerNote,
NodeIdentifier=identifier,
#NOTE: currently fcp supports only this one type
- PeerNoteType=PeerNoteType.Private,
+ PeerNoteType=self.PeerNoteType.Private,
NoteText=note
)
def removePeer(self, identifier):
self.sendMessage(
- Message.RemovePeer,
+ self.Message.RemovePeer,
NodeIdentifier=identifier,
)
@@ -1128,18 +1126,46 @@
## get / put related methods
##
##########################################################
- def fileInfo(self, uri, **params):
+ #TODO: not complete yet
+ def clientGetFile(self, uri, filename):
+ """
+ """
+ identifier = self.new_identifier()
+ msg = self.Message(
+ self.Message.ClientGet,
+ IgnoreDS='false',
+ DSOnly='false',
+ URI=uri,
+ Identifier=identifier,
+ Verbosity='1',
+ ReturnType='disk',
+ #MaxSize=client_get_info['Size'],
+ #MaxTempSize=client_get_info['Size'],
+ #MaxRetries='-1',
+ #PriorityClass='4',
+ Persistence='forever',
+ #ClientToken=identifier,
+ Global='false',
+ #BinaryBlob='false',
+ Filename=filename,
+ )
+ self.sendMessageEx(msg)
+
+ return identifier
+
+
+ def clientGetInfo(self, uri, **params):
"""Requests info about a file
@param uri: uri of the file to request info about
- @event: FileInfo(event, params). If success, params will contain a key 'Metadata.ContentType'
+ @event: clientGetInfo(event, params). If success, params will contain a key 'Metadata.ContentType'
and 'DataLength'. Both may be '' (empty string)
- @event: FileInfoProgress(event, params). Triggered instead of EventSimpleProgress
+ @event: clientGetInfoProgress(event, params). Triggered instead of EventSimpleProgress
@note: for other events see: L{clientGet}
- @return: (str) identifier of the request
+ @return: (str) request identifier
"""
- identifier = IdentifierPrefix.FileInfo + newIdentifier()
+ identifier = self.IdentifierPrefix.ClientGetInfo + self.newIdentifier()
self.sendMessage(
- Message.ClientGet,
+ self.Message.ClientGet,
Identifier=identifier,
URI=uri,
# suggested by Mathew Toseland to use about 32k for mimeType requests
@@ -1152,199 +1178,152 @@
return identifier
- #########################################################
- ## how to tackle TestDDA?
- ##
- ## best idea hear so far is to wait for ProtocolError 25 Test DDA denied (or PersistantGet)
- ## and reinsert the job if necessary after TestDDA completion.
- ##
- ## Problem is, how to wait for a message without flooding the caller. Basic idea of the client
- ## is to enshure a Gui can stay responsitive by letting the caller decide when to process the
- ## next message. So waiting would require to buffer messages and watch messages carefuly
- ## as they flood in.
- ##
- ## If we do not wait, the caller may flood us with download requests, I fear, faster than
- ## the node and we are able to go get the error and through the TestDDA drill. Have to
- ## do some tests to see how the node reacts.
- ##
- ## easiest approach would be to let the caller test a directory explicitely when HE thinks
- ## it might be necessary. But then this code will hang around forever with an already
- ## assigned bug report [https://bugs.freenetproject.org/view.php?id=1753] suggesting
- ## much easier processing to test DDA (DDA Challenge)
- ##
- ##
- ## so.. maybe best is to lurker around a while and keep an eye on the tracker
- ##
- ##
- ## below is just some old boilerplate code.. to be removed sooner or later
- #########################################################
- def testWriteAccess(self, directory):
- canRead, canWrite = False, False
- result = self._jobs.get('RegisteredDirectories', None)
- if result is not None:
- canRead, canWrite = result
- if not canWrite:
- job = JobTestDDA(directory, read=canRead, write=True)
- self.addJob(job, synchron=True)
- canWrite = job.jobResult[1]
- self._jobs['RegisteredDirectories'] = (canRead, canWrite)
- return canWrite
-
- def testReadAccess(self, directory):
- canRead, canWrite = False, False
- result = self._jobs.get('RegisteredDirectories', None)
- if result is not None:
- canRead, canWrite = result
- if not canRead:
- job = JobTestDDA(directory, read=True, write=canWrite)
- self.addJob(job, synchron=True)
- canRead = job.jobResult[0]
- self._jobs['RegisteredDirectories'] = (canRead, canWrite)
- return canRead
-
-
- def downloadFile(self, directory, job):
- if not os.path.isdir(directory):
- raise IOError('No such directory')
-
- self._jobs['PendingJobs'].append(job)
- try:
- result = self.testWriteAccess(directory)
- if result:
- self.addJob(job)
- finally:
- self._jobs['PendingJobs'].remove(job)
- return result
+ def testDDA(self, directory, wantReadDirectory=None, wantWriteDirectory=None):
+ """Tests a directory for read / write access
+ @param directory: directory to test
+ @param read: if not Note, test directory for read access
+ @param write: if not Note, test directory for write access
+ @event: TestDDAComplete(event, params) is triggered on test completion
-
- def uploadFile(self, directory, job):
- if not os.path.isdir(directory):
- raise IOError('No such directory')
-
- self._jobs['PendingJobs'].append(job)
- try:
- result = self.testReadAccess(directory)
- if result:
- self.addJob(job)
- finally:
- self._jobs['PendingJobs'].remove(job)
- return result
-
-
- #################################################
+ @note: you have to test a directory if it can bew written to before downloading files ito it
+ and a directory for read access before uploading content from it
+ @note: the node does not like both parameters being False and will respond with a protocol error in this
+ case. Take care of that.
+ """
+ msg = self.Message(
+ self.Message.TestDDARequest,
+ Directory=directory,
+ )
+ if wantReadDirectory is not None:
+ msg['WantReadDirectory'] = self.fcpBool(wantReadDirectory)
+ if wantWriteDirectory is not None:
+ msg['WantWriteDirectory'] = self.fcpBool(wantWriteDirectory)
+ self.sendMessageEx(msg)
+ ##########################################################
##
- ## public methods
+ ## others
##
- #################################################
- def peerList(self, synchron=False):
- """Lists all peers of the node
- @param synchron: if True, waits untill the call is completed, if False returns emidiately
- @return: (list) of peers in a synchron, always None in an asynchron call
-
- @event: EventListPeers(event).
- @event: EventListNextPeer(event, peer).
- @event: EventEndListPeers(event).
+ ##########################################################
+ def generateSSK(self):
"""
- job = JobListPeers(self)
- self.jobAdd(job, synchron=synchron)
- return job.jobResult
-
-
- def peerNotes(self, peer, synchron=False):
- """Lists all text notes associated to a peer
- @param peer: peer as returned in a call to L{peerList}
- @param synchron: if True, waits untill the call is completed, if False returns emidiately
- @return: (list) of notes in a synchron, always None in an asynchron call
-
- @event: EventListPeerNotes(event).
- @event: EventListNextPeerNote(event, note).
- @event: EventEndListPeerNotes(event).
+ @event: SSKKeypair(event, params), triggered when the request is complete
+ @return: identifier of the request
"""
- if self.pythonBool(peer['opennet']): # opennet peers do not have any notes associated
- return []
- job = JobListPeerNotes(self, peer['identity'])
- self.jobAdd(job, synchron=synchron)
- return job.jobResult
-
+ identifier = self.newIdentifier()
+ self.sendMessage(
+ self.Message.GenerateSSK,
+ Identifier=identifier
+ )
+ return identifier
+
#*****************************************************************************
#
#*****************************************************************************
if __name__ == '__main__':
- c = FcpClient(name='test', verbosity=Verbosity.Warning)
+ c = FcpClient(name='test', verbosity=FcpClient.Verbosity.Debug)
nodeHello = c.connect()
if nodeHello is not None:
+
-
-
- def foo():
- job1 = JobClientHello(c)
- c.jobAdd(job1)
-
- c.run()
- print '---------------------------'
- print job1.jobResult
- print '---------------------------'
+ def testLateClientHello():
+ c.sendMessage(
+ c.Message.ClientHello,
+ Name=c.newIdentifier(),
+ ExpectedVersion=c.Version,
+ )
+ for i in xrange(2):
+ c.next()
+
# should raise
- #foo()
+ #testLateClientHello()
- #ModifyPeer not ok
+
+ def testGetConfig():
+
+ def getBuddyValue(params, settingName, buddyPrefix):
+ buddyName = buddyPrefix + '.' + settingName
+ value = params.get(buddyName, '')
+ return (buddyPrefix, value)
+
+ def cb(event, params):
+
+ settings = [ i for i in params if i.startswith('current.')]
+ settings.sort()
+ for setting in settings:
+
+ configTree, sep, settingName = setting.partition('.')
+ value = params[setting]
+ print '%s=%s' % (settingName, value)
+ print '%s=%s' % getBuddyValue(params, settingName, 'expertFlag')
+ print '%s=%s' % getBuddyValue(params, settingName, 'forceWriteFlag')
+ print '%s=%s' % getBuddyValue(params, settingName, 'shortDescription')
+ prefix, value = getBuddyValue(params, settingName, 'longDescription')
+ value = value.replace('. ', '.\n')
+ value = value.replace('? ', '.\n')
+ print '%s=%s' % (prefix, value)
+ print
+
+ c.EventConfigData += cb
+ oldVerbosity = c.verbosity()
+ ##c.setVerbosity(c.Verbosity.Warning)
+
+ print '\n>> Requesting config\n'
+ c.getConfig()
+ for i in xrange(1):
+ c.next()
+
+ c.setVerbosity(oldVerbosity)
+
+ #testGetConfig()
- #RemovePeer not ok
- #ModifyPeerNote ok
- #ListPeer not ok
+ def testGenerateSSK():
+ def cb(event, params):
+ print params
+
+ c.EventSSKKeypair += cb
+ c.generateSSK()
+ for i in xrange(1):
+ c.next()
+ #testGenerateSSK()
-
- def foo():
- job = JobListPeer(c, '123456')
- c.jobAdd(job, synchron=True)
- print job.jobResult
- #foo()
-
-
- def foo():
- job = JobGenerateSSK(c)
- c.jobAdd(job, synchron=True)
- print job.jobResult
- #foo()
-
-
-
- def foo():
+ def testTestDDA():
+ def cb(event, params):
+ print params
+
+ c.EventTestDDAComplete += cb
d = os.path.dirname(os.path.abspath(__file__))
- job2 = JobTestDDA(c, d)
- c.jobAdd(job2)
- c.run()
- print '---------------------------'
- print job2.jobResult
- print '---------------------------'
- #foo()
+ c.testDDA(d, True, True)
+ for i in xrange(4):
+ c.next()
+
+ #testTestDDA()
- def foo():
+
+ def testListPeerNotes():
def cb(event, params):
- #print params.get('opennet', 'true'), c.pythonBool(params.get('Opennet', 'true')), params['identity']
if params['opennet'] == c.FcpFalse:
c.listPeerNotes(params['identity'])
c.EventPeer += cb
-
-
c.listPeers()
- for i in xrange(80):
+ for i in xrange(100):
c.next()
- #foo()
+ #testListPeerNotes()
- def foo():
- #job = JobGetFileInfo(c, 'USK@IK8rVHfjCz1i1IZwgyafsPNDGODCk~EtBgsKWHmPKoQ,FeDoFZ8YeUsU0vo1-ZI~GRhZjeyXyaGhn-xQkIoGpak,AQACAAE/FreeMulET/15/FreeMulET-0.6.29-lib-jar.zip')
- identifier = c.fileInfo('CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg')
+ def testClientGetInfo():
+ def cb(event, params):
+ print params
+
+ c.EventClientGetInfo += cb
+ identifier = c.clientGetInfo('CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg')
for i in xrange(20):
c.next()
-
- #foo()
+ #testClientGetInfo()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-10-30 15:11:01
|
Revision: 22
http://fclient.svn.sourceforge.net/fclient/?rev=22&view=rev
Author: jUrner
Date: 2007-10-30 08:11:02 -0700 (Tue, 30 Oct 2007)
Log Message:
-----------
Another major rewrite. Cut all down to a plain message handler and events.
For now no way for now to wrap the protocol on a higher level without getting
into deep troubles.
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-28 22:44:22 UTC (rev 21)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-30 15:11:02 UTC (rev 22)
@@ -93,6 +93,11 @@
SocketTimeout = 0.1
+class IdentifierPrefix:
+ """Special purpose identifier prefixes"""
+
+ FileInfo = 'FileInfo::'
+
class Verbosity:
Debug = logging.DEBUG
@@ -100,22 +105,6 @@
Warning = logging.WARNING
-class FixedJobIdentifiers:
- """Fixed job identifiers
- @note: he client can only handle one job of these at a time
- """
- ClientHello = 'ClientHello'
- ListPeers = 'ListPeers'
- ListPeerNotes = 'ListPeerNotes'
- GetNode = 'GetNode'
- GetConfig = 'GetConfig'
- ModifyConfig = 'ModifyConfig'
- WatchGlobal = 'WatchGlobal'
- Shutdown = 'Shutdown'
-
-
-
-
class Priorities:
"""All priorities supported by the client"""
@@ -132,8 +121,8 @@
#TODO: no idea how fcp handles strings as in <Peer volatile.status=CONNECTED>
-# all I could find in the sources where these constants as in PEER_NODE_STATUS_CONNECTED
-# in --> freenet/node/PeerManager.java
+# all I could find in the sources where these constants as in PEER_NODE_STATUS_CONNECTED
+# in --> freenet/node/PeerManager.java
class PeerNodeStatus:
Connected = 1
RoutingBackedOff = 2
@@ -150,16 +139,10 @@
Disconnecting = 13
-#TODO: see if we can get away with these to avoid collisions. TestDDA uses no prefix
-# cos ProtocolError 7 passes the directory as identifier and there is no other hint
-# that the error is related to TestDDA.
-class IdentifierPrefix:
- """Identifier prefixes"""
-
- ClientGet = 'ClientGet::'
- #TestDDA = ''
- PeerNote = 'PeerNote::'
+class PeerNoteType:
+ """All known peer note types"""
+ Private = '1'
#************************************************************************************
@@ -170,7 +153,7 @@
def __init__(self, msg):
"""
- @param msg: (Message) GetFailed message
+ @param msg: (Message) GetFailed message or its parameters dict
"""
self.value = '%s (%s, %s)' % (
msg.get('CodeDescription', 'Unknown error') ,
@@ -214,7 +197,7 @@
def __init__(self, msg):
"""
- @param msg: (Message) PutFailed message
+ @param msg: (Message) PutFailed message or its parameters dict
"""
self.value = '%s (%s, %s)' % (
msg.get('CodeDescription', 'Unknown error') ,
@@ -240,7 +223,7 @@
def __init__(self, msg):
"""
- @param msg: (Message) ProtocolError message
+ @param msg: (Message) ProtocolError message or its parameters dict
"""
self.value = '%s (%s, %s)' % (
msg.get('CodeDescription', 'Unknown error') ,
@@ -279,22 +262,13 @@
CanNotPeerWithSelf = '28'
PeerExists = '29'
OpennetDisabled = '30'
- DarknetOnly = '31'
+ DarknetPeerOnly = '31'
class SocketError(Exception): pass
+class FcpError(Exception): pass
#**********************************************************************
# functions
#**********************************************************************
-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 newIdentifier(prefix=None):
"""Returns a new unique identifier
@return: (str) uuid
@@ -304,14 +278,6 @@
return str(uuid.uuid4())
-def pythonBool(fcpBool):
- """Converts a fcp bool to a python bool
- @param pythonBool: 'true' or 'false'
- @return: (bool) True or False
- """
- return fcpBool == 'true'
-
-
def saveReadFile(fpath):
"""Reads contents of a file in the savest manner possible
@param fpath: file to write
@@ -692,463 +658,7 @@
out.append('EndMessage\n')
return '\n'.join(out)
-
#**************************************************************************
-# jobs
-#**************************************************************************
-#TODO: maybe remove syncron functionality and rely only on signals
-# ...if so, remove timeStart, timeStop aswell.. leave up to caller
-class JobBase(object):
- """Base class for jobs"""
-
-
- def __init__(self, fcpClient, identifier, message):
- """
- @param fcpClient: FcpClient() instance
- @param identifier: (str) identifier of the job
- @param message: (Message) to send to the node whne the job ist started
- @ivar jobClient: FcpClient() instance of the job
- @ivar jobIdentifier: identifier of the job
- @ivar jobMessage: message to be send to the node
- @ivar jobResult: if no error was encountered, holding the result of the job when complete
- @ivar jobTimeStart: time the job was started
- @ivar jobTimeStop: time the job was stopped
- """
- self.jobClient = fcpClient
- self.jobIdentifier = identifier
- self.jobMessage = message
- self.jobResult = None
- self.jobTimeStart = 0
- self.jobTimeStop = 0
-
-
- def handleMessage(self, msg):
- return False
-
-
- def handleStart(self):
- """Starts the job"""
- self.jobResult = None
- self.jobTimeStart = time.time()
- self.jobClient.sendMessageEx(self.jobMessage)
-
-
- # XXX
- def handleStop(self, flagError, msg):
- """Called on job completion to stop the job
- @param flagError: True if an error was encountered, False otherwise
- @param msg: (Message) to pass to the job
- """
- self.jobTimeStop = time.time()
- self.jobResult = (flagError, msg)
-
-
-class JobClientHello(JobBase):
- """Sed a ClientHello message to the node
-
- @note: this must be the first message passed to the node. If everything
- goes well, you will get a NodeHello in response.
- """
-
- def __init__(self, fcpClient, name=None, expectedVersion='2.0'):
- """
- @param name: (str) connection name or None, to use an arbitrary name
- @param expectedVersion: (str) node version expected
- """
- message = Message(
- Message.ClientHello,
- Name=name if name is not None else newIdentifier(),
- ExpectedVersion=expectedVersion,
- )
- JobBase.__init__(self, fcpClient, FixedJobIdentifiers.ClientHello, message)
-
- def displayName(self):
- return 'ClientHello'
-
-
- def handleMessage(self, msg):
- if msg.name == Message.NodeHello:
- return self.handleNodeHello(msg)
- elif msg.name == Message.ProtocolError:
- return self.handleProtocolError(msg)
- else:
- raise ValueError('Unexpected message: %s' % msg.name)
-
-
- def handleNodeHello(self, msg):
- self.jobTimeStop = time.time()
- self.jobResult = msg
- self.jobClient.jobRemove(self.jobIdentifier)
- return True
-
-
- def handleProtocolError(self, msg):
- raise ProtocolError(msg)
-
-
-
-
-
-
-class JobListPeers(JobBase):
- """Lists all known peers of the node
- """
-
- def __init__(self, fcpClient, withMetaData=True, withVolantile=True):
- """
- @param withMetaData: include meta data for each peer?
- @param withVolantile: include volantile data for each peer?
- @ivar jobResult: on job completion, will be a list containing all perrs as one 'Peer' message for each peer
- """
- message = Message(
- Message.ListPeers,
- WithMetadata=fcpBool(withMetaData),
- WithVolatile=fcpBool(withVolantile),
- )
- JobBase.__init__(self, fcpClient, FixedJobIdentifiers.ListPeers, message)
-
-
- def handleStart(self):
- JobBase.handleStart(self)
- self.jobClient.EventListPeers()
-
-
- def handleMessage(self,msg):
- if msg.name == Message.EndListPeers:
- return self.handleEndListPeers(msg)
- elif msg.name == Message.Peer:
- return self.handlePeer(msg)
- else:
- raise ValueError('Unexpected message: %s' % msg.name)
-
-
- def handlePeer(self, msg):
- self.jobClient.EventListNextPeer(msg.params)
- if self.jobResult is None:
- self.jobResult = [msg.params, ]
- else:
- self.jobResult.append(msg.params)
- return True
-
-
- def handleEndListPeers(self, msg):
- self.jobClient.EventEndListPeers()
- self.jobTimeStop = time.time()
- if self.jobResult is None:
- self.jobResult = []
- self.jobClient.jobRemove(self.jobIdentifier)
- return True
-
-
-
-class JobListPeerNotes(JobBase):
- """Lists all notes associated to a peer of the node
- """
-
- def __init__(self, fcpClient, identifier):
- """
- @param identifier: identifier of the peer to list notes for (peer identity)
- @ivar jobResult: on job completion will be a list containing all notes associated to the peer
-
- @note: notes are only available for darknet peers (opennet == false)
- """
-
- message = Message(
- Message.ListPeerNotes,
- NodeIdentifier=identifier
- )
- JobBase.__init__(self, fcpClient, IdentifierPrefix.PeerNote + identifier, message)
-
-
- def handleStart(self):
- JobBase.handleStart(self)
- self.jobClient.EventListPeerNotes()
-
-
- def handleMessage(self,msg):
- if msg.name == Message.EndListPeerNotes:
- return self.handleEndListPeerNotes(msg)
- elif msg.name == Message.PeerNote:
- return self.handlePeerNote(msg)
- else:
- raise ValueError('Unexpected message: %s' % msg.name)
-
- def handlePeerNote(self, msg):
- note = msg.get('NoteText', '')
- self.jobClient.EventListNextPeerNote(note)
- if note:
- note = base64.decodestring(note)
- if self.jobResult is None:
- self.jobResult = [note, ]
- else:
- self.jobResult.append(note)
- return True
-
-
- def handleEndListPeerNotes(self, msg):
- self.jobClient.EventEndListPeerNotes()
- self.jobTimeStop = time.time()
- if self.jobResult is None:
- self.jobResult = []
- self.jobClient.jobRemove(self.jobIdentifier)
- return True
-
-
-
-
-#TODO: identifier collisions are not yet handled
-class JobGetFileInfo(JobBase):
- """Tries to retieve information about a file. If everything goes well
-
- On completion fcpResult will hold a tuple((str) content-type, (str) size) of the file.
- Note, that both members may be '' (empty string)
- """
-
-
- # idea is to provoke a GetFailed message and take mimetype and size from 'GetFailed'
- 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*
-
- @ivar jobResult: will be a tuple(bool error, data). If error is True, no information could be
- retrieved and data will be a GetFailed message containing details. If error is False
- data will be a tuple(str metadataContentType, str size). Note that both may be empty
- string and size may not be accurate.
-
- """
- identifier = IdentifierPrefix.ClientGet + newIdentifier()
- message = Message(
- Message.ClientGet,
- Identifier=identifier,
- URI=uri,
- # suggested by Mathew Toseland to use about 32k for mimeType requests
- # basic sizes of keys are: 1k for SSks and 32k for CHKs
- MaxSize='32000',
- ReturnType='none',
- Verbosity='1',
- **params
- )
- JobBase.__init__(self, fcpClient, identifier, message)
-
-
- def getPrority(self):
- return self.jobMessage.get('PriorityClass', Priorities.PriorityDefault)
-
-
- def setPriority(self, priority):
- if not priority in Priorities:
- raise ValueError('Invalid priority: %r' % priority)
- self.jobClient.sendMessage(
- Message.ModifyPersistentRequest,
- Identifier=self.jobIdentifier,
- Global=fcpBool(False),
- PriorityClass=priority,
- )
- # not shure if the response arrives in any case, so set it here
- self.jobMessage['PriorityClass'] = priority
-
-
- def stopRequest(self):
- self.jobClient.sendMessage(
- Message.RemovePersistentRequest,
- Global=fcpBool(False),
- Identifier=self.jobIdentifier,
- )
-
-
- def handleMessage(self, msg):
- if msg.name == Message.DataFound:
- return self.handleDataFound(msg)
- elif msg.name == Message.GetFailed:
- return self.handleGetFailed(msg)
- elif msg.name == Message.IdentifierCollision:
- return self.handleIdentifierCollision(msg)
- elif msg.name == Message.PersistentRequestModified:
- return self.handlePersistentRequestModified(msg)
- elif msg.name == Message.PersistentRequestRemoved:
- return self.handlePersistentRequestRemoved(msg)
- elif msg.name == Message.SimpleProgress:
- return self.handleSimpleProgress(msg)
-
- else:
- raise ValueError('Unexpected message: %s' % msg.name)
-
-
- def handleDataFound(self, msg):
- self.jobTimeStop = time.time()
- self.jobResult = (
- False,
- (
- msg.get('Metadata.ContentType', ''),
- msg.get('DataLength', '')
- )
- )
- self.jobClient.jobRemove(self.jobIdentifier)
- return True
-
- def handleGetFailed(self, msg):
- self.jobTimeStop = time.time()
- if msg['Code'] == FetchError.TooBig:
- self.jobResult = (False, msg)
- self.jobResult = (
- False,
- (
- msg.get('ExpectedMetadata.ContentType', ''),
- msg.get('ExpectedDataLength', '')
- )
- )
- else:
- self.jobResult = (True, msg)
- self.jobClient.jobRemove(self.jobIdentifier)
- return True
-
-
- def handleIdentifierCollision(self, msg):
- raise
-
-
- def handleSimpleProgress(self, msg):
- return True
-
-
- def handlePersistentRequestModified(self, msg):
- priorityClass = msg.get('PriorityClass', None)
- if priorityClass is not None:
- self.jobMessage['PriorityClass'] = priorityClass
- return True
-
- def handlePersistentRequestRemoved(self, msg):
- if self.jobClient.jobIsRunning(self.jobIdentifier):
- self.jobClient.jobStop(self.jobIdentifier)
- return True
-
-
-#TODO: handle case where directories are registered multiple times
-class JobTestDDA(JobBase):
- """Tests a directory for read / write accesss
- """
-
-
- def __init__(self, fcpClient, directory, read=False, write=False):
- """
-
- @ivar jobResult: when the job is complete this will be set to a tuple(bool readAllowed, bool writeAllowed)
- """
- if not os.path.isdir(directory):
- raise ValueError('No such directory: %r' % directory)
-
- message = Message(
- Message.TestDDARequest,
- Directory=directory,
- WantReadDirectory=fcpBool(read),
- WantWriteDirectory=fcpBool(write),
- )
-
- JobBase.__init__(self, fcpClient, directory, message)
- self.jobTmpFile = None
-
-
- def handleMessage(self, msg):
- if msg.name == Message.TestDDAReply:
- return self.handleTestDDAReply(msg)
- elif msg.name == Message.TestDDAComplete:
- return self.handleTestDDAComplete(msg)
- elif msg.name == Message.ProtocolError:
- return self.handleProtocolError(msg)
- else:
- raise ValueError('Unexpected message: %s' % msg.name)
-
-
- def handleProtocolError(self, msg):
- # most likely code 7 here...
- # "Both WantReadDirectory and WantWriteDirectory are set to false: what's the point of sending a message?"
- # ..a stupid response that is ;-)
- self.jobTimeStop = time.time()
- self.jobClient.jobRemove(self.jobIdentifier)
- if msg['Code'] == ProtocolError.InvalidMessage:
- self.jobResult = (False, False)
- else:
- raise ValueError('Unexpected message: %s' % msg.name)
-
-
- 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.jobTmpFile = fpathWrite
-
- if fpathRead is not None:
- readContent = saveReadFile(fpathRead)
- if readContent is None:
- readContent = ''
-
- self.jobClient.sendMessage(
- Message.TestDDAResponse,
- Directory=msg['Directory'],
- ReadContent=readContent,
- )
- return True
-
-
- def handleTestDDAComplete(self, msg):
- self.jobTimeStop = time.time()
- self.jobResult = (
- pythonBool(msg.get('ReadDirectoryAllowed', 'false')),
- pythonBool(msg.get('WriteDirectoryAllowed', 'false')),
- )
- saveRemoveFile(self.jobTmpFile)
- self.jobTmpFile = None
- self.jobClient.jobRemove(self.jobIdentifier)
- return True
-
-
-
-class JobGenerateSSK(JobBase):
- """Job to generate a SSK key pair
- """
-
-
- def __init__(self, fcpClient):
- """
- @ivar jobResult: on job completion, a tuple(insertURI, requestURI) of the generated
- SSK key
- """
-
- identifier = newIdentifier()
- message = Message(
- Message.GenerateSSK,
- Identifier=identifier,
- )
- JobBase.__init__(self, fcpClient, identifier, message)
-
-
- def handleMessage(self, msg):
- if msg.name == Message.SSKKeypair:
- return self.handleSSKKeypair(msg)
- else:
- raise ValueError('Unexpected message: %s' % msg.name)
-
-
- def handleSSKKeypair(self, msg):
- self.jobTimeStop = time.time()
- self.jobResult = (msg['InsertURI'], msg['RequestURI'])
- self.jobClient.jobRemove(self.jobIdentifier)
- return True
-
-
-#**************************************************************************
# fcp client
#**************************************************************************
class LogMessages:
@@ -1169,7 +679,7 @@
KeyboardInterrupt = 'Keyboard interrupt'
- SocketDead = 'Socket is dead'
+ SocketDied = 'Socket died'
#TODO: no idea what happens on reconnect if socket died. What about running jobs?
@@ -1181,42 +691,62 @@
"""
_events_ = (
+
+ #Peer related events
'EventListPeers',
- 'EventListNextPeer',
'EventEndListPeers',
+ 'EventPeer',
+ 'EventPeerRemoved',
+ 'EventUnknownIdentifier',
'EventListPeerNotes',
- 'EventListNextPeerNote',
'EventEndListPeerNotes',
+ 'EventPeerNote',
+ 'EventShutdown',
+ 'EventSocketDied',
+
+ # get / put related events
+ 'EventIdentifierCollision',
+
+ 'EventFileInfo',
+ 'EventFileInfoProgress',
+
+ 'EventDataFound',
+ 'EventGetFailed',
+ 'EventSimpleProgress',
+ 'EventPersistentRequestModified',
+ 'EventPersistentRequestRemoved',
+
+
+ # others
+ 'EventSSKKeypair',
+
)
+
+ Version = '2.0'
+ FcpTrue = 'true'
+ FcpFalse = 'false'
def __init__(self,
name='',
- errorHandler=None,
+ connectionName=None,
verbosity=Verbosity.Warning,
logMessages=LogMessages
):
"""
@param name: name of the client instance or '' (for debugging)
- @param errorHandler: will be called if the socket conncetion to the node is dead
- with two params: SocketError + details. When the handler is called the client
- is already closed.
+ @param conectionName: name of the connection
@param verbosity: verbosity level for debugging
@param logMessages: LogMessages class containing message strings
"""
- self._isConnected = False
- self._jobs = {
- 'Jobs': {},
- 'PendingJobs': [],
- 'RegisteredDirectories': [],
- }
- self._errorHandler = errorHandler #TODO: check if necessary!
+ self._connectionName = connectionName
+ self._ddaTmpFiles = []
self._log = logging.getLogger(name)
self._logMessages = logMessages
- self._lock = thread.allocate_lock() # lock when resources are accessed
+ self._lock = thread.allocate_lock()
self._socket = None
self.setVerbosity(verbosity)
@@ -1230,6 +760,10 @@
if self._socket is not None:
self._socket.close()
self._socket = None
+
+ # clean left over tmp files
+ for fpath in self._ddaTmpFiles:
+ saveRemoveFile(fpath)
#TODO: an iterator would be nice to enshure Guis stay responsitive in the call
@@ -1245,8 +779,8 @@
self._log.info(self._logMessages.Connecting)
# poll untill freenet responds
- time_elapsed = 0
- while time_elapsed <= repeat:
+ timeElapsed = 0
+ while timeElapsed <= repeat:
# try to Connect socket
if self._socket is not None:
@@ -1265,12 +799,15 @@
# but instad of responding with ClientHelloMustBeFirst
# as expected when not doing so, the node disconnects.
# So take it over here.
- job = JobClientHello(self)
- self.jobAdd(job, synchron=False)
- while time_elapsed <= repeat:
+ self.sendMessage(
+ Message.ClientHello,
+ Name=self._connectionName if self._connectionName is not None else newIdentifier(),
+ ExpectedVersion=self.Version,
+ )
+ while timeElapsed <= repeat:
msg = self.next()
if msg.name == Message.ClientSocketTimeout:
- time_elapsed += SocketTimeout
+ timeElapsed += SocketTimeout
elif msg.name == Message.NodeHello:
return msg.params
else:
@@ -1279,7 +816,7 @@
# continue polling
self._log.info(self._logMessages.ConnectionRetry)
- time_elapsed += timeout
+ timeElapsed += timeout
time.sleep(timeout)
self._log.info(self._logMessages.ConnectingFailed)
@@ -1294,190 +831,172 @@
if msg.name == Message.ClientSocketTimeout:
return True
-
self._log.debug(self._logMessages.MessageReceived + msg.pprint())
-
-
+
if msg.name == Message.ProtocolError:
code = msg['Code']
- if code == ProtocolError.NoLateClientHellos or code == ProtocolError.ClientHelloMustBeFirst:
- return self.jobDispatchMessage(FixedJobIdentifiers.ClientHello, msg)
-
- elif code == ProtocolError.ShuttingDown:
-
- #TODO: ??? why dispatch to ClientHello.. can't remember
- if not self.jobDispatchMessage(FixedJobIdentifiers.ClientHello, msg):
-
- # ########################################
- #TODO: ???
-
- return True
-
- else:
- identifier = msg.get('Identifier', None)
- if identifier is None:
- #TODO: inform caller
- raise ProtocolError(msg)
- else:
- return self.jobDispatchMessage(identifier, msg)
-
- else:
+ if code == ProtocolError.ShuttingDown:
+ self.close()
+ self.EventShutdown(msg.params)
+ return True
- # check if the is something like an identifier in the message
- if msg.name == Message.TestDDAReply:
- identifier = msg['Directory']
- elif msg.name == Message.TestDDAComplete:
- identifier = msg['Directory']
- elif msg.name == Message.PeerNote:
- identifier = IdentifierPrefix.PeerNote + msg['NodeIdentifier']
- elif msg.name == Message.EndListPeerNotes:
- identifier = IdentifierPrefix.PeerNote + msg['NodeIdentifier']
- else:
- identifier = msg.get('Identifier', None)
-
- # dispatch to jobs with fixed identifiers
- if identifier is None:
- if msg.name == Message.NodeHello:
- return self.jobDispatchMessage(FixedJobIdentifiers.ClientHello, msg)
- elif msg.name == Message.EndListPeers:
- return self.jobDispatchMessage(FixedJobIdentifiers.ListPeers, msg)
- elif msg.name == Message.Peer:
- return self.jobDispatchMessage(FixedJobIdentifiers.ListPeers, msg)
-
- # more here.....
-
+ raise ProtocolError(msg)
+
+ ####################################################
+ ##
+ ## TestDDA
+ ##
+ ## Note: if both, ReadDirectoryAllowed and WriteDirectoryAllowed are
+ ## set to false, the node sends a ProtocolError (7, 'Invalid message').
+ ## Have to handle this!
+ ##
+ ####################################################
+ elif msg.name == Message.TestDDAReply:
+ 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:
+ saveRemoveFile(fpathWrite)
else:
- raise ValueError('Unhandled message: ' + msg.name)
+ self._ddaTmpFiles.append(fpathWrite)
- else:
- return self.jobDispatchMessage(identifier, msg)
+ if fpathRead is not None:
+ readContent = saveReadFile(fpathRead)
+ if readContent is None:
+ readContent = ''
- raise RuntimeError('We should not have gotten here: %s' % msg.name)
-
+ self.sendMessage(
+ Message.TestDDAResponse,
+ Directory=msg['Directory'],
+ ReadContent=readContent,
+ )
+ return True
+
-
- #########################################################
- ## jobs
- #########################################################
- def hasJobsRunning(self):
- """Checks if the client has running jobs
- @return: (bool) True if so, False otherwise
- """
- self._lock.acquire(True)
- try:
- result = self._jobs['Jobs'] or self._jobs['PendingJobs']
- finally:
- self._lock.release()
+ elif msg.name == Message.TestDDAComplete:
+ # clean tmp files
+ for fpath in self._ddaTmpFiles:
+ saveRemoveFile(fpath)
+ self._ddaTmpFiles = []
+ return True
+
+ ####################################################
+ ##
+ ## Peer related messages
+ ##
+ ####################################################
+ elif msg.name == Message.EndListPeers:
+ self.EventEndListPeers(msg.params)
+ return True
+
+ elif msg.name == Message.EndListPeerNotes:
+ self.EventEndListPeerNotes(msg.params)
+ return True
+ elif msg.name == Message.Peer:
+ self.EventPeer(msg.params)
+ return True
+ elif msg.name == Message.PeerNote:
+ note = msg.get('NoteText', '')
+ if note:
+ note = base64.decodestring(note)
+ msg['NoteText'] = note
+ self.EventPeerNote(msg.params, note)
+ return True
+
+ elif msg.name == Message.PeerRemoved:
+ self.EventPeerRemoved(msg.params)
+ return True
+
+ elif msg.name == Message.UnknownNodeIdentifier:
+ self.EventUnknownIdentifier(msg.params)
+ return True
+
+ ####################################################
+ ##
+ ## Get related messages
+ ##
+ ####################################################
+ elif msg.name == Message.DataFound:
+ if msg['Identifier'].startswith(IdentifierPrefix.FileInfo):
+ self.EventFileInfo(msg.params)
+ return True
+
+ #TODO:
+ self.EventDataFound(msg.params)
+ return True
+
- return result
-
-
- #TODO: not quite clear about the consequences of a synchron job. Have to think this over
- def jobAdd(self, job, synchron=False):
- """Adds a job to the client
- @param job: (Job*) job to add
- @param synchron: if True, wait untill the job is completed, if False return emidiately
- """
- self._lock.acquire(True)
- try:
- if job.jobIdentifier in self._jobs['Jobs']:
- raise ValueError('Duplicate job: %r' % job.jobIdentifier)
- self._jobs['Jobs'][job.jobIdentifier] = job
- finally:
- self._lock.release()
-
- self._log.info(self._logMessages.JobStart + job.jobMessage.name)
- job.handleStart()
- if synchron:
- while self.jobGet(job.jobIdentifier):
- self.next()
+ elif msg.name == Message.GetFailed:
+ code = msg['Code']
+ if code == FetchError.TooBig:
+ if msg['Identifier'].startswith(IdentifierPrefix.FileInfo):
+ params = {
+ 'Metadata.ContentType': msg.get('ExpectedMetadata.ContentType', ''),
+ 'DataLength': msg.get('ExpectedDataLength', '')
+ }
+ self.EventFileInfo(params)
+ return True
+
+ self.EventGetFailed(msg.params)
+ return True
+
+
+ elif msg.name == Message.SimpleProgress:
+ if msg['Identifier'].startswith(IdentifierPrefix.FileInfo):
+ self.EventFileInfoProgress(msg.params)
+ else:
+ self.EventSimpleProgress(msg.params)
+ return True
+
+
+ elif msg.name == Message.IdentifierCollision:
+ self.EventIdentifierCollision(msg.params)
+ return True
+
+ elif msg.name == Message.PersistentRequestModified:
+ self.EventPersistentRequestModified(msg.params)
+ return True
- def jobDispatchMessage(self, identifier, msg):
- """Dispatches a message to a job
- @param identifier: identifier of the job
- @param msg: (Message) message to dispatch
- @return: True if the message was handled, False otherwise
- """
- job = self.jobGet(identifier)
- if job is not None:
- return job.handleMessage(msg)
- return False
+ elif msg.name == Message.PersistentRequestRemoved:
+ self.EventPersistentRequestRemoved(msg.params)
+ return True
-
- def jobGet(self, identifier):
- """Returns a job given its identifier
- @param identifier: identifier of the job
- @return: (Job*) instance or None, if no corrosponding job was found
- """
- self._lock.acquire(True)
- try:
- result = self._jobs['Jobs'].get(identifier, None)
- finally:
- self._lock.release()
- return result
-
-
- def jobIsRunning(self, identifier):
- """Checks if a job is running
- @param identifier: identifier of the job
- @return: True if so, False otherwise
- """
- self._lock.acquire(True)
- try:
- result = identifier in self._jobs['Jobs']
- finally:
- self._lock.release()
- return result
+ ####################################################
+ ##
+ ## Others
+ ##
+ ####################################################
+ elif msg.name == Message.SSKKeypair:
+ self.EventSSKKeypair(msg.params)
+ return True
- def jobRemove(self, identifier):
- """Removes a job unconditionally
- @param identifier: identifier of the job to remove
- @return: True if the job was found, False otherwise
- """
- self._lock.acquire(True)
- try:
- job = self._jobs['Jobs'].get(identifier, None)
- if job is not None:
- del self._jobs['Jobs'][identifier]
- finally:
- self._lock.release()
- if job is None:
- return False
- self._log.info(self._logMessages.JobStop + job.jobMessage.name)
- return True
+ ## default
+ return False
- #TODO: some info when all jobs are completed?
+ #########################################################
+ ##
+ ##
+ ##
+ #########################################################
def next(self):
"""Pumps the next message waiting
@note: use this method instead of run() to run the client step by step
"""
msg = Message.fromSocket(self._socket)
if msg.name == Message.ClientSocketDied:
+ self.EventSocketDied(msg['Exception'], msg['Details'])
raise SocketError(msg['Details'])
self.handleMessage(msg)
return msg
-
- def run(self):
- """Runs the client untill all jobs passed to it are completed
- @note: use KeyboardInterrupt to stop prematurely
- """
- try:
- #n = 0
- while self.hasJobsRunning():
- #n += 1
- #if n > 40: break
- self.next()
- except KeyboardInterrupt:
- self._log(self._logMessages.KeyboardInterrupt)
- self.close()
-
def sendMessage(self, name, data=None, **params):
"""Sends a message to freenet
@param name: name of the message to send
@@ -1503,10 +1022,9 @@
try:
msg.send(self._socket)
except socket.error, d:
- self._log.info(self._logMessages.SocketDead)
+ self._log.info(self._logMessages.SocketDied)
self.close()
- if self._errorHandler is not None:
- self._errorHandler(SocketError, d)
+ self.EventSocketDied(socket.error, d)
raise SocketError(d)
return msg
@@ -1520,16 +1038,120 @@
""""""
self._log.setLevel(verbosity)
+ #########################################################
+ ##
+ ##
+ ##
+ #########################################################
+ 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 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
+
########################################################
##
+ ## Peer related methods
+ ##
########################################################
- def getFileInfo(self, job):
- pass
+ def listPeer(self, identifier):
+ self.jobClient.sendMessage(
+ Message.ListPeer,
+ NodeIdentifier=identifier,
+ )
+
+
+ def listPeerNotes(self, identifier):
+ """
+ @param identifier: identifier of the peer to list notes for
+ """
+ self.sendMessage(
+ Message.ListPeerNotes,
+ NodeIdentifier=identifier
+ )
+
+
+ def listPeers(self, withMetaData=True, withVolantile=True):
+ """
+ @param withMetaData: include meta data for each peer?
+ @param withVolantile: include volantile data for each peer?
+ """
+ self.sendMessage(
+ Message.ListPeers,
+ WithMetadata=self.fcpBool(withMetaData),
+ WithVolatile=self.fcpBool(withVolantile),
+ )
+ def modifyPeer(self, identifier, allowLocalAddresses=None, isDisabled=None, isListenOnly=None):
+ msg = Message(
+ Message.ModifyPeer,
+ NodeIdentifier=identifier,
+ )
+ 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, identifier, note):
+ self.sendMessage(
+ Message.ModifyPeerNote,
+ NodeIdentifier=identifier,
+ #NOTE: currently fcp supports only this one type
+ PeerNoteType=PeerNoteType.Private,
+ NoteText=note
+ )
+
+
+ def removePeer(self, identifier):
+ self.sendMessage(
+ Message.RemovePeer,
+ NodeIdentifier=identifier,
+ )
+
+ ##########################################################
+ ##
+ ## get / put related methods
+ ##
+ ##########################################################
+ def fileInfo(self, uri, **params):
+ """Requests info about a file
+ @param uri: uri of the file to request info about
+ @event: FileInfo(event, params). If success, params will contain a key 'Metadata.ContentType'
+ and 'DataLength'. Both may be '' (empty string)
+ @event: FileInfoProgress(event, params). Triggered instead of EventSimpleProgress
+ @note: for other events see: L{clientGet}
+ @return: (str) identifier of the request
+ """
+ identifier = IdentifierPrefix.FileInfo + newIdentifier()
+ self.sendMessage(
+ Message.ClientGet,
+ Identifier=identifier,
+ URI=uri,
+ # suggested by Mathew Toseland to use about 32k for mimeType requests
+ # basic sizes of keys are: 1k for SSks and 32k for CHKs
+ MaxSize='32000',
+ ReturnType='none',
+ Verbosity='1',
+ **params
+ )
+ return identifier
+
+
#########################################################
## how to tackle TestDDA?
##
@@ -1638,7 +1260,7 @@
@event: EventListNextPeerNote(event, note).
@event: EventEndListPeerNotes(event).
"""
- if pythonBool(peer['opennet']): # opennet peers do not have any notes associated
+ if self.pythonBool(peer['opennet']): # opennet peers do not have any notes associated
return []
job = JobListPeerNotes(self, peer['identity'])
self.jobAdd(job, synchron=synchron)
@@ -1649,9 +1271,9 @@
#
#*****************************************************************************
if __name__ == '__main__':
- c = FcpClient(name='test', verbosity=logging.DEBUG)
+ c = FcpClient(name='test', verbosity=Verbosity.Warning)
nodeHello = c.connect()
- if nodeHello is not None or 1:
+ if nodeHello is not None:
@@ -1666,7 +1288,24 @@
# should raise
#foo()
+
+ #ModifyPeer not ok
+
+ #RemovePeer not ok
+
+ #ModifyPeerNote ok
+
+ #ListPeer not ok
+
+
def foo():
+ job = JobListPeer(c, '123456')
+ c.jobAdd(job, synchron=True)
+ print job.jobResult
+ #foo()
+
+
+ def foo():
job = JobGenerateSSK(c)
c.jobAdd(job, synchron=True)
print job.jobResult
@@ -1686,30 +1325,26 @@
#foo()
def foo():
- peers = c.peerList(synchron=True)
- for peer in peers:
- print c.peerNotes(peer, synchron=True)
+ def cb(event, params):
+ #print params.get('opennet', 'true'), c.pythonBool(params.get('Opennet', 'true')), params['identity']
+ if params['opennet'] == c.FcpFalse:
+ c.listPeerNotes(params['identity'])
+
+ c.EventPeer += cb
+
+
+ c.listPeers()
+ for i in xrange(80):
+ c.next()
#foo()
def foo():
#job = JobGetFileInfo(c, 'USK@IK8rVHfjCz1i1IZwgyafsPNDGODCk~EtBgsKWHmPKoQ,FeDoFZ8YeUsU0vo1-ZI~GRhZjeyXyaGhn-xQkIoGpak,AQACAAE/FreeMulET/15/FreeMulET-0.6.29-lib-jar.zip')
+ identifier = c.fileInfo('CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg')
+ for i in xrange(20):
+ c.next()
- job = JobGetFileInfo(c, 'CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg')
- #job.jobIdentifier = job.jobMessage['Identifier'] = 1
- #job.jobMessage['Identifier'] = 1
- #job.jobIdentifier = 1
- c.jobAdd(job)
-
- #job = JobGetFileInfo(c, 'CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%201%281%29.jpg')
- #job.jobMessage['Identifier'] = 1
- #job.jobIdentifier = 1
- #c.jobAdd(job)
-
- c.run()
- print '---------------------------'
- print job.jobResult
- print '---------------------------'
#foo()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-10-28 22:44:18
|
Revision: 21
http://fclient.svn.sourceforge.net/fclient/?rev=21&view=rev
Author: jUrner
Date: 2007-10-28 15:44:22 -0700 (Sun, 28 Oct 2007)
Log Message:
-----------
added some fixed identifier prefixes to avoid collisions
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-28 21:56:19 UTC (rev 20)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-28 22:44:22 UTC (rev 21)
@@ -131,6 +131,9 @@
PriorityDefault = Bulk
+#TODO: no idea how fcp handles strings as in <Peer volatile.status=CONNECTED>
+# all I could find in the sources where these constants as in PEER_NODE_STATUS_CONNECTED
+# in --> freenet/node/PeerManager.java
class PeerNodeStatus:
Connected = 1
RoutingBackedOff = 2
@@ -147,6 +150,18 @@
Disconnecting = 13
+#TODO: see if we can get away with these to avoid collisions. TestDDA uses no prefix
+# cos ProtocolError 7 passes the directory as identifier and there is no other hint
+# that the error is related to TestDDA.
+class IdentifierPrefix:
+ """Identifier prefixes"""
+
+ ClientGet = 'ClientGet::'
+ #TestDDA = ''
+ PeerNote = 'PeerNote::'
+
+
+
#************************************************************************************
# exceptions
#************************************************************************************
@@ -656,7 +671,8 @@
def toString(self):
"""Returns the message as formated string ready to be send"""
- # TODO: "Data" not yet implemented
+
+ #TODO: just a guess, so maybe remove this check
if isinstance(self.name, (int, long)):
raise ValueError('You can not send client internal messages to the node')
out = [self.name, ]
@@ -841,7 +857,7 @@
Message.ListPeerNotes,
NodeIdentifier=identifier
)
- JobBase.__init__(self, fcpClient, identifier, message)
+ JobBase.__init__(self, fcpClient, IdentifierPrefix.PeerNote + identifier, message)
def handleStart(self):
@@ -906,7 +922,7 @@
string and size may not be accurate.
"""
- identifier = newIdentifier()
+ identifier = IdentifierPrefix.ClientGet + newIdentifier()
message = Message(
Message.ClientGet,
Identifier=identifier,
@@ -1158,8 +1174,6 @@
#TODO: no idea what happens on reconnect if socket died. What about running jobs?
#TODO: name as specified in NodeHello seems to be usable to keep jobs alive. Have to test this.
-#TODO: no idea if to add support for pending jobs and queue management here
-#
#TODO: do not mix directories as identifiers with identifiers (might lead to collisions)
#TODO: how to handle (ProtocolError code 18: Shutting down)?
class FcpClient(events.Events):
@@ -1290,6 +1304,8 @@
return self.jobDispatchMessage(FixedJobIdentifiers.ClientHello, msg)
elif code == ProtocolError.ShuttingDown:
+
+ #TODO: ??? why dispatch to ClientHello.. can't remember
if not self.jobDispatchMessage(FixedJobIdentifiers.ClientHello, msg):
# ########################################
@@ -1308,39 +1324,26 @@
else:
# check if the is something like an identifier in the message
- #TODO: we run into troubles when using directories and NodeIdentifiers as identifiers
- # have to maintain extra queues to prevent this. jobDispatchMessage(queue='directories')
if msg.name == Message.TestDDAReply:
identifier = msg['Directory']
elif msg.name == Message.TestDDAComplete:
identifier = msg['Directory']
elif msg.name == Message.PeerNote:
- identifier = msg['NodeIdentifier']
+ identifier = IdentifierPrefix.PeerNote + msg['NodeIdentifier']
elif msg.name == Message.EndListPeerNotes:
- identifier = msg['NodeIdentifier']
-
+ identifier = IdentifierPrefix.PeerNote + msg['NodeIdentifier']
else:
identifier = msg.get('Identifier', None)
# dispatch to jobs with fixed identifiers
if identifier is None:
-
if msg.name == Message.NodeHello:
return self.jobDispatchMessage(FixedJobIdentifiers.ClientHello, msg)
-
elif msg.name == Message.EndListPeers:
return self.jobDispatchMessage(FixedJobIdentifiers.ListPeers, msg)
-
elif msg.name == Message.Peer:
return self.jobDispatchMessage(FixedJobIdentifiers.ListPeers, msg)
- #elif msg.name == Message.PeerNote:
- # return self.jobDispatchMessage(FixedJobIdentifiers.ListPeerNotes, msg)
-
- #elif msg.name == Message.EndListPeerNotes:
- # return self.jobDispatchMessage(FixedJobIdentifiers.ListPeerNotes, msg)
-
-
# more here.....
else:
@@ -1349,7 +1352,7 @@
else:
return self.jobDispatchMessage(identifier, msg)
- raise RuntimeError('Should not have endet here: %s' % msg.name)
+ raise RuntimeError('We should not have gotten here: %s' % msg.name)
@@ -1670,14 +1673,8 @@
#foo()
- def foo():
- job = JobGenerateSSK(c)
- c.jobAdd(job, synchron=True)
- print job.jobResult
- #foo()
-
+
-
def foo():
d = os.path.dirname(os.path.abspath(__file__))
job2 = JobTestDDA(c, d)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-10-28 21:56:17
|
Revision: 20
http://fclient.svn.sourceforge.net/fclient/?rev=20&view=rev
Author: jUrner
Date: 2007-10-28 14:56:19 -0700 (Sun, 28 Oct 2007)
Log Message:
-----------
some more cooments
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-28 09:45:09 UTC (rev 19)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-28 21:56:19 UTC (rev 20)
@@ -51,14 +51,6 @@
'''
-#NOTE:
-#
-# downloading data to disk is not supported st the moment. TestDDA code is quite unwritable
-# and as far as I can see there are plans to get rid of it. So wait...
-#
-#
-
-
import atexit
import base64
import logging
@@ -688,6 +680,8 @@
#**************************************************************************
# jobs
#**************************************************************************
+#TODO: maybe remove syncron functionality and rely only on signals
+# ...if so, remove timeStart, timeStop aswell.. leave up to caller
class JobBase(object):
"""Base class for jobs"""
@@ -1165,10 +1159,9 @@
#TODO: no idea what happens on reconnect if socket died. What about running jobs?
#TODO: name as specified in NodeHello seems to be usable to keep jobs alive. Have to test this.
#TODO: no idea if to add support for pending jobs and queue management here
-
+#
#TODO: do not mix directories as identifiers with identifiers (might lead to collisions)
#TODO: how to handle (ProtocolError code 18: Shutting down)?
-
class FcpClient(events.Events):
"""Fcp client implementation
"""
@@ -1316,6 +1309,7 @@
# check if the is something like an identifier in the message
#TODO: we run into troubles when using directories and NodeIdentifiers as identifiers
+ # have to maintain extra queues to prevent this. jobDispatchMessage(queue='directories')
if msg.name == Message.TestDDAReply:
identifier = msg['Directory']
elif msg.name == Message.TestDDAComplete:
@@ -1534,17 +1528,30 @@
#########################################################
- ## boilerplate code to tackle TestDDA
+ ## how to tackle TestDDA?
##
- ## ...but I don't trust it ;-) I was not yet able to wrap my head around
- ## jobAdd(synchron=True) enough to know wether it is save (thread, deadlock) or not.
+ ## best idea hear so far is to wait for ProtocolError 25 Test DDA denied (or PersistantGet)
+ ## and reinsert the job if necessary after TestDDA completion.
##
- ## Another problem is that there is no way to know when a directory is no longer
- ## needed. And I fon't want to write code in a Gui to tackle a problem that will hopefully
- ## go away in the near future.
+ ## Problem is, how to wait for a message without flooding the caller. Basic idea of the client
+ ## is to enshure a Gui can stay responsitive by letting the caller decide when to process the
+ ## next message. So waiting would require to buffer messages and watch messages carefuly
+ ## as they flood in.
+ ##
+ ## If we do not wait, the caller may flood us with download requests, I fear, faster than
+ ## the node and we are able to go get the error and through the TestDDA drill. Have to
+ ## do some tests to see how the node reacts.
##
- ## see: https://bugs.freenetproject.org/view.php?id=1753
+ ## easiest approach would be to let the caller test a directory explicitely when HE thinks
+ ## it might be necessary. But then this code will hang around forever with an already
+ ## assigned bug report [https://bugs.freenetproject.org/view.php?id=1753] suggesting
+ ## much easier processing to test DDA (DDA Challenge)
##
+ ##
+ ## so.. maybe best is to lurker around a while and keep an eye on the tracker
+ ##
+ ##
+ ## below is just some old boilerplate code.. to be removed sooner or later
#########################################################
def testWriteAccess(self, directory):
canRead, canWrite = False, False
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-10-28 09:45:05
|
Revision: 19
http://fclient.svn.sourceforge.net/fclient/?rev=19&view=rev
Author: jUrner
Date: 2007-10-28 02:45:09 -0700 (Sun, 28 Oct 2007)
Log Message:
-----------
ups, wasn't meant to get in here
Removed Paths:
-------------
trunk/fclient/fclient_lib/fcp/test_fcp2_0/oo1.py
Deleted: trunk/fclient/fclient_lib/fcp/test_fcp2_0/oo1.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/test_fcp2_0/oo1.py 2007-10-28 09:44:05 UTC (rev 18)
+++ trunk/fclient/fclient_lib/fcp/test_fcp2_0/oo1.py 2007-10-28 09:45:09 UTC (rev 19)
@@ -1,5 +0,0 @@
-
-import socket
-
-
-raise socket.error('foo')
\ 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...> - 2007-10-28 09:44:01
|
Revision: 18
http://fclient.svn.sourceforge.net/fclient/?rev=18&view=rev
Author: jUrner
Date: 2007-10-28 02:44:05 -0700 (Sun, 28 Oct 2007)
Log Message:
-----------
added some basic unittests for message objects
Added Paths:
-----------
trunk/fclient/fclient_lib/fcp/test_fcp2_0/
trunk/fclient/fclient_lib/fcp/test_fcp2_0/__init__.py
trunk/fclient/fclient_lib/fcp/test_fcp2_0/oo1.py
trunk/fclient/fclient_lib/fcp/test_fcp2_0/test_message_object.py
Added: trunk/fclient/fclient_lib/fcp/test_fcp2_0/__init__.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/test_fcp2_0/__init__.py (rev 0)
+++ trunk/fclient/fclient_lib/fcp/test_fcp2_0/__init__.py 2007-10-28 09:44:05 UTC (rev 18)
@@ -0,0 +1 @@
+
Added: trunk/fclient/fclient_lib/fcp/test_fcp2_0/oo1.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/test_fcp2_0/oo1.py (rev 0)
+++ trunk/fclient/fclient_lib/fcp/test_fcp2_0/oo1.py 2007-10-28 09:44:05 UTC (rev 18)
@@ -0,0 +1,5 @@
+
+import socket
+
+
+raise socket.error('foo')
\ No newline at end of file
Added: trunk/fclient/fclient_lib/fcp/test_fcp2_0/test_message_object.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/test_fcp2_0/test_message_object.py (rev 0)
+++ trunk/fclient/fclient_lib/fcp/test_fcp2_0/test_message_object.py 2007-10-28 09:44:05 UTC (rev 18)
@@ -0,0 +1,177 @@
+"""Unittests for Message object"""
+
+import os, sys
+import socket
+import unittest
+
+#--> rel import hack
+def parentdir(n, fpath):
+ fpath = os.path.abspath(fpath)
+ for i in xrange(n):
+ fpath = os.path.dirname(fpath)
+ return fpath
+sys.path.insert(0, parentdir(2, __file__))
+
+
+from fcp2_0 import Message
+
+sys.path.pop(0)
+del parentdir
+#<-- rel import hack
+
+#********************************************************************
+#
+#********************************************************************
+class DummySocket(object):
+ """Dummy socket for testing"""
+
+ def __init__(self, bytes, closed=False, error=False):
+ self.bytes = bytes
+ self.closed = closed
+ self.error = error
+
+ def recv(self, n):
+ if self.closed:
+ return ''
+ elif self.error:
+ raise socket.error(0, 'I am dead!')
+ self.bytes, bytes = self.bytes[n:], self.bytes[:n]
+ if bytes:
+ return bytes
+ raise socket.timeout(0)
+
+ def sendall(self, bytes):
+ self.bytes += bytes
+
+#****************************************************************************************
+#
+#****************************************************************************************
+class TestMessageObject(unittest.TestCase):
+
+
+ def testFromSocket(self):
+ """Reads a simple message from socket"""
+ s = DummySocket('NodeHello\nfoo=bar\nEndMessage\n')
+ msg = Message.fromSocket(s)
+
+ self.failUnless(msg.name == Message.NodeHello)
+ self.failUnless(len(msg.params) == 1)
+ self.failUnless(msg.get('foo', None) == 'bar')
+
+
+ def testFromSocketClosed(self):
+ """Reads a message from a closed socket"""
+ s = DummySocket('NodeHello\nfoo=bar\nEndMessage\n', closed=True)
+ msg = Message.fromSocket(s)
+
+ self.failUnless(msg.name == Message.ClientSocketDied)
+ self.failUnless(msg.get('Exception', None) == socket.error)
+
+
+
+ def testFromSocketError(self):
+ """Reads a message from a socket that raises emidiately"""
+ s = DummySocket('NodeHello\nfoo=bar\nEndMessage\n', error=True)
+ msg = Message.fromSocket(s)
+
+ self.failUnless(msg.name == Message.ClientSocketDied)
+ self.failUnless(msg.get('Exception', None) == socket.error)
+
+
+ def testFromSocketTimeout(self):
+ """Reads a message from a socket with no bytes available"""
+ s = DummySocket('')
+ msg = Message.fromSocket(s)
+
+ self.failUnless(msg.name == Message.ClientSocketTimeout)
+
+
+ def testDataFromSocket(self):
+ """Reads a message with associated data member from a socket"""
+ s = DummySocket('AllData\nDataLength=8\nData\n\nABC\nDEF')
+ msg = Message.fromSocket(s)
+
+ self.failUnless(msg.name == Message.AllData)
+ self.failUnless(len(msg.params) == 1)
+ self.failUnless(msg.get('DataLength', None) == '8')
+ self.failUnless(msg.data == '\nABC\nDEF')
+
+
+ def testSend(self):
+ """Sends a simple message"""
+ s = DummySocket('')
+ msg = Message(
+ Message.ClientHello,
+ foo='bar'
+ )
+ msg.send(s)
+ self.failUnless(s.bytes == 'ClientHello\nfoo=bar\nEndMessage\n')
+
+
+ def testSendData(self):
+ """Sends a message containing a data ,member"""
+ s = DummySocket('')
+ msg = Message(
+ Message.AllData,
+ DataLength='5',
+ data='ABCDE'
+ )
+ msg.send(s)
+ self.failUnless(s.bytes == 'AllData\nDataLength=5\nData\nABCDE')
+
+ # test error checking of data member
+ msg = Message(
+ Message.AllData,
+ ##DataLength='1', # no data length specified
+ data='A'
+ )
+ self.assertRaises(AssertionError, msg.send, s)
+ msg = Message(
+ Message.AllData,
+ DataLength='1',
+ data='A' * 100
+ )
+ self.assertRaises(AssertionError, msg.send, s)
+ msg = Message(
+ Message.AllData,
+ DataLength='100',
+ data='A'
+ )
+ self.assertRaises(AssertionError, msg.send, s)
+ msg = Message(
+ Message.AllData,
+ DataLength='X',
+ data='A'
+ )
+ self.assertRaises(AssertionError, msg.send, s)
+
+
+
+
+#*********************************************************************************
+#
+#*********************************************************************************
+def suite():
+ return unittest.TestLoader().loadTestsFromTestCase(TestMessageObject)
+
+def test():
+ unittest.main()
+
+if __name__ == '__main__':
+ test()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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...> - 2007-10-28 09:43:18
|
Revision: 17
http://fclient.svn.sourceforge.net/fclient/?rev=17&view=rev
Author: jUrner
Date: 2007-10-28 02:43:20 -0700 (Sun, 28 Oct 2007)
Log Message:
-----------
combed over message object ++ some bug fixes
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-27 20:59:45 UTC (rev 16)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-28 09:43:20 UTC (rev 17)
@@ -328,8 +328,12 @@
"""
if fpath is not None:
if os.path.isfile(fpath):
- os.remove(fpath)
- return True
+ try:
+ os.remove(fpath)
+ except Exception, d:
+ pass
+ else:
+ return True
return False
@@ -465,6 +469,8 @@
class Message(object):
"""Class wrapping a freenet message"""
+ __slots__ = ('name', 'data', 'params')
+
# client messages
ClientHello = 'ClientHello'
ListPeer = 'ListPeer' # (since 1045)
@@ -527,12 +533,11 @@
UnknownPeerNoteType = 'UnknownPeerNoteType'
SubscribedUSKUpdate = 'SubscribedUSKUpdate'
-
# client messages (internal use only)
ClientSocketTimeout = 0
ClientSocketDied = 1
+
-
def __init__(self, name, data=None, **params):
"""
@param name: messge name
@@ -545,70 +550,102 @@
self.params = params
+ @classmethod
+ def bytesFromSocket(clss, socketObj, n):
+ """Reads n bytes from socket
+ @param socketObj: socket to read bytes from
+ @param n: (int) number of bytes to read
+ @return: (tuple) error-message or None, bytes read or None if an error occured
+ or no bytes could be read
+ """
+ error = p = None
+ try:
+ p = socketObj.recv(n)
+ if not p:
+ p = None
+ raise socket.error('Socket shut down by node')
+ except socket.timeout, d: # no new messages in queue
+ error = clss(clss.ClientSocketTimeout)
+ except socket.error, d:
+ error = clss(clss.ClientSocketDied, Exception=socket.error, Details=d)
+ return error, p
+
+
+ @classmethod
+ def fromSocket(clss, socketObj):
+ """Reads a message from a socket
+ @param socketObj: socket to read a message from
+ @return: L{Message} next message from the socket. If the socket dies
+ unexpectedly a L{ClientSocketDied} message is returned containing the parameters
+ 'Exception' and 'Details'. If the socket times out a L{MessageClientSocketTimout}
+ message is returned.
+ """
+
+ msg = clss(None)
+ buf = []
+
+ #TODO: to buffer or not to buffer?
+ while True:
+
+ # get next line from socket
+ error, p = clss.bytesFromSocket(socketObj, 1)
+ if error:
+ return error
+
+ if p != '\n':
+ buf.append(p)
+ continue
+ #TODO: check if '\r\n' is allowed in freenet client protocol
+ else:
+ if buf[-1] == '\r':
+ del buf[-1]
+
+ line = ''.join(buf)
+ buf = []
+ if line == 'EndMessage':
+ break
+
+ # first line == message name
+ if msg.name is None:
+ msg.name = line
+
+ # get data member
+ elif line == 'Data':
+ remaining = int(msg.params['DataLength'])
+ msg.data = ''
+ while remaining > 0:
+ error, p = clss.bytesFromSocket(socketObj, remaining)
+ if error:
+ return error
+ remaining -= len(p)
+ msg.data += p
+ break
+
+ # get next paramater
+ else:
+ head, sep, tail = line.partition('=')
+ msg.params[head] = tail
+ # TODO: errorchek params?
+ #if not sep: pass
+
+ return msg
+
+
def get(self, name, default=None):
"""Returns the message parameter 'name' or 'default' """
return self.params.get(name, default)
+
def __getitem__(self, name):
"""Returns the message parameter 'name' """
return self.params[name]
+
def __setitem__(self, name, value):
"""Sets the message parameter 'name' to 'value' """
self.params[name] = value
- @classmethod
- def fromSocket(clss, socketObj):
- msg = clss(None)
- buf = []
- while True:
-
- try:
- p = socketObj.recv(1)
- if not p: raise ValueError('Socket is dead')
- except socket.timeout, d: # no new messages in queue
- msg.name = clss.ClientSocketTimeOut
- return msg
- except Exception, d:
- msg.name = clss.ClientSocketDied
- msg['Exception'] = Exception
- msg['Details'] = d
- return msg
-
- 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 = socketObj.recv(n)
- if not msg.data: raise ValueError('Socket is dead')
- except Exception, d:
- msg.name = clss.ClientSocketDied
- msg['Exception'] = Exception
- msg['Details'] = d
- return msg
-
- else:
- head, sep, tail = line.partition('=')
- msg.params[head] = tail
- if not sep:
- # TODO: chek for invalid messages or not
- pass
-
- return msg
-
+
def pprint(self):
"""Returns the message as nicely formated human readable string"""
out = ['', '>>' + self.name, ]
@@ -617,6 +654,14 @@
out.append('>>EndMessage')
return '\n'.join(out)
+
+ def send(self, socketObj):
+ """Dumps the message to a socket
+ @param socketObj: socket to dump the message to
+ """
+ socketObj.sendall(self.toString())
+
+
def toString(self):
"""Returns the message as formated string ready to be send"""
# TODO: "Data" not yet implemented
@@ -625,7 +670,18 @@
out = [self.name, ]
for param, value in self.params.items():
out.append('%s=%s' % (param, value))
- out.append('EndMessage\n')
+ if self.data:
+ assert 'DataLength' in self.params, 'DataLength member required'
+ n = None
+ try:
+ n = int(self['DataLength'])
+ except ValueError: pass
+ assert n is not None, 'DataLength member must be an integer'
+ assert n == len(self.data), 'DataLength member must corrospond to lenght of data'
+ out.append('Data')
+ out.append(self.data)
+ else:
+ out.append('EndMessage\n')
return '\n'.join(out)
@@ -1448,7 +1504,7 @@
"""
self._log.debug(self._logMessages.MessageSend + msg.pprint())
try:
- self._socket.sendall(msg.toString())
+ msg.send(self._socket)
except socket.error, d:
self._log.info(self._logMessages.SocketDead)
self.close()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-10-27 20:59:41
|
Revision: 16
http://fclient.svn.sourceforge.net/fclient/?rev=16&view=rev
Author: jUrner
Date: 2007-10-27 13:59:45 -0700 (Sat, 27 Oct 2007)
Log Message:
-----------
bit of refactoring + play ClientHello as save as possible
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-27 17:00:21 UTC (rev 15)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-27 20:59:45 UTC (rev 16)
@@ -1,4 +1,54 @@
'''Freenet client protocol 2.0 implementation
+
+
+@newfield event, events
+
+
+
+Sample code::
+
+ client = FcpClient()
+ nodeHello = client.connect()
+ if nodeHello is not None:
+ # do whatever
+
+
+
+Most method calls can be made either synchron or asynchron::
+
+ peers = client.peerList(synchron=True)
+ for peer in peers:
+ # do whatever
+
+
+To get informed about asynchron events you should connect the relevant events the client provides::
+
+ # connect to one single event
+ client.EventListNextPeer += MyCallback
+
+ # connect to multiple events at once
+ client += (
+ (client.EventListPeers, MyCallback1),
+ (client.EventEndListPeers, MyCallback2),
+ )
+
+ # each callback is called with the event as first parameter, followed by additional parameters,
+ # depending on the event triggered.
+ def MyListNextPeerCallback(event, peer):
+ print peer
+
+ client.peerList(synchron=False)
+
+
+ # when event notifications are no longer required, you should always make shure to disconnect from them
+ client.EventListNextPeer -= MyCallback
+ client -= (
+ (client.EventListPeers, MyCallback1),
+ (client.EventEndListPeers, MyCallback2),
+ )
+
+
+
'''
#NOTE:
@@ -478,6 +528,11 @@
SubscribedUSKUpdate = 'SubscribedUSKUpdate'
+ # client messages (internal use only)
+ ClientSocketTimeout = 0
+ ClientSocketDied = 1
+
+
def __init__(self, name, data=None, **params):
"""
@param name: messge name
@@ -489,15 +544,71 @@
self.name = name
self.params = params
- def toString(self):
- """Returns the message as formated string 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 get(self, name, default=None):
+ """Returns the message parameter 'name' or 'default' """
+ return self.params.get(name, default)
+
+ def __getitem__(self, name):
+ """Returns the message parameter 'name' """
+ return self.params[name]
+
+ def __setitem__(self, name, value):
+ """Sets the message parameter 'name' to 'value' """
+ self.params[name] = value
+
+ @classmethod
+ def fromSocket(clss, socketObj):
+ msg = clss(None)
+ buf = []
+ while True:
+
+ try:
+ p = socketObj.recv(1)
+ if not p: raise ValueError('Socket is dead')
+ except socket.timeout, d: # no new messages in queue
+ msg.name = clss.ClientSocketTimeOut
+ return msg
+ except Exception, d:
+ msg.name = clss.ClientSocketDied
+ msg['Exception'] = Exception
+ msg['Details'] = d
+ return msg
+
+ 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 = socketObj.recv(n)
+ if not msg.data: raise ValueError('Socket is dead')
+ except Exception, d:
+ msg.name = clss.ClientSocketDied
+ msg['Exception'] = Exception
+ msg['Details'] = d
+ return msg
+
+ else:
+ head, sep, tail = line.partition('=')
+ msg.params[head] = tail
+ if not sep:
+ # TODO: chek for invalid messages or not
+ pass
+
+ return msg
+
def pprint(self):
"""Returns the message as nicely formated human readable string"""
out = ['', '>>' + self.name, ]
@@ -505,28 +616,19 @@
out.append('>> %s=%s' % (param, value))
out.append('>>EndMessage')
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
-
+ def toString(self):
+ """Returns the message as formated string ready to be send"""
+ # TODO: "Data" not yet implemented
+ if isinstance(self.name, (int, long)):
+ raise ValueError('You can not send client internal messages to the node')
+ out = [self.name, ]
+ for param, value in self.params.items():
+ out.append('%s=%s' % (param, value))
+ out.append('EndMessage\n')
+ return '\n'.join(out)
-class MessageSocketTimeout(Message):
-
- def __init__(self):
- Message.__init__(self, 'USocketTimeOut')
-
-
#**************************************************************************
# jobs
#**************************************************************************
@@ -1012,7 +1114,8 @@
#TODO: how to handle (ProtocolError code 18: Shutting down)?
class FcpClient(events.Events):
- """Fcp client implementation"""
+ """Fcp client implementation
+ """
_events_ = (
'EventListPeers',
@@ -1094,17 +1197,25 @@
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 the socket simply breaks. So take it over.
+ # as expected when not doing so, the node disconnects.
+ # So take it over here.
job = JobClientHello(self)
- self.jobAdd(job, synchron=True)
- assert job.jobResult is not None, 'ClientHello is not working as expected'
- return job.jobResult
-
+ self.jobAdd(job, synchron=False)
+ while time_elapsed <= repeat:
+ msg = self.next()
+ if msg.name == Message.ClientSocketTimeout:
+ time_elapsed += SocketTimeout
+ elif msg.name == Message.NodeHello:
+ return msg.params
+ else:
+ break
+ break
+
+ # continue polling
self._log.info(self._logMessages.ConnectionRetry)
-
- # continue polling
time_elapsed += timeout
time.sleep(timeout)
@@ -1117,9 +1228,8 @@
@param msg: (Message) to handle
@return: True if the message was handled, False otherwise
"""
-
-
- if msg.name == 'USocketTimeOut':
+
+ if msg.name == Message.ClientSocketTimeout:
return True
self._log.debug(self._logMessages.MessageReceived + msg.pprint())
@@ -1190,8 +1300,7 @@
return self.jobDispatchMessage(identifier, msg)
raise RuntimeError('Should not have endet here: %s' % msg.name)
-
-
+
#########################################################
@@ -1212,6 +1321,7 @@
return result
+ #TODO: not quite clear about the consequences of a synchron job. Have to think this over
def jobAdd(self, job, synchron=False):
"""Adds a job to the client
@param job: (Job*) job to add
@@ -1288,87 +1398,28 @@
return True
- #TODO: some info when all jobs are completed
+ #TODO: some info when all jobs are completed?
def next(self):
"""Pumps the next message waiting
@note: use this method instead of run() to run the client step by step
"""
- msg = self.readMessage()
+ msg = Message.fromSocket(self._socket)
+ if msg.name == Message.ClientSocketDied:
+ raise SocketError(msg['Details'])
self.handleMessage(msg)
return msg
- def readMessage(self):
- """Reads the next message directly from the socket and dispatches it
- @return: (Message) the next message read from the socket
- @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.
- """
- 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:
- self._log.info(self._logMessages.SocketDead)
- self.close()
- if self._errorHandler is not None:
- self._errorHandler(SocketError, d)
- raise SocketError(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)
- if not msg.data: raise ValueError('Socket is dead')
- except Exception, d:
- self._log.info(self._logMessages.SocketDead)
- self.close()
- if self._errorHandler is not None:
- self._errorHandler(SocketError, d)
- raise SocketError(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 run(self):
"""Runs the client untill all jobs passed to it are completed
@note: use KeyboardInterrupt to stop prematurely
"""
try:
#n = 0
-
while self.hasJobsRunning():
#n += 1
#if n > 40: break
self.next()
-
except KeyboardInterrupt:
self._log(self._logMessages.KeyboardInterrupt)
self.close()
@@ -1429,7 +1480,7 @@
#########################################################
## boilerplate code to tackle TestDDA
##
- ## ...but I don't trust it ;-) I was not yet alble to wrap my head around
+ ## ...but I don't trust it ;-) I was not yet able to wrap my head around
## jobAdd(synchron=True) enough to know wether it is save (thread, deadlock) or not.
##
## Another problem is that there is no way to know when a directory is no longer
@@ -1498,13 +1549,30 @@
##
#################################################
def peerList(self, synchron=False):
+ """Lists all peers of the node
+ @param synchron: if True, waits untill the call is completed, if False returns emidiately
+ @return: (list) of peers in a synchron, always None in an asynchron call
+
+ @event: EventListPeers(event).
+ @event: EventListNextPeer(event, peer).
+ @event: EventEndListPeers(event).
+ """
job = JobListPeers(self)
self.jobAdd(job, synchron=synchron)
return job.jobResult
def peerNotes(self, peer, synchron=False):
- if pythonBool(peer['opennet']):
+ """Lists all text notes associated to a peer
+ @param peer: peer as returned in a call to L{peerList}
+ @param synchron: if True, waits untill the call is completed, if False returns emidiately
+ @return: (list) of notes in a synchron, always None in an asynchron call
+
+ @event: EventListPeerNotes(event).
+ @event: EventListNextPeerNote(event, note).
+ @event: EventEndListPeerNotes(event).
+ """
+ if pythonBool(peer['opennet']): # opennet peers do not have any notes associated
return []
job = JobListPeerNotes(self, peer['identity'])
self.jobAdd(job, synchron=synchron)
@@ -1517,7 +1585,10 @@
if __name__ == '__main__':
c = FcpClient(name='test', verbosity=logging.DEBUG)
nodeHello = c.connect()
- if nodeHello is not None:
+ if nodeHello is not None or 1:
+
+
+
def foo():
job1 = JobClientHello(c)
c.jobAdd(job1)
@@ -1529,8 +1600,13 @@
# should raise
#foo()
-
+ def foo():
+ job = JobGenerateSSK(c)
+ c.jobAdd(job, synchron=True)
+ print job.jobResult
+ #foo()
+
def foo():
job = JobGenerateSSK(c)
c.jobAdd(job, synchron=True)
@@ -1554,7 +1630,7 @@
for peer in peers:
print c.peerNotes(peer, synchron=True)
- foo()
+ #foo()
def foo():
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-10-27 17:00:16
|
Revision: 15
http://fclient.svn.sourceforge.net/fclient/?rev=15&view=rev
Author: jUrner
Date: 2007-10-27 10:00:21 -0700 (Sat, 27 Oct 2007)
Log Message:
-----------
started implementing public methods and events
Modified Paths:
--------------
trunk/fclient/fclient_lib/fcp/fcp2_0.py
Modified: trunk/fclient/fclient_lib/fcp/fcp2_0.py
===================================================================
--- trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-27 16:59:07 UTC (rev 14)
+++ trunk/fclient/fclient_lib/fcp/fcp2_0.py 2007-10-27 17:00:21 UTC (rev 15)
@@ -21,6 +21,24 @@
import thread
import uuid
+
+#--> rel import hack
+def parentdir(n, fpath):
+ fpath = os.path.abspath(fpath)
+ for i in xrange(n):
+ fpath = os.path.dirname(fpath)
+ return fpath
+sys.path.insert(0, parentdir(3, __file__))
+
+
+from fclient_lib.pyex import events
+
+
+sys.path.pop(0)
+del parentdir
+#<-- rel import hack
+
+
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
#**************************************************************
# consts
@@ -32,6 +50,14 @@
DefaultFcpPort = 9481
SocketTimeout = 0.1
+
+
+class Verbosity:
+ Debug = logging.DEBUG
+ Info = logging.INFO
+ Warning = logging.WARNING
+
+
class FixedJobIdentifiers:
"""Fixed job identifiers
@note: he client can only handle one job of these at a time
@@ -44,6 +70,7 @@
ModifyConfig = 'ModifyConfig'
WatchGlobal = 'WatchGlobal'
Shutdown = 'Shutdown'
+
@@ -61,6 +88,23 @@
PriorityMin = Minimum
PriorityDefault = Bulk
+
+class PeerNodeStatus:
+ Connected = 1
+ RoutingBackedOff = 2
+ TooNew = 3
+ TooOld = 4
+ Disconnected = 5
+ NeverConnected = 6
+ Disabled = 7
+ Bursting = 8
+ Listening = 9
+ ListenOnly = 10
+ ClockProblem = 11
+ ConnError = 12
+ Disconnecting = 13
+
+
#************************************************************************************
# exceptions
#************************************************************************************
@@ -194,11 +238,13 @@
return 'false'
-def newIdentifier():
+def newIdentifier(prefix=None):
"""Returns a new unique identifier
@return: (str) uuid
"""
- return 'fclient::' + str(uuid.uuid4())
+ if prefix:
+ return prefix + str(uuid.uuid4())
+ return str(uuid.uuid4())
def pythonBool(fcpBool):
@@ -580,7 +626,7 @@
"""Lists all known peers of the node
"""
- def __init__(self, fcpClient, withMetaData=False, withVolantile=False):
+ def __init__(self, fcpClient, withMetaData=True, withVolantile=True):
"""
@param withMetaData: include meta data for each peer?
@param withVolantile: include volantile data for each peer?
@@ -594,6 +640,11 @@
JobBase.__init__(self, fcpClient, FixedJobIdentifiers.ListPeers, message)
+ def handleStart(self):
+ JobBase.handleStart(self)
+ self.jobClient.EventListPeers()
+
+
def handleMessage(self,msg):
if msg.name == Message.EndListPeers:
return self.handleEndListPeers(msg)
@@ -604,14 +655,16 @@
def handlePeer(self, msg):
+ self.jobClient.EventListNextPeer(msg.params)
if self.jobResult is None:
- self.jobResult = [msg, ]
+ self.jobResult = [msg.params, ]
else:
- self.jobResult.append(msg)
+ self.jobResult.append(msg.params)
return True
def handleEndListPeers(self, msg):
+ self.jobClient.EventEndListPeers()
self.jobTimeStop = time.time()
if self.jobResult is None:
self.jobResult = []
@@ -639,6 +692,11 @@
JobBase.__init__(self, fcpClient, identifier, message)
+ def handleStart(self):
+ JobBase.handleStart(self)
+ self.jobClient.EventListPeerNotes()
+
+
def handleMessage(self,msg):
if msg.name == Message.EndListPeerNotes:
return self.handleEndListPeerNotes(msg)
@@ -649,6 +707,7 @@
def handlePeerNote(self, msg):
note = msg.get('NoteText', '')
+ self.jobClient.EventListNextPeerNote(note)
if note:
note = base64.decodestring(note)
if self.jobResult is None:
@@ -659,6 +718,7 @@
def handleEndListPeerNotes(self, msg):
+ self.jobClient.EventEndListPeerNotes()
self.jobTimeStop = time.time()
if self.jobResult is None:
self.jobResult = []
@@ -812,8 +872,6 @@
@ivar jobResult: when the job is complete this will be set to a tuple(bool readAllowed, bool writeAllowed)
"""
-
-
if not os.path.isdir(directory):
raise ValueError('No such directory: %r' % directory)
@@ -823,6 +881,7 @@
WantReadDirectory=fcpBool(read),
WantWriteDirectory=fcpBool(write),
)
+
JobBase.__init__(self, fcpClient, directory, message)
self.jobTmpFile = None
@@ -832,10 +891,24 @@
return self.handleTestDDAReply(msg)
elif msg.name == Message.TestDDAComplete:
return self.handleTestDDAComplete(msg)
+ elif msg.name == Message.ProtocolError:
+ return self.handleProtocolError(msg)
else:
raise ValueError('Unexpected message: %s' % msg.name)
+ def handleProtocolError(self, msg):
+ # most likely code 7 here...
+ # "Both WantReadDirectory and WantWriteDirectory are set to false: what's the point of sending a message?"
+ # ..a stupid response that is ;-)
+ self.jobTimeStop = time.time()
+ self.jobClient.jobRemove(self.jobIdentifier)
+ if msg['Code'] == ProtocolError.InvalidMessage:
+ self.jobResult = (False, False)
+ else:
+ raise ValueError('Unexpected message: %s' % msg.name)
+
+
def handleTestDDAReply(self, msg):
fpathWrite = msg.params.get('WriteFilename', None)
fpathRead = msg.params.get('ReadFilename', None)
@@ -938,14 +1011,25 @@
#TODO: do not mix directories as identifiers with identifiers (might lead to collisions)
#TODO: how to handle (ProtocolError code 18: Shutting down)?
-class FcpClient(object):
+class FcpClient(events.Events):
"""Fcp client implementation"""
+ _events_ = (
+ 'EventListPeers',
+ 'EventListNextPeer',
+ 'EventEndListPeers',
+
+ 'EventListPeerNotes',
+ 'EventListNextPeerNote',
+ 'EventEndListPeerNotes',
+
+ )
+
def __init__(self,
name='',
errorHandler=None,
- verbosity=logging.WARNING,
+ verbosity=Verbosity.Warning,
logMessages=LogMessages
):
"""
@@ -963,7 +1047,7 @@
'PendingJobs': [],
'RegisteredDirectories': [],
}
- self._errorHandler = errorHandler #TODO: check!
+ self._errorHandler = errorHandler #TODO: check if necessary!
self._log = logging.getLogger(name)
self._logMessages = logMessages
self._lock = thread.allocate_lock() # lock when resources are accessed
@@ -1065,6 +1149,7 @@
else:
# check if the is something like an identifier in the message
+ #TODO: we run into troubles when using directories and NodeIdentifiers as identifiers
if msg.name == Message.TestDDAReply:
identifier = msg['Directory']
elif msg.name == Message.TestDDAComplete:
@@ -1405,13 +1490,27 @@
finally:
self._jobs['PendingJobs'].remove(job)
return result
-
-
-
+
+ #################################################
+ ##
+ ## public methods
+ ##
+ #################################################
+ def peerList(self, synchron=False):
+ job = JobListPeers(self)
+ self.jobAdd(job, synchron=synchron)
+ return job.jobResult
+
+ def peerNotes(self, peer, synchron=False):
+ if pythonBool(peer['opennet']):
+ return []
+ job = JobListPeerNotes(self, peer['identity'])
+ self.jobAdd(job, synchron=synchron)
+ return job.jobResult
+
-
#*****************************************************************************
#
#*****************************************************************************
@@ -1436,7 +1535,7 @@
job = JobGenerateSSK(c)
c.jobAdd(job, synchron=True)
print job.jobResult
- foo()
+ #foo()
@@ -1451,29 +1550,18 @@
#foo()
def foo():
- job = JobListPeers(c)
- c.jobAdd(job)
- c.run()
- print '---------------------------'
- print job.jobResult
- print '---------------------------'
+ peers = c.peerList(synchron=True)
+ for peer in peers:
+ print c.peerNotes(peer, synchron=True)
+
+ foo()
- for peer in job.jobResult:
- if not pythonBool(peer['opennet']):
- job = JobListPeerNotes(c, peer['identity'])
- c.jobAdd(job, synchron=True)
- print '>>', job.jobResult
- #.get('NoteText')
-
- #foo()
-
-
def foo():
- job = JobGetFileInfo(c, 'USK@IK8rVHfjCz1i1IZwgyafsPNDGODCk~EtBgsKWHmPKoQ,FeDoFZ8YeUsU0vo1-ZI~GRhZjeyXyaGhn-xQkIoGpak,AQACAAE/FreeMulET/15/FreeMulET-0.6.29-lib-jar.zip')
+ #job = JobGetFileInfo(c, 'USK@IK8rVHfjCz1i1IZwgyafsPNDGODCk~EtBgsKWHmPKoQ,FeDoFZ8YeUsU0vo1-ZI~GRhZjeyXyaGhn-xQkIoGpak,AQACAAE/FreeMulET/15/FreeMulET-0.6.29-lib-jar.zip')
- #job = JobGetFileInfo(c, 'CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg')
+ job = JobGetFileInfo(c, 'CHK@sdNenKGj5mupxaSwo44jcW8dsX7vYTLww~BsRPtur0k,ZNRm9reMjtKEl9e-xFByKXbW6q4f6OQyfg~l9GRSAes,AAIC--8/snow_002%20%281%29.jpg')
#job.jobIdentifier = job.jobMessage['Identifier'] = 1
#job.jobMessage['Identifier'] = 1
#job.jobIdentifier = 1
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-10-27 16:59:08
|
Revision: 14
http://fclient.svn.sourceforge.net/fclient/?rev=14&view=rev
Author: jUrner
Date: 2007-10-27 09:59:07 -0700 (Sat, 27 Oct 2007)
Log Message:
-----------
ups..
Removed Paths:
-------------
trunk/fclient/fclient_lib/pyex/__init__.pyc
trunk/fclient/fclient_lib/pyex/events.pyc
trunk/fclient/fclient_lib/pyex/numbers.pyc
Deleted: trunk/fclient/fclient_lib/pyex/__init__.pyc
===================================================================
(Binary files differ)
Deleted: trunk/fclient/fclient_lib/pyex/events.pyc
===================================================================
(Binary files differ)
Deleted: trunk/fclient/fclient_lib/pyex/numbers.pyc
===================================================================
(Binary files differ)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <jU...@us...> - 2007-10-27 16:57:37
|
Revision: 13
http://fclient.svn.sourceforge.net/fclient/?rev=13&view=rev
Author: jUrner
Date: 2007-10-27 09:57:28 -0700 (Sat, 27 Oct 2007)
Log Message:
-----------
added python extensions module
Added Paths:
-----------
trunk/fclient/fclient_lib/pyex/
trunk/fclient/fclient_lib/pyex/__init__.py
trunk/fclient/fclient_lib/pyex/__init__.pyc
trunk/fclient/fclient_lib/pyex/events.py
trunk/fclient/fclient_lib/pyex/events.pyc
trunk/fclient/fclient_lib/pyex/namespace.py
trunk/fclient/fclient_lib/pyex/numbers.py
trunk/fclient/fclient_lib/pyex/numbers.pyc
trunk/fclient/fclient_lib/pyex/omapping.py
Added: trunk/fclient/fclient_lib/pyex/__init__.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/__init__.py (rev 0)
+++ trunk/fclient/fclient_lib/pyex/__init__.py 2007-10-27 16:57:28 UTC (rev 13)
@@ -0,0 +1 @@
+"""Pytrhon extension modules"""
Added: trunk/fclient/fclient_lib/pyex/__init__.pyc
===================================================================
(Binary files differ)
Property changes on: trunk/fclient/fclient_lib/pyex/__init__.pyc
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/fclient/fclient_lib/pyex/events.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/events.py (rev 0)
+++ trunk/fclient/fclient_lib/pyex/events.py 2007-10-27 16:57:28 UTC (rev 13)
@@ -0,0 +1,125 @@
+"""Signals and events"""
+
+#***********************************************************************
+#
+# event handler. Mostly taken from
+#http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/410686
+#
+#***********************************************************************
+class EventMeta(type):
+ """Metaclass for events"""
+
+ class Event(object):
+ """Event handler"""
+
+ def __init__(self, name):
+ """
+ @param name: name of the event
+ @attr name: name of the event
+ @attr observers: list of observers of the event
+ """
+ self.name = name
+ self.observers = []
+
+ def __call__(self, *args, **kwargs):
+ """Dispatches the event and additional parameters to all observers registerd"""
+ for o in self.observers:
+ o(self, *args, **kwargs)
+
+ def __iadd__(self, observer):
+ """Adds an observer to the event
+ @note: the observer will be called with the event as first paraeter,
+ followed by any number of *args or **kwargs passed by the caller of an event
+ """
+ self.observers.append(observer)
+ return self
+
+ def __isub__(self, observer):
+ """Removes the first occurence of an observer from the event"""
+ self.observers.remove(observer)
+ return self
+
+ def __new__(clss, name, bases, kws):
+ events = kws.get('_events_', None)
+ if events is None:
+ raise ValueError('Event classes must implement an "_event_" attribute')
+ for event_name in events:
+ kws[event_name] = clss.Event(event_name)
+ return type.__new__(clss, name, bases, kws)
+
+
+class Events(object):
+ """Base class for events
+
+ Derrived classes should list events they support in the "_events_" tuple.
+ Each event name is automagically set as attribute of the event
+ class.
+
+ Listeners may register to receiving events by calling __iadd__,
+ unregister by calling __isub__ on these attributes. Callback are
+ always called with the event as first argument, followed by additional
+ arguments, depending on the event.
+
+ Events have the following methods:
+
+ 'name': name of the event
+ 'observers': list of observers listening to the event
+
+
+ Note:
+ Always make shure to disconnnect when event notification is no longer desired
+
+
+ >>> class MyEvents(Events):
+ ... _events_ = ('FooEvent', 'BarEvent')
+ ...
+ >>> events = MyEvents()
+ >>> def cb(event):
+ ... print 'Received: %s' % event.name
+
+ >>> events.FooEvent += cb
+ >>> events.FooEvent()
+ Received: FooEvent
+
+ >>> events.FooEvent -= cb
+ >>> events.FooEvent()
+
+ >>> events += ( (events.FooEvent, cb), (events.BarEvent, cb) )
+ >>> events.FooEvent()
+ Received: FooEvent
+ >>> events.BarEvent()
+ Received: BarEvent
+
+ >>> events -= ( (events.FooEvent, cb), (events.BarEvent, cb) )
+ >>> events.FooEvent()
+
+ >>> events.BarEvent()
+
+ """
+ __metaclass__ = EventMeta
+ _events_ = ()
+
+ def __iadd__(self, events):
+ """Adds one or more events / observers at once
+ @param events: tuple( (event, observer), (event, observer), ...)
+ """
+ for event, observer in events:
+ event += observer
+ return self
+
+ def __isub__(self, events):
+ """Removes one or more events / observers at once
+ @param events: tuple( (event, observer), (event, observer), ...)
+ """
+ for event, observer in events:
+ event -= observer
+ return self
+
+
+#*********************************************************************
+#
+#*********************************************************************
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+
Added: trunk/fclient/fclient_lib/pyex/events.pyc
===================================================================
(Binary files differ)
Property changes on: trunk/fclient/fclient_lib/pyex/events.pyc
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/fclient/fclient_lib/pyex/namespace.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/namespace.py (rev 0)
+++ trunk/fclient/fclient_lib/pyex/namespace.py 2007-10-27 16:57:28 UTC (rev 13)
@@ -0,0 +1,115 @@
+"""Namespace handling"""
+
+import os
+import re
+#*********************************************************************
+#
+#*********************************************************************
+def unique_filename(fpath, prefix='', names=None):
+ """Creates a filepath with a unique (human readable) filename
+ @param: fpath: filepath to patch
+ @param prefix: prefix to add to the filename or ''
+ @param names: list of filenames to patch agaunst or None to check files in the directory
+ of filepath
+
+ @return: a filepath with one of these 'MyFile (1).txt' patched filenames
+
+ >>> names = ['foo.txt', 'foo (1).txt']
+ >>> unique_filename('foo.txt', names=names)
+ 'foo (2).txt'
+
+ >>> names = ['foo.txt', 'foo (2).txt']
+ >>> unique_filename('foo.txt', names=names)
+ 'foo (1).txt'
+
+ >>> names = ['foo.txt', ]
+ >>> result = unique_filename(os.path.join('myDir', 'foo.txt'), names=names)
+ >>> expected = os.path.join('myDir', 'foo (1).txt')
+ >>> result == expected
+ True
+
+ >>> names = ['foo.txt', ]
+ >>> unique_filename('foo.txt', names=names, prefix="Copy Of")
+ 'Copy Of foo.txt'
+
+ >>> names = ['foo.txt', 'Copy Of foo.txt']
+ >>> unique_filename('foo.txt', names=names, prefix="Copy Of")
+ 'Copy Of foo (1).txt'
+
+ """
+ filename = os.path.basename(fpath)
+ dirname = os.path.dirname(fpath)
+ if not filename:
+ raise ValueError("No filename found: %s" % fpath)
+
+ n = 0
+ tmp_filename = filename
+ while True:
+ if names is not None:
+ if tmp_filename not in names: break
+ else:
+ if not os.path.exists(os.path.join(dirname, tmp_filename)): break
+
+ n += 1
+ tmp_filename, tmp_ext = os.path.splitext(filename)
+ if prefix:
+ if n == 1:
+ tmp_filename = '%s %s' % (prefix, tmp_filename)
+ else:
+ tmp_filename = '%s %s (%s)' % (prefix, tmp_filename, n -1)
+ else:
+ tmp_filename = '%s (%s)' % (tmp_filename, n)
+ tmp_filename = tmp_filename + tmp_ext
+
+ return os.path.join(dirname, tmp_filename)
+
+
+#*********************************************************************
+#
+#*********************************************************************
+def unquote_uri(uri, slash='-'):
+ """Unquotes an uri so it can be used as a name in the filesystem
+ @param uri: (str) uri to unquote
+ @param slasch: (str) substitution string shlashes to use or None to keep slashes
+ @return: (str) uri
+
+ >>> unquote_uri('foo/bar')
+ 'foo-bar'
+ >>> unquote_uri('foo/b%20ar')
+ 'foo-b ar'
+ >>> unquote_uri('foo/b%34ar')
+ 'foo-b%34ar'
+ >>> unquote_uri('foo/bar', slash='77')
+ 'foo77bar'
+ >>> unquote_uri('foo/bar', slash=None)
+ 'foo/bar'
+
+ """
+
+ # impossible to handle all the dos and donts on oses. So do minimum
+ # to enshure user readability
+ if slash is not None:
+ uri = uri.replace('/', slash)
+ uri = uri.replace('%20', ' ')
+ uri = uri.replace('%28', '(')
+ uri = uri.replace('%29', ')')
+ return uri
+
+
+#*********************************************************************
+#
+#*********************************************************************
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+
+#TODO: handle fpath='myFolder/foo (1).txt' ?
+#a = 'foo (123)'
+#p = re.compile('\A(.*)\(([0-9]+)\)\Z')
+
+
+
+
+
+
+
Added: trunk/fclient/fclient_lib/pyex/numbers.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/numbers.py (rev 0)
+++ trunk/fclient/fclient_lib/pyex/numbers.py 2007-10-27 16:57:28 UTC (rev 13)
@@ -0,0 +1,143 @@
+"""Number crunching and others
+
+"""
+
+#***************************************************************
+#
+#***************************************************************
+def format_num_bytes(num, short=True, conform=True):
+ """Formats a number representing a number of bytes to a human readable string
+ @param num: (int) number to fomat
+ @param short: use short names
+ @param conform: if True factor is 1000, else factor is 1024
+ @return: (str) formated number
+
+ >>> format_num_bytes(100)
+ '100 b'
+
+ >>> format_num_bytes(1000)
+ '1.00 kb'
+
+ >>> format_num_bytes(1024, conform=False)
+ '1.00 kb'
+
+ >>> format_num_bytes(1000, short=False)
+ '1.00 Kilobyte'
+
+ """
+
+ if short:
+ names = ('b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb')
+ else:
+ names = ('Byte',
+ 'Kilobyte',
+ 'Megabyte',
+ 'Gigabyte',
+ 'Terabyte',
+ 'Petabyte',
+ 'Exabyte',
+ 'Zettabyte',
+ 'Yottabyte'
+ )
+ if conform:
+ factor = 1000
+ else:
+ factor = 1024
+
+ num = float(num)
+ name = names[0]
+ if num >= factor:
+ for tmp_name in names[1: ]:
+ num /= factor
+ name = tmp_name
+ if num < factor:
+ break
+ else:
+ return '%i %s' % (num, name)
+
+
+
+ return '%01.2f %s' % (num, name)
+#***************************************************************
+#
+#***************************************************************
+TimeDurationNames = {
+ 'seconds': 's',
+ 'minutes': 'm',
+ 'hours': 'h',
+ 'days': 'd',
+ 'years': 'y'
+ }
+def format_time_delta(t1, t2, names=None):
+ """Pretty prints a time delta
+ @arg t1: duration starting time
+ @arg t2: duration ending time
+ @arg names: (optional) dict mapping names to names as they should be
+ to be printed (see TimeDurationNames)
+
+ >>> import time
+ >>> t0 = time.time()
+
+ >>> format_time_delta(t0, t0 +1)
+ '1s'
+
+ >>> format_time_delta(t0, t0)
+ '0s'
+
+ >>> format_time_delta(t0, t0 +1.4)
+ '1.4s'
+
+ >>> format_time_delta(t0, t0 +60)
+ '1m'
+
+ >>> format_time_delta(t0, t0 +12345)
+ '3.4h'
+
+ >>> format_time_delta(t0, t0 +1234567890)
+ '39.1y'
+
+ >>> format_time_delta(t0, t0 +1234567890, names={'years': 'leapers', 'seconds': 's', 'minutes': 'm', 'hours': 'h', 'days': 'd'})
+ '39.1leapers'
+
+ """
+ mapping = (
+ ('years', 1),
+ ('days', 365),
+ ('hours', 24),
+ ('minutes', 60),
+ ('seconds', 60),
+ )
+ if names is None:
+ names = TimeDurationNames
+
+ delta = t2 - t1
+ if delta < 0:
+ t = n = 0
+ name = 'seconds'
+ else:
+ start = (60 * 60 * 24 * 365)
+ for name, fac in mapping:
+ start = start / fac
+ t = delta / start
+ t = round(t, 1)
+ n = int(t)
+ if n:
+ break
+
+ name = names[name]
+ if t > n:
+ return '%s%s' % (t, name)
+ else:
+ return '%s%s' % (n, name)
+
+
+#*****************************************************************
+#
+#****************************************************************
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+
+
+
+
Added: trunk/fclient/fclient_lib/pyex/numbers.pyc
===================================================================
(Binary files differ)
Property changes on: trunk/fclient/fclient_lib/pyex/numbers.pyc
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/fclient/fclient_lib/pyex/omapping.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/omapping.py (rev 0)
+++ trunk/fclient/fclient_lib/pyex/omapping.py 2007-10-27 16:57:28 UTC (rev 13)
@@ -0,0 +1,345 @@
+"""Ordered mappings"""
+
+#******************************************************************************
+#
+#******************************************************************************
+class StableMapping(dict):
+ """Stable mapping wich keeps insertion order of items (neither complete nor whatever)
+
+ >>> StableMapping()
+ {}
+ >>> o = StableMapping( ((1, 1), (2, 2)) )
+ >>> for i in o: i
+ (1, 1)
+ (2, 2)
+
+ >>> enum = o.__iter__()
+ >>> enum.next()
+ (1, 1)
+
+ >>> o.popitem(-1)
+ (2, 2)
+
+ >>> for i in o: i
+ (1, 1)
+
+ >>> o['a'] = 'aa'
+ >>> for i in o: i
+ (1, 1)
+ ('a', 'aa')
+
+ >>> o['a'] = 'bb'
+ >>> for i in o: i
+ (1, 1)
+ ('a', 'bb')
+
+ >>> o.getitem(-1)
+ ('a', 'bb')
+
+ >>> del o['a']
+ >>> for i in o: i
+ (1, 1)
+
+
+ >>> o.item_order
+ [1]
+
+ >>> o.remove(1)
+ >>> for i in o: i
+
+
+ """
+
+ def __init__(self, items=None):
+ self.item_order = []
+ if items is None:
+ dict.__init__(self)
+ else:
+ dict.__init__(self, items)
+ self.item_order = [i[0] for i in items]
+
+ def __delitem__(self, name):
+ dict.__delitem__(self, name)
+ self.item_order.remove(name)
+
+ def __iter__(self):
+ for i in self.item_order:
+ yield (i, self[i])
+
+ def __setitem__(self, name, value):
+ isnew = name not in self
+ dict.__setitem__(self, name, value)
+ if isnew:
+ self.item_order.append(name)
+
+ def pop(self, name):
+ item = dict.pop(self, name)
+ self.item_order.remove(name)
+ return item
+
+ def popitem(self, i=-1):
+ item = self.item_order.pop(i)
+ return (item, dict.pop(self, item))
+
+ def getitem(self, i):
+ item = self.item_order[i]
+ return (item, self[item])
+
+ def index(self, item):
+ return self.item_order.index(item)
+
+ def remove(self, item):
+ del self[item]
+
+#******************************************************************************
+#
+#******************************************************************************
+class MappedPriorityQueue(object):
+ """Sorted queue supporting priorities and dictionary lookup of items
+
+ >>> PriorityHigh = 2
+ >>> PriorityMiddle = 1
+ >>> PriorityLow = 0
+
+ >>> m = MappedPriorityQueue()
+ >>> for i in range(4): m.push(str(i), 'MyItem-%s' % i, PriorityLow)
+ >>> len(m)
+ 4
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['0', '1', '2', '3']
+
+ >>> m.set_priority('3', PriorityHigh)
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['3', '0', '1', '2']
+
+ >>> m.set_priority('2', PriorityMiddle)
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['3', '2', '0', '1']
+
+ # iterate over all items with PriorityMiddle
+ >>> for item in m.iter_items(PriorityMiddle, PriorityMiddle): item
+ (1, '2', 'MyItem-2')
+
+ # pop all items with PriorityMiddle and below
+ >>> for item in m.pop_items(PriorityMiddle): item
+ (1, '2', 'MyItem-2')
+ (0, '0', 'MyItem-0')
+ (0, '1', 'MyItem-1')
+
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['3']
+
+
+ # items can be popped by name aswell
+ >>> m.push('MyName', 'MyItem', PriorityLow)
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['3', 'MyName']
+
+ >>> m.pop('MyName')
+ (0, 'MyName', 'MyItem')
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['3']
+
+
+ # dictionary lookup
+ >>> m['3']
+ 'MyItem-3'
+
+
+ # check if queue is empty
+ >>> bool(m)
+ True
+
+ >>> m.clear()
+ >>> bool(m)
+ False
+
+ """
+
+
+ class QueueItem(object):
+
+ __slots__ = ('item', 'priority', 'name')
+
+ def __init__(self, priority, name, item):
+ self.item = item
+ self.priority = priority
+ self.name = name
+
+ def __cmp__(self,other):
+ if isinstance(other, self.__class__):
+ return cmp(self.priority, other.priority)
+ return cmp(self.priority, other)
+
+
+ def __init__(self):
+ """
+ @attr queue: (list) [QueueItem, ...]
+ @attr mapping: (dict) name --> QueueItem
+ @note: both mappings should be considered read only
+ """
+ self.queue = []
+ self.mapping = {}
+
+
+ def __contains__(self, name):
+ """Returns True if an item 'name' exists in the queue, False otherwise"""
+ return name in self.mapping
+
+ def __getitem__(self, name):
+ """Returns an item from the queue given its name"""
+ return self.mapping[name].item
+
+ def __setitem__(self, name, item):
+ """Sets the item associated to name"""
+ self.mapping[name].item = item
+
+
+ def __len__(self):
+ """Retuns the number of items in the queue"""
+ return len(self.queue)
+
+ def __nonzero__(self):
+ """Returns False if the queue is empty, True otherwise"""
+ return bool(self.queue)
+
+
+ def clear(self):
+ """Removes all items from the queue"""
+ self.queue = []
+ self.mapping = {}
+
+
+ def get(self, name, default=None):
+ """Returns an item from the queue given its name or default
+ @return: tuple(priority, item)
+ """
+ queue_item = self.mapping.get(name, None)
+ if queue_item is None:
+ return default
+ return (queue_item.priority, queue_item.item)
+
+
+ def index(self, name):
+ """Returns the first index of an item in the queue"""
+ queue_item = self.mapping[name]
+ return self.queue.index(queue_item)
+
+
+ def iter_items(self, priority_start=None, priority_stop=None):
+ """Iterates over all items in the queue
+ @param priority_start: minimu priority to start iterating at
+ or None to start at the first item
+ @param priority_stop: priority to stop iterating at. If None
+ all items below priority_start are returned
+ @return: tuple(priority, name, item) for the next item in turn
+ """
+ for queue_item in self.queue:
+ if priority_stop is not None:
+ if queue_item < priority_stop:
+ raise StopIteration
+
+ if priority_start is not None:
+ if queue_item > priority_start:
+ continue
+ yield (queue_item.priority, queue_item.name, queue_item.item)
+
+
+
+ def peak(self, index):
+ """Returns an item from a specified index
+ @return: tuple(priority, name, item)
+ """
+ queue_item = self.queue[index]
+ return (queue_item.priority, queue_item.name, queue_item.item)
+
+
+ def pop(self, name=None):
+ """Pops an item from the queue
+ @param name: name of the item to pop. If None, the item with the highest priority is popped
+ @return: tuple(priority, name, item)
+ """
+ if name:
+ queue_item = self.mapping[name]
+ else:
+ queue_item = self.queue[0]
+ self.queue.remove(queue_item)
+ self.mapping.pop(name)
+ return (queue_item.priority, queue_item.name, queue_item.item)
+
+
+ def pop_items(self, priority_start=None, priority_stop=None):
+ """Sequentially pops a number of items from the queue
+ @param priority_start: minimu priority to start popping at
+ @param priority_stop: priority to stop popping at. If None
+ all items below priority_start are popped
+ @return: tuple(name, item) for the next item in turn
+ """
+ i = 0
+ while len(self.queue) > i:
+ queue_item = self.queue[i]
+ if priority_stop is not None:
+ if queue_item < priority_stop:
+ raise StopIteration
+
+ if priority_start is None:
+ del self.queue[i]
+ del self.mapping[queue_item.name]
+ yield (queue_item.name, queue_item.item)
+
+ elif queue_item <= priority_start:
+ del self.queue[i]
+ del self.mapping[queue_item.name]
+ yield (queue_item.priority, queue_item.name, queue_item.item)
+
+ else:
+ i += 1
+
+
+ def push(self, name, item, priority, sort=True):
+ """Pushes an item into the queue
+ @param item: any desired item
+ @param priority: (int) priority of the item
+ @param sort: if True the queue is sorted right away, if False, use sort()
+ to trigger sorting manually
+ """
+ if name in self.mapping:
+ raise KeyError('Item already exists: %r' % name)
+
+ queue_item = self.QueueItem(priority, name, item)
+ self.queue.append(queue_item)
+ self.mapping[name] = queue_item
+ if sort:
+ self.sort()
+
+
+ def get_priority(self, name):
+ """Returns the priority of item name"""
+ return self.mapping[name].priority
+
+
+ def set_priority(self, name, priority, sort=True):
+ """Adjusts the priority of an item
+ @param name: name of the item to adjust priority for
+ @param priority: (int) new priority of the item
+ @param sort: if True the queue is sorted right away, if False, use sort()
+ to trigger sorting manually
+ """
+ self.mapping[name].priority = priority
+ if sort:
+ self.sort()
+
+
+ def sort(self):
+ """Sorts the queue"""
+ self.queue.sort(reverse=True)
+
+
+#***************************************************************
+#
+#***************************************************************
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|