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
12 from mygpo
.cache
import cache_result
13 from mygpo
.decorators
import repeat_on_conflict
17 def episode_state_for_user_episode(user
, episode
):
20 raise QueryParameterMissing('user')
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
)
33 r
= EpisodeUserState
.view('episode_states/by_user_episode',
34 key
= [user
._id
, episode
._id
],
45 podcast
= podcast_by_id(episode
.podcast
)
47 state
= EpisodeUserState()
48 state
.episode
= episode
._id
49 state
.podcast
= episode
.podcast
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
59 def all_episode_states(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
, {}],
73 def all_podcast_episode_states(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(), {}, {}],
87 @cache_result(timeout
=60*60)
88 def podcast_listener_count(episode
):
89 """ returns the number of users that have listened to this podcast """
92 raise QueryParameterMissing('episode')
94 r
= EpisodeUserState
.view('listeners/by_podcast',
95 startkey
= [episode
.get_id(), None],
96 endkey
= [episode
.get_id(), {}],
100 stale
= 'update_after',
102 return r
.first()['value'] if r
else 0
105 @cache_result(timeout
=60*60)
106 def podcast_listener_count_timespan(podcast
, start
=None, end
={}):
107 """ returns (date, listener-count) tuples for all days w/ listeners """
110 raise QueryParameterMissing('podcast')
112 if isinstance(start
, datetime
):
113 start
= start
.isoformat()
115 if isinstance(end
, datetime
):
116 end
= end
.isoformat()
118 r
= EpisodeUserState
.view('listeners/by_podcast',
119 startkey
= [podcast
.get_id(), start
],
120 endkey
= [podcast
.get_id(), end
],
124 stale
= 'update_after',
127 return map(_wrap_listener_count
, r
)
130 @cache_result(timeout
=60*60)
131 def episode_listener_counts(episode
):
132 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
135 raise QueryParameterMissing('episode')
138 r
= EpisodeUserState
.view('listeners/by_podcast_episode',
139 startkey
= [episode
.get_id(), None, None],
140 endkey
= [episode
.get_id(), {}, {}],
144 stale
= 'update_after',
147 return map(_wrap_listeners
, r
)
151 def get_podcasts_episode_states(podcast
, user_id
):
152 """ Returns the latest episode actions for the podcast's episodes """
155 raise QueryParameterMissing('podcast')
158 raise QueryParameterMissing('user_id')
161 db
= get_main_database()
162 res
= db
.view('episode_states/by_user_podcast',
163 startkey
= [user_id
, podcast
.get_id(), None],
164 endkey
= [user_id
, podcast
.get_id(), {}],
167 return map(lambda r
: r
['value'], res
)
171 @cache_result(timeout
=60*60)
172 def episode_listener_count(episode
, start
=None, end
={}):
173 """ returns the number of users that have listened to this episode """
176 raise QueryParameterMissing('episode')
179 r
= EpisodeUserState
.view('listeners/by_episode',
180 startkey
= [episode
._id
, start
],
181 endkey
= [episode
._id
, end
],
185 stale
= 'update_after',
187 return r
.first()['value'] if r
else 0
191 @cache_result(timeout
=60*60)
192 def episode_listener_count_timespan(episode
, start
=None, end
={}):
193 """ returns (date, listener-count) tuples for all days w/ listeners """
196 raise QueryParameterMissing('episode')
199 if isinstance(start
, datetime
):
200 start
= start
.isoformat()
202 if isinstance(end
, datetime
):
203 end
= end
.isoformat()
205 r
= EpisodeUserState
.view('listeners/by_episode',
206 startkey
= [episode
._id
, start
],
207 endkey
= [episode
._id
, end
],
211 stale
= 'update_after',
214 return map(_wrap_listener_count
, r
)
218 def episode_state_for_ref_urls(user
, podcast_url
, episode_url
):
221 raise QueryParameterMissing('user')
224 raise QueryParameterMissing('podcast_url')
227 raise QueryParameterMissing('episode_url')
230 cache_key
= 'episode-state-%s-%s-%s' % (user
._id
,
231 sha1(podcast_url
).hexdigest(),
232 sha1(episode_url
).hexdigest())
234 state
= cache
.get(cache_key
)
238 res
= EpisodeUserState
.view('episode_states/by_ref_urls',
239 key
= [user
._id
, podcast_url
, episode_url
],
246 state
.ref_url
= episode_url
247 state
.podcast_ref_url
= podcast_url
248 cache
.set(cache_key
, state
, 60*60)
252 podcast
= podcast_for_url(podcast_url
, create
=True)
253 episode
= episode_for_podcast_id_url(podcast
.get_id(), episode_url
,
255 return episode_state_for_user_episode(user
, episode
)
259 def get_episode_actions(user_id
, since
=None, until
={}, podcast_id
=None,
261 """ Returns Episode Actions for the given criteria"""
264 raise QueryParameterMissing('user_id')
269 if not podcast_id
and not device_id
:
270 view
= 'episode_actions/by_user'
271 startkey
= [user_id
, since
]
272 endkey
= [user_id
, until
]
274 elif podcast_id
and not device_id
:
275 view
= 'episode_actions/by_podcast'
276 startkey
= [user_id
, podcast_id
, since
]
277 endkey
= [user_id
, podcast_id
, until
]
279 elif device_id
and not podcast_id
:
280 view
= 'episode_actions/by_device'
281 startkey
= [user_id
, device_id
, since
]
282 endkey
= [user_id
, device_id
, until
]
285 view
= 'episode_actions/by_podcast_device'
286 startkey
= [user_id
, podcast_id
, device_id
, since
]
287 endkey
= [user_id
, podcast_id
, device_id
, until
]
289 db
= get_main_database()
295 return map(lambda r
: r
['value'], res
)
299 @cache_result(timeout
=60*60)
300 def episode_states_count():
301 r
= EpisodeUserState
.view('episode_states/by_user_episode',
303 stale
= 'update_after',
308 def get_nth_episode_state(n
):
309 first
= EpisodeUserState
.view('episode_states/by_user_episode',
314 return first
.one() if first
else None
317 def get_duplicate_episode_states(user
, episode
):
320 raise QueryParameterMissing('user')
323 raise QueryParameterMissing('episode')
325 states
= EpisodeUserState
.view('episode_states/by_user_episode',
326 key
= [user
, episode
],
332 def _wrap_listener_count(res
):
333 date
= parser
.parse(res
['key'][1]).date()
334 listeners
= res
['value']
335 return (date
, listeners
)
338 def _wrap_listeners(res
):
339 episode
= res
['key'][1]
340 listeners
= res
['value']
341 return (episode
, listeners
)
344 @cache_result(timeout
=60*60)
345 def get_heatmap(podcast_id
, episode_id
, user_id
):
346 db
= get_main_database()
348 group_level
= len(filter(None, [podcast_id
, episode_id
, user_id
]))
350 r
= db
.view('heatmap/by_episode',
351 startkey
= [podcast_id
, episode_id
, user_id
],
352 endkey
= [podcast_id
, episode_id
or {}, user_id
or {}],
355 group_level
= group_level
,
356 stale
= 'update_after',
363 res
= r
.first()['value']
364 return res
['heatmap'], res
['borders']
367 @repeat_on_conflict(['state'])
368 def add_episode_actions(state
, actions
):
369 state
.add_actions(actions
)