Merge pull request #793 from gpodder/remove-advertise
[mygpo.git] / mygpo / api / advanced / updates.py
blobe5e41d2f69b15e56146dd9e91a360d59c2183fd4
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 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
25 EpisodeStatus = namedtuple("EpisodeStatus", "episode status action")
27 import logging
29 logger = logging.getLogger(__name__)
32 class DeviceUpdates(View):
33 """returns various updates for a device
35 https://gpoddernet.readthedocs.io/en/latest/api//Devices#Get_Updates"""
37 @method_decorator(csrf_exempt)
38 @method_decorator(require_valid_user)
39 @method_decorator(check_username)
40 @method_decorator(never_cache)
41 @method_decorator(cors_origin())
42 def get(self, request, username, device_uid):
44 now = datetime.utcnow()
45 now_ = get_timestamp(now)
47 user = request.user
49 try:
50 device = user.client_set.get(uid=device_uid)
51 except Client.DoesNotExist as e:
52 return HttpResponseNotFound(str(e))
54 try:
55 since = self.get_since(request)
56 except ValueError as e:
57 return HttpResponseBadRequest(str(e))
59 include_actions = parse_bool(request.GET.get("include_actions", False))
61 domain = RequestSite(request).domain
63 add, rem, subscriptions = self.get_subscription_changes(
64 user, device, since, now, domain
66 updates = self.get_episode_changes(
67 user, subscriptions, domain, include_actions, since
70 return JsonResponse(
72 "add": add,
73 "rem": rem,
74 "updates": updates,
75 "timestamp": get_timestamp(now),
79 def get_subscription_changes(self, user, device, since, now, domain):
80 """gets new, removed and current subscriptions"""
82 history = get_subscription_history(user, device, since, now)
83 add, rem = subscription_diff(history)
85 subscriptions = device.get_subscribed_podcasts()
87 add = [podcast_data(p, domain) for p in add]
88 rem = [p.url for p in rem]
90 return add, rem, subscriptions
92 def get_episode_changes(self, user, subscriptions, domain, include_actions, since):
93 devices = {dev.id.hex: dev.uid for dev in user.client_set.all()}
95 # index subscribed podcasts by their Id for fast access
96 podcasts = {p.get_id(): p for p in subscriptions}
98 episode_updates = self.get_episode_updates(user, subscriptions, since)
100 return [
101 self.get_episode_data(
102 status, podcasts, domain, include_actions, user, devices
104 for status in episode_updates
107 def get_episode_updates(self, user, subscribed_podcasts, since, max_per_podcast=5):
108 """Returns the episode updates since the timestamp"""
110 episodes = []
111 for podcast in subscribed_podcasts:
112 eps = Episode.objects.filter(podcast=podcast, released__gt=since).order_by(
113 "-order", "-released"
115 episodes.extend(eps[:max_per_podcast])
117 states = EpisodeState.dict_for_user(user, episodes)
119 for episode in episodes:
120 yield EpisodeStatus(episode, states.get(episode.id, "new"), None)
122 def get_episode_data(
123 self, episode_status, podcasts, domain, include_actions, user, devices
125 """Get episode data for an episode status object"""
127 # TODO: shouldn't the podcast_id be in the episode status?
128 podcast_id = episode_status.episode.podcast
129 podcast = podcasts.get(podcast_id, None)
130 t = episode_data(episode_status.episode, domain, podcast)
131 t["status"] = episode_status.status
133 # include latest action (bug 1419)
134 # TODO
135 if include_actions and episode_status.action:
136 t["action"] = episode_action_json(episode_status.action, user)
138 return t
140 def get_since(self, request):
141 """parses the "since" parameter"""
142 since_ = request.GET.get("since", None)
143 if since_ is None:
144 raise ValueError("parameter since missing")
145 try:
146 return datetime.fromtimestamp(float(since_))
147 except ValueError as e:
148 raise ValueError("'since' is not a valid timestamp: %s" % str(e))