Commit 758c4b36 authored by CHRISTOPHE THERY's avatar CHRISTOPHE THERY
Browse files

task#105492 : managing FileSystem Access and Persistence

parent 261bf980
Pipeline #10974 passed with stage
in 47 seconds
<?php
namespace Pellicule;
use \Buzz\Client\FileGetContents;
use \Slim\Psr7\Factory\RequestFactory;
use \Slim\Psr7\Factory\ResponseFactory;
use \Fig\Http\Message\StatusCodeInterface;
class FileSystem {
use HttpClientAware;
use
HttpClientAware,
StormFileSystem;
protected static $_base_path = '/tmp/',
$_base_url;
......@@ -27,8 +29,13 @@ class FileSystem {
}
public function fileExists($path) {
return \file_exists(static::$_base_path . $path);
public function getBasePath() {
return static::$_base_path;
}
public function fileExists($path){
return $this->getFileSystem()->fileExists($path);
}
......@@ -37,23 +44,26 @@ class FileSystem {
return;
$directory = pathinfo(static::$_base_path . $path, PATHINFO_DIRNAME);
if (false === \file_exists($directory))
\mkdir($directory, 0755, true);
if (false === $this->fileExists($directory))
$this->getFileSystem()->mkdir($directory, 0755, true);
$request = (new RequestFactory())->createRequest('GET', $url);
foreach($headers as $key => $value)
$request = $request->withHeader($key, $value);
$client = new FileGetContents(new ResponseFactory(), ['allow_redirects' => true]);
if ($headers)
foreach($headers as $key => $value)
$request = $request->withHeader($key, $value);
$client = $this->newHttpClient('filegetcontents');
$response = $client
->sendRequest($request, ['timeout' => 4]);
if (($status = $response->getStatusCode()) != StatusCodeInterface::STATUS_OK) {
echo 'file was not downloaded error:'. $status . ' '. $response->getBody();
echo $url. 'file was not download error:'. $status . ' '. $response->getBody();
return $this;
}
file_put_contents(static::$_base_path . $path, $response->getBody());
$this->getFileSystem()
->filePutContents( static::$_base_path . $path, $response->getBody());
}
}
......@@ -3,6 +3,7 @@ namespace Pellicule;
use \Buzz\Browser;
use \Buzz\Client\Curl;
use \Buzz\Client\FileGetContents;
use \Slim\Psr7\Factory\ResponseFactory;
use \Slim\Psr7\Factory\RequestFactory;
......@@ -12,11 +13,15 @@ trait HttpClientAware {
$_default_http_client,
$_http_client_options;
static public function newHttpClient() {
static public function newHttpClient($client_type = 'curl') {
if (static::$_default_http_client)
return static::$_default_http_client;
if ($client_type != 'curl')
return new FileGetContents(new ResponseFactory(), ['allow_redirects' => true]);
$client = new Curl(new ResponseFactory(), static::_httpClientOptions());
$browser = new Browser($client, new RequestFactory());
return $browser;
......
......@@ -3,15 +3,15 @@ namespace Pellicule\Models;
use \Storm\Model\ModelAbstract;
class Media extends ModelAbstract {
use
\Pellicule\StormFileSystem,
\Pellicule\Traits\TimeSource;
const
SIZE_FULL = 'fullsize',
TYPE_COVER = 'cover',
TYPE_BACKCOVER = 'back_cover';
protected static $_file_system;
protected
$_table_name = 'media',
$_belongs_to = [ 'record' => ['model' => Record::class ] ],
......@@ -25,19 +25,6 @@ class Media extends ModelAbstract {
$_provider_headers;
/** @category testing */
public static function setFileSystem($filesystem) {
static::$_file_system = $filesystem;
}
public static function getFileSystem() {
return isset(static::$_file_system)
? static::$_file_system
: new \Pellicule\FileSystem();
}
public function getJsonAttributes() {
return array_intersect_key($this->getRawAttributes(),
['type' => 1,
......@@ -50,7 +37,7 @@ class Media extends ModelAbstract {
public function beforeSave() {
$datenow = (new \DateTime())->format('Y-m-d H:i:s');
$datenow = $this->getCurrentDateTime();
if (!$this->getCreatedAt())
$this->setCreatedAt($datenow);
$this->setUpdatedAt($datenow);
......@@ -59,7 +46,7 @@ class Media extends ModelAbstract {
public function afterSave() {
if (!$this->fileExists())
$this->getFileSystem()->download($this->getUrl(),
$this->getPelliculeFileSystem()->download($this->getUrl(),
$this->getFullsize(),
$this->getProviderHeaders());
}
......@@ -69,9 +56,10 @@ class Media extends ModelAbstract {
return $this->setFullsize($this->_getFilePathFromIdentifier($identifier));
}
public function _getFilePathFromIdentifier($identifier) {
public function _getFilePathFromIdentifier($identifier = "") {
return '/' . implode('/',
array_merge([$this->getSize(),
array_merge(['images',
$this->getSize(),
$this->getType()],
$this->splitId($identifier)))
. '/'. $identifier .".";
......@@ -81,7 +69,7 @@ class Media extends ModelAbstract {
public function fileExists() {
return $this->isNew()
? false
: $this->getFileSystem()->fileExists($this->getFilePath());
: $this->getPelliculeFileSystem()->fileExists($this->getFilePath());
}
......@@ -113,9 +101,7 @@ class Media extends ModelAbstract {
protected function getLocation() {
return $this->isNew()
? ''
: '/' . implode('/', array_merge([$this->getSize(),
$this->getType()],
$this->splitId())) . '/';
: $this->_getFilePathFromIdentifier();
}
......
<?php
namespace Pellicule;
trait StormFileSystem {
protected static $_file_system;
/** @category testing */
public static function setFileSystem($file_system) {
self::$_file_system = $file_system;
}
public static function getFileSystem() {
if (null !== self::$_file_system)
return self::$_file_system;
return new \Storm\FileSystem\Disk();
}
public static function getPelliculeFileSystem() {
if (null !== self::$_file_system)
return self::$_file_system;
return new \Pellicule\FileSystem();
}
}
\ No newline at end of file
<?php
/**
* Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* Encapsule les appels systeme temps pour pouvoir les surcharger pendant les tests
* @category testing
*/
namespace Pellicule;
class TimeSource {
const DAY_AND_HOURS_FORMAT = 'Y-m-d H:i:s';
/** @return int */
public function time() {
return time();
}
public function dateYmd() {
return $this->dateFormat('Y-m-d');
}
public function dateDayAndHours() {
return $this->dateFormat(static::DAY_AND_HOURS_FORMAT);
}
public function dateDayMonthYear() {
return $this->dateFormat('dmY');
}
public function dateFormat($format) {
return date($format, $this->time());
}
public function dateHttpHeader() {
return gmdate('D, d M Y H:i:s \G\M\T', $this->time());
}
public function date() {
$time = $this->time();
return $this->midnightTime(date('n', $time), date('j', $time), date('Y', $time));
}
public function mktime($hour, $minute, $second, $month, $day, $year) {
return mktime($hour, $minute, $second, $month, $day, $year);
}
public function nextDate() {
$time = $this->time();
return $this->midnightTime(date('n', $time),
date('j', $time) + 1,
date('Y', $time));
}
public function nextMonths($number_of_months=1) {
$time = $this->time();
return $this->midnightTime(date('n', $time) + $number_of_months,
date('j', $time),
date('Y', $time));
}
public function getMonth($month) {
$time = $this->time();
$year = date('m', $time) > $month ? date('Y', $time) + 1 : date('Y', $time);
return date('Y-m', $this->midnightTime($month, 1, $year));
}
protected function midnightTime($month, $day, $year) {
return mktime(0, 0, 0, $month, $day, $year);
}
public function lastYear() {
$time = $this->time();
return date('Y', $time) - 1 . date('-m-d', $time);
}
public function daysFrom($time) {
return (int)($this->hoursFrom($time) / 24);
}
public function hoursFrom($time) {
return (int)(($this->time() - $time) / 3660);
}
/**
* @var $min string '00:00'
* @var $max string '23:55'
* @var $step int
* @return array ['00:00' => '00h00', ...]
*/
public function getPossibleHours($min, $max, $step) {
if (!$this->isValidHourMinutes($min) || !$this->isValidHourMinutes($max))
return [];
list($min_h, $min_m) = explode(':', $min);
list($max_h, $max_m) = explode(':', $max);
if ($min_h > $max_h)
return [];
if (($min_h == $max_h) && ($min_m >= $max_m))
return [];
$date_time = new DateTime($min . ':00');
$current_day = $date_time->format('d');
$multioptions_array = [];
do {
$multioptions_array[$date_time->format('H:i')] = $date_time->format('H\hi');
$date_time->modify('+' . $step . ' min');
} while(($date_time->format('H:i') <= $max) && ($date_time->format('d') == $current_day));
return $multioptions_array;
}
public function isValidHourMinutes($value) {
return preg_match('/^(\d{2}):(\d{2})$/', $value, $matches)
? 24 > (int)$matches[1] && 60 > (int)$matches[2]
: false;
}
public function asDateTime() {
return DateTime::createFromFormat(static::DAY_AND_HOURS_FORMAT,
$this->dateDayAndHours());
}
}
\ No newline at end of file
<?php
/**
* Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
* along with BOKEH; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
namespace Pellicule\Traits;
trait TimeSource {
/** @var Pellicule\TimeSource */
protected static $_time_source;
/**
* @category testing
* @return int
*/
public function getCurrentTime() {
return self::getTimeSource()->time();
}
public static function getCurrentYear() {
return date('Y',self::getTimeSource()->time());
}
public static function getCurrentDate() {
return date('Y-m-d',self::getTimeSource()->time());
}
public static function getCurrentDateTime() {
return date('Y-m-d H:i:s',self::getTimeSource()->time());
}
public static function substractYearsToCurrentDate($days) {
return date('Y-m-d',strtotime('-'.(string)$days.' year', self::getTimeSource()->time()));
}
public static function addDaysToCurrentDate($days) {
$now = date('Y-m-d',strtotime((string)$days.' day', self::getTimeSource()->time()));
return $now;
}
/** @return \Pellicule\TimeSource */
public static function getTimeSource() {
if (null == self::$_time_source)
self::$_time_source = new \Pellicule\TimeSource();
return self::$_time_source;
}
/** @param $time_source \Pellicule\TimeSource */
public static function setTimeSource($time_source) {
self::$_time_source = $time_source;
}
}
......@@ -8,10 +8,11 @@ use Pellicule\Models\Media;
use Pellicule\Providers\Electre;
use Pellicule\Providers\Orb;
use Pellicule\FileSystem;
use \Storm\FileSystem\Volatile;
class FileSystemTest extends TestCase {
protected $_file_system;
public function setUp(){
parent::setUp();
......@@ -22,8 +23,10 @@ class FileSystemTest extends TestCase {
->whenCalled('sendRequest')
->answers($this->_forgePSR7Response(['content' => file_get_contents(realpath(dirname(__FILE__)) . '/' .'imagefullsize.jpg')]));
FileSystem::setFileSystem(new Volatile());
FileSystem::setBasePath('/tmp');
FileSystem::setDefaultHttpClient($this->_http_client);
$this->_file_system = new FileSystem();
$this->fixture(Record::class,
['id' => 1,
......@@ -37,6 +40,7 @@ class FileSystemTest extends TestCase {
'type' => 'cover',
'url' => 'http://image.org/city.jpg',
'provider' => 'Me',
'fullsize' => '/images/fullsize/cover/2/2/5/9/2259228194.jpg',
'created_at' => '2020-01-13 08:00:00',
'updated_at' => '2020-01-16 08:00:00'
]);
......@@ -45,12 +49,12 @@ class FileSystemTest extends TestCase {
/** @test */
public function fullsizeCoverDirectoryShouldBeCreated() {
$this->assertTrue($this->_root->hasChild('fullsize/cover/2/2/5/9/'));
$this->assertTrue($this->_file_system->fileExists('/tmp/images/fullsize/cover/2/2/5/9/'));
}
/** @test */
public function fullsizeCoverFileShouldBeCreated() {
$this->assertTrue($this->_root->hasChild('fullsize/cover/2/2/5/9/2259228194.jpg'));
$this->assertTrue($this->_file_system->fileExists('/tmp/images/fullsize/cover/2/2/5/9/2259228194.jpg'));
}
}
......@@ -6,6 +6,7 @@ use \Pellicule\Models\Record;
use \Pellicule\Models\Media;
use \Pellicule\FileSystem;
use \Pellicule\Providers\Provider;
use \Pellicule\Traits\TimeSource;
use \org\bovigo\vfs\vfsStream;
......@@ -22,6 +23,8 @@ class MediaLocalRecordTest extends TestCase {
->whenCalled('fileExists')->answers(true)
->whenCalled('baseUrl')->answers('https://localhost/pellicule/'));
Media::setTimeSource(new TimeSourceForTest('2020-01-21 08:00:00'));
$this->fixture(Record::class,
['id' => 1,
'isbn' => '9782259228190',
......@@ -35,7 +38,7 @@ class MediaLocalRecordTest extends TestCase {
'url' => 'http://image.org/city.jpg',
'provider' => 'Me',
'created_at' => '2020-01-13 08:00:00',
'updated_at' => (new \DateTime())->format('Y-m-d H:i:s')
'updated_at' => '2020-01-21 08:00:00'
]);
$this->fixture(Media::class,
......@@ -45,11 +48,15 @@ class MediaLocalRecordTest extends TestCase {
'url' => 'http://image.org/cityback.jpg',
'provider' => 'Me',
'created_at' => '2020-01-21 08:00:00',
'updated_at' => (new \DateTime())->format('Y-m-d H:i:s')
'updated_at' => '2020-01-21 08:00:00'
]);
}
public function tearDown(){
Media::setTimeSource(null);
Media::setFileSystem(null);
parent::tearDown();
}
public function possibleRecord1Urls() {
return [['/1.0/media/isbn/9782259228190'],
......@@ -68,13 +75,15 @@ class MediaLocalRecordTest extends TestCase {
[ 'url' => 'http://image.org/city.jpg',
'provider' => 'Me',
'created_at' => '2020-01-13 08:00:00',
'updated_at' => (new \DateTime())->format('Y-m-d H:i:s'),
'updated_at' => '2020-01-21 08:00:00',
'fullsize' => '',
'type' => 'cover'],
[ 'url' => 'http://image.org/cityback.jpg',
'type' => 'back_cover',
'provider' => 'Me',
'created_at' => '2020-01-21 08:00:00',
'updated_at' => (new \DateTime())->format('Y-m-d H:i:s')
'provider' => 'Me',
'fullsize' => '',
'created_at' => '2020-01-21 08:00:00',
'updated_at' => '2020-01-21 08:00:00',
]
]
];
......@@ -146,14 +155,14 @@ abstract class MediaWithElectreTestCase extends TestCase {
[ 'media' => [
['url'=> 'http://image.org/city.jpg',
'provider' => 'Electre',
'fullsize' => '/fullsize/cover/2/2/5/9/2259228194.',
'fullsize' => '/images/fullsize/cover/2/2/5/9/2259228194.',
'type' => 'cover',
'created_at' => (new \DateTime())->format('Y-m-d H:i:s'),
'updated_at' => (new \DateTime())->format('Y-m-d H:i:s')
],
['url' => 'http://image.org/cityback.jpg',
'provider' => 'Electre',
'fullsize' => '/fullsize/back_cover/2/2/5/9/2259228194.',
'fullsize' => '/images/fullsize/back_cover/2/2/5/9/2259228194.',
'type' => 'back_cover',
'created_at' => (new \DateTime())->format('Y-m-d H:i:s'),
'updated_at' => (new \DateTime())->format('Y-m-d H:i:s')
......@@ -410,11 +419,19 @@ abstract class MediaWithOrbTestCase extends TestCase {
parent::setUp();
Media::setFileSystem($this->mock()->whenCalled('fileExists')->answers(true));
Media::setTimeSource(new TimeSourceForTest('2020-01-01 00:00:00'));
$this->_http_client
->whenCalled('get')
->answers($this->_forgePSR7Response(['content' => zlib_encode('{"data":[{"ean13":"2259228234","images":{"front":{"original":{"src":"https:\/\/products-images.di-static.com\/image\/orb-9162f416a0b7b2e64a1f5480db8d4394\/2259228234-475x500-1.jpg"},"thumbnail":{"src":"https:\/\/products-images.di-static.com\/image\/orb-9162f416a0b7b2e64a1f5480db8d4394\/2259228234-120x160-1.jpg"}}}}]}',ZLIB_ENCODING_GZIP)]));
}
}
public function tearDown(){
Media::setTimeSource(null);
Media::setFileSystem(null);
parent::tearDown();
}
/** @test */
......@@ -423,10 +440,10 @@ abstract class MediaWithOrbTestCase extends TestCase {
[ 'media' => [
['url'=> 'https://products-images.di-static.com/image/orb-9162f416a0b7b2e64a1f5480db8d4394/2259228234-475x500-1.jpg',
'provider' => 'Orb',
'fullsize' => '/fullsize/cover/2/2/5/9/2259228234.',
'fullsize' => '/images/fullsize/cover/2/2/5/9/2259228234.',
'type' => 'cover',
'created_at' => (new \DateTime())->format('Y-m-d H:i:s'),
'updated_at' => (new \DateTime())->format('Y-m-d H:i:s')
'created_at' => '2020-01-01 00:00:00',
'updated_at' => '2020-01-01 00:00:00'
]
]
......
......@@ -2,6 +2,7 @@
error_reporting(E_ALL^E_DEPRECATED);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
ini_set('xdebug.remote_port', '9312');
ini_set('xdebug.remote_port', '9019');
ini_set('xdebug.remote_host', '10.13.78.15');
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/library/Class/TimeSourceForTest.php';
\ No newline at end of file
<?php
/**
* Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
*
* BOKEH is free software; you can redistribute it and/or modify
* it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
* the Free Software Foundation.
*
* There are special exceptions to the terms and conditions of the AGPL as it
* is applied to this software (see README file).
*
* BOKEH is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the