]> source.dussan.org Git - nextcloud-server.git/commitdiff
Create SimpleFile only when writing the content
authorRobin Appelman <robin@icewind.nl>
Sun, 16 Feb 2020 00:14:52 +0000 (01:14 +0100)
committerRoeland Jago Douma <roeland@famdouma.nl>
Fri, 28 Feb 2020 11:55:22 +0000 (12:55 +0100)
instead of first creating an empty file and then writing the content.

This solves the overhead of creating an empty file with the common pattern:

```php
$file = $simpleFilder->newFile('foo.txt');
$file->putContent('bar.txt');
```

roughly halving the number of storage and database operations that need to be done when creating a `SimpleFile`.

This is not automatically done with `File` because that has a more complex api which I'm more hesitant to touch.
Instead the `Folder::newFile` api has been extended to accept the content for the new file.

In my local testing, the overhead of first creating an empty file took about 20% of the time for preview generation

Signed-off-by: Robin Appelman <robin@icewind.nl>
lib/private/Files/Node/Folder.php
lib/private/Files/Node/LazyRoot.php
lib/private/Files/Node/NonExistingFolder.php
lib/private/Files/SimpleFS/NewSimpleFile.php [new file with mode: 0644]
lib/private/Files/SimpleFS/SimpleFolder.php
lib/public/Files/Folder.php

index 1267c81866834d0b3d91d666fdd3402835563d79..727b08e9335f762735ec6c91b49f6fb7b11e363b 100644 (file)
@@ -173,15 +173,21 @@ class Folder extends Node implements \OCP\Files\Folder {
 
        /**
         * @param string $path
+        * @param string | resource | null $content
         * @return \OC\Files\Node\File
         * @throws \OCP\Files\NotPermittedException
         */
-       public function newFile($path) {
+       public function newFile($path, $content = null) {
                if ($this->checkPermissions(\OCP\Constants::PERMISSION_CREATE)) {
                        $fullPath = $this->getFullPath($path);
                        $nonExisting = new NonExistingFile($this->root, $this->view, $fullPath);
                        $this->sendHooks(['preWrite', 'preCreate'], [$nonExisting]);
-                       if (!$this->view->touch($fullPath)) {
+                       if ($content !== null) {
+                               $result = $this->view->file_put_contents($fullPath, $content);
+                       } else {
+                               $result = $this->view->touch($fullPath);
+                       }
+                       if (!$result) {
                                throw new NotPermittedException('Could not create path');
                        }
                        $node = new File($this->root, $this->view, $fullPath);
index 76868cfa5cdb3bb4976eba1c07f631b423535388..8076c3b4f6abefa163b6d4320930c5b4d7ccb12f 100644 (file)
@@ -394,7 +394,7 @@ class LazyRoot implements IRootFolder {
        /**
         * @inheritDoc
         */
-       public function newFile($path) {
+       public function newFile($path, $content = null) {
                return $this->__call(__FUNCTION__, func_get_args());
        }
 
index 30470740495ca5f17d563fb4fd6c8f1ff5a584ac..65af837da43bded77fcb787692051f10896fe8e4 100644 (file)
@@ -139,7 +139,7 @@ class NonExistingFolder extends Folder {
                throw new NotFoundException();
        }
 
-       public function newFile($path) {
+       public function newFile($path, $content = null) {
                throw new NotFoundException();
        }
 
diff --git a/lib/private/Files/SimpleFS/NewSimpleFile.php b/lib/private/Files/SimpleFS/NewSimpleFile.php
new file mode 100644 (file)
index 0000000..02dde1d
--- /dev/null
@@ -0,0 +1,221 @@
+<?php declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Files\SimpleFS;
+
+use Icewind\Streams\CallbackWrapper;
+use OCP\Files\File;
+use OCP\Files\Folder;
+use OCP\Files\NotFoundException;
+use OCP\Files\NotPermittedException;
+use OCP\Files\SimpleFS\ISimpleFile;
+
+class NewSimpleFile implements ISimpleFile {
+       private $parentFolder;
+       private $name;
+       /** @var File|null */
+       private $file = null;
+
+       /**
+        * File constructor.
+        *
+        * @param File $file
+        */
+       public function __construct(Folder $parentFolder, string $name) {
+               $this->parentFolder = $parentFolder;
+               $this->name = $name;
+       }
+
+       /**
+        * Get the name
+        *
+        * @return string
+        */
+       public function getName() {
+               return $this->name;
+       }
+
+       /**
+        * Get the size in bytes
+        *
+        * @return int
+        */
+       public function getSize() {
+               if ($this->file) {
+                       return $this->file->getSize();
+               } else {
+                       return 0;
+               }
+       }
+
+       /**
+        * Get the ETag
+        *
+        * @return string
+        */
+       public function getETag() {
+               if ($this->file) {
+                       return $this->file->getEtag();
+               } else {
+                       return '';
+               }
+       }
+
+       /**
+        * Get the last modification time
+        *
+        * @return int
+        */
+       public function getMTime() {
+               if ($this->file) {
+                       return $this->file->getMTime();
+               } else {
+                       return time();
+               }
+       }
+
+       /**
+        * Get the content
+        *
+        * @return string
+        * @throws NotFoundException
+        * @throws NotPermittedException
+        */
+       public function getContent() {
+               if ($this->file) {
+                       $result = $this->file->getContent();
+
+                       if ($result === false) {
+                               $this->checkFile();
+                       }
+
+                       return $result;
+               } else {
+                       return '';
+               }
+       }
+
+       /**
+        * Overwrite the file
+        *
+        * @param string|resource $data
+        * @throws NotPermittedException
+        * @throws NotFoundException
+        */
+       public function putContent($data) {
+               try {
+                       if ($this->file) {
+                               return $this->file->putContent($data);
+                       } else {
+                               $this->file = $this->parentFolder->newFile($this->name, $data);
+                       }
+               } catch (NotFoundException $e) {
+                       $this->checkFile();
+               }
+       }
+
+       /**
+        * Sometimes there are some issues with the AppData. Most of them are from
+        * user error. But we should handle them gracefull anyway.
+        *
+        * If for some reason the current file can't be found. We remove it.
+        * Then traverse up and check all folders if they exists. This so that the
+        * next request will have a valid appdata structure again.
+        *
+        * @throws NotFoundException
+        */
+       private function checkFile() {
+               $cur = $this->file;
+
+               while ($cur->stat() === false) {
+                       $parent = $cur->getParent();
+                       try {
+                               $cur->delete();
+                       } catch (NotFoundException $e) {
+                               // Just continue then
+                       }
+                       $cur = $parent;
+               }
+
+               if ($cur !== $this->file) {
+                       throw new NotFoundException('File does not exist');
+               }
+       }
+
+
+       /**
+        * Delete the file
+        *
+        * @throws NotPermittedException
+        */
+       public function delete() {
+               if ($this->file) {
+                       $this->file->delete();
+               }
+       }
+
+       /**
+        * Get the MimeType
+        *
+        * @return string
+        */
+       public function getMimeType() {
+               if ($this->file) {
+                       return $this->file->getMimeType();
+               } else {
+                       return 'text/plain';
+               }
+       }
+
+       /**
+        * Open the file as stream for reading, resulting resource can be operated as stream like the result from php's own fopen
+        *
+        * @return resource
+        * @throws \OCP\Files\NotPermittedException
+        * @since 14.0.0
+        */
+       public function read() {
+               if ($this->file) {
+                       return $this->file->fopen('r');
+               } else {
+                       return fopen('php://temp', 'r');
+               }
+       }
+
+       /**
+        * Open the file as stream for writing, resulting resource can be operated as stream like the result from php's own fopen
+        *
+        * @return resource
+        * @throws \OCP\Files\NotPermittedException
+        * @since 14.0.0
+        */
+       public function write() {
+               if ($this->file) {
+                       return $this->file->fopen('w');
+               } else {
+                       $source = fopen('php://temp', 'w+');
+                       return CallbackWrapper::wrap($source, null, null, null, null, function () use ($source) {
+                               rewind($source);
+                               $this->putContent($source);
+                       });
+               }
+       }
+}
index 833aa7d76cfd988e8a86a282699bcdd6dcf074d0..76f6a198e2520c5f770ae195c24221a6b8bc01c0 100644 (file)
@@ -81,8 +81,7 @@ class SimpleFolder implements ISimpleFolder   {
        }
 
        public function newFile($name) {
-               $file = $this->folder->newFile($name);
-
-               return new SimpleFile($file);
+               // delay creating the file until it's written to
+               return new NewSimpleFile($this->folder, $name);
        }
 }
index 99d331850cfeebc7885e66404441e10da3bda342..e7286ea028c32d755cfa5381aed34500c0ab698c 100644 (file)
@@ -109,11 +109,12 @@ interface Folder extends Node {
         * Create a new file
         *
         * @param string $path relative path of the new file
+        * @param string | resource | null $content content for the new file, since 19.0.0
         * @return \OCP\Files\File
         * @throws \OCP\Files\NotPermittedException
         * @since 6.0.0
         */
-       public function newFile($path);
+       public function newFile($path, $content = null);
 
        /**
         * search for files with the name matching $query