1 from hashlib
import sha1
2 from datetime
import datetime
3 from collections
import Counter
5 from couchdbkit
import MultipleResultsFound
7 from django
.core
.cache
import cache
9 from mygpo
.core
.models
import Podcast
, Episode
, MergedIdException
10 from mygpo
.core
.signals
import incomplete_obj
11 from mygpo
.cache
import cache_result
12 from mygpo
.decorators
import repeat_on_conflict
13 from mygpo
.utils
import get_timestamp
14 from mygpo
.db
import QueryParameterMissing
15 from mygpo
.db
.couchdb
.utils
import is_couchdb_id
16 from mygpo
.db
.couchdb
import get_main_database
, get_userdata_database
17 from mygpo
.db
.couchdb
.podcast
import podcast_for_url
, podcast_for_slug_id
20 logger
= logging
.getLogger(__name__
)
23 @cache_result(timeout
=60*60)
24 def episode_by_id(episode_id
, current_id
=False):
27 raise QueryParameterMissing('episode_id')
29 r
= Episode
.view('episodes/by_id',
38 if current_id
and obj
._id
!= episode_id
:
39 raise MergedIdException(obj
, obj
._id
)
42 incomplete_obj
.send_robust(sender
=obj
)
47 @cache_result(timeout
=60*60)
48 def episodes_by_id(episode_ids
):
50 if episode_ids
is None:
51 raise QueryParameterMissing('episode_ids')
56 r
= Episode
.view('episodes/by_id',
63 for episode
in episodes
:
64 if episode
.needs_update
:
65 incomplete_obj
.send_robust(sender
=episode
)
70 @cache_result(timeout
=60*60)
71 def episode_for_oldid(oldid
):
74 raise QueryParameterMissing('oldid')
77 r
= Episode
.view('episodes/by_oldid',
88 if episode
.needs_update
:
89 incomplete_obj
.send_robust(sender
=episode
)
94 @cache_result(timeout
=60*60)
95 def episode_for_slug(podcast_id
, episode_slug
):
98 raise QueryParameterMissing('podcast_id')
101 raise QueryParameterMissing('episode_slug')
103 _view
= 'episodes/by_slug'
105 r
= Episode
.view(_view
,
106 key
= [podcast_id
, episode_slug
],
116 except MultipleResultsFound
as ex
:
117 logger
.exception('Multiple results found in %s with params %s',
121 if episode
.needs_update
:
122 incomplete_obj
.send_robust(sender
=episode
)
127 def episodes_for_slug(podcast_id
, episode_slug
):
128 """ returns all episodes for the given slug
130 this should normally only return one episode, but there might be multiple
131 due to resolved replication conflicts, etc """
134 raise QueryParameterMissing('podcast_id')
137 raise QueryParameterMissing('episode_slug')
139 r
= Episode
.view('episodes/by_slug',
140 key
= [podcast_id
, episode_slug
],
149 for episode
in episodes
:
150 if episode
.needs_update
:
151 incomplete_obj
.send_robust(sender
=episode
)
157 def episode_for_podcast_url(podcast_url
, episode_url
, create
=False):
160 raise QueryParameterMissing('podcast_url')
163 raise QueryParameterMissing('episode_url')
166 podcast
= podcast_for_url(podcast_url
, create
=create
)
168 if not podcast
: # podcast does not exist and should not be created
171 return episode_for_podcast_id_url(podcast
.get_id(), episode_url
, create
)
174 def episode_for_podcast_id_url(podcast_id
, episode_url
, create
=False):
177 raise QueryParameterMissing('podcast_id')
180 raise QueryParameterMissing('episode_url')
183 key
= u
'episode-podcastid-%s-url-%s' % (
184 sha1(podcast_id
.encode('utf-8')).hexdigest(),
185 sha1(episode_url
.encode('utf-8')).hexdigest())
187 # Disabled as cache invalidation is not working properly
188 # episode = cache.get(key)
192 r
= Episode
.view('episodes/by_podcast_url',
193 key
= [podcast_id
, episode_url
],
201 if episode
.needs_update
:
202 incomplete_obj
.send_robust(sender
=episode
)
204 cache
.set(key
, episode
)
209 episode
.created_timestamp
= get_timestamp(datetime
.utcnow())
210 episode
.podcast
= podcast_id
211 episode
.urls
= [episode_url
]
213 incomplete_obj
.send_robust(sender
=episode
)
219 def episode_for_slug_id(p_slug_id
, e_slug_id
):
220 """ Returns the Episode for Podcast Slug/Id and Episode Slug/Id """
223 raise QueryParameterMissing('p_slug_id')
226 raise QueryParameterMissing('e_slug_id')
229 # The Episode-Id is unique, so take that
230 if is_couchdb_id(e_slug_id
):
231 return episode_by_id(e_slug_id
)
233 # If we search using a slug, we need the Podcast's Id
234 if is_couchdb_id(p_slug_id
):
237 podcast
= podcast_for_slug_id(p_slug_id
)
242 p_id
= podcast
.get_id()
244 return episode_for_slug(p_id
, e_slug_id
)
247 @cache_result(timeout
=60*60)
249 r
= Episode
.view('episodes/by_podcast',
251 stale
= 'update_after',
253 return r
.one()['value'] if r
else 0
256 def episodes_to_dict(ids
, use_cache
=False):
259 raise QueryParameterMissing('ids')
270 res
= cache
.get_many(ids
)
271 cache_objs
.extend(res
.values())
272 ids
= [x
for x
in ids
if x
not in res
.keys()]
274 db_objs
= list(episodes_by_id(ids
))
276 for obj
in (cache_objs
+ db_objs
):
278 # get_multi returns dict {'key': _id, 'error': 'not found'}
279 # for non-existing objects
280 if isinstance(obj
, dict) and 'error' in obj
:
285 for i
in obj
.get_ids():
289 cache
.set_many(dict( (obj
._id
, obj
) for obj
in db_objs
))
294 def episode_slugs_per_podcast(podcast_id
, base_slug
):
297 raise QueryParameterMissing('podcast_id')
300 res
= Episode
.view('episodes/by_slug',
301 startkey
= [podcast_id
, base_slug
],
302 endkey
= [podcast_id
, base_slug
+ 'ZZZZZ'],
305 return [r
['key'][1] for r
in res
]
308 def episodes_for_podcast_current(podcast
, limit
=None):
311 raise QueryParameterMissing('podcast')
313 res
= Episode
.view('episodes/by_podcast_current',
314 startkey
= podcast
.get_id(),
315 endkey
= podcast
.get_id(),
322 for episode
in episodes
:
323 if episode
.needs_update
:
324 incomplete_obj
.send_robust(sender
=episode
)
330 def episodes_for_podcast_uncached(podcast
, since
=None, until
={}, **kwargs
):
333 raise QueryParameterMissing('podcast')
336 if kwargs
.get('descending', False):
337 since
, until
= until
, since
339 if isinstance(since
, datetime
):
340 since
= since
.isoformat()
342 if isinstance(until
, datetime
):
343 until
= until
.isoformat()
345 res
= Episode
.view('episodes/by_podcast',
346 startkey
= [podcast
.get_id(), since
],
347 endkey
= [podcast
.get_id(), until
],
355 for episode
in episodes
:
356 if episode
.needs_update
:
357 incomplete_obj
.send_robust(sender
=episode
)
362 episodes_for_podcast
= cache_result(timeout
=60*60)(episodes_for_podcast_uncached
)
365 @cache_result(timeout
=60*60)
366 def episode_count_for_podcast(podcast
, since
=None, until
={}, **kwargs
):
369 raise QueryParameterMissing('podcast')
372 if kwargs
.get('descending', False):
373 since
, until
= until
, since
375 if isinstance(since
, datetime
):
376 since
= since
.isoformat()
378 if isinstance(until
, datetime
):
379 until
= until
.isoformat()
381 res
= Episode
.view('episodes/by_podcast',
382 startkey
= [podcast
.get_id(), since
],
383 endkey
= [podcast
.get_id(), until
],
389 return res
.one()['value'] if res
else 0
392 def favorite_episode_ids_for_user(user
):
395 raise QueryParameterMissing('user')
397 udb
= get_userdata_database()
398 favorites
= udb
.view('favorites/episodes_by_user',
402 return set(x
['value']['_id'] for x
in favorites
)
405 def favorite_episodes_for_user(user
):
406 episode_ids
= list(favorite_episode_ids_for_user(user
))
407 return episodes_by_id(episode_ids
)
410 def chapters_for_episode(episode_id
):
413 raise QueryParameterMissing('episode_id')
415 udb
= get_userdata_database()
416 r
= udb
.view('chapters/by_episode',
417 startkey
= [episode_id
, None],
418 endkey
= [episode_id
, {}],
421 return map(_wrap_chapter
, r
)
424 def filetype_stats():
425 """ Returns a filetype counter over all episodes """
427 db
= get_main_database()
428 r
= db
.view('episode_stats/filetypes',
429 stale
= 'update_after',
434 return Counter({x
['key']: x
['value'] for x
in r
})
437 def _wrap_chapter(res
):
438 from mygpo
.users
.models
import Chapter
440 chapter
= Chapter
.wrap(res
['value'])
441 udb
= get_userdata_database()
443 return (user
, chapter
)
446 @repeat_on_conflict(['episode'])
447 def set_episode_slug(episode
, slug
):
448 """ sets slug as new main slug of the episode, moves other to merged """
449 episode
.set_slug(slug
)
453 @repeat_on_conflict(['episode'])
454 def remove_episode_slug(episode
, slug
):
455 """ removes slug from main and merged slugs """
456 episode
.remove_slug(slug
)
460 @repeat_on_conflict(['episode_state'])
461 def set_episode_favorite(episode_state
, is_fav
):
462 udb
= get_userdata_database()
463 episode_state
.set_favorite(is_fav
)
464 udb
.save_doc(episode_state
)
467 @repeat_on_conflict(['episode'])
468 def set_episode_listeners(episode
, listeners
):
470 if episode
.listeners
== listeners
:
473 episode
.listeners
= listeners
475 db
= get_main_database()