[Migration] remove episode_for_podcast_id_url
[mygpo.git] / mygpo / db / couchdb / episode_state.py
blob20b53bf3e26e5dd3bb2dbda1c07abbc31d3add6f
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.podcasts.models import Podcast, Episode
8 from mygpo.users.models import EpisodeUserState
9 from mygpo.db import QueryParameterMissing
10 from mygpo.db.couchdb import get_main_database, get_userdata_database, \
11 get_single_result
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')
25 if hasattr(episode, '_id'):
26 episode_id = episode._id
27 else:
28 episode_id = episode.get_id()
29 key = 'episode-state-userid-%s-episodeid-%s' % (sha1(user._id).hexdigest(),
30 sha1(episode_id).hexdigest())
32 # Disabled as cache invalidation does not work properly
33 # state = cache.get(key)
34 # if state:
35 # return state
37 udb = get_userdata_database()
38 state = get_single_result(udb, 'episode_states/by_user_episode',
39 key = [user._id, episode_id],
40 include_docs = True,
41 limit = 1,
42 schema = EpisodeUserState,
45 if state:
46 cache.set(key, state)
47 return state
49 else:
50 if isinstance(episode.podcast, unicode):
51 podcast = Podcast.objects.get_by_any_id(episode.podcast)
52 else:
53 podcast = episode.podcast
55 state = EpisodeUserState()
56 state.episode = episode_id
57 state.podcast = podcast.get_id()
58 state.user = user._id
59 state.ref_url = episode.url
60 state.podcast_ref_url = podcast.url
61 # don't cache here, because the state is saved by the calling function
63 return state
67 def all_episode_states(episode):
69 if not episode:
70 raise QueryParameterMissing('episode')
72 if isinstance(episode.podcast, unicode):
73 podcast_id = episode.podcast
74 else:
75 podcast_id = episode.podcast.get_id()
77 if hasattr(episode, '_id'):
78 episode_id = episode._id
79 else:
80 episode_id = episode.get_id()
82 udb = get_userdata_database()
83 r = udb.view('episode_states/by_podcast_episode',
84 startkey = [podcast_id, episode_id, None],
85 endkey = [podcast_id, episode_id, {}],
86 include_docs = True,
87 schema = EpisodeUserState,
90 states = list(r)
92 for state in states:
93 state.set_db(udb)
95 return states
99 def all_podcast_episode_states(podcast):
101 if not podcast:
102 raise QueryParameterMissing('podcast')
104 udb = get_userdata_database()
105 r = udb.view('episode_states/by_podcast_episode',
106 startkey = [podcast.get_id(), None, None],
107 endkey = [podcast.get_id(), {}, {}],
108 include_docs = True,
109 schema = EpisodeUserState,
112 states = list(r)
114 for state in states:
115 state.set_db(udb)
117 return states
121 @cache_result(timeout=60*60)
122 def podcast_listener_count(episode):
123 """ returns the number of users that have listened to this podcast """
125 if not episode:
126 raise QueryParameterMissing('episode')
128 udb = get_userdata_database()
129 r = get_single_result(udb, 'listeners/by_podcast',
130 startkey = [episode.get_id(), None],
131 endkey = [episode.get_id(), {}],
132 group = True,
133 group_level = 1,
134 reduce = True,
135 stale = 'update_after',
137 return r['value'] if r else 0
140 @cache_result(timeout=60*60)
141 def podcast_listener_count_timespan(podcast, start=None, end={}):
142 """ returns (date, listener-count) tuples for all days w/ listeners """
144 if not podcast:
145 raise QueryParameterMissing('podcast')
147 if isinstance(start, datetime):
148 start = start.isoformat()
150 if isinstance(end, datetime):
151 end = end.isoformat()
153 udb = get_userdata_database()
154 r = udb.view('listeners/by_podcast',
155 startkey = [podcast.get_id(), start],
156 endkey = [podcast.get_id(), end],
157 group = True,
158 group_level = 2,
159 reduce = True,
160 stale = 'update_after',
163 return map(_wrap_listener_count, r)
166 @cache_result(timeout=60*60)
167 def episode_listener_counts(episode):
168 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
170 if not episode:
171 raise QueryParameterMissing('episode')
173 udb = get_userdata_database()
174 r = udb.view('listeners/by_podcast_episode',
175 startkey = [episode.get_id(), None, None],
176 endkey = [episode.get_id(), {}, {}],
177 group = True,
178 group_level = 2,
179 reduce = True,
180 stale = 'update_after',
183 return map(_wrap_listeners, r)
187 def get_podcasts_episode_states(podcast, user_id):
188 """ Returns the latest episode actions for the podcast's episodes """
190 if not podcast:
191 raise QueryParameterMissing('podcast')
193 if not user_id:
194 raise QueryParameterMissing('user_id')
196 udb = get_userdata_database()
197 res = udb.view('episode_states/by_user_podcast',
198 startkey = [user_id, podcast.get_id(), None],
199 endkey = [user_id, podcast.get_id(), {}],
202 return map(lambda r: r['value'], res)
206 @cache_result(timeout=60*60)
207 def episode_listener_count(episode, start=None, end={}):
208 """ returns the number of users that have listened to this episode """
210 if not episode:
211 raise QueryParameterMissing('episode')
213 udb = get_userdata_database()
214 r = get_single_result(udb, 'listeners/by_episode',
215 startkey = [episode.id, start],
216 endkey = [episode.id, end],
217 group = True,
218 group_level = 2,
219 reduce = True,
220 stale = 'update_after',
222 return r['value'] if r else 0
226 @cache_result(timeout=60*60)
227 def episode_listener_count_timespan(episode, start=None, end={}):
228 """ returns (date, listener-count) tuples for all days w/ listeners """
230 if not episode:
231 raise QueryParameterMissing('episode')
234 if isinstance(start, datetime):
235 start = start.isoformat()
237 if isinstance(end, datetime):
238 end = end.isoformat()
240 udb = get_userdata_database()
241 r = udb.view('listeners/by_episode',
242 startkey = [episode._id, start],
243 endkey = [episode._id, end],
244 group = True,
245 group_level = 3,
246 reduce = True,
247 stale = 'update_after',
250 return map(_wrap_listener_count, r)
254 def episode_state_for_ref_urls(user, podcast_url, episode_url):
256 if not user:
257 raise QueryParameterMissing('user')
259 if not podcast_url:
260 raise QueryParameterMissing('podcast_url')
262 if not episode_url:
263 raise QueryParameterMissing('episode_url')
266 cache_key = 'episode-state-%s-%s-%s' % (user._id,
267 sha1(podcast_url).hexdigest(),
268 sha1(episode_url).hexdigest())
270 state = cache.get(cache_key)
271 if state:
272 return state
274 udb = get_userdata_database()
275 state = get_single_result(udb, 'episode_states/by_ref_urls',
276 key = [user._id, podcast_url, episode_url],
277 limit = 1,
278 include_docs=True,
279 schema = EpisodeUserState,
282 if state:
283 state.ref_url = episode_url
284 state.podcast_ref_url = podcast_url
285 cache.set(cache_key, state, 60*60)
286 return state
288 else:
289 podcast = Podcast.objects.get_or_create_for_url(podcast_url)
290 episode = Episode.objects.get_or_create_for_url(podcast, episode_url)
291 return episode_state_for_user_episode(user, episode)
295 def get_episode_actions(user_id, since=None, until={}, podcast_id=None,
296 device_id=None):
297 """ Returns Episode Actions for the given criteria"""
299 if not user_id:
300 raise QueryParameterMissing('user_id')
302 if since >= until:
303 return []
305 if not podcast_id and not device_id:
306 view = 'episode_actions/by_user'
307 startkey = [user_id, since]
308 endkey = [user_id, until]
310 elif podcast_id and not device_id:
311 view = 'episode_actions/by_podcast'
312 startkey = [user_id, podcast_id, since]
313 endkey = [user_id, podcast_id, until]
315 elif device_id and not podcast_id:
316 view = 'episode_actions/by_device'
317 startkey = [user_id, device_id, since]
318 endkey = [user_id, device_id, until]
320 else:
321 view = 'episode_actions/by_podcast_device'
322 startkey = [user_id, podcast_id, device_id, since]
323 endkey = [user_id, podcast_id, device_id, until]
325 udb = get_userdata_database()
326 res = udb.view(view,
327 startkey = startkey,
328 endkey = endkey
331 return map(lambda r: r['value'], res)
335 @cache_result(timeout=60*60)
336 def episode_states_count():
337 udb = get_userdata_database()
338 r = udb.view('episode_states/by_user_episode',
339 limit = 0,
340 stale = 'update_after',
342 return r.total_rows
345 def get_nth_episode_state(n):
346 udb = get_userdata_database()
347 state = get_single_result(udb, 'episode_states/by_user_episode',
348 skip = n,
349 include_docs = True,
350 limit = 1,
351 schema = EpisodeUserState,
354 return state
357 def get_duplicate_episode_states(user, episode):
359 if not user:
360 raise QueryParameterMissing('user')
362 if not episode:
363 raise QueryParameterMissing('episode')
365 udb = get_userdata_database()
366 r = udb.view('episode_states/by_user_episode',
367 key = [user, episode],
368 include_docs = True,
369 schema = EpisodeUserState,
372 states = list(r)
374 for state in states:
375 state.set_db(udb)
377 return states
380 def _wrap_listener_count(res):
381 date = parser.parse(res['key'][1]).date()
382 listeners = res['value']
383 return (date, listeners)
386 def _wrap_listeners(res):
387 episode = res['key'][1]
388 listeners = res['value']
389 return (episode, listeners)
392 @cache_result(timeout=60*60)
393 def get_heatmap(podcast_id, episode_id, user_id):
394 udb = get_userdata_database()
396 group_level = len(filter(None, [podcast_id, episode_id, user_id]))
398 r = udb.view('heatmap/by_episode',
399 startkey = [podcast_id, episode_id, user_id],
400 endkey = [podcast_id, episode_id or {}, user_id or {}],
401 reduce = True,
402 group = True,
403 group_level = group_level,
404 stale = 'update_after',
407 if not r:
408 return [], []
410 else:
411 res = r.first()['value']
412 return res['heatmap'], res['borders']
415 @repeat_on_conflict(['state'])
416 def add_episode_actions(state, actions):
417 udb = get_userdata_database()
418 state.add_actions(actions)
419 udb.save_doc(state)
422 @repeat_on_conflict(['state'])
423 def update_episode_state_object(state, podcast_id, episode_id=None):
424 state.podcast = podcast_id
426 if episode_id is not None:
427 state.episode = episode_id
429 udb = get_userdata_database()
430 udb.save_doc(state)
433 @repeat_on_conflict(['state'])
434 def merge_episode_states(state, state2):
435 state.add_actions(state2.actions)
437 # overwrite settings in state2 with state's settings
438 settings = state2.settings
439 settings.update(state.settings)
440 state.settings = settings
442 merged_ids = set(state.merged_ids + [state2._id] + state2.merged_ids)
443 state.merged_ids = filter(None, merged_ids)
445 state.chapters = list(set(state.chapters + state2.chapters))
447 udb = get_userdata_database()
448 udb.save_doc(state)
451 @repeat_on_conflict(['state'])
452 def delete_episode_state(state):
453 udb = get_userdata_database()
454 udb.delete_doc(state)
457 @repeat_on_conflict(['episode_state'])
458 def update_episode_chapters(episode_state, add=[], rem=[]):
459 """ Updates the Chapter list
461 * add contains the chapters to be added
463 * rem contains tuples of (start, end) times. Chapters that match
464 both endpoints will be removed
467 for chapter in add:
468 episode_state.chapters = episode_state.chapters + [chapter]
470 for start, end in rem:
471 keep = lambda c: c.start != start or c.end != end
472 episode_state.chapters = filter(keep, episode_state.chapters)
474 episode_state.save()
477 def favorite_episode_ids_for_user(user):
479 if not user:
480 raise QueryParameterMissing('user')
482 udb = get_userdata_database()
483 favorites = udb.view('favorites/episodes_by_user',
484 key = user._id,
487 return set(x['value']['_id'] for x in favorites)
490 def chapters_for_episode(episode_id):
492 if not episode_id:
493 raise QueryParameterMissing('episode_id')
495 udb = get_userdata_database()
496 r = udb.view('chapters/by_episode',
497 startkey = [episode_id, None],
498 endkey = [episode_id, {}],
501 return map(_wrap_chapter, r)
504 def _wrap_chapter(res):
505 from mygpo.users.models import Chapter
506 user = res['key'][1]
507 chapter = Chapter.wrap(res['value'])
508 udb = get_userdata_database()
509 chapter.set_db(udb)
510 return (user, chapter)
513 @repeat_on_conflict(['episode_state'])
514 def set_episode_favorite(episode_state, is_fav):
515 udb = get_userdata_database()
516 episode_state.set_favorite(is_fav)
517 udb.save_doc(episode_state)