releasing 1.9
[mygpoclient.git] / mygpoclient / locator.py
blob5078b279a0dc9d166917a368b053c8644b36c84c
1 # -*- coding: utf-8 -*-
2 # gpodder.net API Client
3 # Copyright (C) 2009-2013 Thomas Perl and the gPodder Team
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import mygpoclient
20 try:
21 # Python 3
22 from urllib.parse import quote_plus, quote
24 except ImportError:
25 # Python 2
26 from urllib import quote_plus, quote
28 from mygpoclient import util
31 class Locator(object):
32 """URI Locator for API endpoints
34 This helper class abstracts the URIs for the gpodder.net
35 webservice and provides a nice facility for generating API
36 URIs and checking parameters.
37 """
38 SIMPLE_FORMATS = ('opml', 'json', 'txt')
40 SETTINGS_TYPES = ('account', 'device', 'podcast', 'episode')
42 def __init__(self, username, root_url=mygpoclient.ROOT_URL,
43 version=mygpoclient.VERSION):
44 self._username = username
45 if root_url.endswith('/'):
46 root_url = root_url[:-1]
47 if root_url.startswith('http'):
48 self._simple_base = root_url
49 self._base = '%(root_url)s/api/%(version)s' % locals()
50 else:
51 self._simple_base = 'http://%(root_url)s' % locals()
52 self._base = 'http://%(root_url)s/api/%(version)s' % locals()
54 @staticmethod
55 def _convert_since(since):
56 """Convert "since" into a numeric value
58 This is internally used for value-checking.
59 """
60 try:
61 return int(since)
62 except ValueError:
63 raise ValueError('since must be a numeric value (or None)')
65 def subscriptions_uri(self, device_id=None, format='opml'):
66 """Get the Simple API URI for a subscription list
68 >>> locator = Locator('john')
69 >>> locator.subscriptions_uri('n800')
70 'http://gpodder.net/subscriptions/john/n800.opml'
71 >>> locator.subscriptions_uri('ipod', 'txt')
72 'http://gpodder.net/subscriptions/john/ipod.txt'
73 """
74 if format not in self.SIMPLE_FORMATS:
75 raise ValueError('Unsupported file format')
77 username = self._username
78 if device_id is None:
79 path = '%(username)s.%(format)s' % locals()
80 else:
81 path = '%(username)s/%(device_id)s.%(format)s' % locals()
82 return util.join(self._simple_base,
83 'subscriptions', path)
85 def toplist_uri(self, count=50, format='opml'):
86 """Get the Simple API URI for the toplist
88 >>> locator = Locator(None)
89 >>> locator.toplist_uri()
90 'http://gpodder.net/toplist/50.opml'
91 >>> locator.toplist_uri(70)
92 'http://gpodder.net/toplist/70.opml'
93 >>> locator.toplist_uri(10, 'json')
94 'http://gpodder.net/toplist/10.json'
95 """
96 if format not in self.SIMPLE_FORMATS:
97 raise ValueError('Unsupported file format')
99 filename = 'toplist/%(count)d.%(format)s' % locals()
100 return util.join(self._simple_base, filename)
102 def suggestions_uri(self, count=10, format='opml'):
103 """Get the Simple API URI for user suggestions
105 >>> locator = Locator('john')
106 >>> locator.suggestions_uri()
107 'http://gpodder.net/suggestions/10.opml'
108 >>> locator.suggestions_uri(50)
109 'http://gpodder.net/suggestions/50.opml'
110 >>> locator.suggestions_uri(70, 'json')
111 'http://gpodder.net/suggestions/70.json'
113 if format not in self.SIMPLE_FORMATS:
114 raise ValueError('Unsupported file format')
116 filename = 'suggestions/%(count)d.%(format)s' % locals()
117 return util.join(self._simple_base, filename)
119 def search_uri(self, query, format='opml'):
120 """Get the Simple API URI for podcast search
122 >>> locator = Locator(None)
123 >>> locator.search_uri('outlaws')
124 'http://gpodder.net/search.opml?q=outlaws'
125 >>> locator.search_uri(':something?', 'txt')
126 'http://gpodder.net/search.txt?q=%3Asomething%3F'
127 >>> locator.search_uri('software engineering', 'json')
128 'http://gpodder.net/search.json?q=software+engineering'
130 if format not in self.SIMPLE_FORMATS:
131 raise ValueError('Unsupported file format')
133 query = quote_plus(query)
134 filename = 'search.%(format)s?q=%(query)s' % locals()
135 return util.join(self._simple_base, filename)
137 def add_remove_subscriptions_uri(self, device_id):
138 """Get the Advanced API URI for uploading list diffs
140 >>> locator = Locator('bill')
141 >>> locator.add_remove_subscriptions_uri('n810')
142 'http://gpodder.net/api/2/subscriptions/bill/n810.json'
144 filename = '%(device_id)s.json' % locals()
145 return util.join(self._base,
146 'subscriptions', self._username, filename)
148 def subscription_updates_uri(self, device_id, since=None):
149 """Get the Advanced API URI for downloading list diffs
151 The parameter "since" is optional and should be a numeric
152 value (otherwise a ValueError is raised).
154 >>> locator = Locator('jen')
155 >>> locator.subscription_updates_uri('n900')
156 'http://gpodder.net/api/2/subscriptions/jen/n900.json'
157 >>> locator.subscription_updates_uri('n900', 1234)
158 'http://gpodder.net/api/2/subscriptions/jen/n900.json?since=1234'
160 filename = '%(device_id)s.json' % locals()
161 if since is not None:
162 since = self._convert_since(since)
163 filename += '?since=%(since)d' % locals()
165 return util.join(self._base,
166 'subscriptions', self._username, filename)
168 def upload_episode_actions_uri(self):
169 """Get the Advanced API URI for uploading episode actions
171 >>> locator = Locator('thp')
172 >>> locator.upload_episode_actions_uri()
173 'http://gpodder.net/api/2/episodes/thp.json'
175 filename = self._username + '.json'
176 return util.join(self._base, 'episodes', filename)
178 def download_episode_actions_uri(self, since=None,
179 podcast=None, device_id=None):
180 """Get the Advanced API URI for downloading episode actions
182 The parameter "since" is optional and should be a numeric
183 value (otherwise a ValueError is raised).
185 Both "podcast" and "device_id" are optional and exclusive:
187 "podcast" should be a podcast URL
188 "device_id" should be a device ID
190 >>> locator = Locator('steve')
191 >>> locator.download_episode_actions_uri()
192 'http://gpodder.net/api/2/episodes/steve.json'
193 >>> locator.download_episode_actions_uri(since=1337)
194 'http://gpodder.net/api/2/episodes/steve.json?since=1337'
195 >>> locator.download_episode_actions_uri(podcast='http://example.org/episodes.rss')
196 'http://gpodder.net/api/2/episodes/steve.json?podcast=http%3A//example.org/episodes.rss'
197 >>> locator.download_episode_actions_uri(since=2000, podcast='http://example.com/')
198 'http://gpodder.net/api/2/episodes/steve.json?since=2000&podcast=http%3A//example.com/'
199 >>> locator.download_episode_actions_uri(device_id='ipod')
200 'http://gpodder.net/api/2/episodes/steve.json?device=ipod'
201 >>> locator.download_episode_actions_uri(since=54321, device_id='ipod')
202 'http://gpodder.net/api/2/episodes/steve.json?since=54321&device=ipod'
204 if podcast is not None and device_id is not None:
205 raise ValueError('must not specify both "podcast" and "device_id"')
207 filename = self._username + '.json'
209 params = []
210 if since is not None:
211 since = str(self._convert_since(since))
212 params.append(('since', since))
214 if podcast is not None:
215 params.append(('podcast', podcast))
217 if device_id is not None:
218 params.append(('device', device_id))
220 if params:
221 filename += '?' + '&'.join('%s=%s' %
222 (key, quote(value)) for key, value in params)
224 return util.join(self._base, 'episodes', filename)
226 def device_settings_uri(self, device_id):
227 """Get the Advanced API URI for setting per-device settings uploads
229 >>> locator = Locator('mike')
230 >>> locator.device_settings_uri('ipod')
231 'http://gpodder.net/api/2/devices/mike/ipod.json'
233 filename = '%(device_id)s.json' % locals()
234 return util.join(self._base, 'devices', self._username, filename)
236 def device_list_uri(self):
237 """Get the Advanced API URI for retrieving the device list
239 >>> locator = Locator('jeff')
240 >>> locator.device_list_uri()
241 'http://gpodder.net/api/2/devices/jeff.json'
243 filename = self._username + '.json'
244 return util.join(self._base, 'devices', filename)
246 def toptags_uri(self, count=50):
247 """Get the Advanced API URI for retrieving the top Tags
249 >>> locator = Locator(None)
250 >>> locator.toptags_uri()
251 'http://gpodder.net/api/2/tags/50.json'
252 >>> locator.toptags_uri(70)
253 'http://gpodder.net/api/2/tags/70.json'
255 filename = '%(count)d.json' % locals()
256 return util.join(self._base, 'tags', filename)
258 def podcasts_of_a_tag_uri(self, tag, count=50):
259 """Get the Advanced API URI for retrieving the top Podcasts of a Tag
261 >>> locator = Locator(None)
262 >>> locator.podcasts_of_a_tag_uri('linux')
263 'http://gpodder.net/api/2/tag/linux/50.json'
264 >>> locator.podcasts_of_a_tag_uri('linux',70)
265 'http://gpodder.net/api/2/tag/linux/70.json'
267 filename = '%(tag)s/%(count)d.json' % locals()
268 return util.join(self._base, 'tag', filename)
270 def podcast_data_uri(self, podcast_url):
271 """Get the Advanced API URI for retrieving Podcast Data
273 >>> locator = Locator(None)
274 >>> locator.podcast_data_uri('http://podcast.com')
275 'http://gpodder.net/api/2/data/podcast.json?url=http%3A//podcast.com'
277 filename = 'podcast.json?url=%s' % quote(podcast_url)
278 return util.join(self._base, 'data', filename)
280 def episode_data_uri(self, podcast_url, episode_url):
281 """Get the Advanced API URI for retrieving Episode Data
283 >>> locator = Locator(None)
284 >>> locator.episode_data_uri('http://podcast.com','http://podcast.com/foo')
285 'http://gpodder.net/api/2/data/episode.json?podcast=http%3A//podcast.com&url=http%3A//podcast.com/foo'
287 filename = 'episode.json?podcast=%s&url=%s' % (
288 quote(podcast_url), quote(episode_url))
289 return util.join(self._base, 'data', filename)
291 def favorite_episodes_uri(self):
292 """Get the Advanced API URI for listing favorite episodes
294 >>> locator = Locator('mike')
295 >>> locator.favorite_episodes_uri()
296 'http://gpodder.net/api/2/favorites/mike.json'
298 filename = self._username + '.json'
299 return util.join(self._base, 'favorites', filename)
301 def settings_uri(self, type, scope_param1, scope_param2):
302 """Get the Advanced API URI for retrieving or saving Settings
304 Depending on the Type of setting scope_param2 or scope_param1 and scope_param2 are
305 not necessary.
307 >>> locator = Locator('joe')
308 >>> locator.settings_uri('account',None,None)
309 'http://gpodder.net/api/2/settings/joe/account.json'
310 >>> locator.settings_uri('device','foodevice',None)
311 'http://gpodder.net/api/2/settings/joe/device.json?device=foodevice'
312 >>> locator.settings_uri('podcast','http://podcast.com',None)
313 'http://gpodder.net/api/2/settings/joe/podcast.json?podcast=http%3A//podcast.com'
314 >>> locator.settings_uri('episode','http://podcast.com','http://podcast.com/foo')
315 'http://gpodder.net/api/2/settings/joe/episode.json?podcast=http%3A//podcast.com&episode=http%3A//podcast.com/foo'
317 if type not in self.SETTINGS_TYPES:
318 raise ValueError('Unsupported Setting Type')
320 filename = self._username + '/%(type)s.json' % locals()
322 if type == 'device':
323 if scope_param1 is None:
324 raise ValueError('Devicename not specified')
325 filename += '?device=%(scope_param1)s' % locals()
327 if type == 'podcast':
328 if scope_param1 is None:
329 raise ValueError('Podcast URL not specified')
330 filename += '?podcast=%s' % quote(scope_param1)
332 if type == 'episode':
333 if (scope_param1 is None) or (scope_param2 is None):
334 raise ValueError('Podcast or Episode URL not specified')
335 filename += '?podcast=%s&episode=%s' % (
336 quote(scope_param1), quote(scope_param2))
338 return util.join(self._base, 'settings', filename)
340 def root_uri(self):
341 """ Get the server's root URI.
343 >>> locator = Locator(None)
344 >>> locator.root_uri()
345 'http://gpodder.net'
347 return self._simple_base