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
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()
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())
36 "Subscription Upload @{username}/{device_uid}".format(
37 username
=request
.user
.username
, device_uid
=device_uid
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", [])))
50 "Subscription Upload @{username}/{device_uid}: add "
51 "{num_add}, remove {num_remove}".format(
52 username
=request
.user
.username
,
53 device_uid
=device_uid
,
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
)
67 msg
= "can not add and remove '{}' at the same time".format(str(conflicts
))
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
)
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
)
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
)
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_
)