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

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

Issue 14125010: Docserver: Add support for viewing docs with a codereview patch applied (Closed) Base URL: https://src.chromium.org/svn/trunk/src/
Patch Set: Created 7 years, 7 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
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import json
6 import tarfile
7 from datetime import datetime, timedelta
8 from StringIO import StringIO
9
10 from file_system import FileNotFoundError, ToUnicode
11 from future import Future
12 from patcher import Patcher
13 import svn_constants
14
15 # Use a special value other than None to represent a deleted file in the patch.
16 _FILE_NOT_FOUND_VALUE = (None,)
17
18 _ISSUE_CACHE_MAXAGE = timedelta(seconds=5)
19
20 _CHROMIUM_REPO_BASEURLS = [
21 'https://src.chromium.org/svn/trunk/src/',
22 'http://src.chromium.org/svn/trunk/src/',
23 'svn://svn.chromium.org/chrome/trunk/src',
24 'https://chromium.googlesource.com/chromium/src.git@master',
25 'http://git.chromium.org/chromium/src.git@master',
26 ]
27
28 _DOCS_PATHS = [
29 svn_constants.API_PATH,
30 svn_constants.TEMPLATE_PATH,
31 svn_constants.STATIC_PATH
32 ]
33
34 def _HandleBinary(data, binary):
35 return data if binary else ToUnicode(data)
36
37 class RietveldPatcherError(Exception):
38 def __init__(self, message):
39 self.message = message
40
41 class _AsyncUncachedFuture(object):
42 def __init__(self,
43 cache,
44 version,
45 paths,
46 binary,
47 cached_value,
48 missing_paths,
49 fetch_delegate):
50 self._cache = cache
51 self._version = version
52 self._paths = paths
53 self._binary = binary
54 self._cached_value = cached_value
55 self._missing_paths = missing_paths
56 self._fetch_delegate = fetch_delegate
57
58 def Get(self):
59 uncached_raw_value = self._fetch_delegate.Get()
60 self._cache.CacheFiles(uncached_raw_value, self._version)
61
62 for path in self._missing_paths:
63 if uncached_raw_value.get(path) is None:
64 raise FileNotFoundError('File %s was not found in the patch.' % path)
65 self._cached_value[path] = _HandleBinary(uncached_raw_value[path],
66 self._binary)
67
68 # Make sure all paths exist before returning.
69 for path in self._paths:
70 if self._cached_value[path] == _FILE_NOT_FOUND_VALUE:
71 raise FileNotFoundError('File %s was deleted in the patch.' % path)
72 return self._cached_value
73
74 class _AsyncFetchFuture(object):
75 def __init__(self,
76 base_path,
77 issue,
78 patchset,
79 patched_files,
80 paths_to_skip,
81 fetcher):
82 self._base_path = base_path
83 self._issue = issue
84 self._patchset = patchset
85 self._files = set()
86 for files in patched_files:
87 self._files |= set(files) - set(paths_to_skip)
88 self._tarball = fetcher.FetchAsync('tarball/%s/%s' % (issue, patchset))
89
90 def Get(self):
91 tarball_result = self._tarball.Get()
92 if tarball_result.status_code != 200:
93 raise RietveldPatcherError(
94 'Failed to download tarball for issue %s patchset %s. Status: %s' %
95 (self._issue, self._patchset, tarball_result.status_code))
96
97 try:
98 tar = tarfile.open(fileobj=StringIO(tarball_result.content))
99 except tarfile.TarError as e:
100 raise RietveldPatcherError('Invalid tarball for issue %s patchset %s.' %
101 (self._issue, self._patchset))
102
103 self._value = {}
104 for path in self._files:
105 if self._base_path:
106 tar_path = 'b/%s/%s' % (self._base_path, path)
107 else:
108 tar_path = 'b/%s' % path
109
110 patched_file = None
111 try:
112 patched_file = tar.extractfile(tar_path)
113 data = patched_file.read()
114 except tarfile.TarError as e:
115 # Show appropriate error message in the unlikely case that the tarball
116 # is corrupted.
117 raise RietveldPatcherError(
118 'Error extracting tarball for issue %s patchset %s file %s.' %
119 (self._issue, self._patchset, tar_path))
120 finally:
121 if patched_file:
122 patched_file.close()
123
124 # Deleted files still exist in the tarball, but they are empty.
125 if len(data) == 0:
126 # Mark it empty instead of throwing FileNotFoundError here to make sure
127 # this method completes and returns values to cache.
128 self._value[path] = _FILE_NOT_FOUND_VALUE
129 else:
130 self._value[path] = data
131
132 return self._value
133
134 class RietveldPatcher(Patcher):
135 ''' Class to fetch resources from a patchset in Rietveld.
136 '''
137 class _Cache(object):
not at google - send to devlin 2013/05/07 05:45:40 the caching logic should all be pulled out into a
方觉(Fang Jue) 2013/05/07 06:03:55 I'll try to do that. But I don't think it's possib
not at google - send to devlin 2013/05/07 06:17:21 What do you mean "fetches all patched files at a t
138 def __init__(self, object_store_creator_factory):
139 self._issue_object_store = object_store_creator_factory.Create(
140 RietveldPatcher).Create(category='issue')
141 self._file_object_store = object_store_creator_factory.Create(
142 RietveldPatcher).Create(category='file')
143
144 def GetPatchset(self, fetch_function):
145 key = 'patchset'
146 value = self._issue_object_store.Get(key).Get()
147 if value is not None:
148 patchset, time = value
149 if datetime.now() - time < _ISSUE_CACHE_MAXAGE:
150 return patchset
151
152 patchset = fetch_function()
153 self._issue_object_store.Set(key, (patchset, datetime.now()))
154 return patchset
155
156 def GetPatchedFiles(self, version, fetch_function):
157 value = self._issue_object_store.Get(version).Get()
158 if value is not None:
159 return value
160
161 value = fetch_function()
162 self._issue_object_store.Set(version, value)
163 return value
164
165 ''' Append @version for keys to distinguish between different patchsets of
166 an issue.
167 '''
168 def _MakeKey(self, path_or_paths, version):
169 if isinstance(path_or_paths, list) or isinstance(path_or_paths, set):
170 return ['%s@%s' % (p, version) for p in path_or_paths]
171 return self._MakeKey([path_or_paths], version)[0]
172
173 def _ToObjectStoreValue(self, raw_value, version):
174 return {self._MakeKey(key, version): raw_value[key] for key in raw_value}
175
176 def _FromObjectStoreValue(self, raw_value, binary):
177 return {key[0:key.find('@')]: _HandleBinary(raw_value[key], binary)
178 for key in raw_value}
179
180 def Apply(self, version, paths, binary, fetch_future_function):
181 cached_value = self._FromObjectStoreValue(self._file_object_store.
182 GetMulti(self._MakeKey(paths, version)).Get(), binary)
183 missing_paths = list(set(paths) - set(cached_value.keys()))
184 if len(missing_paths) == 0:
185 return Future(value=cached_value)
186
187 return _AsyncUncachedFuture(self,
188 version,
189 paths,
190 binary,
191 cached_value,
192 missing_paths,
193 fetch_future_function(cached_value.keys()))
194
195 def CacheFiles(self, uncached_raw_value, version):
196 self._file_object_store.SetMulti(self._ToObjectStoreValue(
197 uncached_raw_value, version))
198
199 def __init__(self,
200 base_path,
201 issue,
202 fetcher,
203 object_store_creator_factory):
204 self._base_path = base_path
205 self._issue = issue
206 self._fetcher = fetcher
207 self._object_store = object_store_creator_factory.Create(
208 RietveldPatcher).Create()
209 self._cache = RietveldPatcher._Cache(object_store_creator_factory)
210
211 def GetVersion(self):
212 return self._GetPatchset()
213
214 def _GetPatchset(self):
215 return self._cache.GetPatchset(self._FetchPatchset)
216
217 def _FetchPatchset(self):
218 try:
219 issue_json = json.loads(self._fetcher.Fetch(
220 'api/%s' % self._issue).content)
221 except Exception as e:
222 raise RietveldPatcherError(
223 'Failed to fetch information for issue %s.' % self._issue)
224
225 if issue_json.get('closed'):
226 raise RietveldPatcherError('Issue %s has been closed.' % self._issue)
227
228 patchsets = issue_json.get('patchsets')
229 if not isinstance(patchsets, list) or len(patchsets) == 0:
230 raise RietveldPatcherError('Cannot parse issue %s.' % self._issue)
231
232 if not issue_json.get('base_url') in _CHROMIUM_REPO_BASEURLS:
233 raise RietveldPatcherError('Issue %s\'s base url is unknown.' %
234 self._issue)
235
236 return str(patchsets[-1])
237
238 def GetPatchedFiles(self):
239 return self._cache.GetPatchedFiles(self.GetVersion(),
240 self._FetchPatchedFiles)
241
242 def _FetchPatchedFiles(self):
243 patchset = self.GetVersion()
244 try:
245 patchset_json = json.loads(self._fetcher.Fetch(
246 'api/%s/%s' % (self._issue, patchset)).content)
247 except Exception as e:
248 raise RietveldPatcherError(
249 'Failed to fetch details for issue %s patchset %s.' % (self._issue,
250 patchset))
251
252 files = patchset_json.get('files')
253 if files is None or not isinstance(files, dict):
254 raise RietveldPatcherError('Failed to parse issue %s patchset %s.' %
255 (self._issue, patchset))
256
257 added = []
258 deleted = []
259 modified = []
260 for key in files:
261 f = key.split(self._base_path + '/', 1)[1]
262 if any(f.startswith(path) for path in _DOCS_PATHS):
263 status = (files[key].get('status') or 'M')
264 # status can be 'A ' or 'A + '
265 if 'A' in status:
266 added.append(f)
267 elif 'D' in status:
268 deleted.append(f)
269 else:
270 modified.append(f)
271
272 return (added, deleted, modified)
273
274 def Apply(self, paths, file_system, binary=False):
275 _, deleted, _ = self.GetPatchedFiles()
276 if set(deleted) & set(paths):
277 raise FileNotFoundError('File(s) %s are removed in the patch.' %
278 list(set(deleted) & set(paths)))
279
280 return self._cache.Apply(self.GetVersion(), paths, binary,
281 self._CreateFetchFuture)
282
283 def _CreateFetchFuture(self, paths_to_skip):
284 return Future(delegate=_AsyncFetchFuture(self._base_path,
285 self._issue,
286 self._GetPatchset(),
287 self.GetPatchedFiles(),
288 paths_to_skip,
289 self._fetcher))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698