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
):
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 # Disabled as cache invalidation does not work properly
30 # state = cache.get(key)
34 udb
= get_userdata_database()
35 r
= udb
.view('episode_states/by_user_episode',
36 key
= [user
._id
, episode
._id
],
39 schema
= EpisodeUserState
,
49 podcast
= podcast_by_id(episode
.podcast
)
51 state
= EpisodeUserState()
52 state
.episode
= episode
._id
53 state
.podcast
= episode
.podcast
55 state
.ref_url
= episode
.url
56 state
.podcast_ref_url
= podcast
.url
57 # don't cache here, because the state is saved by the calling function
63 def all_episode_states(episode
):
66 raise QueryParameterMissing('episode')
68 udb
= get_userdata_database()
69 r
= udb
.view('episode_states/by_podcast_episode',
70 startkey
= [episode
.podcast
, episode
._id
, None],
71 endkey
= [episode
.podcast
, episode
._id
, {}],
73 schema
= EpisodeUserState
,
85 def all_podcast_episode_states(podcast
):
88 raise QueryParameterMissing('podcast')
90 udb
= get_userdata_database()
91 r
= udb
.view('episode_states/by_podcast_episode',
92 startkey
= [podcast
.get_id(), None, None],
93 endkey
= [podcast
.get_id(), {}, {}],
95 schema
= EpisodeUserState
,
107 @cache_result(timeout
=60*60)
108 def podcast_listener_count(episode
):
109 """ returns the number of users that have listened to this podcast """
112 raise QueryParameterMissing('episode')
114 udb
= get_userdata_database()
115 r
= udb
.view('listeners/by_podcast',
116 startkey
= [episode
.get_id(), None],
117 endkey
= [episode
.get_id(), {}],
121 stale
= 'update_after',
123 return r
.first()['value'] if r
else 0
126 @cache_result(timeout
=60*60)
127 def podcast_listener_count_timespan(podcast
, start
=None, end
={}):
128 """ returns (date, listener-count) tuples for all days w/ listeners """
131 raise QueryParameterMissing('podcast')
133 if isinstance(start
, datetime
):
134 start
= start
.isoformat()
136 if isinstance(end
, datetime
):
137 end
= end
.isoformat()
139 udb
= get_userdata_database()
140 r
= udb
.view('listeners/by_podcast',
141 startkey
= [podcast
.get_id(), start
],
142 endkey
= [podcast
.get_id(), end
],
146 stale
= 'update_after',
149 return map(_wrap_listener_count
, r
)
152 @cache_result(timeout
=60*60)
153 def episode_listener_counts(episode
):
154 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
157 raise QueryParameterMissing('episode')
159 udb
= get_userdata_database()
160 r
= udb
.view('listeners/by_podcast_episode',
161 startkey
= [episode
.get_id(), None, None],
162 endkey
= [episode
.get_id(), {}, {}],
166 stale
= 'update_after',
169 return map(_wrap_listeners
, r
)
173 def get_podcasts_episode_states(podcast
, user_id
):
174 """ Returns the latest episode actions for the podcast's episodes """
177 raise QueryParameterMissing('podcast')
180 raise QueryParameterMissing('user_id')
182 udb
= get_userdata_database()
183 res
= udb
.view('episode_states/by_user_podcast',
184 startkey
= [user_id
, podcast
.get_id(), None],
185 endkey
= [user_id
, podcast
.get_id(), {}],
188 return map(lambda r
: r
['value'], res
)
192 @cache_result(timeout
=60*60)
193 def episode_listener_count(episode
, start
=None, end
={}):
194 """ returns the number of users that have listened to this episode """
197 raise QueryParameterMissing('episode')
199 udb
= get_userdata_database()
200 r
= udb
.view('listeners/by_episode',
201 startkey
= [episode
._id
, start
],
202 endkey
= [episode
._id
, end
],
206 stale
= 'update_after',
208 return r
.first()['value'] if r
else 0
212 @cache_result(timeout
=60*60)
213 def episode_listener_count_timespan(episode
, start
=None, end
={}):
214 """ returns (date, listener-count) tuples for all days w/ listeners """
217 raise QueryParameterMissing('episode')
220 if isinstance(start
, datetime
):
221 start
= start
.isoformat()
223 if isinstance(end
, datetime
):
224 end
= end
.isoformat()
226 udb
= get_userdata_database()
227 r
= udb
.view('listeners/by_episode',
228 startkey
= [episode
._id
, start
],
229 endkey
= [episode
._id
, end
],
233 stale
= 'update_after',
236 return map(_wrap_listener_count
, r
)
240 def episode_state_for_ref_urls(user
, podcast_url
, episode_url
):
243 raise QueryParameterMissing('user')
246 raise QueryParameterMissing('podcast_url')
249 raise QueryParameterMissing('episode_url')
252 cache_key
= 'episode-state-%s-%s-%s' % (user
._id
,
253 sha1(podcast_url
).hexdigest(),
254 sha1(episode_url
).hexdigest())
256 state
= cache
.get(cache_key
)
260 udb
= get_userdata_database()
261 res
= udb
.view('episode_states/by_ref_urls',
262 key
= [user
._id
, podcast_url
, episode_url
],
265 schema
= EpisodeUserState
,
270 state
.ref_url
= episode_url
271 state
.podcast_ref_url
= podcast_url
273 cache
.set(cache_key
, state
, 60*60)
277 podcast
= podcast_for_url(podcast_url
, create
=True)
278 episode
= episode_for_podcast_id_url(podcast
.get_id(), episode_url
,
280 return episode_state_for_user_episode(user
, episode
)
284 def get_episode_actions(user_id
, since
=None, until
={}, podcast_id
=None,
286 """ Returns Episode Actions for the given criteria"""
289 raise QueryParameterMissing('user_id')
294 if not podcast_id
and not device_id
:
295 view
= 'episode_actions/by_user'
296 startkey
= [user_id
, since
]
297 endkey
= [user_id
, until
]
299 elif podcast_id
and not device_id
:
300 view
= 'episode_actions/by_podcast'
301 startkey
= [user_id
, podcast_id
, since
]
302 endkey
= [user_id
, podcast_id
, until
]
304 elif device_id
and not podcast_id
:
305 view
= 'episode_actions/by_device'
306 startkey
= [user_id
, device_id
, since
]
307 endkey
= [user_id
, device_id
, until
]
310 view
= 'episode_actions/by_podcast_device'
311 startkey
= [user_id
, podcast_id
, device_id
, since
]
312 endkey
= [user_id
, podcast_id
, device_id
, until
]
314 udb
= get_userdata_database()
320 return map(lambda r
: r
['value'], res
)
324 @cache_result(timeout
=60*60)
325 def episode_states_count():
326 udb
= get_userdata_database()
327 r
= udb
.view('episode_states/by_user_episode',
329 stale
= 'update_after',
334 def get_nth_episode_state(n
):
335 udb
= get_userdata_database()
336 first
= udb
.view('episode_states/by_user_episode',
340 schema
= EpisodeUserState
,
352 def get_duplicate_episode_states(user
, episode
):
355 raise QueryParameterMissing('user')
358 raise QueryParameterMissing('episode')
360 udb
= get_userdata_database()
361 r
= udb
.view('episode_states/by_user_episode',
362 key
= [user
, episode
],
364 schema
= EpisodeUserState
,
375 def _wrap_listener_count(res
):
376 date
= parser
.parse(res
['key'][1]).date()
377 listeners
= res
['value']
378 return (date
, listeners
)
381 def _wrap_listeners(res
):
382 episode
= res
['key'][1]
383 listeners
= res
['value']
384 return (episode
, listeners
)
387 @cache_result(timeout
=60*60)
388 def get_heatmap(podcast_id
, episode_id
, user_id
):
389 udb
= get_userdata_database()
391 group_level
= len(filter(None, [podcast_id
, episode_id
, user_id
]))
393 r
= udb
.view('heatmap/by_episode',
394 startkey
= [podcast_id
, episode_id
, user_id
],
395 endkey
= [podcast_id
, episode_id
or {}, user_id
or {}],
398 group_level
= group_level
,
399 stale
= 'update_after',
406 res
= r
.first()['value']
407 return res
['heatmap'], res
['borders']
410 @repeat_on_conflict(['state'])
411 def add_episode_actions(state
, actions
):
412 udb
= get_userdata_database()
413 state
.add_actions(actions
)
417 @repeat_on_conflict(['state'])
418 def update_episode_state_object(state
, podcast_id
, episode_id
=None):
419 state
.podcast
= podcast_id
421 if episode_id
is not None:
422 state
.episode
= episode_id
424 udb
= get_userdata_database()
428 @repeat_on_conflict(['state'])
429 def merge_episode_states(state
, state2
):
430 state
.add_actions(state2
.actions
)
432 # overwrite settings in state2 with state's settings
433 settings
= state2
.settings
434 settings
.update(state
.settings
)
435 state
.settings
= settings
437 merged_ids
= set(state
.merged_ids
+ [state2
._id
] + state2
.merged_ids
)
438 state
.merged_ids
= filter(None, merged_ids
)
440 state
.chapters
= list(set(state
.chapters
+ state2
.chapters
))
442 udb
= get_userdata_database()
446 @repeat_on_conflict(['state'])
447 def delete_episode_state(state
):
448 udb
= get_userdata_database()
449 udb
.delete_doc(state
)
452 @repeat_on_conflict(['episode_state'])
453 def update_episode_chapters(episode_state
, add
=[], rem
=[]):
454 """ Updates the Chapter list
456 * add contains the chapters to be added
458 * rem contains tuples of (start, end) times. Chapters that match
459 both endpoints will be removed
463 episode_state
.chapters
= episode_state
.chapters
+ [chapter
]
465 for start
, end
in rem
:
466 keep
= lambda c
: c
.start
!= start
or c
.end
!= end
467 episode_state
.chapters
= filter(keep
, episode_state
.chapters
)