refactor/introduce IContext - #9062
Conversation
3e17210 to
a2260eb
Compare
ae7bbf6 to
92e1890
Compare
c9d35f7 to
cd2d5b9
Compare
b0a7b15 to
7cedf66
Compare
cd2d5b9 to
c4cca9f
Compare
7cedf66 to
1bb5586
Compare
7232808 to
5907ff4
Compare
1bc577e to
58fe27f
Compare
|
So far I read up to commit What I stumble over is that the IContext file contains the additional classes Maybe this is also OK, just to double check: a call to No biggie: new classes miss the Can consider to mark the new event unstable or experimental, though I am not aware of a phpdoc or internal established annotation. |
|
On my dev instance I get an error during Migration: {
"reqId": "miQrQfvExjkhgL8Z3V6T",
"level": 3,
"time": "2026-08-19T14:14:45+00:00",
"remoteAddr": "",
"user": "--",
"app": "updater",
"method": "",
"url": "--",
"scriptName": "occ",
"message": "\\OC\\Updater::failure: Exception: Database error when running migration 090000Date20260817110024 for app text\nAn exception occurred while executing a query: SQLSTATE[23502]: Not null violation: 7 ERROR: column \"context_type\" of relation \"oc_text_documents\" contains null values",
"userAgent": "--",
"version": "35.0.0.3",
"occ_command": [
"occ",
"upgrade"
],
"data": {
"app": "updater"
}
}DB is postgres. Probably happens because the column is added as UPDATE: same with context_id. Default to 0? |
When creating a new session we load the document by context. Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Droped in a7a63d2 . Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
File ids used to be document ids. But not anymore. In all current calls to `resetDocument` the parameter send actually is a file id. Follow up: Make this generic for all context types.--signoff Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
* Use IShare and IUser in the file context api explicitely. * Make creating the context cheap. * File is accessed when needed and then cached. Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
The two used to be the same - but now we have contexts to store the file id. Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
For direct editing pass the user to the attachment service. For the sake of consistency pass IShare|IUser from the controllers everywhere. This way we can also build on the permission checks and getters in the controllers. Signed-off-by: Max <max@nextcloud.com>
Use exceptions to destinguish between not found and forbidden. We try to not expose wether a file exists or not. But if the file exists but download permissions have not been granted we want to be explicit about that and return a 403 response code. Signed-off-by: Max <max@nextcloud.com>
The directory name cannot be derived from the document id anymore as the document id and the text file id now differ. Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
We can detect the conflict afterwards and handle it in the document service. This makes the `updateDocument` function easier to understand and reuse. Signed-off-by: Max <max@nextcloud.com>
173ad94 to
fe36add
Compare
If this event is emitted for a context the related document and all connected sessions will be cleared. This will cause the next requests to be answered with 412 responses which in turn cause the client to render a conflict screen. Signed-off-by: Max <max@nextcloud.com>
* Separates the file specific concerns from the generic document concerns. * This makes `DocumentService` independent of `LockService` and `FileService`. Signed-off-by: Max <max@nextcloud.com>
fe36add to
4362657
Compare
| #onSync | ||
| #onOpened | ||
| #processingVersion = 0 | ||
| #documentId = 0 |
| return new DataResponse('', Http::STATUS_NOT_FOUND); | ||
| } | ||
|
|
||
| private function getAuth(string $shareToken, bool $updatePermissionRequired = true): IShare|IUser { |
There was a problem hiding this comment.
wonder whether it makes sense to have an umbrella interface here to use as return type hint. Or a wrapper class. Probably overkill. Benefit would be not to have a union return, but at some point you've got to figure out when to work with a share or user at the callee, so… just thinking loudly.
| @@ -361,6 +361,10 @@ public function autosave(Document $document, IContext $context, int $version, st | |||
| if ($lastMTime > 0 && !$force && !$this->cache->get('document-save-lock-' . $document->id)) { | |||
| $updatedDocument = $context->updateDocument($document); | |||
There was a problem hiding this comment.
$document and $updatedDocument are they same. Objects are passed by reference, and FileContext does not clone $document but just returns it (provided the checks pass and don't return null)
| $oldChecksum = $document->getChecksum(); | ||
| $newChecksum = $updatedDocument->getChecksum(); |
There was a problem hiding this comment.
here as well, same document instance that is acted upon
| /** | ||
| * @template-implements IEventListener<Event|DocumentContentUpdated> | ||
| */ | ||
| class DocumentContentUpdatedListener implements IEventListener { |
| : generateUrl('/apps/text') | ||
| const url = `${_baseUrl}/session/${params.fileId}/create` | ||
| export async function openShare(params: OpenShareParams): Promise<{ connection: Connection, data: OpenData }> { | ||
| const url = generateUrl('/apps/text/public/session/123/create') |
| $userFolder = $this->rootFolder->getUserFolder($userId); | ||
| $file = $userFolder->getFirstNodeById($documentId); | ||
| private function getTextFile(int $documentId, IShare|IUser $auth): File { | ||
| $document = $this->documentMapper->find($documentId); |
There was a problem hiding this comment.
could also result in a DoesNotExistException, which is not handled
| } | ||
|
|
||
| public function getContext(string $type, int $id, IShare|IUser $auth): IContext { | ||
| $factoryClassName = $this->getContexts()[$type]; |
There was a problem hiding this comment.
| $factoryClassName = $this->getContexts()[$type]; | |
| $factoryClassName = $this->getContexts()[$type] ?? null; |
to avoid log spam on non-existing keys
|
|
||
| export interface AutofocusOptions { | ||
| fileId: number | null | ||
| id: number | null |
| shareToken = undefined, | ||
| } = defineProps<{ | ||
| fileId?: number | ||
| defineProps<{ |
There was a problem hiding this comment.
I get the hint:
ViewerComponent.vue migrated to type-only defineProps without withDefaults, making active and isEmbedded required and dropping the onLoadedHandler: () => {} default; Viewer mounts this externally via ViewerView.js (props: ViewerComponent.props), so expect missing-required-prop warnings and @loaded="undefined". And createEditor.ts still gates on Boolean(options.fileId), so a caller using the newly-advertised context option falls through to createMarkdownContentEditor.
But I do not fully understand it 🙈
| ->where($qb->expr()->eq('context_type', $qb->createNamedParameter($type))) | ||
| ->andWhere($qb->expr()->eq('context_id', $qb->createNamedParameter($id))) |
There was a problem hiding this comment.
The optional query type is not specified, at least for context_id it should be IQueryBuilder::PARAM_INT. IQueryBuilder::PARAM_STR is the default.
| } | ||
|
|
||
| $column = $table->getColumn('id'); | ||
| if (!$column->getAutoincrement()) { |
There was a problem hiding this comment.
actually, I am not sure whether this is safe, also across DB types. I do not see similar code in Nextcloud. Maybe double check with @nickvergessen
There was a problem hiding this comment.
Adding autoincrement does not work as far as I recall. Had it in another app when we had to add the primary keys to all databases.
Todo
Prerequisits
Backend
syncrequests with IContext.AttachmentServicebased on context.ApiService.pushrequests.Frontend
Provide these props when saving.We still use thedocumentIdto save and resolve the context on the backend.Testing
Feedback
removeInactiveSessionsWithoutSteps()within the create flow was removed, and not introduced back elsewhere.Documentation