openwebapps-photosite-conne.../server.py

358 строки
12 KiB
Python
Executable File

#!/usr/bin/env python
#
import tornado.httpserver
import tornado.auth
import tornado.ioloop
import tornado.web
import os
import base64
import json
import hashlib
import config
import urllib
import cStringIO
import mimetools
import simplejson
# photo provider stuff
import picasa as photosite
class WebHandler(tornado.web.RequestHandler):
"base handler for this entire app"
def get_error_html(self, status_code, **kwargs):
return """
<html><title>Error!</title><style>.box {margin:16px;padding:8px;border:1px solid black;font:14pt Helvetica,arial}
.small {text-align:right;color:#888;font:italic 8pt Helvetica;}</style>
<body><div class='box'>We're sorry, something went wrong!<br><br>Perhaps
you should <a href='/'>return to the front page.</a><br><br><div class='small'>%s %s</div></div>
""" % (status_code, kwargs['exception'])
def render_platform(self, file, templates=False, **kwargs):
target_file = file
if "User-Agent" in self.request.headers:
UA = self.request.headers["User-Agent"]
if UA.find("iPhone") >= 0:
target_file = target_file + "_iphone"
if self.get_argument("cloak", None):
target_file = file + "_" + self.get_argument("cloak", None)
# is this dead code? Not sure what this is used for (Ben 2011-03-29)
tmpl = None
if templates:
f = open(target_file + ".tmpl", "r")
tmpl = f.read()
f.close() # cache this
self.render(target_file + ".html", templates=tmpl, **kwargs)
# Put auth_token in before you call this
def sign_request(self, request):
sigval = config.KEYS["flickrSecret"]
keys = request.keys()
keys.sort()
for k in keys:
sigval += unicode(k)
sigval += unicode(request[k])
sighash = hashlib.md5(sigval).hexdigest()
return sighash
# General, and user administration, handlers
class MainHandler(WebHandler):
def get(self):
self.set_header("X-XRDS-Location", "%s/xrds" % config.DOMAIN)
self.render_platform("index", user_info=None, errorMessage=None)
class XRDSHandler(WebHandler):
def get(self):
self.set_header("Content-Type", "application/xrds+xml")
self.write("""<?xml version="1.0" encoding="UTF-8"?>"""\
"""<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0" xmlns="xri://$xrd*($v*2.0)">"""\
"""<XRD><Service priority="1"><Type>https://specs.openid.net/auth/2.0/return_to</Type>"""\
"""<URI>%s/login</URI>"""\
"""</Service></XRD></xrds:XRDS>""" % config.DOMAIN)
class Connect(WebHandler):
@tornado.web.asynchronous
def get(self):
photosite.generate_authorize_url(self, self.on_response, self.on_error)
def on_response(self, request_token, response):
if request_token:
# a token to store for closing the connection loop
self.set_secure_cookie('request_token', simplejson.dumps(request_token))
self.redirect(response)
def on_error(self, error):
self.write("error: " + error)
self.finish()
class ConnectDone(WebHandler):
@tornado.web.asynchronous
def get(self):
request_token = simplejson.loads(self.get_secure_cookie('request_token'))
photosite.complete_authorization(self, request_token, self.on_success, self.on_error)
def on_success(self, user_id, full_name, credentials):
self.render_platform("index", user_info={'user_id': user_id, 'full_name' : full_name, 'credentials' : credentials})
def on_error(self, message):
self.write(message)
self.finish()
class GetPhotos(WebHandler):
@tornado.web.asynchronous
def get(self):
flickrUserId = self.get_argument("userid", None)
if not flickrUserId:
raise Exception("Missing required flickrUserId")
http = tornado.httpclient.AsyncHTTPClient()
url = "http://api.flickr.com/services/rest/?method=flickr.photosets.getList&api_key=" + config.KEYS["flickr"] + "&user_id=" + flickrUserid + "&format=json&nojsoncallback=1"
http.fetch(url, callback=self.on_response)
def on_response(self, response):
if response.error: raise tornado.web.HTTPError(500)
json = tornado.escape.json_decode(response.body)
self.write("Got something: " + response.body)
class Photosets(WebHandler):
@tornado.web.asynchronous
def get(self):
flickrUserId = self.get_argument("usernsid", None)
if not flickrUserId:
raise Exception("Missing required usernsid")
authToken = self.get_argument("token", None)
if not authToken:
raise Exception("Missing required token")
http = tornado.httpclient.AsyncHTTPClient()
request = {
"auth_token":authToken,
"api_key": config.KEYS["flickrAPIKey"],
"method":"flickr.photosets.getList",
"user_id":flickrUserId,
"format":"json",
"nojsoncallback":1,
}
signature = self.sign_request(request)
request["api_sig"] = signature
req = ["%s=%s" % (key, request[key]) for key in request.keys()] # XX urlescape
url = "http://api.flickr.com/services/rest/?%s" % "&".join(req)
http.fetch(url, callback=self.on_response)
def on_response(self, response):
if response.error: raise tornado.web.HTTPError(500)
json = tornado.escape.json_decode(response.body)
self.write(response.body)
self.finish()
class GetPhotos(WebHandler):
@tornado.web.asynchronous
def get(self):
photosetID = self.get_argument("photosetid", None)
if not photosetID:
raise Exception("Missing required photosetid")
authToken = self.get_argument("token", None)
if not authToken:
raise Exception("Missing required token")
http = tornado.httpclient.AsyncHTTPClient()
request = {
"auth_token":authToken,
"api_key": config.KEYS["flickrAPIKey"],
"method":"flickr.photosets.getPhotos",
"photoset_id": photosetID,
"extras": "url_sq,url_t,url_s,url_m,url_z,url_l,url_o,icon_server,tags",
"format":"json",
"nojsoncallback":1,
}
signature = self.sign_request(request)
request["api_sig"] = signature
req = ["%s=%s" % (key, request[key]) for key in request.keys()] # XX urlescape
url = "http://api.flickr.com/services/rest/?%s" % "&".join(req)
http.fetch(url, callback=self.on_response)
def on_response(self, response):
if response.error: raise tornado.web.HTTPError(500)
json = tornado.escape.json_decode(response.body)
self.write(response.body)
self.finish()
class GetPhotoSizes(WebHandler):
@tornado.web.asynchronous
def get(self):
photoID = self.get_argument("photoid", None)
if not photoid:
raise Exception("Missing required photoid")
authToken = self.get_argument("token", None)
if not authToken:
raise Exception("Missing required token")
http = tornado.httpclient.AsyncHTTPClient()
request = {
"auth_token":authToken,
"api_key": config.KEYS["flickrAPIKey"],
"method":"flickr.photos.getSizes",
"photo_id": photoID,
"format":"json",
"nojsoncallback":1,
}
signature = self.sign_request(request)
request["api_sig"] = signature
req = ["%s=%s" % (key, request[key]) for key in request.keys()] # XX urlescape
url = "http://api.flickr.com/services/rest/?%s" % "&".join(req)
http.fetch(url, callback=self.on_response)
def on_response(self, response):
if response.error: raise tornado.web.HTTPError(500)
json = tornado.escape.json_decode(response.body)
self.write(response.body)
self.finish()
class PostPhoto(WebHandler):
@tornado.web.asynchronous
def post(self):
try:
photo = self.get_argument("photo") #base64ed?
title = self.get_argument("title", None)
description = self.get_argument("description", None)
tags = self.get_argument("tags", None) # space-separated
# maybe hidden?
authToken = self.get_argument("token")
http = tornado.httpclient.AsyncHTTPClient()
request = {
"auth_token":authToken,
"api_key": config.KEYS["flickrAPIKey"],
}
if description: request["description"] = description
if title: request["title"] = title
if tags: request["tags"] = tags
signature = self.sign_request(request)
request["api_sig"] = signature
photoFile = cStringIO.StringIO(base64.b64decode(photo));
# files = {"thefile": photoFile}
boundary, body = multipart_encode(request.items(), [ ("photo", "thefile.jpg", photoFile, "image/jpg" ) ])
headers = { "Content-Type": "multipart/form-data; boundary=" + boundary }
httpRequest = tornado.httpclient.HTTPRequest(
"http://api.flickr.com/services/upload/",
method = "POST",
headers = headers,
body = body
)
http.fetch(httpRequest, callback=self.on_response)
except Exception, e:
logging.exception(e)
raise tornado.web.HTTPError(500)
def on_response(self, response):
logging.error(response.body)
if response.error:
logging.error(response.error)
raise tornado.web.HTTPError(500)
# Response is always XML
# TODO parse the XML. :)
#json = tornado.escape.json_decode(response.body)
self.write(response.body)
self.finish()
def multipart_encode(vars, files, boundary = None, buf = None):
if boundary is None:
boundary = mimetools.choose_boundary()
if buf is None:
buf = cStringIO.StringIO()
for(key, value) in vars:
buf.write('--%s\r\n' % boundary)
buf.write('Content-Disposition: form-data; name="%s"' % key)
buf.write('\r\n\r\n' + value + '\r\n')
for(name, filename, file, contenttype) in files:
file.seek(os.SEEK_END)
file_size = file.tell()
file.seek(os.SEEK_SET)
buf.write('--%s\r\n' % boundary)
buf.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (name, filename))
buf.write('Content-Type: %s\r\n' % contenttype)
# buffer += 'Content-Length: %s\r\n' % file_size
buf.write('\r\n' + file.read() + '\r\n')
buf.write('--' + boundary + '--\r\n\r\n')
buf = buf.getvalue()
return boundary, buf
class Service_GetImage(WebHandler):
def get(self):
self.render("service_getImage.html")
class Service_SendImage(WebHandler):
def get(self):
self.render("service_sendImage.html")
class WebAppManifestHandler(WebHandler):
def get(self):
self.set_header("Content-Type", "application/x-web-app-manifest+json")
self.render("smugmugconnector.webapp")
##################################################################
# Main Application Setup
##################################################################
settings = {
"static_path": os.path.join(os.path.dirname(__file__), "static"),
"login_url": "/login",
"cookie_secret": config.COOKIE_SECRET,
"debug":True,
"xheaders":True,
# "xsrf_cookies": True,
}
application = tornado.web.Application([
(r"/smugmugconnector.webapp", WebAppManifestHandler),
(r"/connect/done", ConnectDone),
(r"/connect/start", Connect),
(r"/get/photosets", Photosets),
(r"/get/photos", GetPhotos),
(r"/get/photosizes", GetPhotoSizes),
(r"/post/photo", PostPhoto),
(r"/retrieve", GetPhotos),
(r"/service/getImage", Service_GetImage),
(r"/service/sendImage", Service_SendImage),
(r"/xrds", XRDSHandler),
(r"/", MainHandler),
], **settings)
def run():
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8411)
print "Starting server on 8411"
tornado.ioloop.IOLoop.instance().start()
import logging
import sys
if __name__ == '__main__':
if '-test' in sys.argv:
import doctest
doctest.testmod()
else:
logging.basicConfig(level = logging.DEBUG)
run()