root/trunk/framework/Web/TAssetManager.php
| Revision 2514, 11.6 kB (checked in by carl, 5 weeks ago) | |
|---|---|
|
|
| Line | |
|---|---|
| 1 | <?php |
| 2 | /** |
| 3 | * TAssetManager class |
| 4 | * |
| 5 | * @author Qiang Xue <qiang.xue@gmail.com> |
| 6 | * @link http://www.pradosoft.com/ |
| 7 | * @copyright Copyright © 2005-2008 PradoSoft |
| 8 | * @license http://www.pradosoft.com/license/ |
| 9 | * @version $Id$ |
| 10 | * @package System.Web |
| 11 | */ |
| 12 | |
| 13 | /** |
| 14 | * TAssetManager class |
| 15 | * |
| 16 | * TAssetManager provides a scheme to allow web clients visiting |
| 17 | * private files that are normally web-inaccessible. |
| 18 | * |
| 19 | * TAssetManager will copy the file to be published into a web-accessible |
| 20 | * directory. The default base directory for storing the file is "assets", which |
| 21 | * should be under the application directory. This can be changed by setting |
| 22 | * the {@link setBasePath BasePath} property together with the |
| 23 | * {@link setBaseUrl BaseUrl} property that refers to the URL for accessing the base path. |
| 24 | * |
| 25 | * By default, TAssetManager will not publish a file or directory if it already |
| 26 | * exists in the publishing directory and has an older modification time. |
| 27 | * If the application mode is set as 'Performance', the modification time check |
| 28 | * will be skipped. You can explicitly require a modification time check |
| 29 | * with the function {@link publishFilePath}. This is usually |
| 30 | * very useful during development. |
| 31 | * |
| 32 | * TAssetManager may be configured in application configuration file as follows, |
| 33 | * <code> |
| 34 | * <module id="asset" BasePath="Application.assets" BaseUrl="/assets" /> |
| 35 | * </code> |
| 36 | * where {@link getBasePath BasePath} and {@link getBaseUrl BaseUrl} are |
| 37 | * configurable properties of TAssetManager. Make sure that BasePath is a namespace |
| 38 | * pointing to a valid directory writable by the Web server process. |
| 39 | * |
| 40 | * @author Qiang Xue <qiang.xue@gmail.com> |
| 41 | * @version $Id$ |
| 42 | * @package System.Web |
| 43 | * @since 3.0 |
| 44 | */ |
| 45 | class TAssetManager extends TModule |
| 46 | { |
| 47 | /** |
| 48 | * Default web accessible base path for storing private files |
| 49 | */ |
| 50 | const DEFAULT_BASEPATH='assets'; |
| 51 | /** |
| 52 | * @var string base web accessible path for storing private files |
| 53 | */ |
| 54 | private $_basePath=null; |
| 55 | /** |
| 56 | * @var string base URL for accessing the publishing directory. |
| 57 | */ |
| 58 | private $_baseUrl=null; |
| 59 | /** |
| 60 | * @var boolean whether to use timestamp checking to ensure files are published with up-to-date versions. |
| 61 | */ |
| 62 | private $_checkTimestamp=false; |
| 63 | /** |
| 64 | * @var TApplication application instance |
| 65 | */ |
| 66 | private $_application; |
| 67 | /** |
| 68 | * @var array published assets |
| 69 | */ |
| 70 | private $_published=array(); |
| 71 | /** |
| 72 | * @var boolean whether the module is initialized |
| 73 | */ |
| 74 | private $_initialized=false; |
| 75 | |
| 76 | /** |
| 77 | * Initializes the module. |
| 78 | * This method is required by IModule and is invoked by application. |
| 79 | * @param TXmlElement module configuration |
| 80 | */ |
| 81 | public function init($config) |
| 82 | { |
| 83 | $application=$this->getApplication(); |
| 84 | if($this->_basePath===null) |
| 85 | $this->_basePath=dirname($application->getRequest()->getApplicationFilePath()).DIRECTORY_SEPARATOR.self::DEFAULT_BASEPATH; |
| 86 | if(!is_writable($this->_basePath) || !is_dir($this->_basePath)) |
| 87 | throw new TConfigurationException('assetmanager_basepath_invalid',$this->_basePath); |
| 88 | if($this->_baseUrl===null) |
| 89 | $this->_baseUrl=rtrim(dirname($application->getRequest()->getApplicationUrl()),'/\\').'/'.self::DEFAULT_BASEPATH; |
| 90 | $application->setAssetManager($this); |
| 91 | $this->_initialized=true; |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * @return string the root directory storing published asset files |
| 96 | */ |
| 97 | public function getBasePath() |
| 98 | { |
| 99 | return $this->_basePath; |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * Sets the root directory storing published asset files. |
| 104 | * The directory must be in namespace format. |
| 105 | * @param string the root directory storing published asset files |
| 106 | * @throws TInvalidOperationException if the module is initialized already |
| 107 | */ |
| 108 | public function setBasePath($value) |
| 109 | { |
| 110 | if($this->_initialized) |
| 111 | throw new TInvalidOperationException('assetmanager_basepath_unchangeable'); |
| 112 | else |
| 113 | { |
| 114 | $this->_basePath=Prado::getPathOfNamespace($value); |
| 115 | if($this->_basePath===null || !is_dir($this->_basePath) || !is_writable($this->_basePath)) |
| 116 | throw new TInvalidDataValueException('assetmanage_basepath_invalid',$value); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * @return string the base url that the published asset files can be accessed |
| 122 | */ |
| 123 | public function getBaseUrl() |
| 124 | { |
| 125 | return $this->_baseUrl; |
| 126 | } |
| 127 | |
| 128 | /** |
| 129 | * @param string the base url that the published asset files can be accessed |
| 130 | * @throws TInvalidOperationException if the module is initialized already |
| 131 | */ |
| 132 | public function setBaseUrl($value) |
| 133 | { |
| 134 | if($this->_initialized) |
| 135 | throw new TInvalidOperationException('assetmanager_baseurl_unchangeable'); |
| 136 | else |
| 137 | $this->_baseUrl=rtrim($value,'/'); |
| 138 | } |
| 139 | |
| 140 | /** |
| 141 | * Publishes a file or a directory (recursively). |
| 142 | * This method will copy the content in a directory (recursively) to |
| 143 | * a web accessible directory and returns the URL for the directory. |
| 144 | * If the application is not in performance mode, the file modification |
| 145 | * time will be used to make sure the published file is latest or not. |
| 146 | * If not, a file copy will be performed. |
| 147 | * @param string the path to be published |
| 148 | * @param boolean If true, file modification time will be checked even if the application |
| 149 | * is in performance mode. |
| 150 | * @return string an absolute URL to the published directory |
| 151 | * @throws TInvalidDataValueException if the file path to be published is |
| 152 | * invalid |
| 153 | */ |
| 154 | public function publishFilePath($path,$checkTimestamp=false) |
| 155 | { |
| 156 | if(isset($this->_published[$path])) |
| 157 | return $this->_published[$path]; |
| 158 | else if(empty($path) || ($fullpath=realpath($path))===false) |
| 159 | throw new TInvalidDataValueException('assetmanager_filepath_invalid',$path); |
| 160 | else if(is_file($fullpath)) |
| 161 | { |
| 162 | $dir=$this->hash(dirname($fullpath)); |
| 163 | $fileName=basename($fullpath); |
| 164 | $dst=$this->_basePath.DIRECTORY_SEPARATOR.$dir; |
| 165 | if(!is_file($dst.DIRECTORY_SEPARATOR.$fileName) || $checkTimestamp || $this->getApplication()->getMode()!==TApplicationMode::Performance) |
| 166 | $this->copyFile($fullpath,$dst); |
| 167 | return $this->_published[$path]=$this->_baseUrl.'/'.$dir.'/'.$fileName; |
| 168 | } |
| 169 | else |
| 170 | { |
| 171 | $dir=$this->hash($fullpath); |
| 172 | if(!is_dir($this->_basePath.DIRECTORY_SEPARATOR.$dir) || $checkTimestamp || $this->getApplication()->getMode()!==TApplicationMode::Performance) |
| 173 | { |
| 174 | Prado::trace("Publishing directory $fullpath",'System.Web.UI.TAssetManager'); |
| 175 | $this->copyDirectory($fullpath,$this->_basePath.DIRECTORY_SEPARATOR.$dir); |
| 176 | } |
| 177 | return $this->_published[$path]=$this->_baseUrl.'/'.$dir; |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | /** |
| 182 | * Returns the published path of a file path. |
| 183 | * This method does not perform any publishing. It merely tells you |
| 184 | * if the file path is published, where it will go. |
| 185 | * @param string directory or file path being published |
| 186 | * @return string the published file path |
| 187 | */ |
| 188 | public function getPublishedPath($path) |
| 189 | { |
| 190 | $path=realpath($path); |
| 191 | if(is_file($path)) |
| 192 | return $this->_basePath.DIRECTORY_SEPARATOR.$this->hash(dirname($path)).DIRECTORY_SEPARATOR.basename($path); |
| 193 | else |
| 194 | return $this->_basePath.DIRECTORY_SEPARATOR.$this->hash($path); |
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * Returns the URL of a published file path. |
| 199 | * This method does not perform any publishing. It merely tells you |
| 200 | * if the file path is published, what the URL will be to access it. |
| 201 | * @param string directory or file path being published |
| 202 | * @return string the published URL for the file path |
| 203 | */ |
| 204 | public function getPublishedUrl($path) |
| 205 | { |
| 206 | $path=realpath($path); |
| 207 | if(is_file($path)) |
| 208 | return $this->_baseUrl.'/'.$this->hash(dirname($path)).'/'.basename($path); |
| 209 | else |
| 210 | return $this->_baseUrl.'/'.$this->hash($path); |
| 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Generate a CRC32 hash for the directory path. Collisions are higher |
| 215 | * than MD5 but generates a much smaller hash string. |
| 216 | * @param string string to be hashed. |
| 217 | * @return string hashed string. |
| 218 | */ |
| 219 | protected function hash($dir) |
| 220 | { |
| 221 | return sprintf('%x',crc32($dir.Prado::getVersion())); |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * Copies a file to a directory. |
| 226 | * Copying is done only when the destination file does not exist |
| 227 | * or has an older file modification time. |
| 228 | * @param string source file path |
| 229 | * @param string destination directory (if not exists, it will be created) |
| 230 | */ |
| 231 | protected function copyFile($src,$dst) |
| 232 | { |
| 233 | if(!is_dir($dst)) |
| 234 | { |
| 235 | @mkdir($dst); |
| 236 | @chmod($dst, PRADO_CHMOD); |
| 237 | } |
| 238 | $dstFile=$dst.DIRECTORY_SEPARATOR.basename($src); |
| 239 | if(@filemtime($dstFile)<@filemtime($src)) |
| 240 | { |
| 241 | Prado::trace("Publishing file $src to $dstFile",'System.Web.TAssetManager'); |
| 242 | @copy($src,$dstFile); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | /** |
| 247 | * Copies a directory recursively as another. |
| 248 | * If the destination directory does not exist, it will be created. |
| 249 | * File modification time is used to ensure the copied files are latest. |
| 250 | * @param string the source directory |
| 251 | * @param string the destination directory |
| 252 | * @todo a generic solution to ignore certain directories and files |
| 253 | */ |
| 254 | public function copyDirectory($src,$dst) |
| 255 | { |
| 256 | if(!is_dir($dst)) |
| 257 | { |
| 258 | @mkdir($dst); |
| 259 | @chmod($dst, PRADO_CHMOD); |
| 260 | } |
| 261 | if($folder=@opendir($src)) |
| 262 | { |
| 263 | while($file=@readdir($folder)) |
| 264 | { |
| 265 | if($file==='.' || $file==='..' || $file==='.svn') |
| 266 | continue; |
| 267 | else if(is_file($src.DIRECTORY_SEPARATOR.$file)) |
| 268 | { |
| 269 | if(@filemtime($dst.DIRECTORY_SEPARATOR.$file)<@filemtime($src.DIRECTORY_SEPARATOR.$file)) |
| 270 | { |
| 271 | @copy($src.DIRECTORY_SEPARATOR.$file,$dst.DIRECTORY_SEPARATOR.$file); |
| 272 | @chmod($dst.DIRECTORY_SEPARATOR.$file, PRADO_CHMOD); |
| 273 | } |
| 274 | } |
| 275 | else |
| 276 | $this->copyDirectory($src.DIRECTORY_SEPARATOR.$file,$dst.DIRECTORY_SEPARATOR.$file); |
| 277 | } |
| 278 | closedir($folder); |
| 279 | } else { |
| 280 | throw new TInvalidDataValueException('assetmanager_source_directory_invalid', $src); |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | /** |
| 285 | * Publish a tar file by extracting its contents to the assets directory. |
| 286 | * Each tar file must be accomplished with its own MD5 check sum file. |
| 287 | * The MD5 file is published when the tar contents are successfully |
| 288 | * extracted to the assets directory. The presence of the MD5 file |
| 289 | * as published asset assumes that the tar file has already been extracted. |
| 290 | * @param string tar filename |
| 291 | * @param string MD5 checksum for the corresponding tar file. |
| 292 | * @param boolean Wether or not to check the time stamp of the file for publishing. Defaults to false. |
| 293 | * @return string URL path to the directory where the tar file was extracted. |
| 294 | */ |
| 295 | public function publishTarFile($tarfile, $md5sum, $checkTimestamp=false) |
| 296 | { |
| 297 | if(isset($this->_published[$md5sum])) |
| 298 | return $this->_published[$md5sum]; |
| 299 | else if(($fullpath=realpath($md5sum))===false || !is_file($fullpath)) |
| 300 | throw new TInvalidDataValueException('assetmanager_tarchecksum_invalid',$md5sum); |
| 301 | else |
| 302 | { |
| 303 | $dir=$this->hash(dirname($fullpath)); |
| 304 | $fileName=basename($fullpath); |
| 305 | $dst=$this->_basePath.DIRECTORY_SEPARATOR.$dir; |
| 306 | if(!is_file($dst.DIRECTORY_SEPARATOR.$fileName) || $checkTimestamp || $this->getApplication()->getMode()!==TApplicationMode::Performance) |
| 307 | { |
| 308 | if(@filemtime($dst.DIRECTORY_SEPARATOR.$fileName)<@filemtime($fullpath)) |
| 309 | { |
| 310 | $this->copyFile($fullpath,$dst); |
| 311 | $this->deployTarFile($tarfile,$dst); |
| 312 | } |
| 313 | } |
| 314 | return $this->_published[$md5sum]=$this->_baseUrl.'/'.$dir; |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * Extracts the tar file to the destination directory. |
| 320 | * N.B Tar file must not be compressed. |
| 321 | * @param string tar file |
| 322 | * @param string path where the contents of tar file are to be extracted |
| 323 | * @return boolean true if extract successful, false otherwise. |
| 324 | */ |
| 325 | protected function deployTarFile($path,$destination) |
| 326 | { |
| 327 | if(($fullpath=realpath($path))===false || !is_file($fullpath)) |
| 328 | throw new TIOException('assetmanager_tarfile_invalid',$path); |
| 329 | else |
| 330 | { |
| 331 | Prado::using('System.IO.TTarFileExtractor'); |
| 332 | $tar = new TTarFileExtractor($fullpath); |
| 333 | return $tar->extract($destination); |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | } |
| 338 | |
| 339 | ?> |
Note: See TracBrowser
for help on using the browser.
