add Flattr and auto-flattr support
[mygpo.git] / mygpo / db / couchdb / episode_state.py
blob249b91892b687b84432464056449fdf94b9e6a23
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.couch 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,
101 return r.first()['value'] if r else 0
104 @cache_result(timeout=60*60)
105 def podcast_listener_count_timespan(podcast, start=None, end={}):
106 """ returns (date, listener-count) tuples for all days w/ listeners """
108 if not podcast:
109 raise QueryParameterMissing('podcast')
111 if isinstance(start, datetime):
112 start = start.isoformat()
114 if isinstance(end, datetime):
115 end = end.isoformat()
117 r = EpisodeUserState.view('listeners/by_podcast',
118 startkey = [podcast.get_id(), start],
119 endkey = [podcast.get_id(), end],
120 group = True,
121 group_level = 2,
122 reduce = True,
125 return map(_wrap_listener_count, r)
128 @cache_result(timeout=60*60)
129 def episode_listener_counts(episode):
130 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
132 if not episode:
133 raise QueryParameterMissing('episode')
136 r = EpisodeUserState.view('listeners/by_podcast_episode',
137 startkey = [episode.get_id(), None, None],
138 endkey = [episode.get_id(), {}, {}],
139 group = True,
140 group_level = 2,
141 reduce = True,
144 return map(_wrap_listeners, r)
148 def get_podcasts_episode_states(podcast, user_id):
149 """ Returns the latest episode actions for the podcast's episodes """
151 if not podcast:
152 raise QueryParameterMissing('podcast')
154 if not user_id:
155 raise QueryParameterMissing('user_id')
158 db = get_main_database()
159 res = db.view('episode_states/by_user_podcast',
160 startkey = [user_id, podcast.get_id(), None],
161 endkey = [user_id, podcast.get_id(), {}],
164 return map(lambda r: r['value'], res)
168 @cache_result(timeout=60*60)
169 def episode_listener_count(episode, start=None, end={}):
170 """ returns the number of users that have listened to this episode """
172 if not episode:
173 raise QueryParameterMissing('episode')
176 r = EpisodeUserState.view('listeners/by_episode',
177 startkey = [episode._id, start],
178 endkey = [episode._id, end],
179 group = True,
180 group_level = 2,
181 reduce = True,
183 return r.first()['value'] if r else 0
187 @cache_result(timeout=60*60)
188 def episode_listener_count_timespan(episode, start=None, end={}):
189 """ returns (date, listener-count) tuples for all days w/ listeners """
191 if not episode:
192 raise QueryParameterMissing('episode')
195 if isinstance(start, datetime):
196 start = start.isoformat()
198 if isinstance(end, datetime):
199 end = end.isoformat()
201 r = EpisodeUserState.view('listeners/by_episode',
202 startkey = [episode._id, start],
203 endkey = [episode._id, end],
204 group = True,
205 group_level = 3,
206 reduce = True,
209 return map(_wrap_listener_count, r)
213 def episode_state_for_ref_urls(user, podcast_url, episode_url):
215 if not user:
216 raise QueryParameterMissing('user')
218 if not podcast_url:
219 raise QueryParameterMissing('podcast_url')
221 if not episode_url:
222 raise QueryParameterMissing('episode_url')
225 cache_key = 'episode-state-%s-%s-%s' % (user._id,
226 sha1(podcast_url).hexdigest(),
227 sha1(episode_url).hexdigest())
229 state = cache.get(cache_key)
230 if state:
231 return state
233 res = EpisodeUserState.view('episode_states/by_ref_urls',
234 key = [user._id, podcast_url, episode_url],
235 limit = 1,
236 include_docs=True,
239 if res:
240 state = res.first()
241 state.ref_url = episode_url
242 state.podcast_ref_url = podcast_url
243 cache.set(cache_key, state, 60*60)
244 return state
246 else:
247 podcast = podcast_for_url(podcast_url, create=True)
248 episode = episode_for_podcast_id_url(podcast.get_id(), episode_url,
249 create=True)
250 return episode_state_for_user_episode(user, episode)
254 def get_episode_actions(user_id, since=None, until={}, podcast_id=None,
255 device_id=None):
256 """ Returns Episode Actions for the given criteria"""
258 if not user_id:
259 raise QueryParameterMissing('user_id')
262 since_str = since.strftime('%Y-%m-%dT%H:%M:%S') if since else None
263 until_str = until.strftime('%Y-%m-%dT%H:%M:%S') if until else {}
265 if since_str >= until_str:
266 return []
268 if not podcast_id and not device_id:
269 view = 'episode_actions/by_user'
270 startkey = [user_id, since_str]
271 endkey = [user_id, until_str]
273 elif podcast_id and not device_id:
274 view = 'episode_actions/by_podcast'
275 startkey = [user_id, podcast_id, since_str]
276 endkey = [user_id, podcast_id, until_str]
278 elif device_id and not podcast_id:
279 view = 'episode_actions/by_device'
280 startkey = [user_id, device_id, since_str]
281 endkey = [user_id, device_id, until_str]
283 else:
284 view = 'episode_actions/by_podcast_device'
285 startkey = [user_id, podcast_id, device_id, since_str]
286 endkey = [user_id, podcast_id, device_id, until_str]
288 db = get_main_database()
289 res = db.view(view,
290 startkey = startkey,
291 endkey = endkey
294 return map(lambda r: r['value'], res)
298 @cache_result(timeout=60*60)
299 def episode_states_count():
300 r = cls.view('episode_states/by_user_episode',
301 limit = 0,
302 stale = 'update_after',
304 return r.total_rows
307 def get_nth_episode_state(n):
308 first = EpisodeUserState.view('episode_states/by_user_episode',
309 skip = n,
310 include_docs = True,
311 limit = 1,
313 return first.one() if first else None
316 def get_duplicate_episode_states(user, episode):
318 if not user:
319 raise QueryParameterMissing('user')
321 if not episode:
322 raise QueryParameterMissing('episode')
324 states = EpisodeUserState.view('episode_states/by_user_episode',
325 key = [user, episode],
326 include_docs = True,
328 return list(states)
331 def _wrap_listener_count(res):
332 date = parser.parse(res['key'][1]).date()
333 listeners = res['value']
334 return (date, listeners)
337 def _wrap_listeners(res):
338 episode = res['key'][1]
339 listeners = res['value']
340 return (episode, listeners)
343 @cache_result(timeout=60*60)
344 def get_heatmap(podcast_id, episode_id, user_id):
345 db = get_main_database()
347 group_level = len(filter(None, [podcast_id, episode_id, user_id]))
349 r = db.view('heatmap/by_episode',
350 startkey = [podcast_id, episode_id, user_id],
351 endkey = [podcast_id, episode_id or {}, user_id or {}],
352 reduce = True,
353 group = True,
354 group_level = group_level,
355 stale = 'update_after',
358 if not r:
359 return [], []
361 else:
362 res = r.first()['value']
363 return res['heatmap'], res['borders']
366 @repeat_on_conflict(['state'])
367 def add_episode_actions(state, actions):
368 state.add_actions(actions)
369 state.save()