-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from ezsystems/dev
Initial implementation
- Loading branch information
Showing
7 changed files
with
747 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# DFS Cluster DFS Dispatcher | ||
|
||
## What is it ? | ||
*This extension by itself won't provide any new end-user feature.* | ||
|
||
This extension provides a custom DFSBackend that dispatches calls to any DFS backend to a backend from a custom list, | ||
based on the path of the file. | ||
|
||
It makes it possible to store some storage subdirectories to custom handlers, such as a cloud based one. | ||
|
||
## Status | ||
Working prototype, close from first release. | ||
|
||
## Requirements | ||
- eZ Publish installed from git, with the EZP-22960-configurable_dfs_backend branch checked out. | ||
- eZ DFS configured (NFS itself doesn't matter, a local directory will work just as fine) | ||
|
||
## Installation | ||
It can be installed via composer from eZ Publish, new stack or legacy: | ||
``` | ||
composer require "ezsystems/ezdfs-fsbackend-dispatcher:dev-master" | ||
``` | ||
|
||
Or by adding `"ezsystems/ezdfs-fsbackend-dispatcher": "dev-master"` to your project's composer.json. | ||
|
||
It can also be manually checked out from http://github.com/ezsystems/ezsystems/ezdfs-fsbackend-dispatcher.git into the | ||
legacy `extension` directory. | ||
|
||
## Configuration | ||
Due to INI settings loading order limitations, some settings can't be stored into extension INI files but in a global override. | ||
|
||
The contents of the settings/file.ini.append.php file must be copied to `settings/override/file.ini.append.php`, or | ||
obviously merged into it, since it should already exist. | ||
|
||
Backends are configured by adding their class name to the DFSBackends array in file.ini. This will make the dispatcher send | ||
operations on files with a path starting with `var/ezdemo_site/storage/images` to MyCustomBackend. | ||
|
||
``` | ||
PathBackends[var/ezdemo_site/storage/images]=MyCustomBackend | ||
``` | ||
|
||
Priority is a simple first-come, first-served. Path that aren't matched by any path in `PathBackends` are handled by | ||
`DispatchableDFS.DefaultBackend`, by default set to the native `eZDFSFileHandlerDFSBackend`. | ||
|
||
## Backends initialization | ||
By default, backends are instanciated with a simple "new $class". But if a backend implements | ||
`eZDFSFileHandlerDFSFactoryBackendInterface` interface, it will be built by calling the static `build()` method. | ||
|
||
If any kind of initialization or injection is required, it can be done in this method. | ||
|
||
A typical settings/override/file.ini.append.php with a custom handler enabled would look like this: | ||
```ini | ||
[ClusteringSettings] | ||
FileHandler=eZDFSFileHandler | ||
|
||
[eZDFSClusteringSettings] | ||
MountPointPath=/media/nfs | ||
DFSBackend=eZDFSFileHandlerDFSDispatcher | ||
DBHost=cluster_server | ||
DBName=db_cluster | ||
DBUser=root | ||
DBPassword= | ||
MetaDataTableNameCache=ezdfsfile_cache | ||
|
||
[DispatchableDFS] | ||
DefaultBackend=eZDFSFileHandlerDFSBackend | ||
|
||
PathBackends[var/site/storage/images]=MyCustomBackend | ||
``` | ||
|
||
Remember that `DefaultBackend` *must* be explicitly configured in the global override to be taken into account. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
<?php | ||
/** | ||
* This file is part of the eZ Publish Legacy package. | ||
* | ||
* @copyright Copyright (C) eZ Systems AS. All rights reserved. | ||
* @license For full copyright and license information view LICENSE file distributed with this source code. | ||
* @version //autogentag// | ||
* @package kernel | ||
*/ | ||
|
||
/** | ||
* DFS FS handler that dispatches/proxies calls to a sub-handler. | ||
*/ | ||
class eZDFSFileHandlerDFSDispatcher implements eZDFSFileHandlerDFSBackendInterface, eZDFSFileHandlerDFSBackendFactoryInterface | ||
{ | ||
/** @var eZDFSFileHandlerDFSRegistry */ | ||
private $fsHandlersRegistry = array(); | ||
|
||
/** | ||
* @param eZDFSFileHandlerDFSRegistry $fsHandlersRegistry | ||
*/ | ||
public function __construct( eZDFSFileHandlerDFSRegistry $fsHandlersRegistry ) | ||
{ | ||
$this->fsHandlersRegistry = $fsHandlersRegistry; | ||
} | ||
|
||
/** | ||
* Instantiates the dispatcher | ||
* @return self | ||
*/ | ||
public static function build() | ||
{ | ||
return new self( eZDFSFileHandlerDFSRegistry::build() ); | ||
} | ||
|
||
/** | ||
* Returns the FSHandler for $path | ||
* @param $path | ||
* @return eZDFSFileHandlerDFSBackendInterface | ||
*/ | ||
private function getHandler( $path ) | ||
{ | ||
return $this->fsHandlersRegistry->getHandler( $path ); | ||
} | ||
|
||
/** | ||
* Returns all the fs handlers | ||
* @return eZDFSFileHandlerDFSBackendInterface[] | ||
*/ | ||
private function getAllHandlers() | ||
{ | ||
return $this->fsHandlersRegistry->getAllHandlers(); | ||
} | ||
|
||
/** | ||
* Creates a copy of $srcFilePath from DFS to $dstFilePath on DFS | ||
* | ||
* @param string $srcFilePath Local source file path | ||
* @param string $dstFilePath Local destination file path | ||
* | ||
* @return bool | ||
*/ | ||
public function copyFromDFSToDFS( $srcFilePath, $dstFilePath ) | ||
{ | ||
$srcHandler = $this->getHandler( $srcFilePath ); | ||
$dstHandler = $this->getHandler( $dstFilePath ); | ||
|
||
if ( $srcHandler === $dstHandler ) | ||
{ | ||
return $srcHandler->copyFromDFSToDFS( $srcFilePath, $dstFilePath ); | ||
} | ||
else | ||
{ | ||
return $dstHandler->createFileOnDFS( $dstFilePath, $srcHandler->getContents( $srcFilePath ) ); | ||
} | ||
} | ||
|
||
/** | ||
* Copies the DFS file $srcFilePath to FS | ||
* | ||
* @param string $srcFilePath Source file path (on DFS) | ||
* @param string|bool $dstFilePath Destination file path (on FS). If not specified, $srcFilePath is used | ||
* | ||
* @return bool | ||
*/ | ||
public function copyFromDFS( $srcFilePath, $dstFilePath = false ) | ||
{ | ||
return $this->getHandler( $srcFilePath )->copyFromDFS( $srcFilePath, $dstFilePath ); | ||
} | ||
|
||
/** | ||
* Copies the local file $filePath to DFS under the same name, or a new name | ||
* if specified | ||
* | ||
* @param string $srcFilePath Local file path to copy from | ||
* @param bool|string $dstFilePath | ||
* Optional path to copy to. If not specified, $srcFilePath is used | ||
* | ||
* @return bool | ||
*/ | ||
public function copyToDFS( $srcFilePath, $dstFilePath = false ) | ||
{ | ||
return $this->getHandler( $dstFilePath ?: $srcFilePath )->copyToDFS( $srcFilePath, $dstFilePath ); | ||
} | ||
|
||
/** | ||
* Deletes one or more files from DFS | ||
* | ||
* @param string|array $filePath Single local filename, or array of local filenames | ||
* | ||
* @return bool true if deletion was successful, false otherwise | ||
*/ | ||
public function delete( $filePath ) | ||
{ | ||
return $this->getHandler( $filePath )->delete( $filePath ); | ||
} | ||
|
||
/** | ||
* Sends the contents of $filePath to default output | ||
* | ||
* @param string $filePath File path | ||
* @param int $startOffset Starting offset | ||
* @param bool|int $length Length to transmit, false means everything | ||
* | ||
* @return bool true, or false if operation failed | ||
*/ | ||
public function passthrough( $filePath, $startOffset = 0, $length = false ) | ||
{ | ||
return $this->getHandler( $filePath )->passthrough( $filePath, $startOffset, $length ); | ||
} | ||
|
||
/** | ||
* Returns the binary content of $filePath from DFS | ||
* | ||
* @param string $filePath local file path | ||
* | ||
* @return string|bool file's content, or false | ||
*/ | ||
public function getContents( $filePath ) | ||
{ | ||
return $this->getHandler( $filePath )->getContents( $filePath ); | ||
} | ||
|
||
/** | ||
* Creates the file $filePath on DFS with content $contents | ||
* | ||
* @param string $filePath | ||
* @param string $contents | ||
* | ||
* @return bool | ||
*/ | ||
public function createFileOnDFS( $filePath, $contents ) | ||
{ | ||
return $this->getHandler( $filePath )->createFileOnDFS( $filePath, $contents ); | ||
} | ||
|
||
/** | ||
* Renamed DFS file $oldPath to DFS file $newPath | ||
* | ||
* @param string $oldPath | ||
* @param string $newPath | ||
* | ||
* @return bool | ||
*/ | ||
public function renameOnDFS( $oldPath, $newPath ) | ||
{ | ||
$oldPathHandler = $this->getHandler( $oldPath ); | ||
$newPathHandler = $this->getHandler( $newPath ); | ||
|
||
// same handler, normal rename | ||
if ( $oldPathHandler === $newPathHandler ) | ||
{ | ||
return $oldPathHandler->renameOnDFS( $oldPath, $newPath ); | ||
} | ||
// different handlers, create on new, delete on old | ||
else | ||
{ | ||
if ( $newPathHandler->createFileOnDFS( $newPath, $oldPathHandler->getContents( $oldPath ) ) !== true ) | ||
return false; | ||
|
||
return $oldPathHandler->delete( $oldPath ); | ||
} | ||
} | ||
|
||
/** | ||
* Checks if a file exists on the DFS | ||
* | ||
* @param string $filePath | ||
* | ||
* @return bool | ||
*/ | ||
public function existsOnDFS( $filePath ) | ||
{ | ||
return $this->getHandler( $filePath )->existsOnDFS( $filePath ); | ||
} | ||
|
||
/** | ||
* Returns size of a file in the DFS backend, from a relative path. | ||
* | ||
* @param string $filePath The relative file path we want to get size of | ||
* | ||
* @return int | ||
*/ | ||
public function getDfsFileSize( $filePath ) | ||
{ | ||
return $this->getHandler( $filePath )->getDfsFileSize( $filePath ); | ||
} | ||
|
||
/** | ||
* Returns an AppendIterator with every handler's iterator | ||
* | ||
* @param string $basePath | ||
* | ||
* @return Iterator | ||
*/ | ||
public function getFilesList( $basePath ) | ||
{ | ||
$iterator = new AppendIterator(); | ||
foreach ( $this->getAllHandlers() as $handler ) | ||
{ | ||
$iterator->append( $handler->getFilesList( $basePath ) ); | ||
} | ||
return $iterator; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php | ||
/** | ||
* This file is part of the eZ Publish Legacy package. | ||
* | ||
* @copyright Copyright (C) eZ Systems AS. All rights reserved. | ||
* @license For full copyright and license information view LICENSE file distributed with this source code. | ||
* @version //autogentag// | ||
* @package kernel | ||
*/ | ||
|
||
/** | ||
* Holds a registry of DFS FS handlers. | ||
* | ||
* Returns the appropriate one | ||
*/ | ||
class eZDFSFileHandlerDFSRegistry implements eZDFSFileHandlerDFSRegistryInterface | ||
{ | ||
/** | ||
* Handlers based on path (as key) | ||
* @var eZDFSFileHandlerDFSBackendInterface[string] */ | ||
private $pathHandlers = array(); | ||
|
||
/** | ||
* The default handler, used when no {@see $pathHandlers} matches | ||
* @var eZDFSFileHandlerDFSBackendInterface | ||
*/ | ||
private $defaultHandler; | ||
|
||
/** | ||
* @param eZDFSFileHandlerDFSBackendInterface $defaultHandler | ||
* @param eZDFSFileHandlerDFSBackendInterface[] $pathHandlers | ||
*/ | ||
public function __construct( eZDFSFileHandlerDFSBackendInterface $defaultHandler, array $pathHandlers = array() ) | ||
{ | ||
foreach ( $pathHandlers as $supportedPath => $handler ) | ||
{ | ||
if ( !$handler instanceof eZDFSFileHandlerDFSBackendInterface ) | ||
{ | ||
throw new InvalidArgumentException( get_class( $handler ) . " does not implement eZDFSFileHandlerDFSBackendInterface" ); | ||
} | ||
} | ||
|
||
$this->defaultHandler = $defaultHandler; | ||
$this->pathHandlers = $pathHandlers; | ||
} | ||
|
||
/** | ||
* Returns the FSHandler for $path | ||
* @param $path | ||
* @return eZDFSFileHandlerDFSBackendInterface | ||
* @throws OutOfRangeException If no handler supports $path | ||
*/ | ||
public function getHandler( $path ) | ||
{ | ||
foreach ( $this->pathHandlers as $supportedPath => $handler ) | ||
{ | ||
if ( strstr( $path, $supportedPath ) !== false ) | ||
{ | ||
return $handler; | ||
} | ||
} | ||
|
||
return $this->defaultHandler; | ||
} | ||
|
||
public function getAllHandlers() | ||
{ | ||
$handlers = array_values( $this->pathHandlers ); | ||
$handlers[] = $this->defaultHandler; | ||
return $handlers; | ||
} | ||
|
||
/** | ||
* Builds a registry using either the provided configuration, or settings from self::getConfiguration | ||
* @return self | ||
*/ | ||
public static function build() | ||
{ | ||
$ini = eZINI::instance( 'file.ini' ); | ||
$defaultHandler = eZDFSFileHandlerBackendFactory::buildHandler( | ||
$ini->variable( 'DispatchableDFS', 'DefaultBackend' ) | ||
); | ||
|
||
$pathHandlers = array(); | ||
foreach ( $ini->variable( 'DispatchableDFS', 'PathBackends' ) as $supportedPath => $backendClass ) | ||
{ | ||
// @todo Make it possible to use a Symfony2 service | ||
$pathHandlers[$supportedPath] = eZDFSFileHandlerBackendFactory::buildHandler( $backendClass ); | ||
} | ||
|
||
return new static( $defaultHandler, $pathHandlers ); | ||
} | ||
} |
Oops, something went wrong.