1 from hashlib
import sha1
2 from datetime
import datetime
3 from collections
import Counter
5 from django
.core
.cache
import cache
7 from mygpo
.core
.models
import Podcast
, Episode
, MergedIdException
8 from mygpo
.core
.signals
import incomplete_obj
9 from mygpo
.cache
import cache_result
10 from mygpo
.decorators
import repeat_on_conflict
11 from mygpo
.db
import QueryParameterMissing
12 from mygpo
.db
.couchdb
.utils
import is_couchdb_id
13 from mygpo
.db
.couchdb
import get_main_database
14 from mygpo
.db
.couchdb
.podcast
import podcast_for_url
, podcast_for_slug_id
17 @cache_result(timeout
=60*60)
18 def episode_by_id(episode_id
, current_id
=False):
21 raise QueryParameterMissing('episode_id')
23 r
= Episode
.view('episodes/by_id',
32 if current_id
and obj
._id
!= episode_id
:
33 raise MergedIdException(obj
, obj
._id
)
36 incomplete_obj
.send_robust(sender
=obj
)
41 @cache_result(timeout
=60*60)
42 def episodes_by_id(episode_ids
):
44 if episode_ids
is None:
45 raise QueryParameterMissing('episode_ids')
50 r
= Episode
.view('episodes/by_id',
57 for episode
in episodes
:
58 if episode
.needs_update
:
59 incomplete_obj
.send_robust(sender
=episode
)
64 @cache_result(timeout
=60*60)
65 def episode_for_oldid(oldid
):
68 raise QueryParameterMissing('oldid')
71 r
= Episode
.view('episodes/by_oldid',
82 if episode
.needs_update
:
83 incomplete_obj
.send_robust(sender
=episode
)
88 @cache_result(timeout
=60*60)
89 def episode_for_slug(podcast_id
, episode_slug
):
92 raise QueryParameterMissing('podcast_id')
95 raise QueryParameterMissing('episode_slug')
98 r
= Episode
.view('episodes/by_slug',
99 key
= [podcast_id
, episode_slug
],
108 if episode
.needs_update
:
109 incomplete_obj
.send_robust(sender
=episode
)
114 def episodes_for_slug(podcast_id
, episode_slug
):
115 """ returns all episodes for the given slug
117 this should normally only return one episode, but there might be multiple
118 due to resolved replication conflicts, etc """
121 raise QueryParameterMissing('podcast_id')
124 raise QueryParameterMissing('episode_slug')
126 r
= Episode
.view('episodes/by_slug',
127 key
= [podcast_id
, episode_slug
],
136 for episode
in episodes
:
137 if episode
.needs_update
:
138 incomplete_obj
.send_robust(sender
=episode
)
144 def episode_for_podcast_url(podcast_url
, episode_url
, create
=False):
147 raise QueryParameterMissing('podcast_url')
150 raise QueryParameterMissing('episode_url')
153 podcast
= podcast_for_url(podcast_url
, create
=create
)
155 if not podcast
: # podcast does not exist and should not be created
158 return episode_for_podcast_id_url(podcast
.get_id(), episode_url
, create
)
161 def episode_for_podcast_id_url(podcast_id
, episode_url
, create
=False):
164 raise QueryParameterMissing('podcast_id')
167 raise QueryParameterMissing('episode_url')
170 key
= u
'episode-podcastid-%s-url-%s' % (
171 sha1(podcast_id
.encode('utf-8')).hexdigest(),
172 sha1(episode_url
.encode('utf-8')).hexdigest())
174 episode
= cache
.get(key
)
178 r
= Episode
.view('episodes/by_podcast_url',
179 key
= [podcast_id
, episode_url
],
187 if episode
.needs_update
:
188 incomplete_obj
.send_robust(sender
=episode
)
190 cache
.set(key
, episode
)
195 episode
.podcast
= podcast_id
196 episode
.urls
= [episode_url
]
198 incomplete_obj
.send_robust(sender
=episode
)
204 def episode_for_slug_id(p_slug_id
, e_slug_id
):
205 """ Returns the Episode for Podcast Slug/Id and Episode Slug/Id """
208 raise QueryParameterMissing('p_slug_id')
211 raise QueryParameterMissing('e_slug_id')
214 # The Episode-Id is unique, so take that
215 if is_couchdb_id(e_slug_id
):
216 return episode_by_id(e_slug_id
)
218 # If we search using a slug, we need the Podcast's Id
219 if is_couchdb_id(p_slug_id
):
222 podcast
= podcast_for_slug_id(p_slug_id
)
227 p_id
= podcast
.get_id()
229 return episode_for_slug(p_id
, e_slug_id
)
232 @cache_result(timeout
=60*60)
234 r
= Episode
.view('episodes/by_podcast',
236 stale
= 'update_after',
238 return r
.one()['value'] if r
else 0
241 def episodes_to_dict(ids
, use_cache
=False):
244 raise QueryParameterMissing('ids')
255 res
= cache
.get_many(ids
)
256 cache_objs
.extend(res
.values())
257 ids
= [x
for x
in ids
if x
not in res
.keys()]
259 db_objs
= list(episodes_by_id(ids
))
261 for obj
in (cache_objs
+ db_objs
):
263 # get_multi returns dict {'key': _id, 'error': 'not found'}
264 # for non-existing objects
265 if isinstance(obj
, dict) and 'error' in obj
:
270 for i
in obj
.get_ids():
274 cache
.set_many(dict( (obj
._id
, obj
) for obj
in db_objs
))
279 def episode_slugs_per_podcast(podcast_id
, base_slug
):
282 raise QueryParameterMissing('podcast_id')
285 res
= Episode
.view('episodes/by_slug',
286 startkey
= [podcast_id
, base_slug
],
287 endkey
= [podcast_id
, base_slug
+ 'ZZZZZ'],
290 return [r
['key'][1] for r
in res
]
293 def episodes_for_podcast_uncached(podcast
, since
=None, until
={}, **kwargs
):
296 raise QueryParameterMissing('podcast')
299 if kwargs
.get('descending', False):
300 since
, until
= until
, since
302 if isinstance(since
, datetime
):
303 since
= since
.isoformat()
305 if isinstance(until
, datetime
):
306 until
= until
.isoformat()
308 res
= Episode
.view('episodes/by_podcast',
309 startkey
= [podcast
.get_id(), since
],
310 endkey
= [podcast
.get_id(), until
],
318 for episode
in episodes
:
319 if episode
.needs_update
:
320 incomplete_obj
.send_robust(sender
=episode
)
325 episodes_for_podcast
= cache_result(timeout
=60*60)(episodes_for_podcast_uncached
)
328 @cache_result(timeout
=60*60)
329 def episode_count_for_podcast(podcast
, since
=None, until
={}, **kwargs
):
332 raise QueryParameterMissing('podcast')
335 if kwargs
.get('descending', False):
336 since
, until
= until
, since
338 if isinstance(since
, datetime
):
339 since
= since
.isoformat()
341 if isinstance(until
, datetime
):
342 until
= until
.isoformat()
344 res
= Episode
.view('episodes/by_podcast',
345 startkey
= [podcast
.get_id(), since
],
346 endkey
= [podcast
.get_id(), until
],
352 return res
.one()['value']
355 def favorite_episodes_for_user(user
):
358 raise QueryParameterMissing('user')
360 favorites
= Episode
.view('favorites/episodes_by_user',
365 episodes
= list(favorites
)
367 for episode
in episodes
:
368 if episode
.needs_update
:
369 incomplete_obj
.send_robust(sender
=episode
)
374 def chapters_for_episode(episode_id
):
377 raise QueryParameterMissing('episode_id')
379 db
= get_main_database()
380 r
= db
.view('chapters/by_episode',
381 startkey
= [episode_id
, None],
382 endkey
= [episode_id
, {}],
385 return map(_wrap_chapter
, r
)
388 def filetype_stats():
389 """ Returns a filetype counter over all episodes """
391 db
= get_main_database()
392 r
= db
.view('episode_stats/filetypes',
393 stale
= 'update_after',
398 return Counter({x
['key']: x
['value'] for x
in r
})
401 def _wrap_chapter(res
):
402 from mygpo
.users
.models
import Chapter
404 chapter
= Chapter
.wrap(res
['value'])
405 return (user
, chapter
)
408 @repeat_on_conflict(['episode'])
409 def set_episode_slug(episode
, slug
):
410 """ sets slug as new main slug of the episode, moves other to merged """
411 episode
.set_slug(slug
)
415 @repeat_on_conflict(['episode'])
416 def remove_episode_slug(episode
, slug
):
417 """ removes slug from main and merged slugs """
418 episode
.remove_slug(slug
)