OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 import cgi | |
7 import logging | |
8 import os | |
9 import re | |
10 | |
11 from google.appengine.ext import webapp | |
12 from google.appengine.ext.webapp.util import run_wsgi_app | |
13 from google.appengine.api import memcache | |
14 from google.appengine.api import urlfetch | |
15 | |
16 import app_known_issues | |
17 | |
18 DEFAULT_CACHE_TIME = 300 | |
19 VIEW_VC_ROOT = 'http://src.chromium.org' | |
20 CHROME_DOMAIN_URL = 'http://developer.chrome.com' | |
21 | |
22 class Channel(): | |
23 def __init__(self, name, tag): | |
24 self.name = name | |
25 self.tag = tag | |
26 | |
27 Channel.DEV = Channel("dev", "2.0-dev") | |
28 Channel.BETA = Channel("beta", "1.1-beta") | |
29 Channel.STABLE = Channel("stable", "") | |
30 Channel.TRUNK = Channel("trunk", "") | |
31 Channel.CHANNELS = [Channel.TRUNK, Channel.DEV, Channel.BETA, Channel.STABLE] | |
32 Channel.DEFAULT = Channel.STABLE | |
33 | |
34 | |
35 def GetChannelByName(channelName): | |
36 for channel in Channel.CHANNELS: | |
37 if channel.name == channelName: | |
38 return channel | |
39 | |
40 return None | |
41 | |
42 | |
43 def GetBranchRoot(branch): | |
44 if branch is None: | |
45 return '%s/viewvc/chrome/trunk/src/' % VIEW_VC_ROOT | |
46 else: | |
47 return '%s/viewvc/chrome/branches/%s/src/' % (VIEW_VC_ROOT, branch) | |
48 | |
49 | |
50 def GetExtensionsRoot(branch): | |
51 return GetBranchRoot(branch) + 'chrome/common/extensions/' | |
52 | |
53 | |
54 def GetDocsRoot(branch, docFamily): | |
55 if docFamily is None: | |
56 return GetExtensionsRoot(branch) + 'docs/' | |
57 else: | |
58 return GetExtensionsRoot(branch) + ('docs/%s/' % docFamily) | |
59 | |
60 | |
61 def GetSrcUrl(branch, docFamily, path): | |
62 # TODO(aa): Need cooler favicon. | |
63 if path[0] == 'favicon.ico': | |
64 return '%s/%s' % (VIEW_VC_ROOT, path[0]) | |
65 | |
66 pathstr = '/'.join(path) | |
67 | |
68 if path[0] == 'api' and path[-1].endswith('.json'): | |
69 return GetExtensionsRoot(branch) + pathstr | |
70 | |
71 if path[0] == 'third_party': | |
72 if path[-1] == 'jstemplate_compiled.js': | |
73 return GetBranchRoot(branch) + ('chrome/%s' % pathstr) | |
74 else: | |
75 return GetBranchRoot(branch) + pathstr | |
76 | |
77 return GetDocsRoot(branch, docFamily) + pathstr | |
78 | |
79 | |
80 def GetBranch(channel): | |
81 """ Gets the name of the branch in source control that contains the code for | |
82 the Chrome version currently being served on |channel|.""" | |
83 branch = memcache.get(channel.name) | |
84 if branch is not None: | |
85 return branch | |
86 | |
87 # query Omaha to figure out which version corresponds to this channel | |
88 postdata = """<?xml version="1.0" encoding="UTF-8"?> | |
89 <o:gupdate xmlns:o="http://www.google.com/update2/request" | |
90 protocol="2.0" testsource="crxdocs"> | |
91 <o:app appid="{8A69D345-D564-463C-AFF1-A69D9E530F96}" | |
92 version="0.0.0.0" lang=""> | |
93 <o:updatecheck tag="%s" | |
94 installsource="ondemandcheckforupdates" /> | |
95 </o:app> | |
96 </o:gupdate> | |
97 """ % channel.tag | |
98 | |
99 result = urlfetch.fetch( | |
100 url="https://tools.google.com/service/update2", | |
101 payload=postdata, | |
102 method=urlfetch.POST, | |
103 headers={'Content-Type':'application/x-www-form-urlencoded', | |
104 'X-USER-IP': '72.1.1.1'}) | |
105 | |
106 if result.status_code != 200: | |
107 return None | |
108 | |
109 match = re.search(r'<updatecheck Version="\d+\.\d+\.(\d+)\.\d+"', | |
110 result.content) | |
111 if match is None: | |
112 logging.error("Cannot find branch for requested channel: " + result.content) | |
113 return None | |
114 | |
115 branch = match.group(1) | |
116 memcache.add(channel.name, branch, DEFAULT_CACHE_TIME) | |
117 return branch | |
118 | |
119 class MainPage(webapp.RequestHandler): | |
120 def redirectToIndexIfNecessary(self): | |
121 if len(self.path) > 0: | |
122 return True | |
123 newPath = self.request.path | |
124 if not newPath.endswith('/'): | |
125 newPath += '/' | |
126 newPath += 'index.html' | |
127 self.redirect(newPath) | |
128 return False | |
129 | |
130 | |
131 def initPath(self): | |
132 self.path = self.request.path.split('/') | |
133 | |
134 # The first component is always empty. | |
135 self.path.pop(0) | |
136 | |
137 # The last component might be empty if there was a trailing slash. | |
138 if len(self.path) > 0 and self.path[-1] == '': | |
139 self.path.pop() | |
140 | |
141 # Temporary hacks for apps. | |
142 # TODO(aa): Remove once the apps content percolates through Chrome's release | |
143 # process more. | |
144 if self.path == ['apps'] or self.path == ['trunk', 'apps']: | |
145 self.redirect('/trunk/apps/about_apps.html') | |
146 return False | |
147 | |
148 # TODO(aa): Remove once we have a homepage for developer.chrome.com. | |
149 if (self.path == [] and | |
150 self.request.url.startswith('http://developer.chrome.com')): | |
151 self.redirect('http://developers.google.com/chrome') | |
152 return False | |
153 | |
154 return self.redirectToIndexIfNecessary() | |
155 | |
156 | |
157 def initChannel(self): | |
158 self.channel = GetChannelByName(self.path[0]) | |
159 if self.channel is not None: | |
160 self.path.pop(0) | |
161 else: | |
162 self.channel = Channel.DEFAULT | |
163 return self.redirectToIndexIfNecessary() | |
164 | |
165 | |
166 def initDocFamily(self): | |
167 if self.path[0] in ('extensions', 'apps'): | |
168 self.docFamily = self.path.pop(0) | |
169 else: | |
170 self.docFamily = 'extensions' | |
171 return self.redirectToIndexIfNecessary() | |
172 | |
173 | |
174 def redirectDomain(self): | |
175 if (self.request.url.startswith(('http://code.google.com', | |
176 'https://code.google.com'))): | |
177 newUrl = CHROME_DOMAIN_URL | |
178 # switch to https if necessary | |
179 if (self.request.url.startswith('https')): | |
180 newUrl = newUrl.replace('http', 'https', 1) | |
181 self.path.pop(0) # 'chrome' | |
182 for channel in ['dev', 'beta', 'stable', 'trunk']: | |
183 if channel in self.path: | |
184 position = self.path.index(channel) | |
185 self.path.pop(position) | |
186 self.path.insert(0, channel) | |
187 self.redirect(newUrl + '/' + '/'.join(self.path), True) | |
188 return False | |
189 else: | |
190 return True | |
191 | |
192 | |
193 def fetchContent(self): | |
194 logging.info("fetching: %s" % str((self.branch, self.docFamily, self.path))) | |
195 | |
196 # For extensions, try the old directory layout first. | |
197 result = None | |
198 oldUrl = '' | |
199 | |
200 if self.docFamily == 'extensions': | |
201 oldUrl = GetSrcUrl(self.branch, None, self.path) | |
202 result = urlfetch.fetch(oldUrl) | |
203 | |
204 if result is None or result.status_code != 200: | |
205 newUrl = GetSrcUrl(self.branch, self.docFamily, self.path) | |
206 if oldUrl != newUrl: | |
207 logging.info('Trying new directory layout...') | |
208 result = urlfetch.fetch(newUrl) | |
209 | |
210 # Files inside of samples should be rendered with content-type | |
211 # text/plain so that their source is visible when linked to. The only | |
212 # types we should serve as-is are images. | |
213 if (self.path[0] == 'examples' and | |
214 not (result.headers['content-type'].startswith('image/') or | |
215 result.headers['Content-Type'].startswith('image/'))): | |
216 result.headers['content-type'] = 'text/plain' | |
217 | |
218 return result | |
219 | |
220 | |
221 def get(self): | |
222 if (not self.initPath() or | |
223 not self.redirectDomain() or | |
224 not self.initChannel() or | |
225 not self.initDocFamily()): | |
226 return | |
227 | |
228 cacheKey = str((self.channel.name, self.docFamily, self.path)) | |
229 result = memcache.get(cacheKey) | |
230 if result is None: | |
231 logging.info("cache miss: " + cacheKey) | |
232 | |
233 self.branch = None | |
234 if self.channel is not Channel.TRUNK: | |
235 self.branch = GetBranch(self.channel) | |
236 | |
237 result = self.fetchContent() | |
238 memcache.add(cacheKey, result, DEFAULT_CACHE_TIME) | |
239 | |
240 if result is not None: | |
241 for key in result.headers: | |
242 self.response.headers[key] = result.headers[key] | |
243 self.response.out.write(result.content) | |
244 | |
245 | |
246 application = webapp.WSGIApplication([ | |
247 ('/app_known_issues_snippet.html', app_known_issues.Handler), | |
248 ('/.*', MainPage), | |
249 ], debug=False) | |
250 | |
251 | |
252 def main(): | |
253 run_wsgi_app(application) | |
254 | |
255 | |
256 if __name__ == '__main__': | |
257 main() | |
OLD | NEW |