OLD | NEW |
(Empty) | |
| 1 # Copyright 2010 The Go Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style |
| 3 # license that can be found in the LICENSE file. |
| 4 |
| 5 # This is the server part of the package dashboard. |
| 6 # It must be run by App Engine. |
| 7 |
| 8 from google.appengine.api import memcache |
| 9 from google.appengine.runtime import DeadlineExceededError |
| 10 from google.appengine.ext import db |
| 11 from google.appengine.ext import webapp |
| 12 from google.appengine.ext.webapp import template |
| 13 from google.appengine.ext.webapp.util import run_wsgi_app |
| 14 import binascii |
| 15 import datetime |
| 16 import hashlib |
| 17 import hmac |
| 18 import logging |
| 19 import os |
| 20 import re |
| 21 import struct |
| 22 import time |
| 23 import urllib2 |
| 24 |
| 25 # Storage model for package info recorded on server. |
| 26 # Just path, count, and time of last install. |
| 27 class Package(db.Model): |
| 28 path = db.StringProperty() |
| 29 web_url = db.StringProperty() # derived from path |
| 30 count = db.IntegerProperty() |
| 31 last_install = db.DateTimeProperty() |
| 32 |
| 33 re_bitbucket = re.compile(r'^bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+$') |
| 34 re_googlecode = re.compile(r'^[a-z0-9\-]+\.googlecode\.com/(svn|hg)$') |
| 35 re_github = re.compile(r'^github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+$') |
| 36 |
| 37 MaxPathLength = 100 |
| 38 |
| 39 class PackagePage(webapp.RequestHandler): |
| 40 def get(self): |
| 41 if self.request.get('fmt') == 'json': |
| 42 return self.json() |
| 43 |
| 44 q = Package.all() |
| 45 q.order('-last_install') |
| 46 by_time = q.fetch(100) |
| 47 |
| 48 q = Package.all() |
| 49 q.order('-count') |
| 50 by_count = q.fetch(100) |
| 51 |
| 52 self.response.headers['Content-Type'] = 'text/html; charset=utf-8' |
| 53 path = os.path.join(os.path.dirname(__file__), 'package.html') |
| 54 self.response.out.write(template.render(path, {"by_time": by_time, "by_c
ount": by_count})) |
| 55 |
| 56 def json(self): |
| 57 self.response.set_status(200) |
| 58 self.response.headers['Content-Type'] = 'text/plain; charset=utf-8' |
| 59 q = Package.all() |
| 60 s = '{"packages": [' |
| 61 sep = '' |
| 62 for r in q.fetch(1000): |
| 63 s += '%s\n\t{"path": "%s", "last_install": "%s", "count": "%s"}' % (
sep, r.path, r.last_install, r.count) |
| 64 sep = ',' |
| 65 s += '\n]}\n' |
| 66 self.response.out.write(s) |
| 67 |
| 68 def can_get_url(self, url): |
| 69 try: |
| 70 req = urllib2.Request(url) |
| 71 response = urllib2.urlopen(req) |
| 72 return True |
| 73 except: |
| 74 return False |
| 75 |
| 76 def is_valid_package_path(self, path): |
| 77 return (re_bitbucket.match(path) or |
| 78 re_googlecode.match(path) or |
| 79 re_github.match(path)) |
| 80 |
| 81 def record_pkg(self, path): |
| 82 # sanity check string |
| 83 if not path or len(path) > MaxPathLength or not self.is_valid_package_pa
th(path): |
| 84 return False |
| 85 |
| 86 # look in datastore |
| 87 key = 'pkg-' + path |
| 88 p = Package.get_by_key_name(key) |
| 89 if p is None: |
| 90 # not in datastore - verify URL before creating |
| 91 if re_bitbucket.match(path): |
| 92 check_url = 'http://' + path + '/?cmd=heads' |
| 93 web = 'http://' + path + '/' |
| 94 elif re_github.match(path): |
| 95 # github doesn't let you fetch the .git directory anymore. |
| 96 # fetch .git/info/refs instead, like git clone would. |
| 97 check_url = 'http://'+path+'.git/info/refs' |
| 98 web = 'http://' + path |
| 99 elif re_googlecode.match(path): |
| 100 check_url = 'http://'+path |
| 101 web = 'http://code.google.com/p/' + path[:path.index('.')] |
| 102 else: |
| 103 logging.error('unrecognized path: %s', path) |
| 104 return False |
| 105 if not self.can_get_url(check_url): |
| 106 logging.error('cannot get %s', check_url) |
| 107 return False |
| 108 p = Package(key_name = key, path = path, count = 0, web_url = web) |
| 109 |
| 110 # update package object |
| 111 p.count += 1 |
| 112 p.last_install = datetime.datetime.utcnow() |
| 113 p.put() |
| 114 return True |
| 115 |
| 116 def post(self): |
| 117 path = self.request.get('path') |
| 118 ok = self.record_pkg(path) |
| 119 if ok: |
| 120 self.response.set_status(200) |
| 121 self.response.out.write('ok') |
| 122 else: |
| 123 logging.error('invalid path in post: %s', path) |
| 124 self.response.set_status(500) |
| 125 self.response.out.write('not ok') |
| 126 |
| 127 def main(): |
| 128 app = webapp.WSGIApplication([('/package', PackagePage)], debug=True) |
| 129 run_wsgi_app(app) |
| 130 |
| 131 if __name__ == '__main__': |
| 132 main() |
OLD | NEW |