aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorskjnldsv <skjnldsv@protonmail.com>2024-05-10 13:57:03 +0200
committerskjnldsv <skjnldsv@protonmail.com>2024-06-12 17:15:51 +0200
commit1d7893dca899fbcec44c82f049a9c10a085f7153 (patch)
treeb9d51ab0fd2db7b15b7e69d19acdac77fc6cc6e1
parent6ec6c1fe4fbebd5c811c45600d79e1835da4b1c2 (diff)
downloadnextcloud-server-1d7893dca899fbcec44c82f049a9c10a085f7153.tar.gz
nextcloud-server-1d7893dca899fbcec44c82f049a9c10a085f7153.zip
fix(files): do not rely on unique fileid
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
-rw-r--r--apps/files/src/components/BreadCrumbs.vue24
-rw-r--r--apps/files/src/components/FileEntry/FileEntryCheckbox.vue17
-rw-r--r--apps/files/src/components/FileEntryMixin.ts21
-rw-r--r--apps/files/src/components/FilesListTableHeader.vue3
-rw-r--r--apps/files/src/components/FilesListTableHeaderActions.vue14
-rw-r--r--apps/files/src/store/dragging.ts4
-rw-r--r--apps/files/src/store/files.ts24
-rw-r--r--apps/files/src/store/paths.ts18
-rw-r--r--apps/files/src/store/selection.ts6
-rw-r--r--apps/files/src/types.ts14
-rw-r--r--apps/files/src/utils/hashUtils.ts9
-rw-r--r--apps/files/src/views/FilesList.vue14
12 files changed, 88 insertions, 80 deletions
diff --git a/apps/files/src/components/BreadCrumbs.vue b/apps/files/src/components/BreadCrumbs.vue
index c0048324d91..88f592a4cdf 100644
--- a/apps/files/src/components/BreadCrumbs.vue
+++ b/apps/files/src/components/BreadCrumbs.vue
@@ -54,6 +54,7 @@ import { useSelectionStore } from '../store/selection.ts'
import { useUploaderStore } from '../store/uploader.ts'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
import logger from '../logger'
+import type { FileSource } from '../types.ts'
export default defineComponent({
name: 'BreadCrumbs',
@@ -106,8 +107,9 @@ export default defineComponent({
sections() {
return this.dirs.map((dir: string, index: number) => {
- const fileid = this.getFileIdFromPath(dir)
- const to = { ...this.$route, params: { fileid }, query: { dir } }
+ const source = this.getFileSourceFromPath(dir)
+ const node: Node | undefined = source ? this.getNodeFromSource(source) : undefined
+ const to = { ...this.$route, params: { node: node?.fileid }, query: { dir } }
return {
dir,
exact: true,
@@ -136,19 +138,19 @@ export default defineComponent({
},
selectedFiles() {
- return this.selectionStore.selected
+ return this.selectionStore.selected as FileSource[]
},
draggingFiles() {
- return this.draggingStore.dragging
+ return this.draggingStore.dragging as FileSource[]
},
},
methods: {
- getNodeFromId(id: number): Node | undefined {
- return this.filesStore.getNode(id)
+ getNodeFromSource(source: FileSource): Node | undefined {
+ return this.filesStore.getNode(source)
},
- getFileIdFromPath(path: string): number | undefined {
+ getFileSourceFromPath(path: string): FileSource | undefined {
return this.pathsStore.getPath(this.currentView?.id, path)
},
getDirDisplayName(path: string): string {
@@ -156,8 +158,8 @@ export default defineComponent({
return this.$navigation?.active?.name || t('files', 'Home')
}
- const fileId: number | undefined = this.getFileIdFromPath(path)
- const node: Node | undefined = (fileId) ? this.getNodeFromId(fileId) : undefined
+ const source: FileSource | undefined = this.getFileSourceFromPath(path)
+ const node: Node | undefined = source ? this.getNodeFromSource(source) : undefined
return node?.attributes?.displayName || basename(path)
},
@@ -227,12 +229,12 @@ export default defineComponent({
}
// Else we're moving/copying files
- const nodes = selection.map(fileid => this.filesStore.getNode(fileid)) as Node[]
+ const nodes = selection.map(source => this.filesStore.getNode(source)) as Node[]
await onDropInternalFiles(nodes, folder, contents.contents, isCopy)
// Reset selection after we dropped the files
// if the dropped files are within the selection
- if (selection.some(fileid => this.selectedFiles.includes(fileid))) {
+ if (selection.some(source => this.selectedFiles.includes(source))) {
logger.debug('Dropped selection, resetting select store...')
this.selectionStore.reset()
}
diff --git a/apps/files/src/components/FileEntry/FileEntryCheckbox.vue b/apps/files/src/components/FileEntry/FileEntryCheckbox.vue
index edb94e65f27..987b48ef8ae 100644
--- a/apps/files/src/components/FileEntry/FileEntryCheckbox.vue
+++ b/apps/files/src/components/FileEntry/FileEntryCheckbox.vue
@@ -24,6 +24,7 @@ import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import { useKeyboardStore } from '../../store/keyboard.ts'
import { useSelectionStore } from '../../store/selection.ts'
import logger from '../../logger.js'
+import type { FileSource } from '../../types.ts'
export default defineComponent({
name: 'FileEntryCheckbox',
@@ -66,10 +67,10 @@ export default defineComponent({
return this.selectionStore.selected
},
isSelected() {
- return this.selectedFiles.includes(this.fileid)
+ return this.selectedFiles.includes(this.source.source)
},
index() {
- return this.nodes.findIndex((node: Node) => node.fileid === this.fileid)
+ return this.nodes.findIndex((node: Node) => node.source === this.source.source)
},
isFile() {
return this.source.type === FileType.File
@@ -88,20 +89,20 @@ export default defineComponent({
// Get the last selected and select all files in between
if (this.keyboardStore?.shiftKey && lastSelectedIndex !== null) {
- const isAlreadySelected = this.selectedFiles.includes(this.fileid)
+ const isAlreadySelected = this.selectedFiles.includes(this.source.source)
const start = Math.min(newSelectedIndex, lastSelectedIndex)
const end = Math.max(lastSelectedIndex, newSelectedIndex)
const lastSelection = this.selectionStore.lastSelection
const filesToSelect = this.nodes
- .map(file => file.fileid)
+ .map(file => file.source)
.slice(start, end + 1)
- .filter(Boolean) as number[]
+ .filter(Boolean) as FileSource[]
// If already selected, update the new selection _without_ the current file
const selection = [...lastSelection, ...filesToSelect]
- .filter(fileid => !isAlreadySelected || fileid !== this.fileid)
+ .filter(source => !isAlreadySelected || source !== this.source.source)
logger.debug('Shift key pressed, selecting all files in between', { start, end, filesToSelect, isAlreadySelected })
// Keep previous lastSelectedIndex to be use for further shift selections
@@ -110,8 +111,8 @@ export default defineComponent({
}
const selection = selected
- ? [...this.selectedFiles, this.fileid]
- : this.selectedFiles.filter(fileid => fileid !== this.fileid)
+ ? [...this.selectedFiles, this.source.source]
+ : this.selectedFiles.filter(source => source !== this.source.source)
logger.debug('Updating selection', { selection })
this.selectionStore.set(selection)
diff --git a/apps/files/src/components/FileEntryMixin.ts b/apps/files/src/components/FileEntryMixin.ts
index 4f6c0899910..c56c10de75a 100644
--- a/apps/files/src/components/FileEntryMixin.ts
+++ b/apps/files/src/components/FileEntryMixin.ts
@@ -4,6 +4,7 @@
*/
import type { ComponentPublicInstance, PropType } from 'vue'
+import type { FileSource } from '../types.ts'
import { showError } from '@nextcloud/dialogs'
import { FileType, Permission, Folder, File as NcFile, NodeStatus, Node, View } from '@nextcloud/files'
@@ -85,13 +86,13 @@ export default defineComponent({
},
draggingFiles() {
- return this.draggingStore.dragging
+ return this.draggingStore.dragging as FileSource[]
},
selectedFiles() {
- return this.selectionStore.selected
+ return this.selectionStore.selected as FileSource[]
},
isSelected() {
- return this.fileid && this.selectedFiles.includes(this.fileid)
+ return this.selectedFiles.includes(this.source.source)
},
isRenaming() {
@@ -116,7 +117,7 @@ export default defineComponent({
// If we're dragging a selection, we need to check all files
if (this.selectedFiles.length > 0) {
- const nodes = this.selectedFiles.map(fileid => this.filesStore.getNode(fileid)) as Node[]
+ const nodes = this.selectedFiles.map(source => this.filesStore.getNode(source)) as Node[]
return nodes.every(canDrag)
}
return canDrag(this.source)
@@ -128,7 +129,7 @@ export default defineComponent({
}
// If the current folder is also being dragged, we can't drop it on itself
- if (this.fileid && this.draggingFiles.includes(this.fileid)) {
+ if (this.draggingFiles.includes(this.source.source)) {
return false
}
@@ -269,14 +270,14 @@ export default defineComponent({
// Dragging set of files, if we're dragging a file
// that is already selected, we use the entire selection
- if (this.selectedFiles.includes(this.fileid)) {
+ if (this.selectedFiles.includes(this.source.source)) {
this.draggingStore.set(this.selectedFiles)
} else {
- this.draggingStore.set([this.fileid])
+ this.draggingStore.set([this.source.source])
}
const nodes = this.draggingStore.dragging
- .map(fileid => this.filesStore.getNode(fileid)) as Node[]
+ .map(source => this.filesStore.getNode(source)) as Node[]
const image = await getDragAndDropPreview(nodes)
event.dataTransfer?.setDragImage(image, -10, -10)
@@ -330,12 +331,12 @@ export default defineComponent({
}
// Else we're moving/copying files
- const nodes = selection.map(fileid => this.filesStore.getNode(fileid)) as Node[]
+ const nodes = selection.map(source => this.filesStore.getNode(source)) as Node[]
await onDropInternalFiles(nodes, folder, contents.contents, isCopy)
// Reset selection after we dropped the files
// if the dropped files are within the selection
- if (selection.some(fileid => this.selectedFiles.includes(fileid))) {
+ if (selection.some(source => this.selectedFiles.includes(source))) {
logger.debug('Dropped selection, resetting select store...')
this.selectionStore.reset()
}
diff --git a/apps/files/src/components/FilesListTableHeader.vue b/apps/files/src/components/FilesListTableHeader.vue
index 5172202662a..7134c957fb8 100644
--- a/apps/files/src/components/FilesListTableHeader.vue
+++ b/apps/files/src/components/FilesListTableHeader.vue
@@ -64,6 +64,7 @@ import FilesListTableHeaderButton from './FilesListTableHeaderButton.vue'
import filesSortingMixin from '../mixins/filesSorting.ts'
import logger from '../logger.js'
import type { Node } from '@nextcloud/files'
+import type { FileSource } from '../types.ts'
export default defineComponent({
name: 'FilesListTableHeader',
@@ -169,7 +170,7 @@ export default defineComponent({
onToggleAll(selected) {
if (selected) {
- const selection = this.nodes.map(node => node.fileid).filter(Boolean) as number[]
+ const selection = this.nodes.map(node => node.source).filter(Boolean) as FileSource[]
logger.debug('Added all nodes to selection', { selection })
this.selectionStore.setLastIndex(null)
this.selectionStore.set(selection)
diff --git a/apps/files/src/components/FilesListTableHeaderActions.vue b/apps/files/src/components/FilesListTableHeaderActions.vue
index 4dd868adf24..c73cd05d016 100644
--- a/apps/files/src/components/FilesListTableHeaderActions.vue
+++ b/apps/files/src/components/FilesListTableHeaderActions.vue
@@ -39,7 +39,7 @@ import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'
import filesListWidthMixin from '../mixins/filesListWidth.ts'
import logger from '../logger.js'
-import type { FileId } from '../types'
+import type { FileSource } from '../types'
// The registered actions list
const actions = getFileActions()
@@ -64,7 +64,7 @@ export default defineComponent({
required: true,
},
selectedNodes: {
- type: Array as PropType<FileId[]>,
+ type: Array as PropType<FileSource[]>,
default: () => ([]),
},
},
@@ -100,7 +100,7 @@ export default defineComponent({
nodes() {
return this.selectedNodes
- .map(fileid => this.getNode(fileid))
+ .map(source => this.getNode(source))
.filter(Boolean) as Node[]
},
@@ -144,7 +144,7 @@ export default defineComponent({
async onActionClick(action) {
const displayName = action.displayName(this.nodes, this.currentView)
- const selectionIds = this.selectedNodes
+ const selectionSources = this.selectedNodes
try {
// Set loading markers
this.loading = action.id
@@ -165,9 +165,9 @@ export default defineComponent({
// Handle potential failures
if (results.some(result => result === false)) {
// Remove the failed ids from the selection
- const failedIds = selectionIds
- .filter((fileid, index) => results[index] === false)
- this.selectionStore.set(failedIds)
+ const failedSources = selectionSources
+ .filter((source, index) => results[index] === false)
+ this.selectionStore.set(failedSources)
if (results.some(result => result === null)) {
// If some actions returned null, we assume that the dev
diff --git a/apps/files/src/store/dragging.ts b/apps/files/src/store/dragging.ts
index 197e1841558..74de1c4af16 100644
--- a/apps/files/src/store/dragging.ts
+++ b/apps/files/src/store/dragging.ts
@@ -4,7 +4,7 @@
*/
import { defineStore } from 'pinia'
import Vue from 'vue'
-import type { FileId, DragAndDropStore } from '../types'
+import type { DragAndDropStore, FileSource } from '../types'
export const useDragAndDropStore = defineStore('dragging', {
state: () => ({
@@ -15,7 +15,7 @@ export const useDragAndDropStore = defineStore('dragging', {
/**
* Set the selection of fileIds
*/
- set(selection = [] as FileId[]) {
+ set(selection = [] as FileSource[]) {
Vue.set(this, 'dragging', selection)
},
diff --git a/apps/files/src/store/files.ts b/apps/files/src/store/files.ts
index 36e7ad37cfd..bf09ec7f88a 100644
--- a/apps/files/src/store/files.ts
+++ b/apps/files/src/store/files.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Folder, Node } from '@nextcloud/files'
-import type { FilesStore, RootsStore, RootOptions, Service, FilesState, FileId } from '../types'
+import type { FilesStore, RootsStore, RootOptions, Service, FilesState, FileSource } from '../types'
import { defineStore } from 'pinia'
import { subscribe } from '@nextcloud/event-bus'
@@ -19,19 +19,20 @@ export const useFilesStore = function(...args) {
getters: {
/**
- * Get a file or folder by id
+ * Get a file or folder by its source
*/
- getNode: (state) => (id: FileId): Node|undefined => state.files[id],
+ getNode: (state) => (source: FileSource): Node|undefined => state.files[source],
/**
* Get a list of files or folders by their IDs
- * Does not return undefined values
+ * Note: does not return undefined values
*/
- getNodes: (state) => (ids: FileId[]): Node[] => ids
- .map(id => state.files[id])
+ getNodes: (state) => (sources: FileSource[]): Node[] => sources
+ .map(source => state.files[source])
.filter(Boolean),
+
/**
- * Get a file or folder by id
+ * Get the root folder of a service
*/
getRoot: (state) => (service: Service): Folder|undefined => state.roots[service],
},
@@ -41,10 +42,11 @@ export const useFilesStore = function(...args) {
// Update the store all at once
const files = nodes.reduce((acc, node) => {
if (!node.fileid) {
- logger.error('Trying to update/set a node without fileid', node)
+ logger.error('Trying to update/set a node without fileid', { node })
return acc
}
- acc[node.fileid] = node
+
+ acc[node.source] = node
return acc
}, {} as FilesStore)
@@ -53,8 +55,8 @@ export const useFilesStore = function(...args) {
deleteNodes(nodes: Node[]) {
nodes.forEach(node => {
- if (node.fileid) {
- Vue.delete(this.files, node.fileid)
+ if (node.source) {
+ Vue.delete(this.files, node.source)
}
})
},
diff --git a/apps/files/src/store/paths.ts b/apps/files/src/store/paths.ts
index 23fd69a91e6..2993cc9d704 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 { FileId, PathsStore, PathOptions, ServicesState } from '../types'
+import type { FileSource, PathsStore, PathOptions, ServicesState } from '../types'
import { defineStore } from 'pinia'
import { FileType, Folder, Node, getNavigation } from '@nextcloud/files'
import { subscribe } from '@nextcloud/event-bus'
@@ -21,7 +21,7 @@ export const usePathsStore = function(...args) {
getters: {
getPath: (state) => {
- return (service: string, path: string): FileId|undefined => {
+ return (service: string, path: string): FileSource|undefined => {
if (!state.paths[service]) {
return undefined
}
@@ -38,7 +38,7 @@ export const usePathsStore = function(...args) {
}
// Now we can set the provided path
- Vue.set(this.paths[payload.service], payload.path, payload.fileid)
+ Vue.set(this.paths[payload.service], payload.path, payload.source)
},
onCreatedNode(node: Node) {
@@ -53,7 +53,7 @@ export const usePathsStore = function(...args) {
this.addPath({
service,
path: node.path,
- fileid: node.fileid,
+ source: node.source,
})
}
@@ -64,26 +64,26 @@ export const usePathsStore = function(...args) {
if (!root._children) {
Vue.set(root, '_children', [])
}
- root._children.push(node.fileid)
+ root._children.push(node.source)
return
}
// If the folder doesn't exists yet, it will be
// fetched later and its children updated anyway.
if (this.paths[service][node.dirname]) {
- const parentId = this.paths[service][node.dirname]
- const parentFolder = files.getNode(parentId) as Folder
+ const parentSource = this.paths[service][node.dirname]
+ const parentFolder = files.getNode(parentSource) as Folder
logger.debug('Path already exists, updating children', { parentFolder, node })
if (!parentFolder) {
- logger.error('Parent folder not found', { parentId })
+ logger.error('Parent folder not found', { parentSource })
return
}
if (!parentFolder._children) {
Vue.set(parentFolder, '_children', [])
}
- parentFolder._children.push(node.fileid)
+ parentFolder._children.push(node.source)
return
}
diff --git a/apps/files/src/store/selection.ts b/apps/files/src/store/selection.ts
index 12a265bc494..c8c5c6d7de3 100644
--- a/apps/files/src/store/selection.ts
+++ b/apps/files/src/store/selection.ts
@@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import type { FileId, SelectionStore } from '../types'
+import type { FileSource, SelectionStore } from '../types'
import { defineStore } from 'pinia'
import Vue from 'vue'
@@ -17,14 +17,14 @@ export const useSelectionStore = defineStore('selection', {
/**
* Set the selection of fileIds
*/
- set(selection = [] as FileId[]) {
+ set(selection = [] as FileSource[]) {
Vue.set(this, 'selected', [...new Set(selection)])
},
/**
* Set the last selected index
*/
- setLastIndex(lastSelectedIndex = null as FileId | null) {
+ setLastIndex(lastSelectedIndex = null as number | null) {
// Update the last selection if we provided a new selection starting point
Vue.set(this, 'lastSelection', lastSelectedIndex ? this.selected : [])
Vue.set(this, 'lastSelectedIndex', lastSelectedIndex)
diff --git a/apps/files/src/types.ts b/apps/files/src/types.ts
index 3213d2c543b..9e1ba049697 100644
--- a/apps/files/src/types.ts
+++ b/apps/files/src/types.ts
@@ -7,12 +7,12 @@ import type { Upload } from '@nextcloud/upload'
// Global definitions
export type Service = string
-export type FileId = number
+export type FileSource = string
export type ViewId = string
// Files store
export type FilesStore = {
- [fileid: FileId]: Node
+ [source: FileSource]: Node
}
export type RootsStore = {
@@ -31,7 +31,7 @@ export interface RootOptions {
// Paths store
export type PathConfig = {
- [path: string]: number
+ [path: string]: FileSource
}
export type ServicesState = {
@@ -45,7 +45,7 @@ export type PathsStore = {
export interface PathOptions {
service: Service
path: string
- fileid: FileId
+ source: FileSource
}
// User config store
@@ -57,8 +57,8 @@ export interface UserConfigStore {
}
export interface SelectionStore {
- selected: FileId[]
- lastSelection: FileId[]
+ selected: FileSource[]
+ lastSelection: FileSource[]
lastSelectedIndex: number | null
}
@@ -92,7 +92,7 @@ export interface UploaderStore {
// Drag and drop store
export interface DragAndDropStore {
- dragging: FileId[]
+ dragging: FileSource[]
}
export interface TemplateFile {
diff --git a/apps/files/src/utils/hashUtils.ts b/apps/files/src/utils/hashUtils.ts
index 6a7b55e5afe..607064947a8 100644
--- a/apps/files/src/utils/hashUtils.ts
+++ b/apps/files/src/utils/hashUtils.ts
@@ -4,8 +4,9 @@
*/
export const hashCode = function(str: string): number {
- return str.split('').reduce(function(a, b) {
- a = ((a << 5) - a) + b.charCodeAt(0)
- return a & a
- }, 0)
+ let hash = 0
+ for (let i = 0; i < str.length; i++) {
+ hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0
+ }
+ return (hash >>> 0)
}
diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue
index 24797d89c32..ae20c58ea32 100644
--- a/apps/files/src/views/FilesList.vue
+++ b/apps/files/src/views/FilesList.vue
@@ -263,12 +263,13 @@ export default defineComponent({
if (this.dir === '/') {
return this.filesStore.getRoot(this.currentView.id)
}
- const fileId = this.pathsStore.getPath(this.currentView.id, this.dir)
- if (fileId === undefined) {
+
+ const source = this.pathsStore.getPath(this.currentView.id, this.dir)
+ if (source === undefined) {
return
}
- return this.filesStore.getNode(fileId) as Folder
+ return this.filesStore.getNode(source) as Folder
},
/**
@@ -518,7 +519,7 @@ export default defineComponent({
// Define current directory children
// TODO: make it more official
- this.$set(folder, '_children', contents.map(node => node.fileid))
+ this.$set(folder, '_children', contents.map(node => node.source))
// If we're in the root dir, define the root
if (dir === '/') {
@@ -527,7 +528,7 @@ export default defineComponent({
// Otherwise, add the folder to the store
if (folder.fileid) {
this.filesStore.updateNodes([folder])
- this.pathsStore.addPath({ service: currentView.id, fileid: folder.fileid, path: dir })
+ this.pathsStore.addPath({ service: currentView.id, source: folder.source, path: dir })
} else {
// If we're here, the view API messed up
logger.fatal('Invalid root folder returned', { dir, folder, currentView })
@@ -537,8 +538,7 @@ export default defineComponent({
// Update paths store
const folders = contents.filter(node => node.type === 'folder')
folders.forEach((node) => {
- // Folders from API always have the fileID set
- this.pathsStore.addPath({ service: currentView.id, fileid: node.fileid!, path: join(dir, node.basename) })
+ this.pathsStore.addPath({ service: currentView.id, source: node.source, path: join(dir, node.basename) })
})
} catch (error) {
logger.error('Error while fetching content', { error })