Fix restoring files from trash with unique name
When restoring a file, a unique name needs to be generated if a file with the same name already exists. Also fixed the restore() method to return false if the file to restore does not exist. Added unit tests to cover restore cases.
This commit is contained in:
parent
e1923bac07
commit
f86699cd48
2 changed files with 338 additions and 6 deletions
|
@ -277,16 +277,16 @@ class Trashbin {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* restore files from trash bin
|
* Restore a file or folder from trash bin
|
||||||
*
|
*
|
||||||
* @param string $file path to the deleted file
|
* @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
|
||||||
* @param string $filename name of the file
|
* including the timestamp suffix ".d12345678"
|
||||||
* @param int $timestamp time when the file was deleted
|
* @param string $filename name of the file/folder
|
||||||
|
* @param int $timestamp time when the file/folder was deleted
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool true on success, false otherwise
|
||||||
*/
|
*/
|
||||||
public static function restore($file, $filename, $timestamp) {
|
public static function restore($file, $filename, $timestamp) {
|
||||||
|
|
||||||
$user = \OCP\User::getUser();
|
$user = \OCP\User::getUser();
|
||||||
$view = new \OC\Files\View('/' . $user);
|
$view = new \OC\Files\View('/' . $user);
|
||||||
|
|
||||||
|
@ -311,6 +311,9 @@ class Trashbin {
|
||||||
|
|
||||||
$source = \OC\Files\Filesystem::normalizePath('files_trashbin/files/' . $file);
|
$source = \OC\Files\Filesystem::normalizePath('files_trashbin/files/' . $file);
|
||||||
$target = \OC\Files\Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
|
$target = \OC\Files\Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
|
||||||
|
if (!$view->file_exists($source)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
$mtime = $view->filemtime($source);
|
$mtime = $view->filemtime($source);
|
||||||
|
|
||||||
// restore file
|
// restore file
|
||||||
|
@ -762,6 +765,8 @@ class Trashbin {
|
||||||
$name = pathinfo($filename, PATHINFO_FILENAME);
|
$name = pathinfo($filename, PATHINFO_FILENAME);
|
||||||
$l = \OC::$server->getL10N('files_trashbin');
|
$l = \OC::$server->getL10N('files_trashbin');
|
||||||
|
|
||||||
|
$location = '/' . trim($location, '/');
|
||||||
|
|
||||||
// if extension is not empty we set a dot in front of it
|
// if extension is not empty we set a dot in front of it
|
||||||
if ($ext !== '') {
|
if ($ext !== '') {
|
||||||
$ext = '.' . $ext;
|
$ext = '.' . $ext;
|
||||||
|
|
|
@ -39,6 +39,11 @@ class Test_Trashbin extends \Test\TestCase {
|
||||||
private static $rememberRetentionObligation;
|
private static $rememberRetentionObligation;
|
||||||
private static $rememberAutoExpire;
|
private static $rememberAutoExpire;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private static $trashBinStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \OC\Files\View
|
* @var \OC\Files\View
|
||||||
*/
|
*/
|
||||||
|
@ -47,6 +52,9 @@ class Test_Trashbin extends \Test\TestCase {
|
||||||
public static function setUpBeforeClass() {
|
public static function setUpBeforeClass() {
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
|
$appManager = \OC::$server->getAppManager();
|
||||||
|
self::$trashBinStatus = $appManager->isEnabledForUser('files_trashbin');
|
||||||
|
|
||||||
// reset backend
|
// reset backend
|
||||||
\OC_User::clearBackends();
|
\OC_User::clearBackends();
|
||||||
\OC_User::useBackend('database');
|
\OC_User::useBackend('database');
|
||||||
|
@ -89,12 +97,18 @@ class Test_Trashbin extends \Test\TestCase {
|
||||||
|
|
||||||
\OC\Files\Filesystem::getLoader()->removeStorageWrapper('oc_trashbin');
|
\OC\Files\Filesystem::getLoader()->removeStorageWrapper('oc_trashbin');
|
||||||
|
|
||||||
|
if (self::$trashBinStatus) {
|
||||||
|
\OC::$server->getAppManager()->enableApp('files_trashbin');
|
||||||
|
}
|
||||||
|
|
||||||
parent::tearDownAfterClass();
|
parent::tearDownAfterClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setUp() {
|
protected function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
\OC::$server->getAppManager()->enableApp('files_trashbin');
|
||||||
|
|
||||||
$this->trashRoot1 = '/' . self::TEST_TRASHBIN_USER1 . '/files_trashbin';
|
$this->trashRoot1 = '/' . self::TEST_TRASHBIN_USER1 . '/files_trashbin';
|
||||||
$this->trashRoot2 = '/' . self::TEST_TRASHBIN_USER2 . '/files_trashbin';
|
$this->trashRoot2 = '/' . self::TEST_TRASHBIN_USER2 . '/files_trashbin';
|
||||||
$this->rootView = new \OC\Files\View();
|
$this->rootView = new \OC\Files\View();
|
||||||
|
@ -102,9 +116,18 @@ class Test_Trashbin extends \Test\TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown() {
|
protected function tearDown() {
|
||||||
|
// disable trashbin to be able to properly clean up
|
||||||
|
\OC::$server->getAppManager()->disableApp('files_trashbin');
|
||||||
|
|
||||||
|
$this->rootView->deleteAll('/' . self::TEST_TRASHBIN_USER1 . '/files');
|
||||||
|
$this->rootView->deleteAll('/' . self::TEST_TRASHBIN_USER2 . '/files');
|
||||||
$this->rootView->deleteAll($this->trashRoot1);
|
$this->rootView->deleteAll($this->trashRoot1);
|
||||||
$this->rootView->deleteAll($this->trashRoot2);
|
$this->rootView->deleteAll($this->trashRoot2);
|
||||||
|
|
||||||
|
// clear trash table
|
||||||
|
$connection = \OC::$server->getDatabaseConnection();
|
||||||
|
$connection->executeUpdate('DELETE FROM `*PREFIX*files_trash`');
|
||||||
|
|
||||||
parent::tearDown();
|
parent::tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,6 +317,310 @@ class Test_Trashbin extends \Test\TestCase {
|
||||||
$this->assertSame('file1.txt', $element['name']);
|
$this->assertSame('file1.txt', $element['name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test restoring a file
|
||||||
|
*/
|
||||||
|
public function testRestoreFileInRoot() {
|
||||||
|
$userFolder = \OC::$server->getUserFolder();
|
||||||
|
$file = $userFolder->newFile('file1.txt');
|
||||||
|
$file->putContent('foo');
|
||||||
|
|
||||||
|
$this->assertTrue($userFolder->nodeExists('file1.txt'));
|
||||||
|
|
||||||
|
$file->delete();
|
||||||
|
|
||||||
|
$this->assertFalse($userFolder->nodeExists('file1.txt'));
|
||||||
|
|
||||||
|
$filesInTrash = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1, 'mtime');
|
||||||
|
$this->assertCount(1, $filesInTrash);
|
||||||
|
|
||||||
|
/** @var \OCP\Files\FileInfo */
|
||||||
|
$trashedFile = $filesInTrash[0];
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
OCA\Files_Trashbin\Trashbin::restore(
|
||||||
|
'file1.txt.d' . $trashedFile->getMtime(),
|
||||||
|
$trashedFile->getName(),
|
||||||
|
$trashedFile->getMtime()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$file = $userFolder->get('file1.txt');
|
||||||
|
$this->assertEquals('foo', $file->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test restoring a file in subfolder
|
||||||
|
*/
|
||||||
|
public function testRestoreFileInSubfolder() {
|
||||||
|
$userFolder = \OC::$server->getUserFolder();
|
||||||
|
$folder = $userFolder->newFolder('folder');
|
||||||
|
$file = $folder->newFile('file1.txt');
|
||||||
|
$file->putContent('foo');
|
||||||
|
|
||||||
|
$this->assertTrue($userFolder->nodeExists('folder/file1.txt'));
|
||||||
|
|
||||||
|
$file->delete();
|
||||||
|
|
||||||
|
$this->assertFalse($userFolder->nodeExists('folder/file1.txt'));
|
||||||
|
|
||||||
|
$filesInTrash = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1, 'mtime');
|
||||||
|
$this->assertCount(1, $filesInTrash);
|
||||||
|
|
||||||
|
/** @var \OCP\Files\FileInfo */
|
||||||
|
$trashedFile = $filesInTrash[0];
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
OCA\Files_Trashbin\Trashbin::restore(
|
||||||
|
'file1.txt.d' . $trashedFile->getMtime(),
|
||||||
|
$trashedFile->getName(),
|
||||||
|
$trashedFile->getMtime()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$file = $userFolder->get('folder/file1.txt');
|
||||||
|
$this->assertEquals('foo', $file->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test restoring a folder
|
||||||
|
*/
|
||||||
|
public function testRestoreFolder() {
|
||||||
|
$userFolder = \OC::$server->getUserFolder();
|
||||||
|
$folder = $userFolder->newFolder('folder');
|
||||||
|
$file = $folder->newFile('file1.txt');
|
||||||
|
$file->putContent('foo');
|
||||||
|
|
||||||
|
$this->assertTrue($userFolder->nodeExists('folder'));
|
||||||
|
|
||||||
|
$folder->delete();
|
||||||
|
|
||||||
|
$this->assertFalse($userFolder->nodeExists('folder'));
|
||||||
|
|
||||||
|
$filesInTrash = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1, 'mtime');
|
||||||
|
$this->assertCount(1, $filesInTrash);
|
||||||
|
|
||||||
|
/** @var \OCP\Files\FileInfo */
|
||||||
|
$trashedFolder = $filesInTrash[0];
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
OCA\Files_Trashbin\Trashbin::restore(
|
||||||
|
'folder.d' . $trashedFolder->getMtime(),
|
||||||
|
$trashedFolder->getName(),
|
||||||
|
$trashedFolder->getMtime()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$file = $userFolder->get('folder/file1.txt');
|
||||||
|
$this->assertEquals('foo', $file->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test restoring a file from inside a trashed folder
|
||||||
|
*/
|
||||||
|
public function testRestoreFileFromTrashedSubfolder() {
|
||||||
|
$userFolder = \OC::$server->getUserFolder();
|
||||||
|
$folder = $userFolder->newFolder('folder');
|
||||||
|
$file = $folder->newFile('file1.txt');
|
||||||
|
$file->putContent('foo');
|
||||||
|
|
||||||
|
$this->assertTrue($userFolder->nodeExists('folder'));
|
||||||
|
|
||||||
|
$folder->delete();
|
||||||
|
|
||||||
|
$this->assertFalse($userFolder->nodeExists('folder'));
|
||||||
|
|
||||||
|
$filesInTrash = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1, 'mtime');
|
||||||
|
$this->assertCount(1, $filesInTrash);
|
||||||
|
|
||||||
|
/** @var \OCP\Files\FileInfo */
|
||||||
|
$trashedFile = $filesInTrash[0];
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
OCA\Files_Trashbin\Trashbin::restore(
|
||||||
|
'folder.d' . $trashedFile->getMtime() . '/file1.txt',
|
||||||
|
'file1.txt',
|
||||||
|
$trashedFile->getMtime()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$file = $userFolder->get('file1.txt');
|
||||||
|
$this->assertEquals('foo', $file->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test restoring a file whenever the source folder was removed.
|
||||||
|
* The file should then land in the root.
|
||||||
|
*/
|
||||||
|
public function testRestoreFileWithMissingSourceFolder() {
|
||||||
|
$userFolder = \OC::$server->getUserFolder();
|
||||||
|
$folder = $userFolder->newFolder('folder');
|
||||||
|
$file = $folder->newFile('file1.txt');
|
||||||
|
$file->putContent('foo');
|
||||||
|
|
||||||
|
$this->assertTrue($userFolder->nodeExists('folder/file1.txt'));
|
||||||
|
|
||||||
|
$file->delete();
|
||||||
|
|
||||||
|
$this->assertFalse($userFolder->nodeExists('folder/file1.txt'));
|
||||||
|
|
||||||
|
$filesInTrash = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1, 'mtime');
|
||||||
|
$this->assertCount(1, $filesInTrash);
|
||||||
|
|
||||||
|
/** @var \OCP\Files\FileInfo */
|
||||||
|
$trashedFile = $filesInTrash[0];
|
||||||
|
|
||||||
|
// delete source folder
|
||||||
|
$folder->delete();
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
OCA\Files_Trashbin\Trashbin::restore(
|
||||||
|
'file1.txt.d' . $trashedFile->getMtime(),
|
||||||
|
$trashedFile->getName(),
|
||||||
|
$trashedFile->getMtime()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$file = $userFolder->get('file1.txt');
|
||||||
|
$this->assertEquals('foo', $file->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test restoring a file in the root folder whenever there is another file
|
||||||
|
* with the same name in the root folder
|
||||||
|
*/
|
||||||
|
public function testRestoreFileDoesNotOverwriteExistingInRoot() {
|
||||||
|
$userFolder = \OC::$server->getUserFolder();
|
||||||
|
$file = $userFolder->newFile('file1.txt');
|
||||||
|
$file->putContent('foo');
|
||||||
|
|
||||||
|
$this->assertTrue($userFolder->nodeExists('file1.txt'));
|
||||||
|
|
||||||
|
$file->delete();
|
||||||
|
|
||||||
|
$this->assertFalse($userFolder->nodeExists('file1.txt'));
|
||||||
|
|
||||||
|
$filesInTrash = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1, 'mtime');
|
||||||
|
$this->assertCount(1, $filesInTrash);
|
||||||
|
|
||||||
|
/** @var \OCP\Files\FileInfo */
|
||||||
|
$trashedFile = $filesInTrash[0];
|
||||||
|
|
||||||
|
// create another file
|
||||||
|
$file = $userFolder->newFile('file1.txt');
|
||||||
|
$file->putContent('bar');
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
OCA\Files_Trashbin\Trashbin::restore(
|
||||||
|
'file1.txt.d' . $trashedFile->getMtime(),
|
||||||
|
$trashedFile->getName(),
|
||||||
|
$trashedFile->getMtime()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$anotherFile = $userFolder->get('file1.txt');
|
||||||
|
$this->assertEquals('bar', $anotherFile->getContent());
|
||||||
|
|
||||||
|
$restoredFile = $userFolder->get('file1 (restored).txt');
|
||||||
|
$this->assertEquals('foo', $restoredFile->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test restoring a file whenever there is another file
|
||||||
|
* with the same name in the source folder
|
||||||
|
*/
|
||||||
|
public function testRestoreFileDoesNotOverwriteExistingInSubfolder() {
|
||||||
|
$userFolder = \OC::$server->getUserFolder();
|
||||||
|
$folder = $userFolder->newFolder('folder');
|
||||||
|
$file = $folder->newFile('file1.txt');
|
||||||
|
$file->putContent('foo');
|
||||||
|
|
||||||
|
$this->assertTrue($userFolder->nodeExists('folder/file1.txt'));
|
||||||
|
|
||||||
|
$file->delete();
|
||||||
|
|
||||||
|
$this->assertFalse($userFolder->nodeExists('folder/file1.txt'));
|
||||||
|
|
||||||
|
$filesInTrash = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1, 'mtime');
|
||||||
|
$this->assertCount(1, $filesInTrash);
|
||||||
|
|
||||||
|
/** @var \OCP\Files\FileInfo */
|
||||||
|
$trashedFile = $filesInTrash[0];
|
||||||
|
|
||||||
|
// create another file
|
||||||
|
$file = $folder->newFile('file1.txt');
|
||||||
|
$file->putContent('bar');
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
OCA\Files_Trashbin\Trashbin::restore(
|
||||||
|
'file1.txt.d' . $trashedFile->getMtime(),
|
||||||
|
$trashedFile->getName(),
|
||||||
|
$trashedFile->getMtime()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$anotherFile = $userFolder->get('folder/file1.txt');
|
||||||
|
$this->assertEquals('bar', $anotherFile->getContent());
|
||||||
|
|
||||||
|
$restoredFile = $userFolder->get('folder/file1 (restored).txt');
|
||||||
|
$this->assertEquals('foo', $restoredFile->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test restoring a non-existing file from trashbin, returns false
|
||||||
|
*/
|
||||||
|
public function testRestoreUnexistingFile() {
|
||||||
|
$this->assertFalse(
|
||||||
|
OCA\Files_Trashbin\Trashbin::restore(
|
||||||
|
'unexist.txt.d123456',
|
||||||
|
'unexist.txt',
|
||||||
|
'123456'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test restoring a file into a read-only folder, will restore
|
||||||
|
* the file to root instead
|
||||||
|
*/
|
||||||
|
public function testRestoreFileIntoReadOnlySourceFolder() {
|
||||||
|
$userFolder = \OC::$server->getUserFolder();
|
||||||
|
$folder = $userFolder->newFolder('folder');
|
||||||
|
$file = $folder->newFile('file1.txt');
|
||||||
|
$file->putContent('foo');
|
||||||
|
|
||||||
|
$this->assertTrue($userFolder->nodeExists('folder/file1.txt'));
|
||||||
|
|
||||||
|
$file->delete();
|
||||||
|
|
||||||
|
$this->assertFalse($userFolder->nodeExists('folder/file1.txt'));
|
||||||
|
|
||||||
|
$filesInTrash = OCA\Files_Trashbin\Helper::getTrashFiles('/', self::TEST_TRASHBIN_USER1, 'mtime');
|
||||||
|
$this->assertCount(1, $filesInTrash);
|
||||||
|
|
||||||
|
/** @var \OCP\Files\FileInfo */
|
||||||
|
$trashedFile = $filesInTrash[0];
|
||||||
|
|
||||||
|
// delete source folder
|
||||||
|
list($storage, $internalPath) = $this->rootView->resolvePath('/' . self::TEST_TRASHBIN_USER1 . '/files/folder');
|
||||||
|
$folderAbsPath = $storage->getSourcePath($internalPath);
|
||||||
|
// make folder read-only
|
||||||
|
chmod($folderAbsPath, 0555);
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
OCA\Files_Trashbin\Trashbin::restore(
|
||||||
|
'file1.txt.d' . $trashedFile->getMtime(),
|
||||||
|
$trashedFile->getName(),
|
||||||
|
$trashedFile->getMtime()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$file = $userFolder->get('file1.txt');
|
||||||
|
$this->assertEquals('foo', $file->getContent());
|
||||||
|
|
||||||
|
chmod($folderAbsPath, 0755);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $user
|
* @param string $user
|
||||||
* @param bool $create
|
* @param bool $create
|
||||||
|
|
Loading…
Reference in a new issue