Index: chrome/common/extensions/docs/server2/availability_finder.py |
diff --git a/chrome/common/extensions/docs/server2/availability_finder.py b/chrome/common/extensions/docs/server2/availability_finder.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..db9ce91814e4f477f50dd8ae6781cff551c5f768 |
--- /dev/null |
+++ b/chrome/common/extensions/docs/server2/availability_finder.py |
@@ -0,0 +1,255 @@ |
+# Copyright (c) 2013 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+import collections |
+import logging |
+import os |
+ |
+from branch_utility import BranchUtility |
+from compiled_file_system import CompiledFileSystem |
+from file_system import FileNotFoundError |
+import svn_constants |
+from third_party.json_schema_compiler import json_parse, model |
+from third_party.json_schema_compiler.memoize import memoize |
+ |
+_API_AVAILABILITIES = svn_constants.JSON_PATH + '/api_availabilities.json' |
+_API_FEATURES = svn_constants.API_PATH + '/_api_features.json' |
+_EXTENSION_API = svn_constants.API_PATH + '/extension_api.json' |
+_MANIFEST_FEATURES = svn_constants.API_PATH + '/_manifest_features.json' |
+_PERMISSION_FEATURES = svn_constants.API_PATH + '/_permission_features.json' |
+_STABLE = 'stable' |
+ |
+class AvailabilityInfo(object): |
+ def __init__(self, channel, version): |
+ self.channel = channel |
+ self.version = version |
+ |
+def _GetChannelFromFeatures(api_name, file_system, path, unix_name=False): |
+ '''Finds API channel information within _features.json files at the given |
+ |path| for the given |file_system|. Returns None if channel information for |
+ the API cannot be located. |
+ ''' |
+ if unix_name: |
+ api_name = model.UnixName(api_name) |
+ feature = file_system.GetFromFile(path).get(api_name) |
+ |
+ if feature is None: |
+ return None |
+ if isinstance(feature, collections.Mapping): |
+ # The channel information dict is nested within a list for whitelisting |
+ # purposes. |
+ return feature.get('channel') |
+ # Features can contain a list of entries. Take the newest branch. |
+ return BranchUtility.NewestChannel(entry.get('channel') |
+ for entry in feature) |
+ |
+def _GetChannelFromApiFeatures(api_name, file_system): |
+ try: |
+ return _GetChannelFromFeatures(api_name, file_system, _API_FEATURES) |
+ except FileNotFoundError as e: |
+ # TODO(epeterson) Remove except block once _api_features is in all channels. |
+ return None |
+ |
+def _GetChannelFromPermissionFeatures(api_name, file_system): |
+ return _GetChannelFromFeatures(api_name, file_system, _PERMISSION_FEATURES) |
+ |
+def _GetChannelFromManifestFeatures(api_name, file_system): |
+ return _GetChannelFromFeatures(api_name, |
+ file_system, |
+ _MANIFEST_FEATURES, |
+ #_manifest_features uses unix_style API names |
+ unix_name=True) |
+ |
+def _ExistsInFileSystem(api_name, file_system): |
+ '''Checks for existence of |api_name| within the list of files in the api/ |
+ directory found using the given file system. |
+ ''' |
+ api_name = model.UnixName(api_name) |
+ return api_name in file_system.GetFromFileListing(svn_constants.API_PATH) |
+ |
+def _ExistsInExtensionApi(api_name, file_system): |
+ '''Parses the api/extension_api.json file (available in Chrome versions |
+ before 18) for an API namespace. If this is successfully found, then the API |
+ is considered to have been 'stable' for the given version. |
+ ''' |
+ try: |
+ extension_api_json = file_system.GetFromFile(_EXTENSION_API) |
+ api_rows = [row.get('namespace') for row in extension_api_json |
+ if 'namespace' in row] |
+ return True if api_name in api_rows else False |
+ except FileNotFoundError as e: |
+ # This should only happen on preview.py since extension_api.json is no |
+ # longer present in trunk. |
+ return False |
+ |
+class AvailabilityFinder(object): |
+ '''Uses API data sources generated by a ChromeVersionDataSource in order to |
+ search the filesystem for the earliest existence of a specified API throughout |
+ the different versions of Chrome; this constitutes an API's availability. |
+ ''' |
+ class Factory(object): |
+ def __init__(self, |
+ object_store_creator, |
+ compiled_host_fs_factory, |
+ branch_utility, |
+ # Takes a |version|, and returns a caching offline or online |
+ # subversion file system for that version. |
+ create_file_system_at_version): |
+ self._object_store_creator = object_store_creator |
+ self._compiled_host_fs_factory = compiled_host_fs_factory |
+ self._branch_utility = branch_utility |
+ self._create_file_system_at_version = create_file_system_at_version |
+ |
+ def Create(self): |
+ return AvailabilityFinder(self._object_store_creator, |
+ self._compiled_host_fs_factory, |
+ self._branch_utility, |
+ self._create_file_system_at_version) |
+ |
+ def __init__(self, |
+ object_store_creator, |
+ compiled_host_fs_factory, |
+ branch_utility, |
+ create_file_system_at_version): |
+ self._object_store_creator = object_store_creator |
+ self._json_cache = compiled_host_fs_factory.Create( |
+ lambda _, json: json_parse.Parse(json), |
+ AvailabilityFinder, |
+ 'json-cache') |
+ self._branch_utility = branch_utility |
+ self._create_file_system_at_version = create_file_system_at_version |
+ self._object_store = object_store_creator.Create(AvailabilityFinder) |
+ |
+ @memoize |
+ def _CreateFeaturesAndNamesFileSystems(self, version): |
+ '''The 'features' compiled file system's populate function parses and |
+ returns the contents of a _features.json file. The 'names' compiled file |
+ system's populate function creates a list of file names with .json or .idl |
+ extensions. |
+ ''' |
+ fs_factory = CompiledFileSystem.Factory( |
+ self._create_file_system_at_version(version), |
+ self._object_store_creator) |
+ features_fs = fs_factory.Create(lambda _, json: json_parse.Parse(json), |
+ AvailabilityFinder, |
+ category='features') |
+ names_fs = fs_factory.Create(self._GetExtNames, |
+ AvailabilityFinder, |
+ category='names') |
+ return (features_fs, names_fs) |
+ |
+ def _GetExtNames(self, base_path, apis): |
+ return [os.path.splitext(api)[0] for api in apis |
+ if os.path.splitext(api)[1][1:] in ['json', 'idl']] |
+ |
+ def _FindEarliestAvailability(self, api_name, version): |
+ '''Searches in descending order through filesystem caches tied to specific |
+ chrome version numbers and looks for the availability of an API, |api_name|, |
+ on the stable channel. When a version is found where the API is no longer |
+ available on stable, returns the previous version number (the last known |
+ version where the API was stable). |
+ ''' |
+ # TestBranchUtility is going to send funky fake data to this method. |
+ if isinstance(version, str): |
+ return version |
+ available = True |
+ |
+ while available: |
+ if version < 5: |
+ return version + 1 |
+ available = False |
+ features_fs, names_fs = self._CreateFeaturesAndNamesFileSystems(version) |
+ if version >= 28: |
+ # The _api_features.json file first appears in version 28 and should be |
+ # the most reliable for finding API availabilities, so it gets checked |
+ # first. The _permission_features.json and _manifest_features.json files |
+ # are present in Chrome 20 and onwards. Fall back to a check for file |
+ # system existence if the API is not stable in any of the _features.json |
+ # files. |
+ available = _GetChannelFromApiFeatures(api_name, features_fs) == _STABLE |
+ if version >= 20: |
+ # Check other _features.json files/file existence if the API wasn't |
+ # found in _api_features.json, or if _api_features.json wasn't present. |
+ available = available or ( |
+ _GetChannelFromPermissionFeatures(api_name, features_fs) == _STABLE |
+ or _GetChannelFromManifestFeatures(api_name, features_fs) == _STABLE |
+ or _ExistsInFileSystem(api_name, names_fs)) |
+ elif version >= 18: |
+ # These versions are a little troublesome. Version 19 has |
+ # _permission_features.json, but it lacks 'channel' information. |
+ # Version 18 lacks all of the _features.json files. For now, we're using |
+ # a simple check for filesystem existence here. |
+ available = _ExistsInFileSystem(api_name, names_fs) |
+ elif version >= 5: |
+ # Versions 17 and down to 5 have an extension_api.json file which |
+ # contains namespaces for each API that was available at the time. We |
+ # can use this file to check for API existence. |
+ available = _ExistsInExtensionApi(api_name, features_fs) |
+ |
+ if not available: |
+ return version + 1 |
+ version -= 1 |
+ |
+ def _GetAvailableChannelForVersion(self, api_name, version): |
+ '''Searches through the _features files for a given |version| and returns |
+ the channel that the given API is determined to be available on. |
+ ''' |
+ features_fs, names_fs = self._CreateFeaturesAndNamesFileSystems(version) |
+ channel = (_GetChannelFromApiFeatures(api_name, features_fs) |
+ or _GetChannelFromPermissionFeatures(api_name, features_fs) |
+ or _GetChannelFromManifestFeatures(api_name, features_fs)) |
+ if channel is None and _ExistsInFileSystem(api_name, names_fs): |
+ # If an API is not represented in any of the _features files, but exists |
+ # in the filesystem, then assume it is available in this version. |
+ # The windows API is an example of this. |
+ for channel_info in self._branch_utility.GetAllChannelInfo(): |
+ if version == channel_info.version: |
+ channel = channel_info.channel |
+ break |
+ return channel |
+ |
+ def GetApiAvailability(self, api_name): |
+ '''Determines the availability for an API by testing several scenarios. |
+ (i.e. Is the API experimental? Only available on certain development |
+ channels? If it's stable, when did it first become stable? etc.) |
+ ''' |
+ availability = self._object_store.Get(api_name).Get() |
+ if availability is not None: |
+ return availability |
+ |
+ # Check for a predetermined availability for this API. |
+ api_info = self._json_cache.GetFromFile(_API_AVAILABILITIES).get(api_name) |
+ if api_info is not None: |
+ return AvailabilityInfo( |
+ api_info.get('channel'), |
+ api_info.get('version') |
+ if api_info.get('channel') == _STABLE else None) |
+ |
+ # Check for the API in the development channels. |
+ availability = None |
+ for channel_info in self._branch_utility.GetAllChannelInfo(): |
+ available_channel = self._GetAvailableChannelForVersion( |
+ api_name, |
+ channel_info.version) |
+ # If the |available_channel| for the API is the same as, or older than, |
+ # the channel we're checking, then the API is available on this channel. |
+ if (available_channel is not None and BranchUtility.NewestChannel( |
+ (available_channel, |
+ channel_info.channel)) == channel_info.channel): |
+ availability = AvailabilityInfo(channel_info.channel, |
+ channel_info.version) |
+ break |
+ |
+ # The API should at least be available on trunk. It's a bug otherwise. |
+ assert availability, 'No availability found for %s' % api_name |
+ |
+ # If the API is in stable, find the chrome version in which it became |
+ # stable. |
+ if availability.channel == _STABLE: |
+ availability.version = self._FindEarliestAvailability( |
+ api_name, |
+ availability.version) |
+ |
+ self._object_store.Set(api_name, availability) |
+ return availability |