diff --git a/ssdp.py b/ssdp.py index f0e9b35..6e9d162 100644 --- a/ssdp.py +++ b/ssdp.py @@ -1,10 +1,12 @@ #!/usr/bin/python # # Copyright 2013 Jeff Rebeiro (jrebeiro@gmail.com) All rights reserved -# Simple SSDP Server for PC Autobackup +# Simple SSDP implementation for PC Autobackup __author__ = 'jrebeiro@gmail.com (Jeff Rebeiro)' +import ConfigParser +import os import re import socket import sys @@ -13,6 +15,9 @@ from twisted.internet import task from twisted.internet.protocol import DatagramProtocol +# TODO(jrebeiro): Move the CONFIG_FILE variable to the main runnable module +CONFIG_FILE = os.path.expanduser("~/pc-autobackup.cfg") + pattern = r'^M-SEARCH.*HOST: (.*):(\d+).*urn:schemas-upnp-org:device:(\w+):1.*' MSEARCH = re.compile(pattern, re.DOTALL) @@ -24,43 +29,76 @@ 'ST: urn:schemas-upnp-org:device:MediaServer:1\r\n' 'USN: %s::urn:schemas-upnp-org:device:MediaServer:1\r\n') -UUID = uuid.uuid4() class SSDP(DatagramProtocol): - def __init__(self, ip_address): - self.ip_address = ip_address + def __init__(self): + self.config = LoadOrCreateConfig() self.ssdp_address = '239.255.255.250' self.ssdp_port = 1900 self.server = reactor.listenMulticast(self.ssdp_port, self, listenMultiple=True) self.server.setLoopbackMode(1) - self.server.joinGroup(self.ssdp_address, interface=ip_address) + self.server.joinGroup(self.ssdp_address, + interface=self.config.get('SSDP', + 'default_interface')) def datagramReceived(self, datagram, address): m = MSEARCH.match(datagram) if m: + # TODO(jrebeiro): Make this print in debug mode when the main runnable + # module is created and implements optparse + # TODO(jrebeiro): Verify that MediaServer is the only discovery request + # PCAutoBackup responds to. print 'Received M-SEARCH for %s from %r' % (m.group(3), address) if m.group(3) == 'MediaServer': self.SendSSDPResponse(address) def SendSSDPResponse(self, address): + """Send a response to an SSDP MediaServer discovery request. + + Args: + address: A tuple of destination IP and Port as strings + """ + # TODO(jrebeiro): Make this send a UDP response once the HTTP server is + # ready. print "Response:" - print SSDP_RESPONSE % (address[0], UUID) + print SSDP_RESPONSE % (address[0], self.config.get('SSDP', 'uuid')) def stop(self): - self.server.leaveGroup(self.ssdp_address, interface=self.ip_address) + self.server.leaveGroup(self.ssdp_address, + interface=self.config.get('SSDP', + 'default_interface')) self.server.stopListening() -def SSDPReactor(ip_address): - ssdp_server = SSDP(ip_address) +def LoadOrCreateConfig(): + """Load an existing configuration or create one.""" + config = ConfigParser.RawConfigParser() + # TODO(jrebeiro): Move the CONFIG_FILE variable to the main runnable module + config.read(CONFIG_FILE) + + if not config.has_section('SSDP'): + config.add_section('SSDP') + config.set('SSDP', 'uuid', uuid.uuid4()) + config.set('SSDP', 'default_interface', + socket.gethostbyname(socket.gethostname())) + with open(CONFIG_FILE, 'wb') as config_file: + config.write(config_file) + + return config + + +def SSDPReactor(): + """Callback function for twisted.internet.reactor.""" + ssdp_server = SSDP() reactor.addSystemEventTrigger('before', 'shutdown', ssdp_server.stop) def main(): - ip_address = socket.gethostbyname(socket.gethostname()) - reactor.callWhenRunning(SSDPReactor, ip_address) + # TODO(jrebeiro): Move this code to a main runnable module + config = LoadOrCreateConfig() + reactor.callWhenRunning(SSDPReactor) reactor.run()