Remove usage for ultrajson (ujson)
[mygpo.git] / mygpo / api / subscriptions.py
blob729c21846db9bf80e7bb686021d1fe68ee5ebdab
2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
18 from datetime import datetime
20 from django.shortcuts import get_object_or_404
22 from mygpo.podcasts.models import Podcast
23 from mygpo.api import APIView, RequestException
24 from mygpo.api.httpresponse import JsonResponse
25 from mygpo.api.backend import get_device
26 from mygpo.utils import get_timestamp, normalize_feed_url, intersect
27 from mygpo.users.models import Client
28 from mygpo.subscriptions import (subscribe, unsubscribe,
29 get_subscription_history, subscription_diff)
31 import logging
32 logger = logging.getLogger(__name__)
35 class SubscriptionsAPI(APIView):
36 """ API for sending and retrieving podcast subscription updates """
38 def get(self, request, version, username, device_uid):
39 """ Client retrieves subscription updates """
40 now = datetime.utcnow()
41 user = request.user
42 device = get_object_or_404(Client, user=user, uid=device_uid)
43 since = self.get_since(request)
44 add, rem, until = self.get_changes(user, device, since, now)
45 return JsonResponse({
46 'add': add,
47 'remove': rem,
48 'timestamp': until
51 def post(self, request, version, username, device_uid):
52 """ Client sends subscription updates """
53 now = get_timestamp(datetime.utcnow())
54 logger.info('Subscription Upload @{username}/{device_uid}'.format(
55 username=request.user.username, device_uid=device_uid))
57 d = get_device(request.user, device_uid,
58 request.META.get('HTTP_USER_AGENT', ''))
60 actions = self.parsed_body(request)
62 add = list(filter(None, actions.get('add', [])))
63 rem = list(filter(None, actions.get('remove', [])))
64 logger.info('Subscription Upload @{username}/{device_uid}: add '
65 '{num_add}, remove {num_remove}'.format(
66 username=request.user.username, device_uid=device_uid,
67 num_add=len(add), num_remove=len(rem)))
69 update_urls = self.update_subscriptions(request.user, d, add, rem)
71 return JsonResponse({
72 'timestamp': now,
73 'update_urls': update_urls,
76 def update_subscriptions(self, user, device, add, remove):
78 conflicts = intersect(add, remove)
79 if conflicts:
80 msg = "can not add and remove '{}' at the same time".format(
81 str(conflicts))
82 logger.warn(msg)
83 raise RequestException(msg)
85 add_s = list(map(normalize_feed_url, add))
86 rem_s = list(map(normalize_feed_url, remove))
88 assert len(add) == len(add_s) and len(remove) == len(rem_s)
90 pairs = zip(add + remove, add_s + rem_s)
91 updated_urls = list(filter(lambda pair: pair[0] != pair[1], pairs))
93 add_s = filter(None, add_s)
94 rem_s = filter(None, rem_s)
96 # If two different URLs (in add and remove) have
97 # been sanitized to the same, we ignore the removal
98 rem_s = filter(lambda x: x not in add_s, rem_s)
100 for add_url in add_s:
101 podcast = Podcast.objects.get_or_create_for_url(add_url)
102 subscribe(podcast, user, device, add_url)
104 remove_podcasts = Podcast.objects.filter(urls__url__in=rem_s)
105 for podcast in remove_podcasts:
106 unsubscribe(podcast, user, device)
108 return updated_urls
110 def get_changes(self, user, device, since, until):
111 """ Returns subscription changes for the given device """
112 history = get_subscription_history(user, device, since, until)
113 logger.info('Subscription History: {num}'.format(num=len(history)))
115 add, rem = subscription_diff(history)
116 logger.info('Subscription Diff: +{num_add}/-{num_remove}'.format(
117 num_add=len(add), num_remove=len(rem)))
119 until_ = get_timestamp(until)
121 # TODO: we'd need to get the ref_urls here somehow
122 add_urls = [p.url for p in add]
123 rem_urls = [p.url for p in rem]
124 return (add_urls, rem_urls, until_)