OLD | NEW |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import logging | 5 import logging |
6 import os | 6 import os |
7 from StringIO import StringIO | 7 from StringIO import StringIO |
8 import sys | 8 import sys |
9 | 9 |
10 from appengine_wrappers import webapp | 10 from appengine_wrappers import webapp |
11 from appengine_wrappers import memcache | 11 from appengine_wrappers import memcache |
12 from appengine_wrappers import urlfetch | 12 from appengine_wrappers import urlfetch |
13 | 13 |
14 from api_data_source import APIDataSource | 14 from api_data_source import APIDataSource |
15 from api_list_data_source import APIListDataSource | 15 from api_list_data_source import APIListDataSource |
16 from appengine_blobstore import AppEngineBlobstore | 16 from appengine_blobstore import AppEngineBlobstore |
17 from in_memory_object_store import InMemoryObjectStore | |
18 from appengine_url_fetcher import AppEngineUrlFetcher | 17 from appengine_url_fetcher import AppEngineUrlFetcher |
| 18 from availability_data_source import AvailabilityDataSource |
19 from branch_utility import BranchUtility | 19 from branch_utility import BranchUtility |
20 from example_zipper import ExampleZipper | 20 from chrome_version_data_source import ChromeVersionDataSource |
21 from compiled_file_system import CompiledFileSystem | 21 from compiled_file_system import CompiledFileSystem |
22 import compiled_file_system as compiled_fs | 22 import compiled_file_system as compiled_fs |
| 23 from example_zipper import ExampleZipper |
23 from github_file_system import GithubFileSystem | 24 from github_file_system import GithubFileSystem |
| 25 from in_memory_object_store import InMemoryObjectStore |
24 from intro_data_source import IntroDataSource | 26 from intro_data_source import IntroDataSource |
25 from known_issues_data_source import KnownIssuesDataSource | 27 from known_issues_data_source import KnownIssuesDataSource |
26 from local_file_system import LocalFileSystem | 28 from local_file_system import LocalFileSystem |
27 from memcache_file_system import MemcacheFileSystem | 29 from memcache_file_system import MemcacheFileSystem |
28 from reference_resolver import ReferenceResolver | 30 from reference_resolver import ReferenceResolver |
29 from samples_data_source import SamplesDataSource | 31 from samples_data_source import SamplesDataSource |
30 from server_instance import ServerInstance | 32 from server_instance import ServerInstance |
31 from sidenav_data_source import SidenavDataSource | 33 from sidenav_data_source import SidenavDataSource |
32 from subversion_file_system import SubversionFileSystem | 34 from subversion_file_system import SubversionFileSystem |
33 from template_data_source import TemplateDataSource | 35 from template_data_source import TemplateDataSource |
34 from third_party.json_schema_compiler.model import UnixName | 36 from third_party.json_schema_compiler.model import UnixName |
35 import url_constants | 37 import url_constants |
36 | 38 |
37 # Increment this version to force the server to reload all pages in the first | 39 # Increment this version to force the server to reload all pages in the first |
38 # cron job that is run. | 40 # cron job that is run. |
39 _VERSION = 1 | 41 _VERSION = 2 |
40 | 42 |
41 # The default channel to serve docs for if no channel is specified. | 43 # The default channel to serve docs for if no channel is specified. |
42 _DEFAULT_CHANNEL = 'stable' | 44 _DEFAULT_CHANNEL = 'stable' |
43 | 45 |
44 BRANCH_UTILITY_MEMCACHE = InMemoryObjectStore('branch_utility') | 46 BRANCH_UTILITY_MEMCACHE = InMemoryObjectStore('branch_utility') |
45 BRANCH_UTILITY = BranchUtility(url_constants.OMAHA_PROXY_URL, | 47 BRANCH_UTILITY = BranchUtility(url_constants.OMAHA_PROXY_URL, |
46 AppEngineUrlFetcher(None), | 48 AppEngineUrlFetcher(None), |
47 BRANCH_UTILITY_MEMCACHE) | 49 BRANCH_UTILITY_MEMCACHE) |
48 | 50 |
| 51 AVAILABILITY_DATA_SOURCE_MEMCACHE = InMemoryObjectStore( |
| 52 'availability_data_source') |
| 53 |
49 GITHUB_MEMCACHE = InMemoryObjectStore('github') | 54 GITHUB_MEMCACHE = InMemoryObjectStore('github') |
50 GITHUB_FILE_SYSTEM = GithubFileSystem( | 55 GITHUB_FILE_SYSTEM = GithubFileSystem( |
51 AppEngineUrlFetcher(url_constants.GITHUB_URL), | 56 AppEngineUrlFetcher(url_constants.GITHUB_URL), |
52 GITHUB_MEMCACHE, | 57 GITHUB_MEMCACHE, |
53 AppEngineBlobstore()) | 58 AppEngineBlobstore()) |
54 GITHUB_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory(GITHUB_FILE_SYSTEM, | 59 GITHUB_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory(GITHUB_FILE_SYSTEM, |
55 GITHUB_MEMCACHE) | 60 GITHUB_MEMCACHE) |
56 | 61 |
57 EXTENSIONS_PATH = 'chrome/common/extensions' | 62 EXTENSIONS_PATH = 'chrome/common/extensions' |
58 DOCS_PATH = 'docs' | 63 DOCS_PATH = 'docs' |
(...skipping 18 matching lines...) Expand all Loading... |
77 return [UnixName(os.path.splitext(f.split('/')[-1])[0]) for f in files] | 82 return [UnixName(os.path.splitext(f.split('/')[-1])[0]) for f in files] |
78 | 83 |
79 def _CreateMemcacheFileSystem(branch, branch_memcache): | 84 def _CreateMemcacheFileSystem(branch, branch_memcache): |
80 svn_url = _GetURLFromBranch(branch) + '/' + EXTENSIONS_PATH | 85 svn_url = _GetURLFromBranch(branch) + '/' + EXTENSIONS_PATH |
81 stat_fetcher = AppEngineUrlFetcher( | 86 stat_fetcher = AppEngineUrlFetcher( |
82 svn_url.replace(url_constants.SVN_URL, url_constants.VIEWVC_URL)) | 87 svn_url.replace(url_constants.SVN_URL, url_constants.VIEWVC_URL)) |
83 fetcher = AppEngineUrlFetcher(svn_url) | 88 fetcher = AppEngineUrlFetcher(svn_url) |
84 return MemcacheFileSystem(SubversionFileSystem(fetcher, stat_fetcher), | 89 return MemcacheFileSystem(SubversionFileSystem(fetcher, stat_fetcher), |
85 branch_memcache) | 90 branch_memcache) |
86 | 91 |
87 _default_branch = BRANCH_UTILITY.GetBranchNumberForChannelName(_DEFAULT_CHANNEL) | 92 def _CreateAPIDataSourceForBranch(branch_number): |
| 93 return _CreateObjectsForBranch(branch_number, None) |
| 94 |
| 95 def _CreateInstanceForBranch(branch_number, channel_info): |
| 96 return _CreateObjectsForBranch(branch_number, channel_info) |
| 97 |
| 98 def _CreateObjectsForBranch(branch_number, channel_info=None): |
| 99 """Passed to the global instance of chrome_version_data_source, and also used |
| 100 in _GetInstanceForBranch. Allows for the creation of api_data_sources linked |
| 101 to |branch_number|, which is the maximum branch number from a given version of |
| 102 chrome, and the creation of a branch instance. |
| 103 """ |
| 104 channel_name = _DEFAULT_CHANNEL |
| 105 availability_data_source = None |
| 106 instance = None |
| 107 |
| 108 if channel_info is not None: |
| 109 channel_name = channel_info['name'] |
| 110 availability_data_source = AvailabilityDataSource( |
| 111 CHROME_VERSION_DATA_SOURCE, |
| 112 channel_info['version'], |
| 113 AVAILABILITY_DATA_SOURCE_MEMCACHE) |
| 114 |
| 115 branch_memcache = InMemoryObjectStore(branch_number) |
| 116 file_system = _CreateMemcacheFileSystem(branch_number, branch_memcache) |
| 117 cache_factory = CompiledFileSystem.Factory(file_system, branch_memcache) |
| 118 api_list_data_source_factory = APIListDataSource.Factory( |
| 119 cache_factory, |
| 120 file_system, |
| 121 API_PATH, |
| 122 PUBLIC_TEMPLATE_PATH) |
| 123 api_data_source_factory = APIDataSource.Factory(cache_factory, |
| 124 API_PATH, |
| 125 availability_data_source) |
| 126 # Give the ReferenceResolver a memcache, to speed up the lookup of |
| 127 # duplicate $refs. |
| 128 ref_resolver_factory = ReferenceResolver.Factory( |
| 129 api_data_source_factory, |
| 130 api_list_data_source_factory, |
| 131 branch_memcache) |
| 132 api_data_source_factory.SetReferenceResolverFactory(ref_resolver_factory) |
| 133 samples_data_source_factory = SamplesDataSource.Factory( |
| 134 channel_name, |
| 135 file_system, |
| 136 GITHUB_FILE_SYSTEM, |
| 137 cache_factory, |
| 138 GITHUB_COMPILED_FILE_SYSTEM, |
| 139 ref_resolver_factory, |
| 140 EXAMPLES_PATH) |
| 141 api_data_source_factory.SetSamplesDataSourceFactory( |
| 142 samples_data_source_factory) |
| 143 |
| 144 if channel_info is not None: |
| 145 intro_data_source_factory = IntroDataSource.Factory( |
| 146 cache_factory, |
| 147 ref_resolver_factory, |
| 148 [INTRO_PATH, ARTICLE_PATH]) |
| 149 sidenav_data_source_factory = SidenavDataSource.Factory(cache_factory, |
| 150 JSON_PATH) |
| 151 template_data_source_factory = TemplateDataSource.Factory( |
| 152 channel_name, |
| 153 api_data_source_factory, |
| 154 api_list_data_source_factory, |
| 155 intro_data_source_factory, |
| 156 samples_data_source_factory, |
| 157 KNOWN_ISSUES_DATA_SOURCE, |
| 158 sidenav_data_source_factory, |
| 159 cache_factory, |
| 160 ref_resolver_factory, |
| 161 PUBLIC_TEMPLATE_PATH, |
| 162 PRIVATE_TEMPLATE_PATH, |
| 163 BRANCH_UTILITY.GetChannelInfoForAllChannels()) |
| 164 example_zipper = ExampleZipper(file_system, |
| 165 cache_factory, |
| 166 DOCS_PATH) |
| 167 instance = ServerInstance(template_data_source_factory, |
| 168 example_zipper, |
| 169 cache_factory) |
| 170 return instance |
| 171 return api_data_source_factory.Create(None) |
| 172 |
| 173 _default_branch = BRANCH_UTILITY.GetChannelInfoForChannelName( |
| 174 _DEFAULT_CHANNEL)['branch'] |
88 APPS_MEMCACHE = InMemoryObjectStore(_default_branch) | 175 APPS_MEMCACHE = InMemoryObjectStore(_default_branch) |
89 APPS_FILE_SYSTEM = _CreateMemcacheFileSystem(_default_branch, APPS_MEMCACHE) | 176 APPS_FILE_SYSTEM = _CreateMemcacheFileSystem(_default_branch, APPS_MEMCACHE) |
90 APPS_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory( | 177 APPS_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory( |
91 APPS_FILE_SYSTEM, | 178 APPS_FILE_SYSTEM, |
92 APPS_MEMCACHE).Create(_SplitFilenameUnix, compiled_fs.APPS_FS) | 179 APPS_MEMCACHE).Create(_SplitFilenameUnix, compiled_fs.APPS_FS) |
93 | 180 |
94 EXTENSIONS_MEMCACHE = InMemoryObjectStore(_default_branch) | 181 EXTENSIONS_MEMCACHE = InMemoryObjectStore(_default_branch) |
95 EXTENSIONS_FILE_SYSTEM = _CreateMemcacheFileSystem(_default_branch, | 182 EXTENSIONS_FILE_SYSTEM = _CreateMemcacheFileSystem(_default_branch, |
96 EXTENSIONS_MEMCACHE) | 183 EXTENSIONS_MEMCACHE) |
97 EXTENSIONS_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory( | 184 EXTENSIONS_COMPILED_FILE_SYSTEM = CompiledFileSystem.Factory( |
98 EXTENSIONS_FILE_SYSTEM, | 185 EXTENSIONS_FILE_SYSTEM, |
99 EXTENSIONS_MEMCACHE).Create(_SplitFilenameUnix, compiled_fs.EXTENSIONS_FS) | 186 EXTENSIONS_MEMCACHE).Create(_SplitFilenameUnix, compiled_fs.EXTENSIONS_FS) |
100 | 187 |
101 KNOWN_ISSUES_DATA_SOURCE = KnownIssuesDataSource( | 188 KNOWN_ISSUES_DATA_SOURCE = KnownIssuesDataSource( |
102 InMemoryObjectStore('KnownIssues'), | 189 InMemoryObjectStore('KnownIssues'), |
103 AppEngineUrlFetcher(None)) | 190 AppEngineUrlFetcher(None)) |
104 | 191 |
| 192 CHROME_VERSION_DATA_SOURCE = ChromeVersionDataSource( |
| 193 url_constants.OMAHA_DEV_HISTORY, |
| 194 AppEngineUrlFetcher(None), |
| 195 InMemoryObjectStore('chrome_version_data_source'), |
| 196 _CreateAPIDataSourceForBranch) |
| 197 |
105 def _MakeInstanceKey(branch, number): | 198 def _MakeInstanceKey(branch, number): |
106 return '%s/%s' % (branch, number) | 199 return '%s/%s' % (branch, number) |
107 | 200 |
108 def _GetInstanceForBranch(channel_name, local_path): | 201 def _GetInstanceForBranch(channel_name): |
109 branch = BRANCH_UTILITY.GetBranchNumberForChannelName(channel_name) | 202 channel_info = BRANCH_UTILITY.GetChannelInfoForChannelName( |
| 203 channel_name) |
| 204 branch = channel_info['branch'] |
110 | 205 |
111 # The key for the server is a tuple of |channel_name| with |branch|, since | 206 # The key for the server is a tuple of |channel_name| with |branch|, since |
112 # sometimes stable and beta point to the same branch. | 207 # sometimes stable and beta point to the same branch. |
113 instance_key = _MakeInstanceKey(channel_name, branch) | 208 instance_key = _MakeInstanceKey(channel_name, branch) |
114 instance = SERVER_INSTANCES.get(instance_key, None) | 209 instance = SERVER_INSTANCES.get(instance_key, None) |
115 if instance is not None: | 210 if instance is not None: |
116 return instance | 211 return instance |
117 | 212 |
118 branch_memcache = InMemoryObjectStore(branch) | 213 instance = _CreateInstanceForBranch(branch, channel_info) |
119 file_system = _CreateMemcacheFileSystem(branch, branch_memcache) | 214 SERVER_INSTANCES[instance_key] = instance |
120 cache_factory = CompiledFileSystem.Factory(file_system, branch_memcache) | |
121 api_list_data_source_factory = APIListDataSource.Factory(cache_factory, | |
122 file_system, | |
123 API_PATH, | |
124 PUBLIC_TEMPLATE_PATH) | |
125 api_data_source_factory = APIDataSource.Factory( | |
126 cache_factory, | |
127 API_PATH) | |
128 | 215 |
129 # Give the ReferenceResolver a memcache, to speed up the lookup of | |
130 # duplicate $refs. | |
131 ref_resolver_factory = ReferenceResolver.Factory( | |
132 api_data_source_factory, | |
133 api_list_data_source_factory, | |
134 branch_memcache) | |
135 api_data_source_factory.SetReferenceResolverFactory(ref_resolver_factory) | |
136 samples_data_source_factory = SamplesDataSource.Factory( | |
137 channel_name, | |
138 file_system, | |
139 GITHUB_FILE_SYSTEM, | |
140 cache_factory, | |
141 GITHUB_COMPILED_FILE_SYSTEM, | |
142 ref_resolver_factory, | |
143 EXAMPLES_PATH) | |
144 api_data_source_factory.SetSamplesDataSourceFactory( | |
145 samples_data_source_factory) | |
146 intro_data_source_factory = IntroDataSource.Factory( | |
147 cache_factory, | |
148 ref_resolver_factory, | |
149 [INTRO_PATH, ARTICLE_PATH]) | |
150 sidenav_data_source_factory = SidenavDataSource.Factory(cache_factory, | |
151 JSON_PATH) | |
152 template_data_source_factory = TemplateDataSource.Factory( | |
153 channel_name, | |
154 api_data_source_factory, | |
155 api_list_data_source_factory, | |
156 intro_data_source_factory, | |
157 samples_data_source_factory, | |
158 KNOWN_ISSUES_DATA_SOURCE, | |
159 sidenav_data_source_factory, | |
160 cache_factory, | |
161 ref_resolver_factory, | |
162 PUBLIC_TEMPLATE_PATH, | |
163 PRIVATE_TEMPLATE_PATH) | |
164 example_zipper = ExampleZipper(file_system, | |
165 cache_factory, | |
166 DOCS_PATH) | |
167 | |
168 instance = ServerInstance(template_data_source_factory, | |
169 example_zipper, | |
170 cache_factory) | |
171 SERVER_INSTANCES[instance_key] = instance | |
172 return instance | 216 return instance |
173 | 217 |
174 def _CleanBranches(): | 218 def _CleanBranches(): |
175 keys = [_MakeInstanceKey(branch, number) | 219 keys = [_MakeInstanceKey(branch, number) |
176 for branch, number in BRANCH_UTILITY.GetAllBranchNumbers()] | 220 for branch, number in BRANCH_UTILITY.GetAllBranchNumbers()] |
177 for key in SERVER_INSTANCES.keys(): | 221 for key in SERVER_INSTANCES.keys(): |
178 if key not in keys: | 222 if key not in keys: |
179 SERVER_INSTANCES.pop(key) | 223 SERVER_INSTANCES.pop(key) |
180 | 224 |
181 class _MockResponse(object): | 225 class _MockResponse(object): |
182 def __init__(self): | 226 def __init__(self): |
183 self.status = 200 | 227 self.status = 200 |
184 self.out = StringIO() | 228 self.out = StringIO() |
185 self.headers = {} | 229 self.headers = {} |
186 | 230 |
187 def set_status(self, status): | 231 def set_status(self, status): |
188 self.status = status | 232 self.status = status |
189 | 233 |
190 class _MockRequest(object): | 234 class _MockRequest(object): |
191 def __init__(self, path): | 235 def __init__(self, path): |
192 self.headers = {} | 236 self.headers = {} |
193 self.path = path | 237 self.path = path |
194 self.url = 'http://localhost' + path | 238 self.url = 'http://localhost' + path |
195 | 239 |
196 class Handler(webapp.RequestHandler): | 240 class Handler(webapp.RequestHandler): |
197 def __init__(self, request, response, local_path=EXTENSIONS_PATH): | 241 def __init__(self, request, response): |
198 self._local_path = local_path | |
199 super(Handler, self).__init__(request, response) | 242 super(Handler, self).__init__(request, response) |
200 | 243 |
201 def _HandleGet(self, path): | 244 def _HandleGet(self, path): |
202 channel_name, real_path = BRANCH_UTILITY.SplitChannelNameFromPath(path) | 245 channel_name, real_path = BRANCH_UTILITY.SplitChannelNameFromPath(path) |
203 | 246 |
204 if channel_name == _DEFAULT_CHANNEL: | 247 if channel_name == _DEFAULT_CHANNEL: |
205 self.redirect('/%s' % real_path) | 248 self.redirect('/%s' % real_path) |
206 return | 249 return |
207 | 250 |
208 # TODO: Detect that these are directories and serve index.html out of them. | 251 # TODO: Detect that these are directories and serve index.html out of them. |
209 if real_path.strip('/') == 'apps': | 252 if real_path.strip('/') == 'apps': |
210 real_path = 'apps/index.html' | 253 real_path = 'apps/index.html' |
211 if real_path.strip('/') == 'extensions': | 254 if real_path.strip('/') == 'extensions': |
212 real_path = 'extensions/index.html' | 255 real_path = 'extensions/index.html' |
213 | 256 |
214 if (not real_path.startswith('extensions/') and | 257 if (not real_path.startswith('extensions/') and |
215 not real_path.startswith('apps/') and | 258 not real_path.startswith('apps/') and |
216 not real_path.startswith('static/')): | 259 not real_path.startswith('static/')): |
217 if self._RedirectBadPaths(real_path, channel_name): | 260 if self._RedirectBadPaths(real_path, channel_name): |
218 return | 261 return |
219 | 262 |
220 _CleanBranches() | 263 _CleanBranches() |
221 | 264 |
222 # Yes, do this after it's passed to RedirectBadPaths. That needs to know | 265 # Yes, do this after it's passed to RedirectBadPaths. That needs to know |
223 # whether or not a branch was specified. | 266 # whether or not a branch was specified. |
224 if channel_name is None: | 267 if channel_name is None: |
225 channel_name = _DEFAULT_CHANNEL | 268 channel_name = _DEFAULT_CHANNEL |
226 _GetInstanceForBranch(channel_name, self._local_path).Get(real_path, | 269 _GetInstanceForBranch(channel_name).Get(real_path, |
227 self.request, | 270 self.request, |
228 self.response) | 271 self.response) |
229 | 272 |
230 def _Render(self, files, channel): | 273 def _Render(self, files, channel): |
231 original_response = self.response | 274 original_response = self.response |
232 for f in files: | 275 for f in files: |
233 if f.endswith('404.html'): | 276 if f.endswith('404.html'): |
234 continue | 277 continue |
235 path = channel + f.split(PUBLIC_TEMPLATE_PATH)[-1] | 278 path = channel + f.split(PUBLIC_TEMPLATE_PATH)[-1] |
236 self.request = _MockRequest(path) | 279 self.request = _MockRequest(path) |
237 self.response = _MockResponse() | 280 self.response = _MockResponse() |
238 try: | 281 try: |
(...skipping 20 matching lines...) Expand all Loading... |
259 # We could list all files in PUBLIC_TEMPLATE_PATH then render them. However, | 302 # We could list all files in PUBLIC_TEMPLATE_PATH then render them. However, |
260 # this would be inefficient in the common case where files haven't changed | 303 # this would be inefficient in the common case where files haven't changed |
261 # since the last cron. | 304 # since the last cron. |
262 # | 305 # |
263 # Instead, let the CompiledFileSystem give us clues when to re-render: we | 306 # Instead, let the CompiledFileSystem give us clues when to re-render: we |
264 # use the CFS to check whether the templates, examples, or API folders have | 307 # use the CFS to check whether the templates, examples, or API folders have |
265 # been changed. If there has been a change, the compilation function will | 308 # been changed. If there has been a change, the compilation function will |
266 # be called. The same is then done separately with the apps samples page, | 309 # be called. The same is then done separately with the apps samples page, |
267 # since it pulls its data from Github. | 310 # since it pulls its data from Github. |
268 channel = path.split('/')[-1] | 311 channel = path.split('/')[-1] |
269 branch = BRANCH_UTILITY.GetBranchNumberForChannelName(channel) | 312 branch = BRANCH_UTILITY.GetChannelInfoForChannelName(channel)['branch'] |
270 logging.info('Running cron job for %s.' % branch) | 313 logging.info('Running cron job for %s.' % branch) |
271 branch_memcache = InMemoryObjectStore(branch) | 314 branch_memcache = InMemoryObjectStore(branch) |
272 file_system = _CreateMemcacheFileSystem(branch, branch_memcache) | 315 file_system = _CreateMemcacheFileSystem(branch, branch_memcache) |
273 factory = CompiledFileSystem.Factory(file_system, branch_memcache) | 316 factory = CompiledFileSystem.Factory(file_system, branch_memcache) |
274 | 317 |
275 needs_render = self._ValueHolder(False) | 318 needs_render = self._ValueHolder(False) |
276 invalidation_cache = factory.Create(lambda _, __: needs_render.Set(True), | 319 invalidation_cache = factory.Create(lambda _, __: needs_render.Set(True), |
277 compiled_fs.CRON_INVALIDATION, | 320 compiled_fs.CRON_INVALIDATION, |
278 version=_VERSION) | 321 version=_VERSION) |
279 for path in [TEMPLATE_PATH, EXAMPLES_PATH, API_PATH]: | 322 for path in [TEMPLATE_PATH, EXAMPLES_PATH, API_PATH]: |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
367 | 410 |
368 # Redirect paths like "directory" to "directory/". This is so relative | 411 # Redirect paths like "directory" to "directory/". This is so relative |
369 # file paths will know to treat this as a directory. | 412 # file paths will know to treat this as a directory. |
370 if os.path.splitext(path)[1] == '' and path[-1] != '/': | 413 if os.path.splitext(path)[1] == '' and path[-1] != '/': |
371 self.redirect(path + '/') | 414 self.redirect(path + '/') |
372 return | 415 return |
373 | 416 |
374 path = path.strip('/') | 417 path = path.strip('/') |
375 if not self._RedirectFromCodeDotGoogleDotCom(path): | 418 if not self._RedirectFromCodeDotGoogleDotCom(path): |
376 self._HandleGet(path) | 419 self._HandleGet(path) |
OLD | NEW |