provide file type statistics for admins
[mygpo.git] / mygpo / db / couchdb / episode.py
blobf03d0a1fea3011ff4ca627f95a6244cc473c9c9c
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):
20 if not episode_id:
21 raise QueryParameterMissing('episode_id')
23 r = Episode.view('episodes/by_id',
24 key = episode_id,
25 include_docs = True,
28 if not r:
29 return None
31 obj = r.one()
32 if current_id and obj._id != episode_id:
33 raise MergedIdException(obj, obj._id)
35 if obj.needs_update:
36 incomplete_obj.send_robust(sender=obj)
38 return 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')
47 if not episode_ids:
48 return []
50 r = Episode.view('episodes/by_id',
51 include_docs = True,
52 keys = episode_ids,
55 episodes = list(r)
57 for episode in episodes:
58 if episode.needs_update:
59 incomplete_obj.send_robust(sender=episode)
61 return episodes
64 @cache_result(timeout=60*60)
65 def episode_for_oldid(oldid):
67 if not oldid:
68 raise QueryParameterMissing('oldid')
70 oldid = int(oldid)
71 r = Episode.view('episodes/by_oldid',
72 key = oldid,
73 limit = 1,
74 include_docs = True,
77 if not r:
78 return None
80 episode = r.one()
82 if episode.needs_update:
83 incomplete_obj.send_robust(sender=episode)
85 return episode
88 @cache_result(timeout=60*60)
89 def episode_for_slug(podcast_id, episode_slug):
91 if not podcast_id:
92 raise QueryParameterMissing('podcast_id')
94 if not episode_slug:
95 raise QueryParameterMissing('episode_slug')
98 r = Episode.view('episodes/by_slug',
99 key = [podcast_id, episode_slug],
100 include_docs = True,
103 if not r:
104 return None
106 episode = r.one()
108 if episode.needs_update:
109 incomplete_obj.send_robust(sender=episode)
111 return 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 """
120 if not podcast_id:
121 raise QueryParameterMissing('podcast_id')
123 if not episode_slug:
124 raise QueryParameterMissing('episode_slug')
126 r = Episode.view('episodes/by_slug',
127 key = [podcast_id, episode_slug],
128 include_docs = True,
131 if not r:
132 return []
134 episodes = r.all()
136 for episode in episodes:
137 if episode.needs_update:
138 incomplete_obj.send_robust(sender=episode)
140 return episodes
144 def episode_for_podcast_url(podcast_url, episode_url, create=False):
146 if not podcast_url:
147 raise QueryParameterMissing('podcast_url')
149 if not episode_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
156 return None
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):
163 if not podcast_id:
164 raise QueryParameterMissing('podcast_id')
166 if not episode_url:
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)
175 if episode:
176 return episode
178 r = Episode.view('episodes/by_podcast_url',
179 key = [podcast_id, episode_url],
180 include_docs = True,
181 reduce = False,
184 if r:
185 episode = r.first()
187 if episode.needs_update:
188 incomplete_obj.send_robust(sender=episode)
189 else:
190 cache.set(key, episode)
191 return episode
193 if create:
194 episode = Episode()
195 episode.podcast = podcast_id
196 episode.urls = [episode_url]
197 episode.save()
198 incomplete_obj.send_robust(sender=episode)
199 return episode
201 return None
204 def episode_for_slug_id(p_slug_id, e_slug_id):
205 """ Returns the Episode for Podcast Slug/Id and Episode Slug/Id """
207 if not p_slug_id:
208 raise QueryParameterMissing('p_slug_id')
210 if not e_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):
220 p_id = p_slug_id
221 else:
222 podcast = podcast_for_slug_id(p_slug_id)
224 if podcast is None:
225 return None
227 p_id = podcast.get_id()
229 return episode_for_slug(p_id, e_slug_id)
232 @cache_result(timeout=60*60)
233 def episode_count():
234 r = Episode.view('episodes/by_podcast',
235 reduce = True,
236 stale = 'update_after',
238 return r.one()['value'] if r else 0
241 def episodes_to_dict(ids, use_cache=False):
243 if ids is None:
244 raise QueryParameterMissing('ids')
246 if not ids:
247 return {}
250 ids = list(set(ids))
251 objs = dict()
253 cache_objs = []
254 if use_cache:
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:
266 _id = obj['key']
267 objs[_id] = None
268 continue
270 for i in obj.get_ids():
271 objs[i] = obj
273 if use_cache:
274 cache.set_many(dict( (obj._id, obj) for obj in db_objs))
276 return objs
279 def episode_slugs_per_podcast(podcast_id, base_slug):
281 if not podcast_id:
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'],
288 wrap_doc = False,
290 return [r['key'][1] for r in res]
293 def episodes_for_podcast_uncached(podcast, since=None, until={}, **kwargs):
295 if not podcast:
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],
311 include_docs = True,
312 reduce = False,
313 **kwargs
316 episodes = list(res)
318 for episode in episodes:
319 if episode.needs_update:
320 incomplete_obj.send_robust(sender=episode)
322 return episodes
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):
331 if not podcast:
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],
347 reduce = True,
348 group_level = 1,
349 **kwargs
352 return res.one()['value']
355 def favorite_episodes_for_user(user):
357 if not user:
358 raise QueryParameterMissing('user')
360 favorites = Episode.view('favorites/episodes_by_user',
361 key = user._id,
362 include_docs = True,
365 episodes = list(favorites)
367 for episode in episodes:
368 if episode.needs_update:
369 incomplete_obj.send_robust(sender=episode)
371 return episodes
374 def chapters_for_episode(episode_id):
376 if not 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',
394 reduce = True,
395 group_level = 1,
398 return Counter({x['key']: x['value'] for x in r})
401 def _wrap_chapter(res):
402 from mygpo.users.models import Chapter
403 user = res['key'][1]
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)
412 episode.save()
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)
419 episode.save()