diff --git a/composer.json b/composer.json index e7de2fe89..ac27e39e5 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,8 @@ "require": { "php": "^7.4", "champs-libres/async-uploader-bundle": "dev-sf4#d57134aee8e504a83c902ff0cf9f8d36ac418290", - "champs-libres/wopi-bundle": "dev-master#59b468503b9413f8d588ef9e626e7675560db3d8", - "champs-libres/wopi-lib": "dev-master#0e1da19bb6de820080b8651867a7e475be590060", + "champs-libres/wopi-bundle": "dev-master#6dd8e0a14e00131eb4b889ecc30270ee4a0e5224", + "champs-libres/wopi-lib": "dev-master#8615f4a45a39fc2b6a98765ea835fcfd39618787", "doctrine/doctrine-bundle": "^2.1", "doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/orm": "^2.7", diff --git a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php index 573d77e2c..7ff9b2ea8 100644 --- a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php +++ b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManager.php @@ -15,12 +15,17 @@ use Base64Url\Base64Url; use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Exception\StoredObjectManagerException; +use DateTimeImmutable; +use DateTimeInterface; +use RuntimeException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; -use Symfony\Contracts\HttpClient\HttpClientInterface; -use Throwable; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; +use Throwable; +use function array_key_exists; use const OPENSSL_RAW_DATA; final class StoredObjectManager implements StoredObjectManagerInterface @@ -29,6 +34,8 @@ final class StoredObjectManager implements StoredObjectManagerInterface private HttpClientInterface $client; + private array $inMemory = []; + private TempUrlGeneratorInterface $tempUrlGenerator; public function __construct( @@ -39,28 +46,35 @@ final class StoredObjectManager implements StoredObjectManagerInterface $this->tempUrlGenerator = $tempUrlGenerator; } - public function read(StoredObject $document): string + public function getLastModified(StoredObject $document): DateTimeInterface { - try { - $response = $this - ->client - ->request( - Request::METHOD_GET, - $this - ->tempUrlGenerator - ->generate( - Request::METHOD_GET, - $document->getFilename() - ) - ->url - ); - } catch (Throwable $e) { - throw StoredObjectManagerException::errorDuringHttpRequest($e); + if ($this->hasCache($document)) { + $response = $this->getResponseFromCache($document); + } else { + try { + $response = $this + ->client + ->request( + Request::METHOD_HEAD, + $this + ->tempUrlGenerator + ->generate( + Request::METHOD_PUT, + $document->getFilename() + ) + ->url + ); + } catch (TransportExceptionInterface $exception) { + throw StoredObjectManagerException::errorDuringHttpRequest($exception); + } } - if ($response->getStatusCode() !== Response::HTTP_OK) { - throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode()); - } + return $this->extractLastModifiedFromResponse($response); + } + + public function read(StoredObject $document): string + { + $response = $this->getResponseFromCache($document); try { $data = $response->getContent(); @@ -90,6 +104,10 @@ final class StoredObjectManager implements StoredObjectManagerInterface public function write(StoredObject $document, string $clearContent): void { + if ($this->hasCache($document)) { + unset($this->inMemory[$document->getUuid()->toString()]); + } + $encryptedContent = $this->hasKeysAndIv($document) ? openssl_encrypt( $clearContent, @@ -126,6 +144,63 @@ final class StoredObjectManager implements StoredObjectManagerInterface } } + private function extractLastModifiedFromResponse(ResponseInterface $response): DateTimeImmutable + { + $lastModifiedString = (($response->getHeaders()['last-modified'] ?? [])[0] ?? ''); + + $date = DateTimeImmutable::createFromFormat( + DateTimeImmutable::RFC7231, + $lastModifiedString + ); + + if (false === $date) { + throw new RuntimeException('the date from remote storage could not be parsed: ' + . $lastModifiedString); + } + + return $date; + } + + private function fillCache(StoredObject $document): void + { + try { + $response = $this + ->client + ->request( + Request::METHOD_GET, + $this + ->tempUrlGenerator + ->generate( + Request::METHOD_GET, + $document->getFilename() + ) + ->url + ); + } catch (Throwable $e) { + throw StoredObjectManagerException::errorDuringHttpRequest($e); + } + + if ($response->getStatusCode() !== Response::HTTP_OK) { + throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode()); + } + + $this->inMemory[$document->getUuid()->toString()] = $response; + } + + private function getResponseFromCache(StoredObject $document): ResponseInterface + { + if (!$this->hasCache($document)) { + $this->fillCache($document); + } + + return $this->inMemory[$document->getUuid()->toString()]; + } + + private function hasCache(StoredObject $document): bool + { + return array_key_exists($document->getUuid()->toString(), $this->inMemory); + } + private function hasKeysAndIv(StoredObject $storedObject): bool { return ([] !== $storedObject->getKeyInfos()) && ([] !== $storedObject->getIv()); diff --git a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php index 3cf67cb0c..ad40b571c 100644 --- a/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Service/StoredObjectManagerInterface.php @@ -12,9 +12,12 @@ declare(strict_types=1); namespace Chill\DocStoreBundle\Service; use Chill\DocStoreBundle\Entity\StoredObject; +use DateTimeInterface; interface StoredObjectManagerInterface { + public function getLastModified(StoredObject $document): DateTimeInterface; + /** * Get the content of a StoredObject. * diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php index 958f35b4e..a6cf0e8cd 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php @@ -147,8 +147,7 @@ final class ChillDocumentManager implements DocumentManagerInterface */ public function getLastModifiedDate(Document $document): DateTimeInterface { - // TODO: Add column 'LastModifiedDate' in StoredObject entity - return $document->getCreationDate(); + return $this->storedObjectManager->getLastModified($document); } public function getLock(Document $document): string diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php index 21725c3f1..b1ab63e8c 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php @@ -13,6 +13,7 @@ namespace Chill\WopiBundle\Service\Wopi; use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; use ChampsLibres\WopiLib\Contract\Service\WopiInterface; +use DateTimeInterface; use loophp\psr17\Psr17Interface; use Psr\Cache\CacheItemPoolInterface; use Psr\Http\Message\RequestInterface; @@ -88,11 +89,14 @@ final class ChillWopi implements WopiInterface 'UserFriendlyName' => $userIdentifier, 'SupportsUpdate' => true, 'SupportsRename' => true, + 'SupportsFolder' => false, 'DisablePrint' => false, 'AllowExternalMarketplace' => true, 'SupportedShareUrlTypes' => [ 'ReadOnly', ], + 'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document) + ->format(DateTimeInterface::ATOM), 'SHA256' => $this->documentManager->getSha256($document), 'UserInfo' => (string) $this->cache->getItem($userCacheKey)->get(), ]