Skip to content

Commit

Permalink
Merge pull request #1019 from nextcloud-libraries/feat/add-displaynam…
Browse files Browse the repository at this point in the history
…e-as-top-level
  • Loading branch information
skjnldsv authored Jul 18, 2024
2 parents d4e9c31 + b23901e commit da5812f
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 25 deletions.
3 changes: 2 additions & 1 deletion __mocks__/@nextcloud/router.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
/*!
* SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

export const generateRemoteUrl = (path) => `https://localhost/${path}`
8 changes: 8 additions & 0 deletions __tests__/dav/dav.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ describe('davResultToNode', () => {
test('path does not contain root', () => {
const node = davResultToNode(result)
expect(node.basename).toBe(result.basename)
expect(node.displayname).toBe(result.props!.displayname)
expect(node.extension).toBe('.md')
expect(node.source).toBe('https://localhost/dav/files/test/New folder/Neue Textdatei.md')
expect(node.root).toBe(davRootPath)
Expand Down Expand Up @@ -85,6 +86,13 @@ describe('davResultToNode', () => {
expect(node.dirname).toBe('/New folder')
})

test('has correct displayname set', () => {
const remoteResult = { ...result, filename: '/root/New folder/Neue Textdatei.md' }
const node = davResultToNode(remoteResult, '/root', 'http://example.com/dav')
expect(node.basename).toBe(remoteResult.basename)
expect(node.displayname).toBe(remoteResult.props!.displayname)
})

// If owner-id is set, it will be used as owner
test('has correct owner set', () => {
vi.spyOn(auth, 'getCurrentUser').mockReturnValue({ uid: 'user1', displayName: 'User 1', isAdmin: false })
Expand Down
76 changes: 66 additions & 10 deletions __tests__/files/node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,57 @@ describe('Permissions attribute', () => {
})
})

describe('Displayname attribute', () => {
test('legacy displayname attribute', () => {
// TODO: This logic can be removed with next major release (v4)
const file = new File({
source: 'https://cloud.domain.com/remote.php/dav/picture.jpg',
mime: 'image/jpeg',
owner: 'emma',
attributes: {
displayname: 'image.png',
},
})
expect(file.basename).toBe('picture.jpg')
expect(file.displayname).toBe('image.png')
expect(file.attributes.displayname).toBe('image.png')
})

test('Read displayname attribute', () => {
const file = new File({
source: 'https://cloud.domain.com/remote.php/dav/picture.jpg',
mime: 'image/jpeg',
owner: 'emma',
displayname: 'image.png',
})
expect(file.basename).toBe('picture.jpg')
expect(file.displayname).toBe('image.png')
})

test('Fallback displayname attribute', () => {
const file = new File({
source: 'https://cloud.domain.com/remote.php/dav/picture.jpg',
mime: 'image/jpeg',
owner: 'emma',
})
expect(file.basename).toBe('picture.jpg')
expect(file.displayname).toBe('picture.jpg')
})

test('Set displayname attribute', () => {
const file = new File({
source: 'https://cloud.domain.com/remote.php/dav/picture.jpg',
mime: 'image/jpeg',
owner: 'emma',
})
expect(file.basename).toBe('picture.jpg')
expect(file.displayname).toBe('picture.jpg')

file.displayname = 'image.png'
expect(file.displayname).toBe('image.png')
})
})

describe('Sanity checks', () => {
test('Invalid id', () => {
expect(() => new File({
Expand Down Expand Up @@ -237,6 +288,15 @@ describe('Sanity checks', () => {
})).toThrowError('Invalid source format, only http(s) is supported')
})

test('Invalid displayname', () => {
expect(() => new File({
source: 'https://cloud.domain.com/remote.php/dav/Photos',
mime: 'image',
displayname: true as unknown as string,
owner: 'emma',
})).toThrowError('Invalid displayname type')
})

test('Invalid mtime', () => {
expect(() => new File({
source: 'https://cloud.domain.com/remote.php/dav/Photos',
Expand Down Expand Up @@ -529,19 +589,17 @@ describe('Move and rename of a node', () => {
test('Move updates the fallback displayname', () => {
const file = new File({
source: 'https://cloud.example.com/dav/files/images/emma.jpeg',
displayname: 'emma.jpeg',
mime: 'image/jpeg',
owner: 'emma',
root: '/dav',
attributes: {
displayname: 'emma.jpeg',
},
})

expect(file.path).toBe('/files/images/emma.jpeg')
expect(file.attributes.displayname).toBe('emma.jpeg')
expect(file.displayname).toBe('emma.jpeg')
file.move('https://cloud.example.com/dav/files/pictures/jane.jpeg')
expect(file.path).toBe('/files/pictures/jane.jpeg')
expect(file.attributes.displayname).toBe('jane.jpeg')
expect(file.displayname).toBe('jane.jpeg')
})

test('Move does not updates custom displayname', () => {
Expand All @@ -550,16 +608,14 @@ describe('Move and rename of a node', () => {
mime: 'image/jpeg',
owner: 'emma',
root: '/dav',
attributes: {
displayname: 'profile.jpeg',
},
displayname: 'profile.jpeg',
})

expect(file.path).toBe('/files/images/emma.jpeg')
expect(file.attributes.displayname).toBe('profile.jpeg')
expect(file.displayname).toBe('profile.jpeg')
file.move('https://cloud.example.com/dav/files/pictures/jane.jpeg')
expect(file.path).toBe('/files/pictures/jane.jpeg')
expect(file.attributes.displayname).toBe('profile.jpeg')
expect(file.displayname).toBe('profile.jpeg')
})
})

Expand Down
12 changes: 3 additions & 9 deletions __tests__/utils/fileSorting.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,9 @@ describe('sortNodes', () => {
mime: 'text/plain',
// Resulting in name "d"
source: 'https://cloud.domain.com/remote.php/dav/d',
displayname: 'a',
mtime: new Date(100),
size: 100,
attributes: {
displayname: 'a',
},
}),
file('b', 100, 100),
file('c', 100, 500),
Expand All @@ -92,23 +90,19 @@ describe('sortNodes', () => {
mime: 'text/plain',
// Resulting in name "d"
source: 'https://cloud.domain.com/remote.php/dav/c',
displayname: 'a',
mtime: new Date(100),
size: 100,
attributes: {
displayname: 'a',
},
}),
// File with basename "b" but displayname "a"
new File({
owner: 'jdoe',
mime: 'text/plain',
// Resulting in name "d"
source: 'https://cloud.domain.com/remote.php/dav/b',
displayname: 'a',
mtime: new Date(100),
size: 100,
attributes: {
displayname: 'a',
},
}),
]

Expand Down
1 change: 1 addition & 0 deletions lib/dav/dav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export const davResultToNode = function(node: FileStat, filesRoot = davRootPath,
source: `${remoteURL}${node.filename}`,
mtime: new Date(Date.parse(node.lastmod)),
mime: node.mime || 'application/octet-stream',
displayname: props.displayname,
size: props?.size || Number.parseInt(props.getcontentlength || '0'),
// The fileid is set to -1 for failed requests
status: id < 0 ? NodeStatus.FAILED : undefined,
Expand Down
35 changes: 30 additions & 5 deletions lib/files/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ export enum NodeStatus {
LOCKED = 'locked',
}

interface NodeInternalData extends NodeData {
attributes: Attribute
}

export abstract class Node {

private _data: NodeData
private _data: NodeInternalData
private _attributes: Attribute
private _knownDavService = /(remote|public)\.php\/(web)?dav/i

Expand Down Expand Up @@ -62,7 +66,12 @@ export abstract class Node {
// Validate data
validateData(data, davService || this._knownDavService)

this._data = { ...data, attributes: {} }
this._data = {
// TODO: Remove with next major release, this is just for compatibility
displayname: data.attributes?.displayname,
...data,
attributes: {},
}

// Proxy the attributes to update the mtime on change
this._attributes = new Proxy(this._data.attributes!, this.handler)
Expand Down Expand Up @@ -102,6 +111,23 @@ export abstract class Node {
return basename(this.source)
}

/**
* The nodes displayname
* By default the display name and the `basename` are identical,
* but it is possible to have a different name. This happens
* on the files app for example for shared folders.
*/
get displayname(): string {
return this._data.displayname || this.basename
}

/**
* Set the displayname
*/
set displayname(displayname: string) {
this._data.displayname = displayname
}

/**
* Get this object's extension
* There is no setter as the source is not meant to be changed manually.
Expand Down Expand Up @@ -311,12 +337,11 @@ export abstract class Node {
this._data.source = destination
// Check if the displayname and the (old) basename were the same
// meaning no special displayname was set but just a fallback to the basename by Nextclouds WebDAV server
if (this._attributes.displayname
&& this._attributes.displayname === oldBasename
if (this.displayname === oldBasename
&& this.basename !== oldBasename) {
// We have to assume that the displayname was not set but just a copy of the basename
// this can not be guaranteed, so to be sure users should better refetch the node
this._attributes.displayname = this.basename
this.displayname = this.basename
}
this.updateMtime()
}
Expand Down
7 changes: 7 additions & 0 deletions lib/files/nodeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export interface NodeData {
/** The owner UID of this node */
owner: string|null

/** Optional the displayname of this node */
displayname?: string

/** The node attributes */
attributes?: Attribute

Expand Down Expand Up @@ -89,6 +92,10 @@ export const validateData = (data: NodeData, davService: RegExp) => {
throw new Error('Invalid source format, only http(s) is supported')
}

if (data.displayname && typeof data.displayname !== 'string') {
throw new Error('Invalid displayname type')
}

if (data.mtime && !(data.mtime instanceof Date)) {
throw new Error('Invalid mtime type')
}
Expand Down

0 comments on commit da5812f

Please sign in to comment.