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 file_system import FileNotFoundError | |
11 import svn_constants | |
12 from third_party.json_schema_compiler import json_parse, model | |
13 | |
14 EXTENSION_API = 'extension_api.json' | |
15 MANIFEST_FEATURES = '_manifest_features.json' | |
16 PERMISSION_FEATURES = '_permission_features.json' | |
17 | |
18 class AvailabilityDataSource(object): | |
not at google - send to devlin
2013/05/13 21:26:41
Late stage for this, but this isn't actually a *Da
epeterson
2013/05/15 07:38:34
Done.
| |
19 '''Uses API data sources generated by a ChromeVersionDataSource in order to | |
20 search the filesystem for the earliest existence of a specified API throughout | |
21 the different versions of Chrome; this constitutes an API's availability. | |
22 ''' | |
23 class Factory(object): | |
24 def __init__(self, | |
25 chrome_version_utility, | |
26 object_store_creator, | |
27 create_file_system): | |
not at google - send to devlin
2013/05/13 21:26:41
Can we try making this a method of ChromeVersionUt
| |
28 self._chrome_version_utility = chrome_version_utility | |
29 self._object_store_creator = object_store_creator | |
30 self._create_file_system = create_file_system | |
31 | |
32 def Create(self): | |
33 return AvailabilityDataSource(self._chrome_version_utility, | |
34 self._object_store_creator, | |
35 self._create_file_system) | |
36 | |
37 def __init__(self, | |
38 chrome_version_utility, | |
39 object_store_creator, | |
40 create_file_system): | |
41 self._chrome_version_utility = chrome_version_utility | |
42 self._object_store_creator = object_store_creator | |
43 self._create_file_system = create_file_system | |
44 | |
45 svn_file_system = create_file_system('trunk') | |
46 def read_json(path): | |
47 return json_parse.Parse(svn_file_system.ReadSingle(path)) | |
48 self._existing_availabilities = read_json( | |
49 '%s/api_availabilities.json' % svn_constants.JSON_PATH) | |
50 self._permission_features = read_json( | |
51 '%s/%s' % (svn_constants.API_PATH, PERMISSION_FEATURES)) | |
52 self._manifest_features = read_json( | |
53 '%s/%s' % (svn_constants.API_PATH, MANIFEST_FEATURES)) | |
not at google - send to devlin
2013/05/13 15:43:32
here is one problem. The file systems are being re
not at google - send to devlin
2013/05/13 21:26:41
Continuing from this comment, and reading below: I
| |
54 | |
55 def create_object_store(category): | |
56 return (object_store_creator.Create(AvailabilityDataSource, | |
57 category=category)) | |
58 self._permission_object_store = create_object_store('permission') | |
59 self._manifest_object_store = create_object_store('manifest') | |
60 self._names_object_store = create_object_store('names') | |
61 self._object_store = create_object_store(None) | |
62 | |
63 self._permission_apis = [] | |
64 self._manifest_apis = [] | |
65 self._orphan_apis = [] | |
not at google - send to devlin
2013/05/13 21:26:41
Looking at the way this is used, it's a bit strang
| |
66 | |
67 def _ReadJson(self, svn_file_system, path): | |
68 return json_parse.Parse(svn_file_system.ReadSingle(path)) | |
69 | |
70 def _GetChannelFromPermissionFeatures(self, api_name, permission_features): | |
not at google - send to devlin
2013/05/13 21:26:41
this function should be able to use self._permissi
epeterson
2013/05/15 07:38:34
Done.
| |
71 '''Handles finding API channel information from _permission_features.json. | |
72 Sometimes this info will be in a dict, but other times it will be | |
73 in a dict contained in a list. | |
74 ''' | |
75 channel = None | |
76 api_info = permission_features.get(api_name) | |
77 if api_info is not None: | |
78 if isinstance(api_info, collections.Mapping): | |
79 channel = api_info.get('channel') | |
80 else: | |
81 channel = BranchUtility.NewestChannel([entry.get('channel') | |
82 for entry in api_info]) | |
not at google - send to devlin
2013/05/13 21:26:41
I don't think that the list comprehension is neces
epeterson
2013/05/15 07:38:34
Done.
| |
83 return channel | |
84 | |
85 def _CheckStablePermissionExistence(self, api_name, version): | |
86 '''Checks _permission_features.json for the given |version| of Chrome, | |
87 and returns a boolean representing whether or not the API was available on | |
88 the stable channel in |version|. | |
89 ''' | |
90 existence = self._permission_object_store.Get( | |
91 '%s%s' % (api_name, version)).Get() | |
92 if existence is not None: | |
93 return existence | |
94 svn_file_system = self._create_file_system( | |
95 self._chrome_version_utility.GetBranchNumberForVersion(version)) | |
96 permission_features = self._ReadJson( | |
97 svn_file_system, | |
98 '%s/%s' % (svn_constants.API_PATH, PERMISSION_FEATURES)) | |
99 existence = 'stable' == self._GetChannelFromPermissionFeatures( | |
100 api_name, | |
101 permission_features) | |
102 self._object_store.Set('%s%s' % (api_name, version), existence) | |
103 return existence | |
104 | |
105 def _CheckManifestExistence(self, api_name, version): | |
106 '''As a fallback measure for an API not being found in _permission_features, | |
107 check _manifest_features.json for the given |version| of Chrome, | |
108 and return a boolean representing whether or not the API was represented | |
109 within the file. | |
110 ''' | |
111 #_manifest_features.json uses unix_hacker_style API names. | |
112 api_name = model.UnixName(api_name) | |
113 existence = self._manifest_object_store.Get( | |
114 '%s%s' % (api_name, version)).Get() | |
115 if existence is not None: | |
116 return existence | |
117 svn_file_system = self._create_file_system( | |
118 self._chrome_version_utility.GetBranchNumberForVersion(version)) | |
119 manifest_features = self._ReadJson( | |
120 svn_file_system, | |
121 '%s/%s' % (svn_constants.API_PATH, MANIFEST_FEATURES)) | |
122 if manifest_features.get(api_name) is not None: | |
123 existence = True | |
124 else: | |
125 existence = False | |
not at google - send to devlin
2013/05/13 21:26:41
existence = api_name in manifest_features
epeterson
2013/05/15 07:38:34
Done.
| |
126 self._object_store.Set('%s%s' % (api_name, version), existence) | |
127 return existence | |
128 | |
129 def _GetAllNames(self, files_dict): | |
130 '''Returns extension-less names for a list of files. | |
131 ''' | |
132 files = files_dict[svn_constants.API_PATH + '/'] | |
133 return [os.path.splitext(f)[0] for f in files] | |
134 | |
135 def _CheckFileSystemExistence(self, api_name, version): | |
136 '''Uses a list of API names for a given branch to check simply for the | |
137 existence of a given API in that branch. | |
138 ''' | |
139 names = self._names_object_store.Get(version).Get() | |
140 if names is not None: | |
141 return api_name in names | |
142 svn_file_system = self._create_file_system( | |
143 self._chrome_version_utility.GetBranchNumberForVersion(version)) | |
144 names = svn_file_system.Read([svn_constants.API_PATH + '/']).Get() | |
not at google - send to devlin
2013/05/13 21:26:41
ReadSingle
search in this file for ".Read(" and s
epeterson
2013/05/15 07:38:34
Done.
| |
145 names = self._GetAllNames(names) | |
146 self._object_store.Set(version, names) | |
147 return api_name in names | |
148 | |
149 def _CheckExtensionAPIExistence(self, api_name, version): | |
150 '''Parses the extension_api.json file (available in earlier versions of | |
151 chrome) for an API namespace. If this is successfully found, then the API | |
152 is considered to have been 'stable' for the given version. | |
153 ''' | |
154 # The extension_api.json files only appear from version 5 to version 17. | |
155 if int(version) > 17 or int(version) < 5: | |
not at google - send to devlin
2013/05/13 21:26:41
make these assert, this method should only be call
epeterson
2013/05/15 07:38:34
Done.
| |
156 return False | |
157 | |
158 # Re-use this object-store - the versions won't conflict with each other. | |
159 existence = self._names_object_store.Get('%s%s' % (api_name, version)).Get() | |
not at google - send to devlin
2013/05/13 21:26:41
I don't think caching needs to be done here, you'r
epeterson
2013/05/15 07:38:34
Done.
| |
160 if existence is not None: | |
161 return existence | |
162 svn_file_system = self._create_file_system( | |
163 self._chrome_version_utility.GetBranchNumberForVersion(version)) | |
164 try: | |
165 extension_api = self._ReadJson( | |
166 svn_file_system, | |
167 '%s/%s' % (svn_constants.API_PATH, EXTENSION_API)) | |
168 api_rows = [row.get('namespace') for row in extension_api | |
169 if 'namespace' in row] | |
170 return True if api_name in api_rows else False | |
171 except FileNotFoundError as e: | |
172 # This should only happen on preview.py, since extension_api.json is no | |
173 # longer present in current versions. | |
174 return False | |
175 | |
176 def _FindEarliestStableAvailability(self, api_name, version): | |
177 '''Searches in descending order through filesystem caches tied to specific | |
178 chrome version numbers and looks for the availability of an API, |api_name|, | |
179 on the stable channel. When a version is found where the API is no longer | |
180 available on stable, returns the previous version number (the last known | |
181 version where the API was stable). | |
182 ''' | |
183 # The _permission_features.json file (with 'channel' keys) is present only | |
184 # in Chrome 20 and onwards. We'll also check the _manifest_features.json if | |
185 # an API isn't found in _permission_features, and, failing that, we'll check | |
186 # for the API's existence in the filesystem. | |
187 while version >= 20: | |
188 if ((api_name in self._permission_apis | |
189 and self._CheckStablePermissionExistence(api_name, str(version))) | |
190 or (api_name in self._manifest_apis | |
191 and self._CheckManifestExistence(api_name, str(version))) | |
192 or (api_name in self._orphan_apis | |
193 and self._CheckFileSystemExistence(api_name, str(version)))): | |
194 version -= 1 | |
195 else: | |
196 break | |
not at google - send to devlin
2013/05/13 21:26:41
THis method is super cool, but a bit odd to read a
epeterson
2013/05/15 07:38:34
Done.
| |
197 # These versions are a little troublesome. Version 19 has | |
198 # _permission_features.json, but it lacks 'channel' information. Version 18 | |
199 # doesn't have either of the json files. For now, we're using a simple check | |
200 # for filesystem existence here. | |
201 while version < 20 and version >= 18: | |
202 if self._CheckFileSystemExistence(api_name, str(version)): | |
203 version -= 1 | |
204 else: | |
205 break | |
206 # Versions 17 and earlier have an extension_api.json file which contains | |
207 # namespaces for each API that was available at the time. We can use this | |
208 # file to check for existence, however, we no longer have | |
209 # file system access after version 5, so stop there. | |
210 while version < 18 and version >= 5: | |
211 if self._CheckExtensionAPIExistence(api_name, str(version)): | |
not at google - send to devlin
2013/05/13 21:26:41
why str(version)? Can we use ints everywhere?
epeterson
2013/05/15 07:38:34
Done.
| |
212 version -= 1 | |
213 else: | |
214 break | |
215 return str(version + 1) | |
216 | |
217 def GetAvailability(self, api_name): | |
not at google - send to devlin
2013/05/13 21:26:41
I will want to add event/function/property availab
epeterson
2013/05/15 07:38:34
Done.
| |
218 '''Determines the availability for an API by testing several scenarios. | |
219 (i.e. Is the API experimental? Only available on certain development | |
220 channels? If it's stable, when did it first become stable? etc.) | |
221 ''' | |
222 availability = self._object_store.Get(api_name).Get() | |
223 if availability is not None: | |
224 return availability | |
225 | |
226 # Check for a predetermined availability for this API. | |
227 availability = self._existing_availabilities.get(api_name) | |
not at google - send to devlin
2013/05/13 21:26:41
e.g. in lazy creation world this would be self._Ge
epeterson
2013/05/15 07:38:34
Done.
| |
228 if availability is not None: | |
229 self._object_store.Set(api_name, availability) | |
230 return availability | |
231 | |
232 latest_version = str(self._chrome_version_utility.GetLatestVersionNumber()) | |
233 channel = self._GetChannelFromPermissionFeatures(api_name, | |
234 self._permission_features) | |
not at google - send to devlin
2013/05/13 21:26:41
This won't actually work properly, though: say if
epeterson
2013/05/15 07:38:34
So this is a bit of a mess now. Couldn't quite get
| |
235 | |
236 # There are three different scenarios for APIs in Chrome 20 and up. They | |
237 # can be in _permission_features, _manifest_features, or neither of the two. | |
238 # APIs are added to a respective list depending on which of these scenarios | |
239 # they fall into within the trunk filesystem. | |
240 if channel is not None: | |
241 # This is a special case: availability can be any of the dev channels. | |
242 # First, check for this case before checking for stable existence. | |
243 availability = channel | |
244 self._permission_apis.append(api_name) | |
245 elif self._CheckManifestExistence(api_name, latest_version): | |
246 availability = 'stable' | |
247 self._manifest_apis.append(api_name) | |
248 elif self._CheckFileSystemExistence(api_name, latest_version): | |
249 availability = 'stable' | |
250 self._orphan_apis.append(api_name) | |
251 else: | |
252 raise FileNotFoundError( | |
253 'The API schema for %s could not be located.' % api_name) | |
254 availability = None | |
255 | |
256 if availability == 'stable': | |
257 availability = self._FindEarliestStableAvailability( | |
258 api_name, | |
259 int(latest_version) - 1) | |
260 self._object_store.Set(api_name, availability) | |
261 return availability | |
OLD | NEW |