Changeset View
Changeset View
Standalone View
Standalone View
src/infrastructure/celerity/CelerityResourceMap.php
<?php | <?php | ||||
/** | /** | ||||
* Interface to the static resource map, which is a graph of available | * Interface to the static resource map, which is a graph of available | ||||
* resources, resource dependencies, and packaging information. You generally do | * resources, resource dependencies, and packaging information. You generally do | ||||
* not need to invoke it directly; instead, you call higher-level Celerity APIs | * not need to invoke it directly; instead, you call higher-level Celerity APIs | ||||
* and it uses the resource map to satisfy your requests. | * and it uses the resource map to satisfy your requests. | ||||
* | |||||
* @group celerity | |||||
*/ | */ | ||||
final class CelerityResourceMap { | final class CelerityResourceMap { | ||||
private static $instance; | private static $instance; | ||||
private $resourceMap; | |||||
private $resources; | |||||
private $symbolMap; | |||||
private $requiresMap; | |||||
private $packageMap; | private $packageMap; | ||||
private $reverseMap; | private $nameMap; | ||||
private $hashMap; | |||||
public static function getInstance() { | public function __construct(CelerityResources $resources) { | ||||
if (empty(self::$instance)) { | $this->resources = $resources; | ||||
self::$instance = new CelerityResourceMap(); | |||||
$root = phutil_get_library_root('phabricator'); | |||||
$path = '__celerity_resource_map__.php'; | $map = $resources->loadMap(); | ||||
$ok = include_once $root.'/'.$path; | $this->symbolMap = idx($map, 'symbols', array()); | ||||
if (!$ok) { | $this->requiresMap = idx($map, 'requires', array()); | ||||
throw new Exception( | $this->packageMap = idx($map, 'packages', array()); | ||||
"Failed to load Celerity resource map!"); | $this->nameMap = idx($map, 'names', array()); | ||||
// We derive these reverse maps at runtime. | |||||
$this->hashMap = array_flip($this->nameMap); | |||||
$this->componentMap = array(); | |||||
foreach ($this->packageMap as $package_name => $symbols) { | |||||
foreach ($symbols as $symbol) { | |||||
$this->componentMap[$symbol] = $package_name; | |||||
} | } | ||||
} | } | ||||
return self::$instance; | |||||
} | } | ||||
public function setResourceMap($resource_map) { | public static function getInstance() { | ||||
$this->resourceMap = $resource_map; | if (empty(self::$instance)) { | ||||
return $this; | $resources = new CelerityPhabricatorResources(); | ||||
self::$instance = new CelerityResourceMap($resources); | |||||
} | |||||
return self::$instance; | |||||
} | } | ||||
public function getPackagedNamesForSymbols(array $symbols) { | public function getPackagedNamesForSymbols(array $symbols) { | ||||
$resolved = $this->resolveResources($symbols); | $resolved = $this->resolveResources($symbols); | ||||
return $this->packageResources($resolved); | return $this->packageResources($resolved); | ||||
} | } | ||||
private function resolveResources(array $symbols) { | private function resolveResources(array $symbols) { | ||||
$map = array(); | $map = array(); | ||||
foreach ($symbols as $symbol) { | foreach ($symbols as $symbol) { | ||||
if (!empty($map[$symbol])) { | if (!empty($map[$symbol])) { | ||||
continue; | continue; | ||||
} | } | ||||
$this->resolveResource($map, $symbol); | $this->resolveResource($map, $symbol); | ||||
} | } | ||||
return $map; | return $map; | ||||
} | } | ||||
private function resolveResource(array &$map, $symbol) { | private function resolveResource(array &$map, $symbol) { | ||||
if (empty($this->resourceMap[$symbol])) { | if (empty($this->symbolMap[$symbol])) { | ||||
throw new Exception( | throw new Exception( | ||||
"Attempting to resolve unknown Celerity resource, '{$symbol}'."); | pht( | ||||
'Attempting to resolve unknown resource, "%s".', | |||||
$symbol)); | |||||
} | } | ||||
$info = $this->resourceMap[$symbol]; | $hash = $this->symbolMap[$symbol]; | ||||
foreach ($info['requires'] as $requires) { | |||||
if (!empty($map[$requires])) { | |||||
continue; | |||||
} | |||||
$this->resolveResource($map, $requires); | |||||
} | |||||
$map[$symbol] = $info; | $map[$symbol] = $hash; | ||||
if (isset($this->requiresMap[$hash])) { | |||||
$requires = $this->requiresMap[$hash]; | |||||
} else { | |||||
$requires = array(); | |||||
} | } | ||||
public function setPackageMap($package_map) { | foreach ($requires as $required_symbol) { | ||||
$this->packageMap = $package_map; | if (!empty($map[$required_symbol])) { | ||||
return $this; | continue; | ||||
} | |||||
$this->resolveResource($map, $required_symbol); | |||||
} | |||||
} | } | ||||
private function packageResources(array $resolved_map) { | private function packageResources(array $resolved_map) { | ||||
$packaged = array(); | $packaged = array(); | ||||
$handled = array(); | $handled = array(); | ||||
foreach ($resolved_map as $symbol => $info) { | foreach ($resolved_map as $symbol => $hash) { | ||||
if (isset($handled[$symbol])) { | if (isset($handled[$symbol])) { | ||||
continue; | continue; | ||||
} | } | ||||
if (empty($this->packageMap['reverse'][$symbol])) { | |||||
$packaged[$symbol] = $info; | |||||
} else { | |||||
$package = $this->packageMap['reverse'][$symbol]; | |||||
$package_info = $this->packageMap['packages'][$package]; | |||||
$packaged[$package_info['name']] = $package_info; | |||||
foreach ($package_info['symbols'] as $packaged_symbol) { | |||||
$handled[$packaged_symbol] = true; | |||||
} | |||||
} | |||||
} | |||||
$names = array(); | if (empty($this->componentMap[$symbol])) { | ||||
foreach ($packaged as $key => $resource) { | $packaged[] = $this->hashMap[$hash]; | ||||
if (isset($resource['disk'])) { | |||||
$names[] = $resource['disk']; | |||||
} else { | } else { | ||||
$names[] = $key; | $package_name = $this->componentMap[$symbol]; | ||||
$packaged[] = $package_name; | |||||
$package_symbols = $this->packageMap[$package_name]; | |||||
foreach ($package_symbols as $package_symbol) { | |||||
$handled[$package_symbol] = true; | |||||
} | |||||
} | } | ||||
} | } | ||||
return $names; | return $packaged; | ||||
} | } | ||||
public function getResourceDataForName($resource_name) { | public function getResourceDataForName($resource_name) { | ||||
$root = phutil_get_library_root('phabricator'); | return $this->resources->getResourceData($resource_name); | ||||
$root = dirname($root).'/webroot/'; | |||||
return Filesystem::readFile($root.$resource_name); | |||||
} | } | ||||
public function getResourceNamesForPackageHash($package_hash) { | public function getResourceNamesForPackageName($package_name) { | ||||
$package = idx($this->packageMap['packages'], $package_hash); | $package_symbols = idx($this->packageMap, $package_name); | ||||
if (!$package) { | if (!$package_symbols) { | ||||
return null; | return null; | ||||
} | } | ||||
$paths = array(); | $resource_names = array(); | ||||
foreach ($package['symbols'] as $symbol) { | foreach ($package_symbols as $symbol) { | ||||
$paths[] = $this->resourceMap[$symbol]['disk']; | $resource_names[] = $this->hashMap[$this->symbolMap[$symbol]]; | ||||
} | |||||
return $paths; | |||||
} | } | ||||
private function lookupSymbolInformation($symbol) { | return $resource_names; | ||||
return idx($this->resourceMap, $symbol); | |||||
} | |||||
private function lookupFileInformation($path) { | |||||
if (empty($this->reverseMap)) { | |||||
$this->reverseMap = array(); | |||||
foreach ($this->resourceMap as $symbol => $data) { | |||||
$data['provides'] = $symbol; | |||||
$this->reverseMap[$data['disk']] = $data; | |||||
} | |||||
} | |||||
return idx($this->reverseMap, $path); | |||||
} | } | ||||
/** | /** | ||||
* Get the epoch timestamp of the last modification time of a symbol. | * Get the epoch timestamp of the last modification time of a symbol. | ||||
* | * | ||||
* @param string Resource symbol to lookup. | * @param string Resource symbol to lookup. | ||||
* @return int Epoch timestamp of last resource modification. | * @return int Epoch timestamp of last resource modification. | ||||
*/ | */ | ||||
public function getModifiedTimeForName($name) { | public function getModifiedTimeForName($name) { | ||||
$package_hash = null; | if ($this->isPackageResource($name)) { | ||||
foreach ($this->packageMap['packages'] as $hash => $package) { | $names = array(); | ||||
if ($package['name'] == $name) { | foreach ($this->packageMap[$name] as $symbol) { | ||||
$package_hash = $hash; | $names[] = $this->getResourceNameForSymbol($symbol); | ||||
break; | |||||
} | } | ||||
} else { | |||||
$names = array($name); | |||||
} | } | ||||
$root = dirname(phutil_get_library_root('phabricator')).'/webroot'; | |||||
$mtime = 0; | $mtime = 0; | ||||
foreach ($names as $name) { | |||||
if ($package_hash) { | $mtime = max($mtime, $this->resources->getResourceModifiedTime($name)); | ||||
$names = $this->getResourceNamesForPackageHash($package_hash); | |||||
foreach ($names as $component_name) { | |||||
$info = $this->lookupFileInformation($component_name); | |||||
if ($info) { | |||||
$mtime = max($mtime, (int)filemtime($root.$info['disk'])); | |||||
} | |||||
} | |||||
} else { | |||||
$info = $this->lookupFileInformation($name); | |||||
if ($info) { | |||||
$root = dirname(phutil_get_library_root('phabricator')).'/webroot'; | |||||
$mtime = (int)filemtime($root.$info['disk']); | |||||
} | |||||
} | } | ||||
return $mtime; | return $mtime; | ||||
} | } | ||||
/** | /** | ||||
* Return the absolute URI for the resource associated with a symbol. This | * Return the absolute URI for the resource associated with a symbol. This | ||||
* method is fairly low-level and ignores packaging. | * method is fairly low-level and ignores packaging. | ||||
* | * | ||||
* @param string Resource symbol to lookup. | * @param string Resource symbol to lookup. | ||||
* @return string|null Fully-qualified resource URI, or null if the symbol | * @return string|null Resource URI, or null if the symbol is unknown. | ||||
* is unknown. | |||||
*/ | */ | ||||
public function getURIForSymbol($symbol) { | public function getURIForSymbol($symbol) { | ||||
$info = $this->lookupSymbolInformation($symbol); | $hash = idx($this->symbolMap, $symbol); | ||||
if ($info) { | return $this->getURIForHash($hash); | ||||
return idx($info, 'uri'); | |||||
} | |||||
return null; | |||||
} | } | ||||
/** | /** | ||||
* Return the absolute URI for the resource associated with a resource name. | * Return the absolute URI for the resource associated with a resource name. | ||||
* This method is fairly low-level and ignores packaging. | * This method is fairly low-level and ignores packaging. | ||||
* | * | ||||
* @param string Resource name to lookup. | * @param string Resource name to lookup. | ||||
* @return string|null Fully-qualified resource URI, or null if the name | * @return string|null Resource URI, or null if the name is unknown. | ||||
* is unknown. | |||||
*/ | */ | ||||
public function getURIForName($name) { | public function getURIForName($name) { | ||||
$info = $this->lookupFileInformation($name); | $hash = idx($this->nameMap, $name); | ||||
if ($info) { | return $this->getURIForHash($hash); | ||||
return idx($info, 'uri'); | |||||
} | } | ||||
foreach ($this->packageMap['packages'] as $hash => $package) { | |||||
if ($package['name'] == $name) { | |||||
return $package['uri']; | |||||
} | |||||
} | |||||
/** | |||||
* Return the absolute URI for a resource, identified by hash. | |||||
* This method is fairly low-level and ignores packaging. | |||||
* | |||||
* @param string Resource hash to lookup. | |||||
* @return string|null Resource URI, or null if the hash is unknown. | |||||
*/ | |||||
private function getURIForHash($hash) { | |||||
if ($hash === null) { | |||||
return null; | return null; | ||||
} | } | ||||
return $this->resources->getResourceURI($hash, $this->hashMap[$hash]); | |||||
} | |||||
/** | /** | ||||
* Return the resource symbols required by a named resource. | * Return the resource symbols required by a named resource. | ||||
* | * | ||||
* @param string Resource name to lookup. | * @param string Resource name to lookup. | ||||
* @return list<string>|null List of required symbols, or null if the name | * @return list<string>|null List of required symbols, or null if the name | ||||
* is unknown. | * is unknown. | ||||
*/ | */ | ||||
public function getRequiredSymbolsForName($name) { | public function getRequiredSymbolsForName($name) { | ||||
$info = $this->lookupFileInformation($name); | $hash = idx($this->symbolMap, $name); | ||||
if ($info) { | if ($hash === null) { | ||||
return idx($info, 'requires', array()); | |||||
} | |||||
return null; | return null; | ||||
} | } | ||||
return idx($this->requiresMap, $hash, array()); | |||||
} | |||||
/** | /** | ||||
* Return the resource name for a given symbol. | * Return the resource name for a given symbol. | ||||
* | * | ||||
* @param string Resource symbol to lookup. | * @param string Resource symbol to lookup. | ||||
* @return string|null Resource name, or null if the symbol is unknown. | * @return string|null Resource name, or null if the symbol is unknown. | ||||
*/ | */ | ||||
public function getResourceNameForSymbol($symbol) { | public function getResourceNameForSymbol($symbol) { | ||||
$info = $this->lookupSymbolInformation($symbol); | $hash = idx($this->symbolMap, $symbol); | ||||
if ($info) { | return idx($this->hashMap, $hash); | ||||
return idx($info, 'disk'); | |||||
} | |||||
return null; | |||||
} | } | ||||
public function isPackageResource($name) { | |||||
return isset($this->packageMap[$name]); | |||||
} | |||||
} | } |