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
)
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()
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
)
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
)
73 'update_urls': update_urls
,
76 def update_subscriptions(self
, user
, device
, add
, remove
):
78 conflicts
= intersect(add
, remove
)
80 msg
= "can not add and remove '{}' at the same time".format(
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
)
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_
)