diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7bf25d6c..04ae654b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
## [Unreleased]
--
+--
+
+## [1.4.0] - 2022-10-25
+
+### Changed
+
+- [#605](https://github.com/owncloud/files_primary_s3/pull/605) - Allow configurable concurrent uploads
+
+### Fixed
+
+- [#618](https://github.com/owncloud/files_primary_s3/pull/618) - Fix stream download release
+
## [1.3.0] - 2022-08-10
@@ -88,7 +99,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- First marketplace release
-[Unreleased]: https://github.com/owncloud/files_primary_s3/compare/v1.3.0...master
+[Unreleased]: https://github.com/owncloud/files_primary_s3/compare/v1.4.0...master
+[1.4.0]: https://github.com/owncloud/files_primary_s3/compare/v1.3.1...v1.4.0
+[1.3.1]: https://github.com/owncloud/files_primary_s3/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/owncloud/files_primary_s3/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/owncloud/files_primary_s3/compare/v1.1.2...v1.2.0
[1.1.2]: https://github.com/owncloud/files_primary_s3/compare/v1.1.1...v1.1.2
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 12e8b5bf..42366b49 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -19,7 +19,7 @@ This extension is the successor of the [ownCloud Objectstore App](https://market
https://github.com/owncloud/files_primary_s3/issues
https://github.com/owncloud/files_primary_s3.git
ownCloud GmbH
- 1.3.0
+ 1.4.0
https://doc.owncloud.com/server/latest/admin_manual/configuration/files/external_storage/s3_compatible_object_storage_as_primary.html
diff --git a/lib/s3storage.php b/lib/s3storage.php
index a3baef3f..6fc1cf4e 100644
--- a/lib/s3storage.php
+++ b/lib/s3storage.php
@@ -31,6 +31,7 @@
use Aws\S3\Exception\S3Exception;
use Aws\S3\ObjectUploader;
use Aws\S3\S3Client;
+use GuzzleHttp\Handler\StreamHandler;
use GuzzleHttp\Handler\CurlMultiHandler;
use GuzzleHttp\Middleware;
use OC\ServiceUnavailableException;
@@ -49,6 +50,9 @@ class S3Storage implements IObjectStore, IVersionedObjectStorage {
*/
private $connection;
+ /** @var S3Client|null */
+ private $downConnection;
+
/**
* @var array
*/
@@ -88,57 +92,20 @@ protected function init(): void {
}
$config = $this->params['options'];
if ($useGuzzle5) {
- /*
- * Note: phan runs in CI with the latest core, which has Guzzle7 or later.
- * So various things that phan reports for this Guzzle5 code have to be suppressed.
- */
- /* @phan-suppress-next-line PhanUndeclaredClassMethod */
- $client = new \GuzzleHttp\Client(['handler' => new \GuzzleHttp\Ring\Client\CurlMultiHandler()]);
- /* @phan-suppress-next-line PhanDeprecatedFunction */
- $emitter = $client->getEmitter();
- /* @phan-suppress-next-line PhanUndeclaredTypeParameter */
- $emitter->on('before', static function (\GuzzleHttp\Event\BeforeEvent $event) {
- /* @phan-suppress-next-line PhanUndeclaredClassMethod */
- $request = $event->getRequest();
- if ($request->getMethod() !== 'PUT') {
- return;
- }
- $body = $request->getBody();
- if ($body !== null && $body->getSize() !== 0) {
- return;
- }
- if ($request->hasHeader('Content-Length')) {
- return;
- }
- // force content length header on empty body
- $request->setHeader('Content-Length', '0');
- });
- $h = new \Aws\Handler\GuzzleV5\GuzzleHandler($client);
+ $h = $this->getHandlerV5(false); // curlMultiHandler
+ $dh = $this->getHandlerV5(true); // streamHandler for downloads
} else {
- // Create a handler stack that has all of the default middlewares attached
- $handler = \GuzzleHttp\HandlerStack::create(new CurlMultiHandler());
- // Push the handler onto the handler stack
- $handler->push(Middleware::mapRequest(function (RequestInterface $request) {
- if ($request->getMethod() !== 'PUT') {
- return $request;
- }
- $body = $request->getBody();
- if ($body !== null && $body->getSize() !== 0) {
- return $request;
- }
- if ($request->hasHeader('Content-Length')) {
- return $request;
- }
- // force content length header on empty body
- return $request->withHeader('Content-Length', '0');
- }));
- // Inject the handler into the client
- $client = new \GuzzleHttp\Client(['handler' => $handler]);
- $h = new GuzzleHandler($client);
+ $h = $this->getHandlerV7(false); // curlMultiHandler
+ $dh = $this->getHandlerV7(true); // streamHandler for downloads
}
$config['http_handler'] = $h;
/* @phan-suppress-next-line PhanDeprecatedFunction */
$this->connection = S3Client::factory($config);
+
+ // replace the http_handler for the download connection
+ $config['http_handler'] = $dh;
+ /* @phan-suppress-next-line PhanDeprecatedFunction */
+ $this->downConnection = S3Client::factory($config);
try {
$this->connection->listBuckets();
} catch (S3Exception $exception) {
@@ -156,6 +123,73 @@ protected function init(): void {
}
}
+ private function getHandlerV5($isStream) {
+ /*
+ * Note: phan runs in CI with the latest core, which has Guzzle7 or later.
+ * So various things that phan reports for this Guzzle5 code have to be suppressed.
+ */
+ if ($isStream) {
+ /* @phan-suppress-next-line PhanUndeclaredClassMethod */
+ $client = new \GuzzleHttp\Client(['handler' => new \GuzzleHttp\Ring\Client\StreamHandler()]);
+ } else {
+ /* @phan-suppress-next-line PhanUndeclaredClassMethod */
+ $client = new \GuzzleHttp\Client(['handler' => new \GuzzleHttp\Ring\Client\CurlMultiHandler()]);
+ }
+
+ /* @phan-suppress-next-line PhanDeprecatedFunction */
+ $emitter = $client->getEmitter();
+ /* @phan-suppress-next-line PhanUndeclaredTypeParameter */
+ $beforeEventFunc = static function (\GuzzleHttp\Event\BeforeEvent $event) {
+ /* @phan-suppress-next-line PhanUndeclaredClassMethod */
+ $request = $event->getRequest();
+ if ($request->getMethod() !== 'PUT') {
+ return;
+ }
+ $body = $request->getBody();
+ if ($body !== null && $body->getSize() !== 0) {
+ return;
+ }
+ if ($request->hasHeader('Content-Length')) {
+ return;
+ }
+ // force content length header on empty body
+ $request->setHeader('Content-Length', '0');
+ };
+ $emitter->on('before', $beforeEventFunc);
+ $h = new \Aws\Handler\GuzzleV5\GuzzleHandler($client);
+ return $h;
+ }
+
+ private function getHandlerV7($isStream) {
+ // Create a handler stack that has all of the default middlewares attached
+ if ($isStream) {
+ $handler = \GuzzleHttp\HandlerStack::create(new StreamHandler());
+ } else {
+ $handler = \GuzzleHttp\HandlerStack::create(new CurlMultiHandler());
+ }
+
+ $requestFunc = function (RequestInterface $request) {
+ if ($request->getMethod() !== 'PUT') {
+ return $request;
+ }
+ $body = $request->getBody();
+ if ($body !== null && $body->getSize() !== 0) {
+ return $request;
+ }
+ if ($request->hasHeader('Content-Length')) {
+ return $request;
+ }
+ // force content length header on empty body
+ return $request->withHeader('Content-Length', '0');
+ };
+ // Push the handler onto the handler stack
+ $handler->push(Middleware::mapRequest($requestFunc));
+ // Inject the handler into the client
+ $client = new \GuzzleHttp\Client(['handler' => $handler]);
+ $h = new GuzzleHandler($client);
+ return $h;
+ }
+
/**
* {@inheritDoc}
*/
@@ -228,7 +262,7 @@ public function readObject($urn) {
$this->init();
try {
$context = stream_context_create([
- 's3' => ['seekable' => true]
+ 's3' => ['seekable' => true, 'client' => $this->downConnection]
]);
return \fopen($this->getUrl($urn), 'rb', false, $context);
} catch (AwsException $ex) {
diff --git a/phpstan.neon b/phpstan.neon
index 3a914e2b..74481873 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -6,6 +6,7 @@ parameters:
- '#Method OCA\\Files_Primary_S3\\Panels\\Admin::getPanel\(\) should return OCP\\AppFramework\\Http\\TemplateResponse|OCP\\Template but returns null.#'
- '#Comparison operation ">=" between 7 and 7 is always true.#'
- '#Call to an undefined method GuzzleHttp\\Client::getEmitter\(\).#'
+ - '#Instantiated class GuzzleHttp\\Ring\\Client\\StreamHandler not found.#'
- '#Instantiated class GuzzleHttp\\Ring\\Client\\CurlMultiHandler not found.#'
- '#Parameter \$event of anonymous function has invalid type GuzzleHttp\\Event\\BeforeEvent.#'
- '#Call to method getRequest\(\) on an unknown class GuzzleHttp\\Event\\BeforeEvent.#'
diff --git a/sonar-project.properties b/sonar-project.properties
index 161c4dbc..3fb0f47c 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -1,7 +1,7 @@
# Organization and project keys are displayed in the right sidebar of the project homepage
sonar.organization=owncloud-1
sonar.projectKey=owncloud_files_primary_s3
-sonar.projectVersion=1.3.0
+sonar.projectVersion=1.4.0
sonar.host.url=https://sonarcloud.io
# =====================================================