Bump babel from 2.9.1 to 2.10.3
[mygpo.git] / mygpo / api / subscriptions.py
blob27f5f094e58582f9d69a0cb7f63c25a87c70c3b6
1 from datetime import datetime
3 from django.shortcuts import get_object_or_404
5 from mygpo.podcasts.models import Podcast
6 from mygpo.api import APIView, RequestException
7 from mygpo.api.httpresponse import JsonResponse
8 from mygpo.api.backend import get_device
9 from mygpo.utils import get_timestamp, normalize_feed_url, intersect
10 from mygpo.users.models import Client
11 from mygpo.subscriptions.tasks import subscribe, unsubscribe
12 from mygpo.subscriptions import get_subscription_history, subscription_diff
13 from mygpo.api.basic_auth import require_valid_user, check_username
15 import logging
17 logger = logging.getLogger(__name__)
20 class SubscriptionsAPI(APIView):
21 """API for sending and retrieving podcast subscription updates"""
23 def get(self, request, version, username, device_uid):
24 """Client retrieves subscription updates"""
25 now = datetime.utcnow()
26 user = request.user
27 device = get_object_or_404(Client, user=user, uid=device_uid)
28 since = self.get_since(request)
29 add, rem, until = self.get_changes(user, device, since, now)
30 return JsonResponse({"add": add, "remove": rem, "timestamp": until})
32 def post(self, request, version, username, device_uid):
33 """Client sends subscription updates"""
34 now = get_timestamp(datetime.utcnow())
35 logger.info(
36 "Subscription Upload @{username}/{device_uid}".format(
37 username=request.user.username, device_uid=device_uid
41 d = get_device(
42 request.user, device_uid, request.META.get("HTTP_USER_AGENT", "")
45 actions = self.parsed_body(request)
47 add = list(filter(None, actions.get("add", [])))
48 rem = list(filter(None, actions.get("remove", [])))
49 logger.info(
50 "Subscription Upload @{username}/{device_uid}: add "
51 "{num_add}, remove {num_remove}".format(
52 username=request.user.username,
53 device_uid=device_uid,
54 num_add=len(add),
55 num_remove=len(rem),
59 update_urls = self.update_subscriptions(request.user, d, add, rem)
61 return JsonResponse({"timestamp": now, "update_urls": update_urls})
63 def update_subscriptions(self, user, device, add, remove):
65 conflicts = intersect(add, remove)
66 if conflicts:
67 msg = "can not add and remove '{}' at the same time".format(str(conflicts))
68 logger.warning(msg)
69 raise RequestException(msg)
71 add_s = list(map(normalize_feed_url, add))
72 rem_s = list(map(normalize_feed_url, remove))
74 assert len(add) == len(add_s) and len(remove) == len(rem_s)
76 pairs = zip(add + remove, add_s + rem_s)
77 updated_urls = list(filter(lambda pair: pair[0] != pair[1], pairs))
79 add_s = filter(None, add_s)
80 rem_s = filter(None, rem_s)
82 # If two different URLs (in add and remove) have
83 # been sanitized to the same, we ignore the removal
84 rem_s = filter(lambda x: x not in add_s, rem_s)
86 for add_url in add_s:
87 podcast = Podcast.objects.get_or_create_for_url(add_url).object
88 subscribe(podcast.pk, user.pk, device.uid, add_url)
90 remove_podcasts = Podcast.objects.filter(urls__url__in=rem_s)
91 for podcast in remove_podcasts:
92 unsubscribe(podcast.pk, user.pk, device.uid)
94 return updated_urls
96 def get_changes(self, user, device, since, until):
97 """Returns subscription changes for the given device"""
98 history = get_subscription_history(user, device, since, until)
99 logger.info("Subscription History: {num}".format(num=len(history)))
101 add, rem = subscription_diff(history)
102 logger.info(
103 "Subscription Diff: +{num_add}/-{num_remove}".format(
104 num_add=len(add), num_remove=len(rem)
108 until_ = get_timestamp(until)
110 # TODO: we'd need to get the ref_urls here somehow
111 add_urls = [p.url for p in add]
112 rem_urls = [p.url for p in rem]
113 return (add_urls, rem_urls, until_)