Remove unused license preamble
[mygpo.git] / mygpo / api / advanced / updates.py
blob1456b247925b84f90bf02437e6b539c0e890453d
1 from itertools import chain
2 from datetime import datetime
4 from django.http import HttpResponseBadRequest, HttpResponseNotFound
5 from django.contrib.sites.requests import RequestSite
6 from django.views.decorators.csrf import csrf_exempt
7 from django.views.decorators.cache import never_cache
8 from django.utils.decorators import method_decorator
9 from django.views.generic.base import View
11 from mygpo.podcasts.models import Episode
12 from mygpo.api.httpresponse import JsonResponse
13 from mygpo.api.advanced import episode_action_json
14 from mygpo.api.advanced.directory import episode_data, podcast_data
15 from mygpo.utils import parse_bool, get_timestamp
16 from mygpo.subscriptions import get_subscription_history, subscription_diff
17 from mygpo.users.models import Client
18 from mygpo.episodestates.models import EpisodeState
19 from mygpo.users.subscriptions import subscription_changes, podcasts_for_states
20 from mygpo.api.basic_auth import require_valid_user, check_username
21 from mygpo.decorators import cors_origin
23 from collections import namedtuple
24 EpisodeStatus = namedtuple('EpisodeStatus', 'episode status action')
26 import logging
27 logger = logging.getLogger(__name__)
30 class DeviceUpdates(View):
31 """ returns various updates for a device
33 http://wiki.gpodder.org/wiki/Web_Services/API_2/Devices#Get_Updates """
35 @method_decorator(csrf_exempt)
36 @method_decorator(require_valid_user)
37 @method_decorator(check_username)
38 @method_decorator(never_cache)
39 @method_decorator(cors_origin())
40 def get(self, request, username, device_uid):
42 now = datetime.utcnow()
43 now_ = get_timestamp(now)
45 user = request.user
47 try:
48 device = user.client_set.get(uid=device_uid)
49 except Client.DoesNotExist as e:
50 return HttpResponseNotFound(str(e))
52 try:
53 since = self.get_since(request)
54 except ValueError as e:
55 return HttpResponseBadRequest(str(e))
57 include_actions = parse_bool(request.GET.get('include_actions', False))
59 domain = RequestSite(request).domain
61 add, rem, subscriptions = self.get_subscription_changes(user, device,
62 since, now,
63 domain)
64 updates = self.get_episode_changes(user, subscriptions, domain,
65 include_actions, since)
67 return JsonResponse({
68 'add': add,
69 'rem': rem,
70 'updates': updates,
71 'timestamp': get_timestamp(now),
75 def get_subscription_changes(self, user, device, since, now, domain):
76 """ gets new, removed and current subscriptions """
78 history = get_subscription_history(user, device, since, now)
79 add, rem = subscription_diff(history)
81 subscriptions = device.get_subscribed_podcasts()
83 add = [podcast_data(p, domain) for p in add]
84 rem = [p.url for p in rem]
86 return add, rem, subscriptions
89 def get_episode_changes(self, user, subscriptions, domain, include_actions, since):
90 devices = {dev.id.hex: dev.uid for dev in user.client_set.all()}
92 # index subscribed podcasts by their Id for fast access
93 podcasts = {p.get_id(): p for p in subscriptions}
95 episode_updates = self.get_episode_updates(user, subscriptions, since)
97 return [self.get_episode_data(status, podcasts, domain,
98 include_actions, user, devices) for status in episode_updates]
101 def get_episode_updates(self, user, subscribed_podcasts, since,
102 max_per_podcast=5):
103 """ Returns the episode updates since the timestamp """
105 episodes = []
106 for podcast in subscribed_podcasts:
107 eps = Episode.objects.filter(podcast=podcast,
108 released__gt=since)\
109 .order_by('-order', '-released')
110 episodes.extend(eps[:max_per_podcast])
112 states = EpisodeState.dict_for_user(user, episodes)
114 for episode in episodes:
115 yield EpisodeStatus(episode, states.get(episode.id, 'new'), None)
118 def get_episode_data(self, episode_status, podcasts, domain, include_actions, user, devices):
119 """ Get episode data for an episode status object """
121 # TODO: shouldn't the podcast_id be in the episode status?
122 podcast_id = episode_status.episode.podcast
123 podcast = podcasts.get(podcast_id, None)
124 t = episode_data(episode_status.episode, domain, podcast)
125 t['status'] = episode_status.status
127 # include latest action (bug 1419)
128 # TODO
129 if include_actions and episode_status.action:
130 t['action'] = episode_action_json(episode_status.action, user)
132 return t
134 def get_since(self, request):
135 """ parses the "since" parameter """
136 since_ = request.GET.get('since', None)
137 if since_ is None:
138 raise ValueError('parameter since missing')
139 try:
140 return datetime.fromtimestamp(float(since_))
141 except ValueError as e:
142 raise ValueError("'since' is not a valid timestamp: %s" % str(e))