def update(self, podcast_url): """ Update the podcast for the supplied URL """ try: parsed = self._fetch_feed(podcast_url) self._validate_parsed(parsed) except (ParserException, FetchFeedException, NoEpisodesException, VimeoError, ValueError, socket.error, urllib2.HTTPError) as ex: #TODO: catch valueError (for invalid Ipv6 in feedservice) if isinstance(ex, VimeoError): logger.exception('Problem when updating Vimeo feed %s', podcast_url) # if we fail to parse the URL, we don't even create the # podcast object p = podcast_for_url(podcast_url, create=False) if p: # if it exists already, we mark it as outdated self._mark_outdated(p, 'error while fetching feed: %s' % str(ex)) return p else: raise NoPodcastCreated(ex) assert parsed, 'fetch_feed must return something' p = podcast_for_url(podcast_url, create=True) episodes = self._update_episodes(p, parsed.episodes) self._update_podcast(p, parsed, episodes) return p
def upload(request): try: emailaddr = request.POST['username'] password = request.POST['password'] action = request.POST['action'] protocol = request.POST['protocol'] opml = request.FILES['opml'].read() except MultiValueDictKeyError: return HttpResponse("@PROTOERROR", mimetype='text/plain') user = auth(emailaddr, password) if (not user): return HttpResponse('@AUTHFAIL', mimetype='text/plain') dev = get_device(user, LEGACY_DEVICE_UID, request.META.get('HTTP_USER_AGENT', '')) existing_urls = [x.url for x in dev.get_subscribed_podcasts()] i = Importer(opml) podcast_urls = [p['url'] for p in i.items] podcast_urls = map(normalize_feed_url, podcast_urls) podcast_urls = filter(None, podcast_urls) new = [u for u in podcast_urls if u not in existing_urls] rem = [u for e in existing_urls if u not in podcast_urls] #remove duplicates new = list(set(new)) rem = list(set(rem)) for n in new: p = podcast_for_url(n, create=True) try: p.subscribe(user, dev) except SubscriptionException as e: logger.exception('Legacy API: %(username)s: could not subscribe to podcast %(podcast_url)s on device %(device_id)s' % {'username': user.username, 'podcast_url': p.url, 'device_id': dev.id}) for r in rem: p = podcast_for_url(r, create=True) try: p.unsubscribe(user, dev) except SubscriptionException as e: logger.exception('Legacy API: %(username): could not unsubscribe from podcast %(podcast_url) on device %(device_id)' % {'username': user.username, 'podcast_url': p.url, 'device_id': dev.id}) return HttpResponse('@SUCCESS', mimetype='text/plain')
def create(request, username, format): """ Creates a new podcast list and links to it in the Location header """ title = request.GET.get('title', None) if not title: return HttpResponseBadRequest('Title missing') slug = slugify(title) if not slug: return HttpResponseBadRequest('Invalid title') plist = podcastlist_for_user_slug(request.user._id, slug) if plist: return HttpResponse('List already exists', status=409) urls = parse_subscription(request.body, format) podcasts = [podcast_for_url(url, create=True) for url in urls] podcast_ids = map(Podcast.get_id, podcasts) plist = PodcastList() plist.created_timestamp = get_timestamp(datetime.utcnow()) plist.title = title plist.slug = slug plist.user = request.user._id plist.podcasts = podcast_ids plist.save() response = HttpResponse(status=201) list_url = reverse('api-get-list', args=[request.user.username, slug, format]) response['Location'] = list_url return response
def handle(self, *args, **options): if len(args) != 5: print 'Usage: ./manage.py group-podcasts <url1> <url2> <group-name> <name1> <name2>' return p1_url = args[0] p2_url = args[1] group_title = args[2] myname = args[3] othername = args[4] p1 = podcast_for_url(p1_url) p2 = podcast_for_url(p2_url) p1.group_with(p2, group_title, myname, othername)
def handle(self, *args, **options): docs = set() progress(0, len(docs), '', stream=sys.stderr) for username in options.get('users', []): user = User.get_user(username) self.add_user_recursive(user, docs) if options.get('toplist', False): toplist = PodcastToplist() for n, podcast in toplist[:25]: self.add_podcast_recursive(podcast, docs) for podcast_url in options.get('podcasts'): podcast = podcast_for_url(podcast_url, docs) if not podcast: logger.warn('podcast not found for URL "%s"', podcast_url) else: self.add_podcast_recursive(podcast, docs) db = get_main_database() docs = sorted(docs) self.dump(docs, db)
def list_favorites(request): user = request.user site = RequestSite(request) episodes = favorite_episodes_for_user(user) recently_listened = get_latest_episodes(user) podcast_ids = [episode.podcast for episode in episodes + recently_listened] podcasts = podcasts_to_dict(podcast_ids) recently_listened = fetch_episode_data(recently_listened, podcasts=podcasts) episodes = fetch_episode_data(episodes, podcasts=podcasts) favfeed = FavoriteFeed(user) feed_url = favfeed.get_public_url(site.domain) podcast = podcast_for_url(feed_url) token = request.user.favorite_feeds_token return render(request, 'favorites.html', { 'episodes': episodes, 'feed_token': token, 'site': site, 'podcast': podcast, 'recently_listened': recently_listened, })
def _get_obj_fun(self, action): url, op = action podcast = self.podcasts.get(url, podcast_for_url(url, create=True)) state = podcast_state_for_user_podcast(self.user, podcast) fun = self.operations[op] return (state, fun)
def post(self, request): podcast_url = request.POST.get('feed') podcast = podcast_for_url(podcast_url) if not podcast: messages.error(request, _('Podcast with URL "%s" does not exist' % (podcast_url,))) return HttpResponseRedirect(reverse('admin-unify-slugs-select')) res = unify_slugs.delay(podcast) return HttpResponseRedirect(reverse('admin-unify-slugs-status', args=[res.task_id]))
def search_podcast(request): form = SearchPodcastForm(request.POST) if form.is_valid(): url = form.cleaned_data['url'] podcast = podcast_for_url(url) if not podcast: raise Http404 url = get_podcast_link_target(podcast, 'podcast-publisher-detail') else: url = reverse('publisher') return HttpResponseRedirect(url)
def search_podcasts(q, limit=20, skip=0): if is_url(q): url = normalize_feed_url(q) podcast = podcast_for_url(url, create=False) if not podcast or not podcast.title: updater = PodcastUpdater() try: updater.update(url) except NoPodcastCreated as npc: return [], 0 podcast = podcast_for_url(url) if podcast: return [podcast], 1 else: return [], 0 return search(q, skip, limit)
def handle(self, *args, **options): urls = list(map(str.strip, fileinput.input(args))) try: examples = ExamplePodcasts.get(EXAMPLES_DOCID) except ResourceNotFound: examples = ExamplePodcasts() examples._id = EXAMPLES_DOCID podcasts = filter(None, [podcast_for_url(url) for url in urls]) examples.podcast_ids = [podcast.get_id() for podcast in podcasts] examples.updated = datetime.utcnow() examples.save()
def subscribe_url(request): url = request.GET.get('url', None) if not url: raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml') url = normalize_feed_url(url) if not url: raise Http404('Please specify a valid url') podcast = podcast_for_url(url, create=True) return HttpResponseRedirect(get_podcast_link_target(podcast, 'subscribe'))
def podcast_info(request): url = normalize_feed_url(request.GET.get('url', '')) # 404 before we query for url, because query would complain # about missing param if not url: raise Http404 podcast = podcast_for_url(url) if not podcast: raise Http404 domain = RequestSite(request).domain resp = podcast_data(podcast, domain) return JsonResponse(resp)
def episode_for_podcast_url(podcast_url, episode_url, create=False): if not podcast_url: raise QueryParameterMissing('podcast_url') if not episode_url: raise QueryParameterMissing('episode_url') podcast = podcast_for_url(podcast_url, create=create) if not podcast: # podcast does not exist and should not be created return None return episode_for_podcast_id_url(podcast.get_id(), episode_url, create)
def get(self, request): user = request.user favfeed = FavoriteFeed(user) site = RequestSite(request) feed_url = favfeed.get_public_url(site.domain) podcast = podcast_for_url(feed_url) token = request.user.favorite_feeds_token return render(request, 'share/favorites.html', { 'feed_token': token, 'site': site, 'podcast': podcast, })
def post(self, request): user = request.user feed = FavoriteFeed(user) site = RequestSite(request) feed_url = feed.get_public_url(site.domain) podcast = podcast_for_url(feed_url, create=True) if not podcast.get_id() in user.published_objects: user.published_objects.append(podcast.get_id()) user.save() updater = PodcastUpdater() updater.update(feed_url) return HttpResponseRedirect(reverse('share-favorites'))
def overview(request): user = request.user site = RequestSite(request) subscriptions_token = user.get_token('subscriptions_token') userpage_token = user.get_token('userpage_token') favfeed_token = user.get_token('favorite_feeds_token') favfeed = FavoriteFeed(user) favfeed_url = favfeed.get_public_url(site.domain) favfeed_podcast = podcast_for_url(favfeed_url) return render(request, 'share/overview.html', { 'site': site, 'subscriptions_token': subscriptions_token, 'userpage_token': userpage_token, 'favfeed_token': favfeed_token, 'favfeed_podcast': favfeed_podcast, })
def test_merge_podcasts(self): self.podcast2.subscribe(self.user, self.device) # merge podcast2 into podcast1 pm = PodcastMerger([self.podcast1, self.podcast2], Counter(), []) pm.merge() # seems that setting delayed_commit = false in the CouchDB config, as # well as a delay here fix the intermittent failures. # TODO: further investiation needed import time time.sleep(2) # get podcast for URL of podcast2 and unsubscribe from it p = podcast_for_url(self.P2_URL) p.unsubscribe(self.user, self.device) subscriptions = subscribed_podcast_ids_by_user_id(self.user._id) self.assertEqual(0, len(subscriptions))
def update_list(request, plist, owner, format): """ Replaces the podcasts in the list and returns 204 No Content """ is_own = owner == request.uuser if not is_own: return HttpResponseForbidden() urls = parse_subscription(request.body, format) podcasts = [podcast_for_url(url, create=True) for url in urls] podcast_ids = map(Podcast.get_id, podcasts) @repeat_on_conflict(['podcast_ids']) def _update(plist, podcast_ids): plist.podcasts = podcast_ids plist.save() _update(plist=plist, podcast_ids=podcast_ids) return HttpResponse(status=204)
def episode_state_for_ref_urls(user, podcast_url, episode_url): if not user: raise QueryParameterMissing('user') if not podcast_url: raise QueryParameterMissing('podcast_url') if not episode_url: raise QueryParameterMissing('episode_url') cache_key = 'episode-state-%s-%s-%s' % (user._id, sha1(podcast_url).hexdigest(), sha1(episode_url).hexdigest()) state = cache.get(cache_key) if state: return state udb = get_userdata_database() state = get_single_result(udb, 'episode_states/by_ref_urls', key = [user._id, podcast_url, episode_url], limit = 1, include_docs=True, schema = EpisodeUserState, ) if state: state.ref_url = episode_url state.podcast_ref_url = podcast_url cache.set(cache_key, state, 60*60) return state else: podcast = podcast_for_url(podcast_url, create=True) episode = episode_for_podcast_id_url(podcast.get_id(), episode_url, create=True) return episode_state_for_user_episode(user, episode)
def post(self, request): username = request.POST.get('username') user = User.get_user(username) if user is None: messages.error(request, 'User "{username}" not found'.format(username=username)) return HttpResponseRedirect(reverse('admin-make-publisher-input')) feeds = request.POST.get('feeds') feeds = feeds.split() podcasts = set() for feed in feeds: podcast = podcast_for_url(feed) if podcast is None: messages.warning(request, 'Podcast with URL {feed} not found'.format(feed=feed)) continue podcasts.add(podcast) self.set_publisher(request, user, podcasts) self.send_mail(request, user, podcasts) return HttpResponseRedirect(reverse('admin-make-publisher-result'))
def get(self, request): site = RequestSite(request) # check if we're doing a query url = request.GET.get('q', None) if not url: podcast = None can_add = False else: podcast = podcast_for_url(url) # if the podcast does already exist, there's nothing more to do if podcast: can_add = False # check if we could add a podcast for the given URL else: podcast = False updater = PodcastUpdater() try: can_add = updater.verify_podcast_url(url) except (ParserException, FetchFeedException, NoEpisodesException) as ex: can_add = False messages.error(request, unicode(ex)) return render(request, 'missing.html', { 'site': site, 'q': url, 'podcast': podcast, 'can_add': can_add, })
def _update_podcast(self, podcast, parsed, episodes): """ updates a podcast according to new parser results """ # we need that later to decide if we can "bump" a category prev_latest_episode_timestamp = podcast.latest_episode_timestamp old_json = copy.deepcopy(podcast.to_json()) podcast.title = parsed.title or podcast.title podcast.urls = list(set(podcast.urls + parsed.urls)) podcast.description = parsed.description or podcast.description podcast.subtitle = parsed.subtitle or podcast.subtitle podcast.link = parsed.link or podcast.link podcast.logo_url = parsed.logo or podcast.logo_url podcast.author = parsed.author or podcast.author podcast.language = parsed.language or podcast.language podcast.content_types = parsed.content_types or podcast.content_types podcast.tags['feed'] = parsed.tags or podcast.tags.get('feed', []) podcast.common_episode_title = parsed.common_title or podcast.common_episode_title podcast.new_location = parsed.new_location or podcast.new_location podcast.flattr_url = parsed.flattr or podcast.flattr_url podcast.hub = parsed.hub or podcast.hub podcast.license = parsed.license or podcast.license if podcast.new_location: new_podcast = podcast_for_url(podcast.new_location) if new_podcast != podcast: self._mark_outdated(podcast, 'redirected to different podcast') return elif not new_podcast: podcast.urls.insert(0, podcast.new_location) logger.info('Retrieved %d episodes in total', len(episodes)) # latest episode timestamp eps = filter(lambda e: bool(e.released), episodes) eps = sorted(eps, key=lambda e: e.released) podcast.update_interval = get_update_interval(eps) if eps: podcast.latest_episode_timestamp = eps[-1].released podcast.episode_count = episode_count_for_podcast(podcast) self._update_categories(podcast, prev_latest_episode_timestamp) # try to download the logo and reset logo_url to None on http errors found = self._save_podcast_logo(podcast.logo_url) if not found: podcast.logo_url = None # The podcast is always saved (not just when there are changes) because # we need to record the last update logger.info('Saving podcast.') podcast.last_update = datetime.utcnow() podcast.save() try: subscribe_at_hub(podcast) except SubscriptionError as se: logger.warn('subscribing to hub failed: %s', str(se)) assign_slug(podcast, PodcastSlug) assign_missing_episode_slugs(podcast)
def podcast_settings(user, url): podcast = podcast_for_url(url) if not podcast: raise Http404 obj = podcast_state_for_user_podcast(user, podcast) return obj, obj, udb
def episodes(request, username, version=1): version = int(version) now = datetime.now() now_ = get_timestamp(now) ua_string = request.META.get('HTTP_USER_AGENT', '') if request.method == 'POST': try: actions = parse_request_body(request) except (JSONDecodeError, UnicodeDecodeError, ValueError) as e: msg = ('Could not decode episode update POST data for ' + 'user %s: %s') % (username, request.body.decode('ascii', errors='replace')) logger.warn(msg, exc_info=True) return HttpResponseBadRequest(msg) logger.info('start: user %s: %d actions from %s' % (request.user._id, len(actions), ua_string)) # handle in background if len(actions) > dsettings.API_ACTIONS_MAX_NONBG: bg_handler = dsettings.API_ACTIONS_BG_HANDLER if bg_handler is not None: modname, funname = bg_handler.rsplit('.', 1) mod = import_module(modname) fun = getattr(mod, funname) fun(request.user, actions, now, ua_string) # TODO: return 202 Accepted return JsonResponse({'timestamp': now_, 'update_urls': []}) try: update_urls = update_episodes(request.user, actions, now, ua_string) except DeviceUIDException as e: logger.warn('invalid device UID while uploading episode actions for user %s', username) return HttpResponseBadRequest(str(e)) except InvalidEpisodeActionAttributes as e: msg = 'invalid episode action attributes while uploading episode actions for user %s' % (username,) logger.warn(msg, exc_info=True) return HttpResponseBadRequest(str(e)) logger.info('done: user %s: %d actions from %s' % (request.user._id, len(actions), ua_string)) return JsonResponse({'timestamp': now_, 'update_urls': update_urls}) elif request.method == 'GET': podcast_url= request.GET.get('podcast', None) device_uid = request.GET.get('device', None) since_ = request.GET.get('since', None) aggregated = parse_bool(request.GET.get('aggregated', False)) try: since = int(since_) if since_ else None except ValueError: return HttpResponseBadRequest('since-value is not a valid timestamp') if podcast_url: podcast = podcast_for_url(podcast_url) if not podcast: raise Http404 else: podcast = None if device_uid: try: device = request.user.get_device_by_uid(device_uid) except DeviceDoesNotExist as e: return HttpResponseNotFound(str(e)) else: device = None changes = get_episode_changes(request.user, podcast, device, since, now_, aggregated, version) return JsonResponse(changes)