Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(67)

Side by Side Diff: chrome/common/extensions/docs/server2/gitiles_file_system.py

Issue 575613003: Docserver: Gitiles auth and cron refactoring. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 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 5
6 from base64 import b64decode 6 from base64 import b64decode
7 from itertools import izip 7 from itertools import izip
8 import json
8 import logging 9 import logging
9 import json
10 import posixpath 10 import posixpath
11 import time 11 import time
12 import traceback 12 import traceback
13 13
14 from appengine_url_fetcher import AppEngineUrlFetcher 14 from appengine_url_fetcher import AppEngineUrlFetcher
15 from appengine_wrappers import IsDownloadError, app_identity 15 from appengine_wrappers import IsDownloadError, app_identity
16 from docs_server_utils import StringIdentity 16 from docs_server_utils import StringIdentity
17 from file_system import (FileNotFoundError, 17 from file_system import (FileNotFoundError,
18 FileSystem, 18 FileSystem,
19 FileSystemError, 19 FileSystemError,
20 FileSystemThrottledError,
20 StatInfo) 21 StatInfo)
21 from future import All, Future 22 from future import All, Future
22 from path_util import AssertIsValid, IsDirectory, ToDirectory 23 from path_util import AssertIsValid, IsDirectory, ToDirectory
23 from third_party.json_schema_compiler.memoize import memoize 24 from third_party.json_schema_compiler.memoize import memoize
24 from url_constants import (GITILES_BASE, 25 from url_constants import (GITILES_BASE,
25 GITILES_BRANCH_BASE, 26 GITILES_SRC_ROOT,
27 GITILES_BRANCHES_PATH,
26 GITILES_OAUTH2_SCOPE) 28 GITILES_OAUTH2_SCOPE)
27 29
30
28 _JSON_FORMAT = '?format=JSON' 31 _JSON_FORMAT = '?format=JSON'
29 _TEXT_FORMAT = '?format=TEXT' 32 _TEXT_FORMAT = '?format=TEXT'
33 _AUTH_PATH_PREFIX = '/a'
30 34
31 35
32 def _ParseGitilesJson(json_data): 36 def _ParseGitilesJson(json_data):
33 '''json.loads with fix-up for non-executable JSON. Use this to parse any JSON 37 '''json.loads with fix-up for non-executable JSON. Use this to parse any JSON
34 data coming from Gitiles views. 38 data coming from Gitiles views.
35 ''' 39 '''
36 return json.loads(json_data[json_data.find('{'):]) 40 return json.loads(json_data[json_data.find('{'):])
37 41
38 42
39 def _CreateStatInfo(json_data): 43 def _CreateStatInfo(json_data):
40 '''Returns a StatInfo object comprised of the tree ID for |json_data|, 44 '''Returns a StatInfo object comprised of the tree ID for |json_data|,
41 as well as the tree IDs for the entries in |json_data|. 45 as well as the tree IDs for the entries in |json_data|.
42 ''' 46 '''
43 tree = _ParseGitilesJson(json_data) 47 tree = _ParseGitilesJson(json_data)
44 return StatInfo(tree['id'], 48 return StatInfo(tree['id'],
45 dict((e['name'], e['id']) for e in tree['entries'])) 49 dict((e['name'], e['id']) for e in tree['entries']))
46 50
47 51
48 class GitilesFileSystem(FileSystem): 52 class GitilesFileSystem(FileSystem):
49 '''Class to fetch filesystem data from the Chromium project's gitiles 53 '''Class to fetch filesystem data from the Chromium project's gitiles
50 service. 54 service.
51 ''' 55 '''
52 @staticmethod 56 @staticmethod
53 def Create(branch='master', commit=None): 57 def Create(branch='master', commit=None):
58 token, _ = app_identity.get_access_token(GITILES_OAUTH2_SCOPE)
59 path_prefix = '' if token is None else _AUTH_PATH_PREFIX
54 if commit: 60 if commit:
55 base_url = '%s/%s' % (GITILES_BASE, commit) 61 base_url = '%s%s/%s/%s' % (
62 GITILES_BASE, path_prefix, GITILES_SRC_ROOT, commit)
56 elif branch is 'master': 63 elif branch is 'master':
57 base_url = '%s/master' % GITILES_BASE 64 base_url = '%s%s/%s/master' % (
65 GITILES_BASE, path_prefix, GITILES_SRC_ROOT)
58 else: 66 else:
59 base_url = '%s/%s' % (GITILES_BRANCH_BASE, branch) 67 base_url = '%s%s/%s/%s/%s' % (
68 GITILES_BASE, path_prefix, GITILES_SRC_ROOT,
69 GITILES_BRANCHES_PATH, branch)
60 return GitilesFileSystem(AppEngineUrlFetcher(), base_url, branch, commit) 70 return GitilesFileSystem(AppEngineUrlFetcher(), base_url, branch, commit)
61 71
62 def __init__(self, fetcher, base_url, branch, commit): 72 def __init__(self, fetcher, base_url, branch, commit):
63 self._fetcher = fetcher 73 self._fetcher = fetcher
64 self._base_url = base_url 74 self._base_url = base_url
65 self._branch = branch 75 self._branch = branch
66 self._commit = commit 76 self._commit = commit
67 77
68 def _FetchAsync(self, url): 78 def _FetchAsync(self, url):
69 '''Convenience wrapper for fetcher.FetchAsync, so callers don't 79 '''Convenience wrapper for fetcher.FetchAsync, so callers don't
70 need to use posixpath.join. 80 need to use posixpath.join.
71 ''' 81 '''
72 AssertIsValid(url) 82 AssertIsValid(url)
73 access_token, _ = app_identity.get_access_token(GITILES_OAUTH2_SCOPE) 83 access_token, _ = app_identity.get_access_token(GITILES_OAUTH2_SCOPE)
74 return self._fetcher.FetchAsync('%s/%s' % (self._base_url, url), 84 return self._fetcher.FetchAsync('%s/%s' % (self._base_url, url),
75 access_token=access_token) 85 access_token=access_token)
76 86
77 def _ResolveFetchContent(self, path, fetch_future, retry, 87 def _ResolveFetchContent(self, path, fetch_future, skip_not_found=False):
78 skip_not_found=False):
79 '''Returns a future to cleanly resolve |fetch_future|. 88 '''Returns a future to cleanly resolve |fetch_future|.
80 ''' 89 '''
81 def handle(e): 90 def handle(e):
82 if skip_not_found and IsDownloadError(e): 91 if skip_not_found and IsDownloadError(e):
83 return None 92 return None
84 exc_type = FileNotFoundError if IsDownloadError(e) else FileSystemError 93 exc_type = FileNotFoundError if IsDownloadError(e) else FileSystemError
85 raise exc_type('%s fetching %s for Get from %s: %s' % 94 raise exc_type('%s fetching %s for Get from %s: %s' %
86 (type(e).__name__, path, self._base_url, traceback.format_exc())) 95 (type(e).__name__, path, self._base_url, traceback.format_exc()))
87 96
88 def get_content(result): 97 def get_content(result):
89 if result.status_code == 404: 98 if result.status_code == 404:
90 if skip_not_found: 99 if skip_not_found:
91 return None 100 return None
92 raise FileNotFoundError('Got 404 when fetching %s for Get from %s' % 101 raise FileNotFoundError('Got 404 when fetching %s for Get from %s' %
93 (path, self._base_url)) 102 (path, self._base_url))
94 if result.status_code == 429: 103 if result.status_code == 429:
95 logging.warning('Access throttled when fetching %s for Get from %s' % 104 logging.warning('Access throttled when fetching %s for Get from %s' %
96 (path, self._base_url)) 105 (path, self._base_url))
97 time.sleep(30) 106 raise FileSystemThrottledError(
98 return retry().Then(get_content, handle) 107 'Access throttled when fetching %s for Get from %s' %
108 (path, self._base_url))
99 if result.status_code != 200: 109 if result.status_code != 200:
100 raise FileSystemError( 110 raise FileSystemError(
101 'Got %s when fetching %s for Get from %s, content %s' % 111 'Got %s when fetching %s for Get from %s, content %s' %
102 (result.status_code, path, self._base_url, result.content)) 112 (result.status_code, path, self._base_url, result.content))
103 return result.content 113 return result.content
104 114
105 return fetch_future.Then(get_content, handle) 115 return fetch_future.Then(get_content, handle)
106 116
107 def Read(self, paths, skip_not_found=False): 117 def Read(self, paths, skip_not_found=False):
108 # Directory content is formatted in JSON in Gitiles as follows: 118 # Directory content is formatted in JSON in Gitiles as follows:
(...skipping 15 matching lines...) Expand all
124 return [e['name'] + ('/' if e['type'] == 'tree' else '') for e in entries] 134 return [e['name'] + ('/' if e['type'] == 'tree' else '') for e in entries]
125 135
126 def fixup_url_format(path): 136 def fixup_url_format(path):
127 # By default, Gitiles URLs display resources in HTML. To get resources 137 # By default, Gitiles URLs display resources in HTML. To get resources
128 # suitable for our consumption, a '?format=' string must be appended to 138 # suitable for our consumption, a '?format=' string must be appended to
129 # the URL. The format may be one of 'JSON' or 'TEXT' for directory or 139 # the URL. The format may be one of 'JSON' or 'TEXT' for directory or
130 # text resources, respectively. 140 # text resources, respectively.
131 return path + (_JSON_FORMAT if IsDirectory(path) else _TEXT_FORMAT) 141 return path + (_JSON_FORMAT if IsDirectory(path) else _TEXT_FORMAT)
132 142
133 # A list of tuples of the form (path, Future). 143 # A list of tuples of the form (path, Future).
134 fetches = [] 144 fetches = [(path, self._FetchAsync(fixup_url_format(path)))
135 for path in paths: 145 for path in paths]
136 def make_fetch_future():
137 return self._FetchAsync(fixup_url_format(path))
138 fetches.append((path, make_fetch_future(), make_fetch_future))
139 146
140 def parse_contents(results): 147 def parse_contents(results):
141 value = {} 148 value = {}
142 for path, content in izip(paths, results): 149 for path, content in izip(paths, results):
143 if content is None: 150 if content is None:
144 continue 151 continue
145 # Gitiles encodes text content in base64 (see 152 # Gitiles encodes text content in base64 (see
146 # http://tools.ietf.org/html/rfc4648 for info about base64). 153 # http://tools.ietf.org/html/rfc4648 for info about base64).
147 value[path] = (list_dir if IsDirectory(path) else b64decode)(content) 154 value[path] = (list_dir if IsDirectory(path) else b64decode)(content)
148 return value 155 return value
149 156
150 return All(self._ResolveFetchContent(path, future, factory, skip_not_found) 157 return All(self._ResolveFetchContent(path, future, skip_not_found)
151 for path, future, factory in fetches).Then(parse_contents) 158 for path, future in fetches).Then(parse_contents)
152 159
153 def Refresh(self): 160 def Refresh(self):
154 return Future(value=()) 161 return Future(value=())
155 162
156 @memoize 163 @memoize
157 def _GetCommitInfo(self, key): 164 def _GetCommitInfo(self, key):
158 '''Gets the commit information specified by |key|. 165 '''Gets the commit information specified by |key|.
159 166
160 The JSON view for commit info looks like: 167 The JSON view for commit info looks like:
161 { 168 {
(...skipping 15 matching lines...) Expand all
177 "message": "...", 184 "message": "...",
178 "tree_diff": [...] 185 "tree_diff": [...]
179 } 186 }
180 ''' 187 '''
181 # Commit information for a branch is obtained by appending '?format=JSON' 188 # Commit information for a branch is obtained by appending '?format=JSON'
182 # to the branch URL. Note that '<gitiles_url>/<branch>?format=JSON' is 189 # to the branch URL. Note that '<gitiles_url>/<branch>?format=JSON' is
183 # different from '<gitiles_url>/<branch>/?format=JSON': the latter serves 190 # different from '<gitiles_url>/<branch>/?format=JSON': the latter serves
184 # the root directory JSON content, whereas the former serves the branch 191 # the root directory JSON content, whereas the former serves the branch
185 # commit info JSON content. 192 # commit info JSON content.
186 193
187 def make_fetch_future(): 194 access_token, _ = app_identity.get_access_token(GITILES_OAUTH2_SCOPE)
188 access_token, _ = app_identity.get_access_token(GITILES_OAUTH2_SCOPE) 195 fetch_future = self._fetcher.FetchAsync(self._base_url + _JSON_FORMAT,
189 return self._fetcher.FetchAsync(self._base_url + _JSON_FORMAT, 196 access_token=access_token)
190 access_token = access_token) 197 content_future = self._ResolveFetchContent(self._base_url, fetch_future)
191
192 fetch_future = make_fetch_future()
193 content_future = self._ResolveFetchContent(self._base_url, fetch_future,
194 make_fetch_future)
195 return content_future.Then(lambda json: _ParseGitilesJson(json)[key]) 198 return content_future.Then(lambda json: _ParseGitilesJson(json)[key])
196 199
197 def GetCommitID(self): 200 def GetCommitID(self):
198 '''Returns a future that resolves to the commit ID for this branch. 201 '''Returns a future that resolves to the commit ID for this branch.
199 ''' 202 '''
200 return self._GetCommitInfo('commit') 203 return self._GetCommitInfo('commit')
201 204
202 def GetPreviousCommitID(self): 205 def GetPreviousCommitID(self):
203 '''Returns a future that resolves to the previous commit ID for this branch. 206 '''Returns a future that resolves to the previous commit ID for this branch.
204 ''' 207 '''
205 return self._GetCommitInfo('parents').Then(lambda parents: parents[0]) 208 return self._GetCommitInfo('parents').Then(lambda parents: parents[0])
206 209
207 def StatAsync(self, path): 210 def StatAsync(self, path):
208 dir_, filename = posixpath.split(path) 211 dir_, filename = posixpath.split(path)
209 def stat(content): 212 def stat(content):
210 stat_info = _CreateStatInfo(content) 213 stat_info = _CreateStatInfo(content)
211 if stat_info.version is None: 214 if stat_info.version is None:
212 raise FileSystemError('Failed to find version of dir %s' % dir_) 215 raise FileSystemError('Failed to find version of dir %s' % dir_)
213 if IsDirectory(path): 216 if IsDirectory(path):
214 return stat_info 217 return stat_info
215 if filename not in stat_info.child_versions: 218 if filename not in stat_info.child_versions:
216 raise FileNotFoundError( 219 raise FileNotFoundError(
217 '%s from %s was not in child versions for Stat' % (filename, path)) 220 '%s from %s was not in child versions for Stat' % (filename, path))
218 return StatInfo(stat_info.child_versions[filename]) 221 return StatInfo(stat_info.child_versions[filename])
219 222
220 def make_fetch_future(): 223 fetch_future = self._FetchAsync(ToDirectory(dir_) + _JSON_FORMAT)
221 return self._FetchAsync(ToDirectory(dir_) + _JSON_FORMAT) 224 return self._ResolveFetchContent(path, fetch_future).Then(stat)
222
223 fetch_future = make_fetch_future()
224 return self._ResolveFetchContent(path, fetch_future,
225 make_fetch_future).Then(stat)
226 225
227 def GetIdentity(self): 226 def GetIdentity(self):
228 # NOTE: Do not use commit information to create the string identity. 227 # NOTE: Do not use commit information to create the string identity.
229 # Doing so will mess up caching. 228 # Doing so will mess up caching.
230 if self._commit is None and self._branch != 'master': 229 if self._commit is None and self._branch != 'master':
231 str_id = GITILES_BRANCH_BASE 230 str_id = '%s/%s/%s/%s' % (
231 GITILES_BASE, GITILES_SRC_ROOT, GITILES_BRANCHES_PATH, self._branch)
232 else: 232 else:
233 str_id = GITILES_BASE 233 str_id = '%s/%s' % (GITILES_BASE, GITILES_SRC_ROOT)
234 return '@'.join((self.__class__.__name__, StringIdentity(str_id))) 234 return '@'.join((self.__class__.__name__, StringIdentity(str_id)))
OLDNEW
« no previous file with comments | « chrome/common/extensions/docs/server2/file_system.py ('k') | chrome/common/extensions/docs/server2/handler.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698