diff options
author | Julius Härtl <jus@bitgrid.net> | 2020-02-04 22:17:01 +0300 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2020-02-04 22:17:01 +0300 |
commit | 478760aaeb0c143cac4dea35289d818de062c279 (patch) | |
tree | 86b10c6142172675e136f2bb2b42dd2955492c26 /lib | |
parent | de537819d51795ff2a8c720030a5c6baee541421 (diff) |
Add occ command to restore documents to their last saved state
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Command/ResetDocument.php | 99 | ||||
-rw-r--r-- | lib/Db/SessionMapper.php | 8 | ||||
-rw-r--r-- | lib/Db/StepMapper.php | 10 | ||||
-rw-r--r-- | lib/Service/DocumentService.php | 77 |
4 files changed, 164 insertions, 30 deletions
diff --git a/lib/Command/ResetDocument.php b/lib/Command/ResetDocument.php new file mode 100644 index 000000000..ca414f7e2 --- /dev/null +++ b/lib/Command/ResetDocument.php @@ -0,0 +1,99 @@ +<?php +/** + * @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net> + * + * @author Julius Härtl <jus@bitgrid.net> + * + * @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 OCA\Text\Command; + +use OCA\Text\Db\DocumentMapper; +use OCA\Text\Db\SessionMapper; +use OCA\Text\Db\StepMapper; +use OCA\Text\Service\DocumentService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class ResetDocument extends Command { + + protected $documentService; + protected $documentMapper; + protected $stepMapper; + protected $sessionMapper; + + public function __construct(DocumentService $documentService, DocumentMapper $documentMapper, StepMapper $stepMapper, SessionMapper $sessionMapper) { + parent::__construct(); + + $this->documentService = $documentService; + $this->documentMapper = $documentMapper; + $this->stepMapper = $stepMapper; + $this->sessionMapper = $sessionMapper; + } + + protected function configure() { + $this + ->setName('text:reset') + ->setDescription('Reset a text document') + ->addArgument( + 'file-id', + InputArgument::REQUIRED, + 'File id of the document to rest' + ) + ->addOption( + 'full', + 'f', + null, + 'Drop all existing steps and use the currently saved version' + ) + ; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) { + + $fileId = $input->getArgument('file-id'); + $fullReset = $input->getOption('full'); + + if ($fullReset) { + $output->writeln('Full document reset'); + $this->documentService->resetDocument($fileId, true); + } else { + $output->writeln('Trying to restore to last saved version'); + $document = $this->documentMapper->find($fileId); + $deleted = $this->stepMapper->deleteAfterVersion($fileId, $document->getLastSavedVersion()); + if ($deleted > 0) { + $document->setCurrentVersion($document->getLastSavedVersion()); + $this->documentMapper->update($document); + $this->sessionMapper->deleteByDocumentId($fileId); + $output->writeln('Reverted document to the last saved version'); + } else { + $output->writeln('Failed revert changes that are newer than the last saved version'); + } + } + + } +} + + diff --git a/lib/Db/SessionMapper.php b/lib/Db/SessionMapper.php index 0bef02a2e..f12aad9d8 100644 --- a/lib/Db/SessionMapper.php +++ b/lib/Db/SessionMapper.php @@ -101,4 +101,12 @@ class SessionMapper extends QBMapper { return $qb->execute(); } + public function deleteByDocumentId($documentId) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->delete($this->getTableName()) + ->where($qb->expr()->eq('document_id', $qb->createNamedParameter($documentId))); + return $qb->execute(); + } + } diff --git a/lib/Db/StepMapper.php b/lib/Db/StepMapper.php index 97cb65990..44b7eafd0 100644 --- a/lib/Db/StepMapper.php +++ b/lib/Db/StepMapper.php @@ -69,4 +69,14 @@ class StepMapper extends QBMapper { ->andWhere($qb->expr()->lte('version', $qb->createNamedParameter($version))) ->execute(); } + + public function deleteAfterVersion($documentId, $version): int { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + return $qb->delete($this->getTableName()) + ->where($qb->expr()->eq('document_id', $qb->createNamedParameter($documentId))) + ->andWhere($qb->expr()->gt('version', $qb->createNamedParameter($version))) + ->execute(); + } + } diff --git a/lib/Service/DocumentService.php b/lib/Service/DocumentService.php index ea10ef569..d2b9bfcb0 100644 --- a/lib/Service/DocumentService.php +++ b/lib/Service/DocumentService.php @@ -189,42 +189,59 @@ class DocumentService { * @throws VersionMismatchException */ public function addStep($documentId, $sessionId, $steps, $version): array { - if (!$this->cache->get('document-push-lock-' . $documentId)) { + $document = null; + $oldVersion = null; + + if ($this->cache->get('document-push-lock-' . $documentId)) { throw new VersionMismatchException('Version does not match'); } $this->cache->set('document-save-lock-' . $documentId, true, 10); - $document = $this->documentMapper->find($documentId); - - if ($version !== $document->getCurrentVersion()) { - throw new VersionMismatchException('Version does not match'); - } - $stepsJson = json_encode($steps); - if (!is_array($steps) || $stepsJson === null) { - throw new InvalidArgumentException('Failed to encode steps'); - } - $validStepTypes = ['addMark', 'removeMark', 'replace', 'replaceAround']; - foreach ($steps as $step) { - if (array_key_exists('stepType', $step) && !in_array($step['stepType'], $validStepTypes, true)) { - throw new InvalidArgumentException('Invalid step data'); + try { + $document = $this->documentMapper->find($documentId); + if ($version !== $document->getCurrentVersion()) { + throw new VersionMismatchException('Version does not match'); + } + $stepsJson = json_encode($steps); + if (!is_array($steps) || $stepsJson === null) { + throw new InvalidArgumentException('Failed to encode steps'); + } + $validStepTypes = ['addMark', 'removeMark', 'replace', 'replaceAround']; + foreach ($steps as $step) { + if (array_key_exists('stepType', $step) && !in_array($step['stepType'], $validStepTypes, true)) { + throw new InvalidArgumentException('Invalid step data'); + } } + $oldVersion = $document->getCurrentVersion(); + $newVersion = $oldVersion + count($steps); + $this->cache->set('document-version-' . $document->getId(), $newVersion); + $document->setCurrentVersion($newVersion); + $this->documentMapper->update($document); + $step = new Step(); + $step->setData($stepsJson); + $step->setSessionId($sessionId); + $step->setDocumentId($documentId); + $step->setVersion($version + 1); + $this->stepMapper->insert($step); + // TODO restore old version for document if adding steps has failed + // TODO write steps to cache for quicker reading + $this->cache->remove('document-push-lock-' . $documentId); + return $steps; + } catch (DoesNotExistException $e) { + $this->cache->remove('document-push-lock-' . $documentId); + throw $e; + } catch (\Throwable $e) { + if ($document !== null && $oldVersion !== null) { + $this->logger->error('This should never happen. An error occured when storing the version, trying to recover the last stable one'); + $this->logger->logException($e); + $document->setCurrentVersion($oldVersion); + $this->documentMapper->update($document); + $this->cache->set('document-version-' . $document->getId(), $oldVersion); + $this->stepMapper->deleteAfterVersion($documentId, $oldVersion); + } + $this->cache->remove('document-push-lock-' . $documentId); + throw $e; } - $newVersion = $document->getCurrentVersion() + count($steps); - $this->cache->set('document-version-'.$document->getId(), $newVersion); - $document->setCurrentVersion($newVersion); - $this->documentMapper->update($document); - // FIXME: DEBUG - // there might be a race condition here where the version is updated w - $step = new Step(); - $step->setData($stepsJson); - $step->setSessionId($sessionId); - $step->setDocumentId($documentId); - $step->setVersion($version+1); - $this->stepMapper->insert($step); - // TODO restore old version for document if adding steps has failed - // TODO write steps to cache for quicker reading - $this->cache->remove('document-push-lock-' . $documentId); - return $steps; } public function getSteps($documentId, $lastVersion) { |