fix(files_sharing): fix parsing of remote shares

Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
This commit is contained in:
skjnldsv 2024-06-06 18:48:02 +02:00
parent f626476b11
commit a0a36563b6
5 changed files with 61 additions and 23 deletions

View file

@ -51,7 +51,8 @@ export const canDownload = (nodes: Node[]) => {
}
export const canCopy = (nodes: Node[]) => {
// For now the only restriction is that a shared file
// cannot be copied if the download is disabled
// a shared file cannot be copied if the download is disabled
// it can be copied if the user has at least read permissions
return canDownload(nodes)
&& !nodes.some(node => node.permissions === Permission.NONE)
}

View file

@ -65,7 +65,7 @@
class="files-list__row-mtime"
data-cy-files-list-row-mtime
@click="openDetailsIfAvailable">
<NcDateTime :timestamp="source.mtime" :ignore-seconds="true" />
<NcDateTime v-if="source.mtime" :timestamp="source.mtime" :ignore-seconds="true" />
</td>
<!-- View columns -->
@ -177,8 +177,8 @@ export default defineComponent({
},
size() {
const size = parseInt(this.source.size, 10) || 0
if (typeof size !== 'number' || size < 0) {
const size = parseInt(this.source.size, 10)
if (typeof size !== 'number' || isNaN(size) || size < 0) {
return this.t('files', 'Pending')
}
return formatFileSize(size, true)
@ -186,8 +186,8 @@ export default defineComponent({
sizeOpacity() {
const maxOpacitySize = 10 * 1024 * 1024
const size = parseInt(this.source.size, 10) || 0
if (!size || size < 0) {
const size = parseInt(this.source.size, 10)
if (!size || isNaN(size) || size < 0) {
return {}
}

View file

@ -17,14 +17,18 @@ import { getCurrentUser } from '@nextcloud/auth'
import './sharingStatusAction.scss'
const generateAvatarSvg = (userId: string) => {
const avatarUrl = generateUrl('/avatar/{userId}/32', { userId })
const generateAvatarSvg = (userId: string, isGuest = false) => {
const avatarUrl = generateUrl(isGuest ? '/avatar/guest/{userId}/32' : '/avatar/{userId}/32?guestFallback=true', { userId })
return `<svg width="32" height="32" viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg" class="sharing-status__avatar">
<image href="${avatarUrl}" height="32" width="32" />
</svg>`
}
const isExternal = (node: Node) => {
return node.attributes.remote_id !== undefined
}
export const action = new FileAction({
id: 'sharing-status',
displayName(nodes: Node[]) {
@ -33,7 +37,7 @@ export const action = new FileAction({
const ownerId = node?.attributes?.['owner-id']
if (shareTypes.length > 0
|| (ownerId && ownerId !== getCurrentUser()?.uid)) {
|| (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
return t('files_sharing', 'Shared')
}
@ -46,11 +50,11 @@ export const action = new FileAction({
const ownerDisplayName = node?.attributes?.['owner-display-name']
// Mixed share types
if (Array.isArray(node.attributes?.['share-types'])) {
if (Array.isArray(node.attributes?.['share-types']) && node.attributes?.['share-types'].length > 1) {
return t('files_sharing', 'Shared multiple times with different people')
}
if (ownerId && ownerId !== getCurrentUser()?.uid) {
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
return t('files_sharing', 'Shared by {ownerDisplayName}', { ownerDisplayName })
}
@ -62,7 +66,7 @@ export const action = new FileAction({
const shareTypes = Object.values(node?.attributes?.['share-types'] || {}).flat() as number[]
// Mixed share types
if (Array.isArray(node.attributes?.['share-types'])) {
if (Array.isArray(node.attributes?.['share-types']) && node.attributes?.['share-types'].length > 1) {
return AccountPlusSvg
}
@ -84,8 +88,8 @@ export const action = new FileAction({
}
const ownerId = node?.attributes?.['owner-id']
if (ownerId && ownerId !== getCurrentUser()?.uid) {
return generateAvatarSvg(ownerId)
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
return generateAvatarSvg(ownerId, isExternal(node))
}
return AccountPlusSvg
@ -107,7 +111,7 @@ export const action = new FileAction({
}
// If the node is shared by someone else
if (ownerId && ownerId !== getCurrentUser()?.uid) {
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
return true
}

View file

@ -336,12 +336,27 @@ describe('SharingService share to Node mapping', () => {
expect(folder.attributes.favorite).toBe(1)
})
test('Empty', async () => {
jest.spyOn(logger, 'error').mockImplementationOnce(() => {})
jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({
data: {
ocs: {
data: [],
},
},
}))
const shares = await getContents(false, true, false, false)
expect(shares.contents).toHaveLength(0)
expect(logger.error).toHaveBeenCalledTimes(0)
})
test('Error', async () => {
jest.spyOn(logger, 'error').mockImplementationOnce(() => {})
jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({
data: {
ocs: {
data: [{}],
data: [null],
},
},
}))

View file

@ -6,7 +6,7 @@
import type { AxiosPromise } from 'axios'
import type { OCSResponse } from '@nextcloud/typings/ocs'
import { Folder, File, type ContentsWithRoot } from '@nextcloud/files'
import { Folder, File, type ContentsWithRoot, Permission } from '@nextcloud/files'
import { generateOcsUrl, generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
@ -19,16 +19,34 @@ const headers = {
'Content-Type': 'application/json',
}
const ocsEntryToNode = function(ocsEntry: any): Folder | File | null {
const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | null> {
try {
// Federated share handling
if (ocsEntry?.remote_id !== undefined) {
const mime = (await import('mime')).default
// This won't catch files without an extension, but this is the best we can do
ocsEntry.mimetype = mime.getType(ocsEntry.name)
ocsEntry.item_type = ocsEntry.mimetype ? 'file' : 'folder'
// Need to set permissions to NONE for federated shares
ocsEntry.item_permissions = Permission.NONE
ocsEntry.permissions = Permission.NONE
ocsEntry.uid_owner = ocsEntry.owner
// TODO: have the real display name stored somewhere
ocsEntry.displayname_owner = ocsEntry.owner
}
const isFolder = ocsEntry?.item_type === 'folder'
const hasPreview = ocsEntry?.has_preview === true
const Node = isFolder ? Folder : File
const fileid = ocsEntry.file_source
// If this is an external share that is not yet accepted,
// we don't have an id. We can fallback to the row id temporarily
const fileid = ocsEntry.file_source || ocsEntry.id
// Generate path and strip double slashes
const path = ocsEntry?.path || ocsEntry.file_target
const path = ocsEntry?.path || ocsEntry.file_target || ocsEntry.name
const source = generateRemoteUrl(`dav/${rootPath}/${path}`.replaceAll(/\/\//gm, '/'))
// Prefer share time if more recent than item mtime
@ -41,7 +59,7 @@ const ocsEntryToNode = function(ocsEntry: any): Folder | File | null {
id: fileid,
source,
owner: ocsEntry?.uid_owner,
mime: ocsEntry?.mimetype,
mime: ocsEntry?.mimetype || 'application/octet-stream',
mtime,
size: ocsEntry?.item_size,
permissions: ocsEntry?.item_permissions || ocsEntry?.permissions,
@ -150,7 +168,7 @@ export const getContents = async (sharedWithYou = true, sharedWithOthers = true,
const responses = await Promise.all(promises)
const data = responses.map((response) => response.data.ocs.data).flat()
let contents = data.map(ocsEntryToNode)
let contents = (await Promise.all(data.map(ocsEntryToNode)))
.filter((node) => node !== null) as (Folder | File)[]
if (filterTypes.length > 0) {