1eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# Copyright 2013 The Chromium Authors. All rights reserved.
2eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# Use of this source code is governed by a BSD-style license that can be
3eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# found in the LICENSE file.
4eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import posixpath
6eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from api_models import GetNodeCategories
868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)from api_schema_graph import APISchemaGraph
95c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufrom branch_utility import BranchUtility, ChannelInfo
10116680a4aac90f2aa7413d9095a592090648e557Ben Murdochfrom compiled_file_system import CompiledFileSystem, SingleFile, Unicode
11effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom extensions_paths import API_PATHS, JSON_TEMPLATES
12effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom features_bundle import FeaturesBundle
13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from file_system import FileNotFoundError
141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccifrom schema_processor import SchemaProcessor
15effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom third_party.json_schema_compiler.memoize import memoize
16424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from third_party.json_schema_compiler.model import UnixName
17eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
18eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
19116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch_DEVTOOLS_API = 'devtools_api.json'
2068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)_EXTENSION_API = 'extension_api.json'
21a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)# The version where api_features.json is first available.
22a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)_API_FEATURES_MIN_VERSION = 28
23a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)# The version where permission_ and manifest_features.json are available and
24a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)# presented in the current format.
25a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)_ORIGINAL_FEATURES_MIN_VERSION = 20
26a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)# API schemas are aggregated in extension_api.json up to this version.
27a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)_EXTENSION_API_MAX_VERSION = 17
28a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)# The earliest version for which we have SVN data.
29a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)_SVN_MIN_VERSION = 5
30a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
31a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
32effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _GetChannelFromFeatures(api_name, features):
33effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  '''Finds API channel information for |api_name| from |features|.
34effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  Returns None if channel information for the API cannot be located.
35eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  '''
36effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  feature = features.Get().get(api_name)
37effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  return feature.get('channel') if feature else None
38eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
39eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
40116680a4aac90f2aa7413d9095a592090648e557Ben Murdochdef _GetChannelFromAPIFeatures(api_name, features_bundle):
41116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  return _GetChannelFromFeatures(api_name, features_bundle.GetAPIFeatures())
42116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
43116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
44116680a4aac90f2aa7413d9095a592090648e557Ben Murdochdef _GetChannelFromManifestFeatures(api_name, features_bundle):
45116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  # _manifest_features.json uses unix_style API names.
46116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  api_name = UnixName(api_name)
47116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  return _GetChannelFromFeatures(api_name,
48116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                                 features_bundle.GetManifestFeatures())
49116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
50116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
51116680a4aac90f2aa7413d9095a592090648e557Ben Murdochdef _GetChannelFromPermissionFeatures(api_name, features_bundle):
52116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  return _GetChannelFromFeatures(api_name,
53116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                                 features_bundle.GetPermissionFeatures())
54116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
55116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
56116680a4aac90f2aa7413d9095a592090648e557Ben Murdochdef _GetAPISchemaFilename(api_name, file_system, version):
57116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  '''Gets the name of the file which may contain the schema for |api_name| in
58116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  |file_system|, or None if the API is not found. Note that this may be the
59116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  single _EXTENSION_API file which all APIs share in older versions of Chrome,
60116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  in which case it is unknown whether the API actually exists there.
61116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  '''
621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if version == 'master' or version > _ORIGINAL_FEATURES_MIN_VERSION:
63116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    # API schema filenames switch format to unix_hacker_style.
64116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    api_name = UnixName(api_name)
65116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
66116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  # Devtools API names have 'devtools.' prepended to them.
67116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  # The corresponding filenames do not.
68116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  if 'devtools_' in api_name:
69116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    api_name = api_name.replace('devtools_', '')
70116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
71116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  for api_path in API_PATHS:
72116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    try:
73116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      for base, _, filenames in file_system.Walk(api_path):
74116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        for ext in ('json', 'idl'):
75116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch          filename = '%s.%s' % (api_name, ext)
76116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch          if filename in filenames:
77116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch            return posixpath.join(api_path, base, filename)
78116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch          if _EXTENSION_API in filenames:
79116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch            return posixpath.join(api_path, base, _EXTENSION_API)
80116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    except FileNotFoundError:
81116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      continue
82116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  return None
83116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
84116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liuclass AvailabilityInfo(object):
865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  '''Represents availability data for an API. |scheduled| is a version number
875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  specifying when dev and beta APIs will become stable, or None if that data
885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  is unknown.
895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  '''
905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def __init__(self, channel_info, scheduled=None):
915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    assert isinstance(channel_info, ChannelInfo)
925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    assert isinstance(scheduled, int) or scheduled is None
935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self.channel_info = channel_info
945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self.scheduled = scheduled
955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def __eq__(self, other):
975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return self.__dict__ == other.__dict__
985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def __ne__(self, other):
1005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return not (self == other)
1015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def __repr__(self):
1035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return '%s%s' % (type(self).__name__, repr(self.__dict__))
1045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def __str__(self):
1065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return repr(self)
1075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
109eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochclass AvailabilityFinder(object):
110424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  '''Generates availability information for APIs by looking at API schemas and
111424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  _features files over multiple release versions of Chrome.
112eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  '''
113eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
114eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  def __init__(self,
11568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)               branch_utility,
1161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)               compiled_fs_factory,
1171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)               file_system_iterator,
1181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)               host_file_system,
119116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch               object_store_creator,
1201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci               platform,
1211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci               schema_processor_factory):
1221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    self._branch_utility = branch_utility
1231e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    self._compiled_fs_factory = compiled_fs_factory
124424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    self._file_system_iterator = file_system_iterator
1251e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    self._host_file_system = host_file_system
126eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    self._object_store_creator = object_store_creator
12768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    def create_object_store(category):
128116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      return object_store_creator.Create(
129116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch          AvailabilityFinder, category='/'.join((platform, category)))
13068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    self._top_level_object_store = create_object_store('top_level')
13168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    self._node_level_object_store = create_object_store('node_level')
132f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._json_fs = compiled_fs_factory.ForJson(self._host_file_system)
133116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    self._platform = platform
1341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # When processing the API schemas, we retain inlined types in the schema
1351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # so that there are not missing nodes in the APISchemaGraphs when trying
1361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    # to lookup availability.
1371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._schema_processor = schema_processor_factory.Create(True)
138f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
139f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def _GetPredeterminedAvailability(self, api_name):
140f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    '''Checks a configuration file for hardcoded (i.e. predetermined)
141f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    availability information for an API.
142f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    '''
143f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    api_info = self._json_fs.GetFromFile(
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        JSON_TEMPLATES + 'api_availabilities.json').Get().get(api_name)
145f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if api_info is None:
146f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return None
147f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if api_info['channel'] == 'stable':
1485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return AvailabilityInfo(
1495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          self._branch_utility.GetStableChannelInfo(api_info['version']))
1505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return AvailabilityInfo(
1515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        self._branch_utility.GetChannelInfo(api_info['channel']))
152a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
153116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  @memoize
154116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  def _CreateAPISchemaFileSystem(self, file_system):
155116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    '''Creates a CompiledFileSystem for parsing raw JSON or IDL API schema
156116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    data and formatting it so that it can be used to create APISchemaGraphs.
157a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    '''
158116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    def process_schema(path, data):
1591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return self._schema_processor.Process(path, data)
160116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    return self._compiled_fs_factory.Create(file_system,
161116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                                            SingleFile(Unicode(process_schema)),
162116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                                            CompiledFileSystem,
163116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                                            category='api-schema')
164a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
1656d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def _GetAPISchema(self, api_name, file_system, version):
166a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    '''Searches |file_system| for |api_name|'s API schema data, and processes
167a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    and returns it if found.
168a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    '''
169116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    api_filename = _GetAPISchemaFilename(api_name, file_system, version)
170a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if api_filename is None:
171a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      # No file for the API could be found in the given |file_system|.
172a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      return None
173a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
174116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    schema_fs = self._CreateAPISchemaFileSystem(file_system)
175a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    api_schemas = schema_fs.GetFromFile(api_filename).Get()
176a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    matching_schemas = [api for api in api_schemas
177a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                        if api['namespace'] == api_name]
178a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    # There should only be a single matching schema per file, or zero in the
179a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    # case of no API data being found in _EXTENSION_API.
180a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    assert len(matching_schemas) <= 1
181a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    return matching_schemas or None
182a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
1836d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def _HasAPISchema(self, api_name, file_system, version):
184116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    '''Whether or not an API schema for |api_name| exists in the given
185a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    |file_system|.
186a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    '''
187116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    filename = _GetAPISchemaFilename(api_name, file_system, version)
188a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if filename is None:
189a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      return False
190116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    if filename.endswith(_EXTENSION_API) or filename.endswith(_DEVTOOLS_API):
1916d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      return self._GetAPISchema(api_name, file_system, version) is not None
192a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    return True
193424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
1945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def _CheckStableAvailability(self,
1955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                               api_name,
1965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                               file_system,
1975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                               version,
1985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                               earliest_version=None):
199424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Checks for availability of an API, |api_name|, on the stable channel.
200424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    Considers several _features.json files, file system existence, and
201424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    extension_api.json depending on the given |version|.
2025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    |earliest_version| is the version of Chrome at which |api_name| first became
2035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    available. It should only be given when checking stable availability for
2045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    API nodes, so it can be used as an alternative to the check for filesystem
2055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    existence.
206eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''
2075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    earliest_version = earliest_version or _SVN_MIN_VERSION
2085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if version < earliest_version:
209a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      # SVN data isn't available below this version.
210424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return False
211effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    features_bundle = self._CreateFeaturesBundle(file_system)
212424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    available_channel = None
213a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if version >= _API_FEATURES_MIN_VERSION:
214424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # The _api_features.json file first appears in version 28 and should be
215424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # the most reliable for finding API availability.
216116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      available_channel = _GetChannelFromAPIFeatures(api_name,
217116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                                                     features_bundle)
218a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if version >= _ORIGINAL_FEATURES_MIN_VERSION:
219424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # The _permission_features.json and _manifest_features.json files are
220424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # present in Chrome 20 and onwards. Use these if no information could be
221424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # found using _api_features.json.
222effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      available_channel = (
223effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          available_channel or
224116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch          _GetChannelFromPermissionFeatures(api_name, features_bundle) or
225116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch          _GetChannelFromManifestFeatures(api_name, features_bundle))
226424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if available_channel is not None:
227424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        return available_channel == 'stable'
2285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
2295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # |earliest_version| == _SVN_MIN_VERSION implies we're dealing with an API.
2305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # Fall back to a check for file system existence if the API is not
2315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # stable in any of the _features.json files, or if the _features files
2325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # do not exist (version 19 and earlier).
2335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if earliest_version == _SVN_MIN_VERSION:
2346d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      return self._HasAPISchema(api_name, file_system, version)
2355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # For API nodes, assume it's available if |version| is greater than the
2365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # version the node became available (which it is, because of the first
2375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # check).
2385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return True
239424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
240a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  def _CheckChannelAvailability(self, api_name, file_system, channel_info):
2411e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    '''Searches through the _features files in a given |file_system|, falling
2421e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    back to checking the file system for API schema existence, to determine
243a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    whether or not an API is available on the given channel, |channel_info|.
244eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''
245effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    features_bundle = self._CreateFeaturesBundle(file_system)
246effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    available_channel = (
247116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        _GetChannelFromAPIFeatures(api_name, features_bundle) or
248116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        _GetChannelFromPermissionFeatures(api_name, features_bundle) or
249116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        _GetChannelFromManifestFeatures(api_name, features_bundle))
250a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if (available_channel is None and
2516d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        self._HasAPISchema(api_name, file_system, channel_info.version)):
252eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      # If an API is not represented in any of the _features files, but exists
253eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      # in the filesystem, then assume it is available in this version.
254a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      # The chrome.windows API is an example of this.
255a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      available_channel = channel_info.channel
256424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    # If the channel we're checking is the same as or newer than the
257424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    # |available_channel| then the API is available at this channel.
258a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    newest = BranchUtility.NewestChannel((available_channel,
259a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                                          channel_info.channel))
260a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    return available_channel is not None and newest == channel_info.channel
261424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
2625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def _CheckChannelAvailabilityForNode(self,
2635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                       node_name,
2645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                       file_system,
2655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                       channel_info,
2665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                       earliest_channel_info):
2675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    '''Searches through the _features files in a given |file_system| to
2685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    determine whether or not an API node is available on the given channel,
2695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    |channel_info|. |earliest_channel_info| is the earliest channel the node
2705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    was introduced.
2715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    '''
2725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    features_bundle = self._CreateFeaturesBundle(file_system)
2735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    available_channel = None
2745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # Only API nodes can have their availability overriden on a per-node basis,
2755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    # so we only need to check _api_features.json.
2765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if channel_info.version >= _API_FEATURES_MIN_VERSION:
2775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      available_channel = _GetChannelFromAPIFeatures(node_name, features_bundle)
2785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if (available_channel is None and
2795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        channel_info.version >= earliest_channel_info.version):
2805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # Most API nodes inherit their availabiltity from their parent, so don't
2815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # explicitly appear in _api_features.json. For example, "tabs.create"
2825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # isn't listed; it inherits from "tabs". Assume these are available at
2835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      # |channel_info|.
2845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      available_channel = channel_info.channel
2855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    newest = BranchUtility.NewestChannel((available_channel,
2865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                          channel_info.channel))
2875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return available_channel is not None and newest == channel_info.channel
2885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
289effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  @memoize
290effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def _CreateFeaturesBundle(self, file_system):
291effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return FeaturesBundle(file_system,
292effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                          self._compiled_fs_factory,
293116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                          self._object_store_creator,
294116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                          self._platform)
295effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
2966d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def _CheckAPIAvailability(self, api_name, file_system, channel_info):
297424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Determines the availability for an API at a certain version of Chrome.
298424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    Two branches of logic are used depending on whether or not the API is
299424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    determined to be 'stable' at the given version.
300424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''
301424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if channel_info.channel == 'stable':
302424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return self._CheckStableAvailability(api_name,
303424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                           file_system,
304424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                           channel_info.version)
305424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return self._CheckChannelAvailability(api_name,
306424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                          file_system,
307a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                                          channel_info)
308eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
3095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def _FindScheduled(self, api_name, earliest_version=None):
3105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    '''Determines the earliest version of Chrome where the API is stable.
3116d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    Unlike the code in GetAPIAvailability, this checks if the API is stable
3125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    even when Chrome is in dev or beta, which shows that the API is scheduled
3135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    to be stable in that verison of Chrome. |earliest_version| is the version
3145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    |api_name| became first available. Only use it when finding scheduled
3155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    availability for nodes.
3165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    '''
3175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    def check_scheduled(file_system, channel_info):
3185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return self._CheckStableAvailability(api_name,
3195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                           file_system,
3205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                           channel_info.version,
3215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                           earliest_version=earliest_version)
3225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    stable_channel = self._file_system_iterator.Descending(
3245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        self._branch_utility.GetChannelInfo('dev'), check_scheduled)
3255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return stable_channel.version if stable_channel else None
3275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def _CheckAPINodeAvailability(self, node_name, earliest_channel_info):
3295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    '''Gets availability data for a node by checking _features files.
3305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    '''
3315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def check_node_availability(file_system, channel_info):
3325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return self._CheckChannelAvailabilityForNode(node_name,
3335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                                   file_system,
3345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                                   channel_info,
3355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                                   earliest_channel_info)
3365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    channel_info = (self._file_system_iterator.Descending(
3375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        self._branch_utility.GetChannelInfo('dev'), check_node_availability) or
3385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        earliest_channel_info)
3395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if channel_info.channel == 'stable':
3415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      scheduled = None
3425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    else:
3435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      scheduled = self._FindScheduled(
3445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          node_name,
3455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          earliest_version=earliest_channel_info.version)
3465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return AvailabilityInfo(channel_info, scheduled=scheduled)
3485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
3496d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def GetAPIAvailability(self, api_name):
350424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Performs a search for an API's top-level availability by using a
351424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    HostFileSystemIterator instance to traverse multiple version of the
352424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    SVN filesystem.
353eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''
35468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    availability = self._top_level_object_store.Get(api_name).Get()
355eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if availability is not None:
356eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      return availability
357eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
358f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # Check for predetermined availability and cache this information if found.
359f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    availability = self._GetPredeterminedAvailability(api_name)
360f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if availability is not None:
361f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      self._top_level_object_store.Set(api_name, availability)
362f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return availability
363f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
364424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    def check_api_availability(file_system, channel_info):
3656d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      return self._CheckAPIAvailability(api_name, file_system, channel_info)
366eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
3675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    channel_info = self._file_system_iterator.Descending(
368424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        self._branch_utility.GetChannelInfo('dev'),
369424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        check_api_availability)
3705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if channel_info is None:
3711320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      # The API wasn't available on 'dev', so it must be a 'master'-only API.
3721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      channel_info = self._branch_utility.GetChannelInfo('master')
3735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    # If the API is not stable, check when it will be scheduled to be stable.
3755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if channel_info.channel == 'stable':
3765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      scheduled = None
3775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    else:
3785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      scheduled = self._FindScheduled(api_name)
3795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    availability = AvailabilityInfo(channel_info, scheduled=scheduled)
3815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
38268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    self._top_level_object_store.Set(api_name, availability)
383eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return availability
38468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
3856d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def GetAPINodeAvailability(self, api_name):
38668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    '''Returns an APISchemaGraph annotated with each node's availability (the
38768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    ChannelInfo at the oldest channel it's available in).
38868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    '''
38968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    availability_graph = self._node_level_object_store.Get(api_name).Get()
39068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    if availability_graph is not None:
39168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      return availability_graph
39268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
393f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    def assert_not_none(value):
394f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      assert value is not None
395f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return value
3964e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
39768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    availability_graph = APISchemaGraph()
398f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    host_fs = self._host_file_system
3991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    master_stat = assert_not_none(host_fs.Stat(_GetAPISchemaFilename(
4001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        api_name, host_fs, 'master')))
401f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
402f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # Weird object thing here because nonlocal is Python 3.
403f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    previous = type('previous', (object,), {'stat': None, 'graph': None})
404f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
40568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    def update_availability_graph(file_system, channel_info):
406116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      # If we can't find a filename, skip checking at this branch.
407116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      # For example, something could have a predetermined availability of 23,
408116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      # but it doesn't show up in the file system until 26.
409116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      # We know that the file will become available at some point.
410116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      #
411116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      # The problem with this is that at the first version where the API file
412116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      # exists, we'll get a huge chunk of new objects that don't match
413116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      # the predetermined API availability.
414116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      version_filename = _GetAPISchemaFilename(api_name,
415116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                                               file_system,
416116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                                               channel_info.version)
417116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      if version_filename is None:
418116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        # Continue the loop at the next version.
419116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        return True
420116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
421a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      version_stat = assert_not_none(file_system.Stat(version_filename))
422f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
423f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # Important optimisation: only re-parse the graph if the file changed in
424f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # the last revision. Parsing the same schema and forming a graph on every
425f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # iteration is really expensive.
426f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if version_stat == previous.stat:
427f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        version_graph = previous.graph
428f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      else:
429f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        # Keep track of any new schema elements from this version by adding
430f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        # them to |availability_graph|.
431f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        #
432f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        # Calling |availability_graph|.Lookup() on the nodes being updated
433f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        # will return the |annotation| object -- the current |channel_info|.
4345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        version_graph = APISchemaGraph(
4355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            api_schema=self._GetAPISchema(api_name,
4365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                          file_system,
4375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                          channel_info.version))
4385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        def annotator(node_name):
4395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          return self._CheckAPINodeAvailability('%s.%s' % (api_name, node_name),
4405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                                channel_info)
4415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
442f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        availability_graph.Update(version_graph.Subtract(availability_graph),
4435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                  annotator)
444f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
445f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      previous.stat = version_stat
446f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      previous.graph = version_graph
44768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
44868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      # Continue looping until there are no longer differences between this
4491320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      # version and master.
4501320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return version_stat != master_stat
45168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
4525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self._file_system_iterator.Ascending(
4536d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        self.GetAPIAvailability(api_name).channel_info,
4545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        update_availability_graph)
45568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
45668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    self._node_level_object_store.Set(api_name, availability_graph)
45768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    return availability_graph
458