replace EpisodeUserState.update_chapters
[mygpo.git] / mygpo / api / advanced / episode.py
blob6c54afbbb32a568921547d853a87905acaae02fb
2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
18 import time
19 from datetime import datetime
21 import dateutil.parser
23 from django.http import HttpResponseBadRequest, Http404
24 from django.views.decorators.csrf import csrf_exempt
25 from django.views.decorators.cache import never_cache
27 from mygpo.core import models
28 from mygpo.api.httpresponse import JsonResponse
29 from mygpo.api.exceptions import ParameterMissing
30 from mygpo.api.backend import get_device
31 from mygpo.users.models import Chapter
32 from mygpo.utils import parse_time, parse_request_body, normalize_feed_url
33 from mygpo.decorators import allowed_methods
34 from mygpo.api.basic_auth import require_valid_user, check_username
35 from mygpo.db.couchdb.episode import episode_for_podcast_url
36 from mygpo.db.couchdb.episode_state import episode_state_for_user_episode, \
37 update_episode_chapters
40 @csrf_exempt
41 @require_valid_user
42 @check_username
43 @never_cache
44 @allowed_methods(['POST', 'GET'])
45 def chapters(request, username):
47 now = datetime.utcnow()
48 now_ = int(time.mktime(now.timetuple()))
50 if request.method == 'POST':
51 req = parse_request_body(request)
53 if not 'podcast' in req:
54 return HttpResponseBadRequest('Podcast URL missing')
56 if not 'episode' in req:
57 return HttpResponseBadRequest('Episode URL missing')
59 podcast_url = req.get('podcast', '')
60 episode_url = req.get('episode', '')
61 update_urls = []
63 # podcast sanitizing
64 s_podcast_url = normalize_feed_url(podcast_url)
65 if s_podcast_url != podcast_url:
66 req['podcast'] = s_podcast_url
67 update_urls.append((podcast_url, s_podcast_url or ''))
69 # episode sanitizing
70 s_episode_url = normalize_feed_url(episode_url, 'episode')
71 if s_episode_url != episode_url:
72 req['episode'] = s_episode_url
73 update_urls.append((episode_url, s_episode_url or ''))
75 if (s_podcast_url != '') and (s_episode_url != ''):
76 try:
77 update_chapters(req, request.user)
78 except ParameterMissing, e:
79 return HttpResponseBadRequest(e)
81 return JsonResponse({
82 'update_url': update_url,
83 'timestamp': now_
86 elif request.method == 'GET':
87 if not 'podcast' in request.GET:
88 return HttpResponseBadRequest('podcast URL missing')
90 if not 'episode' in request.GET:
91 return HttpResponseBadRequest('Episode URL missing')
93 podcast_url = request.GET['podcast']
94 episode_url = request.GET['episode']
96 since_ = request.GET.get('since', None)
97 try:
98 since = datetime.fromtimestamp(float(since_)) if since_ else None
99 except ValueError:
100 return HttpResponseBadRequest('since-value is not a valid timestamp')
102 podcast_url = normalize_feed_url(podcast_url)
103 episode_url = normalize_feed_url(episode_url)
104 episode = episode_for_podcast_url(podcast_url, episode_url)
106 if episode is None:
107 raise Http404
109 e_state = episode_state_for_user_episode(request.user, episode)
111 chapterlist = sorted(e_state.chapters, key=lambda c: c.start)
113 if since:
114 chapterlist = filter(lambda c: c.created >= since, chapters)
116 chapters = []
117 for c in chapterlist:
118 if c.device is not None:
119 device = request.user.get_device(c.device)
120 device_uid = device.uid
121 else:
122 device_uid = None
124 chapters.append({
125 'start': c.start,
126 'end': c.end,
127 'label': c.label,
128 'advertisement': c.advertisement,
129 'timestamp': c.created,
130 'device': device_uid
133 return JsonResponse({
134 'chapters': chapters,
135 'timestamp': now_
139 def update_chapters(req, user):
140 podcast_url = normalize_feed_url(req['podcast'])
141 episode_url = normalize_feed_url(req['episode'])
143 episode = episode_for_podcast_url(podcast_url, episode_url,
144 create=True)
146 e_state = episode_state_for_user_episode(request.user, episode)
148 device = None
149 if 'device' in req:
150 device = get_device(request.user, req['device'],
151 request.META.get('HTTP_USER_AGENT', ''), undelete=True)
153 timestamp = dateutil.parser.parse(req['timestamp']) if 'timestamp' in req else datetime.utcnow()
155 new_chapters = parse_new_chapters(request.user, req.get('chapters_add', []))
156 rem_chapters = parse_rem_chapters(req.get('chapters_remove', []))
158 update_episode_chapters(e_state, new_chapters, rem_chapters)
162 def parse_new_chapters(user, chapters):
163 for c in chapters:
164 if not 'start' in c:
165 raise ParameterMissing('start parameter missing')
166 start = parse_time(c['start'])
168 if not 'end' in c:
169 raise ParameterMissing('end parameter missing')
170 end = parse_time(c['end'])
172 label = c.get('label', '')
173 adv = c.get('advertisement', False)
175 device_uid = c.get('device', None)
176 if device_uid:
177 device_id = get_device(user, device_uid,
178 request.META.get('HTTP_USER_AGENT', ''), undelete=True).id
179 else:
180 device_id = None
182 chapter = Chapter()
183 chapter.device = device_id
184 chapter.created = timestamp
185 chapter.start = start
186 chapter.end = end
187 chapter.label = label
188 chapter.advertisement = adv
190 yield chapter
193 def parse_rem_chapters(chapers):
194 for c in chapters:
195 if not 'start' in c:
196 raise ParameterMissing('start parameter missing')
197 start = parse_time(c['start'])
199 if not 'end' in c:
200 raise ParameterMissing('end parameter missing')
201 end = parse_time(c['end'])
203 yield (start, end)