set_db() for documents loaded from non-default db
[mygpo.git] / mygpo / db / couchdb / episode_state.py
blobafda626be8bf3f5a26d9969a99d3a1b57fa7f468
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, get_userdata_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 # Disabled as cache invalidation does not work properly
30 # state = cache.get(key)
31 # if state:
32 # return state
34 udb = get_userdata_database()
35 r = udb.view('episode_states/by_user_episode',
36 key = [user._id, episode._id],
37 include_docs = True,
38 limit = 1,
39 schema = EpisodeUserState,
42 if r:
43 state = r.one()
44 state.set_db(udb)
45 cache.set(key, state)
46 return state
48 else:
49 podcast = podcast_by_id(episode.podcast)
51 state = EpisodeUserState()
52 state.episode = episode._id
53 state.podcast = episode.podcast
54 state.user = user._id
55 state.ref_url = episode.url
56 state.podcast_ref_url = podcast.url
57 # don't cache here, because the state is saved by the calling function
59 return state
63 def all_episode_states(episode):
65 if not episode:
66 raise QueryParameterMissing('episode')
68 udb = get_userdata_database()
69 r = udb.view('episode_states/by_podcast_episode',
70 startkey = [episode.podcast, episode._id, None],
71 endkey = [episode.podcast, episode._id, {}],
72 include_docs = True,
73 schema = EpisodeUserState,
76 states = list(r)
78 for state in states:
79 state.set_db(udb)
81 return states
85 def all_podcast_episode_states(podcast):
87 if not podcast:
88 raise QueryParameterMissing('podcast')
90 udb = get_userdata_database()
91 r = udb.view('episode_states/by_podcast_episode',
92 startkey = [podcast.get_id(), None, None],
93 endkey = [podcast.get_id(), {}, {}],
94 include_docs = True,
95 schema = EpisodeUserState,
98 states = list(r)
100 for state in states:
101 state.set_db(udb)
103 return states
107 @cache_result(timeout=60*60)
108 def podcast_listener_count(episode):
109 """ returns the number of users that have listened to this podcast """
111 if not episode:
112 raise QueryParameterMissing('episode')
114 udb = get_userdata_database()
115 r = udb.view('listeners/by_podcast',
116 startkey = [episode.get_id(), None],
117 endkey = [episode.get_id(), {}],
118 group = True,
119 group_level = 1,
120 reduce = True,
121 stale = 'update_after',
123 return r.first()['value'] if r else 0
126 @cache_result(timeout=60*60)
127 def podcast_listener_count_timespan(podcast, start=None, end={}):
128 """ returns (date, listener-count) tuples for all days w/ listeners """
130 if not podcast:
131 raise QueryParameterMissing('podcast')
133 if isinstance(start, datetime):
134 start = start.isoformat()
136 if isinstance(end, datetime):
137 end = end.isoformat()
139 udb = get_userdata_database()
140 r = udb.view('listeners/by_podcast',
141 startkey = [podcast.get_id(), start],
142 endkey = [podcast.get_id(), end],
143 group = True,
144 group_level = 2,
145 reduce = True,
146 stale = 'update_after',
149 return map(_wrap_listener_count, r)
152 @cache_result(timeout=60*60)
153 def episode_listener_counts(episode):
154 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
156 if not episode:
157 raise QueryParameterMissing('episode')
159 udb = get_userdata_database()
160 r = udb.view('listeners/by_podcast_episode',
161 startkey = [episode.get_id(), None, None],
162 endkey = [episode.get_id(), {}, {}],
163 group = True,
164 group_level = 2,
165 reduce = True,
166 stale = 'update_after',
169 return map(_wrap_listeners, r)
173 def get_podcasts_episode_states(podcast, user_id):
174 """ Returns the latest episode actions for the podcast's episodes """
176 if not podcast:
177 raise QueryParameterMissing('podcast')
179 if not user_id:
180 raise QueryParameterMissing('user_id')
182 udb = get_userdata_database()
183 res = udb.view('episode_states/by_user_podcast',
184 startkey = [user_id, podcast.get_id(), None],
185 endkey = [user_id, podcast.get_id(), {}],
188 return map(lambda r: r['value'], res)
192 @cache_result(timeout=60*60)
193 def episode_listener_count(episode, start=None, end={}):
194 """ returns the number of users that have listened to this episode """
196 if not episode:
197 raise QueryParameterMissing('episode')
199 udb = get_userdata_database()
200 r = udb.view('listeners/by_episode',
201 startkey = [episode._id, start],
202 endkey = [episode._id, end],
203 group = True,
204 group_level = 2,
205 reduce = True,
206 stale = 'update_after',
208 return r.first()['value'] if r else 0
212 @cache_result(timeout=60*60)
213 def episode_listener_count_timespan(episode, start=None, end={}):
214 """ returns (date, listener-count) tuples for all days w/ listeners """
216 if not episode:
217 raise QueryParameterMissing('episode')
220 if isinstance(start, datetime):
221 start = start.isoformat()
223 if isinstance(end, datetime):
224 end = end.isoformat()
226 udb = get_userdata_database()
227 r = udb.view('listeners/by_episode',
228 startkey = [episode._id, start],
229 endkey = [episode._id, end],
230 group = True,
231 group_level = 3,
232 reduce = True,
233 stale = 'update_after',
236 return map(_wrap_listener_count, r)
240 def episode_state_for_ref_urls(user, podcast_url, episode_url):
242 if not user:
243 raise QueryParameterMissing('user')
245 if not podcast_url:
246 raise QueryParameterMissing('podcast_url')
248 if not episode_url:
249 raise QueryParameterMissing('episode_url')
252 cache_key = 'episode-state-%s-%s-%s' % (user._id,
253 sha1(podcast_url).hexdigest(),
254 sha1(episode_url).hexdigest())
256 state = cache.get(cache_key)
257 if state:
258 return state
260 udb = get_userdata_database()
261 res = udb.view('episode_states/by_ref_urls',
262 key = [user._id, podcast_url, episode_url],
263 limit = 1,
264 include_docs=True,
265 schema = EpisodeUserState,
268 if res:
269 state = res.first()
270 state.ref_url = episode_url
271 state.podcast_ref_url = podcast_url
272 state.set_db(udb)
273 cache.set(cache_key, state, 60*60)
274 return state
276 else:
277 podcast = podcast_for_url(podcast_url, create=True)
278 episode = episode_for_podcast_id_url(podcast.get_id(), episode_url,
279 create=True)
280 return episode_state_for_user_episode(user, episode)
284 def get_episode_actions(user_id, since=None, until={}, podcast_id=None,
285 device_id=None):
286 """ Returns Episode Actions for the given criteria"""
288 if not user_id:
289 raise QueryParameterMissing('user_id')
291 if since >= until:
292 return []
294 if not podcast_id and not device_id:
295 view = 'episode_actions/by_user'
296 startkey = [user_id, since]
297 endkey = [user_id, until]
299 elif podcast_id and not device_id:
300 view = 'episode_actions/by_podcast'
301 startkey = [user_id, podcast_id, since]
302 endkey = [user_id, podcast_id, until]
304 elif device_id and not podcast_id:
305 view = 'episode_actions/by_device'
306 startkey = [user_id, device_id, since]
307 endkey = [user_id, device_id, until]
309 else:
310 view = 'episode_actions/by_podcast_device'
311 startkey = [user_id, podcast_id, device_id, since]
312 endkey = [user_id, podcast_id, device_id, until]
314 udb = get_userdata_database()
315 res = udb.view(view,
316 startkey = startkey,
317 endkey = endkey
320 return map(lambda r: r['value'], res)
324 @cache_result(timeout=60*60)
325 def episode_states_count():
326 udb = get_userdata_database()
327 r = udb.view('episode_states/by_user_episode',
328 limit = 0,
329 stale = 'update_after',
331 return r.total_rows
334 def get_nth_episode_state(n):
335 udb = get_userdata_database()
336 first = udb.view('episode_states/by_user_episode',
337 skip = n,
338 include_docs = True,
339 limit = 1,
340 schema = EpisodeUserState,
343 if first:
344 state = first.one()
345 state.set_db(udb)
346 return state
348 else:
349 return None
352 def get_duplicate_episode_states(user, episode):
354 if not user:
355 raise QueryParameterMissing('user')
357 if not episode:
358 raise QueryParameterMissing('episode')
360 udb = get_userdata_database()
361 r = udb.view('episode_states/by_user_episode',
362 key = [user, episode],
363 include_docs = True,
364 schema = EpisodeUserState,
367 states = list(r)
369 for state in states:
370 state.set_db(udb)
372 return states
375 def _wrap_listener_count(res):
376 date = parser.parse(res['key'][1]).date()
377 listeners = res['value']
378 return (date, listeners)
381 def _wrap_listeners(res):
382 episode = res['key'][1]
383 listeners = res['value']
384 return (episode, listeners)
387 @cache_result(timeout=60*60)
388 def get_heatmap(podcast_id, episode_id, user_id):
389 udb = get_userdata_database()
391 group_level = len(filter(None, [podcast_id, episode_id, user_id]))
393 r = udb.view('heatmap/by_episode',
394 startkey = [podcast_id, episode_id, user_id],
395 endkey = [podcast_id, episode_id or {}, user_id or {}],
396 reduce = True,
397 group = True,
398 group_level = group_level,
399 stale = 'update_after',
402 if not r:
403 return [], []
405 else:
406 res = r.first()['value']
407 return res['heatmap'], res['borders']
410 @repeat_on_conflict(['state'])
411 def add_episode_actions(state, actions):
412 udb = get_userdata_database()
413 state.add_actions(actions)
414 udb.save_doc(state)
417 @repeat_on_conflict(['state'])
418 def update_episode_state_object(state, podcast_id, episode_id=None):
419 state.podcast = podcast_id
421 if episode_id is not None:
422 state.episode = episode_id
424 udb = get_userdata_database()
425 udb.save_doc(state)
428 @repeat_on_conflict(['state'])
429 def merge_episode_states(state, state2):
430 state.add_actions(state2.actions)
432 # overwrite settings in state2 with state's settings
433 settings = state2.settings
434 settings.update(state.settings)
435 state.settings = settings
437 merged_ids = set(state.merged_ids + [state2._id] + state2.merged_ids)
438 state.merged_ids = filter(None, merged_ids)
440 state.chapters = list(set(state.chapters + state2.chapters))
442 udb = get_userdata_database()
443 udb.save_doc(state)
446 @repeat_on_conflict(['state'])
447 def delete_episode_state(state):
448 udb = get_userdata_database()
449 udb.delete_doc(state)
452 @repeat_on_conflict(['episode_state'])
453 def update_episode_chapters(episode_state, add=[], rem=[]):
454 """ Updates the Chapter list
456 * add contains the chapters to be added
458 * rem contains tuples of (start, end) times. Chapters that match
459 both endpoints will be removed
462 for chapter in add:
463 episode_state.chapters = episode_state.chapters + [chapter]
465 for start, end in rem:
466 keep = lambda c: c.start != start or c.end != end
467 episode_state.chapters = filter(keep, episode_state.chapters)
469 episode_state.save()