diff options
author | Julius Härtl <jus@bitgrid.net> | 2019-06-12 15:32:20 +0300 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2019-06-12 15:32:42 +0300 |
commit | 6824232ab2269a7b815c66f2b7cc57612f15f201 (patch) | |
tree | 61ab9e1f0a4221502187895c7c770bc97e132531 | |
parent | a0e5b0ddab7ebbaa4f09b379a7c98ad298b9b90d (diff) |
Cleanup sessions/steps when no session are active anymore
Signed-off-by: Julius Härtl <jus@bitgrid.net>
-rw-r--r-- | lib/Controller/PublicSessionController.php | 4 | ||||
-rw-r--r-- | lib/Controller/SessionController.php | 4 | ||||
-rw-r--r-- | lib/Db/DocumentMapper.php | 8 | ||||
-rw-r--r-- | lib/Db/SessionMapper.php | 11 | ||||
-rw-r--r-- | lib/Db/StepMapper.php | 9 | ||||
-rw-r--r-- | lib/DocumentHasUnsavedChangesException.php | 29 | ||||
-rw-r--r-- | lib/Service/ApiService.php | 27 | ||||
-rw-r--r-- | lib/Service/DocumentService.php | 85 | ||||
-rw-r--r-- | lib/Service/SessionService.php | 1 | ||||
-rw-r--r-- | src/components/EditorWrapper.vue | 6 | ||||
-rw-r--r-- | src/files.js | 2 | ||||
-rw-r--r-- | src/main.js | 22 | ||||
-rw-r--r-- | src/services/SyncService.js | 6 |
13 files changed, 132 insertions, 82 deletions
diff --git a/lib/Controller/PublicSessionController.php b/lib/Controller/PublicSessionController.php index 68eb2c937..9abb5170d 100644 --- a/lib/Controller/PublicSessionController.php +++ b/lib/Controller/PublicSessionController.php @@ -74,8 +74,8 @@ class PublicSessionController extends PublicShareController { * @NoAdminRequired * @PublicPage */ - public function create(string $token, string $file = null, $guestName = null): DataResponse { - return $this->apiService->create(null, $file, $token, $guestName); + public function create(string $token, string $file = null, $guestName = null, $forceRecreate = false): DataResponse { + return $this->apiService->create(null, $file, $token, $guestName, $forceRecreate); } /** diff --git a/lib/Controller/SessionController.php b/lib/Controller/SessionController.php index aff55f0bc..380353a6f 100644 --- a/lib/Controller/SessionController.php +++ b/lib/Controller/SessionController.php @@ -25,8 +25,8 @@ class SessionController extends Controller { /** * @NoAdminRequired */ - public function create(int $fileId = null, string $file = null): DataResponse { - return $this->apiService->create($fileId, $file); + public function create(int $fileId = null, string $file = null, $forceRecreate = false): DataResponse { + return $this->apiService->create($fileId, $file, null, null, $forceRecreate); } /** diff --git a/lib/Db/DocumentMapper.php b/lib/Db/DocumentMapper.php index 9f4113a9d..a4f097b1b 100644 --- a/lib/Db/DocumentMapper.php +++ b/lib/Db/DocumentMapper.php @@ -35,7 +35,12 @@ class DocumentMapper extends QBMapper { parent::__construct($db, 'text_documents', Document::class); } - public function find($documentId) { + /** + * @param $documentId + * @return Document + * @throws DoesNotExistException + */ + public function find($documentId): Document { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $result = $qb->select('*') @@ -50,4 +55,5 @@ class DocumentMapper extends QBMapper { } return Document::fromRow($data); } + } diff --git a/lib/Db/SessionMapper.php b/lib/Db/SessionMapper.php index 36e355d00..87e305cff 100644 --- a/lib/Db/SessionMapper.php +++ b/lib/Db/SessionMapper.php @@ -73,6 +73,17 @@ class SessionMapper extends QBMapper { return $this->findEntities($qb); } + public function findAllInactive() { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->select('id','color','document_id', 'last_contact','user_id','guest_name') + ->from($this->getTableName()) + ->where($qb->expr()->gt('last_contact', $qb->createNamedParameter(time()-SessionService::SESSION_VALID_TIME))) + ->execute(); + + return $this->findEntities($qb); + } + public function deleteInactive($documentId) { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); diff --git a/lib/Db/StepMapper.php b/lib/Db/StepMapper.php index b39c1d79b..1f0ec698e 100644 --- a/lib/Db/StepMapper.php +++ b/lib/Db/StepMapper.php @@ -59,4 +59,13 @@ class StepMapper extends QBMapper { ->where($qb->expr()->eq('document_id', $qb->createNamedParameter($documentId))) ->execute(); } + + public function deleteBeforeVersion($documentId, $version) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $qb->delete($this->getTableName()) + ->where($qb->expr()->eq('document_id', $qb->createNamedParameter($documentId))) + ->andWhere($qb->expr()->lte('version', $qb->createNamedParameter($version))) + ->execute(); + } } diff --git a/lib/DocumentHasUnsavedChangesException.php b/lib/DocumentHasUnsavedChangesException.php new file mode 100644 index 000000000..1fe827817 --- /dev/null +++ b/lib/DocumentHasUnsavedChangesException.php @@ -0,0 +1,29 @@ +<?php +/** + * @copyright Copyright (c) 2019 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; + + +class DocumentHasUnsavedChangesException extends \Exception { + +} diff --git a/lib/Service/ApiService.php b/lib/Service/ApiService.php index 3fd934a34..1d0c517ba 100644 --- a/lib/Service/ApiService.php +++ b/lib/Service/ApiService.php @@ -28,6 +28,7 @@ namespace OCA\Text\Service; use Exception; use OC\Files\Node\File; +use OCA\Text\DocumentHasUnsavedChangesException; use OCA\Text\DocumentSaveConflictException; use OCA\Text\VersionMismatchException; use OCP\AppFramework\Http\DataResponse; @@ -50,26 +51,36 @@ class ApiService { $this->documentService = $documentService; } - public function create($fileId = null, $filePath = null, $token = null, $guestName = null): DataResponse { + public function create($fileId = null, $filePath = null, $token = null, $guestName = null, $forceRecreate = false): DataResponse { try { $readOnly = true; /** @var File $file */ $file = null; if ($token) { - list($document, $file) = $this->documentService->createDocumentByShareToken($token, $filePath); + $file = $this->documentService->getFileByShareToken($token, $filePath); try { $this->documentService->checkSharePermissions($token, Constants::PERMISSION_UPDATE); $readOnly = false; } catch (NotFoundException $e) {} } else if ($fileId) { - list($document, $file) = $this->documentService->createDocumentByFileId($fileId); + $file = $this->documentService->getFileById($fileId); $readOnly = !$file->isUpdateable(); } else if ($filePath) { - list($document, $file) = $this->documentService->createDocumentByPath($filePath); + $file = $this->documentService->getFileByPath($filePath); $readOnly = !$file->isUpdateable(); } else { return new DataResponse('No valid file argument provided', 500); } + + $this->sessionService->removeInactiveSessions($file->getId()); + $activeSessions = $this->sessionService->getActiveSessions($file->getId()); + if (count($activeSessions) === 0) { + try { + $this->documentService->resetDocument($file->getId(), $forceRecreate); + } catch (DocumentHasUnsavedChangesException $e) {} + } + + $document = $this->documentService->createDocument($file); } catch (Exception $e) { return new DataResponse($e->getMessage(), 500); } @@ -97,9 +108,13 @@ class ApiService { public function close($documentId, $sessionId, $sessionToken): DataResponse { $this->sessionService->closeSession($documentId, $sessionId, $sessionToken); - //if ($this->documentService->) - //$this->sessionService->cleanupSessions(); $this->sessionService->removeInactiveSessions($documentId); + $activeSessions = $this->sessionService->getActiveSessions($documentId); + if (count($activeSessions) === 0) { + try { + $this->documentService->resetDocument($documentId); + } catch (DocumentHasUnsavedChangesException $e) {} + } return new DataResponse([]); } diff --git a/lib/Service/DocumentService.php b/lib/Service/DocumentService.php index a5b1f7e28..33062c327 100644 --- a/lib/Service/DocumentService.php +++ b/lib/Service/DocumentService.php @@ -32,6 +32,7 @@ use OCA\Text\Db\Document; use OCA\Text\Db\DocumentMapper; use OCA\Text\Db\Step; use OCA\Text\Db\StepMapper; +use OCA\Text\DocumentHasUnsavedChangesException; use OCA\Text\DocumentSaveConflictException; use OCA\Text\VersionMismatchException; use OCP\AppFramework\Db\DoesNotExistException; @@ -109,53 +110,13 @@ class DocumentService { } /** - * @param $path - * @return array - * @throws NotFoundException - * @throws InvalidPathException - * @throws NotPermittedException - */ - public function createDocumentByPath($path) { - /** @var File $file */ - $file = $this->rootFolder->getUserFolder($this->userId)->get($path); - return [$this->createDocument($file), $file]; - } - - /** - * @param $fileId - * @return array - * @throws NotFoundException - * @throws InvalidPathException - * @throws NotPermittedException - */ - public function createDocumentByFileId($fileId) { - $file = $this->getFileById($fileId); - return [$this->createDocument($file), $file]; - } - - /** - * @param $shareToken - * @param null $filePath - * @return array - * @throws InvalidPathException - * @throws NotFoundException - * @throws NotPermittedException - */ - public function createDocumentByShareToken($shareToken, $filePath = null) { - $file = $this->getFileByShareToken($shareToken, $filePath); - return [$this->createDocument($file), $file]; - } - - - - /** * @param File $file * @return Entity * @throws NotFoundException * @throws InvalidPathException * @throws NotPermittedException */ - protected function createDocument(File $file): Document { + public function createDocument(File $file): Document { try { $document = $this->documentMapper->find($file->getFileInfo()->getId()); @@ -166,14 +127,6 @@ class DocumentService { return $document; } - // TODO: Only do this when no sessions active, otherise we need to resolve the conflict differently - // TODO: Add parameter so that we can force this, else just opening the document will cause a rebuild - $lastMTime = $document->getLastSavedVersionTime(); - if ($file->getMTime() > $lastMTime && $lastMTime > 0) { - $this->resetDocument($document->getId()); - throw new NotFoundException(); - } - return $document; } catch (DoesNotExistException $e) { } catch (InvalidPathException $e) { @@ -304,18 +257,30 @@ class DocumentService { return $document; } - public function resetDocument($documentId): void { - $this->stepMapper->deleteAll($documentId); + /** + * @param $documentId + * @param bool $force + * @throws DocumentHasUnsavedChangesException + */ + public function resetDocument($documentId, $force = false): void { try { $document = $this->documentMapper->find($documentId); - $this->documentMapper->delete($document); - } catch (DoesNotExistException $e) { - } - try { - $this->appData->getFolder('documents')->getFile($documentId)->delete(); - } catch (NotFoundException $e) { - } catch (NotPermittedException $e) { + if ($force || !$this->hasUnsavedChanges($document)) { + $this->stepMapper->deleteAll($documentId); + $this->documentMapper->delete($document); + + try { + $this->appData->getFolder('documents')->getFile($documentId)->delete(); + } catch (NotFoundException $e) { + } catch (NotPermittedException $e) { + } + } + + if ($this->hasUnsavedChanges($document)) { + throw new DocumentHasUnsavedChangesException('Did not reset document, as it has unsaved changes'); + } + } catch (DoesNotExistException $e) { } } @@ -323,6 +288,10 @@ class DocumentService { return $this->rootFolder->getUserFolder($this->userId)->getById($fileId)[0]; } + public function getFileByPath($path): Node { + return $this->rootFolder->getUserFolder($this->userId)->get($path); + } + /** * @param $shareToken * @param null|string $path diff --git a/lib/Service/SessionService.php b/lib/Service/SessionService.php index 9de0ea0a9..fe796955c 100644 --- a/lib/Service/SessionService.php +++ b/lib/Service/SessionService.php @@ -89,6 +89,7 @@ class SessionService { public function removeInactiveSessions($documentId) { return $this->sessionMapper->deleteInactive($documentId); } + public function isValidSession($documentId, $sessionId, $token) { try { $session = $this->sessionMapper->find($documentId, $sessionId, $token); diff --git a/src/components/EditorWrapper.vue b/src/components/EditorWrapper.vue index b45c2e2b2..5de594bd4 100644 --- a/src/components/EditorWrapper.vue +++ b/src/components/EditorWrapper.vue @@ -183,6 +183,7 @@ export default { lastSavedString: '', syncError: null, readOnly: true, + forceRecreate: false, guestName: '', guestNameConfirmed: false, @@ -283,6 +284,7 @@ export default { this.syncService = new SyncService({ shareToken: this.shareToken, guestName: this.guestName, + forceRecreate: this.forceRecreate, serialize: (document) => { const markdown = (createMarkdownSerializer(this.tiptap.nodes, this.tiptap.marks)).serialize(document) console.debug('serialized document', { markdown }) @@ -316,7 +318,7 @@ export default { new Collaboration({ // the initial version we start with // version is an integer which is incremented with every change - version: this.syncService.steps.length, + version: this.document.initialVersion, clientID: this.currentSession.id, // debounce changes so we can save some bandwidth debounce: EDITOR_PUSH_DEBOUNCE, @@ -370,6 +372,7 @@ export default { } }) this.syncService.open({ fileId: this.fileId, filePath: this.filePath }) + this.forceRecreate = false }, resolveUseThisVersion() { @@ -378,6 +381,7 @@ export default { }, resolveUseServerVersion() { + this.forceRecreate = true this.syncService.close() this.syncService = null this.tiptap.destroy() diff --git a/src/files.js b/src/files.js index 8ce91f0be..5bb990507 100644 --- a/src/files.js +++ b/src/files.js @@ -80,5 +80,5 @@ documentReady(() => { }) OCA.Text = { - + Editor } diff --git a/src/main.js b/src/main.js index f4639a102..404cd20a4 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,4 @@ import Vue from 'vue' - import Editor from './components/EditorWrapper' __webpack_nonce__ = btoa(OC.requestToken) // eslint-disable-line @@ -8,12 +7,17 @@ __webpack_public_path__ = OC.linkTo('text', 'js/') // eslint-disable-line Vue.prototype.t = t Vue.prototype.OCA = OCA -new Vue({ - render: h => h(Editor, { - props: { - relativePath: '/welcome.md', - active: true - } - }) +if (document.getElementById('maineditor')) { + new Vue({ + render: h => h(Editor, { + props: { + relativePath: '/welcome.md', + active: true + } + }) + }).$mount('#maineditor') +} -}).$mount('#maineditor') +OCA.Text = { + Editor +} diff --git a/src/services/SyncService.js b/src/services/SyncService.js index f63113f74..976c7b5b6 100644 --- a/src/services/SyncService.js +++ b/src/services/SyncService.js @@ -27,6 +27,7 @@ import { getVersion, sendableSteps } from 'prosemirror-collab' const defaultOptions = { shareToken: null, + forceRecreate: false, serialize: (document) => document } @@ -90,7 +91,7 @@ class SyncService { this.emit('loaded', { document: this.document, session: this.session, - documentSource: data + documentSource: '' + data }) }) }).catch((error) => { @@ -109,7 +110,8 @@ class SyncService { fileId: fileId, file: filePath, token: this.options.shareToken, - guestName: this.options.guestName + guestName: this.options.guestName, + forceRecreate: this.options.forceRecreate } }).then((response) => { this.document = response.data.document |