549b2034d9c3e7b654ad998c7c02f18dfe4645d8
[mygpo.git] / mygpo / db / couchdb / episode_state.py
blob549b2034d9c3e7b654ad998c7c02f18dfe4645d8
1 from hashlib import sha1
2 from datetime import datetime
3 from dateutil import parser
5 from django.core.cache import cache
7 from mygpo.users.models import EpisodeUserState
8 from mygpo.db import QueryParameterMissing
9 from mygpo.db.couchdb.podcast import podcast_by_id, podcast_for_url
10 from mygpo.db.couchdb.episode import episode_for_podcast_id_url
11 from mygpo.db.couchdb import get_main_database
12 from mygpo.cache import cache_result
13 from mygpo.decorators import repeat_on_conflict
17 def episode_state_for_user_episode(user, episode):
19 if not user:
20 raise QueryParameterMissing('user')
22 if not episode:
23 raise QueryParameterMissing('episode')
26 key = 'episode-state-userid-%s-episodeid-%s' % (sha1(user._id).hexdigest(),
27 sha1(episode._id).hexdigest())
29 state = cache.get(key)
30 if state:
31 return state
33 r = EpisodeUserState.view('episode_states/by_user_episode',
34 key = [user._id, episode._id],
35 include_docs = True,
36 limit = 1,
39 if r:
40 state = r.one()
41 cache.set(key, state)
42 return state
44 else:
45 podcast = podcast_by_id(episode.podcast)
47 state = EpisodeUserState()
48 state.episode = episode._id
49 state.podcast = episode.podcast
50 state.user = user._id
51 state.ref_url = episode.url
52 state.podcast_ref_url = podcast.url
53 # don't cache here, because the state is saved by the calling function
55 return state
59 def all_episode_states(episode):
61 if not episode:
62 raise QueryParameterMissing('episode')
64 r = EpisodeUserState.view('episode_states/by_podcast_episode',
65 startkey = [episode.podcast, episode._id, None],
66 endkey = [episode.podcast, episode._id, {}],
67 include_docs = True,
69 return list(r)
73 def all_podcast_episode_states(podcast):
75 if not podcast:
76 raise QueryParameterMissing('podcast')
78 r = EpisodeUserState.view('episode_states/by_podcast_episode',
79 startkey = [podcast.get_id(), None, None],
80 endkey = [podcast.get_id(), {}, {}],
81 include_docs = True
83 return list(r)
87 @cache_result(timeout=60*60)
88 def podcast_listener_count(episode):
89 """ returns the number of users that have listened to this podcast """
91 if not episode:
92 raise QueryParameterMissing('episode')
94 r = EpisodeUserState.view('listeners/by_podcast',
95 startkey = [episode.get_id(), None],
96 endkey = [episode.get_id(), {}],
97 group = True,
98 group_level = 1,
99 reduce = True,
101 return r.first()['value'] if r else 0
104 @cache_result(timeout=60*60)
105 def podcast_listener_count_timespan(podcast, start=None, end={}):
106 """ returns (date, listener-count) tuples for all days w/ listeners """
108 if not podcast:
109 raise QueryParameterMissing('podcast')
111 if isinstance(start, datetime):
112 start = start.isoformat()
114 if isinstance(end, datetime):
115 end = end.isoformat()
117 r = EpisodeUserState.view('listeners/by_podcast',
118 startkey = [podcast.get_id(), start],
119 endkey = [podcast.get_id(), end],
120 group = True,
121 group_level = 2,
122 reduce = True,
125 return map(_wrap_listener_count, r)
128 @cache_result(timeout=60*60)
129 def episode_listener_counts(episode):
130 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
132 if not episode:
133 raise QueryParameterMissing('episode')
136 r = EpisodeUserState.view('listeners/by_podcast_episode',
137 startkey = [episode.get_id(), None, None],
138 endkey = [episode.get_id(), {}, {}],
139 group = True,
140 group_level = 2,
141 reduce = True,
144 return map(_wrap_listeners, r)
148 def get_podcasts_episode_states(podcast, user_id):
149 """ Returns the latest episode actions for the podcast's episodes """
151 if not podcast:
152 raise QueryParameterMissing('podcast')
154 if not user_id:
155 raise QueryParameterMissing('user_id')
158 db = get_main_database()
159 res = db.view('episode_states/by_user_podcast',
160 startkey = [user_id, podcast.get_id(), None],
161 endkey = [user_id, podcast.get_id(), {}],
164 return map(lambda r: r['value'], res)
168 @cache_result(timeout=60*60)
169 def episode_listener_count(episode, start=None, end={}):
170 """ returns the number of users that have listened to this episode """
172 if not episode:
173 raise QueryParameterMissing('episode')
176 r = EpisodeUserState.view('listeners/by_episode',
177 startkey = [episode._id, start],
178 endkey = [episode._id, end],
179 group = True,
180 group_level = 2,
181 reduce = True,
183 return r.first()['value'] if r else 0
187 @cache_result(timeout=60*60)
188 def episode_listener_count_timespan(episode, start=None, end={}):
189 """ returns (date, listener-count) tuples for all days w/ listeners """
191 if not episode:
192 raise QueryParameterMissing('episode')
195 if isinstance(start, datetime):
196 start = start.isoformat()
198 if isinstance(end, datetime):
199 end = end.isoformat()
201 r = EpisodeUserState.view('listeners/by_episode',
202 startkey = [episode._id, start],
203 endkey = [episode._id, end],
204 group = True,
205 group_level = 3,
206 reduce = True,
209 return map(_wrap_listener_count, r)
213 def episode_state_for_ref_urls(user, podcast_url, episode_url):
215 if not user:
216 raise QueryParameterMissing('user')
218 if not podcast_url:
219 raise QueryParameterMissing('podcast_url')
221 if not episode_url:
222 raise QueryParameterMissing('episode_url')
225 cache_key = 'episode-state-%s-%s-%s' % (user._id,
226 sha1(podcast_url).hexdigest(),
227 sha1(episode_url).hexdigest())
229 state = cache.get(cache_key)
230 if state:
231 return state
233 res = EpisodeUserState.view('episode_states/by_ref_urls',
234 key = [user._id, podcast_url, episode_url],
235 limit = 1,
236 include_docs=True,
239 if res:
240 state = res.first()
241 state.ref_url = episode_url
242 state.podcast_ref_url = podcast_url
243 cache.set(cache_key, state, 60*60)
244 return state
246 else:
247 podcast = podcast_for_url(podcast_url, create=True)
248 episode = episode_for_podcast_id_url(podcast.get_id(), episode_url,
249 create=True)
250 return episode_state_for_user_episode(user, episode)
254 def get_episode_actions(user_id, since=None, until={}, podcast_id=None,
255 device_id=None):
256 """ Returns Episode Actions for the given criteria"""
258 if not user_id:
259 raise QueryParameterMissing('user_id')
261 if since >= until:
262 return []
264 if not podcast_id and not device_id:
265 view = 'episode_actions/by_user'
266 startkey = [user_id, since]
267 endkey = [user_id, until]
269 elif podcast_id and not device_id:
270 view = 'episode_actions/by_podcast'
271 startkey = [user_id, podcast_id, since]
272 endkey = [user_id, podcast_id, until]
274 elif device_id and not podcast_id:
275 view = 'episode_actions/by_device'
276 startkey = [user_id, device_id, since]
277 endkey = [user_id, device_id, until]
279 else:
280 view = 'episode_actions/by_podcast_device'
281 startkey = [user_id, podcast_id, device_id, since]
282 endkey = [user_id, podcast_id, device_id, until]
284 db = get_main_database()
285 res = db.view(view,
286 startkey = startkey,
287 endkey = endkey
290 return map(lambda r: r['value'], res)
294 @cache_result(timeout=60*60)
295 def episode_states_count():
296 r = EpisodeUserState.view('episode_states/by_user_episode',
297 limit = 0,
298 stale = 'update_after',
300 return r.total_rows
303 def get_nth_episode_state(n):
304 first = EpisodeUserState.view('episode_states/by_user_episode',
305 skip = n,
306 include_docs = True,
307 limit = 1,
309 return first.one() if first else None
312 def get_duplicate_episode_states(user, episode):
314 if not user:
315 raise QueryParameterMissing('user')
317 if not episode:
318 raise QueryParameterMissing('episode')
320 states = EpisodeUserState.view('episode_states/by_user_episode',
321 key = [user, episode],
322 include_docs = True,
324 return list(states)
327 def _wrap_listener_count(res):
328 date = parser.parse(res['key'][1]).date()
329 listeners = res['value']
330 return (date, listeners)
333 def _wrap_listeners(res):
334 episode = res['key'][1]
335 listeners = res['value']
336 return (episode, listeners)
339 @cache_result(timeout=60*60)
340 def get_heatmap(podcast_id, episode_id, user_id):
341 db = get_main_database()
343 group_level = len(filter(None, [podcast_id, episode_id, user_id]))
345 r = db.view('heatmap/by_episode',
346 startkey = [podcast_id, episode_id, user_id],
347 endkey = [podcast_id, episode_id or {}, user_id or {}],
348 reduce = True,
349 group = True,
350 group_level = group_level,
351 stale = 'update_after',
354 if not r:
355 return [], []
357 else:
358 res = r.first()['value']
359 return res['heatmap'], res['borders']
362 @repeat_on_conflict(['state'])
363 def add_episode_actions(state, actions):
364 state.add_actions(actions)
365 state.save()