Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / server2 / availability_finder.py
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 posixpath
6
7 from api_models import GetNodeCategories
8 from api_schema_graph import APISchemaGraph
9 from branch_utility import BranchUtility, ChannelInfo
10 from compiled_file_system import CompiledFileSystem, SingleFile, Unicode
11 from extensions_paths import API_PATHS, JSON_TEMPLATES
12 from features_bundle import FeaturesBundle
13 from file_system import FileNotFoundError
14 from schema_processor import SchemaProcessor
15 from third_party.json_schema_compiler.memoize import memoize
16 from third_party.json_schema_compiler.model import UnixName
17
18
19 _DEVTOOLS_API = 'devtools_api.json'
20 _EXTENSION_API = 'extension_api.json'
21 # The version where api_features.json is first available.
22 _API_FEATURES_MIN_VERSION = 28
23 # The version where permission_ and manifest_features.json are available and
24 # presented in the current format.
25 _ORIGINAL_FEATURES_MIN_VERSION = 20
26 # API schemas are aggregated in extension_api.json up to this version.
27 _EXTENSION_API_MAX_VERSION = 17
28 # The earliest version for which we have SVN data.
29 _SVN_MIN_VERSION = 5
30
31
32 def _GetChannelFromFeatures(api_name, features):
33   '''Finds API channel information for |api_name| from |features|.
34   Returns None if channel information for the API cannot be located.
35   '''
36   feature = features.Get().get(api_name)
37   return feature.get('channel') if feature else None
38
39
40 def _GetChannelFromAPIFeatures(api_name, features_bundle):
41   return _GetChannelFromFeatures(api_name, features_bundle.GetAPIFeatures())
42
43
44 def _GetChannelFromManifestFeatures(api_name, features_bundle):
45   # _manifest_features.json uses unix_style API names.
46   api_name = UnixName(api_name)
47   return _GetChannelFromFeatures(api_name,
48                                  features_bundle.GetManifestFeatures())
49
50
51 def _GetChannelFromPermissionFeatures(api_name, features_bundle):
52   return _GetChannelFromFeatures(api_name,
53                                  features_bundle.GetPermissionFeatures())
54
55
56 def _GetAPISchemaFilename(api_name, file_system, version):
57   '''Gets the name of the file which may contain the schema for |api_name| in
58   |file_system|, or None if the API is not found. Note that this may be the
59   single _EXTENSION_API file which all APIs share in older versions of Chrome,
60   in which case it is unknown whether the API actually exists there.
61   '''
62   if version == 'master' or version > _ORIGINAL_FEATURES_MIN_VERSION:
63     # API schema filenames switch format to unix_hacker_style.
64     api_name = UnixName(api_name)
65
66   # Devtools API names have 'devtools.' prepended to them.
67   # The corresponding filenames do not.
68   if 'devtools_' in api_name:
69     api_name = api_name.replace('devtools_', '')
70
71   for api_path in API_PATHS:
72     try:
73       for base, _, filenames in file_system.Walk(api_path):
74         for ext in ('json', 'idl'):
75           filename = '%s.%s' % (api_name, ext)
76           if filename in filenames:
77             return posixpath.join(api_path, base, filename)
78           if _EXTENSION_API in filenames:
79             return posixpath.join(api_path, base, _EXTENSION_API)
80     except FileNotFoundError:
81       continue
82   return None
83
84
85 class AvailabilityInfo(object):
86   '''Represents availability data for an API. |scheduled| is a version number
87   specifying when dev and beta APIs will become stable, or None if that data
88   is unknown.
89   '''
90   def __init__(self, channel_info, scheduled=None):
91     assert isinstance(channel_info, ChannelInfo)
92     assert isinstance(scheduled, int) or scheduled is None
93     self.channel_info = channel_info
94     self.scheduled = scheduled
95
96   def __eq__(self, other):
97     return self.__dict__ == other.__dict__
98
99   def __ne__(self, other):
100     return not (self == other)
101
102   def __repr__(self):
103     return '%s%s' % (type(self).__name__, repr(self.__dict__))
104
105   def __str__(self):
106     return repr(self)
107
108
109 class AvailabilityFinder(object):
110   '''Generates availability information for APIs by looking at API schemas and
111   _features files over multiple release versions of Chrome.
112   '''
113
114   def __init__(self,
115                branch_utility,
116                compiled_fs_factory,
117                file_system_iterator,
118                host_file_system,
119                object_store_creator,
120                platform,
121                schema_processor_factory):
122     self._branch_utility = branch_utility
123     self._compiled_fs_factory = compiled_fs_factory
124     self._file_system_iterator = file_system_iterator
125     self._host_file_system = host_file_system
126     self._object_store_creator = object_store_creator
127     def create_object_store(category):
128       return object_store_creator.Create(
129           AvailabilityFinder, category='/'.join((platform, category)))
130     self._top_level_object_store = create_object_store('top_level')
131     self._node_level_object_store = create_object_store('node_level')
132     self._json_fs = compiled_fs_factory.ForJson(self._host_file_system)
133     self._platform = platform
134     # When processing the API schemas, we retain inlined types in the schema
135     # so that there are not missing nodes in the APISchemaGraphs when trying
136     # to lookup availability.
137     self._schema_processor = schema_processor_factory.Create(True)
138
139   def _GetPredeterminedAvailability(self, api_name):
140     '''Checks a configuration file for hardcoded (i.e. predetermined)
141     availability information for an API.
142     '''
143     api_info = self._json_fs.GetFromFile(
144         JSON_TEMPLATES + 'api_availabilities.json').Get().get(api_name)
145     if api_info is None:
146       return None
147     if api_info['channel'] == 'stable':
148       return AvailabilityInfo(
149           self._branch_utility.GetStableChannelInfo(api_info['version']))
150     return AvailabilityInfo(
151         self._branch_utility.GetChannelInfo(api_info['channel']))
152
153   @memoize
154   def _CreateAPISchemaFileSystem(self, file_system):
155     '''Creates a CompiledFileSystem for parsing raw JSON or IDL API schema
156     data and formatting it so that it can be used to create APISchemaGraphs.
157     '''
158     def process_schema(path, data):
159       return self._schema_processor.Process(path, data)
160     return self._compiled_fs_factory.Create(file_system,
161                                             SingleFile(Unicode(process_schema)),
162                                             CompiledFileSystem,
163                                             category='api-schema')
164
165   def _GetAPISchema(self, api_name, file_system, version):
166     '''Searches |file_system| for |api_name|'s API schema data, and processes
167     and returns it if found.
168     '''
169     api_filename = _GetAPISchemaFilename(api_name, file_system, version)
170     if api_filename is None:
171       # No file for the API could be found in the given |file_system|.
172       return None
173
174     schema_fs = self._CreateAPISchemaFileSystem(file_system)
175     api_schemas = schema_fs.GetFromFile(api_filename).Get()
176     matching_schemas = [api for api in api_schemas
177                         if api['namespace'] == api_name]
178     # There should only be a single matching schema per file, or zero in the
179     # case of no API data being found in _EXTENSION_API.
180     assert len(matching_schemas) <= 1
181     return matching_schemas or None
182
183   def _HasAPISchema(self, api_name, file_system, version):
184     '''Whether or not an API schema for |api_name| exists in the given
185     |file_system|.
186     '''
187     filename = _GetAPISchemaFilename(api_name, file_system, version)
188     if filename is None:
189       return False
190     if filename.endswith(_EXTENSION_API) or filename.endswith(_DEVTOOLS_API):
191       return self._GetAPISchema(api_name, file_system, version) is not None
192     return True
193
194   def _CheckStableAvailability(self,
195                                api_name,
196                                file_system,
197                                version,
198                                earliest_version=None):
199     '''Checks for availability of an API, |api_name|, on the stable channel.
200     Considers several _features.json files, file system existence, and
201     extension_api.json depending on the given |version|.
202     |earliest_version| is the version of Chrome at which |api_name| first became
203     available. It should only be given when checking stable availability for
204     API nodes, so it can be used as an alternative to the check for filesystem
205     existence.
206     '''
207     earliest_version = earliest_version or _SVN_MIN_VERSION
208     if version < earliest_version:
209       # SVN data isn't available below this version.
210       return False
211     features_bundle = self._CreateFeaturesBundle(file_system)
212     available_channel = None
213     if version >= _API_FEATURES_MIN_VERSION:
214       # The _api_features.json file first appears in version 28 and should be
215       # the most reliable for finding API availability.
216       available_channel = _GetChannelFromAPIFeatures(api_name,
217                                                      features_bundle)
218     if version >= _ORIGINAL_FEATURES_MIN_VERSION:
219       # The _permission_features.json and _manifest_features.json files are
220       # present in Chrome 20 and onwards. Use these if no information could be
221       # found using _api_features.json.
222       available_channel = (
223           available_channel or
224           _GetChannelFromPermissionFeatures(api_name, features_bundle) or
225           _GetChannelFromManifestFeatures(api_name, features_bundle))
226       if available_channel is not None:
227         return available_channel == 'stable'
228
229     # |earliest_version| == _SVN_MIN_VERSION implies we're dealing with an API.
230     # Fall back to a check for file system existence if the API is not
231     # stable in any of the _features.json files, or if the _features files
232     # do not exist (version 19 and earlier).
233     if earliest_version == _SVN_MIN_VERSION:
234       return self._HasAPISchema(api_name, file_system, version)
235     # For API nodes, assume it's available if |version| is greater than the
236     # version the node became available (which it is, because of the first
237     # check).
238     return True
239
240   def _CheckChannelAvailability(self, api_name, file_system, channel_info):
241     '''Searches through the _features files in a given |file_system|, falling
242     back to checking the file system for API schema existence, to determine
243     whether or not an API is available on the given channel, |channel_info|.
244     '''
245     features_bundle = self._CreateFeaturesBundle(file_system)
246     available_channel = (
247         _GetChannelFromAPIFeatures(api_name, features_bundle) or
248         _GetChannelFromPermissionFeatures(api_name, features_bundle) or
249         _GetChannelFromManifestFeatures(api_name, features_bundle))
250     if (available_channel is None and
251         self._HasAPISchema(api_name, file_system, channel_info.version)):
252       # If an API is not represented in any of the _features files, but exists
253       # in the filesystem, then assume it is available in this version.
254       # The chrome.windows API is an example of this.
255       available_channel = channel_info.channel
256     # If the channel we're checking is the same as or newer than the
257     # |available_channel| then the API is available at this channel.
258     newest = BranchUtility.NewestChannel((available_channel,
259                                           channel_info.channel))
260     return available_channel is not None and newest == channel_info.channel
261
262   def _CheckChannelAvailabilityForNode(self,
263                                        node_name,
264                                        file_system,
265                                        channel_info,
266                                        earliest_channel_info):
267     '''Searches through the _features files in a given |file_system| to
268     determine whether or not an API node is available on the given channel,
269     |channel_info|. |earliest_channel_info| is the earliest channel the node
270     was introduced.
271     '''
272     features_bundle = self._CreateFeaturesBundle(file_system)
273     available_channel = None
274     # Only API nodes can have their availability overriden on a per-node basis,
275     # so we only need to check _api_features.json.
276     if channel_info.version >= _API_FEATURES_MIN_VERSION:
277       available_channel = _GetChannelFromAPIFeatures(node_name, features_bundle)
278     if (available_channel is None and
279         channel_info.version >= earliest_channel_info.version):
280       # Most API nodes inherit their availabiltity from their parent, so don't
281       # explicitly appear in _api_features.json. For example, "tabs.create"
282       # isn't listed; it inherits from "tabs". Assume these are available at
283       # |channel_info|.
284       available_channel = channel_info.channel
285     newest = BranchUtility.NewestChannel((available_channel,
286                                           channel_info.channel))
287     return available_channel is not None and newest == channel_info.channel
288
289   @memoize
290   def _CreateFeaturesBundle(self, file_system):
291     return FeaturesBundle(file_system,
292                           self._compiled_fs_factory,
293                           self._object_store_creator,
294                           self._platform)
295
296   def _CheckAPIAvailability(self, api_name, file_system, channel_info):
297     '''Determines the availability for an API at a certain version of Chrome.
298     Two branches of logic are used depending on whether or not the API is
299     determined to be 'stable' at the given version.
300     '''
301     if channel_info.channel == 'stable':
302       return self._CheckStableAvailability(api_name,
303                                            file_system,
304                                            channel_info.version)
305     return self._CheckChannelAvailability(api_name,
306                                           file_system,
307                                           channel_info)
308
309   def _FindScheduled(self, api_name, earliest_version=None):
310     '''Determines the earliest version of Chrome where the API is stable.
311     Unlike the code in GetAPIAvailability, this checks if the API is stable
312     even when Chrome is in dev or beta, which shows that the API is scheduled
313     to be stable in that verison of Chrome. |earliest_version| is the version
314     |api_name| became first available. Only use it when finding scheduled
315     availability for nodes.
316     '''
317     def check_scheduled(file_system, channel_info):
318       return self._CheckStableAvailability(api_name,
319                                            file_system,
320                                            channel_info.version,
321                                            earliest_version=earliest_version)
322
323     stable_channel = self._file_system_iterator.Descending(
324         self._branch_utility.GetChannelInfo('dev'), check_scheduled)
325
326     return stable_channel.version if stable_channel else None
327
328   def _CheckAPINodeAvailability(self, node_name, earliest_channel_info):
329     '''Gets availability data for a node by checking _features files.
330     '''
331     def check_node_availability(file_system, channel_info):
332       return self._CheckChannelAvailabilityForNode(node_name,
333                                                    file_system,
334                                                    channel_info,
335                                                    earliest_channel_info)
336     channel_info = (self._file_system_iterator.Descending(
337         self._branch_utility.GetChannelInfo('dev'), check_node_availability) or
338         earliest_channel_info)
339
340     if channel_info.channel == 'stable':
341       scheduled = None
342     else:
343       scheduled = self._FindScheduled(
344           node_name,
345           earliest_version=earliest_channel_info.version)
346
347     return AvailabilityInfo(channel_info, scheduled=scheduled)
348
349   def GetAPIAvailability(self, api_name):
350     '''Performs a search for an API's top-level availability by using a
351     HostFileSystemIterator instance to traverse multiple version of the
352     SVN filesystem.
353     '''
354     availability = self._top_level_object_store.Get(api_name).Get()
355     if availability is not None:
356       return availability
357
358     # Check for predetermined availability and cache this information if found.
359     availability = self._GetPredeterminedAvailability(api_name)
360     if availability is not None:
361       self._top_level_object_store.Set(api_name, availability)
362       return availability
363
364     def check_api_availability(file_system, channel_info):
365       return self._CheckAPIAvailability(api_name, file_system, channel_info)
366
367     channel_info = self._file_system_iterator.Descending(
368         self._branch_utility.GetChannelInfo('dev'),
369         check_api_availability)
370     if channel_info is None:
371       # The API wasn't available on 'dev', so it must be a 'master'-only API.
372       channel_info = self._branch_utility.GetChannelInfo('master')
373
374     # If the API is not stable, check when it will be scheduled to be stable.
375     if channel_info.channel == 'stable':
376       scheduled = None
377     else:
378       scheduled = self._FindScheduled(api_name)
379
380     availability = AvailabilityInfo(channel_info, scheduled=scheduled)
381
382     self._top_level_object_store.Set(api_name, availability)
383     return availability
384
385   def GetAPINodeAvailability(self, api_name):
386     '''Returns an APISchemaGraph annotated with each node's availability (the
387     ChannelInfo at the oldest channel it's available in).
388     '''
389     availability_graph = self._node_level_object_store.Get(api_name).Get()
390     if availability_graph is not None:
391       return availability_graph
392
393     def assert_not_none(value):
394       assert value is not None
395       return value
396
397     availability_graph = APISchemaGraph()
398     host_fs = self._host_file_system
399     master_stat = assert_not_none(host_fs.Stat(_GetAPISchemaFilename(
400         api_name, host_fs, 'master')))
401
402     # Weird object thing here because nonlocal is Python 3.
403     previous = type('previous', (object,), {'stat': None, 'graph': None})
404
405     def update_availability_graph(file_system, channel_info):
406       # If we can't find a filename, skip checking at this branch.
407       # For example, something could have a predetermined availability of 23,
408       # but it doesn't show up in the file system until 26.
409       # We know that the file will become available at some point.
410       #
411       # The problem with this is that at the first version where the API file
412       # exists, we'll get a huge chunk of new objects that don't match
413       # the predetermined API availability.
414       version_filename = _GetAPISchemaFilename(api_name,
415                                                file_system,
416                                                channel_info.version)
417       if version_filename is None:
418         # Continue the loop at the next version.
419         return True
420
421       version_stat = assert_not_none(file_system.Stat(version_filename))
422
423       # Important optimisation: only re-parse the graph if the file changed in
424       # the last revision. Parsing the same schema and forming a graph on every
425       # iteration is really expensive.
426       if version_stat == previous.stat:
427         version_graph = previous.graph
428       else:
429         # Keep track of any new schema elements from this version by adding
430         # them to |availability_graph|.
431         #
432         # Calling |availability_graph|.Lookup() on the nodes being updated
433         # will return the |annotation| object -- the current |channel_info|.
434         version_graph = APISchemaGraph(
435             api_schema=self._GetAPISchema(api_name,
436                                           file_system,
437                                           channel_info.version))
438         def annotator(node_name):
439           return self._CheckAPINodeAvailability('%s.%s' % (api_name, node_name),
440                                                 channel_info)
441
442         availability_graph.Update(version_graph.Subtract(availability_graph),
443                                   annotator)
444
445       previous.stat = version_stat
446       previous.graph = version_graph
447
448       # Continue looping until there are no longer differences between this
449       # version and master.
450       return version_stat != master_stat
451
452     self._file_system_iterator.Ascending(
453         self.GetAPIAvailability(api_name).channel_info,
454         update_availability_graph)
455
456     self._node_level_object_store.Set(api_name, availability_graph)
457     return availability_graph