handle lastUpdateTime for Wopi implementation

* get the last updated time from the stored object's storage
* improve perf on loading content from stored object's storage: keep the
response in cache
This commit is contained in:
Julien Fastré 2022-06-20 21:22:02 +02:00
parent 1742dd4951
commit 10095343ec
5 changed files with 106 additions and 25 deletions

View File

@ -10,8 +10,8 @@
"require": { "require": {
"php": "^7.4", "php": "^7.4",
"champs-libres/async-uploader-bundle": "dev-sf4#d57134aee8e504a83c902ff0cf9f8d36ac418290", "champs-libres/async-uploader-bundle": "dev-sf4#d57134aee8e504a83c902ff0cf9f8d36ac418290",
"champs-libres/wopi-bundle": "dev-master#59b468503b9413f8d588ef9e626e7675560db3d8", "champs-libres/wopi-bundle": "dev-master#6dd8e0a14e00131eb4b889ecc30270ee4a0e5224",
"champs-libres/wopi-lib": "dev-master#0e1da19bb6de820080b8651867a7e475be590060", "champs-libres/wopi-lib": "dev-master#8615f4a45a39fc2b6a98765ea835fcfd39618787",
"doctrine/doctrine-bundle": "^2.1", "doctrine/doctrine-bundle": "^2.1",
"doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.7", "doctrine/orm": "^2.7",

View File

@ -15,12 +15,17 @@ use Base64Url\Base64Url;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Exception\StoredObjectManagerException; use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
use DateTimeImmutable;
use DateTimeInterface;
use RuntimeException;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; 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; use const OPENSSL_RAW_DATA;
final class StoredObjectManager implements StoredObjectManagerInterface final class StoredObjectManager implements StoredObjectManagerInterface
@ -29,6 +34,8 @@ final class StoredObjectManager implements StoredObjectManagerInterface
private HttpClientInterface $client; private HttpClientInterface $client;
private array $inMemory = [];
private TempUrlGeneratorInterface $tempUrlGenerator; private TempUrlGeneratorInterface $tempUrlGenerator;
public function __construct( public function __construct(
@ -39,29 +46,36 @@ final class StoredObjectManager implements StoredObjectManagerInterface
$this->tempUrlGenerator = $tempUrlGenerator; $this->tempUrlGenerator = $tempUrlGenerator;
} }
public function read(StoredObject $document): string public function getLastModified(StoredObject $document): DateTimeInterface
{ {
if ($this->hasCache($document)) {
$response = $this->getResponseFromCache($document);
} else {
try { try {
$response = $this $response = $this
->client ->client
->request( ->request(
Request::METHOD_GET, Request::METHOD_HEAD,
$this $this
->tempUrlGenerator ->tempUrlGenerator
->generate( ->generate(
Request::METHOD_GET, Request::METHOD_PUT,
$document->getFilename() $document->getFilename()
) )
->url ->url
); );
} catch (Throwable $e) { } catch (TransportExceptionInterface $exception) {
throw StoredObjectManagerException::errorDuringHttpRequest($e); throw StoredObjectManagerException::errorDuringHttpRequest($exception);
}
} }
if ($response->getStatusCode() !== Response::HTTP_OK) { return $this->extractLastModifiedFromResponse($response);
throw StoredObjectManagerException::invalidStatusCode($response->getStatusCode());
} }
public function read(StoredObject $document): string
{
$response = $this->getResponseFromCache($document);
try { try {
$data = $response->getContent(); $data = $response->getContent();
} catch (Throwable $e) { } catch (Throwable $e) {
@ -90,6 +104,10 @@ final class StoredObjectManager implements StoredObjectManagerInterface
public function write(StoredObject $document, string $clearContent): void public function write(StoredObject $document, string $clearContent): void
{ {
if ($this->hasCache($document)) {
unset($this->inMemory[$document->getUuid()->toString()]);
}
$encryptedContent = $this->hasKeysAndIv($document) $encryptedContent = $this->hasKeysAndIv($document)
? openssl_encrypt( ? openssl_encrypt(
$clearContent, $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 private function hasKeysAndIv(StoredObject $storedObject): bool
{ {
return ([] !== $storedObject->getKeyInfos()) && ([] !== $storedObject->getIv()); return ([] !== $storedObject->getKeyInfos()) && ([] !== $storedObject->getIv());

View File

@ -12,9 +12,12 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Service; namespace Chill\DocStoreBundle\Service;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use DateTimeInterface;
interface StoredObjectManagerInterface interface StoredObjectManagerInterface
{ {
public function getLastModified(StoredObject $document): DateTimeInterface;
/** /**
* Get the content of a StoredObject. * Get the content of a StoredObject.
* *

View File

@ -147,8 +147,7 @@ final class ChillDocumentManager implements DocumentManagerInterface
*/ */
public function getLastModifiedDate(Document $document): DateTimeInterface public function getLastModifiedDate(Document $document): DateTimeInterface
{ {
// TODO: Add column 'LastModifiedDate' in StoredObject entity return $this->storedObjectManager->getLastModified($document);
return $document->getCreationDate();
} }
public function getLock(Document $document): string public function getLock(Document $document): string

View File

@ -13,6 +13,7 @@ namespace Chill\WopiBundle\Service\Wopi;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\WopiInterface; use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
use DateTimeInterface;
use loophp\psr17\Psr17Interface; use loophp\psr17\Psr17Interface;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
@ -88,11 +89,14 @@ final class ChillWopi implements WopiInterface
'UserFriendlyName' => $userIdentifier, 'UserFriendlyName' => $userIdentifier,
'SupportsUpdate' => true, 'SupportsUpdate' => true,
'SupportsRename' => true, 'SupportsRename' => true,
'SupportsFolder' => false,
'DisablePrint' => false, 'DisablePrint' => false,
'AllowExternalMarketplace' => true, 'AllowExternalMarketplace' => true,
'SupportedShareUrlTypes' => [ 'SupportedShareUrlTypes' => [
'ReadOnly', 'ReadOnly',
], ],
'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document)
->format(DateTimeInterface::ATOM),
'SHA256' => $this->documentManager->getSha256($document), 'SHA256' => $this->documentManager->getSha256($document),
'UserInfo' => (string) $this->cache->getItem($userCacheKey)->get(), 'UserInfo' => (string) $this->cache->getItem($userCacheKey)->get(),
] ]