#!/usr/bin/env python # -*- coding: utf-8 -*- """ :module: plexkodiconnect.userselect :synopsis: Prompts the user to add network paths and username passwords for e.g. smb paths """ from __future__ import absolute_import, division, unicode_literals from logging import getLogger import re import socket import urllib import xbmc from .. import path_ops, utils LOG = getLogger('PLEX.direct_path_sources') SUPPORTED_PROTOCOLS = ('smb', 'nfs', 'http', 'https', 'ftp', 'sftp') PATH = path_ops.translate_path('special://userdata/') def get_etree(topelement): try: xml = utils.defused_etree.parse( path_ops.path.join(PATH, '%s.xml' % topelement)) except IOError: # Document is blank or missing LOG.info('%s.xml is missing or blank, creating it', topelement) root = utils.etree.Element(topelement) except utils.ParseError: LOG.error('Error parsing %s', topelement) # "Kodi cannot parse {0}. PKC will not function correctly. Please visit # {1} and correct your file!" utils.messageDialog(utils.lang(29999), utils.lang(39716).format( '%s.xml' % topelement, 'http://forum.kodi.tv/')) return else: root = xml.getroot() return root def is_valid_hostname(hostname): if len(hostname) > 255: return False if hostname[-1] == ".": # strip exactly one dot from the right, if present hostname = hostname[:-1] allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE) return all(allowed.match(x) for x in hostname.split(".")) def is_valid_ip(ip): try: socket.inet_aton(ip) # legal except socket.error: return False return True def start(): """ Hit this function to start entering network credentials """ LOG.info('Editing sources.xml and passwords.xml') # Fix for: # DEBUG: Activating window ID: 13000 # INFO: Activate of window '13000' refused because there are active modal dialogs # DEBUG: Activating window ID: 13000 xbmc.executebuiltin("Dialog.Close(all, true)") # "In the following window, enter the server's hostname (or IP) where your # Plex media resides. Mind the case!" utils.messageDialog(utils.lang(29999), utils.lang(30200)) # "Enter server hostname (or IP)" hostname = utils.dialog('input', utils.lang(30201)) if not hostname: return hostname = hostname.decode('utf-8').strip() if not is_valid_hostname(hostname) and not is_valid_ip(hostname): LOG.error('Entered invalid hostname or IP: %s', hostname) # "The hostname or IP '{0}' that you entered is not valid" utils.messageDialog(utils.lang(29999), utils.lang(30204).format(hostname)) return # "In the following window, enter the network protocol you would like to # use. This is likely 'smb'." utils.messageDialog(utils.lang(29999), utils.lang(30202)) # "Enter network protocol" protocol = utils.dialog('input', utils.lang(30203)) if not protocol: return protocol = protocol.decode('utf-8').lower().strip() if protocol not in SUPPORTED_PROTOCOLS: LOG.error('Entered invalid protocol %s', protocol) # "The protocol '{0}' that you entered is not supported." utils.messageDialog(utils.lang(29999), utils.lang(30205).format(protocol)) return path = '%s://%s' % (protocol, hostname) # Trailing slash at the end paths = (path, '%s/' % path) # Add hostname to sources.xml, if not already there LOG.info('Hostname we are adding to sources.xml and passwords.xml: %s', path) try: with utils.XmlKodiSetting('sources.xml', force_create=True, top_element='sources') as xml: files = xml.root.find('files') if files is None: files = utils.etree.SubElement(xml.root, 'files') utils.etree.SubElement(files, 'default', attrib={'pathversion': '1'}) for source in files: entry = source.find('path') if entry is None: LOG.debug('Entry is None') continue LOG.debug('entry found: %s', entry.text) if entry.text in paths: LOG.debug('Already have %s in sources.xml', path) break else: # Need to add an element for our hostname LOG.debug('Adding subelement to sources.xml for %s', hostname) source = utils.etree.SubElement(files, 'source') utils.etree.SubElement(source, 'name').text = 'PKC %s' % hostname utils.etree.SubElement(source, 'path', attrib={'pathversion': '1'}).text = '%s/' % path utils.etree.SubElement(source, 'allowsharing').text = 'false' xml.write_xml = True except utils.ParseError: return # Add or change username and password in passwords.xml try: with utils.XmlKodiSetting('passwords.xml', force_create=True, top_element='passwords') as xml: for entry in xml.root: source = entry.find('from') if source is None: continue if source.text in paths: LOG.debug('Found an existing passwords.xml entry for %s, ' 'replacing it', path) xml.root.remove(entry) entry = utils.etree.SubElement(xml.root, 'path') # "Username" user = utils.dialog('input', utils.lang(1014)) if user is None: xml.write_xml = False return user = user.strip() user = urllib.quote(user) user = user.decode('utf-8') # "Password" # May also be blank!! (=user aborts dialog) password = utils.dialog('input', utils.lang(733), '', type='{alphanum}', option='{hide}') password = urllib.quote(password) password = password.decode('utf-8') utils.etree.SubElement(entry, 'from', attrib={'pathversion': '1'}).text = '%s/' % path login = '%s://%s:%s@%s/' % (protocol, user, password, hostname) utils.etree.SubElement(entry, 'to', attrib={'pathversion': '1'}).text = login xml.write_xml = True except utils.ParseError: return LOG.info('Successfully completed editing sources.xml and padsswords.xml')