From 37068539bdddbe2558d0aa597b30eaa0e24378d4 Mon Sep 17 00:00:00 2001 From: euromark Date: Mon, 10 Sep 2012 10:23:52 +0200 Subject: [PATCH] allow Folder to merge recursivly and add scheme option --- lib/Cake/Test/Case/Utility/FolderTest.php | 346 +++++++++++++++++----- lib/Cake/Utility/Folder.php | 45 ++- 2 files changed, 307 insertions(+), 84 deletions(-) diff --git a/lib/Cake/Test/Case/Utility/FolderTest.php b/lib/Cake/Test/Case/Utility/FolderTest.php index 76ee08af4..0d86286cb 100644 --- a/lib/Cake/Test/Case/Utility/FolderTest.php +++ b/lib/Cake/Test/Case/Utility/FolderTest.php @@ -29,11 +29,16 @@ class FolderTest extends CakeTestCase { protected static $_tmp = array(); /** - * Save the directory names in TMP + * Save the directory names in TMP and make sure default directories exist * * @return void */ public static function setUpBeforeClass() { + $dirs = array('cache', 'logs', 'sessions', 'tests'); + foreach ($dirs as $dir) { + new Folder(TMP . $dir, true); + } + foreach (scandir(TMP) as $file) { if (is_dir(TMP . $file) && !in_array($file, array('.', '..'))) { self::$_tmp[] = $file; @@ -803,6 +808,62 @@ class FolderTest extends CakeTestCase { /** * testCopy method * + * Verify that subdirectories existing in both destination and source directory + * are merged recursivly. + * + */ + public function testCopy() { + extract($this->_setupFilesystem()); + + $Folder = new Folder($folderOne); + $result = $Folder->copy($folderThree); + $this->assertTrue($result); + $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); + $this->assertTrue(file_exists($folderThree . DS . 'folderA' . DS . 'fileA.php')); + + $Folder = new Folder($folderTwo); + $result = $Folder->copy($folderThree); + $this->assertTrue($result); + $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); + $this->assertTrue(file_exists($folderThree . DS . 'file2.php')); + $this->assertTrue(file_exists($folderThree . DS . 'folderA' . DS . 'fileA.php')); + $this->assertTrue(file_exists($folderThree . DS . 'folderB' . DS . 'fileB.php')); + + $Folder = new Folder($path); + $Folder->delete(); + } + +/** + * testCopyWithMerge method + * + * Verify that subdirectories existing in both destination and source directory + * are merged recursivly. + * + */ + public function testCopyWithMerge() { + extract($this->_setupFilesystem()); + + $Folder = new Folder($folderOne); + $result = $Folder->copy($folderThree); + $this->assertTrue($result); + $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); + $this->assertTrue(file_exists($folderThree . DS . 'folderA' . DS . 'fileA.php')); + + $Folder = new Folder($folderTwo); + $result = $Folder->copy(array('to' => $folderThree, 'scheme' => Folder::MERGE)); + $this->assertTrue($result); + $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); + $this->assertTrue(file_exists($folderThree . DS . 'file2.php')); + $this->assertTrue(file_exists($folderThree . DS . 'folderA' . DS . 'fileA.php')); + $this->assertTrue(file_exists($folderThree . DS . 'folderB' . DS . 'fileB.php')); + + $Folder = new Folder($path); + $Folder->delete(); + } + +/** + * testCopyWithSkip method + * * Verify that directories and files are copied recursively * even if the destination directory already exists. * Subdirectories existing in both destination and source directory @@ -810,125 +871,254 @@ class FolderTest extends CakeTestCase { * * @return void */ - public function testCopy() { - $path = TMP . 'folder_test'; - $folderOne = $path . DS . 'folder1'; - $folderTwo = $folderOne . DS . 'folder2'; - $folderThree = $path . DS . 'folder3'; - $fileOne = $folderOne . DS . 'file1.php'; - $fileTwo = $folderTwo . DS . 'file2.php'; + public function testCopyWithSkip() { + extract($this->_setupFilesystem()); + + $Folder = new Folder($folderOne); + $result = $Folder->copy(array('to' => $folderTwo, 'scheme' => Folder::SKIP)); + $this->assertTrue($result); + $this->assertTrue(file_exists($folderTwo . DS . 'file1.php')); + $this->assertTrue(file_exists($folderTwo . DS . 'folderA' . DS . 'fileA.php')); + + $Folder = new Folder($folderTwo); + $Folder->delete(); + + $Folder = new Folder($folderOne); + $result = $Folder->copy(array('to' => $folderTwo, 'scheme' => Folder::SKIP)); + $this->assertTrue($result); + $this->assertTrue(file_exists($folderTwo . DS . 'file1.php')); + $this->assertTrue(file_exists($folderTwo . DS . 'folderA' . DS . 'fileA.php')); + + $Folder = new Folder($folderTwo); + $Folder->delete(); - new Folder($path, true); - new Folder($folderOne, true); new Folder($folderTwo, true); - new Folder($folderThree, true); - touch($fileOne); - touch($fileTwo); + new Folder($folderTwo . DS . 'folderB', true); + file_put_contents($folderTwo . DS . 'file2.php', 'touched'); + file_put_contents($folderTwo . DS . 'folderB' . DS . 'fileB.php', 'untouched'); - $Folder = new Folder($folderOne); - $result = $Folder->copy($folderThree); + $Folder = new Folder($folderTwo); + $result = $Folder->copy(array('to' => $folderThree, 'scheme' => Folder::SKIP)); $this->assertTrue($result); - $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); - $this->assertTrue(file_exists($folderThree . DS . 'folder2' . DS . 'file2.php')); - - $Folder = new Folder($folderThree); - $Folder->delete(); - - $Folder = new Folder($folderOne); - $result = $Folder->copy($folderThree); - $this->assertTrue($result); - $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); - $this->assertTrue(file_exists($folderThree . DS . 'folder2' . DS . 'file2.php')); - - $Folder = new Folder($folderThree); - $Folder->delete(); - - new Folder($folderThree, true); - new Folder($folderThree . DS . 'folder2', true); - file_put_contents($folderThree . DS . 'folder2' . DS . 'file2.php', 'untouched'); - - $Folder = new Folder($folderOne); - $result = $Folder->copy($folderThree); - $this->assertTrue($result); - $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); - $this->assertEquals('untouched', file_get_contents($folderThree . DS . 'folder2' . DS . 'file2.php')); + $this->assertTrue(file_exists($folderThree . DS . 'file2.php')); + $this->assertEquals('touched', file_get_contents($folderThree . DS . 'file2.php')); + $this->assertEquals('untouched', file_get_contents($folderThree . DS . 'folderB' . DS . 'fileB.php')); $Folder = new Folder($path); $Folder->delete(); } +/** + * testCopyWithOverwrite + * + * Verify that subdirectories existing in both destination and source directory + * are overwritten/replaced recursivly. + * + * $path: folder_test/ + * $folderOne: folder_test/folder1/ + * - file1.php + * $folderTwo: folder_test/folder2/ + * - file2.php + * $folderThree: folder_test/folder1/folder3/ + * - file3.php + * $folderFour: folder_test/folder2/folder4/ + * - file4.php + * $folderThree: folder_test/folder5/ + */ + function testCopyWithOverwrite() { + extract($this->_setupFilesystem()); + + $Folder = new Folder($folderOne); + $result = $Folder->copy(array('to' => $folderThree, 'scheme' => Folder::OVERWRITE)); + + $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); + $this->assertTrue(file_exists($folderThree . DS . 'folderA' . DS . 'fileA.php')); + + $Folder = new Folder($folderTwo); + $result = $Folder->copy(array('to' => $folderThree, 'scheme' => Folder::OVERWRITE)); + $this->assertTrue($result); + + $this->assertTrue(file_exists($folderThree . DS . 'folderA' . DS . 'fileA.php')); + + $Folder = new Folder($folderOne); + unlink($fileOneA); + $result = $Folder->copy(array('to' => $folderThree, 'scheme' => Folder::OVERWRITE)); + $this->assertTrue($result); + + $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); + $this->assertTrue(file_exists($folderThree . DS . 'file2.php')); + $this->assertTrue(!file_exists($folderThree . DS . 'folderA' . DS . 'fileA.php')); + $this->assertTrue(file_exists($folderThree . DS . 'folderB' . DS . 'fileB.php')); + + $Folder = new Folder($path); + $Folder->delete(); + } + +/** + * Setup filesystem for copy tests + * + * @return void + */ + protected function _setupFilesystem() { + $path = TMP . 'folder_test'; + + $folderOne = $path . DS . 'folder1'; + $folderOneA = $folderOne . DS . 'folderA'; + $folderTwo = $path . DS . 'folder2'; + $folderTwoB = $folderTwo . DS . 'folderB'; + $folderThree = $path . DS . 'folder3'; + + $fileOne = $folderOne . DS . 'file1.php'; + $fileTwo = $folderTwo . DS . 'file2.php'; + $fileOneA = $folderOneA . DS . 'fileA.php'; + $fileTwoB = $folderTwoB . DS . 'fileB.php'; + + new Folder($path, true); + new Folder($folderOne, true); + new Folder($folderOneA, true); + new Folder($folderTwo, true); + new Folder($folderTwoB, true); + new Folder($folderThree, true); + touch($fileOne); + touch($fileTwo); + touch($fileOneA); + touch($fileTwoB); + + return compact( + 'path', + 'folderOne', 'folderOneA', 'folderTwo', 'folderTwoB', 'folderThree', + 'fileOne', 'fileOneA', 'fileTwo', 'fileTwoB'); + } + /** * testMove method * * Verify that directories and files are moved recursively * even if the destination directory already exists. * Subdirectories existing in both destination and source directory - * are skipped and not merged or overwritten. + * are merged recursivly. * * @return void */ public function testMove() { - $path = TMP . 'folder_test'; - $folderOne = $path . DS . 'folder1'; - $folderTwo = $folderOne . DS . 'folder2'; - $folderThree = $path . DS . 'folder3'; - $fileOne = $folderOne . DS . 'file1.php'; - $fileTwo = $folderTwo . DS . 'file2.php'; - - new Folder($path, true); - new Folder($folderOne, true); - new Folder($folderTwo, true); - new Folder($folderThree, true); - touch($fileOne); - touch($fileTwo); + extract($this->_setupFilesystem()); $Folder = new Folder($folderOne); - $result = $Folder->move($folderThree); + $result = $Folder->move($folderTwo); $this->assertTrue($result); - $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); - $this->assertTrue(is_dir($folderThree . DS . 'folder2')); - $this->assertTrue(file_exists($folderThree . DS . 'folder2' . DS . 'file2.php')); + $this->assertTrue(file_exists($folderTwo . DS . 'file1.php')); + $this->assertTrue(is_dir($folderTwo . DS . 'folderB')); + $this->assertTrue(file_exists($folderTwo . DS . 'folderB' . DS . 'fileB.php')); $this->assertFalse(file_exists($fileOne)); - $this->assertFalse(file_exists($folderTwo)); - $this->assertFalse(file_exists($fileTwo)); + $this->assertTrue(file_exists($folderTwo . DS . 'folderA')); + $this->assertFalse(file_exists($folderOneA)); + $this->assertFalse(file_exists($fileOneA)); - $Folder = new Folder($folderThree); + $Folder = new Folder($folderTwo); $Folder->delete(); new Folder($folderOne, true); - new Folder($folderTwo, true); + new Folder($folderOneA, true); touch($fileOne); - touch($fileTwo); + touch($fileOneA); $Folder = new Folder($folderOne); - $result = $Folder->move($folderThree); + $result = $Folder->move($folderTwo); $this->assertTrue($result); - $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); - $this->assertTrue(is_dir($folderThree . DS . 'folder2')); - $this->assertTrue(file_exists($folderThree . DS . 'folder2' . DS . 'file2.php')); + $this->assertTrue(file_exists($folderTwo . DS . 'file1.php')); + $this->assertTrue(is_dir($folderTwo . DS . 'folderA')); + $this->assertTrue(file_exists($folderTwo . DS . 'folderA' . DS . 'fileA.php')); $this->assertFalse(file_exists($fileOne)); - $this->assertFalse(file_exists($folderTwo)); - $this->assertFalse(file_exists($fileTwo)); + $this->assertFalse(file_exists($folderOneA)); + $this->assertFalse(file_exists($fileOneA)); - $Folder = new Folder($folderThree); + $Folder = new Folder($folderTwo); $Folder->delete(); new Folder($folderOne, true); + new Folder($folderOneA, true); new Folder($folderTwo, true); - new Folder($folderThree, true); - new Folder($folderThree . DS . 'folder2', true); + new Folder($folderTwoB, true); touch($fileOne); - touch($fileTwo); - file_put_contents($folderThree . DS . 'folder2' . DS . 'file2.php', 'untouched'); + touch($fileOneA); + new Folder($folderOne . DS . 'folderB', true); + touch($folderOne . DS . 'folderB' . DS . 'fileB.php'); + file_put_contents($folderTwoB . DS . 'fileB.php', 'untouched'); $Folder = new Folder($folderOne); - $result = $Folder->move($folderThree); + $result = $Folder->move($folderTwo); $this->assertTrue($result); - $this->assertTrue(file_exists($folderThree . DS . 'file1.php')); - $this->assertEquals('untouched', file_get_contents($folderThree . DS . 'folder2' . DS . 'file2.php')); + $this->assertTrue(file_exists($folderTwo . DS . 'file1.php')); + $this->assertEquals('', file_get_contents($folderTwoB . DS . 'fileB.php')); $this->assertFalse(file_exists($fileOne)); - $this->assertFalse(file_exists($folderTwo)); - $this->assertFalse(file_exists($fileTwo)); + $this->assertFalse(file_exists($folderOneA)); + $this->assertFalse(file_exists($fileOneA)); + + $Folder = new Folder($path); + $Folder->delete(); + } + +/** + * testMoveWithSkip method + * + * Verify that directories and files are moved recursively + * even if the destination directory already exists. + * Subdirectories existing in both destination and source directory + * are skipped and not merged or overwritten. + * + * @return void + */ + public function testMoveWithSkip() { + extract($this->_setupFilesystem()); + + $Folder = new Folder($folderOne); + $result = $Folder->move(array('to' => $folderTwo, 'scheme' => Folder::SKIP)); + $this->assertTrue($result); + $this->assertTrue(file_exists($folderTwo . DS . 'file1.php')); + $this->assertTrue(is_dir($folderTwo . DS . 'folderB')); + $this->assertTrue(file_exists($folderTwoB . DS . 'fileB.php')); + $this->assertFalse(file_exists($fileOne)); + $this->assertFalse(file_exists($folderOneA)); + $this->assertFalse(file_exists($fileOneA)); + + $Folder = new Folder($folderTwo); + $Folder->delete(); + + new Folder($folderOne, true); + new Folder($folderOneA, true); + new Folder($folderTwo, true); + touch($fileOne); + touch($fileOneA); + + $Folder = new Folder($folderOne); + $result = $Folder->move(array('to' => $folderTwo, 'scheme' => Folder::SKIP)); + $this->assertTrue($result); + $this->assertTrue(file_exists($folderTwo . DS . 'file1.php')); + $this->assertTrue(is_dir($folderTwo . DS . 'folderA')); + $this->assertTrue(file_exists($folderTwo . DS . 'folderA' . DS . 'fileA.php')); + $this->assertFalse(file_exists($fileOne)); + $this->assertFalse(file_exists($folderOneA)); + $this->assertFalse(file_exists($fileOneA)); + + $Folder = new Folder($folderTwo); + $Folder->delete(); + + new Folder($folderOne, true); + new Folder($folderOneA, true); + new Folder($folderTwo, true); + new Folder($folderTwoB, true); + touch($fileOne); + touch($fileOneA); + file_put_contents($folderTwoB . DS . 'fileB.php', 'untouched'); + + $Folder = new Folder($folderOne); + $result = $Folder->move(array('to' => $folderTwo, 'scheme' => Folder::SKIP)); + $this->assertTrue($result); + $this->assertTrue(file_exists($folderTwo . DS . 'file1.php')); + $this->assertEquals('untouched', file_get_contents($folderTwoB . DS . 'fileB.php')); + $this->assertFalse(file_exists($fileOne)); + $this->assertFalse(file_exists($folderOneA)); + $this->assertFalse(file_exists($fileOneA)); $Folder = new Folder($path); $Folder->delete(); diff --git a/lib/Cake/Utility/Folder.php b/lib/Cake/Utility/Folder.php index 08895bf2e..e331bd2d4 100644 --- a/lib/Cake/Utility/Folder.php +++ b/lib/Cake/Utility/Folder.php @@ -21,6 +21,30 @@ */ class Folder { +/** + * Default scheme for Folder::copy + * Recursivly merges subfolders with the same name + * + * @constant MERGE + */ + const MERGE = 'merge'; + +/** + * Overwrite scheme for Folder::copy + * subfolders with the same name will be replaced + * + * @constant OVERWRITE + */ + const OVERWRITE = 'overwrite'; + +/** + * Skip scheme for Folder::copy + * if a subfolder with the same name exists it will be skipped + * + * @constant SKIP + */ + const SKIP = 'skip'; + /** * Path to Folder. * @@ -594,6 +618,7 @@ class Folder { * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd(). * - `mode` The mode to copy the files/directories with. * - `skip` Files/directories to skip. + * - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP * * @param array|string $options Either an array of options (see above) or a string of the destination directory. * @return boolean Success @@ -608,7 +633,7 @@ class Folder { $to = $options; $options = array(); } - $options = array_merge(array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()), $options); + $options = array_merge(array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array(), 'scheme' => Folder::MERGE), $options); $fromDir = $options['from']; $toDir = $options['to']; @@ -630,10 +655,10 @@ class Folder { $exceptions = array_merge(array('.', '..', '.svn'), $options['skip']); if ($handle = @opendir($fromDir)) { - while (false !== ($item = readdir($handle))) { - if (!in_array($item, $exceptions)) { - $from = Folder::addPathElement($fromDir, $item); - $to = Folder::addPathElement($toDir, $item); + while (($item = readdir($handle)) !== false) { + $to = Folder::addPathElement($toDir, $item); + if (($options['scheme'] != Folder::SKIP || !is_dir($to)) && !in_array($item, $exceptions)) { + $from = Folder::addPathElement($fromDir, $item); if (is_file($from)) { if (copy($from, $to)) { chmod($to, intval($mode, 8)); @@ -643,6 +668,10 @@ class Folder { $this->_errors[] = __d('cake_dev', '%s NOT copied to %s', $from, $to); } } + + if (is_dir($from) && file_exists($to) && $options['scheme'] == Folder::OVERWRITE) { + $this->delete($to); + } if (is_dir($from) && !file_exists($to)) { $old = umask(0); @@ -657,6 +686,9 @@ class Folder { } else { $this->_errors[] = __d('cake_dev', '%s not created', $to); } + } elseif (is_dir($from) && $options['scheme'] == Folder::MERGE) { + $options = array_merge($options, array('to' => $to, 'from' => $from)); + $this->copy($options); } } } @@ -680,8 +712,9 @@ class Folder { * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd(). * - `chmod` The mode to copy the files/directories with. * - `skip` Files/directories to skip. + * - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP * - * @param array $options (to, from, chmod, skip) + * @param array $options (to, from, chmod, skip, scheme) * @return boolean Success * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::move */