358 строки
12 KiB
Python
Executable File
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()
|
|
|
|
|