1# -*- coding: utf-8 -*-
2# gpodder.net API Client
3# Copyright (C) 2009-2013 Thomas Perl and the gPodder Team
4#
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.
9#
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.
14#
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/>.
17
18from functools import wraps
19
20import mygpoclient
21
22from mygpoclient import locator
23from mygpoclient import json
24
25
26class MissingCredentials(Exception):
27    """ Raised when instantiating a SimpleClient without credentials """
28
29
30def needs_credentials(f):
31    """ apply to all methods that initiate requests that require credentials """
32
33    @wraps(f)
34    def _wrapper(self, *args, **kwargs):
35        if not self.username or not self.password:
36            raise MissingCredentials
37
38        return f(self, *args, **kwargs)
39
40    return _wrapper
41
42
43
44class Podcast(object):
45    """Container class for a podcast
46
47    Encapsulates the metadata for a podcast.
48
49    Attributes:
50    url - The URL of the podcast feed
51    title - The title of the podcast
52    description - The description of the podcast
53    """
54    REQUIRED_FIELDS = ('url', 'title', 'description', 'website', 'subscribers',
55                       'subscribers_last_week', 'mygpo_link', 'logo_url')
56
57    def __init__(self, url, title, description, website, subscribers, subscribers_last_week, mygpo_link, logo_url):
58        self.url = url
59        self.title = title
60        self.description = description
61        self.website = website
62        self.subscribers = subscribers
63        self.subscribers_last_week = subscribers_last_week
64        self.mygpo_link = mygpo_link
65        self.logo_url = logo_url
66
67    @classmethod
68    def from_dict(cls, d):
69        for key in cls.REQUIRED_FIELDS:
70            if key not in d:
71                raise ValueError('Missing keys for toplist podcast')
72
73        return cls(*(d.get(k) for k in cls.REQUIRED_FIELDS))
74
75    def __eq__(self, other):
76        """Test two Podcast objects for equality
77
78        >>> Podcast('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') == Podcast('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h')
79        True
80        >>> Podcast('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') == Podcast('s', 't', 'u', 'v', 'w', 'x', 'y', 'z')
81        False
82        >>> Podcast('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h') == 'a'
83        False
84        """
85        if not isinstance(other, self.__class__):
86            return False
87
88        return all(getattr(self, k) == getattr(other, k) \
89                for k in self.REQUIRED_FIELDS)
90
91
92class SimpleClient(object):
93    """Client for the gpodder.net Simple API
94
95    This is the API client implementation that provides a
96    pythonic interface to the gpodder.net Simple API.
97    """
98    FORMAT = 'json'
99
100    def __init__(self, username, password, root_url=mygpoclient.ROOT_URL,
101            client_class=json.JsonClient):
102        """Creates a new Simple API client
103
104        Username and password must be specified and are
105        the user's login data for the webservice.
106
107        The parameter root_url is optional and defaults to
108        the main webservice. It can be either a hostname or
109        a full URL (to force https, for instance).
110
111        The parameter client_class is optional and should
112        not need to be changed in normal use cases. If it
113        is changed, it should provide the same interface
114        as the json.JsonClient class in mygpoclient.
115        """
116        self.username = username
117        self.password = password
118        self._locator = locator.Locator(username, root_url)
119        self._client = client_class(username, password)
120
121    @needs_credentials
122    def get_subscriptions(self, device_id):
123        """Get a list of subscriptions for a device
124
125        Returns a list of URLs (one per subscription) for
126        the given device_id that reflects the current list
127        of subscriptions.
128
129        Raises http.NotFound if the device does not exist.
130        """
131        uri = self._locator.subscriptions_uri(device_id, self.FORMAT)
132        return self._client.GET(uri)
133
134    @needs_credentials
135    def put_subscriptions(self, device_id, urls):
136        """Update a device's subscription list
137
138        Sets the server-side subscription list for the device
139        "device_id" to be equivalent to the URLs in the list of
140        strings "urls".
141
142        The device will be created if it does not yet exist.
143
144        Returns True if the update was successful, False otherwise.
145        """
146        uri = self._locator.subscriptions_uri(device_id, self.FORMAT)
147        return (self._client.PUT(uri, urls) == None)
148
149    @needs_credentials
150    def get_suggestions(self, count=10):
151        """Get podcast suggestions for the user
152
153        Returns a list of Podcast objects that are
154        to be suggested to the user.
155
156        The parameter count is optional and if
157        specified has to be a value between 1
158        and 100 (with 10 being the default), and
159        determines how much search results are
160        returned (at maximum).
161        """
162        uri = self._locator.suggestions_uri(count, self.FORMAT)
163        return [Podcast.from_dict(x) for x in self._client.GET(uri)]
164
165    @property
166    def locator(self):
167        """ read-only access to the locator """
168        return self._locator
169