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_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')
25 if hasattr(episode
, '_id'):
26 episode_id
= episode
._id
28 episode_id
= episode
.get_id()
29 key
= 'episode-state-userid-%s-episodeid-%s' % (sha1(user
._id
).hexdigest(),
30 sha1(episode_id
).hexdigest())
32 # Disabled as cache invalidation does not work properly
33 # state = cache.get(key)
37 udb
= get_userdata_database()
38 state
= get_single_result(udb
, 'episode_states/by_user_episode',
39 key
= [user
._id
, episode_id
],
42 schema
= EpisodeUserState
,
50 if isinstance(episode
.podcast
, unicode):
51 podcast
= Podcast
.objects
.get_by_any_id(episode
.podcast
)
53 podcast
= episode
.podcast
55 state
= EpisodeUserState()
56 state
.episode
= episode_id
57 state
.podcast
= podcast
.get_id()
59 state
.ref_url
= episode
.url
60 state
.podcast_ref_url
= podcast
.url
61 # don't cache here, because the state is saved by the calling function
67 def all_episode_states(episode
):
70 raise QueryParameterMissing('episode')
72 if isinstance(episode
.podcast
, unicode):
73 podcast_id
= episode
.podcast
75 podcast_id
= episode
.podcast
.get_id()
77 if hasattr(episode
, '_id'):
78 episode_id
= episode
._id
80 episode_id
= episode
.get_id()
82 udb
= get_userdata_database()
83 r
= udb
.view('episode_states/by_podcast_episode',
84 startkey
= [podcast_id
, episode_id
, None],
85 endkey
= [podcast_id
, episode_id
, {}],
87 schema
= EpisodeUserState
,
99 def all_podcast_episode_states(podcast
):
102 raise QueryParameterMissing('podcast')
104 udb
= get_userdata_database()
105 r
= udb
.view('episode_states/by_podcast_episode',
106 startkey
= [podcast
.get_id(), None, None],
107 endkey
= [podcast
.get_id(), {}, {}],
109 schema
= EpisodeUserState
,
121 @cache_result(timeout
=60*60)
122 def podcast_listener_count(episode
):
123 """ returns the number of users that have listened to this podcast """
126 raise QueryParameterMissing('episode')
128 udb
= get_userdata_database()
129 r
= get_single_result(udb
, 'listeners/by_podcast',
130 startkey
= [episode
.get_id(), None],
131 endkey
= [episode
.get_id(), {}],
135 stale
= 'update_after',
137 return r
['value'] if r
else 0
140 @cache_result(timeout
=60*60)
141 def podcast_listener_count_timespan(podcast
, start
=None, end
={}):
142 """ returns (date, listener-count) tuples for all days w/ listeners """
145 raise QueryParameterMissing('podcast')
147 if isinstance(start
, datetime
):
148 start
= start
.isoformat()
150 if isinstance(end
, datetime
):
151 end
= end
.isoformat()
153 udb
= get_userdata_database()
154 r
= udb
.view('listeners/by_podcast',
155 startkey
= [podcast
.get_id(), start
],
156 endkey
= [podcast
.get_id(), end
],
160 stale
= 'update_after',
163 return map(_wrap_listener_count
, r
)
166 @cache_result(timeout
=60*60)
167 def episode_listener_counts(episode
):
168 """ (Episode-Id, listener-count) tuples for episodes w/ listeners """
171 raise QueryParameterMissing('episode')
173 udb
= get_userdata_database()
174 r
= udb
.view('listeners/by_podcast_episode',
175 startkey
= [episode
.get_id(), None, None],
176 endkey
= [episode
.get_id(), {}, {}],
180 stale
= 'update_after',
183 return map(_wrap_listeners
, r
)
187 def get_podcasts_episode_states(podcast
, user_id
):
188 """ Returns the latest episode actions for the podcast's episodes """
191 raise QueryParameterMissing('podcast')
194 raise QueryParameterMissing('user_id')
196 udb
= get_userdata_database()
197 res
= udb
.view('episode_states/by_user_podcast',
198 startkey
= [user_id
, podcast
.get_id(), None],
199 endkey
= [user_id
, podcast
.get_id(), {}],
202 return map(lambda r
: r
['value'], res
)
206 @cache_result(timeout
=60*60)
207 def episode_listener_count(episode
, start
=None, end
={}):
208 """ returns the number of users that have listened to this episode """
211 raise QueryParameterMissing('episode')
213 udb
= get_userdata_database()
214 r
= get_single_result(udb
, 'listeners/by_episode',
215 startkey
= [episode
.id, start
],
216 endkey
= [episode
.id, end
],
220 stale
= 'update_after',
222 return r
['value'] if r
else 0
226 @cache_result(timeout
=60*60)
227 def episode_listener_count_timespan(episode
, start
=None, end
={}):
228 """ returns (date, listener-count) tuples for all days w/ listeners """
231 raise QueryParameterMissing('episode')
234 if isinstance(start
, datetime
):
235 start
= start
.isoformat()
237 if isinstance(end
, datetime
):
238 end
= end
.isoformat()
240 udb
= get_userdata_database()
241 r
= udb
.view('listeners/by_episode',
242 startkey
= [episode
._id
, start
],
243 endkey
= [episode
._id
, end
],
247 stale
= 'update_after',
250 return map(_wrap_listener_count
, r
)
254 def episode_state_for_ref_urls(user
, podcast_url
, episode_url
):
257 raise QueryParameterMissing('user')
260 raise QueryParameterMissing('podcast_url')
263 raise QueryParameterMissing('episode_url')
266 cache_key
= 'episode-state-%s-%s-%s' % (user
._id
,
267 sha1(podcast_url
).hexdigest(),
268 sha1(episode_url
).hexdigest())
270 state
= cache
.get(cache_key
)
274 udb
= get_userdata_database()
275 state
= get_single_result(udb
, 'episode_states/by_ref_urls',
276 key
= [user
._id
, podcast_url
, episode_url
],
279 schema
= EpisodeUserState
,
283 state
.ref_url
= episode_url
284 state
.podcast_ref_url
= podcast_url
285 cache
.set(cache_key
, state
, 60*60)
289 podcast
= Podcast
.objects
.get_or_create_for_url(podcast_url
)
290 episode
= Episode
.objects
.get_or_create_for_url(podcast
, episode_url
)
291 return episode_state_for_user_episode(user
, episode
)
295 def get_episode_actions(user_id
, since
=None, until
={}, podcast_id
=None,
297 """ Returns Episode Actions for the given criteria"""
300 raise QueryParameterMissing('user_id')
305 if not podcast_id
and not device_id
:
306 view
= 'episode_actions/by_user'
307 startkey
= [user_id
, since
]
308 endkey
= [user_id
, until
]
310 elif podcast_id
and not device_id
:
311 view
= 'episode_actions/by_podcast'
312 startkey
= [user_id
, podcast_id
, since
]
313 endkey
= [user_id
, podcast_id
, until
]
315 elif device_id
and not podcast_id
:
316 view
= 'episode_actions/by_device'
317 startkey
= [user_id
, device_id
, since
]
318 endkey
= [user_id
, device_id
, until
]
321 view
= 'episode_actions/by_podcast_device'
322 startkey
= [user_id
, podcast_id
, device_id
, since
]
323 endkey
= [user_id
, podcast_id
, device_id
, until
]
325 udb
= get_userdata_database()
331 return map(lambda r
: r
['value'], res
)
335 @cache_result(timeout
=60*60)
336 def episode_states_count():
337 udb
= get_userdata_database()
338 r
= udb
.view('episode_states/by_user_episode',
340 stale
= 'update_after',
345 def get_nth_episode_state(n
):
346 udb
= get_userdata_database()
347 state
= get_single_result(udb
, 'episode_states/by_user_episode',
351 schema
= EpisodeUserState
,
357 def get_duplicate_episode_states(user
, episode
):
360 raise QueryParameterMissing('user')
363 raise QueryParameterMissing('episode')
365 udb
= get_userdata_database()
366 r
= udb
.view('episode_states/by_user_episode',
367 key
= [user
, episode
],
369 schema
= EpisodeUserState
,
380 def _wrap_listener_count(res
):
381 date
= parser
.parse(res
['key'][1]).date()
382 listeners
= res
['value']
383 return (date
, listeners
)
386 def _wrap_listeners(res
):
387 episode
= res
['key'][1]
388 listeners
= res
['value']
389 return (episode
, listeners
)
392 @cache_result(timeout
=60*60)
393 def get_heatmap(podcast_id
, episode_id
, user_id
):
394 udb
= get_userdata_database()
396 group_level
= len(filter(None, [podcast_id
, episode_id
, user_id
]))
398 r
= udb
.view('heatmap/by_episode',
399 startkey
= [podcast_id
, episode_id
, user_id
],
400 endkey
= [podcast_id
, episode_id
or {}, user_id
or {}],
403 group_level
= group_level
,
404 stale
= 'update_after',
411 res
= r
.first()['value']
412 return res
['heatmap'], res
['borders']
415 @repeat_on_conflict(['state'])
416 def add_episode_actions(state
, actions
):
417 udb
= get_userdata_database()
418 state
.add_actions(actions
)
422 @repeat_on_conflict(['state'])
423 def update_episode_state_object(state
, podcast_id
, episode_id
=None):
424 state
.podcast
= podcast_id
426 if episode_id
is not None:
427 state
.episode
= episode_id
429 udb
= get_userdata_database()
433 @repeat_on_conflict(['state'])
434 def merge_episode_states(state
, state2
):
435 state
.add_actions(state2
.actions
)
437 # overwrite settings in state2 with state's settings
438 settings
= state2
.settings
439 settings
.update(state
.settings
)
440 state
.settings
= settings
442 merged_ids
= set(state
.merged_ids
+ [state2
._id
] + state2
.merged_ids
)
443 state
.merged_ids
= filter(None, merged_ids
)
445 state
.chapters
= list(set(state
.chapters
+ state2
.chapters
))
447 udb
= get_userdata_database()
451 @repeat_on_conflict(['state'])
452 def delete_episode_state(state
):
453 udb
= get_userdata_database()
454 udb
.delete_doc(state
)
457 @repeat_on_conflict(['episode_state'])
458 def update_episode_chapters(episode_state
, add
=[], rem
=[]):
459 """ Updates the Chapter list
461 * add contains the chapters to be added
463 * rem contains tuples of (start, end) times. Chapters that match
464 both endpoints will be removed
468 episode_state
.chapters
= episode_state
.chapters
+ [chapter
]
470 for start
, end
in rem
:
471 keep
= lambda c
: c
.start
!= start
or c
.end
!= end
472 episode_state
.chapters
= filter(keep
, episode_state
.chapters
)
477 def favorite_episode_ids_for_user(user
):
480 raise QueryParameterMissing('user')
482 udb
= get_userdata_database()
483 favorites
= udb
.view('favorites/episodes_by_user',
487 return set(x
['value']['_id'] for x
in favorites
)
490 def chapters_for_episode(episode_id
):
493 raise QueryParameterMissing('episode_id')
495 udb
= get_userdata_database()
496 r
= udb
.view('chapters/by_episode',
497 startkey
= [episode_id
, None],
498 endkey
= [episode_id
, {}],
501 return map(_wrap_chapter
, r
)
504 def _wrap_chapter(res
):
505 from mygpo
.users
.models
import Chapter
507 chapter
= Chapter
.wrap(res
['value'])
508 udb
= get_userdata_database()
510 return (user
, chapter
)
513 @repeat_on_conflict(['episode_state'])
514 def set_episode_favorite(episode_state
, is_fav
):
515 udb
= get_userdata_database()
516 episode_state
.set_favorite(is_fav
)
517 udb
.save_doc(episode_state
)