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