add incomplete_obj signal to DB layer
[mygpo.git] / mygpo / db / couchdb / episode.py
blob550948cd466bbb15c0c3c9e19ae279b904beab15
1 from hashlib import sha1
2 from datetime import datetime
4 from django.core.cache import cache
6 from mygpo.core.models import Podcast, Episode, MergedIdException
7 from mygpo.core.signals import incomplete_obj
8 from mygpo.cache import cache_result
9 from mygpo.db import QueryParameterMissing
10 from mygpo.db.couchdb.utils import is_couchdb_id
11 from mygpo.db.couchdb import get_main_database
12 from mygpo.db.couchdb.podcast import podcast_for_url, podcast_for_slug_id
15 @cache_result(timeout=60*60)
16 def episode_by_id(episode_id, current_id=False):
18 if not episode_id:
19 raise QueryParameterMissing('episode_id')
21 r = Episode.view('episodes/by_id',
22 key = episode_id,
23 include_docs = True,
26 if not r:
27 return None
29 obj = r.one()
30 if current_id and obj._id != episode_id:
31 raise MergedIdException(obj, obj._id)
33 if obj.needs_update:
34 incomplete_obj.send_robust(sender=obj)
36 return obj
39 @cache_result(timeout=60*60)
40 def episodes_by_id(episode_ids):
42 if episode_ids is None:
43 raise QueryParameterMissing('episode_ids')
45 if not episode_ids:
46 return []
48 r = Episode.view('episodes/by_id',
49 include_docs = True,
50 keys = episode_ids,
53 episodes = list(r)
55 for episode in episodes:
56 if episode.needs_update:
57 incomplete_obj.send_robust(sender=episode)
59 return episodes
62 @cache_result(timeout=60*60)
63 def episode_for_oldid(oldid):
65 if not oldid:
66 raise QueryParameterMissing('oldid')
68 oldid = int(oldid)
69 r = Episode.view('episodes/by_oldid',
70 key = oldid,
71 limit = 1,
72 include_docs = True,
75 if not r:
76 return None
78 episode = r.one()
80 if episode.needs_update:
81 incomplete_obj.send_robust(sender=episode)
83 return episode
86 @cache_result(timeout=60*60)
87 def episode_for_slug(podcast_id, episode_slug):
89 if not podcast_id:
90 raise QueryParameterMissing('podcast_id')
92 if not episode_slug:
93 raise QueryParameterMissing('episode_slug')
96 r = Episode.view('episodes/by_slug',
97 key = [podcast_id, episode_slug],
98 include_docs = True,
101 if not r:
102 return None
104 episode = r.one()
106 if episode.needs_update:
107 incomplete_obj.send_robust(sender=episode)
109 return episode
112 def episode_for_podcast_url(podcast_url, episode_url, create=False):
114 if not podcast_url:
115 raise QueryParameterMissing('podcast_url')
117 if not episode_url:
118 raise QueryParameterMissing('episode_url')
121 podcast = podcast_for_url(podcast_url, create=create)
123 if not podcast: # podcast does not exist and should not be created
124 return None
126 return episode_for_podcast_id_url(podcast.get_id(), episode_url, create)
129 def episode_for_podcast_id_url(podcast_id, episode_url, create=False):
131 if not podcast_id:
132 raise QueryParameterMissing('podcast_id')
134 if not episode_url:
135 raise QueryParameterMissing('episode_url')
138 key = u'episode-podcastid-%s-url-%s' % (
139 sha1(podcast_id.encode('utf-8')).hexdigest(),
140 sha1(episode_url.encode('utf-8')).hexdigest())
142 episode = cache.get(key)
143 if episode:
144 return episode
146 r = Episode.view('episodes/by_podcast_url',
147 key = [podcast_id, episode_url],
148 include_docs = True,
149 reduce = False,
152 if r:
153 episode = r.first()
155 if episode.needs_update:
156 incomplete_obj.send_robust(sender=episode)
157 else:
158 cache.set(key, episode)
159 return episode
161 if create:
162 episode = Episode()
163 episode.podcast = podcast_id
164 episode.urls = [episode_url]
165 episode.save()
166 incomplete_obj.send_robust(sender=episode)
167 return episode
169 return None
172 @cache_result(timeout=60*60)
173 def episode_for_slug_id(p_slug_id, e_slug_id):
174 """ Returns the Episode for Podcast Slug/Id and Episode Slug/Id """
176 if not p_slug_id:
177 raise QueryParameterMissing('p_slug_id')
179 if not e_slug_id:
180 raise QueryParameterMissing('e_slug_id')
183 # The Episode-Id is unique, so take that
184 if is_couchdb_id(e_slug_id):
185 return episode_by_id(e_slug_id)
187 # If we search using a slug, we need the Podcast's Id
188 if is_couchdb_id(p_slug_id):
189 p_id = p_slug_id
190 else:
191 podcast = podcast_for_slug_id(p_slug_id)
193 if podcast is None:
194 return None
196 p_id = podcast.get_id()
198 return episode_for_slug(p_id, e_slug_id)
201 @cache_result(timeout=60*60)
202 def episode_count():
203 r = Episode.view('episodes/by_podcast',
204 reduce = True,
205 stale = 'update_after',
207 return r.one()['value'] if r else 0
210 def episodes_to_dict(ids, use_cache=False):
212 if ids is None:
213 raise QueryParameterMissing('ids')
215 if not ids:
216 return {}
219 ids = list(set(ids))
220 objs = dict()
222 cache_objs = []
223 if use_cache:
224 res = cache.get_many(ids)
225 cache_objs.extend(res.values())
226 ids = [x for x in ids if x not in res.keys()]
228 db_objs = list(episodes_by_id(ids))
230 for obj in (cache_objs + db_objs):
232 # get_multi returns dict {'key': _id, 'error': 'not found'}
233 # for non-existing objects
234 if isinstance(obj, dict) and 'error' in obj:
235 _id = obj['key']
236 objs[_id] = None
237 continue
239 for i in obj.get_ids():
240 objs[i] = obj
242 if use_cache:
243 cache.set_many(dict( (obj._id, obj) for obj in db_objs))
245 return objs
248 def episode_slugs_per_podcast(podcast_id, base_slug):
250 if not podcast_id:
251 raise QueryParameterMissing('podcast_id')
254 res = Episode.view('episodes/by_slug',
255 startkey = [podcast_id, base_slug],
256 endkey = [podcast_id, base_slug + 'ZZZZZ'],
257 wrap_doc = False,
259 return [r['key'][1] for r in res]
262 def episodes_for_podcast_uncached(podcast, since=None, until={}, **kwargs):
264 if not podcast:
265 raise QueryParameterMissing('podcast')
268 if kwargs.get('descending', False):
269 since, until = until, since
271 if isinstance(since, datetime):
272 since = since.isoformat()
274 if isinstance(until, datetime):
275 until = until.isoformat()
277 res = Episode.view('episodes/by_podcast',
278 startkey = [podcast.get_id(), since],
279 endkey = [podcast.get_id(), until],
280 include_docs = True,
281 reduce = False,
282 **kwargs
285 episodes = list(res)
287 for episode in episodes:
288 if episode.needs_update:
289 incomplete_obj.send_robust(sender=episode)
291 return episodes
294 episodes_for_podcast = cache_result(timeout=60*60)(episodes_for_podcast_uncached)
297 @cache_result(timeout=60*60)
298 def episode_count_for_podcast(podcast, since=None, until={}, **kwargs):
300 if not podcast:
301 raise QueryParameterMissing('podcast')
304 if kwargs.get('descending', False):
305 since, until = until, since
307 if isinstance(since, datetime):
308 since = since.isoformat()
310 if isinstance(until, datetime):
311 until = until.isoformat()
313 res = Episode.view('episodes/by_podcast',
314 startkey = [podcast.get_id(), since],
315 endkey = [podcast.get_id(), until],
316 reduce = True,
317 group_level = 1,
318 **kwargs
321 return res.one()['value']
324 def favorite_episodes_for_user(user):
326 if not user:
327 raise QueryParameterMissing('user')
329 favorites = Episode.view('favorites/episodes_by_user',
330 key = user._id,
331 include_docs = True,
334 episodes = list(favorites)
336 for episode in episodes:
337 if episode.needs_update:
338 incomplete_obj.send_robust(sender=episode)
340 return episodes
343 def chapters_for_episode(episode_id):
345 if not episode_id:
346 raise QueryParameterMissing('episode_id')
348 db = get_main_database()
349 r = db.view('chapters/by_episode',
350 startkey = [episode_id, None],
351 endkey = [episode_id, {}],
354 return map(_wrap_chapter, r)
357 def _wrap_chapter(res):
358 from mygpo.users.models import Chapter
359 user = res['key'][1]
360 chapter = Chapter.wrap(res['value'])
361 return (user, chapter)