Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -0,0 +1,96 @@
<?php
namespace Xentral\Components\Filesystem\Adapter;
interface AdapterInterface extends ReaderAdapterInterface
{
/**
* Creates a new file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function write($path, $contents, array $config = []);
/**
* Creates a new file using a stream
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @return bool
*/
public function writeStream($path, $resource, array $config = []);
/**
* Updates an existing file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function update($path, $contents, array $config = []);
/**
* Updates an existing file using a stream
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @return bool
*/
public function updateStream($path, $resource, array $config = []);
/**
* Renames a file
*
* @param string $path
* @param string $newpath
*
* @return bool
*/
public function rename($path, $newpath);
/**
* Copies a file to new location
*
* @param string $path
* @param string $newpath
*
* @return bool
*/
public function copy($path, $newpath);
/**
* Deletes a single file
*
* @param string $path
*
* @return bool
*/
public function delete($path);
/**
* Deletes a directory and its contents
*
* @param string $directory
*
* @return bool
*/
public function deleteDir($directory);
/**
* @param string $directory
* @param array $config
*
* @return bool
*/
public function createDir($directory, array $config = []);
}
@@ -0,0 +1,157 @@
<?php
namespace Xentral\Components\Filesystem\Adapter;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
final class FtpConfig
{
/** @var string $hostname */
private $hostname;
/** @var string $username */
private $username;
/** @var string $password */
private $password;
/** @var string $rootDir */
private $rootDir;
/** @var int $port */
private $port;
/** @var int $timeout */
private $timeout;
/** @var bool $passive */
private $passive;
/** @var bool $ssl */
private $ssl;
/**
* @param string $hostname
* @param string $username
* @param string $password
* @param string $rootDir
* @param int $port
* @param int $timeout
* @param bool $passive
* @param bool $ssl
*/
public function __construct(
$hostname,
$username,
$password,
$rootDir = '/',
$port = 21,
$timeout = 30,
$passive = true,
$ssl = false
) {
if (empty($hostname)) {
throw new InvalidArgumentException('Hostname is empty.');
}
if (empty($username)) {
throw new InvalidArgumentException('Username is empty.');
}
if (empty($password)) {
throw new InvalidArgumentException('Password is empty.');
}
if (empty($rootDir)) {
throw new InvalidArgumentException('Root dir is empty.');
}
$this->hostname = (string)$hostname;
$this->username = (string)$username;
$this->password = (string)$password;
$this->rootDir = (string)$rootDir;
$this->port = (int)$port;
$this->timeout = (int)$timeout;
$this->passive = (bool)$passive;
$this->ssl = (bool)$ssl;
}
/**
* @return array
*/
public function toArray()
{
return [
'host' => $this->hostname,
'username' => $this->username,
'password' => $this->password,
'root' => $this->rootDir,
'port' => $this->port,
'timeout' => $this->timeout,
'passive' => $this->passive,
'ssl' => $this->ssl,
'recurseManually' => true,
];
}
/**
* @return string
*/
public function getHostname()
{
return $this->hostname;
}
/**
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* @return null
*/
public function getRootDir()
{
return $this->rootDir;
}
/**
* @return int
*/
public function getPort()
{
return $this->port;
}
/**
* @return int
*/
public function getTimeout()
{
return $this->timeout;
}
/**
* @return bool
*/
public function isPassive()
{
return $this->passive;
}
/**
* @return bool
*/
public function isSsl()
{
return $this->ssl;
}
}
@@ -0,0 +1,355 @@
<?php
namespace Xentral\Components\Filesystem\Adapter;
use League\Flysystem\AdapterInterface as LeagueAdapterInterface;
use League\Flysystem\Config as LeagueConfig;
use League\Flysystem\Util;
use League\Flysystem\Util\ContentListingFormatter;
use Xentral\Components\Filesystem\PathInfo;
final class LeagueAdapterWrapper implements AdapterInterface
{
/** @var LeagueAdapterInterface $league */
private $league;
/** @var bool $caseSensitive */
private $caseSensitive = true;
/**
* @param LeagueAdapterInterface $league
*/
public function __construct(LeagueAdapterInterface $league)
{
$this->league = $league;
}
/**
* @param string $path
*
* @return bool
*/
public function has($path)
{
$path = $this->normalizePath($path);
return (bool)$this->league->has($path) !== false;
}
/**
* @param string $path
*
* @return PathInfo|false
*/
public function getInfo($path)
{
$path = $this->normalizePath($path);
$metainfo = $this->league->getMetadata($path);
if (!$metainfo) {
return false;
}
$directory = Util::dirname($path);
$metainfo['path'] = $path;
$formatter = new ContentListingFormatter($directory, false, $this->caseSensitive);
$contents = $formatter->formatListing([$metainfo]);
if (count($contents) !== 1) {
return false;
}
return new PathInfo($contents[0]);
}
/**
* @param string $path
*
* @return string|false
*/
public function read($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getMetadata($path);
if ($meta['type'] === self::TYPE_DIR) {
return false;
}
$result = $this->league->read($path);
if (!$result || !isset($result['contents'])) {
return false;
}
return $result['contents'];
}
/**
* @param string $path
*
* @return resource|false
*/
public function readStream($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getMetadata($path);
if ($meta['type'] === self::TYPE_DIR) {
return false;
}
$result = $this->league->readStream($path);
if (!$result || !isset($result['stream'])) {
return false;
}
return $result['stream'];
}
/**
* @param string $directory
* @param bool $recursive
*
* @return array
*/
public function listContents($directory = '', $recursive = false)
{
$directory = $this->normalizePath($directory);
$contents = $this->getLeagueAdapter()->listContents($directory, $recursive);
$formatter = new ContentListingFormatter($directory, $recursive, $this->caseSensitive);
return $formatter->formatListing($contents);
}
/**
* @param string $path
*
* @return array|false
*/
public function getMetadata($path)
{
$path = $this->normalizePath($path);
return $this->league->getMetadata($path);
}
/**
* @param string $path
*
* @return string|false
*/
public function getType($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getMetadata($path);
if ($meta === false || !isset($meta['type'])) {
return false;
}
return $meta['type'];
}
/**
* @param string $path
*
* @return int|false
*/
public function getSize($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getSize($path);
if ($meta === false || !isset($meta['size'])) {
return false;
}
return (int)$meta['size'];
}
/**
* @param string $path
*
* @return int|false
*/
public function getTimestamp($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getTimestamp($path);
if ($meta === false || !isset($meta['timestamp'])) {
return false;
}
return (int)$meta['timestamp'];
}
/**
* @param string $path
*
* @return string|false
*/
public function getMimetype($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getMimetype($path);
if ($meta === false || !isset($meta['mimetype'])) {
return false;
}
if ($meta['type'] === 'dir') {
return 'directory';
}
return $meta['mimetype'];
}
/**
* Creates a new file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function write($path, $contents, array $config = [])
{
$path = $this->normalizePath($path);
return $this->league->write($path, $contents, new LeagueConfig($config)) !== false;
}
/**
* Creates a new file using a stream
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @return bool
*/
public function writeStream($path, $resource, array $config = [])
{
$path = $this->normalizePath($path);
return $this->league->writeStream($path, $resource, new LeagueConfig($config)) !== false;
}
/**
* Updates an existing file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function update($path, $contents, array $config = [])
{
$path = $this->normalizePath($path);
return $this->league->update($path, $contents, new LeagueConfig($config)) !== false;
}
/**
* Updates an existing file using a stream
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @return bool
*/
public function updateStream($path, $resource, array $config = [])
{
$path = $this->normalizePath($path);
return $this->league->updateStream($path, $resource, new LeagueConfig($config)) !== false;
}
/**
* Renames a file
*
* @param string $path
* @param string $newpath
*
* @return bool
*/
public function rename($path, $newpath)
{
$path = $this->normalizePath($path);
$newpath = $this->normalizePath($newpath);
return $this->league->rename($path, $newpath);
}
/**
* Copies a file to new location
*
* @param string $path
* @param string $newpath
*
* @return bool
*/
public function copy($path, $newpath)
{
$path = $this->normalizePath($path);
$newpath = $this->normalizePath($newpath);
return $this->league->copy($path, $newpath);
}
/**
* Deletes a single file
*
* @param string $path
*
* @return bool
*/
public function delete($path)
{
$path = $this->normalizePath($path);
return $this->league->delete($path);
}
/**
* Deletes a directory and its contents
*
* @param string $directory
*
* @return bool
*/
public function deleteDir($directory)
{
$directory = $this->normalizePath($directory);
return $this->league->deleteDir($directory);
}
/**
* @param string $directory
* @param array $config
*
* @return bool
*/
public function createDir($directory, array $config = [])
{
$directory = $this->normalizePath($directory);
return $this->league->createDir($directory, new LeagueConfig($config)) !== false;
}
/**
* @return LeagueAdapterInterface
*/
public function getLeagueAdapter()
{
return $this->league;
}
/**
* @param string $path
*
* @return string
*/
public function normalizePath($path)
{
return Util::normalizePath($path);
}
}
@@ -0,0 +1,82 @@
<?php
namespace Xentral\Components\Filesystem\Adapter;
use Xentral\Components\Filesystem\PathInfo;
interface ReaderAdapterInterface
{
const TYPE_DIR = 'dir';
const TYPE_FILE = 'file';
/**
* @param string $path
*
* @return bool
*/
public function has($path);
/**
* @param string $path
*
* @return PathInfo|false
*/
public function getInfo($path);
/**
* @param string $path
*
* @return string|false
*/
public function read($path);
/**
* @param string $path
*
* @return resource|false
*/
public function readStream($path);
/**
* @param string $directory
* @param bool $recursive
*
* @return array
*/
public function listContents($directory = '', $recursive = false);
/**
* @param string $path
*
* @return string|false [dir|file]
*/
public function getType($path);
/**
* @param string $path
*
* @return array|false
*/
public function getMetadata($path);
/**
* @param string $path
*
* @return int|false
*/
public function getSize($path);
/**
* @param string $path
*
* @return int|false
*/
public function getTimestamp($path);
/**
* @param string $path
*
* @return string|false
*/
public function getMimetype($path);
}
@@ -0,0 +1,28 @@
<?php
namespace Xentral\Components\Filesystem;
use Xentral\Core\DependencyInjection\ContainerInterface;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'FilesystemFactory' => 'onInitFilesystemFactory',
];
}
/**
* @param ContainerInterface $container
*
* @return FilesystemFactory
*/
public static function onInitFilesystemFactory(ContainerInterface $container)
{
return new FilesystemFactory($container->get('Database'));
}
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use RuntimeException;
class DirNotFoundException extends RuntimeException implements FilesystemExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use RuntimeException;
class FileExistsException extends RuntimeException implements FilesystemExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use RuntimeException;
class FileNotFoundException extends RuntimeException implements FilesystemExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use RuntimeException;
class FilesystemException extends RuntimeException implements FilesystemExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use Xentral\Core\Exception\ComponentExceptionInterface;
interface FilesystemExceptionInterface extends ComponentExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use InvalidArgumentException as SplInvalidArgumentException;
class InvalidArgumentException extends SplInvalidArgumentException implements FilesystemExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use LogicException;
class RootViolationException extends LogicException implements FilesystemExceptionInterface
{
}
@@ -0,0 +1,429 @@
<?php
namespace Xentral\Components\Filesystem;
use Xentral\Components\Filesystem\Adapter\AdapterInterface;
use Xentral\Components\Filesystem\Exception\DirNotFoundException;
use Xentral\Components\Filesystem\Exception\FileExistsException;
use Xentral\Components\Filesystem\Exception\FileNotFoundException;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
use Xentral\Components\Filesystem\Exception\RootViolationException;
final class Filesystem implements FilesystemInterface
{
/** @var AdapterInterface $adapter */
private $adapter;
/**
* @param AdapterInterface $adapter
*/
public function __construct(AdapterInterface $adapter)
{
$this->adapter = $adapter;
}
/**
* Checks if file or directory exists
*
* @param string $path
*
* @return bool
*/
public function has($path)
{
return $this->adapter->has($path);
}
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return PathInfo|false
*/
public function getInfo($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->getInfo($path);
}
/**
* List directory contents
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listContents($directory = '', $recursive = false)
{
$result = [];
$contents = $this->adapter->listContents($directory, $recursive);
foreach ($contents as $metainfo) {
$result[] = PathInfo::fromMeta($metainfo);
}
return $result;
}
/**
* Lists only directories
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listDirs($directory = '', $recursive = false)
{
$result = [];
$contents = $this->adapter->listContents($directory, $recursive);
foreach ($contents as $metainfo) {
if ($metainfo['type'] === 'dir') {
$result[] = PathInfo::fromMeta($metainfo);
}
}
return $result;
}
/**
* Lists only files
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listFiles($directory = '', $recursive = false)
{
$result = [];
$contents = $this->adapter->listContents($directory, $recursive);
foreach ($contents as $metainfo) {
if ($metainfo['type'] === 'file') {
$result[] = PathInfo::fromMeta($metainfo);
}
}
return $result;
}
/**
* List only paths as strings
*
* @param string $directory
* @param bool $recursive
*
* @return array|string[]
*/
public function listPaths($directory = '', $recursive = false)
{
$contents = $this->adapter->listContents($directory, $recursive);
return array_column($contents, 'path');
}
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return string [dir|file]
*/
public function getType($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->getType($path);
}
/**
* Gets the filesize
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return int|false
*/
public function getSize($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->getSize($path);
}
/**
* Gets the timestamp from last update
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function getTimestamp($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->getTimestamp($path);
}
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function getMimetype($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->getMimetype($path);
}
/**
* Read file content
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function read($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->read($path);
}
/**
* Read file content
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return resource|false
*/
public function readStream($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->readStream($path);
}
/**
* Writes to a new file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @throws FileExistsException
*
* @return bool
*/
public function write($path, $contents, array $config = [])
{
if ($this->has($path)) {
throw new FileExistsException(sprintf('File "%s" exists already.', $path));
}
return $this->adapter->write($path, $contents, $config);
}
/**
* Writes to a new file
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @throws InvalidArgumentException
* @throws FileExistsException
*
* @return bool
*/
public function writeStream($path, $resource, array $config = [])
{
if ($this->has($path)) {
throw new FileExistsException(sprintf('File "%s" exists already.', $path));
}
if (!is_resource($resource)) {
throw new InvalidArgumentException('Second parameter must be a resource.');
}
return $this->adapter->writeStream($path, $resource, $config);
}
/**
* Creates a file or updates the file contents
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function put($path, $contents, array $config = [])
{
if (!$this->has($path)) {
return $this->adapter->write($path, $contents, $config);
}
return $this->adapter->update($path, $contents, $config);
}
/**
* Creates a file or updates the file contents
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @throws InvalidArgumentException
*
* @return bool
*/
public function putStream($path, $resource, array $config = [])
{
if (!is_resource($resource)) {
throw new InvalidArgumentException('Second parameter must be a resource.');
}
if (!$this->has($path)) {
return $this->adapter->writeStream($path, $resource, $config);
}
return $this->adapter->updateStream($path, $resource, $config);
}
/**
* Renames a file
*
* @param string $path
* @param string $newpath
*
* @throws FileExistsException
* @throws FileNotFoundException
*
* @return bool
*/
public function rename($path, $newpath)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
if ($this->has($newpath)) {
throw new FileExistsException(sprintf('File "%s" exists already.', $newpath));
}
return $this->adapter->rename($path, $newpath);
}
/**
* Copies a file to new location
*
* @param string $path
* @param string $newpath
*
* @throws FileExistsException
* @throws FileNotFoundException
*
* @return bool
*/
public function copy($path, $newpath)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
if ($this->has($newpath)) {
throw new FileExistsException(sprintf('File "%s" exists already.', $newpath));
}
return $this->adapter->copy($path, $newpath);
}
/**
* Deletes a single file
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return bool
*/
public function delete($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->delete($path);
}
/**
* Deletes a directory and all its contents
*
* @param string $dirname
*
* @throws DirNotFoundException
* @throws RootViolationException
*
* @return bool
*/
public function deleteDir($dirname)
{
$info = $this->adapter->getInfo($dirname);
if (!$info) {
throw new DirNotFoundException(sprintf('Directory "%s" not found.', $dirname));
}
if ($info->getPath() === '') {
throw new RootViolationException('Root directory can not be deleted.');
}
return $this->adapter->deleteDir($dirname);
}
/**
* Creates a directory
*
* @param string $dirname
* @param array $config
*
* @return bool
*/
public function createDir($dirname, array $config = [])
{
return $this->adapter->createDir($dirname, $config);
}
/**
* @return AdapterInterface
*/
public function getAdapter()
{
return $this->adapter;
}
}
@@ -0,0 +1,87 @@
<?php
namespace Xentral\Components\Filesystem;
use Exception;
use Xentral\Components\Database\Database;
use Xentral\Components\Filesystem\Adapter\FtpConfig;
use Xentral\Components\Filesystem\Adapter\LeagueAdapterWrapper;
use Xentral\Components\Filesystem\Exception\FilesystemException;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
use Xentral\Components\Filesystem\Flysystem\FtpAdapterDecorator;
use Xentral\Components\Filesystem\Flysystem\LocalAdapterDecorator;
final class FilesystemFactory
{
/** @var Database $db */
private $db;
/**
* @param Database $database
*/
public function __construct(Database $database)
{
$this->db = $database;
}
/**
* @param string $root Absolute path
* @param array $config
*
* @return FilesystemInterface
*/
public function createLocal($root, array $config = [])
{
try {
$writeFlags = isset($config['write_flags']) ? $config['write_flags'] : LOCK_EX;
$linkHandling = isset($config['link_handling']) ? $config['link_handling'] : LocalAdapterDecorator::SKIP_LINKS;
$permissions = isset($config['permissions']) ? $config['permissions'] : [];
$leagueLocalAdapter = new LocalAdapterDecorator($root, $writeFlags, $linkHandling, $permissions);
$leagueAdapterWrapper = new LeagueAdapterWrapper($leagueLocalAdapter);
return new Filesystem($leagueAdapterWrapper);
//
} catch (Exception $e) {
throw new FilesystemException($e->getMessage(), (int)$e->getCode(), $e);
}
}
/**
* @param FtpConfig $config
*
* @return FilesystemInterface
*/
public function createFtp(FtpConfig $config)
{
try {
$leagueFtpAdapter = new FtpAdapterDecorator($config->toArray());
$leagueAdapterWrapper = new LeagueAdapterWrapper($leagueFtpAdapter);
return new Filesystem($leagueAdapterWrapper);
//
} catch (Exception $e) {
throw new FilesystemException($e->getMessage(), (int)$e->getCode(), $e);
}
}
/**
* @param FilesystemInterface $filesystem
* @param int $syncId
*
* @return FilesystemSyncCache
*/
public function createSync(FilesystemInterface $filesystem, $syncId)
{
try {
if (get_class($filesystem) === FilesystemSyncCache::class) {
throw new InvalidArgumentException('FilesystemSyncWrapper can not wrap it self.');
}
return new FilesystemSyncCache($this->db, $filesystem, $syncId);
//
} catch (Exception $e) {
throw new FilesystemException($e->getMessage(), (int)$e->getCode(), $e);
}
}
}
@@ -0,0 +1,256 @@
<?php
namespace Xentral\Components\Filesystem;
use Xentral\Components\Filesystem\Adapter\AdapterInterface;
use Xentral\Components\Filesystem\Exception\DirNotFoundException;
use Xentral\Components\Filesystem\Exception\FileExistsException;
use Xentral\Components\Filesystem\Exception\FileNotFoundException;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
use Xentral\Components\Filesystem\Exception\RootViolationException;
interface FilesystemInterface
{
/**
* Checks if file or directory exists
*
* @param string $path
*
* @return bool
*/
public function has($path);
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return PathInfo|false
*/
public function getInfo($path);
/**
* List directory contents
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listContents($directory = '', $recursive = false);
// public function filterContents(array $filter = [], $directory = '', $recursive = false); // @todo
//
// // @todo ExtendedLocal
// public function isReadable();
// public function isWriteable();
// public function getOwner();
// public function getGroup();
/**
* Lists only directories
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listDirs($directory = '', $recursive = false);
/**
* Lists only files
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listFiles($directory = '', $recursive = false);
/**
* List only paths as strings
*
* @param string $directory
* @param bool $recursive
*
* @return array|string[]
*/
public function listPaths($directory = '', $recursive = false);
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return string [dir|file]
*/
public function getType($path);
/**
* Gets the filesize
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return int|false
*/
public function getSize($path);
/**
* Gets the timestamp from last update
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function getTimestamp($path);
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function getMimetype($path);
/**
* Read file content
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function read($path);
/**
* Read file content
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return resource|false
*/
public function readStream($path);
/**
* Writes to a new file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @throws FileExistsException
*
* @return bool
*/
public function write($path, $contents, array $config = []);
/**
* Writes to a new file
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @throws InvalidArgumentException
* @throws FileExistsException
*
* @return bool
*/
public function writeStream($path, $resource, array $config = []);
/**
* Creates a file or updates the file contents
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function put($path, $contents, array $config = []);
/**
* Creates a file or updates the file contents
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @throws InvalidArgumentException
*
* @return bool
*/
public function putStream($path, $resource, array $config = []);
/**
* Renames a file
*
* @param string $path
* @param string $newpath
*
* @throws FileExistsException
* @throws FileNotFoundException
*
* @return bool
*/
public function rename($path, $newpath);
/**
* Copies a file to new location
*
* @param string $path
* @param string $newpath
*
* @throws FileExistsException
* @throws FileNotFoundException
*
* @return bool
*/
public function copy($path, $newpath);
/**
* Deletes a single file
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return bool
*/
public function delete($path);
/**
* Deletes a directory and all its contents
*
* @param string $dirname
*
* @throws DirNotFoundException
* @throws RootViolationException
*
* @return bool
*/
public function deleteDir($dirname);
/**
* Creates a directory
*
* @param string $dirname
* @param array $config
*
* @return bool
*/
public function createDir($dirname, array $config = []);
/**
* @return AdapterInterface
*/
public function getAdapter();
}
@@ -0,0 +1,446 @@
<?php
namespace Xentral\Components\Filesystem;
use Xentral\Components\Database\Database;
use Xentral\Components\Filesystem\Exception\FileNotFoundException;
use Xentral\Components\Filesystem\Exception\FilesystemException;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
/**
* @todo Datenbank anlegen
*/
final class FilesystemSyncCache implements FilesystemInterface
{
/** @var Database $db */
private $db;
/** @var FilesystemInterface $fs */
private $fs;
/** @var int $syncId */
private $syncId;
/**
* @param Database $database
* @param FilesystemInterface $filesystem
* @param int $syncId
*/
public function __construct(Database $database, FilesystemInterface $filesystem, $syncId)
{
if ((int)$syncId <= 0) {
throw new InvalidArgumentException(sprintf('Sync-ID "%s" is invalid.', $syncId));
}
$this->db = $database;
$this->fs = $filesystem;
$this->syncId = (int)$syncId;
}
/**
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listChanges($directory = '', $recursive = false)
{
$result = $this->fs->listContents($directory, $recursive);
$location = PathUtil::normalizePath($directory);
$cache = $this->readCache($location, $recursive);
foreach ($result as $item) {
$path = $item->getPath();
// Ignore directories; only files are nessesary for syncing
if ($item->isDir()) {
continue;
}
// Defaults
$item->set('missing', false);
$item->set('modified', null);
if (!array_key_exists($path, $cache)) {
$item->set('missing', true);
continue;
}
if ((int)$cache[$path]['size'] !== (int)$item->getSize()) {
$item->set('modified', true);
continue;
}
if ((int)$cache[$path]['timestamp'] !== (int)$item->getTimestamp()) {
$item->set('modified', true);
continue;
}
$item->set('modified', false);
}
return $result;
}
/**
* Lists deleted files
*
* Lists files that are present in cache but does not exist on the filesystem any more.
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listDeleted($directory = '', $recursive = false)
{
$paths = $this->fs->listPaths($directory, $recursive);
$location = PathUtil::normalizePath($directory);
$cache = $this->readCache($location, $recursive);
foreach ($cache as $path => $cacheItem) {
if (in_array($path, $paths, true)) {
unset($cache[$path]);
}
}
$result = [];
foreach ($cache as $cacheItem) {
$pathinfo = PathUtil::pathinfo($cacheItem['path']);
$result[] = new PathInfo(array_merge($cacheItem, $pathinfo));
}
return $result;
}
/**
* @inheritdoc
*/
public function has($path)
{
return $this->fs->has($path);
}
/**
* @inheritdoc
*/
public function getInfo($path)
{
return $this->fs->getInfo($path);
}
/**
* @inheritdoc
*/
public function getType($path)
{
return $this->fs->getType($path);
}
/**
* @inheritdoc
*/
public function getSize($path)
{
return $this->fs->getSize($path);
}
/**
* @inheritdoc
*/
public function getTimestamp($path)
{
return $this->fs->getTimestamp($path);
}
/**
* @inheritdoc
*/
public function getMimetype($path)
{
return $this->fs->getMimetype($path);
}
/**
* @inheritdoc
*/
public function listContents($directory = '', $recursive = false)
{
return $this->fs->listContents($directory, $recursive);
}
/**
* @inheritdoc
*/
public function listDirs($directory = '', $recursive = false)
{
return $this->fs->listDirs($directory, $recursive);
}
/**
* @inheritdoc
*/
public function listFiles($directory = '', $recursive = false)
{
return $this->fs->listFiles($directory, $recursive);
}
/**
* @inheritdoc
*/
public function listPaths($directory = '', $recursive = false)
{
return $this->fs->listPaths($directory, $recursive);
}
/**
* @inheritdoc
*/
public function read($path)
{
$result = $this->fs->read($path);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function readStream($path)
{
$result = $this->fs->readStream($path);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function write($path, $contents, array $config = [])
{
$result = $this->fs->write($path, $contents, $config);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function writeStream($path, $resource, array $config = [])
{
$result = $this->fs->writeStream($path, $resource, $config);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function put($path, $contents, array $config = [])
{
$result = $this->fs->put($path, $contents, $config);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function putStream($path, $resource, array $config = [])
{
$result = $this->fs->putStream($path, $resource, $config);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function delete($path)
{
$result = $this->fs->delete($path);
$this->deleteCachePath($path);
return $result;
}
/**
* Deletes a single file, but without Exception if path does not exist.
*
* @param string $path
*
* @return bool
*/
public function softDelete($path)
{
$result = false;
try {
$this->deleteCachePath($path);
$result = $this->fs->delete($path);
} catch (FileNotFoundException $e) {
// nope - its soft
}
return $result;
}
/**
* @inheritdoc
*/
public function deleteDir($dirname)
{
$result = $this->fs->deleteDir($dirname);
$this->deleteCacheDir($dirname);
return $result;
}
/**
* @inheritdoc
*/
public function createDir($dirname, array $config = [])
{
return $this->fs->createDir($dirname, $config);
}
/**
* @inheritdoc
*/
public function rename($path, $newpath)
{
$result = $this->fs->rename($path, $newpath);
$this->deleteCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function copy($path, $newpath)
{
$result = $this->fs->copy($path, $newpath);
$this->updateCachePath($newpath);
return $result;
}
/**
* @inheritdoc
*/
public function getAdapter()
{
return $this->fs->getAdapter();
}
/**
* @param string $path
* @param bool $recursive
*
* @return array
*/
private function readCache($path, $recursive = false)
{
$this->ensureDependencies();
$path = $this->normalizePath($path);
return $this->db->fetchAssoc(
'SELECT f.path, f.dirname, f.type, f.size, f.updated_at AS timestamp ' .
'FROM sync_files AS f ' .
'WHERE f.sync_id = :sync_id AND f.dirname LIKE :path_prefix',
[
'sync_id' => $this->syncId,
'path_prefix' => $recursive === true ? $path . '%' : $path,
]
);
}
/**
* @param string $path
*
* @return void
*/
private function updateCachePath($path)
{
$this->ensureDependencies();
$info = $this->fs->getInfo($path);
$this->db->perform(
'REPLACE INTO sync_files (sync_id, `path`, dirname, type, size, updated_at) ' .
'VALUES (:sync_id, :path, :dirname, :type, :size, :updated_at)',
[
'sync_id' => $this->syncId,
'path' => $info->getPath(),
'dirname' => $info->getDir(),
'type' => $info->getType(),
'size' => (int)$info->getSize(),
'updated_at' => (int)$info->getTimestamp(),
]
);
}
/**
* @param string $path
*
* @return int Deleted row count
*/
private function deleteCachePath($path)
{
$this->ensureDependencies();
$path = $this->normalizePath($path);
return $this->db->fetchAffected(
'DELETE FROM sync_files WHERE sync_id = :sync_id AND `path` = :path',
['sync_id' => $this->syncId, 'path' => $path]
);
}
/**
* @param string $dirname
*
* @return int Deleted row count
*/
private function deleteCacheDir($dirname)
{
$this->ensureDependencies();
$dirname = $this->normalizePath($dirname);
return $this->db->fetchAffected(
'DELETE FROM sync_files WHERE sync_id = :sync_id AND `dirname` LIKE :dirname',
['sync_id' => $this->syncId, 'dirname' => $dirname . '%']
);
}
/**
* @param string $path
*
* @return string
*/
private function normalizePath($path)
{
return PathUtil::normalizePath($path);
}
/**
* @throws FilesystemException
*
* @return void
*/
private function ensureDependencies()
{
if ($this->db === null || $this->syncId === null) {
throw new FilesystemException('Can not continue. Required dependencies are missing.');
}
}
}
@@ -0,0 +1,52 @@
<?php
namespace Xentral\Components\Filesystem\Flysystem;
use League\Flysystem\Adapter\Ftp;
final class FtpAdapterDecorator extends Ftp
{
/**
* @inheritdoc
*/
public function getMetadata($path)
{
$metadata = parent::getMetadata($path);
if ($metadata === false) {
return false; // File does not exist
}
if ($metadata['type'] === 'dir') {
$metadata['timestamp'] = null; // ftp_mdtm() does not work with directories.
return $metadata;
}
if ($metadata['timestamp'] === null) {
$data = $this->getTimestamp($path);
$metadata['timestamp'] = $data !== false && isset($data['timestamp']) ? $data['timestamp'] : null;
}
return $metadata;
}
/**
* @inheritdoc
*/
protected function normalizeListing(array $listing, $prefix = '')
{
$result = parent::normalizeListing($listing, $prefix);
foreach ($result as &$item) {
if ($item['type'] === 'dir') {
$item['timestamp'] = null; // ftp_mdtm() does not work with directories.
continue;
}
if (!isset($item['timestamp'])) {
$data = $this->getTimestamp($item['path']);
$item['timestamp'] = $data !== false && isset($data['timestamp']) ? $data['timestamp'] : null;
}
}
return $result;
}
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Flysystem;
use League\Flysystem\Adapter\Local;
final class LocalAdapterDecorator extends Local
{
}
+221
View File
@@ -0,0 +1,221 @@
<?php
namespace Xentral\Components\Filesystem;
use League\Flysystem\Util;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
final class PathInfo
{
const TYPE_FILE = 'file';
const TYPE_DIR = 'dir';
/** @var array $readonlyProperties */
private static $readonlyProperties = [
'type',
'path',
'dirname',
'filename',
'basename',
'extension',
'timestamp',
'size',
];
/** @var array $data */
private $data;
/**
* @param array $data
*/
public function __construct(array $data)
{
// required
$data['type'] = (string)$data['type'];
$data['path'] = (string)$data['path'];
$data['dirname'] = (string)$data['dirname'];
$data['filename'] = (string)$data['filename'];
$data['basename'] = (string)$data['basename'];
// optional
$data['extension'] = !empty($data['extension']) ? (string)$data['extension'] : null;
$data['timestamp'] = is_numeric($data['timestamp']) ? (int)$data['timestamp'] : null;
$data['size'] = is_numeric($data['size']) ? (int)$data['size'] : null;
$this->data = $data;
}
/**
* @param array $metainfo
*
* @return PathInfo
*/
public static function fromMeta(array $metainfo = [])
{
$pathinfo = Util::pathinfo($metainfo['path']);
return new PathInfo(array_merge($pathinfo, $metainfo));
}
/**
* @return array
*/
public function toArray()
{
return $this->data;
}
/**
* @return bool
*/
public function isFile()
{
return $this->getType() === self::TYPE_FILE;
}
/**
* @return bool
*/
public function isDir()
{
return $this->getType() === self::TYPE_DIR;
}
/**
* @return string [dir|file]
*/
public function getType()
{
return $this->data['type'];
}
/**
* @return string Filename with extension and without path
*/
public function getBasename()
{
return $this->data['basename'];
}
/**
* @return string Filename without extension and without path
*/
public function getFilename()
{
return $this->data['filename'];
}
/**
* @return string|null File extension or null if directory
*/
public function getExtension()
{
return $this->data['extension'];
}
/**
* @return string Path without filename
*/
public function getDir()
{
return $this->data['dirname'];
}
/**
* Relativer Pfad zum Mountpoint
*
* @return string Path with filename
*/
public function getPath()
{
return $this->data['path'];
}
/**
* @return int|null File size or null if not available
*/
public function getSize()
{
return $this->data['size'];
}
/**
* @return int|null Last updated timestamp or null if not available
*/
public function getTimestamp()
{
return $this->data['timestamp'];
}
/**
* @param string $name
*
* @return bool
*/
public function has($name)
{
return isset($this->data[(string)$name]);
}
/**
* @param string $name
*
* @return mixed
*/
public function get($name)
{
if ($this->has($name)) {
return $this->data[(string)$name];
}
return null;
}
/**
* @param string $name
* @param mixed $value
*
* @throws InvalidArgumentException
*
* @return void
*/
public function set($name, $value)
{
if (in_array((string)$name, self::$readonlyProperties, true)) {
throw new InvalidArgumentException(sprintf('Property "%s" is readonly.', $name));
}
$this->data[(string)$name] = $value;
}
/**
* @param string $name
*
* @return bool
*/
public function __isset($name)
{
return $this->has($name);
}
/**
* @param string $name
*
* @return mixed
*/
public function __get($name)
{
return $this->get($name);
}
/**
* @param string $name
* @param mixed $value
*
* @return void
*/
public function __set($name, $value)
{
$this->set($name, $value);
}
}
@@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem;
use League\Flysystem\Util;
final class PathUtil extends Util
{
}
@@ -0,0 +1,63 @@
<?php
namespace Xentral\Components\Filesystem\Plugin;
use Xentral\Components\Filesystem\FilesystemInterface;
final class FilterPathsPlugin implements PluginInterface
{
/** @var FilesystemInterface $filesystem */
private $filesystem;
/**
* @return string
*/
public function getMethod()
{
return 'filterPaths';
}
/**
* @param FilesystemInterface $filesystem
*/
public function setFilesystem(FilesystemInterface $filesystem)
{
$this->filesystem = $filesystem;
}
/**
* @example filterPaths(['extension' => 'php', 'filename' => 'Bootstrap'])
*
* @param array $filter
* @param string $path
* @param bool $recursive
*
* @return array
*/
public function handle(array $filter = [], $path = '', $recursive = false)
{
$result = [];
$contents = $this->filesystem->listContents($path, $recursive);
foreach ($contents as $object) {
$matchesFilter = true;
foreach ($filter as $property => $value) {
if (!isset($object[$property])) {
$matchesFilter = false;
continue;
}
if ($object[$property] !== $value) {
$matchesFilter = false;
continue;
}
}
if ($matchesFilter === true) {
$result[] = $object['path'];
}
}
return $result;
}
}
@@ -0,0 +1,37 @@
<?php
namespace Xentral\Components\Filesystem\Plugin;
use League\Flysystem\Plugin\AbstractPlugin;
final class ListDirectoriesPlugin extends AbstractPlugin
{
/**
* Get the method name.
*
* @return string
*/
public function getMethod()
{
return 'listDirectories';
}
/**
* List all directories in the directory.
*
* @param string $directory
* @param bool $recursive
*
* @return array
*/
public function handle($directory = '', $recursive = false)
{
$contents = $this->filesystem->listContents($directory, $recursive);
$filter = function ($object) {
return $object['type'] === 'dir';
};
return array_values(array_filter($contents, $filter));
}
}
@@ -0,0 +1,18 @@
<?php
namespace Xentral\Components\Filesystem\Plugin;
use Xentral\Components\Filesystem\FilesystemInterface;
interface PluginInterface
{
/**
* @return string
*/
public function getMethod();
/**
* @param FilesystemInterface $filesystem
*/
public function setFilesystem(FilesystemInterface $filesystem);
}
+245
View File
@@ -0,0 +1,245 @@
# Filesystem-Komponente
## Beispiele
### FTP
```php
<?php
/** @var \Xentral\Components\Filesystem\FilesystemFactory $factory */
$factory = $this->app->Container->get('FilesystemFactory');
$config = new \Xentral\Components\Filesystem\Adapter\FtpConfig('192.168.0.123', 'username', 'passwort', '/root-dir');
$ftp = $factory->createFtp($config);
if ($ftp->has('/some/file.txt')) {
$contents = $ftp->read('/some/file.txt');
} else {
$ftp->write('/some/file.txt', 'hello world!');
}
```
### Lokales Dateisystem
```php
<?php
/** @var \Xentral\Components\Filesystem\FilesystemFactory $factory */
$factory = $this->app->Container->get('FilesystemFactory');
$local = $factory->createLocal(dirname(__DIR__));
if ($local->has('/some/file.txt')) {
$contents = $local->read('/some/file.txt');
} else {
$local->write('/some/file.txt', 'hello world!');
}
```
### Große Dateien kopieren
```php
<?php
$ftp = $factory->createFtp($config);
$local = $factory->createLocal(dirname(__DIR__));
$ftp->writeStream('/ziel-pfad', $local->readStream('/quell-pfad'));
```
### Datei-Uploads
```php
<?php
$factory = $this->app->Container->get('FilesystemFactory');
$local = $factory->createLocal(dirname(__DIR__));
$stream = fopen($_FILES['upload']['tmp_name'], 'rb+');
$local->writeStream(
'uploads/' . $_FILES['upload']['name'],
$stream
);
if (is_resource($stream)) {
fclose($stream);
}
```
### FilesystemSyncCache
```php
<?php
$ftp = $factory->createFtp($config);
$sync = $factory->createSync($ftp, 1);
$changes = $sync->listChanges('/sub', true); // Hinzugekommene und geänderte Dateien abrufen
$deletes = $sync->listDeleted('/sub', true); // Gelöschte Dateien abrufen
```
## API
### FilesystemInterface
---
#### `has($path)`
Prüfen ob eine Datei oder ein Verzeichnis existiert.
Rückgabe: `true` oder `false`
---
#### `listContents($directory = '', $recursive = false)`
Verzeichnisinhalte abrufen.
Rückgabe: `array` mit `PathInfo`-Objekten
---
#### `listDirs($directory = '', $recursive = false)`
Verzeichnisinhalte abrufen; nur Verzeichnisse.
Rückgabe: `array` mit `PathInfo`-Objekten
---
#### `listFiles($directory = '', $recursive = false)`
Verzeichnisinhalte abrufen; nur Dateien.
Rückgabe: `array` mit `PathInfo`-Objekten
---
#### `listPaths($directory = '', $recursive = false)`
Verzeichnisinhalte abrufen; nur Pfadauflistung; keine Details.
Rückgabe: `array` mit Pfaden als `string`
---
#### `getInfo($path)`
Informationen über eine Datei oder ein Verzeichnis abrufen.
Rückgabe: `PathInfo`-Objekt
---
#### `getType($path)`
Prüfen ob Pfad eine Datei oder ein Verzeichnis ist.
Rückgabe: `'dir'` oder `'file''`
---
#### `getSize($path)`
Dateigröße abrufen.
Rückgabe: Dateigröße als `int` oder `false` bei einem Verzeichnis.
---
#### `getTimestamp($path)`
Datum der letzten Änderung abrufen.
Rückgabe: Timestamp als `int` oder `false` falls Information nicht verfügbar
(z.B. bei Verzeichnissen über FTP).
---
#### `getMimetype($path)`
Mimetype abrufen.
Rückgabe: Mimetype als `string` oder `'directory'` bei einem Verzeichnis.
---
#### `read($path)`
Datei-Inhalt abrufen.
Rückgabe: `string`
---
#### `readStream($path)`
Datei-Inhalt als Stream abrufen.
Rückgabe: `resource`
---
#### `write($path, $contents, array $config = [])`
#### `writeStream($path, $resource, array $config = [])`
Datei anlegen.
Besonderheit: Wenn Zieldatei bereits existiert, wird eine `FileExistsException` geworfen.
Rückgabe: `true` oder `false`
---
#### `put($path, $contents, array $config = [])`
#### `putStream($path, $resource, array $config = [])`
Datei anlegen oder überschreiben.
Besonderheit: Im Unterschied zu `write()` und `writeStream()` wird keine Exception geworfen wenn das Ziel bereits existiert.
Rückgabe: `true` oder `false`
---
#### `createDir($dirname)`
Verzeichnis anlegen; funktioniert auch rekursiv.
Rückgabe: `true` oder `false`
---
#### `deleteDir($dirname)`
Verzeichnis löschen; funktioniert auch wenn Verzeichnis nicht leer.
Rückgabe: `true` oder `false`
---
#### `delete($path)`
Einzelne Datei löschen.
Rückgabe: `true` oder `false`
---
#### `rename($path, $newpath)`
Datei umbenennen.
Rückgabe: `true` oder `false`
---
#### `copy($path, $newpath)`
Datei kopieren.
Rückgabe: `true` oder `false`
---