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
51e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)from collections import Mapping
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import posixpath
7eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)from api_schema_graph import APISchemaGraph
95c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufrom branch_utility import BranchUtility, ChannelInfo
10effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom extensions_paths import API_PATHS, JSON_TEMPLATES
11effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom features_bundle import FeaturesBundle
12effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochimport features_utility
13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from file_system import FileNotFoundError
14effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom third_party.json_schema_compiler.memoize import memoize
15424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from third_party.json_schema_compiler.model import UnixName
16eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
17eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
1868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)_EXTENSION_API = 'extension_api.json'
1968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
20a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)# The version where api_features.json is first available.
21a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)_API_FEATURES_MIN_VERSION = 28
22a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)# The version where permission_ and manifest_features.json are available and
23a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)# presented in the current format.
24a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)_ORIGINAL_FEATURES_MIN_VERSION = 20
25a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)# API schemas are aggregated in extension_api.json up to this version.
26a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)_EXTENSION_API_MAX_VERSION = 17
27a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)# The earliest version for which we have SVN data.
28a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)_SVN_MIN_VERSION = 5
29a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
30a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
31effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochdef _GetChannelFromFeatures(api_name, features):
32effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  '''Finds API channel information for |api_name| from |features|.
33effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  Returns None if channel information for the API cannot be located.
34eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  '''
35effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  feature = features.Get().get(api_name)
36effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  return feature.get('channel') if feature else None
37eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
38eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liuclass AvailabilityInfo(object):
405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  '''Represents availability data for an API. |scheduled| is a version number
415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  specifying when dev and beta APIs will become stable, or None if that data
425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  is unknown.
435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  '''
445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def __init__(self, channel_info, scheduled=None):
455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    assert isinstance(channel_info, ChannelInfo)
465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    assert isinstance(scheduled, int) or scheduled is None
475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self.channel_info = channel_info
485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self.scheduled = scheduled
495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def __eq__(self, other):
515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return self.__dict__ == other.__dict__
525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def __ne__(self, other):
545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return not (self == other)
555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def __repr__(self):
575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return '%s%s' % (type(self).__name__, repr(self.__dict__))
585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def __str__(self):
605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return repr(self)
615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
63eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochclass AvailabilityFinder(object):
64424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  '''Generates availability information for APIs by looking at API schemas and
65424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  _features files over multiple release versions of Chrome.
66eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  '''
67eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
68eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  def __init__(self,
6968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)               branch_utility,
701e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)               compiled_fs_factory,
711e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)               file_system_iterator,
721e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)               host_file_system,
731e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)               object_store_creator):
741e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    self._branch_utility = branch_utility
751e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    self._compiled_fs_factory = compiled_fs_factory
76424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    self._file_system_iterator = file_system_iterator
771e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    self._host_file_system = host_file_system
78eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    self._object_store_creator = object_store_creator
7968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    def create_object_store(category):
8068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      return object_store_creator.Create(AvailabilityFinder, category=category)
8168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    self._top_level_object_store = create_object_store('top_level')
8268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    self._node_level_object_store = create_object_store('node_level')
83f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    self._json_fs = compiled_fs_factory.ForJson(self._host_file_system)
84f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
85f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def _GetPredeterminedAvailability(self, api_name):
86f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    '''Checks a configuration file for hardcoded (i.e. predetermined)
87f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    availability information for an API.
88f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    '''
89f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    api_info = self._json_fs.GetFromFile(
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        JSON_TEMPLATES + 'api_availabilities.json').Get().get(api_name)
91f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if api_info is None:
92f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return None
93f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if api_info['channel'] == 'stable':
945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return AvailabilityInfo(
955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          self._branch_utility.GetStableChannelInfo(api_info['version']))
965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return AvailabilityInfo(
975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        self._branch_utility.GetChannelInfo(api_info['channel']))
98a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
996d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def _GetAPISchemaFilename(self, api_name, file_system, version):
100a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    '''Gets the name of the file which may contain the schema for |api_name| in
101a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    |file_system|, or None if the API is not found. Note that this may be the
102a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    single _EXTENSION_API file which all APIs share in older versions of Chrome,
103a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    in which case it is unknown whether the API actually exists there.
104a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    '''
105a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if version == 'trunk' or version > _ORIGINAL_FEATURES_MIN_VERSION:
106a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      # API schema filenames switch format to unix_hacker_style.
107a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      api_name = UnixName(api_name)
108a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
109e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    found_files = file_system.Read(API_PATHS, skip_not_found=True)
110e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch    for path, filenames in found_files.Get().iteritems():
111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      try:
112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        for ext in ('json', 'idl'):
113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          filename = '%s.%s' % (api_name, ext)
114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          if filename in filenames:
115a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            return path + filename
116a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          if _EXTENSION_API in filenames:
117a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            return path + _EXTENSION_API
118a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      except FileNotFoundError:
119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        pass
120a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    return None
121a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
1226d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def _GetAPISchema(self, api_name, file_system, version):
123a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    '''Searches |file_system| for |api_name|'s API schema data, and processes
124a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    and returns it if found.
125a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    '''
1266d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    api_filename = self._GetAPISchemaFilename(api_name, file_system, version)
127a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if api_filename is None:
128a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      # No file for the API could be found in the given |file_system|.
129a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      return None
130a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
1316d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    schema_fs = self._compiled_fs_factory.ForAPISchema(file_system)
132a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    api_schemas = schema_fs.GetFromFile(api_filename).Get()
133a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    matching_schemas = [api for api in api_schemas
134a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                        if api['namespace'] == api_name]
135a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    # There should only be a single matching schema per file, or zero in the
136a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    # case of no API data being found in _EXTENSION_API.
137a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    assert len(matching_schemas) <= 1
138a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    return matching_schemas or None
139a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)
1406d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def _HasAPISchema(self, api_name, file_system, version):
141a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    '''Whether or not an API schema for |api_name|exists in the given
142a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    |file_system|.
143a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    '''
1446d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    filename = self._GetAPISchemaFilename(api_name, file_system, version)
145a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if filename is None:
146a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      return False
147a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if filename.endswith(_EXTENSION_API):
1486d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      return self._GetAPISchema(api_name, file_system, version) is not None
149a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    return True
150424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
151424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def _CheckStableAvailability(self, api_name, file_system, version):
152424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Checks for availability of an API, |api_name|, on the stable channel.
153424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    Considers several _features.json files, file system existence, and
154424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    extension_api.json depending on the given |version|.
155eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''
156a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if version < _SVN_MIN_VERSION:
157a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      # SVN data isn't available below this version.
158424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return False
159effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    features_bundle = self._CreateFeaturesBundle(file_system)
160424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    available_channel = None
161a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if version >= _API_FEATURES_MIN_VERSION:
162424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # The _api_features.json file first appears in version 28 and should be
163424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # the most reliable for finding API availability.
1646d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      available_channel = self._GetChannelFromAPIFeatures(api_name,
165effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                                          features_bundle)
166a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if version >= _ORIGINAL_FEATURES_MIN_VERSION:
167424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # The _permission_features.json and _manifest_features.json files are
168424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # present in Chrome 20 and onwards. Use these if no information could be
169424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # found using _api_features.json.
170effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      available_channel = (
171effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          available_channel or
172effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          self._GetChannelFromPermissionFeatures(api_name, features_bundle) or
173effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          self._GetChannelFromManifestFeatures(api_name, features_bundle))
174424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if available_channel is not None:
175424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        return available_channel == 'stable'
176a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if version >= _SVN_MIN_VERSION:
17768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      # Fall back to a check for file system existence if the API is not
17868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      # stable in any of the _features.json files, or if the _features files
17968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      # do not exist (version 19 and earlier).
1806d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      return self._HasAPISchema(api_name, file_system, version)
181424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
182a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  def _CheckChannelAvailability(self, api_name, file_system, channel_info):
1831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    '''Searches through the _features files in a given |file_system|, falling
1841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    back to checking the file system for API schema existence, to determine
185a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    whether or not an API is available on the given channel, |channel_info|.
186eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''
187effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    features_bundle = self._CreateFeaturesBundle(file_system)
188effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    available_channel = (
1896d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        self._GetChannelFromAPIFeatures(api_name, features_bundle) or
190effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        self._GetChannelFromPermissionFeatures(api_name, features_bundle) or
191effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        self._GetChannelFromManifestFeatures(api_name, features_bundle))
192a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    if (available_channel is None and
1936d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        self._HasAPISchema(api_name, file_system, channel_info.version)):
194eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      # If an API is not represented in any of the _features files, but exists
195eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      # in the filesystem, then assume it is available in this version.
196a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      # The chrome.windows API is an example of this.
197a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      available_channel = channel_info.channel
198424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    # If the channel we're checking is the same as or newer than the
199424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    # |available_channel| then the API is available at this channel.
200a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    newest = BranchUtility.NewestChannel((available_channel,
201a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                                          channel_info.channel))
202a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    return available_channel is not None and newest == channel_info.channel
203424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
204effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  @memoize
205effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def _CreateFeaturesBundle(self, file_system):
206effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return FeaturesBundle(file_system,
207effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                          self._compiled_fs_factory,
208effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                          self._object_store_creator)
209effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
2106d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def _GetChannelFromAPIFeatures(self, api_name, features_bundle):
211effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return _GetChannelFromFeatures(api_name, features_bundle.GetAPIFeatures())
212effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
213effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def _GetChannelFromManifestFeatures(self, api_name, features_bundle):
214effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    # _manifest_features.json uses unix_style API names.
215effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    api_name = UnixName(api_name)
216effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return _GetChannelFromFeatures(api_name,
217effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                   features_bundle.GetManifestFeatures())
218effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
219effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  def _GetChannelFromPermissionFeatures(self, api_name, features_bundle):
220effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return _GetChannelFromFeatures(api_name,
221effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                   features_bundle.GetPermissionFeatures())
222effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
2236d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def _CheckAPIAvailability(self, api_name, file_system, channel_info):
224424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Determines the availability for an API at a certain version of Chrome.
225424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    Two branches of logic are used depending on whether or not the API is
226424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    determined to be 'stable' at the given version.
227424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''
228424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if channel_info.channel == 'stable':
229424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return self._CheckStableAvailability(api_name,
230424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                           file_system,
231424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                           channel_info.version)
232424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return self._CheckChannelAvailability(api_name,
233424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                          file_system,
234a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                                          channel_info)
235eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
2365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  def _FindScheduled(self, api_name):
2375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    '''Determines the earliest version of Chrome where the API is stable.
2386d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    Unlike the code in GetAPIAvailability, this checks if the API is stable
2395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    even when Chrome is in dev or beta, which shows that the API is scheduled
2405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    to be stable in that verison of Chrome.
2415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    '''
2425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    def check_scheduled(file_system, channel_info):
2435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return self._CheckStableAvailability(
2445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          api_name, file_system, channel_info.version)
2455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    stable_channel = self._file_system_iterator.Descending(
2475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        self._branch_utility.GetChannelInfo('dev'), check_scheduled)
2485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return stable_channel.version if stable_channel else None
2505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2516d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def GetAPIAvailability(self, api_name):
252424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Performs a search for an API's top-level availability by using a
253424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    HostFileSystemIterator instance to traverse multiple version of the
254424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    SVN filesystem.
255eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''
25668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    availability = self._top_level_object_store.Get(api_name).Get()
257eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if availability is not None:
258eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      return availability
259eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
260f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # Check for predetermined availability and cache this information if found.
261f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    availability = self._GetPredeterminedAvailability(api_name)
262f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if availability is not None:
263f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      self._top_level_object_store.Set(api_name, availability)
264f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return availability
265f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
266424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    def check_api_availability(file_system, channel_info):
2676d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      return self._CheckAPIAvailability(api_name, file_system, channel_info)
268eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
2695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    channel_info = self._file_system_iterator.Descending(
270424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        self._branch_utility.GetChannelInfo('dev'),
271424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        check_api_availability)
2725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if channel_info is None:
273424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # The API wasn't available on 'dev', so it must be a 'trunk'-only API.
2745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      channel_info = self._branch_utility.GetChannelInfo('trunk')
2755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    # If the API is not stable, check when it will be scheduled to be stable.
2775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if channel_info.channel == 'stable':
2785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      scheduled = None
2795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    else:
2805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      scheduled = self._FindScheduled(api_name)
2815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    availability = AvailabilityInfo(channel_info, scheduled=scheduled)
2835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
28468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    self._top_level_object_store.Set(api_name, availability)
285eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return availability
28668043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
2876d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  def GetAPINodeAvailability(self, api_name):
28868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    '''Returns an APISchemaGraph annotated with each node's availability (the
28968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    ChannelInfo at the oldest channel it's available in).
29068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    '''
29168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    availability_graph = self._node_level_object_store.Get(api_name).Get()
29268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    if availability_graph is not None:
29368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      return availability_graph
29468043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
295f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    def assert_not_none(value):
296f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      assert value is not None
297f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return value
2984e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
29968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    availability_graph = APISchemaGraph()
300f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
301f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    host_fs = self._host_file_system
3026d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    trunk_stat = assert_not_none(host_fs.Stat(self._GetAPISchemaFilename(
303a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        api_name, host_fs, 'trunk')))
304f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
305f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # Weird object thing here because nonlocal is Python 3.
306f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    previous = type('previous', (object,), {'stat': None, 'graph': None})
307f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
30868043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    def update_availability_graph(file_system, channel_info):
3096d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)      version_filename = assert_not_none(self._GetAPISchemaFilename(
310a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)          api_name, file_system, channel_info.version))
311a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      version_stat = assert_not_none(file_system.Stat(version_filename))
312f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
313f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # Important optimisation: only re-parse the graph if the file changed in
314f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # the last revision. Parsing the same schema and forming a graph on every
315f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      # iteration is really expensive.
316f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if version_stat == previous.stat:
317f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        version_graph = previous.graph
318f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      else:
319f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        # Keep track of any new schema elements from this version by adding
320f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        # them to |availability_graph|.
321f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        #
322f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        # Calling |availability_graph|.Lookup() on the nodes being updated
323f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        # will return the |annotation| object -- the current |channel_info|.
3246d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        version_graph = APISchemaGraph(self._GetAPISchema(
325a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)            api_name, file_system, channel_info.version))
326f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        availability_graph.Update(version_graph.Subtract(availability_graph),
327f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                  annotation=channel_info)
328f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
329f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      previous.stat = version_stat
330f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      previous.graph = version_graph
33168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
33268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      # Continue looping until there are no longer differences between this
33368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)      # version and trunk.
334f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return version_stat != trunk_stat
33568043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
3365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self._file_system_iterator.Ascending(
3376d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)        self.GetAPIAvailability(api_name).channel_info,
3385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        update_availability_graph)
33968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)
34068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    self._node_level_object_store.Set(api_name, availability_graph)
34168043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    return availability_graph
342