Author: aum
Date: 2006-05-07 11:15:53 +0000 (Sun, 07 May 2006)
New Revision: 8638

Added:
   trunk/apps/pyFreenet/code.leo
   trunk/apps/pyFreenet/fcp.py
Log:
Added first vers of python fcp lib,
compatible with FCP v2.0
comes with inbuilt xml-rpc server as well

by aum



Added: trunk/apps/pyFreenet/code.leo
===================================================================
--- trunk/apps/pyFreenet/code.leo       2006-05-07 10:05:14 UTC (rev 8637)
+++ trunk/apps/pyFreenet/code.leo       2006-05-07 11:15:53 UTC (rev 8638)
@@ -0,0 +1,757 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<leo_file>
+<leo_header file_format="2" tnodes="0" max_tnode_index="2" clone_windows="0"/>
+<globals body_outline_ratio="0.25">
+       <global_window_position top="67" left="66" height="668" width="1200"/>
+       <global_log_window_position top="0" left="0" height="0" width="0"/>
+</globals>
+<preferences/>
+<find_panel_settings/>
+<vnodes>
+<v t="aum.20060506215300"><vh>PyFCP</vh></v>
+<v t="aum.20060506215707" a="EV" 
tnodeList="aum.20060506215707,aum.20060506215707.1,aum.20060506220237,aum.20060506215707.2,aum.20060506215707.3,aum.20060506220237.1,aum.20060506220237.2,aum.20060506224238,aum.20060506224238.1,aum.20060506231352,aum.20060507003931,aum.20060506231352.1,aum.20060506232639,aum.20060506232639.1,aum.20060506223545,aum.20060506231352.2,aum.20060506220856,aum.20060506222005,aum.20060507124316,aum.20060507155016,aum.20060507162314,aum.20060507162543,aum.20060507162314.1,aum.20060507162314.2,aum.20060507203051,aum.20060507162314.3,aum.20060507203123,aum.20060507162543.1,aum.20060507154638,aum.20060507163143,aum.20060507195029,aum.20060507195029.1,aum.20060506224545"><vh>@nosent
 fcp.py</vh>
+<v t="aum.20060506215707.1"><vh>imports</vh></v>
+<v t="aum.20060506220237"><vh>exceptions</vh></v>
+<v t="aum.20060506215707.2"><vh>globals</vh></v>
+<v t="aum.20060506215707.3" a="E"><vh>class FCPNodeConnection</vh>
+<v t="aum.20060506220237.1"><vh>__init__</vh></v>
+<v t="aum.20060506220237.2"><vh>__del__</vh></v>
+<v t="aum.20060506224238" a="E"><vh>High Level Methods</vh>
+<v t="aum.20060506224238.1"><vh>hello</vh></v>
+<v t="aum.20060506231352"><vh>get</vh></v>
+<v t="aum.20060507003931"><vh>put</vh></v>
+<v t="aum.20060506231352.1"><vh>genkey</vh></v>
+</v>
+<v t="aum.20060506232639"><vh>Receiver Thread</vh>
+<v t="aum.20060506232639.1"><vh>_rxThread</vh></v>
+</v>
+<v t="aum.20060506223545"><vh>Low Level Methods</vh>
+<v t="aum.20060506231352.2"><vh>_getUniqueId</vh></v>
+<v t="aum.20060506220856"><vh>_sendMessage</vh></v>
+<v t="aum.20060506222005"><vh>_receiveMessage</vh></v>
+<v t="aum.20060507124316"><vh>_log</vh></v>
+</v>
+</v>
+<v t="aum.20060507155016" a="E"><vh>class FreenetXMLRPCRequest</vh>
+<v t="aum.20060507162314"><vh>__init__</vh></v>
+<v t="aum.20060507162543"><vh>_getNode</vh></v>
+<v t="aum.20060507162314.1"><vh>_hello</vh></v>
+<v t="aum.20060507162314.2"><vh>get</vh></v>
+<v t="aum.20060507203051"><vh>getfile</vh></v>
+<v t="aum.20060507162314.3"><vh>put</vh></v>
+<v t="aum.20060507203123"><vh>putfile</vh></v>
+<v t="aum.20060507162543.1"><vh>genkey</vh></v>
+</v>
+<v t="aum.20060507154638"><vh>runServer</vh></v>
+<v t="aum.20060507163143"><vh>testServer</vh></v>
+<v t="aum.20060507195029"><vh>usage</vh></v>
+<v t="aum.20060507195029.1"><vh>main</vh></v>
+<v t="aum.20060506224545"><vh>mainline</vh></v>
+</v>
+</vnodes>
+<tnodes>
+<t tx="aum.20060506215300">@nocolor
+
+A hacked up implementation of a library for Freenet FCP v2
+
+</t>
+<t tx="aum.20060506215707">@first #!/usr/bin/env python
+"""
+An implementation of a freenet client library for
+FCP v2
+
+This can be imported as a module by client apps wanting
+a simple Freenet FCP v2 interface, or it can be executed
+to run an XML-RPC server talking to FCP (run with -h for more info)
+
+Written by aum, May 2006, released under the GNU Lesser General
+Public License.
+
+No warranty, yada yada
+
+Python hackers please feel free to hack constructively, but I
+strongly request that you preserve the simplicity and not impose
+any red tape on client writers.
+
+"""
+
+ at others
+
+</t>
+<t tx="aum.20060506215707.1">import sys, os, socket, time, thread, threading
+
+from SimpleXMLRPCServer import SimpleXMLRPCServer
+
+</t>
+<t tx="aum.20060506215707.2">defaultFCPHost = "127.0.0.1"
+defaultFCPPort = 9481
+
+xmlrpcHost = "127.0.0.1"
+xmlrpcPort = 19481
+
+# list of keywords sent from node to client, which have
+# int values
+intKeys = [
+    'DataLength', 'Code',
+    ]
+
+expectedVersion="2.0"
+
+SILENT = 0
+FATAL = 1
+CRITICAL = 2
+ERROR = 3
+INFO = 4
+DETAIL = 5
+DEBUG = 6
+
+</t>
+<t tx="aum.20060506215707.3">class FCPNodeConnection:
+    """
+    Low-level transport for connections to
+    FCP port
+    """
+    @others
+
+</t>
+<t tx="aum.20060506220237">class ConnectionRefused(Exception):
+    """
+    cannot connect to given host/port
+    """
+
+class FCPException(Exception):
+
+    def __init__(self, info=None):
+        print "Creating fcp exception"
+        if not info:
+            info = {}
+        self.info = info
+        print "fcp exception created"
+
+    def __str__(self):
+        
+        parts = []
+        for k in ['header', 'ShortCodeDescription', 'CodeDescription']:
+            if self.info.has_key(k):
+                parts.append(str(self.info[k]))
+        return ";".join(parts) or "??"
+
+class FCPGetFailed(FCPException):
+    pass
+
+class FCPPutFailed(FCPException):
+    pass
+
+class FCPProtocolError(FCPException):
+    pass
+
+</t>
+<t tx="aum.20060506220237.1">def __init__(self, **kw):
+    """
+    Create a connection object
+    
+    Arguments:
+        - clientName - name of client to use with reqs, defaults to random
+        - host - hostname, defaults to defaultFCPHost
+        - port - port number, defaults to defaultFCPPort
+        - logfile - a pathname or writable file object, to which log messages
+          should be written, defaults to stdout
+        - verbosity - how detailed the log messages should be, defaults to 0
+          (silence)
+    """
+    self.name = kw.get('clientName', self._getUniqueId())
+    self.host = kw.get('host', defaultFCPHost)
+    self.port = kw.get('port', defaultFCPPort)
+    
+    logfile = kw.get('logfile', sys.stderr)
+    if not hasattr(logfile, 'write'):
+        # might be a pathname
+        if not isinstance(logfile, str):
+            raise Exception("Bad logfile, must be pathname or file object")
+        logfile = file(logfile, "a")
+    self.logfile = logfile
+
+    self.verbosity = kw.get('verbosity', 0)
+
+    # try to connect
+    self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    self.socket.connect((self.host, self.port))
+
+    # the incoming response queues
+    self.pendingResponses = {} # keyed by request ID
+
+    # lock for socket operations
+    self.socketLock = threading.Lock()
+        
+    # launch receiver thread
+    #thread.start_new_thread(self.rxThread, ())
+
+</t>
+<t tx="aum.20060506220237.2">def __del__(self):
+    """
+    object is getting cleaned up, so disconnect
+    """
+    if self.socket:
+        self.socket.close()
+        del self.socket
+
+    if self.logfile not in [sys.stdout, sys.stderr]:
+        self.logfile.close()
+
+</t>
+<t tx="aum.20060506220856">def _sendMessage(self, msgType, 
sendEndMessage=True, **kw):
+    """
+    low level message send
+    
+    Arguments:
+        - msgType - one of the FCP message headers, such as 'ClientHello'
+        - args - zero or more (keyword, value) tuples
+    """
+    if kw.has_key("Data"):
+        data = kw.pop("Data")
+    else:
+        data = None
+
+    log = self._log
+    
+    items = [msgType + "\n"]
+    log(DETAIL, "CLIENT: %s" % msgType)
+
+    #print "CLIENT: %s" % msgType
+    for k, v in kw.items():
+        #print "CLIENT: %s=%s" % (k,v)
+        line = k + "=" + str(v)
+        items.append(line + "\n")
+        log(DETAIL, "CLIENT: %s" % line)
+
+    if data != None:
+        items.append("DataLength=%d\n" % len(data))
+        log(DETAIL, "CLIENT: DataLength=%d" % len(data))
+        items.append("Data\n")
+        log(DETAIL, "CLIENT: ...data...")
+        items.append(data)
+
+    #print "sendEndMessage=%s" % sendEndMessage
+
+    if sendEndMessage:
+        items.append("EndMessage\n")
+        log(DETAIL, "CLIENT: EndMessage")
+    raw = "".join(items)
+
+    self.socket.send(raw)
+
+</t>
+<t tx="aum.20060506222005">def _receiveMessage(self):
+    """
+    Receives and returns a message as a dict
+    
+    The header keyword is included as key 'header'
+    """
+    log = self._log
+
+    # shorthand, for reading n bytes
+    def read(n):
+        if n &gt; 1:
+            log(DEBUG, "read: want %d bytes" % n)
+        chunks = []
+        remaining = n
+        while remaining &gt; 0:
+            chunk = self.socket.recv(remaining)
+            chunklen = len(chunk)
+            chunks.append(chunk)
+            remaining -= chunklen
+            if remaining &gt; 0:
+                if n &gt; 1:
+                    log(DEBUG,
+                        "wanted %s, got %s still need %s bytes" % (n, 
chunklen, remaining)
+                        )
+                pass
+        buf = "".join(chunks)
+        return buf
+
+    # read a line
+    def readln():
+        buf = []
+        while True:
+            c = read(1)
+            buf.append(c)
+            if c == '\n':
+                break
+        ln = "".join(buf)
+        log(DETAIL, "NODE: " + ln[:-1])
+        return ln
+
+    items = {}
+
+    # read the header line
+    while True:
+        line = readln().strip()
+        if line:
+            items['header'] = line
+            break
+
+    # read the body
+    while True:
+        line = readln().strip()
+        if line in ['End', 'EndMessage']:
+            break
+
+        if line == 'Data':
+            # read the following data
+            buf = read(items['DataLength'])
+            items['Data'] = buf
+            log(DETAIL, "NODE: ...&lt;%d bytes of data&gt;" % len(buf))
+            break
+        else:
+            # it's a normal 'key=val' pair
+            try:
+                k, v = line.split("=")
+            except:
+                #print "unexpected: %s"%  line
+                raise
+
+            # attempt int conversion
+            try:
+                v = int(v)
+            except:
+                pass
+
+            items[k] = v
+
+    # all done
+    return items
+
+</t>
+<t tx="aum.20060506223545"># low level noce comms methods
+
+ at others
+</t>
+<t tx="aum.20060506224238"># high level client methods
+
+ at others
+
+
+
+</t>
+<t tx="aum.20060506224238.1">def hello(self):
+    
+    self._sendMessage("ClientHello", 
+                     Name=self.name,
+                     ExpectedVersion=expectedVersion)
+    
+    resp = self._receiveMessage()
+    return resp
+
+</t>
+<t tx="aum.20060506224545">if __name__ == '__main__':
+    
+    main()
+
+</t>
+<t tx="aum.20060506231352">def get(self, uri, **kw):
+    """
+    Does a direct get, returning the value as a string
+
+    Keywords:
+        - DSOnly - whether to only check local datastore
+        - file - if given, this is a pathname to which to store the retrieved 
key
+    """
+    opts = {}
+    file = kw.pop("file", None)
+    if file:
+        opts['ReturnType'] = "disk"
+        opts['File'] = file
+    else:
+        opts['ReturnType'] = "direct"
+    
+    opts['Identifier'] = self._getUniqueId()
+    
+    if kw.get("IgnoreDS", False):
+        opts["IgnoreDS"] = "true"
+    else:
+        opts["IgnoreDS"] = "false"
+    
+    if kw.get("DSOnly", False):
+        opts["DSOnly"] = "true"
+    else:
+        opts["DSOnly"] = "false"
+    
+    opts['URI'] = uri
+    opts['Verbosity'] = "0"
+
+    opts['MaxRetries'] = kw.get("MaxRetries", 3)
+    opts['MaxSize'] = 10000000000
+    opts['PriorityClass'] = 1
+    opts['Global'] = "false"
+
+    self._sendMessage("ClientGet", **opts)
+   
+#ClientGet
+#IgnoreDS=false // true = ignore the datastore (in old FCP this was 
RemoveLocalKey)
+#DSOnly=false // true = only check the datastore, don't route (~= htl 0)
+#URI=KSK at gpl.txt // key to fetch
+#Identifier=Request Number One
+#Verbosity=0 // no status, just tell us when it's done
+#ReturnType=direct // return all at once over the FCP connection
+#MaxSize=100 // maximum size of returned data (all numbers in DECIMAL)
+#MaxTempSize=1000 // maximum size of intermediary data
+#MaxRetries=100 // automatic retry supported as an option; -1 means retry 
forever
+#PriorityClass=1 // priority class 1 = interactive
+#Persistence=reboot // continue until node is restarted; report progress while 
client is
+#       connected, including if it reconnects after losing connection
+#ClientToken=hello // returned in PersistentGet, a hint to the client, so the 
client
+#       doesn't need to maintain its own state
+#Global=false // see Persistence section below
+#EndMessage
+
+    # get a response
+    resp = self._receiveMessage()
+    hdr = resp['header']
+    if hdr == 'DataFound':
+        if file:
+            # already stored to disk, done
+            resp['file'] = file
+            return
+        else:
+            resp = self._receiveMessage()
+            if resp['header'] == 'AllData':
+                return resp['Data']
+            else:
+                raise FCPProtocolError(resp)
+    elif hdr == 'GetFailed':
+        raise FCPGetFailed(resp)
+    elif hdr == 'ProtocolError':
+        raise FCPProtocolError(resp)
+    else:
+        raise FCPException(resp)
+
+</t>
+<t tx="aum.20060506231352.1">def genkey(self, id=None):
+    """
+    Generates and returns an SSK keypair
+    """
+    if not id:
+        id = self._getUniqueId()
+    
+    self._sendMessage("GenerateSSK",
+                     Identifier=id)
+    
+    while True:
+        resp = self._receiveMessage()
+        print resp
+        if resp['header'] == 'SSKKeypair' and str(resp['Identifier']) == id:
+            break
+
+    return resp['RequestURI'], resp['InsertURI']
+
+</t>
+<t tx="aum.20060506231352.2">def _getUniqueId(self):
+    return "id" + str(int(time.time() * 1000000))
+
+</t>
+<t tx="aum.20060506232639"># methods for receiver thread
+
+ at others
+
+</t>
+<t tx="aum.20060506232639.1">def _rxThread(self):
+    """
+    Receives all incoming messages
+    """
+    while self.running:
+        self.socketLock.acquire()
+        self.socket.settimeout(0.1)
+        try:
+            msg = self._receiveMessage()
+        except socket.timeout:
+            self.socketLock.release()
+            continue
+        
+</t>
+<t tx="aum.20060507003931">def put(self, uri, **kw):
+    """
+    Inserts a key
+    
+    Arguments:
+        - uri - uri under which to insert the key
+    
+    Keywords:
+        - file - path of file from which to read the key data
+        - data - the raw data of the key as string
+        - mimetype - the mime type, default text/plain
+    """
+#ClientPut
+#URI=CHK@ // could as easily be an insertable SSK or KSK URI
+#Metadata.ContentType=text/html // MIME type; for text, if charset is not 
specified, node #should auto-detect it and force the auto-detected version
+#Identifier=Insert-1 // identifier, as always
+#Verbosity=0 // just report when complete
+#MaxRetries=999999 // lots of retries; -1 = retry forever
+#PriorityClass=1 // fproxy priority level
+#GetCHKOnly=false // true = don't insert the data, just return the key it 
would generate
+#Global=false // see Persistence section below
+#DontCompress=true // hint to node: don't try to compress the data, it's 
already compressed
+#ClientToken=Hello!!! // sent back to client on the PersistentPut if this is a 
persistent #request
+
+# the following fields decide where the data is to come from:
+
+#UploadFrom=direct // attached directly to this message
+#DataLength=100 // 100 bytes decimal
+#Data
+#&lt;data&gt;
+# or
+#UploadFrom=disk // upload a file from disk
+#Filename=/home/toad/something.html
+#End
+# or
+#UploadFrom=redirect // create a redirect to another key
+#TargetURI=KSK at gpl.txt // some other freenet URI
+#End
+
+    opts = {}
+    opts['URI'] = uri
+    opts['Metadata.ContentType'] = kw.get("mimetype", "text/plain")
+    id = self._getUniqueId()
+    opts['Identifier'] = id
+    opts['Verbosity'] = 0
+    opts['MaxRetries'] = 3
+    opts['PriorityClass'] = 1
+    opts['GetCHKOnly'] = "false"
+    opts['DontCompress'] = "false"
+    
+    if kw.has_key("file"):
+        opts['UploadFrom'] = "disk"
+        opts['Filename'] = kw['file']
+        sendEnd = True
+    elif kw.has_key("data"):
+        opts["UploadFrom"] = "direct"
+        opts["Data"] = kw['data']
+        sendEnd = False
+    elif kw.has_key("redirect"):
+        opts["UploadFrom"] = "redirect"
+        opts["TargetURI"] = kw['redirect']
+        sendEnd = True
+    else:
+        raise Exception("Must specify file, data or redirect keywords")
+
+    #print "sendEnd=%s" % sendEnd
+    
+    self._sendMessage("ClientPut", sendEnd, **opts)
+
+    # expect URIGenerated
+    resp1 = self._receiveMessage()
+    hdr = resp1['header']
+    if hdr != 'URIGenerated':
+        raise FCPException(resp1)
+
+    newUri = resp1['URI']
+    
+    # expect outcome
+    resp2 = self._receiveMessage()
+    hdr = resp2['header']
+    if hdr == 'PutSuccessful':
+        return resp2['URI']
+    elif hdr == 'PutFailed':
+        raise FCPPutFailed(resp2)
+    elif hdr == 'ProtocolError':
+        raise FCPProtocolError(resp2)
+    else:
+        raise FCPException(resp2)
+
+</t>
+<t tx="aum.20060507124316">def _log(self, level, msg):
+    """
+    Logs a message. If level &gt; verbosity, don't output it
+    """
+    if level &gt; self.verbosity:
+        return
+
+    if not msg.endswith("\n"): msg += "\n"
+    self.logfile.write(msg)
+    self.logfile.flush()
+
+</t>
+<t tx="aum.20060507154638">def runServer(**kw):
+    """
+    Runs a basic XML-RPC server for FCP access
+    """
+    host = kw.get('host', xmlrpcHost)
+    port = kw.get('port', xmlrpcPort)
+    fcpHost = kw.get('fcpHost', defaultFCPHost)
+    fcpPort = kw.get('fcpPort', defaultFCPPort)
+    verbosity = kw.get('verbosity', SILENT)
+
+    server = SimpleXMLRPCServer((xmlrpcHost, xmlrpcPort))
+    inst = FreenetXMLRPCRequest(host=fcpHost, port=fcpPort, 
verbosity=verbosity)
+    server.register_instance(inst)
+    server.serve_forever()
+
+</t>
+<t tx="aum.20060507155016">class FreenetXMLRPCRequest:
+    """
+    Simple class which exposes basic primitives
+    for freenet xmlrpc server
+    """
+    @others
+
+</t>
+<t tx="aum.20060507162314">def __init__(self, **kw):
+
+    self.kw = kw
+
+</t>
+<t tx="aum.20060507162314.1">def _hello(self):
+    
+    self.node.hello()
+
+</t>
+<t tx="aum.20060507162314.2">def get(self, uri):
+    """
+    Gets and returns a uri directly
+    """
+    node = self._getNode()
+
+    return node.get(uri)
+
+</t>
+<t tx="aum.20060507162314.3">def put(self, uri, data):
+    """
+    Inserts to node
+    """
+    node = self._getNode()
+
+    return node.put(uri, data=data)
+
+</t>
+<t tx="aum.20060507162543">def _getNode(self):
+    
+    node = FCPNodeConnection(**self.kw)
+    node.hello()
+    return node
+
+</t>
+<t tx="aum.20060507162543.1">def genkey(self):
+    
+    node = self._getNode()
+
+    return self.node.genkey()
+
+</t>
+<t tx="aum.20060507163143">def testServer():
+    
+    runServer(host="", fcpHost="10.0.0.1", verbosity=DETAIL)
+
+</t>
+<t tx="aum.20060507195029">def usage(msg="", ret=1):
+
+    if msg:
+        sys.stderr.write(msg+"\n")
+
+    print "\n".join([
+        "Freenet XML-RPC Server",
+        "Usage: %s [options]" % sys.argv[0],
+        "Options:",
+        "  -h, --help",
+        "       show this usage message",
+        "  -v, --verbosity=",
+        "       set verbosity level, values are:",
+        "         0 (SILENT) show only 1 line for incoming hits",
+        "         1 (FATAL) show only fatal messages",
+        "         2 (CRITICAL) show only major failures",
+        "         3 (ERROR) show significant errors",
+        "         4 (INFO) show basic request details",
+        "         5 (DETAIL) show FCP dialogue",
+        "         6 (DEBUG) show ridiculous amounts of debug info",
+        "  --host=",
+        "       listen hostname for xml-rpc requests, default %s" % xmlrpcHost,
+        "  --port=",
+        "       listen port number for xml-rpc requests, default %s" % 
xmlrpcPort,
+        "  --fcphost=",
+        "       set hostname of freenet FCP interface, default %s" % 
defaultFCPHost,
+        "  --fcpport=",
+        "       set port number of freenet FCP interface, default %s" % 
defaultFCPPort,
+        ])
+
+    sys.exit(ret)
+
+</t>
+<t tx="aum.20060507195029.1">def main():
+    """
+    When this script is executed, it runs the XML-RPC server
+    """
+    import getopt
+
+    opts = {'verbosity': 0,
+            'host':xmlrpcHost,
+            'port':xmlrpcPort,
+            'fcpHost':defaultFCPHost,
+            'fcpPort':defaultFCPPort,
+            }
+
+    try:
+        cmdopts, args = getopt.getopt(sys.argv[1:],
+                                   "?hv:",
+                                   ["help", "verbosity=", "host=", "port=",
+                                    "fcphost=", "fcpport="])
+    except getopt.GetoptError:
+        # print help information and exit:
+        usage()
+        sys.exit(2)
+    output = None
+    verbose = False
+    #print cmdopts
+    for o, a in cmdopts:
+        if o == "-v":
+            verbose = True
+        elif o in ("-h", "--help"):
+            usage(ret=0)
+        elif o == "--host":
+            opts['host'] = a
+        elif o == "--port":
+            try:
+                opts['port'] = int(a)
+            except:
+                usage("Invalid port number '%s'" % a)
+        elif o == "--fcphost":
+            opts['fcpHost'] = a
+        elif o == "--fcpport":
+            opts['fcpPort'] = a
+        elif o in ['-v', '--verbosity']:
+            try:
+                opts['verbosity'] = int(a)
+                #print "verbosity=%s" % opts['verbosity']
+            except:
+                usage("Invalid verbosity '%s'" % a)
+
+    if opts['verbosity'] &gt;= INFO:
+        print "Launching Freenet XML-RPC server"
+        print "Listening on %s:%s" % (opts['host'], opts['port'])
+        print "Talking to Freenet FCP at %s:%s" % (opts['fcpHost'], 
opts['fcpPort'])
+
+    try:
+        runServer(**opts)
+    except KeyboardInterrupt:
+        print "Freenet XML-RPC server terminated by user"
+
+
+
+</t>
+<t tx="aum.20060507203051">def getfile(self, uri, path):
+    """
+    Gets and returns a uri directly
+    """
+    node = self._getNode()
+
+    return node.get(uri, file=path)
+
+</t>
+<t tx="aum.20060507203123">def putfile(self, uri, path):
+    """
+    Inserts to node from a file
+    """
+    node = self._getNode()
+
+    return node.put(uri, file=path)
+
+</t>
+</tnodes>
+</leo_file>

Added: trunk/apps/pyFreenet/fcp.py
===================================================================
--- trunk/apps/pyFreenet/fcp.py 2006-05-07 10:05:14 UTC (rev 8637)
+++ trunk/apps/pyFreenet/fcp.py 2006-05-07 11:15:53 UTC (rev 8638)
@@ -0,0 +1,659 @@
+#!/usr/bin/env python
+"""
+An implementation of a freenet client library for
+FCP v2
+
+This can be imported as a module by client apps wanting
+a simple Freenet FCP v2 interface, or it can be executed
+to run an XML-RPC server talking to FCP (run with -h for more info)
+
+Written by aum, May 2006, released under the GNU Lesser General
+Public License.
+
+No warranty, yada yada
+
+Python hackers please feel free to hack constructively, but I
+strongly request that you preserve the simplicity and not impose
+any red tape on client writers.
+
+"""
+
+import sys, os, socket, time, thread, threading
+
+from SimpleXMLRPCServer import SimpleXMLRPCServer
+
+class ConnectionRefused(Exception):
+    """
+    cannot connect to given host/port
+    """
+
+class FCPException(Exception):
+
+    def __init__(self, info=None):
+        print "Creating fcp exception"
+        if not info:
+            info = {}
+        self.info = info
+        print "fcp exception created"
+
+    def __str__(self):
+        
+        parts = []
+        for k in ['header', 'ShortCodeDescription', 'CodeDescription']:
+            if self.info.has_key(k):
+                parts.append(str(self.info[k]))
+        return ";".join(parts) or "??"
+
+class FCPGetFailed(FCPException):
+    pass
+
+class FCPPutFailed(FCPException):
+    pass
+
+class FCPProtocolError(FCPException):
+    pass
+
+defaultFCPHost = "127.0.0.1"
+defaultFCPPort = 9481
+
+xmlrpcHost = "127.0.0.1"
+xmlrpcPort = 19481
+
+# list of keywords sent from node to client, which have
+# int values
+intKeys = [
+    'DataLength', 'Code',
+    ]
+
+expectedVersion="2.0"
+
+SILENT = 0
+FATAL = 1
+CRITICAL = 2
+ERROR = 3
+INFO = 4
+DETAIL = 5
+DEBUG = 6
+
+class FCPNodeConnection:
+    """
+    Low-level transport for connections to
+    FCP port
+    """
+    def __init__(self, **kw):
+        """
+        Create a connection object
+        
+        Arguments:
+            - clientName - name of client to use with reqs, defaults to random
+            - host - hostname, defaults to defaultFCPHost
+            - port - port number, defaults to defaultFCPPort
+            - logfile - a pathname or writable file object, to which log 
messages
+              should be written, defaults to stdout
+            - verbosity - how detailed the log messages should be, defaults to 0
+              (silence)
+        """
+        self.name = kw.get('clientName', self._getUniqueId())
+        self.host = kw.get('host', defaultFCPHost)
+        self.port = kw.get('port', defaultFCPPort)
+        
+        logfile = kw.get('logfile', sys.stderr)
+        if not hasattr(logfile, 'write'):
+            # might be a pathname
+            if not isinstance(logfile, str):
+                raise Exception("Bad logfile, must be pathname or file object")
+            logfile = file(logfile, "a")
+        self.logfile = logfile
+    
+        self.verbosity = kw.get('verbosity', 0)
+    
+        # try to connect
+        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.socket.connect((self.host, self.port))
+    
+        # the incoming response queues
+        self.pendingResponses = {} # keyed by request ID
+    
+        # lock for socket operations
+        self.socketLock = threading.Lock()
+            
+        # launch receiver thread
+        #thread.start_new_thread(self.rxThread, ())
+    
+    def __del__(self):
+        """
+        object is getting cleaned up, so disconnect
+        """
+        if self.socket:
+            self.socket.close()
+            del self.socket
+    
+        if self.logfile not in [sys.stdout, sys.stderr]:
+            self.logfile.close()
+    
+    # high level client methods
+    
+    def hello(self):
+        
+        self._sendMessage("ClientHello", 
+                         Name=self.name,
+                         ExpectedVersion=expectedVersion)
+        
+        resp = self._receiveMessage()
+        return resp
+    
+    def get(self, uri, **kw):
+        """
+        Does a direct get, returning the value as a string
+    
+        Keywords:
+            - DSOnly - whether to only check local datastore
+            - file - if given, this is a pathname to which to store the 
retrieved key
+        """
+        opts = {}
+        file = kw.pop("file", None)
+        if file:
+            opts['ReturnType'] = "disk"
+            opts['File'] = file
+        else:
+            opts['ReturnType'] = "direct"
+        
+        opts['Identifier'] = self._getUniqueId()
+        
+        if kw.get("IgnoreDS", False):
+            opts["IgnoreDS"] = "true"
+        else:
+            opts["IgnoreDS"] = "false"
+        
+        if kw.get("DSOnly", False):
+            opts["DSOnly"] = "true"
+        else:
+            opts["DSOnly"] = "false"
+        
+        opts['URI'] = uri
+        opts['Verbosity'] = "0"
+    
+        opts['MaxRetries'] = kw.get("MaxRetries", 3)
+        opts['MaxSize'] = 10000000000
+        opts['PriorityClass'] = 1
+        opts['Global'] = "false"
+    
+        self._sendMessage("ClientGet", **opts)
+       
+    #ClientGet
+    #IgnoreDS=false // true = ignore the datastore (in old FCP this was 
RemoveLocalKey)
+    #DSOnly=false // true = only check the datastore, don't route (~= htl 0)
+    #URI=KSK at gpl.txt // key to fetch
+    #Identifier=Request Number One
+    #Verbosity=0 // no status, just tell us when it's done
+    #ReturnType=direct // return all at once over the FCP connection
+    #MaxSize=100 // maximum size of returned data (all numbers in DECIMAL)
+    #MaxTempSize=1000 // maximum size of intermediary data
+    #MaxRetries=100 // automatic retry supported as an option; -1 means retry 
forever
+    #PriorityClass=1 // priority class 1 = interactive
+    #Persistence=reboot // continue until node is restarted; report progress 
while client is
+    #   connected, including if it reconnects after losing connection
+    #ClientToken=hello // returned in PersistentGet, a hint to the client, so 
the client
+    #   doesn't need to maintain its own state
+    #Global=false // see Persistence section below
+    #EndMessage
+    
+        # get a response
+        resp = self._receiveMessage()
+        hdr = resp['header']
+        if hdr == 'DataFound':
+            if file:
+                # already stored to disk, done
+                resp['file'] = file
+                return
+            else:
+                resp = self._receiveMessage()
+                if resp['header'] == 'AllData':
+                    return resp['Data']
+                else:
+                    raise FCPProtocolError(resp)
+        elif hdr == 'GetFailed':
+            raise FCPGetFailed(resp)
+        elif hdr == 'ProtocolError':
+            raise FCPProtocolError(resp)
+        else:
+            raise FCPException(resp)
+    
+    def put(self, uri, **kw):
+        """
+        Inserts a key
+        
+        Arguments:
+            - uri - uri under which to insert the key
+        
+        Keywords:
+            - file - path of file from which to read the key data
+            - data - the raw data of the key as string
+            - mimetype - the mime type, default text/plain
+        """
+    #ClientPut
+    #URI=CHK@ // could as easily be an insertable SSK or KSK URI
+    #Metadata.ContentType=text/html // MIME type; for text, if charset is not 
specified, node #should auto-detect it and force the auto-detected version
+    #Identifier=Insert-1 // identifier, as always
+    #Verbosity=0 // just report when complete
+    #MaxRetries=999999 // lots of retries; -1 = retry forever
+    #PriorityClass=1 // fproxy priority level
+    #GetCHKOnly=false // true = don't insert the data, just return the key it 
would generate
+    #Global=false // see Persistence section below
+    #DontCompress=true // hint to node: don't try to compress the data, it's 
already compressed
+    #ClientToken=Hello!!! // sent back to client on the PersistentPut if this 
is a persistent #request
+    
+    # the following fields decide where the data is to come from:
+    
+    #UploadFrom=direct // attached directly to this message
+    #DataLength=100 // 100 bytes decimal
+    #Data
+    #<data>
+    # or
+    #UploadFrom=disk // upload a file from disk
+    #Filename=/home/toad/something.html
+    #End
+    # or
+    #UploadFrom=redirect // create a redirect to another key
+    #TargetURI=KSK at gpl.txt // some other freenet URI
+    #End
+    
+        opts = {}
+        opts['URI'] = uri
+        opts['Metadata.ContentType'] = kw.get("mimetype", "text/plain")
+        id = self._getUniqueId()
+        opts['Identifier'] = id
+        opts['Verbosity'] = 0
+        opts['MaxRetries'] = 3
+        opts['PriorityClass'] = 1
+        opts['GetCHKOnly'] = "false"
+        opts['DontCompress'] = "false"
+        
+        if kw.has_key("file"):
+            opts['UploadFrom'] = "disk"
+            opts['Filename'] = kw['file']
+            sendEnd = True
+        elif kw.has_key("data"):
+            opts["UploadFrom"] = "direct"
+            opts["Data"] = kw['data']
+            sendEnd = False
+        elif kw.has_key("redirect"):
+            opts["UploadFrom"] = "redirect"
+            opts["TargetURI"] = kw['redirect']
+            sendEnd = True
+        else:
+            raise Exception("Must specify file, data or redirect keywords")
+    
+        #print "sendEnd=%s" % sendEnd
+        
+        self._sendMessage("ClientPut", sendEnd, **opts)
+    
+        # expect URIGenerated
+        resp1 = self._receiveMessage()
+        hdr = resp1['header']
+        if hdr != 'URIGenerated':
+            raise FCPException(resp1)
+    
+        newUri = resp1['URI']
+        
+        # expect outcome
+        resp2 = self._receiveMessage()
+        hdr = resp2['header']
+        if hdr == 'PutSuccessful':
+            return resp2['URI']
+        elif hdr == 'PutFailed':
+            raise FCPPutFailed(resp2)
+        elif hdr == 'ProtocolError':
+            raise FCPProtocolError(resp2)
+        else:
+            raise FCPException(resp2)
+    
+    def genkey(self, id=None):
+        """
+        Generates and returns an SSK keypair
+        """
+        if not id:
+            id = self._getUniqueId()
+        
+        self._sendMessage("GenerateSSK",
+                         Identifier=id)
+        
+        while True:
+            resp = self._receiveMessage()
+            print resp
+            if resp['header'] == 'SSKKeypair' and str(resp['Identifier']) == 
id:
+                break
+    
+        return resp['RequestURI'], resp['InsertURI']
+    
+    
+    
+    
+    # methods for receiver thread
+    
+    def _rxThread(self):
+        """
+        Receives all incoming messages
+        """
+        while self.running:
+            self.socketLock.acquire()
+            self.socket.settimeout(0.1)
+            try:
+                msg = self._receiveMessage()
+            except socket.timeout:
+                self.socketLock.release()
+                continue
+            
+    
+    # low level noce comms methods
+    
+    def _getUniqueId(self):
+        return "id" + str(int(time.time() * 1000000))
+    
+    def _sendMessage(self, msgType, sendEndMessage=True, **kw):
+        """
+        low level message send
+        
+        Arguments:
+            - msgType - one of the FCP message headers, such as 'ClientHello'
+            - args - zero or more (keyword, value) tuples
+        """
+        if kw.has_key("Data"):
+            data = kw.pop("Data")
+        else:
+            data = None
+    
+        log = self._log
+        
+        items = [msgType + "\n"]
+        log(DETAIL, "CLIENT: %s" % msgType)
+    
+        #print "CLIENT: %s" % msgType
+        for k, v in kw.items():
+            #print "CLIENT: %s=%s" % (k,v)
+            line = k + "=" + str(v)
+            items.append(line + "\n")
+            log(DETAIL, "CLIENT: %s" % line)
+    
+        if data != None:
+            items.append("DataLength=%d\n" % len(data))
+            log(DETAIL, "CLIENT: DataLength=%d" % len(data))
+            items.append("Data\n")
+            log(DETAIL, "CLIENT: ...data...")
+            items.append(data)
+    
+        #print "sendEndMessage=%s" % sendEndMessage
+    
+        if sendEndMessage:
+            items.append("EndMessage\n")
+            log(DETAIL, "CLIENT: EndMessage")
+        raw = "".join(items)
+    
+        self.socket.send(raw)
+    
+    def _receiveMessage(self):
+        """
+        Receives and returns a message as a dict
+        
+        The header keyword is included as key 'header'
+        """
+        log = self._log
+    
+        # shorthand, for reading n bytes
+        def read(n):
+            if n > 1:
+                log(DEBUG, "read: want %d bytes" % n)
+            chunks = []
+            remaining = n
+            while remaining > 0:
+                chunk = self.socket.recv(remaining)
+                chunklen = len(chunk)
+                chunks.append(chunk)
+                remaining -= chunklen
+                if remaining > 0:
+                    if n > 1:
+                        log(DEBUG,
+                            "wanted %s, got %s still need %s bytes" % (n, 
chunklen, remaining)
+                            )
+                    pass
+            buf = "".join(chunks)
+            return buf
+    
+        # read a line
+        def readln():
+            buf = []
+            while True:
+                c = read(1)
+                buf.append(c)
+                if c == '\n':
+                    break
+            ln = "".join(buf)
+            log(DETAIL, "NODE: " + ln[:-1])
+            return ln
+    
+        items = {}
+    
+        # read the header line
+        while True:
+            line = readln().strip()
+            if line:
+                items['header'] = line
+                break
+    
+        # read the body
+        while True:
+            line = readln().strip()
+            if line in ['End', 'EndMessage']:
+                break
+    
+            if line == 'Data':
+                # read the following data
+                buf = read(items['DataLength'])
+                items['Data'] = buf
+                log(DETAIL, "NODE: ...<%d bytes of data>" % len(buf))
+                break
+            else:
+                # it's a normal 'key=val' pair
+                try:
+                    k, v = line.split("=")
+                except:
+                    #print "unexpected: %s"%  line
+                    raise
+    
+                # attempt int conversion
+                try:
+                    v = int(v)
+                except:
+                    pass
+    
+                items[k] = v
+    
+        # all done
+        return items
+    
+    def _log(self, level, msg):
+        """
+        Logs a message. If level > verbosity, don't output it
+        """
+        if level > self.verbosity:
+            return
+    
+        if not msg.endswith("\n"): msg += "\n"
+        self.logfile.write(msg)
+        self.logfile.flush()
+    
+
+class FreenetXMLRPCRequest:
+    """
+    Simple class which exposes basic primitives
+    for freenet xmlrpc server
+    """
+    def __init__(self, **kw):
+    
+        self.kw = kw
+    
+    def _getNode(self):
+        
+        node = FCPNodeConnection(**self.kw)
+        node.hello()
+        return node
+    
+    def _hello(self):
+        
+        self.node.hello()
+    
+    def get(self, uri):
+        """
+        Gets and returns a uri directly
+        """
+        node = self._getNode()
+    
+        return node.get(uri)
+    
+    def getfile(self, uri, path):
+        """
+        Gets and returns a uri directly
+        """
+        node = self._getNode()
+    
+        return node.get(uri, file=path)
+    
+    def put(self, uri, data):
+        """
+        Inserts to node
+        """
+        node = self._getNode()
+    
+        return node.put(uri, data=data)
+    
+    def putfile(self, uri, path):
+        """
+        Inserts to node from a file
+        """
+        node = self._getNode()
+    
+        return node.put(uri, file=path)
+    
+    def genkey(self):
+        
+        node = self._getNode()
+    
+        return self.node.genkey()
+    
+
+def runServer(**kw):
+    """
+    Runs a basic XML-RPC server for FCP access
+    """
+    host = kw.get('host', xmlrpcHost)
+    port = kw.get('port', xmlrpcPort)
+    fcpHost = kw.get('fcpHost', defaultFCPHost)
+    fcpPort = kw.get('fcpPort', defaultFCPPort)
+    verbosity = kw.get('verbosity', SILENT)
+
+    server = SimpleXMLRPCServer((xmlrpcHost, xmlrpcPort))
+    inst = FreenetXMLRPCRequest(host=fcpHost, port=fcpPort, 
verbosity=verbosity)
+    server.register_instance(inst)
+    server.serve_forever()
+
+def testServer():
+    
+    runServer(host="", fcpHost="10.0.0.1", verbosity=DETAIL)
+
+def usage(msg="", ret=1):
+
+    if msg:
+        sys.stderr.write(msg+"\n")
+
+    print "\n".join([
+        "Freenet XML-RPC Server",
+        "Usage: %s [options]" % sys.argv[0],
+        "Options:",
+        "  -h, --help",
+        "       show this usage message",
+        "  -v, --verbosity=",
+        "       set verbosity level, values are:",
+        "         0 (SILENT) show only 1 line for incoming hits",
+        "         1 (FATAL) show only fatal messages",
+        "         2 (CRITICAL) show only major failures",
+        "         3 (ERROR) show significant errors",
+        "         4 (INFO) show basic request details",
+        "         5 (DETAIL) show FCP dialogue",
+        "         6 (DEBUG) show ridiculous amounts of debug info",
+        "  --host=",
+        "       listen hostname for xml-rpc requests, default %s" % xmlrpcHost,
+        "  --port=",
+        "       listen port number for xml-rpc requests, default %s" % 
xmlrpcPort,
+        "  --fcphost=",
+        "       set hostname of freenet FCP interface, default %s" % 
defaultFCPHost,
+        "  --fcpport=",
+        "       set port number of freenet FCP interface, default %s" % 
defaultFCPPort,
+        ])
+
+    sys.exit(ret)
+
+def main():
+    """
+    When this script is executed, it runs the XML-RPC server
+    """
+    import getopt
+
+    opts = {'verbosity': 0,
+            'host':xmlrpcHost,
+            'port':xmlrpcPort,
+            'fcpHost':defaultFCPHost,
+            'fcpPort':defaultFCPPort,
+            }
+
+    try:
+        cmdopts, args = getopt.getopt(sys.argv[1:],
+                                   "?hv:",
+                                   ["help", "verbosity=", "host=", "port=",
+                                    "fcphost=", "fcpport="])
+    except getopt.GetoptError:
+        # print help information and exit:
+        usage()
+        sys.exit(2)
+    output = None
+    verbose = False
+    #print cmdopts
+    for o, a in cmdopts:
+        if o == "-v":
+            verbose = True
+        elif o in ("-h", "--help"):
+            usage(ret=0)
+        elif o == "--host":
+            opts['host'] = a
+        elif o == "--port":
+            try:
+                opts['port'] = int(a)
+            except:
+                usage("Invalid port number '%s'" % a)
+        elif o == "--fcphost":
+            opts['fcpHost'] = a
+        elif o == "--fcpport":
+            opts['fcpPort'] = a
+        elif o in ['-v', '--verbosity']:
+            try:
+                opts['verbosity'] = int(a)
+                #print "verbosity=%s" % opts['verbosity']
+            except:
+                usage("Invalid verbosity '%s'" % a)
+
+    if opts['verbosity'] >= INFO:
+        print "Launching Freenet XML-RPC server"
+        print "Listening on %s:%s" % (opts['host'], opts['port'])
+        print "Talking to Freenet FCP at %s:%s" % (opts['fcpHost'], 
opts['fcpPort'])
+
+    try:
+        runServer(**opts)
+    except KeyboardInterrupt:
+        print "Freenet XML-RPC server terminated by user"
+
+
+
+if __name__ == '__main__':
+    
+    main()
+
+


Property changes on: trunk/apps/pyFreenet/fcp.py
___________________________________________________________________
Name: svn:executable
   + *


Reply via email to