diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php
index c2253fcaa3c40097a267a6492d196073d497b149..8c2f03b141970a3c8e81b7509d1ebc51f697d4a0 100644
--- a/apps/files_sharing/lib/sharedstorage.php
+++ b/apps/files_sharing/lib/sharedstorage.php
@@ -586,4 +586,38 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
 		return $result;
 	}
 
+	/**
+	 * Resolve the path for the source of the share
+	 *
+	 * @param string $path
+	 * @return array
+	 */
+	private function resolvePath($path) {
+		$source = $this->getSourcePath($path);
+		return \OC\Files\Filesystem::resolvePath($source);
+	}
+
+	/**
+	 * @param \OCP\Files\Storage $sourceStorage
+	 * @param string $sourceInternalPath
+	 * @param string $targetInternalPath
+	 * @return bool
+	 */
+	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+		/** @var \OCP\Files\Storage $targetStorage */
+		list($targetStorage, $targetInternalPath) = $this->resolvePath($targetInternalPath);
+		return $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+	}
+
+	/**
+	 * @param \OCP\Files\Storage $sourceStorage
+	 * @param string $sourceInternalPath
+	 * @param string $targetInternalPath
+	 * @return bool
+	 */
+	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+		/** @var \OCP\Files\Storage $targetStorage */
+		list($targetStorage, $targetInternalPath) = $this->resolvePath($targetInternalPath);
+		return $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+	}
 }
diff --git a/apps/files_sharing/tests/sharedstorage.php b/apps/files_sharing/tests/sharedstorage.php
index a8ce92775d6c58576cde1f3e737cd46d211db7be..a1469a74687a92323affe38d0cb0b4ad0e005cb2 100644
--- a/apps/files_sharing/tests/sharedstorage.php
+++ b/apps/files_sharing/tests/sharedstorage.php
@@ -117,21 +117,21 @@ class Test_Files_Sharing_Storage extends OCA\Files_sharing\Tests\TestCase {
 		$this->assertTrue($user2View->file_exists($this->folder));
 
 		// create part file
-		$result = $user2View->file_put_contents($this->folder. '/foo.txt.part', 'some test data');
+		$result = $user2View->file_put_contents($this->folder . '/foo.txt.part', 'some test data');
 
 		$this->assertTrue(is_int($result));
 		// rename part file to real file
-		$result = $user2View->rename($this->folder. '/foo.txt.part', $this->folder. '/foo.txt');
+		$result = $user2View->rename($this->folder . '/foo.txt.part', $this->folder . '/foo.txt');
 
 		$this->assertTrue($result);
 
 		// check if the new file really exists
-		$this->assertTrue($user2View->file_exists( $this->folder. '/foo.txt'));
+		$this->assertTrue($user2View->file_exists($this->folder . '/foo.txt'));
 
 		// check if the rename also affected the owner
 		self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
 
-		$this->assertTrue($this->view->file_exists( $this->folder. '/foo.txt'));
+		$this->assertTrue($this->view->file_exists($this->folder . '/foo.txt'));
 
 		//cleanup
 		\OCP\Share::unshare('folder', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
@@ -144,7 +144,7 @@ class Test_Files_Sharing_Storage extends OCA\Files_sharing\Tests\TestCase {
 		$fileinfoFile = $this->view->getFileInfo($this->filename);
 
 		$folderSize = $this->view->filesize($this->folder);
-		$file1Size = $this->view->filesize($this->folder. $this->filename);
+		$file1Size = $this->view->filesize($this->folder . $this->filename);
 		$file2Size = $this->view->filesize($this->filename);
 
 		$result = \OCP\Share::shareItem('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
@@ -373,11 +373,69 @@ class Test_Files_Sharing_Storage extends OCA\Files_sharing\Tests\TestCase {
 		$this->assertTrue($rootView->file_exists('/' . self::TEST_FILES_SHARING_API_USER3 . '/files/' . $this->filename));
 
 		// make sure we didn't double setup shares for user 2 or mounted the shares for user 3 in user's 2 home
-		$this->assertFalse($rootView->file_exists('/' . self::TEST_FILES_SHARING_API_USER2 . '/files/' . $this->folder .' (2)'));
+		$this->assertFalse($rootView->file_exists('/' . self::TEST_FILES_SHARING_API_USER2 . '/files/' . $this->folder . ' (2)'));
 		$this->assertFalse($rootView->file_exists('/' . self::TEST_FILES_SHARING_API_USER2 . '/files/' . $this->filename));
 
 		//cleanup
 		self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
 		$this->view->unlink($this->folder);
 	}
+
+	public function testCopyFromStorage() {
+		$folderInfo = $this->view->getFileInfo($this->folder);
+		self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
+
+		// share 2 different files with 2 different users
+		\OCP\Share::shareItem('folder', $folderInfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
+			self::TEST_FILES_SHARING_API_USER2, 31);
+
+		self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
+		$view = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
+		$this->assertTrue($view->file_exists($this->folder));
+
+		/**
+		 * @var \OCP\Files\Storage $sharedStorage
+		 */
+		list($sharedStorage,) = $view->resolvePath($this->folder);
+		$this->assertTrue($sharedStorage->instanceOfStorage('OCA\Files_Sharing\ISharedStorage'));
+
+		$sourceStorage = new \OC\Files\Storage\Temporary(array());
+		$sourceStorage->file_put_contents('foo.txt', 'asd');
+
+		$sharedStorage->copyFromStorage($sourceStorage, 'foo.txt', 'bar.txt');
+		$this->assertTrue($sharedStorage->file_exists('bar.txt'));
+		$this->assertEquals('asd', $sharedStorage->file_get_contents('bar.txt'));
+
+		self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
+		$this->view->unlink($this->folder);
+	}
+
+	public function testMoveFromStorage() {
+		$folderInfo = $this->view->getFileInfo($this->folder);
+		self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
+
+		// share 2 different files with 2 different users
+		\OCP\Share::shareItem('folder', $folderInfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
+			self::TEST_FILES_SHARING_API_USER2, 31);
+
+		self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
+		$view = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
+		$this->assertTrue($view->file_exists($this->folder));
+
+		/**
+		 * @var \OCP\Files\Storage $sharedStorage
+		 */
+		list($sharedStorage,) = $view->resolvePath($this->folder);
+		$this->assertTrue($sharedStorage->instanceOfStorage('OCA\Files_Sharing\ISharedStorage'));
+
+		$sourceStorage = new \OC\Files\Storage\Temporary(array());
+		$sourceStorage->file_put_contents('foo.txt', 'asd');
+
+		$sharedStorage->moveFromStorage($sourceStorage, 'foo.txt', 'bar.txt');
+		$this->assertTrue($sharedStorage->file_exists('bar.txt'));
+		$this->assertEquals('asd', $sharedStorage->file_get_contents('bar.txt'));
+
+		self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
+		$this->view->unlink($this->folder);
+	}
 }
diff --git a/apps/files_trashbin/tests/storage.php b/apps/files_trashbin/tests/storage.php
index d5bd7c318d3d50e7ea2a106ae754b61ea2f049aa..d1468522dc211c4086f522a1bf9131a4eec46090 100644
--- a/apps/files_trashbin/tests/storage.php
+++ b/apps/files_trashbin/tests/storage.php
@@ -202,12 +202,13 @@ class Storage extends \Test\TestCase {
 
 		$cache = $storage->getCache();
 
-		Filesystem::mount($storage, [], '/' . $this->user . '/files');
+		Filesystem::mount($storage, [], '/' . $this->user);
+		$storage->mkdir('files');
 		$this->userView->file_put_contents('test.txt', 'foo');
-		$this->assertTrue($storage->file_exists('test.txt'));
+		$this->assertTrue($storage->file_exists('files/test.txt'));
 		$this->assertFalse($this->userView->unlink('test.txt'));
-		$this->assertTrue($storage->file_exists('test.txt'));
-		$this->assertTrue($cache->inCache('test.txt'));
+		$this->assertTrue($storage->file_exists('files/test.txt'));
+		$this->assertTrue($cache->inCache('files/test.txt'));
 
 		// file should not be in the trashbin
 		$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
diff --git a/lib/private/files/storage/common.php b/lib/private/files/storage/common.php
index ed85d3c07ccb5e7f0cbe031c2a5ce1cbe6803caa..66ed713e22d1d0a99dce1b95b545c01d02be0351 100644
--- a/lib/private/files/storage/common.php
+++ b/lib/private/files/storage/common.php
@@ -525,4 +525,59 @@ abstract class Common implements Storage {
 	public function getMountOption($name, $default = null) {
 		return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
 	}
+	/**
+	 * @param \OCP\Files\Storage $sourceStorage
+	 * @param string $sourceInternalPath
+	 * @param string $targetInternalPath
+	 * @param bool $preserveMtime
+	 * @return bool
+	 */
+	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
+		if ($sourceStorage->is_dir($sourceInternalPath)) {
+			$dh = $sourceStorage->opendir($sourceInternalPath);
+			$result = $this->mkdir($targetInternalPath);
+			if (is_resource($dh)) {
+				while ($result and ($file = readdir($dh)) !== false) {
+					if (!Filesystem::isIgnoredDir($file)) {
+						$result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
+					}
+				}
+			}
+		} else {
+			$source = $sourceStorage->fopen($sourceInternalPath, 'r');
+			$target = $this->fopen($targetInternalPath, 'w');
+			list(, $result) = \OC_Helper::streamCopy($source, $target);
+			if ($result and $preserveMtime) {
+				$this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
+			}
+			fclose($source);
+			fclose($target);
+
+			if (!$result) {
+				// delete partially written target file
+				$this->unlink($targetInternalPath);
+				// delete cache entry that was created by fopen
+				$this->getCache()->remove($targetInternalPath);
+			}
+		}
+		return (bool)$result;
+	}
+
+	/**
+	 * @param \OCP\Files\Storage $sourceStorage
+	 * @param string $sourceInternalPath
+	 * @param string $targetInternalPath
+	 * @return bool
+	 */
+	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+		$result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
+		if ($result) {
+			if ($sourceStorage->is_dir($sourceInternalPath)) {
+				$result &= $sourceStorage->rmdir($sourceInternalPath);
+			} else {
+				$result &= $sourceStorage->unlink($sourceInternalPath);
+			}
+		}
+		return $result;
+	}
 }
diff --git a/lib/private/files/storage/local.php b/lib/private/files/storage/local.php
index 6bd9b4401d6efaafaba17e664367abf5e10a5598..175b33b0329998107d3fe8b540b86161d07031ea 100644
--- a/lib/private/files/storage/local.php
+++ b/lib/private/files/storage/local.php
@@ -322,7 +322,7 @@ if (\OC_Util::runningOnWindows()) {
 		 * @param string $path
 		 * @return string
 		 */
-		protected function getSourcePath($path) {
+		public function getSourcePath($path) {
 			$fullPath = $this->datadir . $path;
 			return $fullPath;
 		}
@@ -353,5 +353,41 @@ if (\OC_Util::runningOnWindows()) {
 				return parent::getETag($path);
 			}
 		}
+
+		/**
+		 * @param \OCP\Files\Storage $sourceStorage
+		 * @param string $sourceInternalPath
+		 * @param string $targetInternalPath
+		 * @return bool
+		 */
+		public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+			if($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')){
+				/**
+				 * @var \OC\Files\Storage\Local $sourceStorage
+				 */
+				$rootStorage = new Local(['datadir' => '/']);
+				return $rootStorage->copy($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
+			} else {
+				return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+			}
+		}
+
+		/**
+		 * @param \OCP\Files\Storage $sourceStorage
+		 * @param string $sourceInternalPath
+		 * @param string $targetInternalPath
+		 * @return bool
+		 */
+		public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+			if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Local')) {
+				/**
+				 * @var \OC\Files\Storage\Local $sourceStorage
+				 */
+				$rootStorage = new Local(['datadir' => '/']);
+				return $rootStorage->rename($sourceStorage->getSourcePath($sourceInternalPath), $this->getSourcePath($targetInternalPath));
+			} else {
+				return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+			}
+		}
 	}
 }
diff --git a/lib/private/files/storage/wrapper/quota.php b/lib/private/files/storage/wrapper/quota.php
index 3c0fda98dad08d49501f03fad35ea752eb8ba22a..92d749a814fd5c8eaffb891b031fba7d3ae09198 100644
--- a/lib/private/files/storage/wrapper/quota.php
+++ b/lib/private/files/storage/wrapper/quota.php
@@ -55,9 +55,14 @@ class Quota extends Wrapper {
 
 	/**
 	 * @param string $path
+	 * @param \OC\Files\Storage\Storage $storage
 	 */
-	protected function getSize($path) {
-		$cache = $this->getCache();
+	protected function getSize($path, $storage = null) {
+		if (is_null($storage)) {
+			$cache = $this->getCache();
+		} else {
+			$cache = $storage->getCache();
+		}
 		$data = $cache->get($path);
 		if (is_array($data) and isset($data['size'])) {
 			return $data['size'];
@@ -141,4 +146,34 @@ class Quota extends Wrapper {
 			return $source;
 		}
 	}
+
+	/**
+	 * @param \OCP\Files\Storage $sourceStorage
+	 * @param string $sourceInternalPath
+	 * @param string $targetInternalPath
+	 * @return bool
+	 */
+	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+		$free = $this->free_space('');
+		if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
+			return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * @param \OCP\Files\Storage $sourceStorage
+	 * @param string $sourceInternalPath
+	 * @param string $targetInternalPath
+	 * @return bool
+	 */
+	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+		$free = $this->free_space('');
+		if ($free < 0 or $this->getSize($sourceInternalPath, $sourceStorage) < $free) {
+			return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+		} else {
+			return false;
+		}
+	}
 }
diff --git a/lib/private/files/storage/wrapper/wrapper.php b/lib/private/files/storage/wrapper/wrapper.php
index 6550313f710d87c9952aee5e2eea53d7f0b66dbf..2552c926e021169b626a53e3bf5c90fd7a125dbe 100644
--- a/lib/private/files/storage/wrapper/wrapper.php
+++ b/lib/private/files/storage/wrapper/wrapper.php
@@ -505,4 +505,24 @@ class Wrapper implements \OC\Files\Storage\Storage {
 	public function verifyPath($path, $fileName) {
 		$this->storage->verifyPath($path, $fileName);
 	}
+
+	/**
+	 * @param \OCP\Files\Storage $sourceStorage
+	 * @param string $sourceInternalPath
+	 * @param string $targetInternalPath
+	 * @return bool
+	 */
+	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+		return $this->storage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+	}
+
+	/**
+	 * @param \OCP\Files\Storage $sourceStorage
+	 * @param string $sourceInternalPath
+	 * @param string $targetInternalPath
+	 * @return bool
+	 */
+	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+		return $this->storage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+	}
 }
diff --git a/lib/private/files/view.php b/lib/private/files/view.php
index be14521990a04e83fb4644de1bf004e294d2f71a..3300998da35d73f4e9722d8f8340292b90c17bd0 100644
--- a/lib/private/files/view.php
+++ b/lib/private/files/view.php
@@ -584,8 +584,6 @@ class View {
 	 * @return bool|mixed
 	 */
 	public function rename($path1, $path2) {
-		$postFix1 = (substr($path1, -1, 1) === '/') ? '/' : '';
-		$postFix2 = (substr($path2, -1, 1) === '/') ? '/' : '';
 		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
 		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
 		if (
@@ -617,58 +615,33 @@ class View {
 			if ($run) {
 				$this->verifyPath(dirname($path2), basename($path2));
 
-				$mp1 = $this->getMountPoint($path1 . $postFix1);
-				$mp2 = $this->getMountPoint($path2 . $postFix2);
 				$manager = Filesystem::getMountManager();
-				$mount = $manager->find($absolutePath1 . $postFix1);
-				$storage1 = $mount->getStorage();
-				$internalPath1 = $mount->getInternalPath($absolutePath1 . $postFix1);
-				list($storage2, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
-				if ($internalPath1 === '' and $mount instanceof MoveableMount) {
+				$mount1 = $this->getMount($path1);
+				$mount2 = $this->getMount($path2);
+				$storage1 = $mount1->getStorage();
+				$storage2 = $mount2->getStorage();
+				$internalPath1 = $mount1->getInternalPath($absolutePath1);
+				$internalPath2 = $mount2->getInternalPath($absolutePath2);
+
+				if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
 					if ($this->isTargetAllowed($absolutePath2)) {
 						/**
-						 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount
+						 * @var \OC\Files\Mount\MountPoint | \OC\Files\Mount\MoveableMount $mount1
 						 */
-						$sourceMountPoint = $mount->getMountPoint();
-						$result = $mount->moveMount($absolutePath2);
-						$manager->moveMount($sourceMountPoint, $mount->getMountPoint());
+						$sourceMountPoint = $mount1->getMountPoint();
+						$result = $mount1->moveMount($absolutePath2);
+						$manager->moveMount($sourceMountPoint, $mount1->getMountPoint());
 					} else {
 						$result = false;
 					}
-				} elseif ($mp1 == $mp2) {
+				} elseif ($storage1 == $storage2) {
 					if ($storage1) {
 						$result = $storage1->rename($internalPath1, $internalPath2);
 					} else {
 						$result = false;
 					}
 				} else {
-					if ($this->is_dir($path1)) {
-						$result = $this->copy($path1, $path2, true);
-						if ($result === true) {
-							$result = $storage1->rmdir($internalPath1);
-						}
-					} else {
-						$source = $this->fopen($path1 . $postFix1, 'r');
-						$target = $this->fopen($path2 . $postFix2, 'w');
-						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
-						fclose($source);
-						fclose($target);
-
-						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);
-						}
-					}
+					$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
 				}
 				if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
 					// if it was a rename from a part file to a regular file it was a write and not a rename operation
@@ -710,8 +683,6 @@ class View {
 	 * @return bool|mixed
 	 */
 	public function copy($path1, $path2, $preserveMtime = false) {
-		$postFix1 = (substr($path1, -1, 1) === '/') ? '/' : '';
-		$postFix2 = (substr($path2, -1, 1) === '/') ? '/' : '';
 		$absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
 		$absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
 		if (
@@ -740,52 +711,20 @@ class View {
 				$this->emit_file_hooks_pre($exists, $path2, $run);
 			}
 			if ($run) {
-				$mp1 = $this->getMountPoint($path1 . $postFix1);
-				$mp2 = $this->getMountPoint($path2 . $postFix2);
-				if ($mp1 == $mp2) {
-					list($storage, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1);
-					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);
-						}
+				$mount1 = $this->getMount($path1);
+				$mount2 = $this->getMount($path2);
+				$storage1 = $mount1->getStorage();
+				$internalPath1 = $mount1->getInternalPath($absolutePath1);
+				$storage2 = $mount2->getStorage();
+				$internalPath2 = $mount2->getInternalPath($absolutePath2);
+				if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
+					if ($storage1) {
+						$result = $storage1->copy($internalPath1, $internalPath2);
 					} else {
 						$result = false;
 					}
 				} else {
-					if ($this->is_dir($path1) && ($dh = $this->opendir($path1))) {
-						$result = $this->mkdir($path2);
-						if ($preserveMtime) {
-							$this->touch($path2, $this->filemtime($path1));
-						}
-						if (is_resource($dh)) {
-							while (($file = readdir($dh)) !== false) {
-								if (!Filesystem::isIgnoredDir($file)) {
-									if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file, $preserveMtime)) {
-										$result = false;
-									}
-								}
-							}
-						}
-					} else {
-						list($storage2, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
-						$source = $this->fopen($path1 . $postFix1, 'r');
-						$target = $this->fopen($path2 . $postFix2, 'w');
-						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);
-						}
-					}
+					$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
 				}
 				$this->updater->update($path2);
 				if ($this->shouldEmitHooks() && $result !== false) {
diff --git a/lib/public/files/storage.php b/lib/public/files/storage.php
index 8a20eff2d9ff8559daa04c3805fffc0d1463ed6a..bac2c95ebced9ee5adc1f938dfe2c5f51c6a39e7 100644
--- a/lib/public/files/storage.php
+++ b/lib/public/files/storage.php
@@ -358,4 +358,20 @@ interface Storage {
 	 * @throws InvalidPathException
 	 */
 	public function verifyPath($path, $fileName);
+
+	/**
+	 * @param \OCP\Files\Storage $sourceStorage
+	 * @param string $sourceInternalPath
+	 * @param string $targetInternalPath
+	 * @return bool
+	 */
+	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath);
+
+	/**
+	 * @param \OCP\Files\Storage $sourceStorage
+	 * @param string $sourceInternalPath
+	 * @param string $targetInternalPath
+	 * @return bool
+	 */
+	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath);
 }
diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php
index 2ea9e8de78f2c2b6cab13bbcd1a910c5be3d213a..269b8b23e7d3b595fefd34b2eb3cce65eb89e1ae 100644
--- a/tests/lib/files/view.php
+++ b/tests/lib/files/view.php
@@ -8,6 +8,7 @@
 namespace Test\Files;
 
 use OC\Files\Cache\Watcher;
+use OC\Files\Storage\Common;
 use OC\Files\Mount\MountPoint;
 use OC\Files\Storage\Temporary;
 
@@ -17,6 +18,26 @@ class TemporaryNoTouch extends \OC\Files\Storage\Temporary {
 	}
 }
 
+class TemporaryNoCross extends \OC\Files\Storage\Temporary {
+	public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+		return Common::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+	}
+
+	public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+		return Common::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+	}
+}
+
+class TemporaryNoLocal extends \OC\Files\Storage\Temporary {
+	public function instanceOfStorage($className) {
+		if($className === '\OC\Files\Storage\Local') {
+			return false;
+		} else {
+			return parent::instanceOfStorage($className);
+		}
+	}
+}
+
 class View extends \Test\TestCase {
 	/**
 	 * @var \OC\Files\Storage\Storage[] $storages
@@ -291,9 +312,31 @@ class View extends \Test\TestCase {
 	/**
 	 * @medium
 	 */
-	function testCopyBetweenStorages() {
+	function testCopyBetweenStorageNoCross() {
+		$storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
+		$storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
+		$this->copyBetweenStorages($storage1, $storage2);
+	}
+
+	/**
+	 * @medium
+	 */
+	function testCopyBetweenStorageCross() {
 		$storage1 = $this->getTestStorage();
 		$storage2 = $this->getTestStorage();
+		$this->copyBetweenStorages($storage1, $storage2);
+	}
+
+	/**
+	 * @medium
+	 */
+	function testCopyBetweenStorageCrossNonLocal() {
+		$storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
+		$storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
+		$this->copyBetweenStorages($storage1, $storage2);
+	}
+
+	function copyBetweenStorages($storage1, $storage2) {
 		\OC\Files\Filesystem::mount($storage1, array(), '/');
 		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
 
@@ -315,9 +358,31 @@ class View extends \Test\TestCase {
 	/**
 	 * @medium
 	 */
-	function testMoveBetweenStorages() {
+	function testMoveBetweenStorageNoCross() {
+		$storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
+		$storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
+		$this->moveBetweenStorages($storage1, $storage2);
+	}
+
+	/**
+	 * @medium
+	 */
+	function testMoveBetweenStorageCross() {
 		$storage1 = $this->getTestStorage();
 		$storage2 = $this->getTestStorage();
+		$this->moveBetweenStorages($storage1, $storage2);
+	}
+
+	/**
+	 * @medium
+	 */
+	function testMoveBetweenStorageCrossNonLocal() {
+		$storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
+		$storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
+		$this->moveBetweenStorages($storage1, $storage2);
+	}
+
+	function moveBetweenStorages($storage1, $storage2) {
 		\OC\Files\Filesystem::mount($storage1, array(), '/');
 		\OC\Files\Filesystem::mount($storage2, array(), '/substorage');
 
@@ -879,8 +944,19 @@ class View extends \Test\TestCase {
 
 	private function doTestCopyRenameFail($operation) {
 		$storage1 = new Temporary(array());
-		$storage2 = new Temporary(array());
-		$storage2 = new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage2, 'quota' => 9));
+		/** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage2 */
+		$storage2 = $this->getMockBuilder('\Test\Files\TemporaryNoCross')
+			->setConstructorArgs([[]])
+			->setMethods(['fopen'])
+			->getMock();
+
+		$storage2->expects($this->any())
+			->method('fopen')
+			->will($this->returnCallback(function($path, $mode) use($storage2) {
+				$source = fopen($storage2->getSourcePath($path), $mode);
+				return \OC\Files\Stream\Quota::wrap($source, 9);
+			}));
+
 		$storage1->mkdir('sub');
 		$storage1->file_put_contents('foo.txt', '0123456789ABCDEFGH');
 		$storage1->mkdir('dirtomove');
@@ -911,10 +987,8 @@ class View extends \Test\TestCase {
 		$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'));
+		// second file not moved/copied
 		$this->assertFalse($storage2->file_exists('dirtomove/indir2.txt'));
 		$this->assertFalse($storage2->getCache()->get('dirtomove/indir2.txt'));