#!/bin/env php
|
Size: | 14394 |
Storage flags: |
<?php
//=============================================================================
//
// Copyright Francois Laupretre <phk@tekwire.net>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//=============================================================================
/**
* @copyright Francois Laupretre <phk@tekwire.net>
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0
* @category PHK
* @package PHK
*///==========================================================================
namespace PHK {
if (!class_exists('PHK\Mgr',false))
{
//=============================================================================
/**
* The PHK manager
*
* This class manages the table of currently mounted packages.
*
* Each package is uniquely identified by a 'mount point' (a string computed
* at mount time). A given package file always gets the same mount point in
* every request, as long as it is not modified.
*
* Among others, this class allows to mount and umount packages.
*
* Runtime code -> 100% read-only.
*
* Static-only
* API status: Public
* Included in the PHK PHP runtime: Yes
* Implemented in the extension: Yes
*///==========================================================================
class Mgr
{
//-- Global static properties
/** @var array Currently mounted PHK instances */
private static $phk_tab=array(); // Key=mount ID, value=PHK instance
/**
* @var array Proxy objects for each currently mounted package. As
* long as the proxy object is not instantiated through the proxy() method,
* the corresponding value is null in this array. Contains exactly the same
* keys as $phk_tab.
*/
private static $proxy_tab=array(); // Key=mount ID, value=PHK\Proxy|null
/* @var int Running value for \PHK\Build\Creator mount points */
private static $tmp_mnt_num=0;
/* @var boolean|null Global cache toggle. If null, per-instance logic is used */
private static $caching=null;
//-----
/**
* Checks if a mount point is valid (if it corresponds to a currently mounted
* package)
*
* @param string $mnt Mount point to check
* @return boolean
*/
public static function isMounted($mnt)
{
return isset(self::$phk_tab[$mnt]);
}
//-----
/**
* Same as isMounted but throws an exception is the mount point is invalid.
*
* Returns the mount point so that it can be embedded in a call string.
*
* @param string $mnt Mount point to check
* @return string mount point (not modified)
* @throws \Exception if mount point is invalid
*/
public static function validate($mnt)
{
if (!self::isMounted($mnt)) throw new \Exception($mnt.': Invalid mount point');
return $mnt;
}
//-----
/**
* Returns the PHK object corresponding to a given mount point
*
* @param string $mnt Mount point
* @return PHK instance
* @throws \Exception if mount point is invalid
*/
public static function instance($mnt)
{
self::validate($mnt);
return self::$phk_tab[$mnt];
}
//-----
/**
* Returns the \PHK\Proxy object corresponding to a given mount point
*
* If the corresponding \PHK\Proxy object does not exist yet, it is created.
*
* @param string $mnt Mount point
* @return \PHK\Proxy proxy object
* @throws \Exception if mount point is invalid
*/
public static function proxy($mnt)
{
self::validate($mnt);
if (is_null(self::$proxy_tab[$mnt]))
{
$phk=self::instance($mnt);
self::$proxy_tab[$mnt]=new \PHK\Proxy($phk->path(),$phk->flags());
}
return self::$proxy_tab[$mnt];
}
//-----
/**
* Returns the list of the defined mount points.
*
* @return array
*/
public static function mntList()
{
return array_keys(self::$phk_tab);
}
//---------
/**
* Sets the global caching toggle
*
* Normally, the global cache toggle is always null, except in 'webinfo'
* mode, where it is false to inhibit any caching of webinfo information
* (2 reasons: useless in terms of performance, and could use the same keys as
* the 'non-webinfo' mode, so we would have to use another key prefix).
*
* @param boolean|null $caching True: always cache, False: never cache,
* null: use per-instance logic.
* @return void
*/
public static function setCache($caching)
{
self::$caching=$caching;
}
//---------
/**
* Determines if a an URI can be cached.
*
* For performance reasons, the input URI is splitted.
*
* Called by the wrapper to know if it should cache the data it got from its
* backend.
*
* The global cache toggle is checked first. If it is null, control is
* transferred to the instance logic.
*
* @param string|null $mnt Mount point or null if global command
* @param string|null $command command if defined
* @param array|null $params Command parameters if defined
* @param string $path Path
* @return boolean whether the data should be cached or not
* @throws \Exception if mount point is invalid
*/
public static function cacheEnabled($mnt,$command,$params,$path)
{
if (!is_null(self::$caching)) return self::$caching;
if (is_null($mnt)) return false;
return self::instance($mnt)->cacheEnabled($command,$params,$path);
}
//---------
/**
* Given a file path, tries to determine if it is currently mounted. If it is
* the case, the corresponding mount point is returned. If not, an exception is
* thrown.
*
* Note: when dealing with sub-packages, the input path parameter can be a PHK
* URI.
*
* @param string $path Path of a PHK package
* @return the corresponding mount point
* @throws \Exception if the file is not currently mounted
*/
public static function pathToMnt($path)
{
$dummy1=$mnt=$dummy2=null;
self::computeMnt($path,$dummy1,$mnt,$dummy2);
if (self::isMounted($mnt)) return $mnt;
throw new \Exception($path.': path is not mounted');
}
//---------
/**
* Given a PHK uri, returns the path of the first-level package for
* this path. The function recurses until it finds a physical path.
*
* @param string $path A path typically set as '__FILE__'
* @return the physical path of the 1st-level package containing this path
*/
public static function topLevelPath($path)
{
while (self::isPhkUri($path))
{
$mnt=self::uriToMnt($path);
$map=self::instance($mnt);
$path=$map->path();
}
return $path;
}
//---------
/**
* Mount a PHK package and returns the new (or previous, if already loaded)
* PHK mount point.
*
* Can also create empty \PHK\Build\Creator instances (when the 'CREATOR' flag is set).
*
* @param string $path The path of an existing PHK archive, or the path of the
* archive to create if ($flags & \PHK::IS_CREATOR)
* @param int $flags Or-ed combination of PHK mount flags.
* @return string the mount point
*/
public static function mount($path,$flags=0)
{
try
{
if ($flags & \PHK::IS_CREATOR)
{
$mnt='_tmp_mnt_'.(self::$tmp_mnt_num++);
self::$proxy_tab[$mnt]=null;
self::$phk_tab[$mnt]=new \PHK\Build\Creator($mnt,$path,$flags);
}
else // Mount an existing archive
{
$parentMnt=$mnt=$mtime=$options=$buildInfo=null;
self::computeMnt($path,$parentMnt,$mnt,$mtime);
if (self::isMounted($mnt)) return $mnt;
self::$proxy_tab[$mnt]=null;
self::$phk_tab[$mnt]=$phk=new \PHK($parentMnt,$mnt,$path,$flags,$mtime);
self::getStoreData($mnt,$options,$buildInfo);
$phk->init($options,$buildInfo);
}
}
catch (\Exception $e)
{
if (isset($mnt) && self::isMounted($mnt)) unset(self::$phk_tab[$mnt]);
throw new \Exception($path.': Cannot mount - '.$e->getMessage());
}
return $mnt;
}
//-----
/**
* Checks the PHK version this package requires against the current version.
* Then, retrieves the 'options' and 'buildInfo' arrays.
*
* This function is separated from mount() to mimic the behavior of the PHK
* extension, where this data is cached in persistent memory.
*
* @param string $mnt the mount point
* @param array $options on return, contains the options array
* @param array $buildInfo on return, contains the buildInfo array
* @return void
*/
private static function getStoreData($mnt,&$options,&$buildInfo)
{
$caching=(is_null(self::$caching) ? true : self::$caching);
// Must check this first
$mv=\PHK\Tools\Util::getMinVersion($mnt,$caching);
if (version_compare($mv,\PHK::RUNTIME_VERSION) > 0)
{
\PHK\Tools\Util::formatError('Cannot understand this version. '
.'Requires at least PHK version '.$mv);
}
$options=\PHK\Tools\Util::getOptions($mnt,$caching);
$buildInfo=\PHK\Tools\Util::getBuildInfo($mnt,$caching);
}
//---------------------------------
/**
* Computes the mount point corresponding to a given path.
*
* Also returns the parent mount point (for sub-packages), and the modification
* time (allows to call stat() only once).
*
* Mount point uniqueness is based on a combination of device+inode+mtime.
*
* When dealing with sub-packages, the input path is a PHK URI.
*
* Sub-packages inherit their parent's modification time.
*
* @param string $path The path to be mounted
* @param $parentMnt string|null returns the parent mount point. Not
* null only for sub-packages.
* @param string $mnt returns the computed mount point
* @param int $mtime returns the modification time
* @return void
* @throws \Exception with message 'File not found' if unable to stat($path).
*/
private static function computeMnt($path,&$parentMnt,&$mnt,&$mtime)
{
if (self::isPhkUri($path)) // Sub-package
{
$dummy1=$dummy2=$subpath=$parentMnt=null;
\PHK\Stream\Wrapper::parseURI($path,$dummy1,$dummy2,$parentMnt,$subpath);
self::validate($parentMnt);
$mnt=$parentMnt.'#'.str_replace('/','*',$subpath);
$mtime=self::instance($parentMnt)->mtime(); // Inherit mtime
}
else
{
$mnt=\PHK\Tools\Util::pathUniqueID('p',$path,$mtime);
$parentMnt=null;
}
}
//---------------------------------
/**
* Umounts a mounted package and any mounted descendant.
*
* We dont use __destruct because :
* 1. We don't want this to be called on script shutdown
* 2. \Exceptions cannot be caught when sent from a destructor.
*
* Accepts to remove a non registered mount point without error
*
* @param string $mnt The mount point to umount
*/
public static function umount($mnt)
{
if (self::isMounted($mnt))
{
// Umount children
foreach (array_keys(self::$phk_tab) as $dmnt)
{
if (isset(self::$phk_tab[$dmnt])
&& self::$phk_tab[$dmnt]->parentMnt()===$mnt)
self::umount($dmnt);
}
// Call instance's umount procedure
self::$phk_tab[$mnt]->umount();
// Remove from the list
unset(self::$phk_tab[$mnt]);
unset(self::$proxy_tab[$mnt]);
}
}
//---------
/**
* Builds a 'phk://' uri, from a mount ID and a path
*
* @param string $mnt The mount point
* @param string $path The path
* @return string The computed URI
*/
public static function uri($mnt,$path)
{
return self::baseURI($mnt).ltrim($path,'/');
}
//-----
/** Checks if a string is a PHK URI
*
* @param string $uri
* @return boolean
*/
public static function isPhkUri($uri)
{
$u=$uri.' ';
// Much faster this way
return ($u{0}=='p' && $u{1}=='h' && $u{2}=='k' && $u{3}==':'
&& $u{4}=='/' && $u{5}=='/');
//return (strlen($uri) >= 6) && (substr($uri,0,6)=='phk://');
}
//-----
/**
* Returns the base string used to build URIs for a given mount point.
*
* The base URI has the form : phk://<mount point>/
*
* @param string $mnt A mount point
* @return string
*/
public static function baseURI($mnt)
{
return 'phk://'.$mnt.'/';
}
//---------
/**
* Returns a 'command' URI, given a mount point and a 'command' string
*
* Command URIs have the form : phk://<mount point>/?<command>
*
* @param string $mnt A mount point
* @param string $command Command string
* @return string
*/
public static function commandURI($mnt,$command)
{
return self::uri($mnt,'?'.$command);
}
//---------
/**
* Returns the URI allowing to retrieve a section.
*
* Section URIs have the form : phk://<mount point>/?section&name=<section>
*
* @param string $mnt A mount point
* @param string $section The section to retrieve
* @return string
*/
public static function sectionURI($mnt,$section)
{
return self::commandURI($mnt,'section&name='.$section);
}
//-----
/**
* Returns the URI of the Automap map if it is defined
*
* @param string $mnt A mount point
* @return string|null returns null if the package does not define an automap.
*/
public static function automapURI($mnt)
{
if ((!self::isMounted($mnt))||(!self::instance($mnt)->mapDefined()))
return null;
return self::sectionURI($mnt,'AUTOMAP');
}
//---------------------------------
/**
* Replaces '\' characters by '/' in a URI.
*
* @param string $uri
* @return string
*/
public static function normalizeURI($uri)
{
return str_replace('\\','/',$uri);
}
//-----
/**
* Returns the mount ID of a subfile's phk uri.
* Allows to reference other subfiles in the same package if you don't want
* or cannot use Automap (the preferred method) or a relative path.
* Example : include(\PHK\Mgr::uri(\PHK\Mgr::uriToMnt(__FILE__),<path>));
*
* @param string $uri
* @return string a mount point
* @throws \Exception if the input URI is not a PHK URI
*/
public static function uriToMnt($uri)
{
if (! self::isPhkUri($uri))
throw new \Exception($uri.': Not a PHK URI');
$buf=substr(self::normalizeURI($uri),6);
$buf=substr($buf,0,strcspn($buf,'/'));
return trim($buf);
}
//---------
/**
* Check if the current PHP version is supported.
*
* Note that, if PHP version < 5.3, parsing fails because of namespaces and we
* don't even start execution. So, this test is only executed when PHP version
* >= 5.3
*
* As a side effect, until we require a version > 5.3, this function
* never fails.
*
* Calls exit(1) if PHP version is not supported by the PHK runtime
*
* @return void
*/
public static function checkPhpVersion()
{
if (version_compare(PHP_VERSION,'5.3.0') < 0)
{
echo PHP_VERSION.': Unsupported PHP version '
.'- PHK needs at least version 5.3.0';
exit(1);
}
}
//---
} // End of class
//===========================================================================
} // End of class_exists
//===========================================================================
} // End of namespace
//===========================================================================
?>