aboutsummaryrefslogtreecommitdiff
path: root/pydora
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2015-07-07 22:14:31 -0700
committerMike Crute <mcrute@gmail.com>2015-07-07 22:14:31 -0700
commitda2e2df828a01de82b8ac2839df5912ffd232d1b (patch)
tree6473eb1f3e388e74c9f8080320f02040c9cadcdd /pydora
parent4300dc20ef95af928b459e145ef46e3a87b35a31 (diff)
downloadpydora-da2e2df828a01de82b8ac2839df5912ffd232d1b.tar.bz2
pydora-da2e2df828a01de82b8ac2839df5912ffd232d1b.tar.xz
pydora-da2e2df828a01de82b8ac2839df5912ffd232d1b.zip
Add interactive configuration
Diffstat (limited to 'pydora')
-rw-r--r--pydora/configure.py166
-rw-r--r--pydora/utils.py23
2 files changed, 188 insertions, 1 deletions
diff --git a/pydora/configure.py b/pydora/configure.py
new file mode 100644
index 0000000..ed0d862
--- /dev/null
+++ b/pydora/configure.py
@@ -0,0 +1,166 @@
1import os
2import re
3import sys
4import requests
5
6try:
7 from configparser import SafeConfigParser
8except ImportError:
9 from ConfigParser import SafeConfigParser
10
11from pandora.client import APIClient
12from pandora.clientbuilder import PydoraConfigFileBuilder
13
14from .utils import Screen, Colors
15
16
17class umask(object):
18 """Set/Restore Umask Context Manager
19 """
20
21 def __init__(self, umask):
22 self.umask = umask
23 self.old_umask = None
24
25 def __enter__(self):
26 self.old_umask = os.umask(self.umask)
27
28 def __exit__(self, exc_type, exc_val, exc_tb):
29 os.umask(self.old_umask)
30
31
32class PandoraKeysConfigParser(object):
33 """Parser for Pandora Keys Source Page
34
35 This is an extremely naive restructured text parser designed only to parse
36 the pandora API docs keys source file.
37 """
38
39 KEYS_URL = ("http://6xq.net/git/lars/pandora-apidoc.git/"
40 "plain/json/partners.rst")
41
42 FIELD_RE = re.compile(
43 ":(?P<key>[^:]+): (?:`{2})?(?P<value>[^`\n]+)(?:`{2})?$")
44
45 def _fixup_key(self, key):
46 key = key.lower()
47
48 if key.startswith("dec") and "password" in key:
49 return "decryption_key"
50 elif key.startswith("encrypt"):
51 return "encryption_key"
52 elif key == "deviceid":
53 return "device"
54 else:
55 return key
56
57 def _format_api_host(self, host):
58 return "{}/services/json/".format(host)
59
60 def _clean_device_name(self, name):
61 return re.sub("[^a-z]+", "_", name, flags=re.I)
62
63 def _fetch_config(self):
64 return requests.get(self.KEYS_URL).text.split("\n")
65
66 def _match_key(self, line):
67 key_match = self.FIELD_RE.match(line)
68 if key_match:
69 match = key_match.groupdict()
70 match["key"] = self._fixup_key(match["key"])
71 return match
72 else:
73 return None
74
75 def _is_host_terminator(self, line):
76 return line.startswith("--")
77
78 def _is_device_terminator(self, line):
79 return line.startswith("^^")
80
81 def load(self):
82 buffer = []
83 current_partner = {}
84 api_host = None
85 partners = {}
86
87 for line in self._fetch_config():
88 key_match = self._match_key(line)
89 if key_match:
90 current_partner[key_match["key"]] = key_match["value"]
91 elif self._is_host_terminator(line):
92 api_host = buffer.pop()
93 elif self._is_device_terminator(line):
94 key = self._clean_device_name(buffer.pop())
95 current_partner = partners[key] = {
96 "api_host": self._format_api_host(api_host) }
97
98 buffer.append(line.strip().lower())
99
100 return partners
101
102
103class Configurator(object):
104 """Interactive Configuration Builder
105
106 Allows a user to configure pydora interactively. Ultimately writes the
107 pydora config file.
108 """
109
110 def __init__(self):
111 self.builder = PydoraConfigFileBuilder()
112
113 self.cfg = SafeConfigParser()
114 self.cfg.add_section("user")
115 self.cfg.add_section("api")
116
117 def fail(self, message):
118 print(Screen.print_error(message))
119 sys.exit(1)
120
121 def finished(self, message):
122 Screen.print_success(message)
123 sys.exit(0)
124
125 def print_message(self, message):
126 print(Colors.cyan(message))
127
128 def get_partner_config(self):
129 try:
130 return PandoraKeysConfigParser().load()["ios"]
131 except:
132 self.fail("Error loading config file. Unable to continue.")
133
134 def get_value(self, section, key, prompt):
135 self.cfg.set(section, key, Screen.get_string(prompt))
136
137 def get_password(self, section, key, prompt):
138 self.cfg.set(section, key, Screen.get_password(prompt))
139
140 def set_static_value(self, section, key, value):
141 self.cfg.set(section, key, value)
142
143 def add_partner_config(self, config):
144 for key, value in config.items():
145 self.cfg.set("api", key, value)
146
147 def write_config(self):
148 with umask(0o077), open(self.builder.path, "w") as fp:
149 self.cfg.write(fp)
150
151 def configure(self):
152 if self.builder.file_exists:
153 self.finished("You already have a pydora config!")
154
155 self.print_message("Welcome to Pydora, let's configure a few things")
156 self.add_partner_config(self.get_partner_config())
157 self.get_value("user", "username", "Pandora Username: ")
158 self.get_password("user", "password", "Pandora Password: ")
159 self.set_static_value("api", "default_audio_quality",
160 APIClient.HIGH_AUDIO_QUALITY)
161
162 self.write_config()
163
164
165def main():
166 Configurator().configure()
diff --git a/pydora/utils.py b/pydora/utils.py
index 76d12f3..92a15a3 100644
--- a/pydora/utils.py
+++ b/pydora/utils.py
@@ -2,6 +2,7 @@ from __future__ import print_function
2 2
3import sys 3import sys
4import termios 4import termios
5import getpass
5 6
6 7
7def input(prompt): 8def input(prompt):
@@ -62,6 +63,26 @@ class Screen(object):
62 print(Colors.green(msg)) 63 print(Colors.green(msg))
63 64
64 @staticmethod 65 @staticmethod
66 def get_string(prompt):
67 while True:
68 value = input(prompt).strip()
69
70 if not value:
71 print(Colors.red("Value Required!"))
72 else:
73 return value
74
75 @staticmethod
76 def get_password(prompt="Password: "):
77 while True:
78 value = getpass.getpass(prompt)
79
80 if not value:
81 print(Colors.red("Value Required!"))
82 else:
83 return value
84
85 @staticmethod
65 def get_integer(prompt): 86 def get_integer(prompt):
66 """Gather user input and convert it to an integer 87 """Gather user input and convert it to an integer
67 88
@@ -72,7 +93,7 @@ class Screen(object):
72 try: 93 try:
73 return int(input(prompt).strip()) 94 return int(input(prompt).strip())
74 except ValueError: 95 except ValueError:
75 print(Colors.red("Invaid Input!")) 96 print(Colors.red("Invalid Input!"))
76 97
77 98
78def clear_screen(): 99def clear_screen():