OLD | NEW |
---|---|
(Empty) | |
1 # Copyright (c) 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 collections | |
6 import os | |
7 | |
8 from branch_utility import BranchUtility | |
9 from chrome_version_utility import ChromeVersionUtility | |
10 from compiled_file_system import CompiledFileSystem | |
11 from file_system import FileNotFoundError | |
12 import svn_constants | |
13 from third_party.json_schema_compiler import json_parse, model | |
14 | |
15 API_AVAILABILITIES = 'api_availabilities.json' | |
16 EXTENSION_API = 'extension_api.json' | |
17 MANIFEST_FEATURES = '_manifest_features.json' | |
18 PERMISSION_FEATURES = '_permission_features.json' | |
not at google - send to devlin
2013/05/15 08:15:00
these are all private so should start with a _, an
epeterson
2013/06/02 00:25:50
Done.
| |
19 | |
20 def _GetChannelFromPermissionFeature(permission_feature): | |
21 '''Handles finding API channel information from _permission_features.json. | |
22 Sometimes this info will be in a dict, but other times it will be | |
23 in a dict contained in a list. | |
24 ''' | |
25 channel = None | |
26 if permission_feature is not None: | |
27 if isinstance(permission_feature, collections.Mapping): | |
28 channel = permission_feature.get('channel') | |
29 else: | |
30 channel = BranchUtility.NewestChannel(entry.get('channel') | |
31 for entry in permission_feature) | |
32 return channel | |
33 | |
34 class AvailabilityFinder(object): | |
35 '''Uses API data sources generated by a ChromeVersionDataSource in order to | |
36 search the filesystem for the earliest existence of a specified API throughout | |
37 the different versions of Chrome; this constitutes an API's availability. | |
38 ''' | |
39 class AvailabilityInfo(object): | |
not at google - send to devlin
2013/05/15 08:15:00
this object is picklable, so it can be directly pu
epeterson
2013/06/02 00:25:50
Done. (I had tried this initially, but I think it
| |
40 def __init__(self, channel, version): | |
41 self.channel = channel | |
42 self.version = version | |
43 | |
44 class Factory(object): | |
45 def __init__(self, | |
46 chrome_version_utility, | |
47 object_store_creator, | |
48 compiled_host_fs_factory, | |
49 create_file_system): | |
50 self._chrome_version_utility = chrome_version_utility | |
51 self._object_store_creator = object_store_creator | |
52 self._compiled_host_fs_factory = compiled_host_fs_factory | |
53 self._create_file_system = create_file_system | |
54 | |
55 def Create(self): | |
56 return AvailabilityFinder(self._chrome_version_utility, | |
57 self._object_store_creator, | |
58 self._compiled_host_fs_factory, | |
59 self._create_file_system) | |
60 | |
61 def __init__(self, | |
62 chrome_version_utility, | |
63 object_store_creator, | |
64 compiled_host_fs_factory, | |
65 create_file_system): | |
66 self._chrome_version_utility = chrome_version_utility | |
67 self._object_store_creator = object_store_creator | |
68 self._json_cache = compiled_host_fs_factory.Create(self._ParseForCompiledFS, | |
not at google - send to devlin
2013/05/15 08:15:00
just pass in json_parse.Parse, no need to wrap it
epeterson
2013/06/02 00:25:50
compiled filesystems send a path and file data to
| |
69 AvailabilityFinder, | |
70 'json-cache') | |
71 self._branch_utility = BranchUtility.Create(object_store_creator) | |
72 self._create_file_system = create_file_system | |
73 self._object_store = object_store_creator.Create(AvailabilityFinder) | |
74 self._permission_apis = [] | |
not at google - send to devlin
2013/05/15 08:15:00
as I mentioned in IRC - holding onto these (permis
epeterson
2013/06/02 00:25:50
The intent was to use these to force APIs that wer
| |
75 self._manifest_apis = [] | |
76 self._orphan_apis = [] | |
77 | |
78 def _ParseForCompiledFS(self, path, json): | |
79 '''Wrapper method to be passed to compiled_file_systems upon creation. | |
80 ''' | |
81 return json_parse.Parse(json) | |
82 | |
83 def _CreateCompiledFS(self, branch): | |
84 # TODO(epeterson) Move create_file_system logic into ChromeVersionUtility. | |
not at google - send to devlin
2013/05/15 08:15:00
don't worry about it
| |
85 compiled_fs_factory = CompiledFileSystem.Factory( | |
86 self._create_file_system(branch), | |
87 self._object_store_creator) | |
88 return compiled_fs_factory.Create(self._ParseForCompiledFS, | |
not at google - send to devlin
2013/05/15 08:15:00
ditto
epeterson
2013/06/02 00:25:50
Done.
| |
89 AvailabilityFinder, | |
90 'branch') | |
91 | |
92 def _GetExistingAvailability(self, api_name, compiled_fs): | |
93 api_info = compiled_fs.GetFromFile( | |
94 '%s/%s' % (svn_constants.JSON_PATH, API_AVAILABILITIES)).get(api_name) | |
95 if api_info is not None: | |
96 return [api_info.get('channel'), api_info.get('version')] | |
97 return None | |
not at google - send to devlin
2013/05/15 08:15:00
as I mentioned, AvailabilityFinder is picklable, s
epeterson
2013/06/02 00:25:50
Done.
| |
98 | |
99 def _GetPermissionFeature(self, api_name, compiled_fs): | |
100 return compiled_fs.GetFromFile( | |
101 '%s/%s' % (svn_constants.API_PATH, PERMISSION_FEATURES)).get(api_name) | |
102 | |
103 def _GetManifestFeature(self, api_name, compiled_fs): | |
104 return compiled_fs.GetFromFile( | |
105 '%s/%s' % (svn_constants.API_PATH, MANIFEST_FEATURES)).get(api_name) | |
106 | |
107 def _GetJSONForExtensionAPI(self, api_name, compiled_fs): | |
108 return compiled_fs.GetFromFile( | |
109 '%s/%s' % (svn_constants.API_PATH, EXTENSION_API)) | |
110 | |
111 def _ReadJson(self, svn_file_system, path): | |
not at google - send to devlin
2013/05/15 08:15:00
not used
epeterson
2013/06/02 00:25:50
Done.
| |
112 return json_parse.Parse(svn_file_system.ReadSingle(path)) | |
113 | |
not at google - send to devlin
2013/05/15 08:15:00
extra \n
epeterson
2013/06/02 00:25:50
Done.
| |
114 | |
115 def _CheckStablePermissionExistence(self, api_name, version): | |
116 '''Checks _permission_features.json for the given |version| of Chrome, | |
117 and returns a boolean representing whether or not the API was available on | |
118 the stable channel in |version|. | |
119 ''' | |
120 compiled_fs = self._CreateCompiledFS( | |
121 self._chrome_version_utility.GetBranchNumberForVersion(version)) | |
122 permission_feature = self._GetPermissionFeature(api_name, compiled_fs) | |
123 existence = 'stable' == _GetChannelFromPermissionFeature(permission_feature) | |
124 return existence | |
125 | |
126 def _CheckChannelPermissionExistence(self, api_name, version, channel): | |
127 compiled_fs = self._CreateCompiledFS( | |
128 self._chrome_version_utility.GetBranchNumberForVersion(version)) | |
129 permission_feature = self._GetPermissionFeature(api_name, compiled_fs) | |
130 existence = channel == _GetChannelFromPermissionFeature(permission_feature) | |
131 return existence | |
132 | |
not at google - send to devlin
2013/05/15 08:15:00
extra \n
epeterson
2013/06/02 00:25:50
Done.
| |
133 | |
134 def _CheckSimplePermissionExistence(self, api_name, version): | |
not at google - send to devlin
2013/05/15 08:15:00
looks like this method can be combined with checks
epeterson
2013/06/02 00:25:50
Logic has been moved up above.
| |
135 compiled_fs = self._CreateCompiledFS( | |
136 self._chrome_version_utility.GetBranchNumberForVersion(version)) | |
137 permission_feature = self._GetPermissionFeature(api_name, compiled_fs) | |
138 return permission_feature is not None | |
139 | |
140 def _CheckManifestExistence(self, api_name, version): | |
not at google - send to devlin
2013/05/15 08:15:00
_ExistsInManifestFeatures, take a file system
epeterson
2013/06/02 00:25:50
Done.
| |
141 '''As a fallback measure for an API not being found in _permission_features, | |
142 check _manifest_features.json for the given |version| of Chrome, | |
143 and return a boolean representing whether or not the API was represented | |
144 within the file. | |
145 ''' | |
146 #_manifest_features.json uses unix_hacker_style API names. | |
147 api_name = model.UnixName(api_name) | |
148 compiled_fs = self._CreateCompiledFS( | |
149 self._chrome_version_utility.GetBranchNumberForVersion(version)) | |
150 manifest_feature = self._GetManifestFeature(api_name, compiled_fs) | |
151 return manifest_feature is not None | |
152 | |
153 def _GetAllNames(self, files): | |
154 '''Returns extension-less names for a list of files. | |
155 ''' | |
156 return [os.path.splitext(f)[0] for f in files] | |
157 | |
158 def _CheckFileSystemExistence(self, api_name, version): | |
not at google - send to devlin
2013/05/15 08:15:00
_ExistsInFileSystem, take a file system
epeterson
2013/06/02 00:25:50
Done.
| |
159 '''Uses a list of API names for a given branch to check simply for the | |
160 existence of a given API in that branch. | |
161 ''' | |
162 names = self._object_store.Get(str(version)).Get() | |
not at google - send to devlin
2013/05/15 08:15:00
shouldn't need to stringify version here
epeterson
2013/06/02 00:25:50
This has been removed completely. AvailabilityFind
| |
163 if names is not None: | |
164 return api_name in names | |
165 svn_file_system = self._create_file_system( | |
not at google - send to devlin
2013/05/15 08:15:00
use the compiled file system for this instead?
epeterson
2013/06/02 00:25:50
Done, although the logic's been moved into a funct
| |
166 self._chrome_version_utility.GetBranchNumberForVersion(version)) | |
167 names = self._GetAllNames( | |
168 svn_file_system.ReadSingle('%s/' % svn_constants.API_PATH)) | |
169 self._object_store.Set(str(version), names) | |
170 return api_name in names | |
171 | |
172 def _CheckExtensionAPIExistence(self, api_name, version): | |
173 '''Parses the extension_api.json file (available in earlier versions of | |
174 chrome) for an API namespace. If this is successfully found, then the API | |
175 is considered to have been 'stable' for the given version. | |
176 ''' | |
177 # The extension_api.json files only appear from version 5 to version 17. | |
178 assert(version <= 17 and version >= 5) | |
179 | |
180 compiled_fs = self._CreateCompiledFS( | |
181 self._chrome_version_utility.GetBranchNumberForVersion(version)) | |
182 try: | |
183 extension_api = self._GetJSONForExtensionAPI(api_name, compiled_fs) | |
184 api_rows = [row.get('namespace') for row in extension_api | |
185 if 'namespace' in row] | |
186 return True if api_name in api_rows else False | |
187 except FileNotFoundError as e: | |
188 # This should only happen on preview.py, since extension_api.json is no | |
189 # longer present in current versions. | |
190 return False | |
191 | |
192 def _FindEarliestAvailability(self, api_name, version): | |
193 '''Searches in descending order through filesystem caches tied to specific | |
194 chrome version numbers and looks for the availability of an API, |api_name|, | |
195 on the stable channel. When a version is found where the API is no longer | |
196 available on stable, returns the previous version number (the last known | |
197 version where the API was stable). | |
198 ''' | |
199 # The _permission_features.json file (with 'channel' keys) is present only | |
200 # in Chrome 20 and onwards. We'll also check the _manifest_features.json if | |
201 # an API isn't found in _permission_features, and, failing that, we'll check | |
202 # for the API's existence in the filesystem. | |
203 while version >= 20: | |
204 if ((api_name in self._permission_apis | |
205 and self._CheckChannelPermissionExistence(api_name, | |
206 version, | |
207 'stable')) | |
208 or (api_name in self._manifest_apis | |
209 and self._CheckManifestExistence(api_name, version)) | |
210 or (api_name in self._orphan_apis | |
211 and self._CheckFileSystemExistence(api_name, version))): | |
212 version -= 1 | |
213 else: | |
214 return version + 1 | |
not at google - send to devlin
2013/05/15 08:15:00
pls see earlier comments about simplifying the ret
| |
215 # These versions are a little troublesome. Version 19 has | |
216 # _permission_features.json, but it lacks 'channel' information. Version 18 | |
217 # doesn't have either of the json files. For now, we're using a simple check | |
218 # for filesystem existence here. | |
219 while version >= 18: | |
220 if self._CheckFileSystemExistence(api_name, version): | |
221 version -= 1 | |
222 else: | |
223 return version + 1 | |
224 # Versions 17 and earlier have an extension_api.json file which contains | |
225 # namespaces for each API that was available at the time. We can use this | |
226 # file to check for existence; however, we no longer have | |
227 # file system access after version 5, so the search stops there. | |
228 while version >= 5 and self._CheckExtensionAPIExistence(api_name, version): | |
229 version -= 1 | |
230 return version + 1 | |
231 | |
232 def _IsAvailable(self, api_name, version): | |
not at google - send to devlin
2013/05/15 08:15:00
yes, except pass this an api and a file system, no
epeterson
2013/06/02 00:25:50
The functions for checking existence now take a fi
| |
233 return ((api_name in self._permission_apis | |
234 and self._CheckSimplePermissionExistence(api_name, version)) | |
235 or (api_name in self._manifest_apis | |
236 and self._CheckManifestExistence(api_name, version)) | |
237 or (api_name in self._orphan_apis | |
238 and self._CheckFileSystemExistence(api_name, version))) | |
239 | |
240 def GetApiAvailability(self, api_name): | |
241 '''Determines the availability for an API by testing several scenarios. | |
242 (i.e. Is the API experimental? Only available on certain development | |
243 channels? If it's stable, when did it first become stable? etc.) | |
244 ''' | |
245 availability = self._object_store.Get(api_name).Get() | |
246 if availability is not None: | |
247 return self.AvailabilityInfo(availability[0], availability[1]) | |
248 | |
249 # Check for a predetermined availability for this API. | |
250 availability = self._GetExistingAvailability(api_name, self._json_cache) | |
251 if availability is not None: | |
252 self._object_store.Set(api_name, availability) | |
253 return self.AvailabilityInfo(availability[0], availability[1]) | |
not at google - send to devlin
2013/05/15 08:15:00
this block of code seems to be doing the same as t
epeterson
2013/06/02 00:25:50
The previous block is checking the object_store, w
| |
254 | |
255 latest_version = self._chrome_version_utility.GetLatestVersionNumber() | |
256 | |
257 # TODO(epeterson) This shouldn't be done upfront | |
not at google - send to devlin
2013/05/15 08:15:00
yeah I think this block of code can basically be d
epeterson
2013/06/02 00:25:50
Done.
| |
258 # There are three different scenarios for APIs in Chrome 20 and up. They | |
259 # can be in _permission_features, _manifest_features, or neither of the two. | |
260 # APIs are added to a respective list depending on which of these scenarios | |
261 # they fall into within the trunk filesystem. | |
262 if self._CheckSimplePermissionExistence(api_name, 'trunk'): | |
263 # This is a special case: availability can be any of the dev channels. | |
264 # First, check for this case before checking for stable existence. | |
265 self._permission_apis.append(api_name) | |
266 elif self._CheckManifestExistence(api_name, 'trunk'): | |
267 self._manifest_apis.append(api_name) | |
268 elif self._CheckFileSystemExistence(api_name, 'trunk'): | |
269 self._orphan_apis.append(api_name) | |
270 else: | |
271 raise FileNotFoundError( | |
272 'The API schema for %s could not be located.' % api_name) | |
273 | |
274 # Channel: availability[0]; Version: availability[1] | |
275 availability = ['trunk', latest_version] | |
276 for channel in BranchUtility.GetAllChannelNames(): | |
277 channel_info = self._branch_utility.GetChannelInfo(channel) | |
278 # TODO(epeterson) unicode to ints in branch_utility | |
279 if self._IsAvailable(api_name, availability[1]): | |
280 availability[0] = channel | |
281 if channel_info.version == 'trunk': | |
282 availability[1] = 'trunk' | |
283 else: | |
284 availability[1] = int(channel_info.version) | |
285 break | |
not at google - send to devlin
2013/05/15 08:15:00
this all looks very complicated?
I think it can b
epeterson
2013/06/02 00:25:50
Done.
| |
286 if availability[0] == 'stable': | |
287 availability[1] = self._FindEarliestAvailability( | |
288 api_name, | |
289 int(availability[1])) | |
290 | |
291 self._object_store.Set(api_name, availability) | |
292 return self.AvailabilityInfo(availability[0], availability[1]) | |
OLD | NEW |