add some stale='update_after' for better performance
[mygpo.git] / mygpo / db / couchdb / episode_state.py
blobb03ba24dc5927d770eb6e2b21c5f23eaa5cc79bd
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,
100 stale = 'update_after',
102 return r.first()['value'] if r else 0
105 @cache_result(timeout=60*60)
106 def podcast_listener_count_timespan(podcast, start=None, end={}):
107 """ returns (date, listener-count) tuples for all days w/ listeners """
109 if not podcast:
110 raise QueryParameterMissing('podcast')
112 if isinstance(start, datetime):
113 start = start.isoformat()
115 if isinstance(end, datetime):
116 end = end.isoformat()
118 r = EpisodeUserState.view('listeners/by_podcast',
119 startkey = [podcast.get_id(), start],
120 endkey = [podcast.get_id(), end],
121 group = True,
122 group_level = 2,
123 reduce = True,
124 stale = 'update_after',
127 return map(_wrap_listener_count, r)
130 @cache_result(timeout=60*60)
131 def episode_listener_counts(episode):
132 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
134 if not episode:
135 raise QueryParameterMissing('episode')
138 r = EpisodeUserState.view('listeners/by_podcast_episode',
139 startkey = [episode.get_id(), None, None],
140 endkey = [episode.get_id(), {}, {}],
141 group = True,
142 group_level = 2,
143 reduce = True,
144 stale = 'update_after',
147 return map(_wrap_listeners, r)
151 def get_podcasts_episode_states(podcast, user_id):
152 """ Returns the latest episode actions for the podcast's episodes """
154 if not podcast:
155 raise QueryParameterMissing('podcast')
157 if not user_id:
158 raise QueryParameterMissing('user_id')
161 db = get_main_database()
162 res = db.view('episode_states/by_user_podcast',
163 startkey = [user_id, podcast.get_id(), None],
164 endkey = [user_id, podcast.get_id(), {}],
167 return map(lambda r: r['value'], res)
171 @cache_result(timeout=60*60)
172 def episode_listener_count(episode, start=None, end={}):
173 """ returns the number of users that have listened to this episode """
175 if not episode:
176 raise QueryParameterMissing('episode')
179 r = EpisodeUserState.view('listeners/by_episode',
180 startkey = [episode._id, start],
181 endkey = [episode._id, end],
182 group = True,
183 group_level = 2,
184 reduce = True,
185 stale = 'update_after',
187 return r.first()['value'] if r else 0
191 @cache_result(timeout=60*60)
192 def episode_listener_count_timespan(episode, start=None, end={}):
193 """ returns (date, listener-count) tuples for all days w/ listeners """
195 if not episode:
196 raise QueryParameterMissing('episode')
199 if isinstance(start, datetime):
200 start = start.isoformat()
202 if isinstance(end, datetime):
203 end = end.isoformat()
205 r = EpisodeUserState.view('listeners/by_episode',
206 startkey = [episode._id, start],
207 endkey = [episode._id, end],
208 group = True,
209 group_level = 3,
210 reduce = True,
211 stale = 'update_after',
214 return map(_wrap_listener_count, r)
218 def episode_state_for_ref_urls(user, podcast_url, episode_url):
220 if not user:
221 raise QueryParameterMissing('user')
223 if not podcast_url:
224 raise QueryParameterMissing('podcast_url')
226 if not episode_url:
227 raise QueryParameterMissing('episode_url')
230 cache_key = 'episode-state-%s-%s-%s' % (user._id,
231 sha1(podcast_url).hexdigest(),
232 sha1(episode_url).hexdigest())
234 state = cache.get(cache_key)
235 if state:
236 return state
238 res = EpisodeUserState.view('episode_states/by_ref_urls',
239 key = [user._id, podcast_url, episode_url],
240 limit = 1,
241 include_docs=True,
244 if res:
245 state = res.first()
246 state.ref_url = episode_url
247 state.podcast_ref_url = podcast_url
248 cache.set(cache_key, state, 60*60)
249 return state
251 else:
252 podcast = podcast_for_url(podcast_url, create=True)
253 episode = episode_for_podcast_id_url(podcast.get_id(), episode_url,
254 create=True)
255 return episode_state_for_user_episode(user, episode)
259 def get_episode_actions(user_id, since=None, until={}, podcast_id=None,
260 device_id=None):
261 """ Returns Episode Actions for the given criteria"""
263 if not user_id:
264 raise QueryParameterMissing('user_id')
266 if since >= until:
267 return []
269 if not podcast_id and not device_id:
270 view = 'episode_actions/by_user'
271 startkey = [user_id, since]
272 endkey = [user_id, until]
274 elif podcast_id and not device_id:
275 view = 'episode_actions/by_podcast'
276 startkey = [user_id, podcast_id, since]
277 endkey = [user_id, podcast_id, until]
279 elif device_id and not podcast_id:
280 view = 'episode_actions/by_device'
281 startkey = [user_id, device_id, since]
282 endkey = [user_id, device_id, until]
284 else:
285 view = 'episode_actions/by_podcast_device'
286 startkey = [user_id, podcast_id, device_id, since]
287 endkey = [user_id, podcast_id, device_id, until]
289 db = get_main_database()
290 res = db.view(view,
291 startkey = startkey,
292 endkey = endkey
295 return map(lambda r: r['value'], res)
299 @cache_result(timeout=60*60)
300 def episode_states_count():
301 r = EpisodeUserState.view('episode_states/by_user_episode',
302 limit = 0,
303 stale = 'update_after',
305 return r.total_rows
308 def get_nth_episode_state(n):
309 first = EpisodeUserState.view('episode_states/by_user_episode',
310 skip = n,
311 include_docs = True,
312 limit = 1,
314 return first.one() if first else None
317 def get_duplicate_episode_states(user, episode):
319 if not user:
320 raise QueryParameterMissing('user')
322 if not episode:
323 raise QueryParameterMissing('episode')
325 states = EpisodeUserState.view('episode_states/by_user_episode',
326 key = [user, episode],
327 include_docs = True,
329 return list(states)
332 def _wrap_listener_count(res):
333 date = parser.parse(res['key'][1]).date()
334 listeners = res['value']
335 return (date, listeners)
338 def _wrap_listeners(res):
339 episode = res['key'][1]
340 listeners = res['value']
341 return (episode, listeners)
344 @cache_result(timeout=60*60)
345 def get_heatmap(podcast_id, episode_id, user_id):
346 db = get_main_database()
348 group_level = len(filter(None, [podcast_id, episode_id, user_id]))
350 r = db.view('heatmap/by_episode',
351 startkey = [podcast_id, episode_id, user_id],
352 endkey = [podcast_id, episode_id or {}, user_id or {}],
353 reduce = True,
354 group = True,
355 group_level = group_level,
356 stale = 'update_after',
359 if not r:
360 return [], []
362 else:
363 res = r.first()['value']
364 return res['heatmap'], res['borders']
367 @repeat_on_conflict(['state'])
368 def add_episode_actions(state, actions):
369 state.add_actions(actions)
370 state.save()