availability_finder.py revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
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 5eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochimport collections 6eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochimport os 7eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 8424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)import svn_constants 9eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochfrom branch_utility import BranchUtility 10eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochfrom compiled_file_system import CompiledFileSystem 11eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochfrom file_system import FileNotFoundError 12424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from third_party.json_schema_compiler import json_parse 13eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochfrom third_party.json_schema_compiler.memoize import memoize 14424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from third_party.json_schema_compiler.model import UnixName 15eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 16eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 17eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochdef _GetChannelFromFeatures(api_name, file_system, path): 18eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch '''Finds API channel information within _features.json files at the given 19eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch |path| for the given |file_system|. Returns None if channel information for 20eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch the API cannot be located. 21eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch ''' 22eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch feature = file_system.GetFromFile(path).get(api_name) 23eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 24eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if feature is None: 25eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return None 26eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if isinstance(feature, collections.Mapping): 27ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch # The channel information exists as a solitary dict. 28eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return feature.get('channel') 29ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch # The channel information dict is nested within a list for whitelisting 30ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch # purposes. Take the newest channel out of all of the entries. 31ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch return BranchUtility.NewestChannel(entry.get('channel') for entry in feature) 32eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 33424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) 34eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochdef _GetChannelFromApiFeatures(api_name, file_system): 35424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return _GetChannelFromFeatures( 36424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) api_name, 37424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) file_system, 38424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) '%s/_api_features.json' % svn_constants.API_PATH) 39eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 40eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 41eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochdef _GetChannelFromManifestFeatures(api_name, file_system): 42424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return _GetChannelFromFeatures( 43424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) UnixName(api_name), #_manifest_features uses unix_style API names 44424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) file_system, 45424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) '%s/_manifest_features.json' % svn_constants.API_PATH) 46424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) 47424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) 48424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)def _GetChannelFromPermissionFeatures(api_name, file_system): 49424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return _GetChannelFromFeatures( 50424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) api_name, 51424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) file_system, 52424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) '%s/_permission_features.json' % svn_constants.API_PATH) 53eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 54eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 55eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochdef _ExistsInExtensionApi(api_name, file_system): 56eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch '''Parses the api/extension_api.json file (available in Chrome versions 57eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch before 18) for an API namespace. If this is successfully found, then the API 58eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch is considered to have been 'stable' for the given version. 59eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch ''' 60eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch try: 61424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) extension_api_json = file_system.GetFromFile( 62424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) '%s/extension_api.json' % svn_constants.API_PATH) 63eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch api_rows = [row.get('namespace') for row in extension_api_json 64eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if 'namespace' in row] 65424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return api_name in api_rows 66ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch except FileNotFoundError: 67eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch # This should only happen on preview.py since extension_api.json is no 68eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch # longer present in trunk. 69eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return False 70eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 71424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) 72eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochclass AvailabilityFinder(object): 73424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) '''Generates availability information for APIs by looking at API schemas and 74424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) _features files over multiple release versions of Chrome. 75eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch ''' 76eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 77eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch def __init__(self, 78424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) file_system_iterator, 79eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch object_store_creator, 80424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) branch_utility): 81424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) self._file_system_iterator = file_system_iterator 82eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch self._object_store_creator = object_store_creator 83424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) self._object_store = self._object_store_creator.Create(AvailabilityFinder) 84eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch self._branch_utility = branch_utility 85eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 86424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) def _ExistsInFileSystem(self, api_name, file_system): 87424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) '''Checks for existence of |api_name| within the list of api files in the 88424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) api/ directory found using the given |file_system|. 89424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) ''' 90424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) file_names = file_system.ReadSingle('%s/' % svn_constants.API_PATH) 91424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) api_names = tuple(os.path.splitext(name)[0] for name in file_names 92424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if os.path.splitext(name)[1][1:] in ['json', 'idl']) 93424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) 94424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # API file names in api/ are unix_name at every version except for versions 95424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # 18, 19, and 20. Since unix_name is the more common format, check it first. 96424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return (UnixName(api_name) in api_names) or (api_name in api_names) 97424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) 98424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) def _CheckStableAvailability(self, api_name, file_system, version): 99424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) '''Checks for availability of an API, |api_name|, on the stable channel. 100424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) Considers several _features.json files, file system existence, and 101424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) extension_api.json depending on the given |version|. 102eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch ''' 103424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if version < 5: 104424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # SVN data isn't available below version 5. 105424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return False 106424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) available_channel = None 107424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) fs_factory = CompiledFileSystem.Factory(file_system, 108424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) self._object_store_creator) 109eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch features_fs = fs_factory.Create(lambda _, json: json_parse.Parse(json), 110eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch AvailabilityFinder, 111eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch category='features') 112424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if version >= 28: 113424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # The _api_features.json file first appears in version 28 and should be 114424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # the most reliable for finding API availability. 115424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) available_channel = _GetChannelFromApiFeatures(api_name, features_fs) 116424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if version >= 20: 117424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # The _permission_features.json and _manifest_features.json files are 118424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # present in Chrome 20 and onwards. Use these if no information could be 119424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # found using _api_features.json. 120424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) available_channel = available_channel or ( 121424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) _GetChannelFromPermissionFeatures(api_name, features_fs) 122424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) or _GetChannelFromManifestFeatures(api_name, features_fs)) 123424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if available_channel is not None: 124424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return available_channel == 'stable' 125424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if version >= 18: 126424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # Fall back to a check for file system existence if the API is not 127424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # stable in any of the _features.json files, OR if we're dealing with 128424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # version 18 or 19, which don't contain relevant _features information. 129424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return self._ExistsInFileSystem(api_name, file_system) 130424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if version >= 5: 131424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # Versions 17 down to 5 have an extension_api.json file which 132424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # contains namespaces for each API that was available at the time. 133424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return _ExistsInExtensionApi(api_name, features_fs) 134424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) 135424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) def _CheckChannelAvailability(self, api_name, file_system, channel_name): 136424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) '''Searches through the _features files in a given |file_system| and 137424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) determines whether or not an API is available on the given channel, 138424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) |channel_name|. 139eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch ''' 140424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) fs_factory = CompiledFileSystem.Factory(file_system, 141424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) self._object_store_creator) 142424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) features_fs = fs_factory.Create(lambda _, json: json_parse.Parse(json), 143424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) AvailabilityFinder, 144424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) category='features') 145ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch available_channel = (_GetChannelFromApiFeatures(api_name, features_fs) 146ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch or _GetChannelFromPermissionFeatures(api_name, features_fs) 147ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch or _GetChannelFromManifestFeatures(api_name, features_fs)) 148424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if (available_channel is None and 149424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) self._ExistsInFileSystem(api_name, file_system)): 150eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch # If an API is not represented in any of the _features files, but exists 151eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch # in the filesystem, then assume it is available in this version. 152eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch # The windows API is an example of this. 153424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) available_channel = channel_name 154424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # If the channel we're checking is the same as or newer than the 155424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # |available_channel| then the API is available at this channel. 156424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return (available_channel is not None and 157424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) BranchUtility.NewestChannel((available_channel, channel_name)) 158424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) == channel_name) 159424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) 160424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) def _CheckApiAvailability(self, api_name, file_system, channel_info): 161424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) '''Determines the availability for an API at a certain version of Chrome. 162424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) Two branches of logic are used depending on whether or not the API is 163424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) determined to be 'stable' at the given version. 164424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) ''' 165424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) if channel_info.channel == 'stable': 166424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return self._CheckStableAvailability(api_name, 167424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) file_system, 168424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) channel_info.version) 169424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return self._CheckChannelAvailability(api_name, 170424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) file_system, 171424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) channel_info.channel) 172eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 173eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch def GetApiAvailability(self, api_name): 174424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) '''Performs a search for an API's top-level availability by using a 175424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) HostFileSystemIterator instance to traverse multiple version of the 176424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) SVN filesystem. 177eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch ''' 178eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch availability = self._object_store.Get(api_name).Get() 179eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if availability is not None: 180eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return availability 181eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 182424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) def check_api_availability(file_system, channel_info): 183424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) return self._CheckApiAvailability(api_name, file_system, channel_info) 184eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 185424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) availability = self._file_system_iterator.Descending( 186424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) self._branch_utility.GetChannelInfo('dev'), 187424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) check_api_availability) 1883551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) if availability is None: 189424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) # The API wasn't available on 'dev', so it must be a 'trunk'-only API. 190424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) availability = self._branch_utility.GetChannelInfo('trunk') 191eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch self._object_store.Set(api_name, availability) 192eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return availability 193