Skip to content

Commit

Permalink
fix(files): add mount root property and adjust delete wording
Browse files Browse the repository at this point in the history
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Feb 6, 2024
1 parent 414a4ca commit 6cf4fe1
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 24 deletions.
11 changes: 11 additions & 0 deletions apps/dav/lib/Connector/Sabre/FilesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class FilesPlugin extends ServerPlugin {
public const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
public const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview';
public const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type';
public const MOUNT_ROOT_PROPERTYNAME = '{http://nextcloud.org/ns}is-mount-root';
public const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted';
public const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag';
public const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time';
Expand Down Expand Up @@ -361,6 +362,16 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node)
return $node->getFileInfo()->getMountPoint()->getMountType();
});

/**
* This is a special property which is used to determine if a node
* is a mount root or not, e.g. a shared folder.
* If so, then the node can only be unshared and not deleted.
* @see https://github.com/nextcloud/server/blob/cc75294eb6b16b916a342e69998935f89222619d/lib/private/Files/View.php#L696-L698
*/
$propFind->handle(self::MOUNT_ROOT_PROPERTYNAME, function () use ($node) {
return $node->getNode()->getInternalPath() === '' ? 'true' : 'false';
});

$propFind->handle(self::SHARE_NOTE, function () use ($node, $httpRequest): ?string {
$user = $this->userSession->getUser();
if ($user === null) {
Expand Down
81 changes: 63 additions & 18 deletions apps/files/src/actions/deleteAction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
import { action } from './deleteAction'
import { expect } from '@jest/globals'
import { File, Folder, Permission, View, FileAction } from '@nextcloud/files'
import * as auth from '@nextcloud/auth'
import * as eventBus from '@nextcloud/event-bus'
import axios from '@nextcloud/axios'

import logger from '../logger'

const view = {
Expand All @@ -50,36 +50,81 @@ describe('Delete action conditions tests', () => {
permissions: Permission.ALL,
})

// const file2 = new File({
// id: 1,
// source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
// owner: 'admin',
// mime: 'text/plain',
// permissions: Permission.ALL,
// })
const file2 = new File({
id: 1,
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
owner: 'admin',
mime: 'text/plain',
permissions: Permission.ALL,
attributes: {
'is-mount-root': true,
'mount-type': 'shared',
},
})

const folder = new Folder({
id: 1,
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo',
owner: 'admin',
mime: 'text/plain',
permissions: Permission.ALL,
})

const folder2 = new Folder({
id: 1,
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo',
owner: 'admin',
mime: 'text/plain',
permissions: Permission.ALL,
attributes: {
'is-mount-root': true,
'mount-type': 'shared',
},
})

const folder3 = new Folder({
id: 1,
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo',
owner: 'admin',
mime: 'text/plain',
permissions: Permission.ALL,
attributes: {
'is-mount-root': true,
'mount-type': 'external',
},
})

test('Default values', () => {
expect(action).toBeInstanceOf(FileAction)
expect(action.id).toBe('delete')
expect(action.displayName([file], view)).toBe('Delete')
expect(action.displayName([file], view)).toBe('Delete file')
expect(action.iconSvgInline([], view)).toBe('<svg>SvgMock</svg>')
expect(action.default).toBeUndefined()
expect(action.order).toBe(100)
})

test('Default trashbin view values', () => {
test('Default folder displayName', () => {
expect(action.displayName([folder], view)).toBe('Delete folder')
})

test('Default trashbin view displayName', () => {
expect(action.displayName([file], trashbinView)).toBe('Delete permanently')
})

// TODO: Fix this test
// test('Shared node values', () => {
// jest.spyOn(auth, 'getCurrentUser').mockReturnValue(null)
// expect(action.displayName([file2], view)).toBe('Unshare')
// })
test('Shared root node displayName', () => {
expect(action.displayName([file2], view)).toBe('Leave this share')
expect(action.displayName([folder2], view)).toBe('Leave this share')
expect(action.displayName([file2, folder2], view)).toBe('Leave these shares')
})

test('External storage root node displayName', () => {
expect(action.displayName([folder3], view)).toBe('Disconnect storage')
expect(action.displayName([folder3, folder3], view)).toBe('Disconnect storages')
})

// test('Shared and owned nodes values', () => {
// expect(action.displayName([file, file2], view)).toBe('Delete and unshare')
// })
test('Shared and owned nodes displayName', () => {
expect(action.displayName([file, file2], view)).toBe('Delete and unshare')
})
})

describe('Delete action enabled tests', () => {
Expand Down
93 changes: 87 additions & 6 deletions apps/files/src/actions/deleteAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,102 @@
*
*/
import { emit } from '@nextcloud/event-bus'
import { Permission, Node, View, FileAction } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { Permission, Node, View, FileAction, FileType } from '@nextcloud/files'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'

import CloseSvg from '@mdi/svg/svg/close.svg?raw'
import NetworkOffSvg from '@mdi/svg/svg/network-off.svg?raw'
import TrashCanSvg from '@mdi/svg/svg/trash-can.svg?raw'

import logger from '../logger.js'

const canUnshareOnly = (nodes: Node[]) => {
return nodes.every(node => node.attributes['is-mount-root'] === true
&& node.attributes['mount-type'] === 'shared')
}

const canDisconnectOnly = (nodes: Node[]) => {
return nodes.every(node => node.attributes['is-mount-root'] === true
&& node.attributes['mount-type'] === 'external')
}

const isMixedUnshareAndDelete = (nodes: Node[]) => {
if (nodes.length === 1) {
return false
}

const hasSharedItems = nodes.some(node => canUnshareOnly([node]))
const hasDeleteItems = nodes.some(node => !canUnshareOnly([node]))
return hasSharedItems && hasDeleteItems
}

const isAllFiles = (nodes: Node[]) => {
return !nodes.some(node => node.type !== FileType.File)
}

const isAllFolders = (nodes: Node[]) => {
return !nodes.some(node => node.type !== FileType.Folder)
}

export const action = new FileAction({
id: 'delete',
displayName(nodes: Node[], view: View) {
return view.id === 'trashbin'
? t('files', 'Delete permanently')
: t('files', 'Delete')
/**
* If we're in the trashbin, we can only delete permanently
*/
if (view.id === 'trashbin') {
return t('files', 'Delete permanently')
}

/**
* If we're in the sharing view, we can only unshare
*/
if (isMixedUnshareAndDelete(nodes)) {
return t('files', 'Delete and unshare')
}

/**
* If those nodes are all the root node of a
* share, we can only unshare them.
*/
if (canUnshareOnly(nodes)) {
return n('files', 'Leave this share', 'Leave these shares', nodes.length)
}

/**
* If those nodes are all the root node of an
* external storage, we can only disconnect it.
*/
if (canDisconnectOnly(nodes)) {
return n('files', 'Disconnect storage', 'Disconnect storages', nodes.length)
}

/**
* If we're only selecting files, use proper wording
*/
if (isAllFiles(nodes)) {
return n('files', 'Delete file', 'Delete files', nodes.length)
}

/**
* If we're only selecting folders, use proper wording
*/
if (isAllFolders(nodes)) {
return n('files', 'Delete folder', 'Delete folders', nodes.length)
}

return t('files', 'Delete')
},
iconSvgInline: () => {
iconSvgInline: (nodes: Node[]) => {
if (canUnshareOnly(nodes)) {
return CloseSvg
}

if (canDisconnectOnly(nodes)) {
return NetworkOffSvg
}

return TrashCanSvg
},

Expand Down
1 change: 1 addition & 0 deletions apps/files/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,6 @@ registerRecentView()
registerPreviewServiceWorker()

registerDavProperty('nc:hidden', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('nc:is-mount-root', { nc: 'http://nextcloud.org/ns' })

initLivePhotos()
5 changes: 5 additions & 0 deletions apps/files_sharing/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { registerDavProperty } from '@nextcloud/files'
import registerSharingViews from './views/shares'

import './actions/acceptShareAction'
Expand All @@ -29,3 +30,7 @@ import './actions/restoreShareAction'
import './actions/sharingStatusAction'

registerSharingViews()

registerDavProperty('nc:share-attributes', { nc: 'http://nextcloud.org/ns' })
registerDavProperty('oc:share-types', { oc: 'http://owncloud.org/ns' })
registerDavProperty('ocs:share-permissions', { ocs: 'http://open-collaboration-services.org/ns' })

0 comments on commit 6cf4fe1

Please sign in to comment.