2012-03-27 14:58:13 -07:00
|
|
|
# Adapted from djpubsubhubbub. See License: http://git.participatoryculture.org/djpubsubhubbub/tree/LICENSE
|
|
|
|
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
import feedparser
|
2012-03-27 17:34:39 -07:00
|
|
|
import requests
|
2012-03-27 14:58:13 -07:00
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
from django.contrib.sites.models import Site
|
|
|
|
from django.core.urlresolvers import reverse, Resolver404
|
|
|
|
from django.db import models
|
|
|
|
from django.utils.hashcompat import sha_constructor
|
|
|
|
|
2012-03-27 16:26:07 -07:00
|
|
|
from apps.push import signals
|
|
|
|
from apps.rss_feeds.models import Feed
|
2012-03-27 18:51:29 -07:00
|
|
|
from utils import log as logging
|
2012-03-27 14:58:13 -07:00
|
|
|
|
|
|
|
DEFAULT_LEASE_SECONDS = 2592000 # 30 days in seconds
|
|
|
|
|
2012-03-27 16:26:07 -07:00
|
|
|
class PushSubscriptionManager(models.Manager):
|
2012-03-27 14:58:13 -07:00
|
|
|
|
2012-03-27 16:26:07 -07:00
|
|
|
def subscribe(self, topic, feed, hub=None, callback=None,
|
2012-03-27 14:58:13 -07:00
|
|
|
lease_seconds=None):
|
|
|
|
if hub is None:
|
|
|
|
hub = self._get_hub(topic)
|
|
|
|
|
|
|
|
if hub is None:
|
2012-03-28 11:59:30 -07:00
|
|
|
raise TypeError('hub cannot be None if the feed does not provide it')
|
2012-03-27 14:58:13 -07:00
|
|
|
|
|
|
|
if lease_seconds is None:
|
|
|
|
lease_seconds = getattr(settings, 'PUBSUBHUBBUB_LEASE_SECONDS',
|
|
|
|
DEFAULT_LEASE_SECONDS)
|
|
|
|
|
|
|
|
subscription, created = self.get_or_create(
|
2012-03-27 16:26:07 -07:00
|
|
|
hub=hub, topic=topic, feed=feed)
|
2012-03-27 14:58:13 -07:00
|
|
|
signals.pre_subscribe.send(sender=subscription, created=created)
|
|
|
|
subscription.set_expiration(lease_seconds)
|
|
|
|
|
|
|
|
if callback is None:
|
|
|
|
try:
|
2012-03-28 11:59:30 -07:00
|
|
|
callback_path = reverse('push-callback', args=(subscription.pk,))
|
2012-03-27 14:58:13 -07:00
|
|
|
except Resolver404:
|
2012-03-27 18:37:04 -07:00
|
|
|
raise TypeError('callback cannot be None if there is not a reverable URL')
|
2012-03-27 14:58:13 -07:00
|
|
|
else:
|
2012-03-28 11:59:30 -07:00
|
|
|
# callback = 'http://' + Site.objects.get_current() + callback_path
|
|
|
|
callback = 'http://' + "dev.newsblur.com" + callback_path
|
2012-03-27 14:58:13 -07:00
|
|
|
|
|
|
|
response = self._send_request(hub, {
|
2012-03-27 17:34:39 -07:00
|
|
|
'hub.mode': 'subscribe',
|
|
|
|
'hub.callback': callback,
|
|
|
|
'hub.topic': topic,
|
|
|
|
'hub.verify': ['async', 'sync'],
|
|
|
|
'hub.verify_token': subscription.generate_token('subscribe'),
|
|
|
|
'hub.lease_seconds': lease_seconds,
|
|
|
|
})
|
2012-03-27 18:37:04 -07:00
|
|
|
|
2012-03-27 17:34:39 -07:00
|
|
|
if response.status_code == 204:
|
2012-03-27 14:58:13 -07:00
|
|
|
subscription.verified = True
|
2012-03-27 17:34:39 -07:00
|
|
|
elif response.status_code == 202: # async verification
|
2012-03-27 14:58:13 -07:00
|
|
|
subscription.verified = False
|
|
|
|
else:
|
2012-03-27 17:34:39 -07:00
|
|
|
error = response.content
|
2012-03-27 18:51:29 -07:00
|
|
|
logging.debug(u' ---> [%-30s] ~FR~BKFeed failed to subscribe to push: %s' % (
|
|
|
|
unicode(subscription.feed)[:30], error))
|
2012-03-27 14:58:13 -07:00
|
|
|
|
|
|
|
subscription.save()
|
2012-03-27 18:37:04 -07:00
|
|
|
feed.setup_push()
|
2012-03-27 14:58:13 -07:00
|
|
|
if subscription.verified:
|
|
|
|
signals.verified.send(sender=subscription)
|
|
|
|
return subscription
|
|
|
|
|
|
|
|
|
|
|
|
def _get_hub(self, topic):
|
|
|
|
parsed = feedparser.parse(topic)
|
|
|
|
for link in parsed.feed.links:
|
|
|
|
if link['rel'] == 'hub':
|
|
|
|
return link['href']
|
|
|
|
|
|
|
|
def _send_request(self, url, data):
|
2012-03-27 17:34:39 -07:00
|
|
|
return requests.post(url, data=data)
|
2012-03-27 14:58:13 -07:00
|
|
|
|
2012-03-27 16:26:07 -07:00
|
|
|
class PushSubscription(models.Model):
|
|
|
|
feed = models.OneToOneField(Feed, db_index=True, related_name='push')
|
|
|
|
hub = models.URLField(db_index=True)
|
|
|
|
topic = models.URLField(db_index=True)
|
2012-03-27 14:58:13 -07:00
|
|
|
verified = models.BooleanField(default=False)
|
|
|
|
verify_token = models.CharField(max_length=60)
|
|
|
|
lease_expires = models.DateTimeField(default=datetime.now)
|
|
|
|
|
2012-03-27 16:26:07 -07:00
|
|
|
objects = PushSubscriptionManager()
|
2012-03-27 14:58:13 -07:00
|
|
|
|
|
|
|
# class Meta:
|
|
|
|
# unique_together = [
|
|
|
|
# ('hub', 'topic')
|
|
|
|
# ]
|
|
|
|
|
|
|
|
def set_expiration(self, lease_seconds):
|
|
|
|
self.lease_expires = datetime.now() + timedelta(
|
|
|
|
seconds=lease_seconds)
|
|
|
|
self.save()
|
|
|
|
|
|
|
|
def generate_token(self, mode):
|
|
|
|
assert self.pk is not None, \
|
|
|
|
'Subscription must be saved before generating token'
|
|
|
|
token = mode[:20] + sha_constructor('%s%i%s' % (
|
|
|
|
settings.SECRET_KEY, self.pk, mode)).hexdigest()
|
|
|
|
self.verify_token = token
|
|
|
|
self.save()
|
|
|
|
return token
|
|
|
|
|
|
|
|
def __unicode__(self):
|
|
|
|
if self.verified:
|
|
|
|
verified = u'verified'
|
|
|
|
else:
|
|
|
|
verified = u'unverified'
|
|
|
|
return u'to %s on %s: %s' % (
|
|
|
|
self.topic, self.hub, verified)
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return str(unicode(self))
|