mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-16 06:23:18 +00:00
Add static file mode to deliver thumbnails
This commit is contained in:
@@ -19,6 +19,9 @@ namespace KonsoleKommander;
|
|||||||
use Alchemy\Phrasea\Command\Plugin\ListPlugin;
|
use Alchemy\Phrasea\Command\Plugin\ListPlugin;
|
||||||
use Alchemy\Phrasea\Command\Setup\H264ConfigurationDumper;
|
use Alchemy\Phrasea\Command\Setup\H264ConfigurationDumper;
|
||||||
use Alchemy\Phrasea\Command\Setup\H264MappingGenerator;
|
use Alchemy\Phrasea\Command\Setup\H264MappingGenerator;
|
||||||
|
use Alchemy\Phrasea\Command\Setup\StaticConfigurationDumper;
|
||||||
|
use Alchemy\Phrasea\Command\Setup\StaticMappingGenerator;
|
||||||
|
use Alchemy\Phrasea\Command\Setup\StaticSymLinkGenerator;
|
||||||
use Alchemy\Phrasea\Core\Version;
|
use Alchemy\Phrasea\Core\Version;
|
||||||
use Alchemy\Phrasea\Command\BuildMissingSubdefs;
|
use Alchemy\Phrasea\Command\BuildMissingSubdefs;
|
||||||
use Alchemy\Phrasea\Command\CreateCollection;
|
use Alchemy\Phrasea\Command\CreateCollection;
|
||||||
@@ -105,6 +108,10 @@ $cli->command(new H264MappingGenerator());
|
|||||||
$cli->command(new XSendFileConfigurationDumper());
|
$cli->command(new XSendFileConfigurationDumper());
|
||||||
$cli->command(new XSendFileMappingGenerator());
|
$cli->command(new XSendFileMappingGenerator());
|
||||||
|
|
||||||
|
$cli->command(new StaticConfigurationDumper());
|
||||||
|
$cli->command(new StaticMappingGenerator());
|
||||||
|
$cli->command(new StaticSymLinkGenerator());
|
||||||
|
|
||||||
$cli->loadPlugins();
|
$cli->loadPlugins();
|
||||||
|
|
||||||
exit(is_int($cli->run()) ? : 1);
|
exit(is_int($cli->run()) ? : 1);
|
||||||
|
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Command\Setup;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Command\Command;
|
||||||
|
use Alchemy\Phrasea\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class StaticConfigurationDumper extends Command
|
||||||
|
{
|
||||||
|
public function __construct($name = null)
|
||||||
|
{
|
||||||
|
parent::__construct('static-file:dump-configuration');
|
||||||
|
|
||||||
|
$this->setDescription('Dump the virtual host configuration depending on Phraseanet configuration');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$output->writeln('');
|
||||||
|
|
||||||
|
if ($this->container['phraseanet.xsendfile-factory']->isXSendFileModeEnabled()) {
|
||||||
|
throw new \LogicException('XSendFile mode is already activated');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->container['phraseanet.static-file-factory']->isStaticFileModeEnabled()) {
|
||||||
|
$output->writeln('Static file support is <error>disabled</error>');
|
||||||
|
$ret = 1;
|
||||||
|
} else {
|
||||||
|
$output->writeln('Static file support is <info>enabled</info>');
|
||||||
|
$ret = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$configuration = $this->container['phraseanet.static-file-factory']->getMode(true, true)->getVirtualHostConfiguration();
|
||||||
|
$output->writeln('Static file configuration seems <info>OK</info>');
|
||||||
|
$output->writeln($configuration);
|
||||||
|
} catch (RuntimeException $e) {
|
||||||
|
$output->writeln('Static file configuration seems <error>invalid</error>');
|
||||||
|
$ret = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln('');
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
}
|
103
lib/Alchemy/Phrasea/Command/Setup/StaticMappingGenerator.php
Normal file
103
lib/Alchemy/Phrasea/Command/Setup/StaticMappingGenerator.php
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Command\Setup;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Command\Command;
|
||||||
|
use Alchemy\Phrasea\Http\StaticFile\StaticFileFactory;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class StaticMappingGenerator extends Command
|
||||||
|
{
|
||||||
|
public function __construct($name = null)
|
||||||
|
{
|
||||||
|
parent::__construct('static-file:generate-mapping');
|
||||||
|
|
||||||
|
$this->addOption('write', 'w', null, 'Writes the configuration')
|
||||||
|
->addOption('enabled', 'e', null, 'Set the enable toggle to `true`')
|
||||||
|
->addArgument('type', InputArgument::REQUIRED, 'The configuration type, either `nginx` or `apache`')
|
||||||
|
->setDescription('Generates Phraseanet Static file configuration');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$enabled = $input->getOption('enabled');
|
||||||
|
$type = strtolower($input->getArgument('type'));
|
||||||
|
|
||||||
|
$factory = new StaticFileFactory($this->container['monolog'], true, $type, $this->container['phraseanet.thumb-symlinker']);
|
||||||
|
$mode = $factory->getMode(true);
|
||||||
|
|
||||||
|
$conf = array(
|
||||||
|
'enabled' => $enabled,
|
||||||
|
'type' => $type,
|
||||||
|
'mapping' => $mode->getMapping(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($input->getOption('write')) {
|
||||||
|
$output->write("Writing configuration ...");
|
||||||
|
$this->container['phraseanet.configuration']['static-file'] = $conf;
|
||||||
|
$output->writeln(" <info>OK</info>");
|
||||||
|
$output->writeln("");
|
||||||
|
$output->write("It is now strongly recommended to use <info>static-file:dump-configuration</info> command to upgrade your virtual-host");
|
||||||
|
} else {
|
||||||
|
$output->writeln("Configuration will <info>not</info> be written, use <info>--write</info> option to write it");
|
||||||
|
$output->writeln("");
|
||||||
|
$output->writeln(Yaml::dump(array('static-file' => $conf), 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function computeMapping($paths)
|
||||||
|
{
|
||||||
|
$paths = array_unique($paths);
|
||||||
|
|
||||||
|
$ret = array();
|
||||||
|
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
$ret[$path] = $this->pathsToConf($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function pathsToConf($path)
|
||||||
|
{
|
||||||
|
static $n = 0;
|
||||||
|
$n++;
|
||||||
|
|
||||||
|
return array('mount-point' => 'mp4-videos-'.$n, 'directory' => $path, 'passphrase' => \random::generatePassword(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractPath(\appbox $appbox)
|
||||||
|
{
|
||||||
|
$paths = array();
|
||||||
|
|
||||||
|
foreach ($appbox->get_databoxes() as $databox) {
|
||||||
|
foreach ($databox->get_subdef_structure() as $group => $subdefs) {
|
||||||
|
if ('video' !== $group) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($subdefs as $subdef) {
|
||||||
|
$paths[] = $subdef->get_path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_filter(array_unique($paths));
|
||||||
|
}
|
||||||
|
}
|
85
lib/Alchemy/Phrasea/Command/Setup/StaticSymLinkGenerator.php
Normal file
85
lib/Alchemy/Phrasea/Command/Setup/StaticSymLinkGenerator.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Command\Setup;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Command\Command;
|
||||||
|
use Alchemy\Phrasea\Http\StaticFile\StaticFileFactory;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class StaticSymLinkGenerator extends Command
|
||||||
|
{
|
||||||
|
public function __construct($name = null)
|
||||||
|
{
|
||||||
|
parent::__construct('static-file:generate-symlink');
|
||||||
|
|
||||||
|
$this
|
||||||
|
->setDescription('Generates Phraseanet Static file symlinks');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
if (!$this->container['phraseanet.static-file-factory']->isStaticFileModeEnabled()) {
|
||||||
|
$output->writeln('Static file support is <error>disabled</error>');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln("Removing symlinks ...");
|
||||||
|
$this->container['filesystem']->remove($this->container['phraseanet.thumb-symlinker']->getPublicDir());
|
||||||
|
$total = 0;
|
||||||
|
foreach ($this->container['phraseanet.appbox']->get_databoxes() as $databox) {
|
||||||
|
$sql = 'SELECT count(subdef_id) as total FROM subdef WHERE `name`="thumbnail"';
|
||||||
|
$stmt = $databox->get_connection()->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||||
|
$stmt->closeCursor();
|
||||||
|
$total += $row['total'];
|
||||||
|
}
|
||||||
|
$output->writeln("Creating symlinks ...");
|
||||||
|
$progress = $this->getHelperSet()->get('progress');
|
||||||
|
$progress->start($output, $total);
|
||||||
|
$i = 0;
|
||||||
|
do {
|
||||||
|
foreach ($this->container['phraseanet.appbox']->get_databoxes() as $databox) {
|
||||||
|
$sql = 'SELECT record_id FROM record';
|
||||||
|
$stmt = $databox->get_connection()->prepare($sql);
|
||||||
|
$stmt->execute();
|
||||||
|
$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
$stmt->closeCursor();
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$record = $databox->get_record($row['record_id']);
|
||||||
|
foreach ($record->get_subdefs() as $subdef) {
|
||||||
|
if ($subdef->get_name() !== 'thumbnail') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->container['phraseanet.thumb-symlinker']->symlink($subdef->get_pathfile());
|
||||||
|
$progress->advance();
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while ($i < $total);
|
||||||
|
|
||||||
|
$progress->finish();
|
||||||
|
|
||||||
|
$output->writeln("<info>OK</info>");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -14,6 +14,7 @@ namespace Alchemy\Phrasea\Core\Provider;
|
|||||||
use Alchemy\Phrasea\Core\Event\Subscriber\XSendFileSubscriber;
|
use Alchemy\Phrasea\Core\Event\Subscriber\XSendFileSubscriber;
|
||||||
use Alchemy\Phrasea\Http\H264PseudoStreaming\H264Factory;
|
use Alchemy\Phrasea\Http\H264PseudoStreaming\H264Factory;
|
||||||
use Alchemy\Phrasea\Http\ServeFileResponseFactory;
|
use Alchemy\Phrasea\Http\ServeFileResponseFactory;
|
||||||
|
use Alchemy\Phrasea\Http\StaticFile\StaticFileFactory;
|
||||||
use Alchemy\Phrasea\Http\XSendFile\XSendFileFactory;
|
use Alchemy\Phrasea\Http\XSendFile\XSendFileFactory;
|
||||||
use Silex\Application;
|
use Silex\Application;
|
||||||
use Silex\ServiceProviderInterface;
|
use Silex\ServiceProviderInterface;
|
||||||
@@ -33,10 +34,18 @@ class FileServeServiceProvider implements ServiceProviderInterface
|
|||||||
return H264Factory::create($app);
|
return H264Factory::create($app);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$app['phraseanet.static-file-factory'] = $app->share(function ($app) {
|
||||||
|
return StaticFileFactory::create($app);
|
||||||
|
});
|
||||||
|
|
||||||
$app['phraseanet.h264'] = $app->share(function ($app) {
|
$app['phraseanet.h264'] = $app->share(function ($app) {
|
||||||
return $app['phraseanet.h264-factory']->createMode(false);
|
return $app['phraseanet.h264-factory']->createMode(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$app['phraseanet.static-file'] = $app->share(function ($app) {
|
||||||
|
return $app['phraseanet.static-file-factory']->getMode(false);
|
||||||
|
});
|
||||||
|
|
||||||
$app['phraseanet.file-serve'] = $app->share(function (Application $app) {
|
$app['phraseanet.file-serve'] = $app->share(function (Application $app) {
|
||||||
return ServeFileResponseFactory::create($app);
|
return ServeFileResponseFactory::create($app);
|
||||||
});
|
});
|
||||||
|
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace Alchemy\Phrasea\Core\Provider;
|
namespace Alchemy\Phrasea\Core\Provider;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Http\StaticFile\Symlink\SymLinker;
|
||||||
|
use Alchemy\Phrasea\Http\StaticFile\Symlink\SymLinkerEncoder;
|
||||||
use Alchemy\Phrasea\Metadata\PhraseanetMetadataReader;
|
use Alchemy\Phrasea\Metadata\PhraseanetMetadataReader;
|
||||||
use Alchemy\Phrasea\Metadata\PhraseanetMetadataSetter;
|
use Alchemy\Phrasea\Metadata\PhraseanetMetadataSetter;
|
||||||
use Alchemy\Phrasea\Security\Firewall;
|
use Alchemy\Phrasea\Security\Firewall;
|
||||||
@@ -41,6 +43,14 @@ class PhraseanetServiceProvider implements ServiceProviderInterface
|
|||||||
return $events;
|
return $events;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$app['phraseanet.thumb-symlinker'] = $app->share(function (SilexApplication $app) {
|
||||||
|
return SymLinker::create($app);
|
||||||
|
});
|
||||||
|
|
||||||
|
$app['phraseanet.thumb-symlinker-encoder'] = $app->share(function (SilexApplication $app) {
|
||||||
|
return SymLinkerEncoder::create($app);
|
||||||
|
});
|
||||||
|
|
||||||
$app['phraseanet.metadata-reader'] = $app->share(function (SilexApplication $app) {
|
$app['phraseanet.metadata-reader'] = $app->share(function (SilexApplication $app) {
|
||||||
$reader = new PhraseanetMetadataReader();
|
$reader = new PhraseanetMetadataReader();
|
||||||
|
|
||||||
|
27
lib/Alchemy/Phrasea/Http/StaticFile/AbstractStaticMode.php
Normal file
27
lib/Alchemy/Phrasea/Http/StaticFile/AbstractStaticMode.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Http\StaticFile;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Http\AbstractServerMode;
|
||||||
|
use Alchemy\Phrasea\Http\StaticFile\Symlink\SymLinker;
|
||||||
|
|
||||||
|
abstract class AbstractStaticMode extends AbstractServerMode
|
||||||
|
{
|
||||||
|
protected $symlinker;
|
||||||
|
|
||||||
|
public function __construct(array $mapping, SymLinker $symlinker)
|
||||||
|
{
|
||||||
|
$this->symlinker = $symlinker;
|
||||||
|
|
||||||
|
parent::__construct($mapping);
|
||||||
|
}
|
||||||
|
}
|
62
lib/Alchemy/Phrasea/Http/StaticFile/Apache.php
Normal file
62
lib/Alchemy/Phrasea/Http/StaticFile/Apache.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Http\StaticFile;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Exception\InvalidArgumentException;
|
||||||
|
use Alchemy\Phrasea\Http\AbstractServerMode;
|
||||||
|
use Guzzle\Http\Url;
|
||||||
|
|
||||||
|
class Apache extends AbstractStaticMode implements StaticFileModeInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @params array $mapping
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException if mapping is invalid;
|
||||||
|
*/
|
||||||
|
public function setMapping(array $mapping)
|
||||||
|
{
|
||||||
|
if (!isset($mapping['directory'])) {
|
||||||
|
throw new InvalidArgumentException('Static file mapping entry must contain at least a "directory" key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($mapping['mount-point'])) {
|
||||||
|
throw new InvalidArgumentException('Static file mapping entry must contain at least a "mount-point" key');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mapping = $mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getUrl($pathFile)
|
||||||
|
{
|
||||||
|
return Url::factory(sprintf('%s/%s', $this->mapping['mount-point'], $this->symlinker->getSymlinkBasePath($pathFile)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getVirtualHostConfiguration()
|
||||||
|
{
|
||||||
|
$output = "\n";
|
||||||
|
$output .= " Alias ".$this->mapping['mount-point']." ".$this->mapping['directory']."\n";
|
||||||
|
$output .= "\n";
|
||||||
|
$output .= " <Location ".$this->mapping['directory'].">\n";
|
||||||
|
$output .= " Order allow,deny\n";
|
||||||
|
$output .= " Allow from all\n";
|
||||||
|
$output .= " </Location>\n";
|
||||||
|
$output .= "\n";
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
}
|
58
lib/Alchemy/Phrasea/Http/StaticFile/Nginx.php
Normal file
58
lib/Alchemy/Phrasea/Http/StaticFile/Nginx.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Http\StaticFile;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Exception\InvalidArgumentException;
|
||||||
|
use Alchemy\Phrasea\Http\AbstractServerMode;
|
||||||
|
use Guzzle\Http\Url;
|
||||||
|
|
||||||
|
class Nginx extends AbstractStaticMode implements StaticFileModeInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @params array $mapping
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException if mapping is invalid;
|
||||||
|
*/
|
||||||
|
public function setMapping(array $mapping)
|
||||||
|
{
|
||||||
|
if (!isset($mapping['directory'])) {
|
||||||
|
throw new InvalidArgumentException('Static file mapping entry must contain at least a "directory" key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($mapping['mount-point'])) {
|
||||||
|
throw new InvalidArgumentException('Static file mapping entry must contain at least a "mount-point" key');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mapping = $mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getUrl($pathFile)
|
||||||
|
{
|
||||||
|
return Url::factory(sprintf('%s/%s', $this->mapping['mount-point'], $this->symlinker->getSymlinkBasePath($pathFile)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getVirtualHostConfiguration()
|
||||||
|
{
|
||||||
|
$output = "\n";
|
||||||
|
$output .= " location " . $this->mapping['mount-point']. " {\n";
|
||||||
|
$output .= " alias ".$this->mapping['directory'].";\n";
|
||||||
|
$output .= " }\n";
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
}
|
30
lib/Alchemy/Phrasea/Http/StaticFile/NullMode.php
Normal file
30
lib/Alchemy/Phrasea/Http/StaticFile/NullMode.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Http\StaticFile;
|
||||||
|
|
||||||
|
class NullMode extends AbstractServerMode implements StaticFileModeInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getVirtualHostConfiguration()
|
||||||
|
{
|
||||||
|
return "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getUrl($pathFile)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
101
lib/Alchemy/Phrasea/Http/StaticFile/StaticFileFactory.php
Normal file
101
lib/Alchemy/Phrasea/Http/StaticFile/StaticFileFactory.php
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Http\StaticFile;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Application;
|
||||||
|
use Alchemy\Phrasea\Exception\InvalidArgumentException;
|
||||||
|
use Alchemy\Phrasea\Http\StaticFile\Symlink\SymLinker;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class StaticFileFactory
|
||||||
|
{
|
||||||
|
private $enabled;
|
||||||
|
private $logger;
|
||||||
|
private $type;
|
||||||
|
private $symlinker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param LoggerInterface $logger
|
||||||
|
* @param boolean $enabled
|
||||||
|
* @param string $type
|
||||||
|
* @param array $symlinker
|
||||||
|
*/
|
||||||
|
public function __construct(LoggerInterface $logger, $enabled, $type, SymLinker $symlinker)
|
||||||
|
{
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->enabled = (Boolean) $enabled;
|
||||||
|
$this->type = strtolower($type);
|
||||||
|
$this->symlinker = $symlinker;
|
||||||
|
|
||||||
|
$this->mapping = array(
|
||||||
|
'mount-point' => $symlinker->getDefaultAlias(),
|
||||||
|
'directory' => $symlinker->getPublicDir()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of XSendFile Factory according to the application
|
||||||
|
* configuration.
|
||||||
|
*
|
||||||
|
* @param Application $app
|
||||||
|
* @return XSendFileFactory
|
||||||
|
*/
|
||||||
|
public static function create(Application $app)
|
||||||
|
{
|
||||||
|
$conf = $app['phraseanet.configuration']['static-file'];
|
||||||
|
|
||||||
|
return new self($app['monolog'], $conf['enabled'], $conf['type'], $app['phraseanet.thumb-symlinker']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new instance of ModeInterface.
|
||||||
|
*
|
||||||
|
* @return ModeInterface
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException if mode type is unknown
|
||||||
|
*/
|
||||||
|
public function getMode($throwException = false, $forceMode = false)
|
||||||
|
{
|
||||||
|
if (false === $this->enabled && true !== $forceMode) {
|
||||||
|
return new NullMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($this->type) {
|
||||||
|
case 'nginx':
|
||||||
|
return new Nginx($this->mapping, $this->symlinker);
|
||||||
|
break;
|
||||||
|
case 'apache':
|
||||||
|
case 'apache2':
|
||||||
|
return new Apache($this->mapping, $this->symlinker);
|
||||||
|
default:
|
||||||
|
$this->logger->error('Invalid static file configuration.');
|
||||||
|
if ($throwException) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'Invalid static file type value "%s"',
|
||||||
|
$this->type
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NullMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
|
public function isStaticFileModeEnabled()
|
||||||
|
{
|
||||||
|
return $this->enabled;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Http\StaticFile;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Http\ServerModeInterface;
|
||||||
|
use Guzzle\Http\Url;
|
||||||
|
|
||||||
|
interface StaticFileModeInterface extends ServerModeInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param $pathFile
|
||||||
|
*
|
||||||
|
* @return Url|null
|
||||||
|
*/
|
||||||
|
public function getUrl($pathFile);
|
||||||
|
}
|
89
lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinker.php
Normal file
89
lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinker.php
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Http\StaticFile\Symlink;
|
||||||
|
|
||||||
|
use Silex\Application;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Guzzle\Http\Url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create & retrieve symlinks from public directory
|
||||||
|
*/
|
||||||
|
class SymLinker
|
||||||
|
{
|
||||||
|
const ALIAS = 'thumb';
|
||||||
|
|
||||||
|
protected $encoder;
|
||||||
|
protected $fs;
|
||||||
|
protected $publicDir;
|
||||||
|
protected $registry;
|
||||||
|
protected $rootPath;
|
||||||
|
|
||||||
|
public static function create(Application $app)
|
||||||
|
{
|
||||||
|
return new SymLinker(
|
||||||
|
$app['phraseanet.thumb-symlinker-encoder'],
|
||||||
|
$app['filesystem'],
|
||||||
|
$app['phraseanet.registry'],
|
||||||
|
$app['root.path']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(SymLinkerEncoder $encoder, Filesystem $fs, \registryInterface $registry, $rootPath)
|
||||||
|
{
|
||||||
|
$this->encoder = $encoder;
|
||||||
|
$this->fs = $fs;
|
||||||
|
$this->registry = $registry;
|
||||||
|
$this->rootPath = $rootPath;
|
||||||
|
$this->publicDir = sprintf('%s/public/thumbnails', rtrim($this->rootPath, '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPublicDir()
|
||||||
|
{
|
||||||
|
return $this->publicDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefaultAlias()
|
||||||
|
{
|
||||||
|
return sprintf('/%s', self::ALIAS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function symlink($pathFile)
|
||||||
|
{
|
||||||
|
$this->fs->symlink($pathFile, $this->getSymlinkPath($pathFile)) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSymlink($pathFile)
|
||||||
|
{
|
||||||
|
return $this->encoder->encode($pathFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSymlinkBasePath($pathFile)
|
||||||
|
{
|
||||||
|
$symlinkName = $this->getSymlink($pathFile);
|
||||||
|
|
||||||
|
return sprintf('%s/%s/%s',
|
||||||
|
substr($symlinkName, 0, 2),
|
||||||
|
substr($symlinkName, 2, 2),
|
||||||
|
substr($symlinkName, 4)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSymlinkPath($pathFile)
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'%s/%s',
|
||||||
|
$this->publicDir,
|
||||||
|
$this->getSymlinkBasePath($pathFile)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Phraseanet
|
||||||
|
*
|
||||||
|
* (c) 2005-2014 Alchemy
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\Http\StaticFile\Symlink;
|
||||||
|
|
||||||
|
use Silex\Application;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Guzzle\Http\Url;
|
||||||
|
|
||||||
|
class SymLinkerEncoder
|
||||||
|
{
|
||||||
|
protected $key;
|
||||||
|
|
||||||
|
public static function create(Application $app)
|
||||||
|
{
|
||||||
|
return new self(
|
||||||
|
$app['phraseanet.configuration']['main']['key']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct($key)
|
||||||
|
{
|
||||||
|
$this->key = $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function encode($pathFile)
|
||||||
|
{
|
||||||
|
return hash_hmac('sha512', $pathFile , $this->key);
|
||||||
|
}
|
||||||
|
}
|
@@ -730,6 +730,10 @@ class media_subdef extends media_abstract implements cache_cacheableInterface
|
|||||||
$subdef->get_permalink()->delete_data_from_cache();
|
$subdef->get_permalink()->delete_data_from_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($name === 'thumbnail' && $app['phraseanet.static-file-factory']->isStaticFileModeEnabled()) {
|
||||||
|
$app['phraseanet.thumb-symlinker']->symlink($subdef->get_pathfile());
|
||||||
|
}
|
||||||
|
|
||||||
unset($media);
|
unset($media);
|
||||||
|
|
||||||
return $subdef;
|
return $subdef;
|
||||||
@@ -742,10 +746,18 @@ class media_subdef extends media_abstract implements cache_cacheableInterface
|
|||||||
*/
|
*/
|
||||||
protected function generate_url()
|
protected function generate_url()
|
||||||
{
|
{
|
||||||
if ( ! $this->is_physically_present()) {
|
if (!$this->is_physically_present()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->get_name() === 'thumbnail') {
|
||||||
|
if ($this->app['phraseanet.static-file-factory']->isStaticFileModeEnabled() && null !== $url = $this->app['phraseanet.static-file']->getUrl($this->get_pathfile())) {
|
||||||
|
$this->url = $url;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->app['phraseanet.h264-factory']->isH264Enabled() && in_array($this->mime, array('video/mp4'))) {
|
if ($this->app['phraseanet.h264-factory']->isH264Enabled() && in_array($this->mime, array('video/mp4'))) {
|
||||||
if (null !== $url = $this->app['phraseanet.h264']->getUrl($this->get_pathfile())) {
|
if (null !== $url = $this->app['phraseanet.h264']->getUrl($this->get_pathfile())) {
|
||||||
$this->url = $url;
|
$this->url = $url;
|
||||||
@@ -758,6 +770,7 @@ class media_subdef extends media_abstract implements cache_cacheableInterface
|
|||||||
. "/" . $this->record->get_record_id() . "/"
|
. "/" . $this->record->get_record_id() . "/"
|
||||||
. $this->get_name() . "/");
|
. $this->get_name() . "/");
|
||||||
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1514,6 +1514,10 @@ class record_adapter implements record_Interface, cache_cacheableInterface
|
|||||||
if (!$subdef->is_physically_present())
|
if (!$subdef->is_physically_present())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if ($subdef->get_name() === 'thumbnail' && $this->app['phraseanet.static-file-factory']->isStaticFileModeEnabled()) {
|
||||||
|
$this->app['filesystem']->remove($this->app['phraseanet.thumb-symlinker']->getSymlinkPath($subdef->get_pathfile()));
|
||||||
|
}
|
||||||
|
|
||||||
$ftodel[] = $subdef->get_pathfile();
|
$ftodel[] = $subdef->get_pathfile();
|
||||||
$watermark = $subdef->get_path() . 'watermark_' . $subdef->get_file();
|
$watermark = $subdef->get_path() . 'watermark_' . $subdef->get_file();
|
||||||
if (file_exists($watermark))
|
if (file_exists($watermark))
|
||||||
|
Reference in New Issue
Block a user