Index: chrome/common/extensions/docs/server2/availability_data_source.py |
diff --git a/chrome/common/extensions/docs/server2/availability_data_source.py b/chrome/common/extensions/docs/server2/availability_data_source.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b7e4687a14c4ab6e3ed2d72172d750250a847303 |
--- /dev/null |
+++ b/chrome/common/extensions/docs/server2/availability_data_source.py |
@@ -0,0 +1,261 @@ |
+# 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 os |
+ |
+from branch_utility import BranchUtility |
+from chrome_version_utility import ChromeVersionUtility |
+from file_system import FileNotFoundError |
+import svn_constants |
+from third_party.json_schema_compiler import json_parse, model |
+ |
+EXTENSION_API = 'extension_api.json' |
+MANIFEST_FEATURES = '_manifest_features.json' |
+PERMISSION_FEATURES = '_permission_features.json' |
+ |
+class AvailabilityDataSource(object): |
not at google - send to devlin
2013/05/13 21:26:41
Late stage for this, but this isn't actually a *Da
epeterson
2013/05/15 07:38:34
Done.
|
+ '''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, |
+ chrome_version_utility, |
+ object_store_creator, |
+ create_file_system): |
not at google - send to devlin
2013/05/13 21:26:41
Can we try making this a method of ChromeVersionUt
|
+ self._chrome_version_utility = chrome_version_utility |
+ self._object_store_creator = object_store_creator |
+ self._create_file_system = create_file_system |
+ |
+ def Create(self): |
+ return AvailabilityDataSource(self._chrome_version_utility, |
+ self._object_store_creator, |
+ self._create_file_system) |
+ |
+ def __init__(self, |
+ chrome_version_utility, |
+ object_store_creator, |
+ create_file_system): |
+ self._chrome_version_utility = chrome_version_utility |
+ self._object_store_creator = object_store_creator |
+ self._create_file_system = create_file_system |
+ |
+ svn_file_system = create_file_system('trunk') |
+ def read_json(path): |
+ return json_parse.Parse(svn_file_system.ReadSingle(path)) |
+ self._existing_availabilities = read_json( |
+ '%s/api_availabilities.json' % svn_constants.JSON_PATH) |
+ self._permission_features = read_json( |
+ '%s/%s' % (svn_constants.API_PATH, PERMISSION_FEATURES)) |
+ self._manifest_features = read_json( |
+ '%s/%s' % (svn_constants.API_PATH, MANIFEST_FEATURES)) |
not at google - send to devlin
2013/05/13 15:43:32
here is one problem. The file systems are being re
not at google - send to devlin
2013/05/13 21:26:41
Continuing from this comment, and reading below: I
|
+ |
+ def create_object_store(category): |
+ return (object_store_creator.Create(AvailabilityDataSource, |
+ category=category)) |
+ self._permission_object_store = create_object_store('permission') |
+ self._manifest_object_store = create_object_store('manifest') |
+ self._names_object_store = create_object_store('names') |
+ self._object_store = create_object_store(None) |
+ |
+ self._permission_apis = [] |
+ self._manifest_apis = [] |
+ self._orphan_apis = [] |
not at google - send to devlin
2013/05/13 21:26:41
Looking at the way this is used, it's a bit strang
|
+ |
+ def _ReadJson(self, svn_file_system, path): |
+ return json_parse.Parse(svn_file_system.ReadSingle(path)) |
+ |
+ def _GetChannelFromPermissionFeatures(self, api_name, permission_features): |
not at google - send to devlin
2013/05/13 21:26:41
this function should be able to use self._permissi
epeterson
2013/05/15 07:38:34
Done.
|
+ '''Handles finding API channel information from _permission_features.json. |
+ Sometimes this info will be in a dict, but other times it will be |
+ in a dict contained in a list. |
+ ''' |
+ channel = None |
+ api_info = permission_features.get(api_name) |
+ if api_info is not None: |
+ if isinstance(api_info, collections.Mapping): |
+ channel = api_info.get('channel') |
+ else: |
+ channel = BranchUtility.NewestChannel([entry.get('channel') |
+ for entry in api_info]) |
not at google - send to devlin
2013/05/13 21:26:41
I don't think that the list comprehension is neces
epeterson
2013/05/15 07:38:34
Done.
|
+ return channel |
+ |
+ def _CheckStablePermissionExistence(self, api_name, version): |
+ '''Checks _permission_features.json for the given |version| of Chrome, |
+ and returns a boolean representing whether or not the API was available on |
+ the stable channel in |version|. |
+ ''' |
+ existence = self._permission_object_store.Get( |
+ '%s%s' % (api_name, version)).Get() |
+ if existence is not None: |
+ return existence |
+ svn_file_system = self._create_file_system( |
+ self._chrome_version_utility.GetBranchNumberForVersion(version)) |
+ permission_features = self._ReadJson( |
+ svn_file_system, |
+ '%s/%s' % (svn_constants.API_PATH, PERMISSION_FEATURES)) |
+ existence = 'stable' == self._GetChannelFromPermissionFeatures( |
+ api_name, |
+ permission_features) |
+ self._object_store.Set('%s%s' % (api_name, version), existence) |
+ return existence |
+ |
+ def _CheckManifestExistence(self, api_name, version): |
+ '''As a fallback measure for an API not being found in _permission_features, |
+ check _manifest_features.json for the given |version| of Chrome, |
+ and return a boolean representing whether or not the API was represented |
+ within the file. |
+ ''' |
+ #_manifest_features.json uses unix_hacker_style API names. |
+ api_name = model.UnixName(api_name) |
+ existence = self._manifest_object_store.Get( |
+ '%s%s' % (api_name, version)).Get() |
+ if existence is not None: |
+ return existence |
+ svn_file_system = self._create_file_system( |
+ self._chrome_version_utility.GetBranchNumberForVersion(version)) |
+ manifest_features = self._ReadJson( |
+ svn_file_system, |
+ '%s/%s' % (svn_constants.API_PATH, MANIFEST_FEATURES)) |
+ if manifest_features.get(api_name) is not None: |
+ existence = True |
+ else: |
+ existence = False |
not at google - send to devlin
2013/05/13 21:26:41
existence = api_name in manifest_features
epeterson
2013/05/15 07:38:34
Done.
|
+ self._object_store.Set('%s%s' % (api_name, version), existence) |
+ return existence |
+ |
+ def _GetAllNames(self, files_dict): |
+ '''Returns extension-less names for a list of files. |
+ ''' |
+ files = files_dict[svn_constants.API_PATH + '/'] |
+ return [os.path.splitext(f)[0] for f in files] |
+ |
+ def _CheckFileSystemExistence(self, api_name, version): |
+ '''Uses a list of API names for a given branch to check simply for the |
+ existence of a given API in that branch. |
+ ''' |
+ names = self._names_object_store.Get(version).Get() |
+ if names is not None: |
+ return api_name in names |
+ svn_file_system = self._create_file_system( |
+ self._chrome_version_utility.GetBranchNumberForVersion(version)) |
+ names = svn_file_system.Read([svn_constants.API_PATH + '/']).Get() |
not at google - send to devlin
2013/05/13 21:26:41
ReadSingle
search in this file for ".Read(" and s
epeterson
2013/05/15 07:38:34
Done.
|
+ names = self._GetAllNames(names) |
+ self._object_store.Set(version, names) |
+ return api_name in names |
+ |
+ def _CheckExtensionAPIExistence(self, api_name, version): |
+ '''Parses the extension_api.json file (available in earlier versions of |
+ chrome) for an API namespace. If this is successfully found, then the API |
+ is considered to have been 'stable' for the given version. |
+ ''' |
+ # The extension_api.json files only appear from version 5 to version 17. |
+ if int(version) > 17 or int(version) < 5: |
not at google - send to devlin
2013/05/13 21:26:41
make these assert, this method should only be call
epeterson
2013/05/15 07:38:34
Done.
|
+ return False |
+ |
+ # Re-use this object-store - the versions won't conflict with each other. |
+ existence = self._names_object_store.Get('%s%s' % (api_name, version)).Get() |
not at google - send to devlin
2013/05/13 21:26:41
I don't think caching needs to be done here, you'r
epeterson
2013/05/15 07:38:34
Done.
|
+ if existence is not None: |
+ return existence |
+ svn_file_system = self._create_file_system( |
+ self._chrome_version_utility.GetBranchNumberForVersion(version)) |
+ try: |
+ extension_api = self._ReadJson( |
+ svn_file_system, |
+ '%s/%s' % (svn_constants.API_PATH, EXTENSION_API)) |
+ api_rows = [row.get('namespace') for row in extension_api |
+ 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 current versions. |
+ return False |
+ |
+ def _FindEarliestStableAvailability(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). |
+ ''' |
+ # The _permission_features.json file (with 'channel' keys) is present only |
+ # in Chrome 20 and onwards. We'll also check the _manifest_features.json if |
+ # an API isn't found in _permission_features, and, failing that, we'll check |
+ # for the API's existence in the filesystem. |
+ while version >= 20: |
+ if ((api_name in self._permission_apis |
+ and self._CheckStablePermissionExistence(api_name, str(version))) |
+ or (api_name in self._manifest_apis |
+ and self._CheckManifestExistence(api_name, str(version))) |
+ or (api_name in self._orphan_apis |
+ and self._CheckFileSystemExistence(api_name, str(version)))): |
+ version -= 1 |
+ else: |
+ break |
not at google - send to devlin
2013/05/13 21:26:41
THis method is super cool, but a bit odd to read a
epeterson
2013/05/15 07:38:34
Done.
|
+ # These versions are a little troublesome. Version 19 has |
+ # _permission_features.json, but it lacks 'channel' information. Version 18 |
+ # doesn't have either of the json files. For now, we're using a simple check |
+ # for filesystem existence here. |
+ while version < 20 and version >= 18: |
+ if self._CheckFileSystemExistence(api_name, str(version)): |
+ version -= 1 |
+ else: |
+ break |
+ # Versions 17 and earlier 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 existence, however, we no longer have |
+ # file system access after version 5, so stop there. |
+ while version < 18 and version >= 5: |
+ if self._CheckExtensionAPIExistence(api_name, str(version)): |
not at google - send to devlin
2013/05/13 21:26:41
why str(version)? Can we use ints everywhere?
epeterson
2013/05/15 07:38:34
Done.
|
+ version -= 1 |
+ else: |
+ break |
+ return str(version + 1) |
+ |
+ def GetAvailability(self, api_name): |
not at google - send to devlin
2013/05/13 21:26:41
I will want to add event/function/property availab
epeterson
2013/05/15 07:38:34
Done.
|
+ '''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. |
+ availability = self._existing_availabilities.get(api_name) |
not at google - send to devlin
2013/05/13 21:26:41
e.g. in lazy creation world this would be self._Ge
epeterson
2013/05/15 07:38:34
Done.
|
+ if availability is not None: |
+ self._object_store.Set(api_name, availability) |
+ return availability |
+ |
+ latest_version = str(self._chrome_version_utility.GetLatestVersionNumber()) |
+ channel = self._GetChannelFromPermissionFeatures(api_name, |
+ self._permission_features) |
not at google - send to devlin
2013/05/13 21:26:41
This won't actually work properly, though: say if
epeterson
2013/05/15 07:38:34
So this is a bit of a mess now. Couldn't quite get
|
+ |
+ # There are three different scenarios for APIs in Chrome 20 and up. They |
+ # can be in _permission_features, _manifest_features, or neither of the two. |
+ # APIs are added to a respective list depending on which of these scenarios |
+ # they fall into within the trunk filesystem. |
+ if channel is not None: |
+ # This is a special case: availability can be any of the dev channels. |
+ # First, check for this case before checking for stable existence. |
+ availability = channel |
+ self._permission_apis.append(api_name) |
+ elif self._CheckManifestExistence(api_name, latest_version): |
+ availability = 'stable' |
+ self._manifest_apis.append(api_name) |
+ elif self._CheckFileSystemExistence(api_name, latest_version): |
+ availability = 'stable' |
+ self._orphan_apis.append(api_name) |
+ else: |
+ raise FileNotFoundError( |
+ 'The API schema for %s could not be located.' % api_name) |
+ availability = None |
+ |
+ if availability == 'stable': |
+ availability = self._FindEarliestStableAvailability( |
+ api_name, |
+ int(latest_version) - 1) |
+ self._object_store.Set(api_name, availability) |
+ return availability |