[Migration] remove unused CouchDB methods
[mygpo.git] / mygpo / db / couchdb / episode_state.py
blob01df8bd6193a2c9ab7e4ac69835d52c0d3db13f0
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_userdata_database, get_single_result
11 from mygpo.cache import cache_result
12 from mygpo.decorators import repeat_on_conflict
16 def episode_state_for_user_episode(user, episode):
18 if not user:
19 raise QueryParameterMissing('user')
21 if not episode:
22 raise QueryParameterMissing('episode')
24 if hasattr(episode, '_id'):
25 episode_id = episode._id
26 else:
27 episode_id = episode.get_id()
28 key = 'episode-state-userid-%s-episodeid-%s' % (sha1(user.profile.uuid.hex).hexdigest(),
29 sha1(episode_id).hexdigest())
31 # Disabled as cache invalidation does not work properly
32 # state = cache.get(key)
33 # if state:
34 # return state
36 udb = get_userdata_database()
37 state = get_single_result(udb, 'episode_states/by_user_episode',
38 key = [user.profile.uuid.hex, episode_id],
39 include_docs = True,
40 limit = 1,
41 schema = EpisodeUserState,
44 if state:
45 cache.set(key, state)
46 return state
48 else:
49 if isinstance(episode.podcast, unicode):
50 podcast = Podcast.objects.get_by_any_id(episode.podcast)
51 else:
52 podcast = episode.podcast
54 state = EpisodeUserState()
55 state.episode = episode_id
56 state.podcast = podcast.get_id()
57 state.user = user.profile.uuid.hex
58 state.ref_url = episode.url
59 state.podcast_ref_url = podcast.url
60 # don't cache here, because the state is saved by the calling function
62 return state
66 def all_episode_states(episode):
68 if not episode:
69 raise QueryParameterMissing('episode')
71 if isinstance(episode.podcast, unicode):
72 podcast_id = episode.podcast
73 else:
74 podcast_id = episode.podcast.get_id()
76 if hasattr(episode, '_id'):
77 episode_id = episode._id
78 else:
79 episode_id = episode.get_id()
81 udb = get_userdata_database()
82 r = udb.view('episode_states/by_podcast_episode',
83 startkey = [podcast_id, episode_id, None],
84 endkey = [podcast_id, episode_id, {}],
85 include_docs = True,
86 schema = EpisodeUserState,
89 states = list(r)
91 for state in states:
92 state.set_db(udb)
94 return states
97 @cache_result(timeout=60*60)
98 def podcast_listener_count_timespan(podcast, start=None, end={}):
99 """ returns (date, listener-count) tuples for all days w/ listeners """
101 if not podcast:
102 raise QueryParameterMissing('podcast')
104 if isinstance(start, datetime):
105 start = start.isoformat()
107 if isinstance(end, datetime):
108 end = end.isoformat()
110 udb = get_userdata_database()
111 r = udb.view('listeners/by_podcast',
112 startkey = [podcast.get_id(), start],
113 endkey = [podcast.get_id(), end],
114 group = True,
115 group_level = 2,
116 reduce = True,
117 stale = 'update_after',
120 return map(_wrap_listener_count, r)
123 @cache_result(timeout=60*60)
124 def episode_listener_counts(episode):
125 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
127 if not episode:
128 raise QueryParameterMissing('episode')
130 udb = get_userdata_database()
131 r = udb.view('listeners/by_podcast_episode',
132 startkey = [episode.get_id(), None, None],
133 endkey = [episode.get_id(), {}, {}],
134 group = True,
135 group_level = 2,
136 reduce = True,
137 stale = 'update_after',
140 return map(_wrap_listeners, r)
144 def get_podcasts_episode_states(podcast, user_id):
145 """ Returns the latest episode actions for the podcast's episodes """
147 if not podcast:
148 raise QueryParameterMissing('podcast')
150 if not user_id:
151 raise QueryParameterMissing('user_id')
153 udb = get_userdata_database()
154 res = udb.view('episode_states/by_user_podcast',
155 startkey = [user_id, podcast.get_id(), None],
156 endkey = [user_id, podcast.get_id(), {}],
159 return map(lambda r: r['value'], res)
162 @cache_result(timeout=60*60)
163 def episode_listener_count_timespan(episode, start=None, end={}):
164 """ returns (date, listener-count) tuples for all days w/ listeners """
166 if not episode:
167 raise QueryParameterMissing('episode')
170 if isinstance(start, datetime):
171 start = start.isoformat()
173 if isinstance(end, datetime):
174 end = end.isoformat()
176 udb = get_userdata_database()
177 r = udb.view('listeners/by_episode',
178 startkey = [episode._id, start],
179 endkey = [episode._id, end],
180 group = True,
181 group_level = 3,
182 reduce = True,
183 stale = 'update_after',
186 return map(_wrap_listener_count, r)
190 def episode_state_for_ref_urls(user, podcast_url, episode_url):
192 if not user:
193 raise QueryParameterMissing('user')
195 if not podcast_url:
196 raise QueryParameterMissing('podcast_url')
198 if not episode_url:
199 raise QueryParameterMissing('episode_url')
202 cache_key = 'episode-state-%s-%s-%s' % (user.profile.uuid.hex,
203 sha1(podcast_url).hexdigest(),
204 sha1(episode_url).hexdigest())
206 state = cache.get(cache_key)
207 if state:
208 return state
210 udb = get_userdata_database()
211 state = get_single_result(udb, 'episode_states/by_ref_urls',
212 key = [user.profile.uuid.hex, podcast_url, episode_url],
213 limit = 1,
214 include_docs=True,
215 schema = EpisodeUserState,
218 if state:
219 state.ref_url = episode_url
220 state.podcast_ref_url = podcast_url
221 cache.set(cache_key, state, 60*60)
222 return state
224 else:
225 podcast = Podcast.objects.get_or_create_for_url(podcast_url)
226 episode = Episode.objects.get_or_create_for_url(podcast, episode_url)
227 return episode_state_for_user_episode(user, episode)
231 def get_episode_actions(user_id, since=None, until={}, podcast_id=None,
232 device_id=None, limit=1000):
233 """ Returns Episode Actions for the given criteria
235 There is an upper limit on how many actions will be returned; until is the
236 timestamp of the last episode action.
239 if not user_id:
240 raise QueryParameterMissing('user_id')
242 if since >= until:
243 return [], until
245 if not podcast_id and not device_id:
246 view = 'episode_actions/by_user'
247 startkey = [user_id, since]
248 endkey = [user_id, until]
250 elif podcast_id and not device_id:
251 view = 'episode_actions/by_podcast'
252 startkey = [user_id, podcast_id, since]
253 endkey = [user_id, podcast_id, until]
255 elif device_id and not podcast_id:
256 view = 'episode_actions/by_device'
257 startkey = [user_id, device_id, since]
258 endkey = [user_id, device_id, until]
260 else:
261 view = 'episode_actions/by_podcast_device'
262 startkey = [user_id, podcast_id, device_id, since]
263 endkey = [user_id, podcast_id, device_id, until]
265 udb = get_userdata_database()
266 res = udb.view(view,
267 startkey = startkey,
268 endkey = endkey,
269 limit = limit,
272 results = list(res)
273 actions = map(lambda r: r['value'], results)
274 if actions:
275 # the upload_timestamp is always the last part of the key
276 until = results[-1]['key'][-1]
278 return actions, until
282 @cache_result(timeout=60*60)
283 def episode_states_count():
284 udb = get_userdata_database()
285 r = udb.view('episode_states/by_user_episode',
286 limit = 0,
287 stale = 'update_after',
289 return r.total_rows
292 def get_nth_episode_state(n):
293 udb = get_userdata_database()
294 state = get_single_result(udb, 'episode_states/by_user_episode',
295 skip = n,
296 include_docs = True,
297 limit = 1,
298 schema = EpisodeUserState,
301 return state
304 def get_duplicate_episode_states(user, episode):
306 if not user:
307 raise QueryParameterMissing('user')
309 if not episode:
310 raise QueryParameterMissing('episode')
312 udb = get_userdata_database()
313 r = udb.view('episode_states/by_user_episode',
314 key = [user, episode],
315 include_docs = True,
316 schema = EpisodeUserState,
319 states = list(r)
321 for state in states:
322 state.set_db(udb)
324 return 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 udb = get_userdata_database()
343 group_level = len(filter(None, [podcast_id, episode_id, user_id]))
345 r = udb.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',
353 # TODO: Heatmap not available during transition to Django ORM
354 r = False
356 if not r:
357 return [], []
359 else:
360 res = r.first()['value']
361 return res['heatmap'], res['borders']
364 @repeat_on_conflict(['state'])
365 def add_episode_actions(state, actions):
366 udb = get_userdata_database()
367 state.add_actions(actions)
368 udb.save_doc(state)
371 @repeat_on_conflict(['state'])
372 def update_episode_state_object(state, podcast_id, episode_id=None):
373 state.podcast = podcast_id
375 if episode_id is not None:
376 state.episode = episode_id
378 udb = get_userdata_database()
379 udb.save_doc(state)
382 @repeat_on_conflict(['state'])
383 def merge_episode_states(state, state2):
384 state.add_actions(state2.actions)
386 # overwrite settings in state2 with state's settings
387 settings = state2.settings
388 settings.update(state.settings)
389 state.settings = settings
391 merged_ids = set(state.merged_ids + [state2._id] + state2.merged_ids)
392 state.merged_ids = filter(None, merged_ids)
394 state.chapters = list(set(state.chapters + state2.chapters))
396 udb = get_userdata_database()
397 udb.save_doc(state)
400 @repeat_on_conflict(['state'])
401 def delete_episode_state(state):
402 udb = get_userdata_database()
403 udb.delete_doc(state)
406 @repeat_on_conflict(['episode_state'])
407 def update_episode_chapters(episode_state, add=[], rem=[]):
408 """ Updates the Chapter list
410 * add contains the chapters to be added
412 * rem contains tuples of (start, end) times. Chapters that match
413 both endpoints will be removed
416 for chapter in add:
417 episode_state.chapters = episode_state.chapters + [chapter]
419 for start, end in rem:
420 keep = lambda c: c.start != start or c.end != end
421 episode_state.chapters = filter(keep, episode_state.chapters)
423 episode_state.save()
426 def chapters_for_episode(episode_id):
428 if not episode_id:
429 raise QueryParameterMissing('episode_id')
431 udb = get_userdata_database()
432 r = udb.view('chapters/by_episode',
433 startkey = [episode_id, None],
434 endkey = [episode_id, {}],
437 return map(_wrap_chapter, r)
440 def _wrap_chapter(res):
441 from mygpo.users.models import Chapter
442 user = res['key'][1]
443 chapter = Chapter.wrap(res['value'])
444 udb = get_userdata_database()
445 chapter.set_db(udb)
446 return (user, chapter)