From 63863271503384e124e39833d0598e8652d3a380 Mon Sep 17 00:00:00 2001
From: Robin Appelman <icewind@owncloud.com>
Date: Wed, 12 Aug 2015 15:06:59 +0200
Subject: work directly on storages when doing a chunked upload assembly

---
 lib/private/connector/sabre/file.php | 117 ++++++++++++++++++++++-------------
 lib/private/filechunking.php         |  27 ++++----
 2 files changed, 87 insertions(+), 57 deletions(-)

(limited to 'lib')

diff --git a/lib/private/connector/sabre/file.php b/lib/private/connector/sabre/file.php
index 1730feee264..a7e529183b6 100644
--- a/lib/private/connector/sabre/file.php
+++ b/lib/private/connector/sabre/file.php
@@ -35,6 +35,7 @@ namespace OC\Connector\Sabre;
 use OC\Connector\Sabre\Exception\EntityTooLarge;
 use OC\Connector\Sabre\Exception\FileLocked;
 use OC\Connector\Sabre\Exception\UnsupportedMediaType;
+use OC\Files\Filesystem;
 use OCP\Encryption\Exceptions\GenericEncryptionException;
 use OCP\Files\EntityTooLargeException;
 use OCP\Files\InvalidContentException;
@@ -126,7 +127,7 @@ class File extends Node implements IFile {
 				// because we have no clue about the cause we can only throw back a 500/Internal Server Error
 				throw new Exception('Could not write file contents');
 			}
-			list($count, ) = \OC_Helper::streamCopy($data, $target);
+			list($count,) = \OC_Helper::streamCopy($data, $target);
 			fclose($target);
 
 			// if content length is sent by client:
@@ -148,25 +149,8 @@ class File extends Node implements IFile {
 
 		try {
 			$view = \OC\Files\Filesystem::getView();
-			$run = true;
 			if ($view) {
-				$hookPath = $view->getRelativePath($this->fileView->getAbsolutePath($this->path));
-
-				if (!$exists) {
-					\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array(
-						\OC\Files\Filesystem::signal_param_path => $hookPath,
-						\OC\Files\Filesystem::signal_param_run => &$run,
-					));
-				} else {
-					\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array(
-						\OC\Files\Filesystem::signal_param_path => $hookPath,
-						\OC\Files\Filesystem::signal_param_run => &$run,
-					));
-				}
-				\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array(
-					\OC\Files\Filesystem::signal_param_path => $hookPath,
-					\OC\Files\Filesystem::signal_param_run => &$run,
-				));
+				$this->emitPreHooks($exists);
 			}
 
 			try {
@@ -181,11 +165,9 @@ class File extends Node implements IFile {
 			if ($needsPartFile) {
 				// rename to correct path
 				try {
-					if ($run) {
-						$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
-						$fileExists = $storage->file_exists($internalPath);
-					}
-					if (!$run || $renameOkay === false || $fileExists === false) {
+					$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
+					$fileExists = $storage->file_exists($internalPath);
+					if ($renameOkay === false || $fileExists === false) {
 						\OCP\Util::writeLog('webdav', 'renaming part file to final file failed', \OCP\Util::ERROR);
 						throw new Exception('Could not rename part file to final file');
 					}
@@ -205,18 +187,7 @@ class File extends Node implements IFile {
 			$this->fileView->getUpdater()->update($this->path);
 
 			if ($view) {
-				if (!$exists) {
-					\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array(
-						\OC\Files\Filesystem::signal_param_path => $hookPath
-					));
-				} else {
-					\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array(
-						\OC\Files\Filesystem::signal_param_path => $hookPath
-					));
-				}
-				\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array(
-					\OC\Files\Filesystem::signal_param_path => $hookPath
-				));
+				$this->emitPostHooks($exists);
 			}
 
 			// allow sync clients to send the mtime along in a header
@@ -234,6 +205,43 @@ class File extends Node implements IFile {
 		return '"' . $this->info->getEtag() . '"';
 	}
 
+	private function emitPreHooks($exists) {
+		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($this->path));
+		$run = true;
+
+		if (!$exists) {
+			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array(
+				\OC\Files\Filesystem::signal_param_path => $hookPath,
+				\OC\Files\Filesystem::signal_param_run => &$run,
+			));
+		} else {
+			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array(
+				\OC\Files\Filesystem::signal_param_path => $hookPath,
+				\OC\Files\Filesystem::signal_param_run => &$run,
+			));
+		}
+		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array(
+			\OC\Files\Filesystem::signal_param_path => $hookPath,
+			\OC\Files\Filesystem::signal_param_run => &$run,
+		));
+	}
+
+	private function emitPostHooks($exists) {
+		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($this->path));
+		if (!$exists) {
+			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array(
+				\OC\Files\Filesystem::signal_param_path => $hookPath
+			));
+		} else {
+			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array(
+				\OC\Files\Filesystem::signal_param_path => $hookPath
+			));
+		}
+		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array(
+			\OC\Files\Filesystem::signal_param_path => $hookPath
+		));
+	}
+
 	/**
 	 * Returns the data
 	 *
@@ -347,15 +355,30 @@ class File extends Node implements IFile {
 			$needsPartFile = $this->needsPartFile($storage);
 			$partFile = null;
 
+			$targetPath = $path . '/' . $info['name'];
+			/** @var \OC\Files\Storage\Storage $targetStorage */
+			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
+
+			$exists = $this->fileView->file_exists($targetPath);
+
 			try {
-				$targetPath = $path . '/' . $info['name'];
+				$this->emitPreHooks($exists);
+
+				$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
+
 				if ($needsPartFile) {
 					// we first assembly the target file as a part file
 					$partFile = $path . '/' . $info['name'] . '.ocTransferId' . $info['transferid'] . '.part';
-					$chunk_handler->file_assemble($partFile);
+
+
+					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
+
+
+					$chunk_handler->file_assemble($partStorage, $partInternalPath, $this->fileView->getAbsolutePath($targetPath));
 
 					// here is the final atomic rename
-					$renameOkay = $this->fileView->rename($partFile, $targetPath);
+					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
+
 					$fileExists = $this->fileView->file_exists($targetPath);
 					if ($renameOkay === false || $fileExists === false) {
 						\OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::rename() failed', \OCP\Util::ERROR);
@@ -364,28 +387,36 @@ class File extends Node implements IFile {
 							// set to null to avoid double-deletion when handling exception
 							// stray part file
 							$partFile = null;
-							$this->fileView->unlink($targetPath);
+							$targetStorage->unlink($targetInternalPath);
 						}
+						$this->changeLock(ILockingProvider::LOCK_SHARED);
 						throw new Exception('Could not rename part file assembled from chunks');
 					}
 				} else {
 					// assemble directly into the final file
-					$chunk_handler->file_assemble($targetPath);
+					$chunk_handler->file_assemble($targetStorage, $targetInternalPath, $this->fileView->getAbsolutePath($targetPath));
 				}
 
 				// allow sync clients to send the mtime along in a header
 				$request = \OC::$server->getRequest();
 				if (isset($request->server['HTTP_X_OC_MTIME'])) {
-					if ($this->fileView->touch($targetPath, $request->server['HTTP_X_OC_MTIME'])) {
+					if ($targetStorage->touch($targetInternalPath, $request->server['HTTP_X_OC_MTIME'])) {
 						header('X-OC-MTime: accepted');
 					}
 				}
 
+				$this->changeLock(ILockingProvider::LOCK_SHARED);
+
+				// since we skipped the view we need to scan and emit the hooks ourselves
+				$this->fileView->getUpdater()->update($this->path);
+
+				$this->emitPostHooks($exists);
+
 				$info = $this->fileView->getFileInfo($targetPath);
 				return $info->getEtag();
 			} catch (\Exception $e) {
 				if ($partFile !== null) {
-					$this->fileView->unlink($partFile);
+					$targetStorage->unlink($targetInternalPath);
 				}
 				$this->convertToSabreException($e);
 			}
diff --git a/lib/private/filechunking.php b/lib/private/filechunking.php
index 82bf61fa7b1..64399ad4366 100644
--- a/lib/private/filechunking.php
+++ b/lib/private/filechunking.php
@@ -178,27 +178,26 @@ class OC_FileChunking {
 	 * Assembles the chunks into the file specified by the path.
 	 * Also triggers the relevant hooks and proxies.
 	 *
-	 * @param string $path target path
+	 * @param \OC\Files\Storage\Storage $storage
+	 * @param string $path target path relative to the storage
+	 * @param string $absolutePath
+	 * @return bool assembled file size or false if file could not be created
 	 *
-	 * @return boolean assembled file size or false if file could not be created
-	 *
-	 * @throws \OC\InsufficientStorageException when file could not be fully
-	 * assembled due to lack of free space
+	 * @throws \OC\ServerNotAvailableException
 	 */
-	public function file_assemble($path) {
-		$absolutePath = \OC\Files\Filesystem::normalizePath(\OC\Files\Filesystem::getView()->getAbsolutePath($path));
+	public function file_assemble($storage, $path, $absolutePath) {
 		$data = '';
 		// use file_put_contents as method because that best matches what this function does
 		if (\OC\Files\Filesystem::isValidPath($path)) {
-			$path = \OC\Files\Filesystem::getView()->getRelativePath($absolutePath);
-			$exists = \OC\Files\Filesystem::file_exists($path);
+			$exists = $storage->file_exists($path);
 			$run = true;
+			$hookPath = \OC\Files\Filesystem::getView()->getRelativePath($absolutePath);
 			if(!$exists) {
 				OC_Hook::emit(
 					\OC\Files\Filesystem::CLASSNAME,
 					\OC\Files\Filesystem::signal_create,
 					array(
-						\OC\Files\Filesystem::signal_param_path => $path,
+						\OC\Files\Filesystem::signal_param_path => $hookPath,
 						\OC\Files\Filesystem::signal_param_run => &$run
 					)
 				);
@@ -207,14 +206,14 @@ class OC_FileChunking {
 				\OC\Files\Filesystem::CLASSNAME,
 				\OC\Files\Filesystem::signal_write,
 				array(
-					\OC\Files\Filesystem::signal_param_path => $path,
+					\OC\Files\Filesystem::signal_param_path => $hookPath,
 					\OC\Files\Filesystem::signal_param_run => &$run
 				)
 			);
 			if(!$run) {
 				return false;
 			}
-			$target = \OC\Files\Filesystem::fopen($path, 'w');
+			$target = $storage->fopen($path, 'w');
 			if($target) {
 				$count = $this->assemble($target);
 				fclose($target);
@@ -222,13 +221,13 @@ class OC_FileChunking {
 					OC_Hook::emit(
 						\OC\Files\Filesystem::CLASSNAME,
 						\OC\Files\Filesystem::signal_post_create,
-						array( \OC\Files\Filesystem::signal_param_path => $path)
+						array( \OC\Files\Filesystem::signal_param_path => $hookPath)
 					);
 				}
 				OC_Hook::emit(
 					\OC\Files\Filesystem::CLASSNAME,
 					\OC\Files\Filesystem::signal_post_write,
-					array( \OC\Files\Filesystem::signal_param_path => $path)
+					array( \OC\Files\Filesystem::signal_param_path => $hookPath)
 				);
 				return $count > 0;
 			}else{
-- 
cgit v1.2.3