New file |
0,0 → 1,707 |
<?php |
/** |
* PEAR_DependencyDB, advanced installed packages dependency database |
* |
* PHP versions 4 and 5 |
* |
* LICENSE: This source file is subject to version 3.0 of the PHP license |
* that is available through the world-wide-web at the following URI: |
* http://www.php.net/license/3_0.txt. If you did not receive a copy of |
* the PHP License and are unable to obtain it through the web, please |
* send a note to license@php.net so we can mail you a copy immediately. |
* |
* @category pear |
* @package PEAR |
* @author Tomas V. V. Cox <cox@idecnet.com> |
* @author Greg Beaver <cellog@php.net> |
* @copyright 1997-2006 The PHP Group |
* @license http://www.php.net/license/3_0.txt PHP License 3.0 |
* @version CVS: $Id: DependencyDB.php,v 1.35 2007/01/06 04:03:32 cellog Exp $ |
* @link http://pear.php.net/package/PEAR |
* @since File available since Release 1.4.0a1 |
*/ |
|
/** |
* Needed for error handling |
*/ |
require_once 'PEAR.php'; |
require_once 'PEAR/Config.php'; |
|
$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] = array(); |
/** |
* Track dependency relationships between installed packages |
* @category pear |
* @package PEAR |
* @author Greg Beaver <cellog@php.net> |
* @author Tomas V.V.Cox <cox@idec.net.com> |
* @copyright 1997-2006 The PHP Group |
* @license http://www.php.net/license/3_0.txt PHP License 3.0 |
* @version Release: 1.5.1 |
* @link http://pear.php.net/package/PEAR |
* @since Class available since Release 1.4.0a1 |
*/ |
class PEAR_DependencyDB |
{ |
// {{{ properties |
|
/** |
* This is initialized by {@link setConfig()} |
* @var PEAR_Config |
* @access private |
*/ |
var $_config; |
/** |
* This is initialized by {@link setConfig()} |
* @var PEAR_Registry |
* @access private |
*/ |
var $_registry; |
/** |
* Filename of the dependency DB (usually .depdb) |
* @var string |
* @access private |
*/ |
var $_depdb = false; |
/** |
* File name of the lockfile (usually .depdblock) |
* @var string |
* @access private |
*/ |
var $_lockfile = false; |
/** |
* Open file resource for locking the lockfile |
* @var resource|false |
* @access private |
*/ |
var $_lockFp = false; |
/** |
* API version of this class, used to validate a file on-disk |
* @var string |
* @access private |
*/ |
var $_version = '1.0'; |
/** |
* Cached dependency database file |
* @var array|null |
* @access private |
*/ |
var $_cache; |
|
// }}} |
// {{{ & singleton() |
|
/** |
* Get a raw dependency database. Calls setConfig() and assertDepsDB() |
* @param PEAR_Config |
* @param string|false full path to the dependency database, or false to use default |
* @return PEAR_DependencyDB|PEAR_Error |
* @static |
*/ |
function &singleton(&$config, $depdb = false) |
{ |
if (!isset($GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] |
[$config->get('php_dir', null, 'pear.php.net')])) { |
$a = new PEAR_DependencyDB; |
$GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] |
[$config->get('php_dir', null, 'pear.php.net')] = &$a; |
$a->setConfig($config, $depdb); |
if (PEAR::isError($e = $a->assertDepsDB())) { |
return $e; |
} |
} |
return $GLOBALS['_PEAR_DEPENDENCYDB_INSTANCE'] |
[$config->get('php_dir', null, 'pear.php.net')]; |
} |
|
/** |
* Set up the registry/location of dependency DB |
* @param PEAR_Config|false |
* @param string|false full path to the dependency database, or false to use default |
*/ |
function setConfig(&$config, $depdb = false) |
{ |
if (!$config) { |
$this->_config = &PEAR_Config::singleton(); |
} else { |
$this->_config = &$config; |
} |
$this->_registry = &$this->_config->getRegistry(); |
if (!$depdb) { |
$this->_depdb = $this->_config->get('php_dir', null, 'pear.php.net') . |
DIRECTORY_SEPARATOR . '.depdb'; |
} else { |
$this->_depdb = $depdb; |
} |
$this->_lockfile = dirname($this->_depdb) . DIRECTORY_SEPARATOR . '.depdblock'; |
} |
// }}} |
|
function hasWriteAccess() |
{ |
if (!file_exists($this->_depdb)) { |
$dir = $this->_depdb; |
while ($dir && $dir != '.') { |
$dir = dirname($dir); // cd .. |
if ($dir != '.' && file_exists($dir)) { |
if (is_writeable($dir)) { |
return true; |
} else { |
return false; |
} |
} |
} |
return false; |
} |
return is_writeable($this->_depdb); |
} |
|
// {{{ assertDepsDB() |
|
/** |
* Create the dependency database, if it doesn't exist. Error if the database is |
* newer than the code reading it. |
* @return void|PEAR_Error |
*/ |
function assertDepsDB() |
{ |
if (!is_file($this->_depdb)) { |
$this->rebuildDB(); |
} else { |
$depdb = $this->_getDepDB(); |
// Datatype format has been changed, rebuild the Deps DB |
if ($depdb['_version'] < $this->_version) { |
$this->rebuildDB(); |
} |
if ($depdb['_version']{0} > $this->_version{0}) { |
return PEAR::raiseError('Dependency database is version ' . |
$depdb['_version'] . ', and we are version ' . |
$this->_version . ', cannot continue'); |
} |
} |
} |
|
/** |
* Get a list of installed packages that depend on this package |
* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array |
* @return array|false |
*/ |
function getDependentPackages(&$pkg) |
{ |
$data = $this->_getDepDB(); |
if (is_object($pkg)) { |
$channel = strtolower($pkg->getChannel()); |
$package = strtolower($pkg->getPackage()); |
} else { |
$channel = strtolower($pkg['channel']); |
$package = strtolower($pkg['package']); |
} |
if (isset($data['packages'][$channel][$package])) { |
return $data['packages'][$channel][$package]; |
} |
return false; |
} |
|
/** |
* Get a list of the actual dependencies of installed packages that depend on |
* a package. |
* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array |
* @return array|false |
*/ |
function getDependentPackageDependencies(&$pkg) |
{ |
$data = $this->_getDepDB(); |
if (is_object($pkg)) { |
$channel = strtolower($pkg->getChannel()); |
$package = strtolower($pkg->getPackage()); |
} else { |
$channel = strtolower($pkg['channel']); |
$package = strtolower($pkg['package']); |
} |
$depend = $this->getDependentPackages($pkg); |
if (!$depend) { |
return false; |
} |
$dependencies = array(); |
foreach ($depend as $info) { |
$temp = $this->getDependencies($info); |
foreach ($temp as $dep) { |
if (strtolower($dep['dep']['channel']) == strtolower($channel) && |
strtolower($dep['dep']['name']) == strtolower($package)) { |
if (!isset($dependencies[$info['channel']])) { |
$dependencies[$info['channel']] = array(); |
} |
if (!isset($dependencies[$info['channel']][$info['package']])) { |
$dependencies[$info['channel']][$info['package']] = array(); |
} |
$dependencies[$info['channel']][$info['package']][] = $dep; |
} |
} |
} |
return $dependencies; |
} |
|
/** |
* Get a list of dependencies of this installed package |
* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array |
* @return array|false |
*/ |
function getDependencies(&$pkg) |
{ |
if (is_object($pkg)) { |
$channel = strtolower($pkg->getChannel()); |
$package = strtolower($pkg->getPackage()); |
} else { |
$channel = strtolower($pkg['channel']); |
$package = strtolower($pkg['package']); |
} |
$data = $this->_getDepDB(); |
if (isset($data['dependencies'][$channel][$package])) { |
return $data['dependencies'][$channel][$package]; |
} |
return false; |
} |
|
/** |
* Determine whether $parent depends on $child, near or deep |
* @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 |
* @param array|PEAR_PackageFile_v2|PEAR_PackageFile_v2 |
*/ |
function dependsOn($parent, $child) |
{ |
$c = array(); |
$this->_getDepDB(); |
return $this->_dependsOn($parent, $child, $c); |
} |
|
function _dependsOn($parent, $child, &$checked) |
{ |
if (is_object($parent)) { |
$channel = strtolower($parent->getChannel()); |
$package = strtolower($parent->getPackage()); |
} else { |
$channel = strtolower($parent['channel']); |
$package = strtolower($parent['package']); |
} |
if (is_object($child)) { |
$depchannel = strtolower($child->getChannel()); |
$deppackage = strtolower($child->getPackage()); |
} else { |
$depchannel = strtolower($child['channel']); |
$deppackage = strtolower($child['package']); |
} |
if (isset($checked[$channel][$package][$depchannel][$deppackage])) { |
return false; // avoid endless recursion |
} |
$checked[$channel][$package][$depchannel][$deppackage] = true; |
if (!isset($this->_cache['dependencies'][$channel][$package])) { |
return false; |
} |
foreach ($this->_cache['dependencies'][$channel][$package] as $info) { |
if (isset($info['dep']['uri'])) { |
if (is_object($child)) { |
if ($info['dep']['uri'] == $child->getURI()) { |
return true; |
} |
} elseif (isset($child['uri'])) { |
if ($info['dep']['uri'] == $child['uri']) { |
return true; |
} |
} |
return false; |
} |
if (strtolower($info['dep']['channel']) == strtolower($depchannel) && |
strtolower($info['dep']['name']) == strtolower($deppackage)) { |
return true; |
} |
} |
foreach ($this->_cache['dependencies'][$channel][$package] as $info) { |
if (isset($info['dep']['uri'])) { |
if ($this->_dependsOn(array( |
'uri' => $info['dep']['uri'], |
'package' => $info['dep']['name']), $child, $checked)) { |
return true; |
} |
} else { |
if ($this->_dependsOn(array( |
'channel' => $info['dep']['channel'], |
'package' => $info['dep']['name']), $child, $checked)) { |
return true; |
} |
} |
} |
return false; |
} |
|
/** |
* Register dependencies of a package that is being installed or upgraded |
* @param PEAR_PackageFile_v2|PEAR_PackageFile_v2 |
*/ |
function installPackage(&$package) |
{ |
$data = $this->_getDepDB(); |
unset($this->_cache); |
$this->_setPackageDeps($data, $package); |
$this->_writeDepDB($data); |
} |
|
/** |
* Remove dependencies of a package that is being uninstalled, or upgraded. |
* |
* Upgraded packages first uninstall, then install |
* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2|array If an array, then it must have |
* indices 'channel' and 'package' |
*/ |
function uninstallPackage(&$pkg) |
{ |
$data = $this->_getDepDB(); |
unset($this->_cache); |
if (is_object($pkg)) { |
$channel = strtolower($pkg->getChannel()); |
$package = strtolower($pkg->getPackage()); |
} else { |
$channel = strtolower($pkg['channel']); |
$package = strtolower($pkg['package']); |
} |
if (!isset($data['dependencies'][$channel][$package])) { |
return true; |
} |
foreach ($data['dependencies'][$channel][$package] as $dep) { |
$found = false; |
if (isset($dep['dep']['uri'])) { |
$depchannel = '__uri'; |
} else { |
$depchannel = strtolower($dep['dep']['channel']); |
} |
if (isset($data['packages'][$depchannel][strtolower($dep['dep']['name'])])) { |
foreach ($data['packages'][$depchannel][strtolower($dep['dep']['name'])] as |
$i => $info) { |
if ($info['channel'] == $channel && |
$info['package'] == $package) { |
$found = true; |
break; |
} |
} |
} |
if ($found) { |
unset($data['packages'][$depchannel][strtolower($dep['dep']['name'])][$i]); |
if (!count($data['packages'][$depchannel][strtolower($dep['dep']['name'])])) { |
unset($data['packages'][$depchannel][strtolower($dep['dep']['name'])]); |
if (!count($data['packages'][$depchannel])) { |
unset($data['packages'][$depchannel]); |
} |
} else { |
$data['packages'][$depchannel][strtolower($dep['dep']['name'])] = |
array_values( |
$data['packages'][$depchannel][strtolower($dep['dep']['name'])]); |
} |
} |
} |
unset($data['dependencies'][$channel][$package]); |
if (!count($data['dependencies'][$channel])) { |
unset($data['dependencies'][$channel]); |
} |
if (!count($data['dependencies'])) { |
unset($data['dependencies']); |
} |
if (!count($data['packages'])) { |
unset($data['packages']); |
} |
$this->_writeDepDB($data); |
} |
|
/** |
* Rebuild the dependency DB by reading registry entries. |
* @return true|PEAR_Error |
*/ |
function rebuildDB() |
{ |
$depdb = array('_version' => $this->_version); |
if (!$this->hasWriteAccess()) { |
// allow startup for read-only with older Registry |
return $depdb; |
} |
$packages = $this->_registry->listAllPackages(); |
foreach ($packages as $channel => $ps) { |
foreach ($ps as $package) { |
$package = $this->_registry->getPackage($package, $channel); |
$this->_setPackageDeps($depdb, $package); |
} |
} |
$error = $this->_writeDepDB($depdb); |
if (PEAR::isError($error)) { |
return $error; |
} |
$this->_cache = $depdb; |
return true; |
} |
|
/** |
* Register usage of the dependency DB to prevent race conditions |
* @param int one of the LOCK_* constants |
* @return true|PEAR_Error |
* @access private |
*/ |
function _lock($mode = LOCK_EX) |
{ |
if (!eregi('Windows 9', php_uname())) { |
if ($mode != LOCK_UN && is_resource($this->_lockFp)) { |
// XXX does not check type of lock (LOCK_SH/LOCK_EX) |
return true; |
} |
$open_mode = 'w'; |
// XXX People reported problems with LOCK_SH and 'w' |
if ($mode === LOCK_SH) { |
if (!file_exists($this->_lockfile)) { |
touch($this->_lockfile); |
} elseif (!is_file($this->_lockfile)) { |
return PEAR::raiseError('could not create Dependency lock file, ' . |
'it exists and is not a regular file'); |
} |
$open_mode = 'r'; |
} |
|
if (!is_resource($this->_lockFp)) { |
$this->_lockFp = @fopen($this->_lockfile, $open_mode); |
} |
if (!is_resource($this->_lockFp)) { |
return PEAR::raiseError("could not create Dependency lock file" . |
(isset($php_errormsg) ? ": " . $php_errormsg : "")); |
} |
if (!(int)flock($this->_lockFp, $mode)) { |
switch ($mode) { |
case LOCK_SH: $str = 'shared'; break; |
case LOCK_EX: $str = 'exclusive'; break; |
case LOCK_UN: $str = 'unlock'; break; |
default: $str = 'unknown'; break; |
} |
return PEAR::raiseError("could not acquire $str lock ($this->_lockfile)"); |
} |
} |
return true; |
} |
|
/** |
* Release usage of dependency DB |
* @return true|PEAR_Error |
* @access private |
*/ |
function _unlock() |
{ |
$ret = $this->_lock(LOCK_UN); |
if (is_resource($this->_lockFp)) { |
fclose($this->_lockFp); |
} |
$this->_lockFp = null; |
return $ret; |
} |
|
/** |
* Load the dependency database from disk, or return the cache |
* @return array|PEAR_Error |
*/ |
function _getDepDB() |
{ |
if (!$this->hasWriteAccess()) { |
return array('_version' => $this->_version); |
} |
if (isset($this->_cache)) { |
return $this->_cache; |
} |
if (!$fp = fopen($this->_depdb, 'r')) { |
$err = PEAR::raiseError("Could not open dependencies file `".$this->_depdb."'"); |
return $err; |
} |
$rt = get_magic_quotes_runtime(); |
set_magic_quotes_runtime(0); |
clearstatcache(); |
fclose($fp); |
$data = unserialize(file_get_contents($this->_depdb)); |
set_magic_quotes_runtime($rt); |
$this->_cache = $data; |
return $data; |
} |
|
/** |
* Write out the dependency database to disk |
* @param array the database |
* @return true|PEAR_Error |
* @access private |
*/ |
function _writeDepDB(&$deps) |
{ |
if (PEAR::isError($e = $this->_lock(LOCK_EX))) { |
return $e; |
} |
if (!$fp = fopen($this->_depdb, 'wb')) { |
$this->_unlock(); |
return PEAR::raiseError("Could not open dependencies file `".$this->_depdb."' for writing"); |
} |
$rt = get_magic_quotes_runtime(); |
set_magic_quotes_runtime(0); |
fwrite($fp, serialize($deps)); |
set_magic_quotes_runtime($rt); |
fclose($fp); |
$this->_unlock(); |
$this->_cache = $deps; |
return true; |
} |
|
/** |
* Register all dependencies from a package in the dependencies database, in essence |
* "installing" the package's dependency information |
* @param array the database |
* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 |
* @access private |
*/ |
function _setPackageDeps(&$data, &$pkg) |
{ |
$pkg->setConfig($this->_config); |
if ($pkg->getPackagexmlVersion() == '1.0') { |
$gen = &$pkg->getDefaultGenerator(); |
$deps = $gen->dependenciesToV2(); |
} else { |
$deps = $pkg->getDeps(true); |
} |
if (!$deps) { |
return; |
} |
if (!is_array($data)) { |
$data = array(); |
} |
if (!isset($data['dependencies'])) { |
$data['dependencies'] = array(); |
} |
if (!isset($data['dependencies'][strtolower($pkg->getChannel())])) { |
$data['dependencies'][strtolower($pkg->getChannel())] = array(); |
} |
$data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())] |
= array(); |
if (isset($deps['required']['package'])) { |
if (!isset($deps['required']['package'][0])) { |
$deps['required']['package'] = array($deps['required']['package']); |
} |
foreach ($deps['required']['package'] as $dep) { |
$this->_registerDep($data, $pkg, $dep, 'required'); |
} |
} |
if (isset($deps['optional']['package'])) { |
if (!isset($deps['optional']['package'][0])) { |
$deps['optional']['package'] = array($deps['optional']['package']); |
} |
foreach ($deps['optional']['package'] as $dep) { |
$this->_registerDep($data, $pkg, $dep, 'optional'); |
} |
} |
if (isset($deps['required']['subpackage'])) { |
if (!isset($deps['required']['subpackage'][0])) { |
$deps['required']['subpackage'] = array($deps['required']['subpackage']); |
} |
foreach ($deps['required']['subpackage'] as $dep) { |
$this->_registerDep($data, $pkg, $dep, 'required'); |
} |
} |
if (isset($deps['optional']['subpackage'])) { |
if (!isset($deps['optional']['subpackage'][0])) { |
$deps['optional']['subpackage'] = array($deps['optional']['subpackage']); |
} |
foreach ($deps['optional']['subpackage'] as $dep) { |
$this->_registerDep($data, $pkg, $dep, 'optional'); |
} |
} |
if (isset($deps['group'])) { |
if (!isset($deps['group'][0])) { |
$deps['group'] = array($deps['group']); |
} |
foreach ($deps['group'] as $group) { |
if (isset($group['package'])) { |
if (!isset($group['package'][0])) { |
$group['package'] = array($group['package']); |
} |
foreach ($group['package'] as $dep) { |
$this->_registerDep($data, $pkg, $dep, 'optional', |
$group['attribs']['name']); |
} |
} |
if (isset($group['subpackage'])) { |
if (!isset($group['subpackage'][0])) { |
$group['subpackage'] = array($group['subpackage']); |
} |
foreach ($group['subpackage'] as $dep) { |
$this->_registerDep($data, $pkg, $dep, 'optional', |
$group['attribs']['name']); |
} |
} |
} |
} |
if ($data['dependencies'][strtolower($pkg->getChannel())] |
[strtolower($pkg->getPackage())] == array()) { |
unset($data['dependencies'][strtolower($pkg->getChannel())] |
[strtolower($pkg->getPackage())]); |
if (!count($data['dependencies'][strtolower($pkg->getChannel())])) { |
unset($data['dependencies'][strtolower($pkg->getChannel())]); |
} |
} |
} |
|
/** |
* @param array the database |
* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 |
* @param array the specific dependency |
* @param required|optional whether this is a required or an optional dep |
* @param string|false dependency group this dependency is from, or false for ordinary dep |
*/ |
function _registerDep(&$data, &$pkg, $dep, $type, $group = false) |
{ |
$info = array( |
'dep' => $dep, |
'type' => $type, |
'group' => $group); |
|
if (isset($dep['channel'])) { |
$depchannel = $dep['channel']; |
} else { |
$depchannel = '__uri'; |
} |
if (!isset($data['dependencies'])) { |
$data['dependencies'] = array(); |
} |
if (!isset($data['dependencies'][strtolower($pkg->getChannel())])) { |
$data['dependencies'][strtolower($pkg->getChannel())] = array(); |
} |
if (!isset($data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())])) { |
$data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())] = array(); |
} |
$data['dependencies'][strtolower($pkg->getChannel())][strtolower($pkg->getPackage())][] |
= $info; |
if (isset($data['packages'][strtolower($depchannel)][strtolower($dep['name'])])) { |
$found = false; |
foreach ($data['packages'][strtolower($depchannel)][strtolower($dep['name'])] |
as $i => $p) { |
if ($p['channel'] == strtolower($pkg->getChannel()) && |
$p['package'] == strtolower($pkg->getPackage())) { |
$found = true; |
break; |
} |
} |
if (!$found) { |
$data['packages'][strtolower($depchannel)][strtolower($dep['name'])][] |
= array('channel' => strtolower($pkg->getChannel()), |
'package' => strtolower($pkg->getPackage())); |
} |
} else { |
if (!isset($data['packages'])) { |
$data['packages'] = array(); |
} |
if (!isset($data['packages'][strtolower($depchannel)])) { |
$data['packages'][strtolower($depchannel)] = array(); |
} |
if (!isset($data['packages'][strtolower($depchannel)][strtolower($dep['name'])])) { |
$data['packages'][strtolower($depchannel)][strtolower($dep['name'])] = array(); |
} |
$data['packages'][strtolower($depchannel)][strtolower($dep['name'])][] |
= array('channel' => strtolower($pkg->getChannel()), |
'package' => strtolower($pkg->getPackage())); |
} |
} |
} |
?> |