diff --git a/src/Storm/FileSystem/Abstract.php b/src/Storm/FileSystem/Abstract.php new file mode 100644 index 0000000000000000000000000000000000000000..397b679d9ffed3e91124808303f9d632b520b725 --- /dev/null +++ b/src/Storm/FileSystem/Abstract.php @@ -0,0 +1,54 @@ +<?php +/* +STORM is under the MIT License (MIT) + +Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + + +abstract class Storm_FileSystem_Abstract { + abstract public function fileExists($path); + + abstract public function fileGetContents($path); + + abstract public function fileGetSize($path); + + abstract public function filePutContents($path, $data); + + abstract public function fileMTime(string $path); + + abstract public function directoryNamesAt($path); + + abstract public function fileNamesAt($path); + + abstract public function rename(string $from, string $to) : bool; + + abstract public function isReadable(string $path) : bool; + + abstract public function isWritable(string $path) : bool; + + abstract public function fileGetContentAsArray(string $path); + + abstract public function delete(string $path) : bool; + + abstract public function readfile(string $path); +} diff --git a/src/Storm/FileSystem/Disk.php b/src/Storm/FileSystem/Disk.php index 20d36aece1c7878bc9b98bdc94e60ec215c6920c..452ad0e2f29a34bf299a607d9f88b0253bd744f8 100644 --- a/src/Storm/FileSystem/Disk.php +++ b/src/Storm/FileSystem/Disk.php @@ -24,7 +24,7 @@ THE SOFTWARE. */ -class Storm_FileSystem_Disk { +class Storm_FileSystem_Disk extends Storm_FileSystem_Abstract { public function fileExists($path) { return file_exists($path); } @@ -46,6 +46,41 @@ class Storm_FileSystem_Disk { } + public function fileMTime(string $path) { + return filemtime($path); + } + + + public function rename(string $from, string $to) : bool { + return rename($from, $to); + } + + + public function isReadable(string $path) : bool { + return is_readable($path); + } + + + public function isWritable(string $path) : bool { + return is_writable($path); + } + + + public function fileGetContentAsArray(string $path) { + return file($path); + } + + + public function delete(string $path) : bool { + return unlink($path); + } + + + public function readfile(string $path) { + return readfile($path); + } + + public function directoryNamesAt($path) { if (!file_exists($path)) return []; diff --git a/src/Storm/FileSystem/Volatile.php b/src/Storm/FileSystem/Volatile.php index 96375333af631fc56b6d63c2d293f7e22d76833e..d9623b75df35be2f9acdb85b1498ebb10903d0f5 100644 --- a/src/Storm/FileSystem/Volatile.php +++ b/src/Storm/FileSystem/Volatile.php @@ -24,7 +24,7 @@ THE SOFTWARE. */ -class Storm_FileSystem_Volatile { +class Storm_FileSystem_Volatile extends Storm_FileSystem_Abstract { protected $_current_directory; public function __construct() { @@ -46,9 +46,9 @@ class Storm_FileSystem_Volatile { } - public function touch($path) { + public function touch($path, $init_callback=null) { if ($directory = $this->asPath(dirname($path))->asDirectory()) - $directory->touch(basename($path)); + $directory->touch(basename($path), $init_callback); return $this; } @@ -79,12 +79,66 @@ class Storm_FileSystem_Volatile { } + public function fileMTime(string $path) { + return $this->fileExists($path) + ? $this->entryAt($path)->getMTime() + : 0; + } + + + public function rename(string $from, string $to) : bool { + if (!$this->fileExists($from)) + return false; + + $this->filePutContents($to, $this->fileGetContents($from)) + ->rm($from); + + return true; + } + + + public function isReadable(string $path) : bool { + return $this->fileExists($path); + } + + + public function isWritable(string $path) : bool { + return $this->fileExists($path); + } + + + public function fileGetContentAsArray(string $path) { + if (null === $content = $this->fileGetContents($path)) + return false; + + $lines = explode("\n", $content); + + return array_map(fn($line) => $line . "\n", + $lines); + } + + public function rm($path) { $this->asPath($path)->rm(); return $this; } + public function delete(string $path) : bool { + $this->rm($path); + return !$this->isReadable($path); + } + + + public function readfile(string $path) { + if (!$this->isReadable($path)) + return false; + + echo $this->fileGetContents($path); + return $this->fileGetSize($path); + } + + public function asPath($path) { return (new Storm_FileSystem_Volatile_Path($path, $this->_current_directory)); } @@ -107,10 +161,8 @@ class Storm_FileSystem_Volatile { public function fileNamesAt($path) { - $collection = $this->entryAt($path)->select( - function ($entry) { - return $entry->isFile(); - }); + $collection = $this->entryAt($path) + ->select(fn($entry) => $entry->isFile()); array_walk($collection, function (&$entry) {$entry = $entry->getName();}); return $collection; @@ -217,7 +269,8 @@ class Storm_FileSystem_Volatile_Path { abstract class Storm_FileSystem_Volatile_Entry { protected $_name, - $_parent; + $_parent, + $_mtime; public function __construct($name, $parent) { $this->_name = $name; @@ -246,7 +299,20 @@ abstract class Storm_FileSystem_Volatile_Entry { } + public function getMTime() : int { + return $this->_mtime ?? 0; + } + + + public function setMTime(int $mtime) : self { + $this->_mtime = $mtime; + return $this; + } + + abstract function isDirectory(); + + abstract function isFile(); } @@ -273,6 +339,9 @@ class Storm_FileSystem_Volatile_Directory extends Storm_FileSystem_Volatile_Entr public function getSubdirectoryNamed($name) { + if ('..' === $name) + return $this->_parent ?? $this; + return $this->fileExists($name) ? $this->_entries[$name] : null; } @@ -284,8 +353,11 @@ class Storm_FileSystem_Volatile_Directory extends Storm_FileSystem_Volatile_Entr } - public function touch($name) { - $this->_entries [$name]= new Storm_FileSystem_Volatile_File($name, $this); + public function touch($name, $init_callback=null) { + $this->_entries[$name] = $entry = new Storm_FileSystem_Volatile_File($name, $this); + if (null !== $init_callback) + $init_callback($entry); + return $this; } @@ -310,13 +382,20 @@ class Storm_FileSystem_Volatile_Directory extends Storm_FileSystem_Volatile_Entr public function isDirectory() { return true; } + + + public function isFile() { + return false; + } } class Storm_FileSystem_Volatile_File extends Storm_FileSystem_Volatile_Entry { - protected $_contents = ''; + protected + $_contents, + $_size; public function isDirectory() { return false; @@ -330,18 +409,24 @@ class Storm_FileSystem_Volatile_File extends Storm_FileSystem_Volatile_Entry { public function putContents($contents) { $this->_contents = $contents; + return $this; } public function getContents() { - return $this->_contents; + return $this->_contents ?? ''; } public function getSize() { - return strlen($this->_contents); + return null !== $this->_size + ? $this->_size + : strlen($this->_contents); } -} -?> \ No newline at end of file + public function setSize(int $size) : self { + $this->_size = $size; + return $this; + } +} diff --git a/tests/Storm/FileSystem/VolatileTest.php b/tests/Storm/FileSystem/VolatileTest.php index 91a091f52ffec7cb0b75e87d289a55725f821771..730691039196d735a6a2d8206fb846b130e6533c 100644 --- a/tests/Storm/FileSystem/VolatileTest.php +++ b/tests/Storm/FileSystem/VolatileTest.php @@ -25,10 +25,22 @@ THE SOFTWARE. */ -class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase { +abstract class Storm_FileSystem_VolatileTestCase extends PHPUnit_Framework_TestCase { + protected Storm_FileSystem_Volatile $_fs; + + public function setUp() { + parent::setUp(); + $this->_fs = new Storm_FileSystem_Volatile; + } +} + + + + +class Storm_FileSystem_VolatileBasicTest extends Storm_FileSystem_VolatileTestCase { public function setUp() { parent::setUp(); - $this->_fs = (new Storm_FileSystem_Volatile()) + $this->_fs ->mkdir('/public/bokeh/skins') ->cd('/public/bokeh/skins') ->mkdir('dark') @@ -36,7 +48,7 @@ class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase { ->touch('.htaccess') ->cd('light') - ->touch('style.css') + ->touch('style.css', fn($entry) => $entry->setMTime(22)) ->cd('/') ->mkdir('etc/') @@ -67,6 +79,13 @@ class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase { } + /** @test */ + public function notesTxtShouldNoLongerExistAfterDelete() { + $this->_fs->delete('/var/notes.txt'); + $this->assertFalse($this->_fs->fileExists('/var/notes.txt')); + } + + /** @test */ public function ghostGetContentsShouldAnswersNull() { $this->assertNull($this->_fs->fileGetContents('ghost.txt')); @@ -79,6 +98,42 @@ class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase { } + /** @test */ + public function styleCssMtimeShouldBe22() { + $this->assertEquals(22, $this->_fs->fileMTime('/public/bokeh/skins/light/style.css')); + } + + + /** @test */ + public function notExistingFileMtimeShouldBe0() { + $this->assertEquals(0, $this->_fs->fileMTime('/I/do/not/exist')); + } + + + /** @test */ + public function styleCssShouldBeReadable() { + $this->assertTrue($this->_fs->isReadable('/public/bokeh/skins/light/style.css')); + } + + + /** @test */ + public function notExistingFileShouldNotBeReadable() { + $this->assertFalse($this->_fs->isReadable('/I/do/not/exist')); + } + + + /** @test */ + public function styleCssShouldBeWritable() { + $this->assertTrue($this->_fs->isWritable('/public/bokeh/skins/light/style.css')); + } + + + /** @test */ + public function notExistingFileShouldNotBeWritable() { + $this->assertFalse($this->_fs->isWritable('/I/do/not/exist')); + } + + public function validPaths() { return [ ['/etc'], @@ -90,7 +145,7 @@ class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase { ['/var/www'], ['php/php.ini'], ['./php/php.ini'], -// ['../etc/php/php.ini'], + ['../etc/php/php.ini'], ['/public/bokeh/skins/light/style.css'] ]; } @@ -138,4 +193,115 @@ class Storm_FileSystem_VolatileTest extends PHPUnit_Framework_TestCase { $dirs = $this->_fs->directoryNamesAt('/public/bokeh/skins'); $this->assertEquals(['dark' => 'dark', 'light' => 'light'], $dirs); } +} + + + + +class Storm_FileSystem_VolatileRenameTest extends Storm_FileSystem_VolatileTestCase { + public function setUp() { + parent::setUp(); + $this->_fs + ->mkdir('/testing/transfert') + ->filePutContents('/testing/transfert/mysuperfile', 'my super content') + + ->mkdir('/testing/integration') + ; + } + + + /** @test */ + public function renameNotExistingFileShouldDoNothing() { + $this->_fs->rename('/I/do/not/exist', '/testing/integration/newname.txt'); + $this->assertFalse($this->_fs->fileExists('/testing/integration/newname.txt')); + } + + + /** @test */ + public function renamedMysuperfileToNewnameTxtShouldContainsMySuperContent() { + $this->_fs->rename('/testing/transfert/mysuperfile', '/testing/integration/newname.txt'); + $this->assertEquals('my super content', + $this->_fs->fileGetContents('/testing/integration/newname.txt')); + } +} + + + + +class Storm_FileSystem_VolatileGetContentsAsArrayTest extends Storm_FileSystem_VolatileTestCase { + public function setUp() { + parent::setUp(); + $this->_fs + ->mkdir('/testing/transfert') + ->filePutContents('/testing/transfert/mysuperfile', + implode("\n", ['first line', 'second line'])) + ; + } + + + /** @test */ + public function contentAsArrayOfNotExistingFileShouldBeFalse() { + $this->assertFalse($this->_fs->fileGetContentAsArray('/I/do/not/exist')); + } + + + /** @test */ + public function mysuperfileContentAsArrayShouldBeFirstAndSecondLine() { + $this->assertEquals(["first line\n", "second line\n"], + $this->_fs->fileGetContentAsArray('/testing/transfert/mysuperfile')); + } +} + + + + +class Storm_FileSystem_VolatileReadfileTest extends Storm_FileSystem_VolatileTestCase { + protected $_buffer; + + public function setUp() { + parent::setUp(); + $this->_fs + ->mkdir('/testing/transfert') + ->filePutContents('/testing/transfert/mysuperfile', '>>>> Mega super content <<<<') + ; + } + + + protected function _readFileWithBuffering($path) { + ob_start(); + try { + $result = $this->_fs->readfile($path); + $this->_buffer = ob_get_clean(); + return $result; + } catch(Exception $e) { + ob_end_clean(); + throw $e; + } + } + + + /** @test */ + public function readFileOfNotExistingFileShouldBeFalse() { + $this->assertFalse($this->_readFileWithBuffering('/I/do/not/exist')); + } + + + /** @test */ + public function readFileOfNotExistingFileShouldOutputNothing() { + $this->_readFileWithBuffering('/I/do/not/exist'); + $this->assertEquals('', $this->_buffer); + } + + + /** @test */ + public function readFileOfMysuperfileShouldBeContentLength() { + $this->assertEquals(28, $this->_readFileWithBuffering('/testing/transfert/mysuperfile')); + } + + + /** @test */ + public function readFileOfMysuperfileShouldOutputMegaSuperContent() { + $this->_readFileWithBuffering('/testing/transfert/mysuperfile'); + $this->assertEquals('>>>> Mega super content <<<<', $this->_buffer); + } } \ No newline at end of file