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
24 EpisodeStatus
= namedtuple('EpisodeStatus', 'episode status action')
27 logger
= logging
.getLogger(__name__
)
30 class DeviceUpdates(View
):
31 """ returns various updates for a device
33 http://wiki.gpodder.org/wiki/Web_Services/API_2/Devices#Get_Updates """
35 @method_decorator(csrf_exempt
)
36 @method_decorator(require_valid_user
)
37 @method_decorator(check_username
)
38 @method_decorator(never_cache
)
39 @method_decorator(cors_origin())
40 def get(self
, request
, username
, device_uid
):
42 now
= datetime
.utcnow()
43 now_
= get_timestamp(now
)
48 device
= user
.client_set
.get(uid
=device_uid
)
49 except Client
.DoesNotExist
as e
:
50 return HttpResponseNotFound(str(e
))
53 since
= self
.get_since(request
)
54 except ValueError as e
:
55 return HttpResponseBadRequest(str(e
))
57 include_actions
= parse_bool(request
.GET
.get('include_actions', False))
59 domain
= RequestSite(request
).domain
61 add
, rem
, subscriptions
= self
.get_subscription_changes(user
, device
,
64 updates
= self
.get_episode_changes(user
, subscriptions
, domain
,
65 include_actions
, since
)
71 'timestamp': get_timestamp(now
),
75 def get_subscription_changes(self
, user
, device
, since
, now
, domain
):
76 """ gets new, removed and current subscriptions """
78 history
= get_subscription_history(user
, device
, since
, now
)
79 add
, rem
= subscription_diff(history
)
81 subscriptions
= device
.get_subscribed_podcasts()
83 add
= [podcast_data(p
, domain
) for p
in add
]
84 rem
= [p
.url
for p
in rem
]
86 return add
, rem
, subscriptions
89 def get_episode_changes(self
, user
, subscriptions
, domain
, include_actions
, since
):
90 devices
= {dev
.id.hex: dev
.uid
for dev
in user
.client_set
.all()}
92 # index subscribed podcasts by their Id for fast access
93 podcasts
= {p
.get_id(): p
for p
in subscriptions
}
95 episode_updates
= self
.get_episode_updates(user
, subscriptions
, since
)
97 return [self
.get_episode_data(status
, podcasts
, domain
,
98 include_actions
, user
, devices
) for status
in episode_updates
]
101 def get_episode_updates(self
, user
, subscribed_podcasts
, since
,
103 """ Returns the episode updates since the timestamp """
106 for podcast
in subscribed_podcasts
:
107 eps
= Episode
.objects
.filter(podcast
=podcast
,
109 .order_by('-order', '-released')
110 episodes
.extend(eps
[:max_per_podcast
])
112 states
= EpisodeState
.dict_for_user(user
, episodes
)
114 for episode
in episodes
:
115 yield EpisodeStatus(episode
, states
.get(episode
.id, 'new'), None)
118 def get_episode_data(self
, episode_status
, podcasts
, domain
, include_actions
, user
, devices
):
119 """ Get episode data for an episode status object """
121 # TODO: shouldn't the podcast_id be in the episode status?
122 podcast_id
= episode_status
.episode
.podcast
123 podcast
= podcasts
.get(podcast_id
, None)
124 t
= episode_data(episode_status
.episode
, domain
, podcast
)
125 t
['status'] = episode_status
.status
127 # include latest action (bug 1419)
129 if include_actions
and episode_status
.action
:
130 t
['action'] = episode_action_json(episode_status
.action
, user
)
134 def get_since(self
, request
):
135 """ parses the "since" parameter """
136 since_
= request
.GET
.get('since', None)
138 raise ValueError('parameter since missing')
140 return datetime
.fromtimestamp(float(since_
))
141 except ValueError as e
:
142 raise ValueError("'since' is not a valid timestamp: %s" % str(e
))