Skip to content

Commit 37ea4b0

Browse files
committed
fix(files): properly update paths and folder children on node move
Signed-off-by: skjnldsv <[email protected]>
1 parent d334773 commit 37ea4b0

File tree

3 files changed

+112
-57
lines changed

3 files changed

+112
-57
lines changed

apps/files/src/store/files.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,17 @@ export const useFilesStore = function(...args) {
115115
this.updateNodes([node])
116116
},
117117

118+
onMovedNode({ node, oldSource }: { node: Node, oldSource: string }) {
119+
if (!node.fileid) {
120+
logger.error('Trying to update/set a node without fileid', { node })
121+
return
122+
}
123+
124+
// Update the path of the node
125+
Vue.delete(this.files, oldSource)
126+
this.updateNodes([node])
127+
},
128+
118129
async onUpdatedNode(node: Node) {
119130
if (!node.fileid) {
120131
logger.error('Trying to update/set a node without fileid', { node })
@@ -147,6 +158,7 @@ export const useFilesStore = function(...args) {
147158
subscribe('files:node:created', fileStore.onCreatedNode)
148159
subscribe('files:node:deleted', fileStore.onDeletedNode)
149160
subscribe('files:node:updated', fileStore.onUpdatedNode)
161+
subscribe('files:node:moved', fileStore.onMovedNode)
150162

151163
fileStore._initialized = true
152164
}

apps/files/src/store/paths.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,40 @@ describe('Path store', () => {
127127
// See the child is removed
128128
expect(root._children).toEqual([])
129129
})
130+
131+
test('Folder is moved', () => {
132+
const node = new Folder({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/folder', id: 2 })
133+
emit('files:node:created', node)
134+
// see that the path is added and the children are set-up
135+
expect(store.paths).toEqual({ files: { [node.path]: node.source } })
136+
expect(root._children).toEqual([node.source])
137+
138+
const renamedNode = node.clone()
139+
renamedNode.rename('new-folder')
140+
141+
expect(renamedNode.path).toBe('/new-folder')
142+
expect(renamedNode.source).toBe('http://example.com/remote.php/dav/files/test/new-folder')
143+
144+
emit('files:node:moved', { node: renamedNode, oldSource: node.source })
145+
// See the path is updated
146+
expect(store.paths).toEqual({ files: { [renamedNode.path]: renamedNode.source } })
147+
// See the child is updated
148+
expect(root._children).toEqual([renamedNode.source])
149+
})
150+
151+
test('File is moved', () => {
152+
const node = new File({ owner: 'test', source: 'http://example.com/remote.php/dav/files/test/file.txt', id: 2, mime: 'text/plain' })
153+
emit('files:node:created', node)
154+
// see that the children are set-up
155+
expect(root._children).toEqual([node.source])
156+
expect(store.paths).toEqual({})
157+
158+
const renamedNode = node.clone()
159+
renamedNode.rename('new-file.txt')
160+
161+
emit('files:node:moved', { node: renamedNode, oldSource: node.source })
162+
// See the child is updated
163+
expect(root._children).toEqual([renamedNode.source])
164+
expect(store.paths).toEqual({})
165+
})
130166
})

apps/files/src/store/paths.ts

Lines changed: 64 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
*/
55
import type { FileSource, PathsStore, PathOptions, ServicesState, Service } from '../types'
66
import { defineStore } from 'pinia'
7-
import { FileType, Folder, Node, getNavigation } from '@nextcloud/files'
7+
import { dirname } from '@nextcloud/paths'
8+
import { File, FileType, Folder, Node, getNavigation } from '@nextcloud/files'
89
import { subscribe } from '@nextcloud/event-bus'
910
import Vue from 'vue'
1011
import logger from '../logger'
@@ -50,6 +51,27 @@ export const usePathsStore = function(...args) {
5051
Vue.delete(this.paths[service], path)
5152
},
5253

54+
onCreatedNode(node: Node) {
55+
const service = getNavigation()?.active?.id || 'files'
56+
if (!node.fileid) {
57+
logger.error('Node has no fileid', { node })
58+
return
59+
}
60+
61+
// Only add path if it's a folder
62+
if (node.type === FileType.Folder) {
63+
this.addPath({
64+
service,
65+
path: node.path,
66+
source: node.source,
67+
})
68+
}
69+
70+
// Update parent folder children if exists
71+
// If the folder is the root, get it and update it
72+
this.addNodeToParentChildren(node)
73+
},
74+
5375
onDeletedNode(node: Node) {
5476
const service = getNavigation()?.active?.id || 'files'
5577

@@ -61,95 +83,80 @@ export const usePathsStore = function(...args) {
6183
)
6284
}
6385

64-
// Remove node from children
65-
if (node.dirname === '/') {
66-
const root = files.getRoot(service) as Folder & { _children?: string[] }
67-
// ensure sources are unique
68-
const children = new Set(root._children ?? [])
69-
children.delete(node.source)
70-
Vue.set(root, '_children', [...children.values()])
71-
return
72-
}
73-
74-
if (this.paths[service][node.dirname]) {
75-
const parentSource = this.paths[service][node.dirname]
76-
const parentFolder = files.getNode(parentSource) as Folder & { _children?: string[] }
77-
78-
if (!parentFolder) {
79-
logger.error('Parent folder not found', { parentSource })
80-
return
81-
}
82-
83-
logger.debug('Path exists, removing from children', { parentFolder, node })
84-
85-
// ensure sources are unique
86-
const children = new Set(parentFolder._children ?? [])
87-
children.delete(node.source)
88-
Vue.set(parentFolder, '_children', [...children.values()])
89-
return
90-
}
91-
92-
logger.debug('Parent path does not exists, skipping children update', { node })
86+
this.deleteNodeFromParentChildren(node)
9387
},
9488

95-
onCreatedNode(node: Node) {
89+
onMovedNode({ node, oldSource }: { node: Node, oldSource: string }) {
9690
const service = getNavigation()?.active?.id || 'files'
97-
if (!node.fileid) {
98-
logger.error('Node has no fileid', { node })
99-
return
100-
}
10191

102-
// Only add path if it's a folder
92+
// Update the path of the node
10393
if (node.type === FileType.Folder) {
94+
// Delete the old path if it exists
95+
const oldPath = Object.entries(this.paths[service]).find(([, source]) => source === oldSource)
96+
if (oldPath?.[0]) {
97+
this.deletePath(service, oldPath[0])
98+
}
99+
100+
// Add the new path
104101
this.addPath({
105102
service,
106103
path: node.path,
107104
source: node.source,
108105
})
109106
}
110107

111-
// Update parent folder children if exists
112-
// If the folder is the root, get it and update it
113-
if (node.dirname === '/') {
114-
const root = files.getRoot(service) as Folder & { _children?: string[] }
108+
// Dummy simple clone of the renamed node from a previous state
109+
const oldNode = new File({ source: oldSource, owner: node.owner, mime: node.mime })
110+
111+
this.deleteNodeFromParentChildren(oldNode)
112+
this.addNodeToParentChildren(node)
113+
},
114+
115+
deleteNodeFromParentChildren(node: Node) {
116+
const service = getNavigation()?.active?.id || 'files'
117+
118+
// Update children of a root folder
119+
const parentSource = dirname(node.source)
120+
const folder = (node.dirname === '/' ? files.getRoot(service) : files.getNode(parentSource)) as Folder & { _children?: string[] }
121+
if (folder) {
115122
// ensure sources are unique
116-
const children = new Set(root._children ?? [])
117-
children.add(node.source)
118-
Vue.set(root, '_children', [...children.values()])
123+
const children = new Set(folder._children ?? [])
124+
children.delete(node.source)
125+
Vue.set(folder, '_children', [...children.values()])
126+
logger.debug('Children updated', { parent: folder, node, children: folder._children })
119127
return
120128
}
121129

122-
// If the folder doesn't exists yet, it will be
123-
// fetched later and its children updated anyway.
124-
if (this.paths[service][node.dirname]) {
125-
const parentSource = this.paths[service][node.dirname]
126-
const parentFolder = files.getNode(parentSource) as Folder & { _children?: string[] }
127-
logger.debug('Path already exists, updating children', { parentFolder, node })
130+
logger.debug('Parent path does not exists, skipping children update', { node })
131+
},
128132

129-
if (!parentFolder) {
130-
logger.error('Parent folder not found', { parentSource })
131-
return
132-
}
133+
addNodeToParentChildren(node: Node) {
134+
const service = getNavigation()?.active?.id || 'files'
133135

136+
// Update children of a root folder
137+
const parentSource = dirname(node.source)
138+
const folder = (node.dirname === '/' ? files.getRoot(service) : files.getNode(parentSource)) as Folder & { _children?: string[] }
139+
if (folder) {
134140
// ensure sources are unique
135-
const children = new Set(parentFolder._children ?? [])
141+
const children = new Set(folder._children ?? [])
136142
children.add(node.source)
137-
Vue.set(parentFolder, '_children', [...children.values()])
143+
Vue.set(folder, '_children', [...children.values()])
144+
logger.debug('Children updated', { parent: folder, node, children: folder._children })
138145
return
139146
}
140147

141148
logger.debug('Parent path does not exists, skipping children update', { node })
142149
},
150+
143151
},
144152
})
145153

146154
const pathsStore = store(...args)
147155
// Make sure we only register the listeners once
148156
if (!pathsStore._initialized) {
149-
// TODO: watch folders to update paths?
150157
subscribe('files:node:created', pathsStore.onCreatedNode)
151158
subscribe('files:node:deleted', pathsStore.onDeletedNode)
152-
// subscribe('files:node:moved', pathsStore.onMovedNode)
159+
subscribe('files:node:moved', pathsStore.onMovedNode)
153160

154161
pathsStore._initialized = true
155162
}

0 commit comments

Comments
 (0)