Index: client/samples/swarm/appengine/main.py |
=================================================================== |
--- client/samples/swarm/appengine/main.py (revision 3770) |
+++ client/samples/swarm/appengine/main.py (working copy) |
@@ -1,739 +0,0 @@ |
-# Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
-# for details. All rights reserved. Use of this source code is governed by a |
-# BSD-style license that can be found in the LICENSE file. |
- |
-#!/usr/bin/env python |
-# |
-import re, base64, logging, pickle, httplib2, time, urlparse, urllib2, urllib, StringIO, gzip, zipfile |
- |
-from google.appengine.ext import webapp, db |
- |
-from google.appengine.api import taskqueue, urlfetch, memcache, images, users |
-from google.appengine.ext.webapp.util import login_required |
-from google.appengine.ext.webapp import template |
- |
-from django.utils import simplejson as json |
-from django.utils.html import strip_tags |
- |
-from oauth2client.appengine import CredentialsProperty |
-from oauth2client.client import OAuth2WebServerFlow |
- |
-import encoder |
- |
-# TODO(jimhug): Allow client to request desired thumb size. |
-THUMB_SIZE = (57, 57) |
-READER_API = 'http://www.google.com/reader/api/0' |
- |
-MAX_SECTIONS = 5 |
-MAX_ARTICLES = 20 |
- |
-class UserData(db.Model): |
- credentials = CredentialsProperty() |
- sections = db.ListProperty(db.Key) |
- |
- def getEncodedData(self, articleKeys=None): |
- enc = encoder.Encoder() |
- # TODO(jimhug): Only return initially visible section in first reply. |
- maxSections = min(MAX_SECTIONS, len(self.sections)) |
- enc.writeInt(maxSections) |
- for section in db.get(self.sections[:maxSections]): |
- section.encode(enc, articleKeys) |
- return enc.getRaw() |
- |
- |
-class Section(db.Model): |
- title = db.TextProperty() |
- feeds = db.ListProperty(db.Key) |
- |
- def fixedTitle(self): |
- return self.title.split('_')[0] |
- |
- def encode(self, enc, articleKeys=None): |
- # TODO(jimhug): Need to optimize format and support incremental updates. |
- enc.writeString(self.key().name()) |
- enc.writeString(self.fixedTitle()) |
- enc.writeInt(len(self.feeds)) |
- for feed in db.get(self.feeds): |
- feed.ensureEncodedFeed() |
- enc.writeRaw(feed.encodedFeed3) |
- if articleKeys is not None: |
- articleKeys.extend(feed.topArticles) |
- |
-class Feed(db.Model): |
- title = db.TextProperty() |
- iconUrl = db.TextProperty() |
- lastUpdated = db.IntegerProperty() |
- |
- encodedFeed3 = db.TextProperty() |
- topArticles = db.ListProperty(db.Key) |
- |
- def ensureEncodedFeed(self, force=False): |
- if force or self.encodedFeed3 is None: |
- enc = encoder.Encoder() |
- articleSet = [] |
- self.encode(enc, MAX_ARTICLES, articleSet) |
- logging.info('articleSet length is %s' % len(articleSet)) |
- self.topArticles = articleSet |
- self.encodedFeed3 = enc.getRaw() |
- self.put() |
- |
- def encode(self, enc, maxArticles, articleSet): |
- enc.writeString(self.key().name()) |
- enc.writeString(self.title) |
- enc.writeString(self.iconUrl) |
- |
- logging.info('encoding feed: %s' % self.title) |
- encodedArts = [] |
- |
- for article in self.article_set.order('-date').fetch(limit=maxArticles): |
- encodedArts.append(article.encodeHeader()) |
- articleSet.append(article.key()) |
- |
- enc.writeInt(len(encodedArts)) |
- enc.writeRaw(''.join(encodedArts)) |
- |
- |
-class Article(db.Model): |
- feed = db.ReferenceProperty(Feed) |
- |
- title = db.TextProperty() |
- author = db.TextProperty() |
- content = db.TextProperty() |
- snippet = db.TextProperty() |
- thumbnail = db.BlobProperty() |
- thumbnailSize = db.TextProperty() |
- srcurl = db.TextProperty() |
- date = db.IntegerProperty() |
- |
- def ensureThumbnail(self): |
- # If our desired thumbnail size has changed, regenerate it and cache. |
- if self.thumbnailSize != str(THUMB_SIZE): |
- self.thumbnail = makeThumbnail(self.content) |
- self.thumbnailSize = str(THUMB_SIZE) |
- self.put() |
- |
- def encodeHeader(self): |
- # TODO(jmesserly): for now always unescape until the crawler catches up |
- enc = encoder.Encoder() |
- enc.writeString(self.key().name()) |
- enc.writeString(unescape(self.title)) |
- enc.writeString(self.srcurl) |
- enc.writeBool(self.thumbnail is not None) |
- enc.writeString(self.author) |
- enc.writeInt(self.date) |
- enc.writeString(unescape(self.snippet)) |
- return enc.getRaw() |
- |
-class HtmlFile(db.Model): |
- content = db.BlobProperty() |
- compressed = db.BooleanProperty() |
- filename = db.StringProperty() |
- author = db.UserProperty(auto_current_user=True) |
- date = db.DateTimeProperty(auto_now_add=True) |
- |
- |
-class UpdateHtml(webapp.RequestHandler): |
- def post(self): |
- upload_files = self.request.POST.multi.__dict__['_items'] |
- version = self.request.get('version') |
- logging.info('files: %r' % upload_files) |
- for data in upload_files: |
- if data[0] != 'files': continue |
- file = data[1] |
- filename = file.filename |
- if version: |
- filename = '%s-%s' % (version, filename) |
- logging.info('upload: %r' % filename) |
- |
- htmlFile = HtmlFile.get_or_insert(filename) |
- htmlFile.filename = filename |
- |
- # If text > (1MB - 1KB) then gzip text to fit in 1MB space |
- text = file.value |
- if len(text) > 1024*1023: |
- data = StringIO.StringIO() |
- gz = gzip.GzipFile(str(filename), 'wb', fileobj=data) |
- gz.write(text) |
- gz.close() |
- htmlFile.content = data.getvalue() |
- htmlFile.compressed = True |
- else: |
- htmlFile.content = text |
- htmlFile.compressed = False |
- |
- htmlFile.put() |
- |
- self.redirect('/') |
- |
-class TopHandler(webapp.RequestHandler): |
- @login_required |
- def get(self): |
- user = users.get_current_user() |
- prefs = UserData.get_by_key_name(user.user_id()) |
- if prefs is None: |
- self.redirect('/update/user') |
- return |
- |
- params = {'files': HtmlFile.all().order('-date').fetch(limit=30)} |
- self.response.out.write(template.render('top.html', params)) |
- |
- |
-class MainHandler(webapp.RequestHandler): |
- |
- @login_required |
- def get(self, name): |
- if name == 'dev': |
- return self.handleDev() |
- |
- elif name == 'login': |
- return self.handleLogin() |
- |
- elif name == 'upload': |
- return self.handleUpload() |
- |
- user = users.get_current_user() |
- prefs = UserData.get_by_key_name(user.user_id()) |
- if prefs is None: |
- return self.handleLogin() |
- |
- html = HtmlFile.get_by_key_name(name) |
- if html is None: |
- self.error(404) |
- return |
- |
- self.response.headers['Content-Type'] = 'text/html' |
- |
- if html.compressed: |
- # TODO(jimhug): This slightly sucks ;-) |
- # Can we write directly to the response.out? |
- gz = gzip.GzipFile(name, 'rb', fileobj=StringIO.StringIO(html.content)) |
- self.response.out.write(gz.read()) |
- gz.close() |
- else: |
- self.response.out.write(html.content) |
- |
- # TODO(jimhug): Include first data packet with html. |
- |
- def handleLogin(self): |
- user = users.get_current_user() |
- # TODO(jimhug): Manage secrets for dart.googleplex.com better. |
- # TODO(jimhug): Confirm that we need client_secret. |
- flow = OAuth2WebServerFlow( |
- client_id='267793340506.apps.googleusercontent.com', |
- client_secret='5m8H-zyamfTYg5vnpYu1uGMU', |
- scope=READER_API, |
- user_agent='swarm') |
- |
- callback = self.request.relative_url('/oauth2callback') |
- authorize_url = flow.step1_get_authorize_url(callback) |
- |
- memcache.set(user.user_id(), pickle.dumps(flow)) |
- |
- content = template.render('login.html', {'authorize': authorize_url}) |
- self.response.out.write(content) |
- |
- def handleDev(self): |
- user = users.get_current_user() |
- content = template.render('dev.html', {'user': user}) |
- self.response.out.write(content) |
- |
- def handleUpload(self): |
- user = users.get_current_user() |
- content = template.render('upload.html', {'user': user}) |
- self.response.out.write(content) |
- |
- |
-class UploadFeed(webapp.RequestHandler): |
- def post(self): |
- upload_files = self.request.POST.multi.__dict__['_items'] |
- version = self.request.get('version') |
- logging.info('files: %r' % upload_files) |
- for data in upload_files: |
- if data[0] != 'files': continue |
- file = data[1] |
- logging.info('upload feed: %r' % file.filename) |
- |
- data = json.loads(file.value) |
- |
- feedId = file.filename |
- feed = Feed.get_or_insert(feedId) |
- |
- # Find the section to add it to. |
- sectionTitle = data['section'] |
- section = findSectionByTitle(sectionTitle) |
- if section != None: |
- if feed.key() in section.feeds: |
- logging.warn('Already contains feed %s, replacing' % feedId) |
- section.feeds.remove(feed.key()) |
- |
- # Add the feed to the section. |
- section.feeds.insert(0, feed.key()) |
- section.put() |
- |
- # Add the articles. |
- collectFeed(feed, data) |
- |
- else: |
- logging.error('Could not find section %s to add the feed to' % |
- sectionTitle) |
- |
- self.redirect('/') |
- |
-# TODO(jimhug): Batch these up and request them more agressively. |
-class DataHandler(webapp.RequestHandler): |
- def get(self, name): |
- if name.endswith('.jpg'): |
- # Must be a thumbnail |
- key = urllib2.unquote(name[:-len('.jpg')]) |
- article = Article.get_by_key_name(key) |
- self.response.headers['Content-Type'] = 'image/jpeg' |
- # cache images for 10 hours |
- self.response.headers['Cache-Control'] = 'public,max-age=36000' |
- article.ensureThumbnail() |
- self.response.out.write(article.thumbnail) |
- elif name.endswith('.html'): |
- # Must be article content |
- key = urllib2.unquote(name[:-len('.html')]) |
- article = Article.get_by_key_name(key) |
- self.response.headers['Content-Type'] = 'text/html' |
- if article is None: |
- content = '<h2>Missing article</h2>' |
- else: |
- content = article.content |
- # cache article content for 10 hours |
- self.response.headers['Cache-Control'] = 'public,max-age=36000' |
- self.response.out.write(content) |
- elif name == 'user.data': |
- self.response.out.write(self.getUserData()) |
- elif name == 'CannedData.dart': |
- self.canData() |
- elif name == 'CannedData.zip': |
- self.canDataZip() |
- else: |
- self.error(404) |
- |
- def getUserData(self, articleKeys=None): |
- user = users.get_current_user() |
- user_id = user.user_id() |
- |
- key = 'data_' + user_id |
- # need to flush memcache fairly frequently... |
- data = memcache.get(key) |
- if data is None: |
- prefs = UserData.get_or_insert(user_id) |
- if prefs is None: |
- # TODO(jimhug): Graceful failure for unknown users. |
- pass |
- data = prefs.getEncodedData(articleKeys) |
- # TODO(jimhug): memcache.set(key, data) |
- |
- return data |
- |
- def canData(self): |
- def makeDartSafe(data): |
- return repr(unicode(data))[1:].replace('$', '\\$') |
- |
- lines = ['// TODO(jimhug): Work out correct copyright for this file.', |
- 'class CannedData {'] |
- |
- user = users.get_current_user() |
- prefs = UserData.get_by_key_name(user.user_id()) |
- articleKeys = [] |
- data = prefs.getEncodedData(articleKeys) |
- lines.append(' static final Map<String,String> data = const {') |
- for article in db.get(articleKeys): |
- key = makeDartSafe(urllib.quote(article.key().name())+'.html') |
- lines.append(' %s:%s, ' % (key, makeDartSafe(article.content))) |
- |
- lines.append(' "user.data":%s' % makeDartSafe(data)) |
- |
- lines.append(' };') |
- |
- lines.append('}') |
- self.response.headers['Content-Type'] = 'application/dart' |
- self.response.out.write('\n'.join(lines)) |
- |
- # Get canned static data |
- def canDataZip(self): |
- # We need to zip into an in-memory buffer to get the right string encoding |
- # behavior. |
- data = StringIO.StringIO() |
- result = zipfile.ZipFile(data, 'w') |
- |
- articleKeys = [] |
- result.writestr('data/user.data', |
- self.getUserData(articleKeys).encode('utf-8')) |
- logging.info(' adding articles %s' % len(articleKeys)) |
- images = [] |
- for article in db.get(articleKeys): |
- article.ensureThumbnail() |
- path = 'data/' + article.key().name() + '.html' |
- result.writestr(path.encode('utf-8'), article.content.encode('utf-8')) |
- if article.thumbnail: |
- path = 'data/' + article.key().name() + '.jpg' |
- result.writestr(path.encode('utf-8'), article.thumbnail) |
- |
- result.close() |
- logging.info('writing CannedData.zip') |
- self.response.headers['Content-Type'] = 'multipart/x-zip' |
- disposition = 'attachment; filename=CannedData.zip' |
- self.response.headers['Content-Disposition'] = disposition |
- self.response.out.write(data.getvalue()) |
- data.close() |
- |
- |
-class SetDefaultFeeds(webapp.RequestHandler): |
- @login_required |
- def get(self): |
- user = users.get_current_user() |
- prefs = UserData.get_or_insert(user.user_id()) |
- |
- prefs.sections = [ |
- db.Key.from_path('Section', 'user/17857667084667353155/label/Top'), |
- db.Key.from_path('Section', 'user/17857667084667353155/label/Design'), |
- db.Key.from_path('Section', 'user/17857667084667353155/label/Eco'), |
- db.Key.from_path('Section', 'user/17857667084667353155/label/Geek'), |
- db.Key.from_path('Section', 'user/17857667084667353155/label/Google'), |
- db.Key.from_path('Section', 'user/17857667084667353155/label/Seattle'), |
- db.Key.from_path('Section', 'user/17857667084667353155/label/Tech'), |
- db.Key.from_path('Section', 'user/17857667084667353155/label/Web')] |
- |
- prefs.put() |
- |
- self.redirect('/') |
- |
-class SetTestFeeds(webapp.RequestHandler): |
- @login_required |
- def get(self): |
- user = users.get_current_user() |
- prefs = UserData.get_or_insert(user.user_id()) |
- |
- sections = [] |
- for i in range(3): |
- s1 = Section.get_or_insert('Test%d' % i) |
- s1.title = 'Section %d' % (i+1) |
- |
- feeds = [] |
- for j in range(4): |
- label = '%d_%d' % (i, j) |
- f1 = Feed.get_or_insert('Test%s' % label) |
- f1.title = 'Feed %s' % label |
- f1.iconUrl = getFeedIcon('http://google.com') |
- f1.lastUpdated = 0 |
- f1.put() |
- feeds.append(f1.key()) |
- |
- for k in range(8): |
- label = '%d_%d_%d' % (i, j, k) |
- a1 = Article.get_or_insert('Test%s' % label) |
- if a1.title is None: |
- a1.feed = f1 |
- a1.title = 'Article %s' % label |
- a1.author = 'anon' |
- a1.content = 'Lorem ipsum something or other...' |
- a1.snippet = 'Lorem ipsum something or other...' |
- a1.thumbnail = None |
- a1.srcurl = '' |
- a1.date = 0 |
- |
- s1.feeds = feeds |
- s1.put() |
- sections.append(s1.key()) |
- |
- prefs.sections = sections |
- prefs.put() |
- |
- self.redirect('/') |
- |
- |
-class UserLoginHandler(webapp.RequestHandler): |
- @login_required |
- def get(self): |
- user = users.get_current_user() |
- prefs = UserData.get_or_insert(user.user_id()) |
- if prefs.credentials: |
- http = prefs.credentials.authorize(httplib2.Http()) |
- |
- response, content = http.request('%s/subscription/list?output=json' % |
- READER_API) |
- self.collectFeeds(prefs, content) |
- self.redirect('/') |
- else: |
- self.redirect('/login') |
- |
- |
- def collectFeeds(self, prefs, content): |
- data = json.loads(content) |
- |
- queue_name = self.request.get('queue_name', 'priority-queue') |
- sections = {} |
- for feedData in data['subscriptions']: |
- feed = Feed.get_or_insert(feedData['id']) |
- feed.put() |
- category = feedData['categories'][0] |
- categoryId = category['id'] |
- if not sections.has_key(categoryId): |
- sections[categoryId] = (category['label'], []) |
- |
- # TODO(jimhug): Use Reader preferences to sort feeds in a section. |
- sections[categoryId][1].append(feed.key()) |
- |
- # Kick off a high priority feed update |
- taskqueue.add(url='/update/feed', queue_name=queue_name, |
- params={'id': feed.key().name()}) |
- |
- sectionKeys = [] |
- for name, (title, feeds) in sections.items(): |
- section = Section.get_or_insert(name) |
- section.feeds = feeds |
- section.title = title |
- section.put() |
- # Forces Top to be the first section |
- if title == 'Top': title = '0Top' |
- sectionKeys.append( (title, section.key()) ) |
- |
- # TODO(jimhug): Use Reader preferences API to get users true sort order. |
- prefs.sections = [key for t, key in sorted(sectionKeys)] |
- prefs.put() |
- |
- |
-class AllFeedsCollector(webapp.RequestHandler): |
- '''Ensures that a given feed object is locally up to date.''' |
- def post(self): return self.get() |
- |
- def get(self): |
- queue_name = self.request.get('queue_name', 'background') |
- for feed in Feed.all(): |
- taskqueue.add(url='/update/feed', queue_name=queue_name, |
- params={'id': feed.key().name()}) |
- |
-UPDATE_COUNT = 4 # The number of articles to request on periodic updates. |
-INITIAL_COUNT = 40 # The number of articles to get first for a new queue. |
-SNIPPET_SIZE = 180 # The length of plain-text snippet to extract. |
-class FeedCollector(webapp.RequestHandler): |
- def post(self): return self.get() |
- |
- def get(self): |
- feedId = self.request.get('id') |
- feed = Feed.get_or_insert(feedId) |
- |
- if feed.lastUpdated is None: |
- self.fetchn(feed, feedId, INITIAL_COUNT) |
- else: |
- self.fetchn(feed, feedId, UPDATE_COUNT) |
- |
- self.response.headers['Content-Type'] = "text/plain" |
- |
- def fetchn(self, feed, feedId, n, continuation=None): |
- # basic pattern is to read by ARTICLE_COUNT until we hit existing. |
- if continuation is None: |
- apiUrl = '%s/stream/contents/%s?n=%d' % ( |
- READER_API, feedId, n) |
- else: |
- apiUrl = '%s/stream/contents/%s?n=%d&c=%s' % ( |
- READER_API, feedId, n, continuation) |
- |
- logging.info('fetching: %s' % apiUrl) |
- result = urlfetch.fetch(apiUrl) |
- |
- if result.status_code == 200: |
- data = json.loads(result.content) |
- collectFeed(feed, data, continuation) |
- elif result.status_code == 401: |
- self.response.out.write( '<pre>%s</pre>' % result.content) |
- else: |
- self.response.out.write(result.status_code) |
- |
-def findSectionByTitle(title): |
- for section in Section.all(): |
- if section.fixedTitle() == title: |
- return section |
- return None |
- |
-def collectFeed(feed, data, continuation=None): |
- ''' |
- Reads a feed from the given JSON object and populates the given feed object |
- in the datastore with its data. |
- ''' |
- if continuation is None: |
- if 'alternate' in data: |
- feed.iconUrl = getFeedIcon(data['alternate'][0]['href']) |
- feed.title = data['title'] |
- feed.lastUpdated = data['updated'] |
- |
- articles = data['items'] |
- logging.info('%d new articles for %s' % (len(articles), feed.title)) |
- |
- for articleData in articles: |
- if not collectArticle(feed, articleData): |
- feed.put() |
- return False |
- |
- if len(articles) > 0 and data.has_key('continuation'): |
- logging.info('would have looked for more articles') |
- # TODO(jimhug): Enable this continuation check when more robust |
- #self.fetchn(feed, feedId, data['continuation']) |
- |
- feed.ensureEncodedFeed(force=True) |
- feed.put() |
- return True |
- |
-def collectArticle(feed, data): |
- ''' |
- Reads an article from the given JSON object and populates the datastore with |
- it. |
- ''' |
- if not 'title' in data: |
- # Skip this articles without titles |
- return True |
- |
- articleId = data['id'] |
- article = Article.get_or_insert(articleId) |
- # TODO(jimhug): This aborts too early - at lease for one adafruit case. |
- if article.date == data['published']: |
- logging.info('found existing, aborting: %r, %r' % |
- (articleId, article.date)) |
- return False |
- |
- if data.has_key('content'): |
- content = data['content']['content'] |
- elif data.has_key('summary'): |
- content = data['summary']['content'] |
- else: |
- content = '' |
- #TODO(jimhug): better summary? |
- article.content = content |
- article.date = data['published'] |
- article.title = unescape(data['title']) |
- article.snippet = unescape(strip_tags(content)[:SNIPPET_SIZE]) |
- |
- article.feed = feed |
- |
- # TODO(jimhug): make this canonical so UX can change for this state |
- article.author = data.get('author', 'anonymous') |
- |
- article.ensureThumbnail() |
- |
- article.srcurl = '' |
- if data.has_key('alternate'): |
- for alt in data['alternate']: |
- if alt.has_key('href'): |
- article.srcurl = alt['href'] |
- return True |
- |
-def unescape(html): |
- "Inverse of Django's utils.html.escape function" |
- if not isinstance(html, basestring): |
- html = str(html) |
- html = html.replace(''', "'").replace('"', '"') |
- return html.replace('>', '>').replace('<', '<').replace('&', '&') |
- |
-def getFeedIcon(url): |
- url = urlparse.urlparse(url).netloc |
- return 'http://s2.googleusercontent.com/s2/favicons?domain=%s&alt=feed' % url |
- |
-def findImage(text): |
- img = findImgTag(text, 'jpg|jpeg|png') |
- if img is not None: |
- return img |
- |
- img = findVideoTag(text) |
- if img is not None: |
- return img |
- |
- img = findImgTag(text, 'gif') |
- return img |
- |
-def findImgTag(text, extensions): |
- m = re.search(r'src="(http://\S+\.(%s))(\?.*)?"' % extensions, text) |
- if m is None: |
- return None |
- return m.group(1) |
- |
-def findVideoTag(text): |
- # TODO(jimhug): Add other videos beyond youtube. |
- m = re.search(r'src="http://www.youtube.com/(\S+)/(\S+)[/|"]', text) |
- if m is None: |
- return None |
- |
- return 'http://img.youtube.com/vi/%s/0.jpg' % m.group(2) |
- |
-def makeThumbnail(text): |
- url = None |
- try: |
- url = findImage(text) |
- if url is None: |
- return None |
- return generateThumbnail(url) |
- except: |
- logging.info('error decoding: %s' % (url or text)) |
- return None |
- |
-def generateThumbnail(url): |
- logging.info('generating thumbnail: %s' % url) |
- thumbWidth, thumbHeight = THUMB_SIZE |
- |
- result = urlfetch.fetch(url) |
- img = images.Image(result.content) |
- |
- w, h = img.width, img.height |
- |
- aspect = float(w) / h |
- thumbAspect = float(thumbWidth) / thumbHeight |
- |
- if aspect > thumbAspect: |
- # Too wide, so crop on the sides. |
- normalizedCrop = (w - h * thumbAspect) / (2.0 * w) |
- img.crop(normalizedCrop, 0., 1. - normalizedCrop, 1. ) |
- elif aspect < thumbAspect: |
- # Too tall, so crop out the bottom. |
- normalizedCrop = (h - w / thumbAspect) / h |
- img.crop(0., 0., 1., 1. - normalizedCrop) |
- |
- img.resize(thumbWidth, thumbHeight) |
- |
- # Chose JPEG encoding because informal experiments showed it generated |
- # the best size to quality ratio for thumbnail images. |
- nimg = img.execute_transforms(output_encoding=images.JPEG) |
- logging.info(' finished thumbnail: %s' % url) |
- |
- return nimg |
- |
-class OAuthHandler(webapp.RequestHandler): |
- |
- @login_required |
- def get(self): |
- user = users.get_current_user() |
- flow = pickle.loads(memcache.get(user.user_id())) |
- if flow: |
- prefs = UserData.get_or_insert(user.user_id()) |
- prefs.credentials = flow.step2_exchange(self.request.params) |
- prefs.put() |
- self.redirect('/update/user') |
- else: |
- pass |
- |
- |
-def main(): |
- application = webapp.WSGIApplication( |
- [ |
- ('/data/(.*)', DataHandler), |
- |
- # This is called periodically from cron.yaml. |
- ('/update/allFeeds', AllFeedsCollector), |
- ('/update/feed', FeedCollector), |
- ('/update/user', UserLoginHandler), |
- ('/update/defaultFeeds', SetDefaultFeeds), |
- ('/update/testFeeds', SetTestFeeds), |
- ('/update/html', UpdateHtml), |
- ('/update/upload', UploadFeed), |
- ('/oauth2callback', OAuthHandler), |
- |
- ('/', TopHandler), |
- ('/(.*)', MainHandler), |
- ], |
- debug=True) |
- webapp.util.run_wsgi_app(application) |
- |
-if __name__ == '__main__': |
- main() |