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
16 def episode_state_for_user_episode(user
, episode
):
19 raise QueryParameterMissing('user')
22 raise QueryParameterMissing('episode')
25 key
= 'episode-state-userid-%s-episodeid-%s' % (sha1(user
._id
).hexdigest(),
26 sha1(episode
._id
).hexdigest())
28 state
= cache
.get(key
)
32 r
= EpisodeUserState
.view('episode_states/by_user_episode',
33 key
= [user
._id
, episode
._id
],
44 podcast
= podcast_by_id(episode
.podcast
)
46 state
= EpisodeUserState()
47 state
.episode
= episode
._id
48 state
.podcast
= episode
.podcast
50 state
.ref_url
= episode
.url
51 state
.podcast_ref_url
= podcast
.url
52 # don't cache here, because the state is saved by the calling function
58 def all_episode_states(episode
):
61 raise QueryParameterMissing('episode')
63 r
= EpisodeUserState
.view('episode_states/by_podcast_episode',
64 startkey
= [episode
.podcast
, episode
._id
, None],
65 endkey
= [episode
.podcast
, episode
._id
, {}],
72 def all_podcast_episode_states(podcast
):
75 raise QueryParameterMissing('podcast')
77 r
= EpisodeUserState
.view('episode_states/by_podcast_episode',
78 startkey
= [podcast
.get_id(), None, None],
79 endkey
= [podcast
.get_id(), {}, {}],
86 @cache_result(timeout
=60*60)
87 def podcast_listener_count(episode
):
88 """ returns the number of users that have listened to this podcast """
91 raise QueryParameterMissing('episode')
93 r
= EpisodeUserState
.view('listeners/by_podcast',
94 startkey
= [episode
.get_id(), None],
95 endkey
= [episode
.get_id(), {}],
100 return r
.first()['value'] if r
else 0
103 @cache_result(timeout
=60*60)
104 def podcast_listener_count_timespan(podcast
, start
=None, end
={}):
105 """ returns (date, listener-count) tuples for all days w/ listeners """
108 raise QueryParameterMissing('podcast')
110 if isinstance(start
, datetime
):
111 start
= start
.isoformat()
113 if isinstance(end
, datetime
):
114 end
= end
.isoformat()
116 r
= EpisodeUserState
.view('listeners/by_podcast',
117 startkey
= [podcast
.get_id(), start
],
118 endkey
= [podcast
.get_id(), end
],
124 return map(_wrap_listener_count
, r
)
127 @cache_result(timeout
=60*60)
128 def episode_listener_counts(episode
):
129 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
132 raise QueryParameterMissing('episode')
135 r
= EpisodeUserState
.view('listeners/by_podcast_episode',
136 startkey
= [episode
.get_id(), None, None],
137 endkey
= [episode
.get_id(), {}, {}],
143 return map(_wrap_listeners
, r
)
147 def get_podcasts_episode_states(podcast
, user_id
):
148 """ Returns the latest episode actions for the podcast's episodes """
151 raise QueryParameterMissing('podcast')
154 raise QueryParameterMissing('user_id')
157 db
= get_main_database()
158 res
= db
.view('episode_states/by_user_podcast',
159 startkey
= [user_id
, podcast
.get_id(), None],
160 endkey
= [user_id
, podcast
.get_id(), {}],
163 return map(lambda r
: r
['value'], res
)
167 @cache_result(timeout
=60*60)
168 def episode_listener_count(episode
, start
=None, end
={}):
169 """ returns the number of users that have listened to this episode """
172 raise QueryParameterMissing('episode')
175 r
= EpisodeUserState
.view('listeners/by_episode',
176 startkey
= [episode
._id
, start
],
177 endkey
= [episode
._id
, end
],
182 return r
.first()['value'] if r
else 0
186 @cache_result(timeout
=60*60)
187 def episode_listener_count_timespan(episode
, start
=None, end
={}):
188 """ returns (date, listener-count) tuples for all days w/ listeners """
191 raise QueryParameterMissing('episode')
194 if isinstance(start
, datetime
):
195 start
= start
.isoformat()
197 if isinstance(end
, datetime
):
198 end
= end
.isoformat()
200 r
= EpisodeUserState
.view('listeners/by_episode',
201 startkey
= [episode
._id
, start
],
202 endkey
= [episode
._id
, end
],
208 return map(_wrap_listener_count
, r
)
212 def episode_state_for_ref_urls(user
, podcast_url
, episode_url
):
215 raise QueryParameterMissing('user')
218 raise QueryParameterMissing('podcast_url')
221 raise QueryParameterMissing('episode_url')
224 cache_key
= 'episode-state-%s-%s-%s' % (user
._id
,
225 sha1(podcast_url
).hexdigest(),
226 sha1(episode_url
).hexdigest())
228 state
= cache
.get(cache_key
)
232 res
= EpisodeUserState
.view('episode_states/by_ref_urls',
233 key
= [user
._id
, podcast_url
, episode_url
],
240 state
.ref_url
= episode_url
241 state
.podcast_ref_url
= podcast_url
242 cache
.set(cache_key
, state
, 60*60)
246 podcast
= podcast_for_url(podcast_url
, create
=True)
247 episode
= episode_for_podcast_id_url(podcast
.get_id(), episode_url
,
249 return episode_state_for_user_episode(user
, episode
)
253 def get_episode_actions(user_id
, since
=None, until
={}, podcast_id
=None,
255 """ Returns Episode Actions for the given criteria"""
258 raise QueryParameterMissing('user_id')
261 since_str
= since
.strftime('%Y-%m-%dT%H:%M:%S') if since
else None
262 until_str
= until
.strftime('%Y-%m-%dT%H:%M:%S') if until
else {}
264 if since_str
>= until_str
:
267 if not podcast_id
and not device_id
:
268 view
= 'episode_actions/by_user'
269 startkey
= [user_id
, since_str
]
270 endkey
= [user_id
, until_str
]
272 elif podcast_id
and not device_id
:
273 view
= 'episode_actions/by_podcast'
274 startkey
= [user_id
, podcast_id
, since_str
]
275 endkey
= [user_id
, podcast_id
, until_str
]
277 elif device_id
and not podcast_id
:
278 view
= 'episode_actions/by_device'
279 startkey
= [user_id
, device_id
, since_str
]
280 endkey
= [user_id
, device_id
, until_str
]
283 view
= 'episode_actions/by_podcast_device'
284 startkey
= [user_id
, podcast_id
, device_id
, since_str
]
285 endkey
= [user_id
, podcast_id
, device_id
, until_str
]
287 db
= get_main_database()
293 return map(lambda r
: r
['value'], res
)
297 @cache_result(timeout
=60*60)
298 def episode_states_count():
299 r
= cls
.view('episode_states/by_user_episode',
301 stale
= 'update_after',
306 def get_nth_episode_state(n
):
307 first
= EpisodeUserState
.view('episode_states/by_user_episode',
312 return first
.one() if first
else None
315 def get_duplicate_episode_states(user
, episode
):
318 raise QueryParameterMissing('user')
321 raise QueryParameterMissing('episode')
323 states
= EpisodeUserState
.view('episode_states/by_user_episode',
324 key
= [user
, episode
],
330 def _wrap_listener_count(res
):
331 date
= parser
.parse(res
['key'][1]).date()
332 listeners
= res
['value']
333 return (date
, listeners
)
336 def _wrap_listeners(res
):
337 episode
= res
['key'][1]
338 listeners
= res
['value']
339 return (episode
, listeners
)
342 @cache_result(timeout
=60*60)
343 def get_heatmap(podcast_id
, episode_id
, user_id
):
344 db
= get_main_database()
346 group_level
= len(filter(None, [podcast_id
, episode_id
, user_id
]))
348 r
= db
.view('heatmap/by_episode',
349 startkey
= [podcast_id
, episode_id
, user_id
],
350 endkey
= [podcast_id
, episode_id
or {}, user_id
or {}],
353 group_level
= group_level
,
354 stale
= 'update_after',
361 res
= r
.first()['value']
362 return res
['heatmap'], res
['borders']