diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-09-20 13:31:30 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-20 13:31:30 +0200 |
commit | 7ff911665e7507a800e05fe9cd80e0304cd11dbc (patch) | |
tree | cebdf95fc04e72fcd00e3462943cdcca5b62b6f2 /apps | |
parent | 5a501f24593b7cb34c8625370b44c69c124e04af (diff) | |
parent | a6ebe942050ca5ce1ea59659cdb3926ada2653eb (diff) | |
download | nextcloud-server-7ff911665e7507a800e05fe9cd80e0304cd11dbc.tar.gz nextcloud-server-7ff911665e7507a800e05fe9cd80e0304cd11dbc.zip |
Merge pull request #47905 from nextcloud/fix/files-duplicated-nodes
fix(files): Ensure children are removed from folder and not duplicated
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files/src/store/paths.spec.ts | 130 | ||||
-rw-r--r-- | apps/files/src/store/paths.ts | 75 | ||||
-rw-r--r-- | apps/files/src/views/FilesList.vue | 2 |
3 files changed, 194 insertions, 13 deletions
diff --git a/apps/files/src/store/paths.spec.ts b/apps/files/src/store/paths.spec.ts new file mode 100644 index 00000000000..b4c3f2fa686 --- /dev/null +++ b/apps/files/src/store/paths.spec.ts @@ -0,0 +1,130 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { describe, beforeEach, test, expect } from 'vitest' +import { setActivePinia, createPinia } from 'pinia' +import { usePathsStore } from './paths.ts' +import { emit } from '@nextcloud/event-bus' +import { File, Folder } from '@nextcloud/files' +import { useFilesStore } from './files.ts' + +describe('Path store', () => { + + let store: ReturnType<typeof usePathsStore> + let files: ReturnType<typeof useFilesStore> + let root: Folder & { _children?: string[] } + + beforeEach(() => { + setActivePinia(createPinia()) + + root = new Folder({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/', id: 1 }) + files = useFilesStore() + files.setRoot({ service: 'files', root }) + + store = usePathsStore() + }) + + test('Folder is created', () => { + // no defined paths + expect(store.paths).toEqual({}) + + // create the folder + const node = new Folder({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/folder', id: 2 }) + emit('files:node:created', node) + + // see that the path is added + expect(store.paths).toEqual({ files: { [node.path]: node.source } }) + + // see that the node is added + expect(root._children).toEqual([node.source]) + }) + + test('File is created', () => { + // no defined paths + expect(store.paths).toEqual({}) + + // create the file + const node = new File({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/file.txt', id: 2, mime: 'text/plain' }) + emit('files:node:created', node) + + // see that there are still no paths + expect(store.paths).toEqual({}) + + // see that the node is added + expect(root._children).toEqual([node.source]) + }) + + test('Existing file is created', () => { + // no defined paths + expect(store.paths).toEqual({}) + + // create the file + const node1 = new File({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/file.txt', id: 2, mime: 'text/plain' }) + emit('files:node:created', node1) + + // see that there are still no paths + expect(store.paths).toEqual({}) + + // see that the node is added + expect(root._children).toEqual([node1.source]) + + // create the same named file again + const node2 = new File({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/file.txt', id: 2, mime: 'text/plain' }) + emit('files:node:created', node2) + + // see that there are still no paths and the children are not duplicated + expect(store.paths).toEqual({}) + expect(root._children).toEqual([node1.source]) + + }) + + test('Existing folder is created', () => { + // no defined paths + expect(store.paths).toEqual({}) + + // create the file + const node1 = new Folder({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/folder', id: 2 }) + emit('files:node:created', node1) + + // see the path is added + expect(store.paths).toEqual({ files: { [node1.path]: node1.source } }) + + // see that the node is added + expect(root._children).toEqual([node1.source]) + + // create the same named file again + const node2 = new Folder({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/folder', id: 2 }) + emit('files:node:created', node2) + + // see that there is still only one paths and the children are not duplicated + expect(store.paths).toEqual({ files: { [node1.path]: node1.source } }) + expect(root._children).toEqual([node1.source]) + }) + + test('Folder is deleted', () => { + const node = new Folder({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/folder', id: 2 }) + emit('files:node:created', node) + // see that the path is added and the children are set-up + expect(store.paths).toEqual({ files: { [node.path]: node.source } }) + expect(root._children).toEqual([node.source]) + + emit('files:node:deleted', node) + // See the path is removed + expect(store.paths).toEqual({ files: {} }) + // See the child is removed + expect(root._children).toEqual([]) + }) + + test('File is deleted', () => { + const node = new File({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/file.txt', id: 2, mime: 'text/plain' }) + emit('files:node:created', node) + // see that the children are set-up + expect(root._children).toEqual([node.source]) + + emit('files:node:deleted', node) + // See the child is removed + expect(root._children).toEqual([]) + }) +}) diff --git a/apps/files/src/store/paths.ts b/apps/files/src/store/paths.ts index 2993cc9d704..8c197400eac 100644 --- a/apps/files/src/store/paths.ts +++ b/apps/files/src/store/paths.ts @@ -2,7 +2,7 @@ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import type { FileSource, PathsStore, PathOptions, ServicesState } from '../types' +import type { FileSource, PathsStore, PathOptions, ServicesState, Service } from '../types' import { defineStore } from 'pinia' import { FileType, Folder, Node, getNavigation } from '@nextcloud/files' import { subscribe } from '@nextcloud/event-bus' @@ -41,6 +41,57 @@ export const usePathsStore = function(...args) { Vue.set(this.paths[payload.service], payload.path, payload.source) }, + deletePath(service: Service, path: string) { + // skip if service does not exist + if (!this.paths[service]) { + return + } + + Vue.delete(this.paths[service], path) + }, + + onDeletedNode(node: Node) { + const service = getNavigation()?.active?.id || 'files' + + if (node.type === FileType.Folder) { + // Delete the path + this.deletePath( + service, + node.path, + ) + } + + // Remove node from children + if (node.dirname === '/') { + const root = files.getRoot(service) as Folder & { _children?: string[] } + // ensure sources are unique + const children = new Set(root._children ?? []) + children.delete(node.source) + Vue.set(root, '_children', [...children.values()]) + return + } + + if (this.paths[service][node.dirname]) { + const parentSource = this.paths[service][node.dirname] + const parentFolder = files.getNode(parentSource) as Folder & { _children?: string[] } + + if (!parentFolder) { + logger.error('Parent folder not found', { parentSource }) + return + } + + logger.debug('Path exists, removing from children', { parentFolder, node }) + + // ensure sources are unique + const children = new Set(parentFolder._children ?? []) + children.delete(node.source) + Vue.set(parentFolder, '_children', [...children.values()]) + return + } + + logger.debug('Parent path does not exists, skipping children update', { node }) + }, + onCreatedNode(node: Node) { const service = getNavigation()?.active?.id || 'files' if (!node.fileid) { @@ -60,11 +111,11 @@ export const usePathsStore = function(...args) { // Update parent folder children if exists // If the folder is the root, get it and update it if (node.dirname === '/') { - const root = files.getRoot(service) - if (!root._children) { - Vue.set(root, '_children', []) - } - root._children.push(node.source) + const root = files.getRoot(service) as Folder & { _children?: string[] } + // ensure sources are unique + const children = new Set(root._children ?? []) + children.add(node.source) + Vue.set(root, '_children', [...children.values()]) return } @@ -72,7 +123,7 @@ export const usePathsStore = function(...args) { // fetched later and its children updated anyway. if (this.paths[service][node.dirname]) { const parentSource = this.paths[service][node.dirname] - const parentFolder = files.getNode(parentSource) as Folder + const parentFolder = files.getNode(parentSource) as Folder & { _children?: string[] } logger.debug('Path already exists, updating children', { parentFolder, node }) if (!parentFolder) { @@ -80,10 +131,10 @@ export const usePathsStore = function(...args) { return } - if (!parentFolder._children) { - Vue.set(parentFolder, '_children', []) - } - parentFolder._children.push(node.source) + // ensure sources are unique + const children = new Set(parentFolder._children ?? []) + children.add(node.source) + Vue.set(parentFolder, '_children', [...children.values()]) return } @@ -97,7 +148,7 @@ export const usePathsStore = function(...args) { if (!pathsStore._initialized) { // TODO: watch folders to update paths? subscribe('files:node:created', pathsStore.onCreatedNode) - // subscribe('files:node:deleted', pathsStore.onDeletedNode) + subscribe('files:node:deleted', pathsStore.onDeletedNode) // subscribe('files:node:moved', pathsStore.onMovedNode) pathsStore._initialized = true diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 75d25e2a492..cd2053da134 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -658,7 +658,7 @@ export default defineComponent({ }, filterDirContent() { - let nodes = this.dirContents + let nodes: INode[] = this.dirContents for (const filter of this.filtersStore.sortedFilters) { nodes = filter.filter(nodes) } |