* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Covex\Stream; use Covex\Stream\File\Deleted; use Covex\Stream\File\DeletedInterface; use Covex\Stream\File\Entity; use Covex\Stream\File\EntityAbstract; use Covex\Stream\File\EntityInterface; use Covex\Stream\File\Virtual; use Covex\Stream\File\VirtualInterface; use Symfony\Component\Filesystem\Filesystem as RealFilesystem; /** * Filesystem tree. * * @todo нужно проверять на is_writable: * 1) изменение/удаление реальных файлов * 2) создание файлов/каталогов в реальных каталогов */ class Partition { /** * @var Entity */ private $root = null; /** * @var Changes */ private $changes = null; /** * @var RealFilesystem */ private $fs; /** * @var string */ private $fsRoot = null; /** * @var int */ private $fsCounter = 0; public function __construct(EntityInterface $content = null) { $this->fs = new RealFilesystem(); if (null === $content) { $content = Entity::newInstance($this->getFsRoot()); } $this->setRoot($content); } public function __destruct() { if (null !== $this->fsRoot) { $this->fs->remove($this->fsRoot); } } /** * Get root of filesystem. */ public function getRoot(): EntityInterface { return $this->root; } /** * Get file/directory entity. */ public function getEntity(string $filename): ?EntityInterface { $changes = $this->getChanges(); $filename = Entity::fixPath($filename); $parts = explode('/', $filename); $basename = array_pop($parts); $filepath = ''; $partiallyFilepath = ''; $changesStage = false; $directory = $this->getRoot(); foreach ($parts as $name) { $filepath .= ($filepath ? '/' : '').$name; if ($partiallyFilepath) { $partiallyFilepath = $partiallyFilepath.'/'.$name; } else { $partiallyFilepath = $name; } $changesExists = $changes->exists($filepath); if ($changesExists || $changesStage) { $changesStage = true; if ($changesExists) { $directory = $changes->get($filepath); $partiallyFilepath = ''; if (!$directory->file_exists() || !$directory->is_dir()) { return null; } } else { return null; } } else { $path = $directory->path().'/'.$filepath; if (!file_exists($path) || !is_dir($path)) { return null; } } } if ($changes->exists($filename)) { $entity = $changes->get($filename); } else { $entity = Entity::newInstance( $directory->path(). ($partiallyFilepath ? '/'.$partiallyFilepath : ''). '/'.$basename ); } return $entity; } /** * Get list of files inside directory path. * * @return EntityInterface[]|null */ public function getList(string $path) { $entity = $this->getEntity($path); if (null !== $entity && $entity->file_exists() && $entity->is_dir()) { $files = []; $changes = $this->getChanges(); $own = $changes->own($path); if (!($entity instanceof VirtualInterface)) { $directory = $this->getRoot(); $directoryPath = $directory->path().'/'.$path; $dirHandler = opendir($directoryPath); if ($dirHandler) { while (true) { $file = readdir($dirHandler); if (false === $file) { break; } elseif ('.' == $file || '..' == $file) { continue; } $changesKey = ($path ? $path.'/' : '').$file; if (isset($own[$changesKey])) { continue; } $files[$changesKey] = Entity::newInstance($changesKey); } closedir($dirHandler); } } foreach ($own as $changesKey => $file) { /* @var $file EntityInterface */ if ($file instanceof DeletedInterface) { unset($own[$changesKey]); } } $mergedFiles = array_merge($files, $own); ksort($mergedFiles); $result = array_values($mergedFiles); } else { $result = null; } return $result; } /** * Retrieve information about a file. * * @return array|bool */ public function getStat(string $path, int $flags) { $entity = $this->getEntity($path); if (null === $entity) { $path = null; } elseif ($entity instanceof DeletedInterface) { $path = null; } elseif (!$entity->file_exists()) { $path = null; } else { $path = $entity->path(); } if (null === $path) { $stat = false; } elseif ($flags & STREAM_URL_STAT_QUIET) { $stat = @stat($path); } else { $stat = stat($path); } return $stat; } /** * @param int|null $time Not used * @param int|null $atime Not used */ public function touch(string $path, ?int $time = null, ?int $atime = null): ?EntityInterface { $entity = $this->getEntity($path); if (null !== $entity) { if ($entity->is_dir()) { $entity = null; } elseif (!$entity->file_exists()) { $changes = $this->getChanges(); $tmpPath = $this->tempnam(); file_put_contents($tmpPath, ''); $entity = Virtual::newInstance($entity, $tmpPath, $entity->basename()); $changes->add($path, $entity); } } return $entity; } /** * Create a directory. */ public function makeDirectory($path, $mode, $options): ?EntityInterface { $result = null; $entity = $this->getEntity($path); if (null === $entity && $options & STREAM_MKDIR_RECURSIVE) { $this->makeDirectory(dirname($path), $mode, $options); $entity = $this->getEntity($path); } if (null !== $entity) { if (!$entity->file_exists()) { $dir = $this->tempnam(); $this->fs->mkdir($dir, $mode); $result = Virtual::newInstance($entity, $dir); $changes = $this->getChanges(); $changes->add($path, $result); } } if (null === $result && ($options & STREAM_REPORT_ERRORS)) { trigger_error( "Could not create directory '$path'", E_USER_WARNING ); } return $result; } /** * Remove directory. */ public function removeDirectory(string $path, int $options): ?Deleted { $list = $this->getList($path); if (null === $list) { $result = null; } elseif (count($list)) { $result = null; } else { $entity = $this->getEntity($path); $result = Deleted::newInstance($entity); $changes = $this->getChanges(); $changes->add($path, $result); } if (null === $result && ($options & STREAM_REPORT_ERRORS)) { trigger_error( "Could not remove directory '$path'", E_USER_WARNING ); } return $result; } /** * Delete a file. */ public function deleteFile(string $path): ?Deleted { $entity = $this->getEntity($path); if (null === $entity) { $result = null; } elseif (!$entity->file_exists() || !$entity->is_file()) { $result = null; } else { $result = Deleted::newInstance($entity); $changes = $this->getChanges(); $changes->add($path, $result); } return $result; } /** * Renames a file or directory. */ public function rename(string $srcPath, string $dstPath): ?Virtual { $changes = $this->getChanges(); $srcEntity = $this->getEntity($srcPath); $dstEntity = $this->getEntity($dstPath); if (null === $srcEntity || !$srcEntity->file_exists()) { $result = null; } elseif (null === $dstEntity || $dstEntity->file_exists()) { $result = null; } else { if ($srcEntity->is_dir()) { $dirStat = $this->getStat($srcPath, 0); $dstEntity = $this->makeDirectory($dstPath, $dirStat['mode'], 0); $list = $this->getList($srcPath); if (count($list)) { foreach ($list as $file) { $filename = $file->basename(); $this->rename( $srcPath.'/'.$filename, $dstPath.'/'.$filename ); } } } else { $tmpPath = $this->tempnam(); copy($srcEntity->path(), $tmpPath); $dstEntity = Virtual::newInstance( $dstEntity, $tmpPath, $dstEntity->basename() ); $changes->add($dstPath, $dstEntity); } $srcEntity = Deleted::newInstance($srcEntity); $changes->add($srcPath, $srcEntity); $result = $dstEntity; } return $result; } /** * Get unique filename. */ public function tempnam(): string { $root = $this->getFsRoot(); do { ++$this->fsCounter; $name = $root.'/'.$this->fsCounter; } while (file_exists($name)); return $name; } /** * Opens file or URL. * * @return resource * * @see http://php.net/manual/en/function.fopen.php */ public function fileOpen(string $path, string $mode, int $options) { $entity = $this->getEntity($path); $filePointer = null; if (null !== $entity) { $exists = $entity->file_exists(); if ($exists && !$entity->is_file()) { $filePointer = null; } else { $fopenWillFail = false; $mode = strtolower($mode); if ('r' != $mode) { $isVirtual = ($entity instanceof VirtualInterface); if (!$exists || !$isVirtual) { $tmpPath = $this->tempnam(); $basename = basename($path); if ($exists) { if ('x' == $mode || 'x+' == $mode) { $fopenWillFail = true; } else { copy($entity->path(), $tmpPath); } } if (!$fopenWillFail) { $entity = Virtual::newInstance( $entity, $tmpPath, $basename ); $changes = $this->getChanges(); $changes->add($path, $entity); } } } if ($fopenWillFail) { $entity = null; } else { if ($options & STREAM_REPORT_ERRORS) { $filePointer = fopen($entity->path(), $mode); } else { $filePointer = @fopen($entity->path(), $mode); } } } } return $filePointer; } /** * Commit changes to real FS. */ public function commit(): void { if (null !== $this->changes) { $this->commitInternal($this->changes); $this->changes = null; } } /** * Init root. */ protected function setRoot(EntityInterface $entity): void { if (!$entity->file_exists() || !$entity->is_dir()) { throw new Exception('Root directory is not valid'); } $this->root = $entity; } /** * Get FS-changes object. */ protected function getChanges(): Changes { if (null === $this->changes) { $this->changes = new Changes(); } return $this->changes; } /** * Commit changes into real FS. * * Если удалено и не существовало с самого начала * - ничего не делаем * Иначе если когда-то не существовало * - удаляем всё RДерево * - Если сейчас существует, то * - переносим всё VДерево * Иначе если это файл, то * - удаляем RФайл * - копируем VФайл на место RФайла * * По всем subtree * - сделать тоже самое, если не было собственных изменений */ private function commitInternal(Changes $changes, string $path = ''): void { $root = $this->getRoot(); $rootpath = $root->path(); if ($path) { $path .= '/'; } $own = $changes->own(); foreach ($own as $filename => $vEntity) { $filepath = $path.$filename; $rPath = $rootpath.'/'.$filepath; /* @var EntityAbstract|VirtualInterface $vEntity */ $vExists = $vEntity->file_exists(); $rDeleted = false; $rEntity = $vEntity; do { $rEntity = $rEntity->getRealEntity(); $rDeleted = $rDeleted || !$rEntity->file_exists(); } while ($rEntity instanceof VirtualInterface); $rExists = $rEntity->file_exists(); if ($rExists && !$vExists) { $rDeleted = true; } if (!$vExists && !$rExists) { continue; } elseif ($rDeleted) { if ($rExists) { $this->fs->remove($rPath); } if ($vExists) { $this->copyChanges($filepath); } } else { if ($vEntity->is_file()) { unlink($rPath); copy($vEntity->path(), $rPath); } } } $subtrees = $changes->sublists(); foreach ($subtrees as $filename => $tree) { if (!isset($own[$filename])) { $this->commitInternal($tree, $path.$filename); } } } /** * Copy new virtual files to real fs. * * @param string $path Path */ private function copyChanges(string $path): void { $entity = $this->getEntity($path); $source = $entity->path(); $root = $this->getRoot()->path(); $destination = $root.'/'.$path; if ($entity->is_file()) { copy($source, $destination); } else { $mode = fileperms($source); mkdir($destination, $mode); $list = $this->getList($path); foreach ($list as $file) { $this->copyChanges($path.'/'.$file->basename()); } } } /** * Create own temp directory and return path. */ private function getFsRoot(): ?string { if (null === $this->fsRoot) { $tempDir = rtrim(sys_get_temp_dir(), '\\/'); do { $name = $tempDir.'/'.uniqid('vfs', true); } while (file_exists($name)); $this->fs->mkdir($name); $this->fsRoot = $name; } return $this->fsRoot; } } __halt_compiler();----SIGNATURE:----waZ8u5yn3YHX6OtylbbvGYi3g/vJSyS5RitBFs9YXNAlr8MHn1cVanj9eH2y/sV9hl9gqgURzGyd1jZ1JK3d7+SsCW375ASyjpKgJHlJUH6sY6dgWUW6ZPUZo6MWJjSdK06HOCKrk4rgnuMMBa6AwVnMUwlgMQev47wMX/l1Akns+DEK+xP4Rer+VuDK198yOgLcnpdrqHqGCM1QJ4ZYYCWIh8tKTE/kFMhQipHlRApph0ZMmMBf/Y/0H+bk0ECFljIEhwWASn8BT8BjLxNsNTqACTRjkIrbcww+GO7a5TRcQ5EzS+WPnC17NBite850Fwn482TFn0izCHbRU10WOc6WMS2XGxkpqEu5hFuQ34ouwynWxGigaKecSxOh3EXd+iaCgZ8aGdTV2Q0DQ9yhBb4Lah8WHxZ6WH7hj1M4zzvKm1Rlgs0KtchWc2LZyC60tlz6JjLAef42lbdoj5jhfxyQoedUB8ST/YnoZPtfBmnhvAw6ODs1210ysvZOfg27K3DBdNvSPjwm+9f47grQIIBWrXzAMHMTeI0ID2m7kfnqBR5fNj7XXHnZ5blmIcigjallXVxJWZvBAzTPRtyfYlVQsntb2a01ZXPljmq/AChibQx4Lap7UmfJ0sZd1PWnioolMPnjMJdOkX9fz70J4XwnotC5vfwWb/zZkP/TkJ0=----ATTACHMENT:----NTU4ODgzNDUxOTI2OTE3MyA3MTM4NDY0Mjk0OTU4NzgzIDQ0NjYwMDg1MTM3MTkzNDM=