Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Open sidebar
afi
Pellicule
Commits
d83c0efe
Commit
d83c0efe
authored
Aug 26, 2020
by
CHRISTOPHE THERY
Browse files
task#105492 : managing FileSystem Access and Persistence
parent
261bf980
Pipeline
#10986
passed with stage
in 41 seconds
Changes
11
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
452 additions
and
96 deletions
+452
-96
.gitignore
.gitignore
+7
-0
src/FileSystem.php
src/FileSystem.php
+44
-12
src/HttpClientAware.php
src/HttpClientAware.php
+9
-2
src/Models/Media.php
src/Models/Media.php
+23
-53
src/StormFileSystem.php
src/StormFileSystem.php
+25
-0
src/TimeSource.php
src/TimeSource.php
+158
-0
src/Traits/TimeSource.php
src/Traits/TimeSource.php
+76
-0
tests/FileSystemTest.php
tests/FileSystemTest.php
+27
-15
tests/MediaTest.php
tests/MediaTest.php
+30
-13
tests/bootstrap.php
tests/bootstrap.php
+2
-1
tests/library/Class/TimeSourceForTest.php
tests/library/Class/TimeSourceForTest.php
+51
-0
No files found.
.gitignore
View file @
d83c0efe
*~
GPATH
GRTAGS
GTAGS
conf/config.php
conf/database.php
conf/http_client.php
conf/persistence.php
src/FileSystem.php
View file @
d83c0efe
<?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,33 +29,63 @@ 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
);
}
public
function
fileExtension
(
$response
){
foreach
(
$response
->
getHeader
(
'content-type'
)
as
$key
=>
$value
)
if
(
$extension
=
$this
->
_getExtensionFromContentType
(
$value
))
return
"."
.
$extension
;
}
protected
function
_getExtensionFromContentType
(
$value
){
$elements
=
explode
(
"/"
,
$value
);
if
(
isset
(
$elements
[
1
])
&&
in_array
(
$elements
[
1
],[
'jpg'
,
'bmp'
,
'gif'
,
'png'
,
'jpeg'
]))
return
$elements
[
1
];
return
""
;
}
public
function
download
(
$url
,
$path
,
$headers
=
[])
{
public
function
download
(
$media
)
{
$url
=
$media
->
getUrl
();
$path
=
$media
->
getFullsize
();
$headers
=
$media
->
getProviderHeaders
();
if
(
!
$url
||
!
$path
)
return
;
$directory
=
pathinfo
(
static
::
$_base_path
.
$path
,
PATHINFO_DIRNAME
);
if
(
false
===
\
file
_e
xists
(
$directory
))
\
mkdir
(
$directory
,
0755
,
true
);
if
(
false
===
$this
->
file
E
xists
(
$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
->
newFileGetContents
();
$response
=
$client
->
sendRequest
(
$request
,
[
'timeout'
=>
4
]);
if
((
$status
=
$response
->
getStatusCode
())
!=
StatusCodeInterface
::
STATUS_OK
)
{
echo
'file was not download
ed
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
());
$extension
=
$this
->
fileExtension
(
$response
);
$this
->
getFileSystem
()
->
filePutContents
(
static
::
$_base_path
.
$path
.
$extension
,
$response
->
getBody
());
$media
->
setFullsize
(
$path
.
$extension
);
}
}
src/HttpClientAware.php
View file @
d83c0efe
...
...
@@ -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
;
...
...
@@ -17,9 +18,15 @@ trait HttpClientAware {
return
static
::
$_default_http_client
;
$client
=
new
Curl
(
new
ResponseFactory
(),
static
::
_httpClientOptions
());
$browser
=
new
Browser
(
$client
,
new
RequestFactory
());
return
new
Browser
(
$client
,
new
RequestFactory
());
}
static
public
function
newFileGetContents
()
{
if
(
static
::
$_default_http_client
)
return
static
::
$_default_http_client
;
return
$browser
;
return
new
FileGetContents
(
new
ResponseFactory
(),
[
'allow_redirects'
=>
true
])
;
}
...
...
src/Models/Media.php
View file @
d83c0efe
...
...
@@ -3,38 +3,35 @@ 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
]
],
$_default_attribute_values
=
[
'id'
=>
0
,
$_default_attribute_values
=
[
'id'
=>
0
,
'fullsize'
=>
''
,
'provider_headers'
=>
''
,
'created_at'
=>
''
,
'updated_at'
=>
''
],
$_provider_headers
;
'updated_at'
=>
''
],
$_provider_headers
=
[];
/** @category testing */
public
static
function
setFileSystem
(
$filesystem
)
{
static
::
$_file_system
=
$filesystem
;
public
function
getProviderHeaders
()
{
return
$this
->
_provider_headers
;
}
public
static
function
getFileSystem
()
{
return
isset
(
static
::
$_file_system
)
?
static
::
$_file_system
:
new
\
Pellicule\FileSystem
();
public
function
setProviderHeaders
(
$headers
)
{
$this
->
_provider_headers
=
$headers
;
return
$this
;
}
...
...
@@ -50,18 +47,14 @@ 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
);
}
public
function
afterSave
()
{
if
(
!
$this
->
fileExists
())
$this
->
getFileSystem
()
->
download
(
$this
->
getUrl
(),
$this
->
getFullsize
(),
$this
->
getProviderHeaders
());
$this
->
getPelliculeFileSystem
()
->
download
(
$this
);
}
...
...
@@ -69,19 +62,19 @@ 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
.
"."
;
.
'/'
.
$identifier
;
}
public
function
fileExists
()
{
return
$this
->
isNew
()
?
false
:
$this
->
getFileSystem
()
->
fileExists
(
$this
->
getFilePath
());
return
$this
->
getPelliculeFileSystem
()
->
fileExists
(
$this
->
getFullsize
());
}
...
...
@@ -98,11 +91,6 @@ class Media extends ModelAbstract {
}
public
function
getFilePath
()
{
return
$this
->
getLocation
()
.
$this
->
getLocalFileName
();
}
public
function
splitId
(
$identifier
=
""
)
{
return
array_slice
(
str_split
(
sprintf
(
'%04d'
,
(
$identifier
?
$identifier
:
$this
->
getId
()))),
...
...
@@ -110,25 +98,7 @@ class Media extends ModelAbstract {
}
protected
function
getLocation
()
{
return
$this
->
isNew
()
?
''
:
'/'
.
implode
(
'/'
,
array_merge
([
$this
->
getSize
(),
$this
->
getType
()],
$this
->
splitId
()))
.
'/'
;
}
protected
function
getLocalFileName
()
{
return
$this
->
isNew
()
?
''
:
$this
->
getId
()
.
'.'
.
$this
->
getExtension
();
}
protected
function
getExtension
()
{
return
$this
->
hasUrl
()
?
pathinfo
(
$this
->
getUrl
(),
PATHINFO_EXTENSION
)
:
''
;
protected
function
getLocation
(
$identifier
=
""
)
{
return
$this
->
_getFilePathFromIdentifier
(
$identifier
);
}
}
src/StormFileSystem.php
0 → 100644
View file @
d83c0efe
<?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
src/TimeSource.php
0 → 100644
View file @
d83c0efe
<?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
src/Traits/TimeSource.php
0 → 100644
View file @
d83c0efe
<?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
;
}
}
tests/FileSystemTest.php
View file @
d83c0efe
...
...
@@ -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
{
class
FileSystemMediaDownloadTest
extends
TestCase
{
protected
$_file_system
;
public
function
setUp
(){
parent
::
setUp
();
...
...
@@ -20,10 +21,13 @@ class FileSystemTest extends TestCase {
$this
->
_http_client
->
whenCalled
(
'sendRequest'
)
->
answers
(
$this
->
_forgePSR7Response
([
'content'
=>
file_get_contents
(
realpath
(
dirname
(
__FILE__
))
.
'/'
.
'imagefullsize.jpg'
)]));
->
answers
(
$this
->
_forgePSR7Response
([
'headers'
=>
(
new
\
Slim\Psr7\Headers
)
->
addHeader
(
'Content-Type'
,
'image/jpg'
),
'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
,
...
...
@@ -31,26 +35,34 @@ class FileSystemTest extends TestCase {
'ean'
=>
'9782259228190'
,
'ark'
=>
'https://catalogue.bnf.fr/ark:/12148/cb445155569'
]);
$this
->
fixture
(
Media
::
class
,
[
'id'
=>
135
,
'record_id'
=>
1
,
'type'
=>
'cover'
,
'url'
=>
'http://image.org/city.jpg'
,
'provider'
=>
'Me'
,
'created_at'
=>
'2020-01-13 08:00:00'
,
'updated_at'
=>
'2020-01-16 08:00:00'
]);
(
new
Media
())
->
updateAttributes
([
'record_id'
=>
1
,
'type'
=>
'cover'
,
'url'
=>
'http://image.org/city.jpg'
,
'provider'
=>
'Me'
,
'fullsize'
=>
'/images/fullsize/cover/2/2/5/9/2259228194'
,
'created_at'
=>
'2020-01-13 08:00:00'
,
'updated_at'
=>
'2020-01-16 08:00:00'
])
->
save
();
}
/** @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'
));
}
/** @test */
public
function
mediaShouldHaveFullsizeWithExtension
()
{
$this
->
assertEquals
(
'/images/fullsize/cover/2/2/5/9/2259228194.jpg'
,
Media
::
find
(
1
)
->
getFullsize
());
}
}
tests/MediaTest.php
View file @
d83c0efe
...
...
@@ -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'
)