[Chapters] use Django ORM in Chapters API
[mygpo.git] / mygpo / db / couchdb / episode_state.py
blob86be893ca332acb4d7ebcd6155f15e56a0aca119
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_userdata_database, get_single_result
11 from mygpo.cache import cache_result
12 from mygpo.decorators import repeat_on_conflict
16 def episode_state_for_user_episode(user, episode):
18 if not user:
19 raise QueryParameterMissing('user')
21 if not episode:
22 raise QueryParameterMissing('episode')
24 if hasattr(episode, '_id'):
25 episode_id = episode._id
26 else:
27 episode_id = episode.get_id()
28 key = 'episode-state-userid-%s-episodeid-%s' % (sha1(user.profile.uuid.hex).hexdigest(),
29 sha1(episode_id).hexdigest())
31 # Disabled as cache invalidation does not work properly
32 # state = cache.get(key)
33 # if state:
34 # return state
36 udb = get_userdata_database()
37 state = get_single_result(udb, 'episode_states/by_user_episode',
38 key = [user.profile.uuid.hex, episode_id],
39 include_docs = True,
40 limit = 1,
41 schema = EpisodeUserState,
44 if state:
45 cache.set(key, state)
46 return state
48 else:
49 if isinstance(episode.podcast, unicode):
50 podcast = Podcast.objects.get_by_any_id(episode.podcast)
51 else:
52 podcast = episode.podcast
54 state = EpisodeUserState()
55 state.episode = episode_id
56 state.podcast = podcast.get_id()
57 state.user = user.profile.uuid.hex
58 state.ref_url = episode.url
59 state.podcast_ref_url = podcast.url
60 # don't cache here, because the state is saved by the calling function
62 return state
66 def all_episode_states(episode):
68 if not episode:
69 raise QueryParameterMissing('episode')
71 if isinstance(episode.podcast, unicode):
72 podcast_id = episode.podcast
73 else:
74 podcast_id = episode.podcast.get_id()
76 if hasattr(episode, '_id'):
77 episode_id = episode._id
78 else:
79 episode_id = episode.get_id()
81 udb = get_userdata_database()
82 r = udb.view('episode_states/by_podcast_episode',
83 startkey = [podcast_id, episode_id, None],
84 endkey = [podcast_id, episode_id, {}],
85 include_docs = True,
86 schema = EpisodeUserState,
89 states = list(r)
91 for state in states:
92 state.set_db(udb)
94 return states
97 def get_podcasts_episode_states(podcast, user_id):
98 """ Returns the latest episode actions for the podcast's episodes """
100 if not podcast:
101 raise QueryParameterMissing('podcast')
103 if not user_id:
104 raise QueryParameterMissing('user_id')
106 udb = get_userdata_database()
107 res = udb.view('episode_states/by_user_podcast',
108 startkey = [user_id, podcast.get_id(), None],
109 endkey = [user_id, podcast.get_id(), {}],
112 return map(lambda r: r['value'], res)
115 def episode_state_for_ref_urls(user, podcast_url, episode_url):
117 if not user:
118 raise QueryParameterMissing('user')
120 if not podcast_url:
121 raise QueryParameterMissing('podcast_url')
123 if not episode_url:
124 raise QueryParameterMissing('episode_url')
127 cache_key = 'episode-state-%s-%s-%s' % (user.profile.uuid.hex,
128 sha1(podcast_url).hexdigest(),
129 sha1(episode_url).hexdigest())
131 state = cache.get(cache_key)
132 if state:
133 return state
135 udb = get_userdata_database()
136 state = get_single_result(udb, 'episode_states/by_ref_urls',
137 key = [user.profile.uuid.hex, podcast_url, episode_url],
138 limit = 1,
139 include_docs=True,
140 schema = EpisodeUserState,
143 if state:
144 state.ref_url = episode_url
145 state.podcast_ref_url = podcast_url
146 cache.set(cache_key, state, 60*60)
147 return state
149 else:
150 podcast = Podcast.objects.get_or_create_for_url(podcast_url)
151 episode = Episode.objects.get_or_create_for_url(podcast, episode_url)
152 return episode_state_for_user_episode(user, episode)
156 def get_episode_actions(user_id, since=None, until={}, podcast_id=None,
157 device_id=None, limit=1000):
158 """ Returns Episode Actions for the given criteria
160 There is an upper limit on how many actions will be returned; until is the
161 timestamp of the last episode action.
164 if not user_id:
165 raise QueryParameterMissing('user_id')
167 if since >= until:
168 return [], until
170 if not podcast_id and not device_id:
171 view = 'episode_actions/by_user'
172 startkey = [user_id, since]
173 endkey = [user_id, until]
175 elif podcast_id and not device_id:
176 view = 'episode_actions/by_podcast'
177 startkey = [user_id, podcast_id, since]
178 endkey = [user_id, podcast_id, until]
180 elif device_id and not podcast_id:
181 view = 'episode_actions/by_device'
182 startkey = [user_id, device_id, since]
183 endkey = [user_id, device_id, until]
185 else:
186 view = 'episode_actions/by_podcast_device'
187 startkey = [user_id, podcast_id, device_id, since]
188 endkey = [user_id, podcast_id, device_id, until]
190 udb = get_userdata_database()
191 res = udb.view(view,
192 startkey = startkey,
193 endkey = endkey,
194 limit = limit,
197 results = list(res)
198 actions = map(lambda r: r['value'], results)
199 if actions:
200 # the upload_timestamp is always the last part of the key
201 until = results[-1]['key'][-1]
203 return actions, until
207 @cache_result(timeout=60*60)
208 def episode_states_count():
209 udb = get_userdata_database()
210 r = udb.view('episode_states/by_user_episode',
211 limit = 0,
212 stale = 'update_after',
214 return r.total_rows
217 def get_nth_episode_state(n):
218 udb = get_userdata_database()
219 state = get_single_result(udb, 'episode_states/by_user_episode',
220 skip = n,
221 include_docs = True,
222 limit = 1,
223 schema = EpisodeUserState,
226 return state
229 def get_duplicate_episode_states(user, episode):
231 if not user:
232 raise QueryParameterMissing('user')
234 if not episode:
235 raise QueryParameterMissing('episode')
237 udb = get_userdata_database()
238 r = udb.view('episode_states/by_user_episode',
239 key = [user, episode],
240 include_docs = True,
241 schema = EpisodeUserState,
244 states = list(r)
246 for state in states:
247 state.set_db(udb)
249 return states
252 @cache_result(timeout=60*60)
253 def get_heatmap(podcast_id, episode_id, user_id):
254 udb = get_userdata_database()
256 group_level = len(filter(None, [podcast_id, episode_id, user_id]))
258 r = udb.view('heatmap/by_episode',
259 startkey = [podcast_id, episode_id, user_id],
260 endkey = [podcast_id, episode_id or {}, user_id or {}],
261 reduce = True,
262 group = True,
263 group_level = group_level,
264 stale = 'update_after',
266 # TODO: Heatmap not available during transition to Django ORM
267 r = False
269 if not r:
270 return [], []
272 else:
273 res = r.first()['value']
274 return res['heatmap'], res['borders']
277 @repeat_on_conflict(['state'])
278 def add_episode_actions(state, actions):
279 udb = get_userdata_database()
280 state.add_actions(actions)
281 udb.save_doc(state)
284 @repeat_on_conflict(['state'])
285 def update_episode_state_object(state, podcast_id, episode_id=None):
286 state.podcast = podcast_id
288 if episode_id is not None:
289 state.episode = episode_id
291 udb = get_userdata_database()
292 udb.save_doc(state)
295 @repeat_on_conflict(['state'])
296 def merge_episode_states(state, state2):
297 state.add_actions(state2.actions)
299 # overwrite settings in state2 with state's settings
300 settings = state2.settings
301 settings.update(state.settings)
302 state.settings = settings
304 merged_ids = set(state.merged_ids + [state2._id] + state2.merged_ids)
305 state.merged_ids = filter(None, merged_ids)
307 state.chapters = list(set(state.chapters + state2.chapters))
309 udb = get_userdata_database()
310 udb.save_doc(state)
313 @repeat_on_conflict(['state'])
314 def delete_episode_state(state):
315 udb = get_userdata_database()
316 udb.delete_doc(state)