Index: MythTV/MythDB.py =================================================================== --- MythTV/MythDB.py (revision 23116) +++ MythTV/MythDB.py (working copy) @@ -5,10 +5,12 @@ """ import os import sys -import xml.dom.minidom as minidom +import xml.etree.cElementTree as etree import code import getopt +import re from datetime import datetime +from urllib import urlopen from MythLog import * @@ -28,7 +30,7 @@ """ A connection to the mythtv database. """ - def __init__(self, args=None): + def __init__(self, args=None, **kwargs): # Setup connection variables dbconn = { 'host' : None, @@ -47,7 +49,7 @@ found_config = False for config_file in config_files: try: - config = minidom.parse(config_file) + config = etree.parse(config_file).getroot() except: continue @@ -55,22 +57,20 @@ dbconn['name'] = None dbconn['user'] = None dbconn['pass'] = None - for token in config.getElementsByTagName('Configuration')[0].getElementsByTagName('UPnP')[0].getElementsByTagName('MythFrontend')[0].getElementsByTagName('DefaultBackend')[0].childNodes: - if token.nodeType == token.TEXT_NODE: - continue + for child in config.find('UPnP').find('MythFrontend').find('DefaultBackend').getchildren(): try: - if token.tagName == "DBHostName": - dbconn['host'] = token.childNodes[0].data - elif token.tagName == "DBName": - dbconn['name'] = token.childNodes[0].data - elif token.tagName == "DBUserName": - dbconn['user'] = token.childNodes[0].data - elif token.tagName == "DBPassword": - dbconn['pass'] = token.childNodes[0].data - elif token.tagName == "USN": - dbconn['USN'] = token.childNodes[0].data - elif token.tagName == "SecurityPin": - dbconn['PIN'] = token.childNodes[0].data + if child.tag == "DBHostName": + dbconn['host'] = child.text + elif child.tag == "DBName": + dbconn['name'] = child.text + elif child.tag == "DBUserName": + dbconn['user'] = child.text + elif child.tag == "DBPassword": + dbconn['pass'] = child.text + elif child.tag == "USN": + dbconn['USN'] = child.text + elif child.tag == "SecurityPin": + dbconn['PIN'] = child.text except: pass @@ -94,9 +94,19 @@ except: pass - if not dbconn['host'] and not found_config: - raise MythError('Unable to find MythTV configuration file') + # Overrides from keyword parameters + for o, a in kwargs.items(): + dbconn[o] = a + if not self._check_dbconn(dbconn): + # try to pull the information over upnp + if 'SecurityPin' not in dbconn: + dbconn['SecurityPin'] = 0 + dbconn = self._listenUPNP(dhconn['SecurityPin'], 5.0) + if not self._check_dbconn(dbconn): + # no database credentials available + raise MythError('Unable to find MythTV configuration file') + try: self.db = MySQLdb.connect(user=dbconn['user'], host=dbconn['host'], passwd=dbconn['pass'], db=dbconn['name'], use_unicode=True, @@ -114,6 +124,95 @@ sver, SCHEMA_VERSION) raise MythError('Mismatched schema version') + def _check_dbconn(self, dbconn): + reqs = ['host','name','user','pass'] + for req in reqs: + if req not in dbconn: + return False + if dbconn[req] is None: + return False + return True + + def _listenUPNP(self, pin, timeout): + # open socket + upnpport = 1900 + upnptup = ('239.255.255.250', upnpport) + sreq = '\r\n'.join(['M-SEARCH * HTTP/1.1', + 'HOST: %s:%s' % upnptup, + 'MAN: "ssdp:discover"', + 'MX: 5', + 'ST: ssdp:all','']) + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, + socket.IPPROTO_UDP) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + sock.bind(('', upnpport)) + except: + # socket already in use, exiting upnp attmept + return + sock.setblocking(0) + + # spam the request a couple times + sock.sendto(sreq, upnptup) + sock.sendto(sreq, upnptup) + sock.sendto(sreq, upnptup) + + reLOC = re.compile('http://(?P[0-9\.]+):(?P[0-9]+)/.*') + # listen for + atime = time()+timeout + while time() < atime: + sleep(0.1) + try: + sdata, saddr = sock.recvfrom(2048) + except socket.error: + continue # on fault from nonblocking recv + + lines = sdata.split('\n') + sdict = {'request':lines[0].strip()} + for line in lines[1:]: + fields = line.split(':',1) + if len(fields) == 2: + sdict[fields[0].strip().lower()] = fields[1].strip() + + if 'st' not in sdict: + continue + if sdict['st'] not in \ + ('urn:schemas-mythtv-org:device:MasterMediaServer:1', + 'urn:schemas-mythtv-org:device:SlaveMediaServer:1', + 'urn:schemas-upnp-org:device:MediaServer:1'): + continue + ip, port = reLOC.match(sdict['location']).group(1,2) + try: + dbconn = self._processPage(ip, port, pin) + break + except: + continue + + else: + sock.close() + return {} + + sock.close() + return dbconn + + def _processPage(self, ip, port, pin): + dbconn = {'SecurityPin':pin} + # try opening settings page with SecurityPin + up = urlopen('http://%s:%s/Myth/GetConnectionInfo?Pin=%s' \ + % (ip, port, pin)) + config = etree.fromstring(up.read()) + up.close() + + # load data from webpage + conv = {'Host':'host', 'UserName':'user', 'Password':'pass', 'Name':'name'} + for child in config.find('Info').find('Database').getchildren(): + if child.tag in conv: + dbconn[conv[child.tag]] = child.text + if not self._check_dbconn(dbconn): + return {} + return dbconn + def __del__(self): self.db.close()