[History] create episode heatmap from Django ORM
[mygpo.git] / mygpo / db / couchdb / episode_state.py
blobe2966ba1b86222231fea692c9addd7b4772629f9
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
206 @repeat_on_conflict(['state'])
207 def add_episode_actions(state, actions):
208 udb = get_userdata_database()
209 state.add_actions(actions)
210 udb.save_doc(state)
213 @repeat_on_conflict(['state'])
214 def update_episode_state_object(state, podcast_id, episode_id=None):
215 state.podcast = podcast_id
217 if episode_id is not None:
218 state.episode = episode_id
220 udb = get_userdata_database()
221 udb.save_doc(state)
224 @repeat_on_conflict(['state'])
225 def merge_episode_states(state, state2):
226 state.add_actions(state2.actions)
228 # overwrite settings in state2 with state's settings
229 settings = state2.settings
230 settings.update(state.settings)
231 state.settings = settings
233 merged_ids = set(state.merged_ids + [state2._id] + state2.merged_ids)
234 state.merged_ids = filter(None, merged_ids)
236 state.chapters = list(set(state.chapters + state2.chapters))
238 udb = get_userdata_database()
239 udb.save_doc(state)
242 @repeat_on_conflict(['state'])
243 def delete_episode_state(state):
244 udb = get_userdata_database()
245 udb.delete_doc(state)