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")
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
)
50 device
= user
.client_set
.get(uid
=device_uid
)
51 except Client
.DoesNotExist
as e
:
52 return HttpResponseNotFound(str(e
))
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
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
)
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"""
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)
135 if include_actions
and episode_status
.action
:
136 t
["action"] = episode_action_json(episode_status
.action
, user
)
140 def get_since(self
, request
):
141 """parses the "since" parameter"""
142 since_
= request
.GET
.get("since", None)
144 raise ValueError("parameter since missing")
146 return datetime
.fromtimestamp(float(since_
))
147 except ValueError as e
:
148 raise ValueError("'since' is not a valid timestamp: %s" % str(e
))