replace EpisodeUserState.update_chapters
[mygpo.git] / mygpo / db / couchdb / episode_state.py
blobd7b5423f01f24d631c4addb86fb37b74b336d772
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 cache.set(key, state)
45 return state
47 else:
48 podcast = podcast_by_id(episode.podcast)
50 state = EpisodeUserState()
51 state.episode = episode._id
52 state.podcast = episode.podcast
53 state.user = user._id
54 state.ref_url = episode.url
55 state.podcast_ref_url = podcast.url
56 # don't cache here, because the state is saved by the calling function
58 return state
62 def all_episode_states(episode):
64 if not episode:
65 raise QueryParameterMissing('episode')
67 udb = get_userdata_database()
68 r = udb.view('episode_states/by_podcast_episode',
69 startkey = [episode.podcast, episode._id, None],
70 endkey = [episode.podcast, episode._id, {}],
71 include_docs = True,
72 schema = EpisodeUserState,
74 return list(r)
78 def all_podcast_episode_states(podcast):
80 if not podcast:
81 raise QueryParameterMissing('podcast')
83 udb = get_userdata_database()
84 r = udb.view('episode_states/by_podcast_episode',
85 startkey = [podcast.get_id(), None, None],
86 endkey = [podcast.get_id(), {}, {}],
87 include_docs = True,
88 schema = EpisodeUserState,
90 return list(r)
94 @cache_result(timeout=60*60)
95 def podcast_listener_count(episode):
96 """ returns the number of users that have listened to this podcast """
98 if not episode:
99 raise QueryParameterMissing('episode')
101 udb = get_userdata_database()
102 r = udb.view('listeners/by_podcast',
103 startkey = [episode.get_id(), None],
104 endkey = [episode.get_id(), {}],
105 group = True,
106 group_level = 1,
107 reduce = True,
108 stale = 'update_after',
110 return r.first()['value'] if r else 0
113 @cache_result(timeout=60*60)
114 def podcast_listener_count_timespan(podcast, start=None, end={}):
115 """ returns (date, listener-count) tuples for all days w/ listeners """
117 if not podcast:
118 raise QueryParameterMissing('podcast')
120 if isinstance(start, datetime):
121 start = start.isoformat()
123 if isinstance(end, datetime):
124 end = end.isoformat()
126 udb = get_userdata_database()
127 r = udb.view('listeners/by_podcast',
128 startkey = [podcast.get_id(), start],
129 endkey = [podcast.get_id(), end],
130 group = True,
131 group_level = 2,
132 reduce = True,
133 stale = 'update_after',
136 return map(_wrap_listener_count, r)
139 @cache_result(timeout=60*60)
140 def episode_listener_counts(episode):
141 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
143 if not episode:
144 raise QueryParameterMissing('episode')
146 udb = get_userdata_database()
147 r = udb.view('listeners/by_podcast_episode',
148 startkey = [episode.get_id(), None, None],
149 endkey = [episode.get_id(), {}, {}],
150 group = True,
151 group_level = 2,
152 reduce = True,
153 stale = 'update_after',
156 return map(_wrap_listeners, r)
160 def get_podcasts_episode_states(podcast, user_id):
161 """ Returns the latest episode actions for the podcast's episodes """
163 if not podcast:
164 raise QueryParameterMissing('podcast')
166 if not user_id:
167 raise QueryParameterMissing('user_id')
169 udb = get_userdata_database()
170 res = udb.view('episode_states/by_user_podcast',
171 startkey = [user_id, podcast.get_id(), None],
172 endkey = [user_id, podcast.get_id(), {}],
175 return map(lambda r: r['value'], res)
179 @cache_result(timeout=60*60)
180 def episode_listener_count(episode, start=None, end={}):
181 """ returns the number of users that have listened to this episode """
183 if not episode:
184 raise QueryParameterMissing('episode')
186 udb = get_userdata_database()
187 r = udb.view('listeners/by_episode',
188 startkey = [episode._id, start],
189 endkey = [episode._id, end],
190 group = True,
191 group_level = 2,
192 reduce = True,
193 stale = 'update_after',
195 return r.first()['value'] if r else 0
199 @cache_result(timeout=60*60)
200 def episode_listener_count_timespan(episode, start=None, end={}):
201 """ returns (date, listener-count) tuples for all days w/ listeners """
203 if not episode:
204 raise QueryParameterMissing('episode')
207 if isinstance(start, datetime):
208 start = start.isoformat()
210 if isinstance(end, datetime):
211 end = end.isoformat()
213 udb = get_userdata_database()
214 r = udb.view('listeners/by_episode',
215 startkey = [episode._id, start],
216 endkey = [episode._id, end],
217 group = True,
218 group_level = 3,
219 reduce = True,
220 stale = 'update_after',
223 return map(_wrap_listener_count, r)
227 def episode_state_for_ref_urls(user, podcast_url, episode_url):
229 if not user:
230 raise QueryParameterMissing('user')
232 if not podcast_url:
233 raise QueryParameterMissing('podcast_url')
235 if not episode_url:
236 raise QueryParameterMissing('episode_url')
239 cache_key = 'episode-state-%s-%s-%s' % (user._id,
240 sha1(podcast_url).hexdigest(),
241 sha1(episode_url).hexdigest())
243 state = cache.get(cache_key)
244 if state:
245 return state
247 udb = get_userdata_database()
248 res = udb.view('episode_states/by_ref_urls',
249 key = [user._id, podcast_url, episode_url],
250 limit = 1,
251 include_docs=True,
252 schema = EpisodeUserState,
255 if res:
256 state = res.first()
257 state.ref_url = episode_url
258 state.podcast_ref_url = podcast_url
259 cache.set(cache_key, state, 60*60)
260 return state
262 else:
263 podcast = podcast_for_url(podcast_url, create=True)
264 episode = episode_for_podcast_id_url(podcast.get_id(), episode_url,
265 create=True)
266 return episode_state_for_user_episode(user, episode)
270 def get_episode_actions(user_id, since=None, until={}, podcast_id=None,
271 device_id=None):
272 """ Returns Episode Actions for the given criteria"""
274 if not user_id:
275 raise QueryParameterMissing('user_id')
277 if since >= until:
278 return []
280 if not podcast_id and not device_id:
281 view = 'episode_actions/by_user'
282 startkey = [user_id, since]
283 endkey = [user_id, until]
285 elif podcast_id and not device_id:
286 view = 'episode_actions/by_podcast'
287 startkey = [user_id, podcast_id, since]
288 endkey = [user_id, podcast_id, until]
290 elif device_id and not podcast_id:
291 view = 'episode_actions/by_device'
292 startkey = [user_id, device_id, since]
293 endkey = [user_id, device_id, until]
295 else:
296 view = 'episode_actions/by_podcast_device'
297 startkey = [user_id, podcast_id, device_id, since]
298 endkey = [user_id, podcast_id, device_id, until]
300 udb = get_userdata_database()
301 res = udb.view(view,
302 startkey = startkey,
303 endkey = endkey
306 return map(lambda r: r['value'], res)
310 @cache_result(timeout=60*60)
311 def episode_states_count():
312 udb = get_userdata_database()
313 r = udb.view('episode_states/by_user_episode',
314 limit = 0,
315 stale = 'update_after',
317 return r.total_rows
320 def get_nth_episode_state(n):
321 udb = get_userdata_database()
322 first = udb.view('episode_states/by_user_episode',
323 skip = n,
324 include_docs = True,
325 limit = 1,
326 schema = EpisodeUserState,
328 return first.one() if first else None
331 def get_duplicate_episode_states(user, episode):
333 if not user:
334 raise QueryParameterMissing('user')
336 if not episode:
337 raise QueryParameterMissing('episode')
339 udb = get_userdata_database()
340 states = udb.view('episode_states/by_user_episode',
341 key = [user, episode],
342 include_docs = True,
343 schema = EpisodeUserState,
345 return list(states)
348 def _wrap_listener_count(res):
349 date = parser.parse(res['key'][1]).date()
350 listeners = res['value']
351 return (date, listeners)
354 def _wrap_listeners(res):
355 episode = res['key'][1]
356 listeners = res['value']
357 return (episode, listeners)
360 @cache_result(timeout=60*60)
361 def get_heatmap(podcast_id, episode_id, user_id):
362 udb = get_userdata_database()
364 group_level = len(filter(None, [podcast_id, episode_id, user_id]))
366 r = udb.view('heatmap/by_episode',
367 startkey = [podcast_id, episode_id, user_id],
368 endkey = [podcast_id, episode_id or {}, user_id or {}],
369 reduce = True,
370 group = True,
371 group_level = group_level,
372 stale = 'update_after',
375 if not r:
376 return [], []
378 else:
379 res = r.first()['value']
380 return res['heatmap'], res['borders']
383 @repeat_on_conflict(['state'])
384 def add_episode_actions(state, actions):
385 udb = get_userdata_database()
386 state.add_actions(actions)
387 udb.save_doc(state)
390 @repeat_on_conflict(['state'])
391 def update_episode_state_object(state, podcast_id, episode_id=None):
392 state.podcast = podcast_id
394 if episode_id is not None:
395 state.episode = episode_id
397 udb = get_userdata_database()
398 udb.save_doc(state)
401 @repeat_on_conflict(['state'])
402 def merge_episode_states(state, state2):
403 state.add_actions(state2.actions)
405 # overwrite settings in state2 with state's settings
406 settings = state2.settings
407 settings.update(state.settings)
408 state.settings = settings
410 merged_ids = set(state.merged_ids + [state2._id] + state2.merged_ids)
411 state.merged_ids = filter(None, merged_ids)
413 state.chapters = list(set(state.chapters + state2.chapters))
415 udb = get_userdata_database()
416 udb.save_doc(state)
419 @repeat_on_conflict(['state'])
420 def delete_episode_state(state):
421 udb = get_userdata_database()
422 udb.delete_doc(state)
425 @repeat_on_conflict(['episode_state'])
426 def update_episode_chapters(episode_state, add=[], rem=[]):
427 """ Updates the Chapter list
429 * add contains the chapters to be added
431 * rem contains tuples of (start, end) times. Chapters that match
432 both endpoints will be removed
435 for chapter in add:
436 episode_state.chapters = episode_state.chapters + [chapter]
438 for start, end in rem:
439 keep = lambda c: c.start != start or c.end != end
440 episode_state.chapters = filter(keep, episode_state.chapters)
442 episode_state.save()