]> source.dussan.org Git - nextcloud-server.git/commitdiff
fix(files): do not rely on unique fileid
authorskjnldsv <skjnldsv@protonmail.com>
Fri, 10 May 2024 11:57:03 +0000 (13:57 +0200)
committerskjnldsv <skjnldsv@protonmail.com>
Thu, 13 Jun 2024 07:32:23 +0000 (09:32 +0200)
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
12 files changed:
apps/files/src/components/BreadCrumbs.vue
apps/files/src/components/FileEntry/FileEntryCheckbox.vue
apps/files/src/components/FileEntryMixin.ts
apps/files/src/components/FilesListTableHeader.vue
apps/files/src/components/FilesListTableHeaderActions.vue
apps/files/src/store/dragging.ts
apps/files/src/store/files.ts
apps/files/src/store/paths.ts
apps/files/src/store/selection.ts
apps/files/src/types.ts
apps/files/src/utils/hashUtils.ts
apps/files/src/views/FilesList.vue

index f09eed4d6e607a739891e369224e6f1c067543b6..e979405b599cde2fdf9d32b6f86fd852f5b8a9bf 100644 (file)
@@ -71,6 +71,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',
@@ -123,8 +124,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,
@@ -153,19 +155,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 {
@@ -173,8 +175,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)
                },
 
@@ -244,12 +246,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()
                        }
index 747ff8d6cc9e021eed6ce2438147e921fecde3fe..26161df628adab14da465518fbfb2aff49d31474 100644 (file)
@@ -41,6 +41,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',
@@ -83,10 +84,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
@@ -105,20 +106,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
@@ -127,8 +128,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)
index bfdd016d1655fc91b3847ff9d589e95776bfa717..03fefe80f7ba2013b66698900f229e1f4d3fc2c8 100644 (file)
@@ -21,6 +21,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'
@@ -102,13 +103,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() {
@@ -133,7 +134,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)
@@ -145,7 +146,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
                        }
 
@@ -286,14 +287,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)
@@ -347,12 +348,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()
                        }
index c45090ca37dcf6ad17755d1c91ee44a0c8991f5c..caa549fa9ba7eee098a009939ce8620964fdf1a5 100644 (file)
@@ -81,6 +81,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',
@@ -186,7 +187,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)
index ff9c0ee9bc5c058ae98f57d12097d0df0e177c31..c10b56731304461db2b986f32ba74380cf818892 100644 (file)
@@ -56,7 +56,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()
@@ -81,7 +81,7 @@ export default defineComponent({
                        required: true,
                },
                selectedNodes: {
-                       type: Array as PropType<FileId[]>,
+                       type: Array as PropType<FileSource[]>,
                        default: () => ([]),
                },
        },
@@ -117,7 +117,7 @@ export default defineComponent({
 
                nodes() {
                        return this.selectedNodes
-                               .map(fileid => this.getNode(fileid))
+                               .map(source => this.getNode(source))
                                .filter(Boolean) as Node[]
                },
 
@@ -161,7 +161,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
@@ -182,9 +182,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
index e189d55204645943d0e92659078719954d4183f5..8ec9d6d1fdb002902edaf364e212ff43c680dc59 100644 (file)
@@ -21,7 +21,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: () => ({
@@ -32,7 +32,7 @@ export const useDragAndDropStore = defineStore('dragging', {
                /**
                 * Set the selection of fileIds
                 */
-               set(selection = [] as FileId[]) {
+               set(selection = [] as FileSource[]) {
                        Vue.set(this, 'dragging', selection)
                },
 
index 56ae01192ef21469b443edf23a86e65b1af41475..0f3b266db8e37c19178490f7eaedb8b161f7d3ad 100644 (file)
@@ -20,7 +20,7 @@
  *
  */
 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'
@@ -36,19 +36,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],
                },
@@ -58,10 +59,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)
 
@@ -70,8 +72,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)
                                        }
                                })
                        },
index 15d16b1760b520bdf77b3cd8778fceb6db6d6c98..816014feb56e1733c0255cf88ad5f9770240d36a 100644 (file)
@@ -19,7 +19,7 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  *
  */
-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'
@@ -38,7 +38,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
                                        }
@@ -55,7 +55,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) {
@@ -70,7 +70,7 @@ export const usePathsStore = function(...args) {
                                        this.addPath({
                                                service,
                                                path: node.path,
-                                               fileid: node.fileid,
+                                               source: node.source,
                                        })
                                }
 
@@ -81,26 +81,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
                                }
 
index 348ace25ae453f6d778048da43cdf13d49bdccfa..525c77d496b192ae0a725ace4d749cabd20eec1f 100644 (file)
@@ -19,7 +19,7 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  *
  */
-import type { FileId, SelectionStore } from '../types'
+import type { FileSource, SelectionStore } from '../types'
 import { defineStore } from 'pinia'
 import Vue from 'vue'
 
@@ -34,14 +34,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)
index aa9164c873f1054d855e4ad00724bf3eab03936f..7a1aedfdf9d1515533f0f97278f60e0d9f924941 100644 (file)
@@ -24,12 +24,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 = {
@@ -48,7 +48,7 @@ export interface RootOptions {
 
 // Paths store
 export type PathConfig = {
-       [path: string]: number
+       [path: string]: FileSource
 }
 
 export type ServicesState = {
@@ -62,7 +62,7 @@ export type PathsStore = {
 export interface PathOptions {
        service: Service
        path: string
-       fileid: FileId
+       source: FileSource
 }
 
 // User config store
@@ -74,8 +74,8 @@ export interface UserConfigStore {
 }
 
 export interface SelectionStore {
-       selected: FileId[]
-       lastSelection: FileId[]
+       selected: FileSource[]
+       lastSelection: FileSource[]
        lastSelectedIndex: number | null
 }
 
@@ -109,7 +109,7 @@ export interface UploaderStore {
 
 // Drag and drop store
 export interface DragAndDropStore {
-       dragging: FileId[]
+       dragging: FileSource[]
 }
 
 export interface TemplateFile {
index 55cf8b9f51af2507cf4aad7ebddf84579b1e53d2..305a603b952c1da2a85a71c73870ce30db72d608 100644 (file)
@@ -21,8 +21,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)
 }
index 9c73947679324445b4f07d13b191824152a65b10..a9c7dacc1ae0e97856b7201522141e4c6ec35bc6 100644 (file)
@@ -259,8 +259,13 @@ export default defineComponent({
                        if (this.dir === '/') {
                                return this.filesStore.getRoot(this.currentView.id)
                        }
-                       const fileId = this.pathsStore.getPath(this.currentView.id, this.dir)
-                       return this.filesStore.getNode(fileId)
+
+                       const source = this.pathsStore.getPath(this.currentView.id, this.dir)
+                       if (source === undefined) {
+                               return
+                       }
+
+                       return this.filesStore.getNode(source) as Folder
                },
 
                /**
@@ -507,7 +512,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 === '/') {
@@ -516,7 +521,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.error('Invalid root folder returned', { dir, folder, currentView })