aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/dav/lib/Upload/AssemblyStream.php22
-rw-r--r--apps/dav/tests/unit/Upload/AssemblyStreamTest.php22
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php8
-rw-r--r--lib/private/Files/ObjectStore/S3ObjectTrait.php33
4 files changed, 64 insertions, 21 deletions
diff --git a/apps/dav/lib/Upload/AssemblyStream.php b/apps/dav/lib/Upload/AssemblyStream.php
index 736905d01c2..642a8604b17 100644
--- a/apps/dav/lib/Upload/AssemblyStream.php
+++ b/apps/dav/lib/Upload/AssemblyStream.php
@@ -75,6 +75,10 @@ class AssemblyStream implements \Icewind\Streams\File {
$offset = $this->size + $offset;
}
+ if ($offset === $this->pos) {
+ return true;
+ }
+
if ($offset > $this->size) {
return false;
}
@@ -95,7 +99,7 @@ class AssemblyStream implements \Icewind\Streams\File {
$stream = $this->getStream($this->nodes[$nodeIndex]);
$nodeOffset = $offset - $nodeStart;
- if (fseek($stream, $nodeOffset) === -1) {
+ if ($nodeOffset > 0 && fseek($stream, $nodeOffset) === -1) {
return false;
}
$this->currentNode = $nodeIndex;
@@ -126,9 +130,14 @@ class AssemblyStream implements \Icewind\Streams\File {
}
}
- do {
+ $collectedData = '';
+ // read data until we either got all the data requested or there is no more stream left
+ while ($count > 0 && !is_null($this->currentStream)) {
$data = fread($this->currentStream, $count);
$read = strlen($data);
+
+ $count -= $read;
+ $collectedData .= $data;
$this->currentNodeRead += $read;
if (feof($this->currentStream)) {
@@ -145,14 +154,11 @@ class AssemblyStream implements \Icewind\Streams\File {
$this->currentStream = null;
}
}
- // if no data read, try again with the next node because
- // returning empty data can make the caller think there is no more
- // data left to read
- } while ($read === 0 && !is_null($this->currentStream));
+ }
// update position
- $this->pos += $read;
- return $data;
+ $this->pos += strlen($collectedData);
+ return $collectedData;
}
/**
diff --git a/apps/dav/tests/unit/Upload/AssemblyStreamTest.php b/apps/dav/tests/unit/Upload/AssemblyStreamTest.php
index 51c53fb262f..854d02bfc10 100644
--- a/apps/dav/tests/unit/Upload/AssemblyStreamTest.php
+++ b/apps/dav/tests/unit/Upload/AssemblyStreamTest.php
@@ -25,12 +25,16 @@ class AssemblyStreamTest extends \Test\TestCase {
/**
* @dataProvider providesNodes()
*/
- public function testGetContentsFread($expected, $nodes): void {
+ public function testGetContentsFread($expected, $nodes, $chunkLength = 3): void {
$stream = AssemblyStream::wrap($nodes);
$content = '';
while (!feof($stream)) {
- $content .= fread($stream, 3);
+ $chunk = fread($stream, $chunkLength);
+ $content .= $chunk;
+ if ($chunkLength !== 3) {
+ $this->assertEquals($chunkLength, strlen($chunk));
+ }
}
$this->assertEquals($expected, $content);
@@ -103,7 +107,19 @@ class AssemblyStreamTest extends \Test\TestCase {
]],
'a ton of nodes' => [
$tonofdata, $tonofnodes
- ]
+ ],
+ 'one read over multiple nodes' => [
+ '1234567890', [
+ $this->buildNode('0', '1234'),
+ $this->buildNode('1', '5678'),
+ $this->buildNode('2', '90'),
+ ], 10],
+ 'two reads over multiple nodes' => [
+ '1234567890', [
+ $this->buildNode('0', '1234'),
+ $this->buildNode('1', '5678'),
+ $this->buildNode('2', '90'),
+ ], 5],
];
}
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index 0963ffbb28f..78eb52a53d9 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -457,6 +457,14 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
}
public function writeStream(string $path, $stream, ?int $size = null): int {
+ if ($size === null) {
+ $stats = fstat($stream);
+ if (is_array($stats) && isset($stats['size'])) {
+ $size = $stats['size'];
+ $this->logger->warning("stream size $size");
+ }
+ }
+
$stat = $this->stat($path);
if (empty($stat)) {
// create new file
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php
index 2e625033751..9d7cfa644e6 100644
--- a/lib/private/Files/ObjectStore/S3ObjectTrait.php
+++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php
@@ -140,20 +140,33 @@ trait S3ObjectTrait {
* @since 7.0.0
*/
public function writeObject($urn, $stream, ?string $mimetype = null) {
+ $canSeek = fseek($stream, 0, SEEK_CUR) === 0;
$psrStream = Utils::streamFor($stream);
- // ($psrStream->isSeekable() && $psrStream->getSize() !== null) evaluates to true for a On-Seekable stream
- // so the optimisation does not apply
- $buffer = new Psr7\Stream(fopen('php://memory', 'rwb+'));
- Utils::copyToStream($psrStream, $buffer, $this->putSizeLimit);
- $buffer->seek(0);
- if ($buffer->getSize() < $this->putSizeLimit) {
- // buffer is fully seekable, so use it directly for the small upload
- $this->writeSingle($urn, $buffer, $mimetype);
+
+ $size = $psrStream->getSize();
+ if ($size === null || !$canSeek) {
+ // The s3 single-part upload requires the size to be known for the stream.
+ // So for input streams that don't have a known size, we need to copy (part of)
+ // the input into a temporary stream so the size can be determined
+ $buffer = new Psr7\Stream(fopen('php://temp', 'rw+'));
+ Utils::copyToStream($psrStream, $buffer, $this->putSizeLimit);
+ $buffer->seek(0);
+ if ($buffer->getSize() < $this->putSizeLimit) {
+ // buffer is fully seekable, so use it directly for the small upload
+ $this->writeSingle($urn, $buffer, $mimetype);
+ } else {
+ $loadStream = new Psr7\AppendStream([$buffer, $psrStream]);
+ $this->writeMultiPart($urn, $loadStream, $mimetype);
+ }
} else {
- $loadStream = new Psr7\AppendStream([$buffer, $psrStream]);
- $this->writeMultiPart($urn, $loadStream, $mimetype);
+ if ($size < $this->putSizeLimit) {
+ $this->writeSingle($urn, $psrStream, $mimetype);
+ } else {
+ $this->writeMultiPart($urn, $psrStream, $mimetype);
+ }
}
+ $psrStream->close();
}
/**