from xmlrpc.client import ServerProxy from os.path import join, isfile from xmlrpc.server import SimpleXMLRPCServer from urllib.parse import urlparse import sys MAX_HISTORY_LENGTH = 6 OK = 1 FAIL = 2 EMPTY = '' def get_port(url): 'Extracts the port from a URL' name = urlparse(url)[1] parts = name.split(':') return int(parts[-1]) class Node: """ A node in a peer-to-peer network. """ def __init__(self, url, dirname, secret): self.url = url self.dirname = dirname self.secret = secret self.known = set() def query(self, query, history=[]): """ Performs a query for a file, possibly asking other known Nodes for help. Returns the file as a string. """ code, data = self._handle(query) if code == OK: return code, data else: history = history + [self.url] if len(history) >= MAX_HISTORY_LENGTH: return FAIL, EMPTY return self._broadcast(query, history) def hello(self, other): """ Used to introduce the Node to other Nodes. """ self.known.add(other) return OK def fetch(self, query, secret): """ Used to make the Node find a file and download it. """ if secret != self.secret: return FAIL code, data = self.query(query) if code == OK: f = open(join(self.dirname, query), 'w') f.write(data) f.close() return OK else: return FAIL def _start(self): """ Used internally to start the XML-RPC server. """ s = SimpleXMLRPCServer(("", get_port(self.url)), logRequests=False) s.register_instance(self) s.serve_forever() def _handle(self, query): """ Used internally to handle queries. """ dir = self.dirname name = join(dir, query) if not isfile(name): return FAIL, EMPTY return OK, open(name).read() def _broadcast(self, query, history): """ Used internally to broadcast a query to all known Nodes. """ for other in self.known.copy(): if other in history: continue try: s = ServerProxy(other) code, data = s.query(query, history) if code == OK: return code, data except: self.known.remove(other) return FAIL, EMPTY def main(): url, directory, secret = sys.argv[1:] n = Node(url, directory, secret) n._start() if __name__ == '__main__': main()