Richard Jones' Log: Ugly python made better

Fri, 17 Dec 2004

Yeah, it bugged me too... so here's an eminently more readable version of tinyp2p.py. With a couple of bugfixes thrown in too :)

It's still a bit more compact than I guess I'd like, but it's certainly much easier to understand what the code does now.

# simplep2p.py v1.1 (C) 2004, Richard Jones
# with litle touch from Leonardo Santagada
# A slightly more readable version of tinyp2p.py (C) 2004, E.W. Felten
# (also handles binary transmissions now too)
# license: http://creativecommons.org/licenses/by-nc-sa/2.0
# Usage:
#   python simplep2p.py password server hostname portnum [otherurl]
# or
#   python simplep2p.py password client serverurl pattern
import os, SimpleXMLRPCServer, re, hmac, sets, base64
from sys import argv
from xmlrpclib import ServerProxy

def gen_password(url):
   return hmac.new(argv[1], url).hexdigest()
def ls(pat=""):
    ''' List the files in the current working directory that optionall match
    a regular expression "pat". '''
    return [fn for fn in os.listdir(os.getcwd())
        if not pat or re.search(pat, fn)]

if argv[2] == "server":
    my_url = "http://"+argv[3]+":"+argv[4]

    # keep a list of servers we know about
    servers = sets.Set([my_url] + argv[5:])
    def update_servers(new_servers=[]):
        servers.union_update(sets.Set(new_servers))
        return list(servers)
    def discover(other_url):
        if other_url == my_url: return servers
        pw = gen_password(other_url)
        server = ServerProxy(other_url)
        return update_servers(server.list_servers(pw, update_servers()))

    # ask all our known servers about the servers *they* know about
    if servers: [discover(url) for url in list(servers)]

    # serve up the files
    def list_servers(password, arg=[]):
        if password == gen_password(my_url):
            return update_servers(arg)
    def list_files(password, arg):
        if password == gen_password(my_url):
            return ls(arg)
    def get_file(password, arg):
        if password == gen_password(my_url):
            f = file(arg)
            try:
                return base64.encodestring(f.read())
            finally:
                f.close()
    server = SimpleXMLRPCServer.SimpleXMLRPCServer((argv[3], int(argv[4])))
    server.register_function(list_servers)
    server.register_function(list_files)
    server.register_function(get_file)
    server.serve_forever()

# client - contact our server
for url in ServerProxy(argv[3]).list_servers(gen_password(argv[3])):
    # ask for the files we want, that we don't already have
    files = sets.Set(ServerProxy(url).list_files(gen_password(url), argv[4]))
    my_files = sets.Set(ls())
    for fn in files - my_files:
        # and fetch
        c = ServerProxy(url).get_file(gen_password(url), fn)
        f = file(fn, "wb")
        f.write(base64.decodestring(c))
        f.close()

Update: v1.1 incorporates some feedback from Leonardo Santagada.

Note: simplep2p.py will not talk with tinyp2p.py servers.

Comment by Julian on Fri, 07 Jan 2005

Thanks for decrypting the original script!

If you have the time, could you explain how the lambda's used in the original map onto/do the same functionality as this clearer script?

Cheers!!

Julian

Comment by Richard on Fri, 07 Jan 2005

It's unlikely I'll find time to revisit this, sorry.