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
):
19 raise QueryParameterMissing('user')
22 raise QueryParameterMissing('episode')
24 if hasattr(episode
, '_id'):
25 episode_id
= episode
._id
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)
36 udb
= get_userdata_database()
37 state
= get_single_result(udb
, 'episode_states/by_user_episode',
38 key
= [user
.profile
.uuid
.hex, episode_id
],
41 schema
= EpisodeUserState
,
49 if isinstance(episode
.podcast
, unicode):
50 podcast
= Podcast
.objects
.get_by_any_id(episode
.podcast
)
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
66 def all_episode_states(episode
):
69 raise QueryParameterMissing('episode')
71 if isinstance(episode
.podcast
, unicode):
72 podcast_id
= episode
.podcast
74 podcast_id
= episode
.podcast
.get_id()
76 if hasattr(episode
, '_id'):
77 episode_id
= episode
._id
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
, {}],
86 schema
= EpisodeUserState
,
97 def get_podcasts_episode_states(podcast
, user_id
):
98 """ Returns the latest episode actions for the podcast's episodes """
101 raise QueryParameterMissing('podcast')
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
):
118 raise QueryParameterMissing('user')
121 raise QueryParameterMissing('podcast_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
)
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
],
140 schema
= EpisodeUserState
,
144 state
.ref_url
= episode_url
145 state
.podcast_ref_url
= podcast_url
146 cache
.set(cache_key
, state
, 60*60)
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.
165 raise QueryParameterMissing('user_id')
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
]
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()
198 actions
= map(lambda r
: r
['value'], results
)
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',
212 stale
= 'update_after',
217 def get_nth_episode_state(n
):
218 udb
= get_userdata_database()
219 state
= get_single_result(udb
, 'episode_states/by_user_episode',
223 schema
= EpisodeUserState
,
229 def get_duplicate_episode_states(user
, episode
):
232 raise QueryParameterMissing('user')
235 raise QueryParameterMissing('episode')
237 udb
= get_userdata_database()
238 r
= udb
.view('episode_states/by_user_episode',
239 key
= [user
, episode
],
241 schema
= EpisodeUserState
,
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 {}],
263 group_level
= group_level
,
264 stale
= 'update_after',
266 # TODO: Heatmap not available during transition to Django ORM
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
)
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()
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()
313 @repeat_on_conflict(['state'])
314 def delete_episode_state(state
):
315 udb
= get_userdata_database()
316 udb
.delete_doc(state
)