diff options
author | Mike Crute <mcrute@gmail.com> | 2015-07-07 22:14:31 -0700 |
---|---|---|
committer | Mike Crute <mcrute@gmail.com> | 2015-07-07 22:14:31 -0700 |
commit | da2e2df828a01de82b8ac2839df5912ffd232d1b (patch) | |
tree | 6473eb1f3e388e74c9f8080320f02040c9cadcdd /pydora | |
parent | 4300dc20ef95af928b459e145ef46e3a87b35a31 (diff) | |
download | pydora-da2e2df828a01de82b8ac2839df5912ffd232d1b.tar.bz2 pydora-da2e2df828a01de82b8ac2839df5912ffd232d1b.tar.xz pydora-da2e2df828a01de82b8ac2839df5912ffd232d1b.zip |
Add interactive configuration
Diffstat (limited to 'pydora')
-rw-r--r-- | pydora/configure.py | 166 | ||||
-rw-r--r-- | pydora/utils.py | 23 |
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 @@ | |||
1 | import os | ||
2 | import re | ||
3 | import sys | ||
4 | import requests | ||
5 | |||
6 | try: | ||
7 | from configparser import SafeConfigParser | ||
8 | except ImportError: | ||
9 | from ConfigParser import SafeConfigParser | ||
10 | |||
11 | from pandora.client import APIClient | ||
12 | from pandora.clientbuilder import PydoraConfigFileBuilder | ||
13 | |||
14 | from .utils import Screen, Colors | ||
15 | |||
16 | |||
17 | class 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 | |||
32 | class 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 | |||
103 | class 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 | |||
165 | def 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 | ||
3 | import sys | 3 | import sys |
4 | import termios | 4 | import termios |
5 | import getpass | ||
5 | 6 | ||
6 | 7 | ||
7 | def input(prompt): | 8 | def 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 | ||
78 | def clear_screen(): | 99 | def clear_screen(): |