summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/private/files/view.php44
-rw-r--r--lib/private/helper.php16
-rw-r--r--tests/lib/files/storage/wrapper/quota.php22
-rw-r--r--tests/lib/files/view.php51
4 files changed, 120 insertions, 13 deletions
diff --git a/lib/private/files/view.php b/lib/private/files/view.php
index 7de7ef7b32c..e4732f8bb3d 100644
--- a/lib/private/files/view.php
+++ b/lib/private/files/view.php
@@ -574,8 +574,11 @@ class View {
}
/**
- * @param string $path1
- * @param string $path2
+ * Rename/move a file or folder from the source path to target path.
+ *
+ * @param string $path1 source path
+ * @param string $path2 target path
+ *
* @return bool|mixed
*/
public function rename($path1, $path2) {
@@ -617,7 +620,7 @@ class View {
$mount = $manager->find($absolutePath1 . $postFix1);
$storage1 = $mount->getStorage();
$internalPath1 = $mount->getInternalPath($absolutePath1 . $postFix1);
- list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
+ list($storage2, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
if ($internalPath1 === '' and $mount instanceof MoveableMount) {
if ($this->isTargetAllowed($absolutePath2)) {
/**
@@ -646,8 +649,10 @@ class View {
} else {
$source = $this->fopen($path1 . $postFix1, 'r');
$target = $this->fopen($path2 . $postFix2, 'w');
- list($count, $result) = \OC_Helper::streamCopy($source, $target);
- $this->touch($path2, $this->filemtime($path1));
+ list(, $result) = \OC_Helper::streamCopy($source, $target);
+ if ($result !== false) {
+ $this->touch($path2, $this->filemtime($path1));
+ }
// close open handle - especially $source is necessary because unlink below will
// throw an exception on windows because the file is locked
@@ -656,6 +661,11 @@ class View {
if ($result !== false) {
$result &= $storage1->unlink($internalPath1);
+ } else {
+ // delete partially written target file
+ $storage2->unlink($internalPath2);
+ // delete cache entry that was created by fopen
+ $storage2->getCache()->remove($internalPath2);
}
}
}
@@ -688,9 +698,12 @@ class View {
}
/**
- * @param string $path1
- * @param string $path2
- * @param bool $preserveMtime
+ * Copy a file/folder from the source path to target path
+ *
+ * @param string $path1 source path
+ * @param string $path2 target path
+ * @param bool $preserveMtime whether to preserve mtime on the copy
+ *
* @return bool|mixed
*/
public function copy($path1, $path2, $preserveMtime = false) {
@@ -732,6 +745,11 @@ class View {
list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
if ($storage) {
$result = $storage->copy($internalPath1, $internalPath2);
+ if (!$result) {
+ // delete partially written target file
+ $storage->unlink($internalPath2);
+ $storage->getCache()->remove($internalPath2);
+ }
} else {
$result = false;
}
@@ -749,14 +767,20 @@ class View {
}
}
} else {
+ list($storage2, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
$source = $this->fopen($path1 . $postFix1, 'r');
$target = $this->fopen($path2 . $postFix2, 'w');
- list($count, $result) = \OC_Helper::streamCopy($source, $target);
- if($preserveMtime) {
+ list(, $result) = \OC_Helper::streamCopy($source, $target);
+ if($result && $preserveMtime) {
$this->touch($path2, $this->filemtime($path1));
}
fclose($source);
fclose($target);
+ if (!$result) {
+ // delete partially written target file
+ $storage2->unlink($internalPath2);
+ $storage2->getCache()->remove($internalPath2);
+ }
}
}
$this->updater->update($path2);
diff --git a/lib/private/helper.php b/lib/private/helper.php
index 04c063145ba..f4992744ad9 100644
--- a/lib/private/helper.php
+++ b/lib/private/helper.php
@@ -594,13 +594,23 @@ class OC_Helper {
if (!$source or !$target) {
return array(0, false);
}
+ $bufSize = 8192;
$result = true;
$count = 0;
while (!feof($source)) {
- if (($c = fwrite($target, fread($source, 8192))) === false) {
+ $buf = fread($source, $bufSize);
+ $bytesWritten = fwrite($target, $buf);
+ if ($bytesWritten !== false) {
+ $count += $bytesWritten;
+ }
+ // note: strlen is expensive so only use it when necessary,
+ // on the last block
+ if ($bytesWritten === false
+ || ($bytesWritten < $bufSize && $bytesWritten < strlen($buf))
+ ) {
+ // write error, could be disk full ?
$result = false;
- } else {
- $count += $c;
+ break;
}
}
return array($count, $result);
diff --git a/tests/lib/files/storage/wrapper/quota.php b/tests/lib/files/storage/wrapper/quota.php
index dc4de4697db..8ca8f308b71 100644
--- a/tests/lib/files/storage/wrapper/quota.php
+++ b/tests/lib/files/storage/wrapper/quota.php
@@ -99,6 +99,28 @@ class Quota extends \Test\Files\Storage\Storage {
$this->assertEquals('foobarqwe', $instance->file_get_contents('foo'));
}
+ public function testStreamCopyWithEnoughSpace() {
+ $instance = $this->getLimitedStorage(16);
+ $inputStream = fopen('data://text/plain,foobarqwerty', 'r');
+ $outputStream = $instance->fopen('foo', 'w+');
+ list($count, $result) = \OC_Helper::streamCopy($inputStream, $outputStream);
+ $this->assertEquals(12, $count);
+ $this->assertTrue($result);
+ fclose($inputStream);
+ fclose($outputStream);
+ }
+
+ public function testStreamCopyNotEnoughSpace() {
+ $instance = $this->getLimitedStorage(9);
+ $inputStream = fopen('data://text/plain,foobarqwerty', 'r');
+ $outputStream = $instance->fopen('foo', 'w+');
+ list($count, $result) = \OC_Helper::streamCopy($inputStream, $outputStream);
+ $this->assertEquals(9, $count);
+ $this->assertFalse($result);
+ fclose($inputStream);
+ fclose($outputStream);
+ }
+
public function testReturnFalseWhenFopenFailed() {
$failStorage = $this->getMock(
'\OC\Files\Storage\Local',
diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php
index b4b6d0deb2e..0d88ec1d66a 100644
--- a/tests/lib/files/view.php
+++ b/tests/lib/files/view.php
@@ -872,6 +872,57 @@ class View extends \Test\TestCase {
$this->assertEquals($time, $view->filemtime('/test/sub/storage/foo/bar.txt'));
}
+ public function testRenameFailDeleteTargetKeepSource() {
+ $this->doTestCopyRenameFail('rename');
+ }
+
+ public function testCopyFailDeleteTargetKeepSource() {
+ $this->doTestCopyRenameFail('copy');
+ }
+
+ private function doTestCopyRenameFail($operation) {
+ $storage1 = new Temporary(array());
+ $storage2 = new Temporary(array());
+ $storage2 = new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage2, 'quota' => 9));
+ $storage1->mkdir('sub');
+ $storage1->file_put_contents('foo.txt', '0123456789ABCDEFGH');
+ $storage1->mkdir('dirtomove');
+ $storage1->file_put_contents('dirtomove/indir1.txt', '0123456'); // fits
+ $storage1->file_put_contents('dirtomove/indir2.txt', '0123456789ABCDEFGH'); // doesn't fit
+ $storage2->file_put_contents('existing.txt', '0123');
+ $storage1->getScanner()->scan('');
+ $storage2->getScanner()->scan('');
+ \OC\Files\Filesystem::mount($storage1, array(), '/test/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage');
+
+ // move file
+ $view = new \OC\Files\View('');
+ $this->assertTrue($storage1->file_exists('foo.txt'));
+ $this->assertFalse($storage2->file_exists('foo.txt'));
+ $this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/foo.txt'));
+ $this->assertFalse($storage2->file_exists('foo.txt'));
+ $this->assertFalse($storage2->getCache()->get('foo.txt'));
+ $this->assertTrue($storage1->file_exists('foo.txt'));
+
+ // if target exists, it will be deleted too
+ $this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/existing.txt'));
+ $this->assertFalse($storage2->file_exists('existing.txt'));
+ $this->assertFalse($storage2->getCache()->get('existing.txt'));
+ $this->assertTrue($storage1->file_exists('foo.txt'));
+
+ // move folder
+ $this->assertFalse($view->$operation('/test/dirtomove/', '/test/sub/storage/dirtomove/'));
+ // since the move failed, the full source tree is kept
+ $this->assertTrue($storage1->file_exists('dirtomove/indir1.txt'));
+ // but the target file stays
+ $this->assertTrue($storage2->file_exists('dirtomove/indir1.txt'));
+ // second file not moved/copied
+ $this->assertTrue($storage1->file_exists('dirtomove/indir2.txt'));
+ $this->assertFalse($storage2->file_exists('dirtomove/indir2.txt'));
+ $this->assertFalse($storage2->getCache()->get('dirtomove/indir2.txt'));
+
+ }
+
public function testDeleteFailKeepCache() {
/**
* @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage