[ Index ]

PHP Cross Reference of PHK Manager

title

Body

[close]

/PHK/Virtual/ -> Tree.php (source)

   1  <?php
   2  //=============================================================================
   3  //
   4  // Copyright Francois Laupretre <phk@tekwire.net>
   5  //
   6  //   Licensed under the Apache License, Version 2.0 (the "License");
   7  //   you may not use this file except in compliance with the License.
   8  //   You may obtain a copy of the License at
   9  //
  10  //       http://www.apache.org/licenses/LICENSE-2.0
  11  //
  12  //   Unless required by applicable law or agreed to in writing, software
  13  //   distributed under the License is distributed on an "AS IS" BASIS,
  14  //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15  //   See the License for the specific language governing permissions and
  16  //   limitations under the License.
  17  //
  18  //=============================================================================
  19  /**
  20  * @copyright Francois Laupretre <phk@tekwire.net>
  21  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0
  22  * @category PHK
  23  * @package PHK
  24  *///==========================================================================
  25  
  26  namespace PHK\Virtual {
  27  
  28  if (!class_exists('PHK\Virtual\Tree',false))
  29  {
  30  //============================================================================
  31  /**
  32  * A virtual tree
  33  *
  34  * A virtual tree contains virtual nodes (files and directories)
  35  *
  36  * API status: Private
  37  * Included in the PHK PHP runtime: Yes
  38  * Implemented in the extension: No
  39  *///==========================================================================
  40  
  41  class Tree
  42  {
  43  
  44  public $fspace;        // Associated filespace
  45  
  46  private $edata; // Exported data. Always contains a key for every node, even
  47                  // in creator mode.
  48  
  49  private $nodes;    // Tree nodes (key=path, value=Node object). Contains
  50                  // only the unserialized nodes. In creator mode, contains
  51                  // every node.
  52  
  53  private static $char_to_class=array( 'D' => 'Dir', 'F' => 'File');
  54  
  55  //---
  56  // Create a tree from the edata stored in the PHK archive
  57  
  58  public static function createFromEdata($serial_edata,\PHK\PkgFileSpace $fspace)
  59  {
  60  $tree=new self($fspace);
  61  
  62  $tree->edata=unserialize($serial_edata);
  63  
  64  return $tree;
  65  }
  66  
  67  //---
  68  
  69  public function pathList()
  70  {
  71  return array_keys($this->edata);
  72  }
  73  
  74  //---
  75  
  76  public function pathExists($rpath)
  77  {
  78  return array_key_exists($rpath,$this->edata);
  79  }
  80  
  81  //---
  82  
  83  public function count()
  84  {
  85  return count($this->edata);
  86  }
  87  
  88  //---
  89  
  90  public function walk($method)
  91  {
  92  $args=func_get_args();
  93  array_shift($args);
  94  
  95  foreach($this->pathList() as $path)
  96      {
  97      $node=$this->rlookup($path);
  98      call_user_func_array(array($node,$method),$args);
  99      }
 100  }
 101  
 102  //---
 103  // Reduce the path to a canonical path - suppress '..' and '.' components
 104  // Root=''
 105  // Non-root: /xxx[/yyy...]
 106  
 107  private function realpath($path)
 108  {
 109  $a=explode('/',trim($path,'/'));
 110  $ra=array();
 111  foreach($a as $comp)
 112      {
 113      switch($comp)
 114          {
 115          case '':
 116          case '.':
 117              break;
 118          case '..':
 119              if (count($ra)) array_pop($ra);
 120              break;
 121          default:
 122              $ra[]=$comp;
 123          }
 124      }
 125  if (!count($ra)) return '';
 126  return '/'.implode('/',$ra);
 127  }
 128  
 129  //---
 130  
 131  public function lookup($path,$exception_flag=true)
 132  {
 133  return $this->rlookup(self::realpath($path),$exception_flag);
 134  }
 135  
 136  //---
 137  // Lookup without path canonicalization - faster if self::realpath() has
 138  // already been called
 139  
 140  private function rlookup($path,$exception_flag=true)
 141  {
 142  if (array_key_exists($path,$this->edata))
 143      {
 144      if (!array_key_exists($path,$this->nodes))
 145          {
 146          $edata=$this->edata[$path];
 147          $class=__NAMESPACE__.'\\'.self::$char_to_class[$edata{0}];
 148          $node=$this->nodes[$path]=new $class($path,$this);
 149          $node->import(substr($edata,1));
 150          }
 151      return $this->nodes[$path];
 152      }
 153  
 154  //echo "Lookup failed : <$path> <$rpath>\n";//TRACE
 155  //print_r(array_keys($this->nodes));//TRACE
 156      
 157  if ($exception_flag) throw new \Exception($path.': path not found');
 158  else return null;
 159  }
 160  
 161  //---
 162  
 163  public function lookupFile($path,$exception_flag=true)
 164  {
 165  $f=$this->lookup($path,$exception_flag);
 166  
 167  if ((!is_null($f)) && (!($f instanceof File)))
 168      {
 169      if ($exception_flag) throw new \Exception($path.': No such file');
 170      else return null;
 171      }
 172  
 173  return $f;
 174  }
 175  
 176  //---
 177  
 178  public function displayHeader($html)
 179  {
 180  if ($html) echo '<table border=1 bordercolor="#BBBBBB" cellpadding=3 '
 181      .'cellspacing=0 style="border-collapse: collapse"><tr><th>T</th>'
 182      .'<th>Name</th><th>Size</th><th>Flags</th></tr>';
 183  }
 184  
 185  //---
 186  
 187  public function displayFooter($html)
 188  {
 189  if ($html) echo '</table>';
 190  }
 191  
 192  //---
 193  // $link = wether we display an hyperlink on file names (in HTML mode)
 194  
 195  public function display($link)
 196  {
 197  $html=\PHK\Tools\Util::envIsWeb();
 198  
 199  $this->displayHeader($html);
 200  $this->walk('display',$html,$link);
 201  $this->displayFooter($html);
 202  }
 203  
 204  //---
 205  
 206  public function displayPackages()
 207  {
 208  $html=\PHK\Tools\Util::envIsWeb();
 209  
 210  ob_start();
 211  $this->walk('displayPackage',$html);
 212  $data=ob_get_clean();
 213  
 214  if ($data!=='')
 215      {
 216      $this->displayHeader($html);
 217      $this->walk('displayPackage',$html);
 218      $this->displayFooter($html);
 219      }
 220  }
 221  
 222  //---
 223  
 224  public function dump($base)
 225  {
 226  $this->walk('dump',$base);
 227  }
 228  
 229  //---
 230  // Same as dirname() function except:
 231  // - Always use '/' as separator
 232  // - Returns '' for 1st level paths ('/xxx')
 233  
 234  public static function dirBaseName($path)
 235  {
 236  $dir=preg_replace(',/[^/]*$,','',$path);
 237  $base=preg_replace(',^.*/,','',$path);
 238  return array($dir,$base);
 239  }
 240  
 241  //---
 242  // called from createEmpty() or createFromEdata() only => private
 243  
 244  private function __construct($fspace)
 245  {
 246  $this->fspace=$fspace;
 247  $this->nodes=array();
 248  }
 249  
 250  // <CREATOR> //---------------
 251  
 252  // Check for a list of forbidden chars in node names. Especially important for
 253  // '#*' which can create conflicts in mount points (for subpackages), and ';'
 254  // which is used as separator when exporting the list of dir children.
 255  
 256  public function addNode($path,$node)
 257  {
 258  $path=self::realpath($path);
 259  
 260  if (strpbrk($path,'#*?!&~"|`\^@[]={}$;,<>')!==false)
 261      throw new \Exception("$path: Invalid characters in path");
 262  
 263  if ($path != '')
 264      {
 265      list($dir,$basename)=self::dirBaseName($path);
 266  
 267      $dirnode=$this->rlookup($dir,false);
 268      if (is_null($dirnode)) $dirnode=$this->mkdir($dir);
 269  
 270      if (!($dirnode instanceof Dir))
 271          throw new \Exception("Cannot add node over a non-directory node ($dir)");
 272  
 273      $dirnode->addChild($basename);
 274      }
 275  
 276  // Add the node
 277  
 278  $this->edata[$path]=null;
 279  $this->nodes[$path]=$node;
 280  }
 281  
 282  //---
 283  // Create an empty tree
 284  
 285  public static function createEmpty()
 286  {
 287  $tree=new self(null);
 288  $tree->addNode('',new Dir('',$tree));
 289  
 290  return $tree;
 291  }
 292  
 293  //---
 294  
 295  public function export(\PHK\Build\Creator $phk,$map=null)
 296  {
 297  $edata=array();
 298  $stacker=new \PHK\Build\DataStacker();
 299  
 300  foreach($this->nodes as $path => $node)
 301      {
 302      $edata[$path]=array_search(substr(get_class($node),strlen(__NAMESPACE__)+1),self::$char_to_class)
 303          .$node->export($phk,$stacker,$map);
 304      }
 305  ksort($edata); // To display files in the right order
 306  
 307  return array(serialize($edata),$stacker->data);
 308  }
 309  
 310  //---
 311  // target: absolute target path. '&' is replaced by source basename
 312  // sapath: Absolute source path
 313  // modifiers: array received from \PHK\Build\PSF\CmdOptions
 314  
 315  public function mergeIntoFileTree($target,$sapath,$modifiers)
 316  {
 317  if (!file_exists($sapath))
 318      throw new \Exception($sapath.': Path not found');
 319  
 320  $target=self::realpath(str_replace('&',basename($sapath),$target));
 321  
 322  switch($type=filetype($sapath))
 323      {
 324      case 'file':
 325          if ($target=='') throw new \Exception('Cannot replace root dir with a file');
 326          $this->remove($target);
 327          $this->mkfile($target,\PHK\Tools\Util::readFile($sapath),$modifiers);
 328          break;
 329  
 330      case 'dir':
 331          foreach(\PHK\Tools\Util::scandir($sapath) as $subname)
 332              {
 333              $this->mergeIntoFileTree($target.'/'.$subname,$sapath.'/'.$subname
 334                  ,$modifiers);
 335              }
 336          break;
 337  
 338      default:
 339          \Phool\Display::info("$sapath : Unsupported file type ($type) - Ignored");
 340      }
 341  }
 342  
 343  //---
 344  
 345  private function getSubtree($path)
 346  {
 347  $rpath=self::realpath($path);
 348  
 349  if ($rpath=='') return $this->pathList();
 350  
 351  $result=array();
 352  $prefix=$rpath.'/';
 353  $len=strlen($prefix);
 354  foreach($this->pathList() as $p)
 355      {
 356      if (($p==$rpath)||((strlen($p)>=$len)&&(substr($p,0,$len)==$prefix)))
 357          $result[]=$p;
 358      }
 359  return $result;
 360  }
 361  
 362  //---
 363  
 364  public function modify($path,$modifiers)
 365  {
 366  $path=self::realpath($path);
 367  
 368  foreach ($this->getSubtree($path) as $subpath)
 369      {
 370      $this->lookup($subpath)->modify($modifiers);
 371      }
 372  }
 373  
 374  //---
 375  // If parent dir does not exist, addNode() will call us back to create it,
 376  // and it goes on recursively until the root node is reached.
 377  
 378  public function mkdir($path,$modifiers=array())
 379  {
 380  $rpath=self::realpath($path);
 381  
 382  if (is_null($node=$this->rlookup($rpath,false))) // If node does not exist
 383      {
 384      $node=new Dir($path,$this);
 385      $node->modify($modifiers);
 386      $this->addNode($path,$node);
 387      }
 388  else // If node already exists, check that it is a directory
 389      {
 390      if (($type=$node->type())!='dir')
 391          throw new \Exception("mkdir: $path is already a $type");
 392      }
 393  return $node;
 394  }
 395  
 396  //---
 397  
 398  public function mkfile($path,$data,$modifiers=array())
 399  {
 400  $rpath=self::realpath($path);
 401  
 402  $node=new File($rpath,$this);
 403  $node->setData($data);
 404  $node->modify($modifiers);
 405  
 406  $this->addNode($rpath,$node);
 407  
 408  return $node;
 409  }
 410  
 411  //---
 412  
 413  public function remove($path)
 414  {
 415  $rpath=self::realpath($path);
 416  if ($rpath=='') throw new \Exception('Cannot remove root directory');
 417  
 418  if (is_null($this->rlookup($rpath,false))) return; // Path does not exist
 419  
 420  foreach($this->getSubtree($rpath) as $p)
 421      {
 422      unset($this->nodes[$p]);
 423      unset($this->edata[$p]);
 424      }
 425  
 426  list($dir,$name)=self::dirBaseName($rpath);
 427  $this->rlookup($dir)->removeChild($name);
 428  }
 429  
 430  // </CREATOR> //---------------
 431  
 432  //---
 433  } // End of class
 434  //===========================================================================
 435  } // End of class_exists
 436  //===========================================================================
 437  } // End of namespace
 438  //===========================================================================
 439  ?>


Generated: Thu Jun 4 18:33:15 2015 Cross-referenced by PHPXref 0.7.1