Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions __tests__/utils/filename.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later or LGPL-3.0-or-later
*/
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { getUniqueName } from '../../lib/index'

describe('isFilenameValid', () => {
beforeEach(() => {
Expand Down Expand Up @@ -42,3 +43,43 @@ describe('isFilenameValid', () => {
expect(isFilenameValid('.filepart')).toBe(false)
})
})

describe('getUniqueName', () => {
it('returns the same name if unique', () => {
const name = 'file.txt'
const others = ['other.png', 'folder']

expect(getUniqueName(name, others)).toBe(name)
})

it('adds the index as a suffix by default', () => {
const name = 'file.txt'
const others = ['file.txt', 'folder']

expect(getUniqueName(name, others)).toBe('file (1).txt')
})

it('increases the index if needed', () => {
const name = 'file.txt'
const others = ['file.txt', 'file (1).txt']

expect(getUniqueName(name, others)).toBe('file (2).txt')
})

it('uses custom suffix if provided', () => {
const name = 'file.txt'
const others = ['file.txt', 'folder']
const suffix = vi.fn((i) => `[${i}]`)

expect(getUniqueName(name, others, { suffix })).toBe('file [1].txt')
expect(suffix).toBeCalledTimes(1)
expect(suffix).toBeCalledWith(1)
})

it('can ignore the file extension', () => {
const name = 'folder.with.dot'
const others = ['folder', 'folder.with.dot', 'file.txt']

expect(getUniqueName(name, others, { ignoreFileExtension: true })).toBe('folder.with.dot (1)')
})
})
2 changes: 1 addition & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export { File } from './files/file'
export { Folder } from './files/folder'
export { Node, NodeStatus } from './files/node'

export { isFilenameValid } from './utils/filename'
export { isFilenameValid, getUniqueName } from './utils/filename'
export { formatFileSize, parseFileSize } from './utils/fileSize'
export { orderBy } from './utils/sorting'
export { sortNodes, FilesSortingMode, type FilesSortingOptions } from './utils/fileSorting'
Expand Down
42 changes: 42 additions & 0 deletions lib/utils/filename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-or-later or LGPL-3.0-or-later
*/

import { basename, extname } from 'path'

const forbiddenCharacters = window._oc_config?.forbidden_filenames_characters ?? ['/', '\\']
const forbiddenFilenameRegex = window._oc_config?.blacklist_files_regex ? new RegExp(window._oc_config.blacklist_files_regex) : null

Expand All @@ -23,3 +25,43 @@ export function isFilenameValid(filename: string): boolean {
// in Nextcloud 30 also check forbidden file extensions and file prefixes
return true
}

interface UniqueNameOptions {
/**
* A function that takes an index and returns a suffix to add to the file name, defaults to '(index)'
* @param index The current index to add
*/
suffix?: (index: number) => string
/**
* Set to true to ignore the file extension when adding the suffix (when getting a unique directory name)
*/
ignoreFileExtension?: boolean
}

/**
* Create an unique file name
* @param name The initial name to use
* @param otherNames Other names that are already used
* @param options Optional parameters for tuning the behavior
* @return {string} Either the initial name, if unique, or the name with the suffix so that the name is unique
*/
export function getUniqueName(
name: string,
otherNames: string[],
options?: UniqueNameOptions,
): string {
const opts = {
suffix: (n: number) => `(${n})`,
ignoreFileExtension: false,
...options,
}

let newName = name
let i = 1
while (otherNames.includes(newName)) {
const ext = opts.ignoreFileExtension ? '' : extname(name)
const base = basename(name, ext)
newName = `${base} ${opts.suffix(i++)}${ext}`
}
return newName
}