Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[VO-711] feat: Add a collection to access files into a Nextcloud #1471

Merged
merged 3 commits into from
May 23, 2024
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
2 changes: 1 addition & 1 deletion docs/api/cozy-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ Fetches a queryDefinition and returns the queryState

| Name | Type | Description |
| :------ | :------ | :------ |
| `queryDefinition` | [`QueryDefinition`](classes/QueryDefinition.md) | Definition created with Q() |
| `queryDefinition` | [`QueryDefinition`](classes/QueryDefinition.md) | () => [`QueryDefinition`](classes/QueryDefinition.md) | Definition created with Q() |
| `options` | `QueryOptions` | Options created with Q() |

*Returns*
Expand Down
15 changes: 15 additions & 0 deletions docs/api/cozy-stack-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ query to work</p>
<dd><p>Rules determine the behavior of the sharing when changes are made to the shared document
See <a href="https://docs.cozy.io/en/cozy-stack/sharing-design/#description-of-a-sharing">https://docs.cozy.io/en/cozy-stack/sharing-design/#description-of-a-sharing</a></p>
</dd>
<dt><a href="#forceDownload">forceDownload</a></dt>
<dd><p>Force a download from the given href</p>
</dd>
</dl>

## Functions
Expand Down Expand Up @@ -2200,6 +2203,18 @@ See https://docs.cozy.io/en/cozy-stack/sharing-design/#description-of-a-sharing
| document | [<code>Sharing</code>](#Sharing) | The document to share. Should have and _id and a name |
| sharingType | [<code>SharingType</code>](#SharingType) | The type of the sharing |

<a name="forceDownload"></a>

## forceDownload
Force a download from the given href

**Kind**: global constant

| Param | Type | Description |
| --- | --- | --- |
| href | <code>string</code> | The link to download |
| filename | <code>string</code> | The file name to download |

<a name="getAccessToken"></a>

## getAccessToken() ⇒ <code>string</code>
Expand Down
4 changes: 2 additions & 2 deletions packages/cozy-client/src/hooks/useQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const generateFetchMoreQueryDefinition = queryResult => {
/**
* Fetches a queryDefinition and returns the queryState
*
* @param {QueryDefinition} queryDefinition - Definition created with Q()
* @param {QueryDefinition|(() => QueryDefinition)} queryDefinition - Definition created with Q()
* @param {import("../types").QueryOptions} options - Options created with Q()
* @returns {import("../types").UseQueryReturnValue}
*/
Expand Down Expand Up @@ -51,7 +51,7 @@ const useQuery = (queryDefinition, options) => {

const client = useClient()
const queryState = useSelector(() => {
if (options.singleDocData === undefined && queryDefinition.id) {
if (options.singleDocData === undefined && definition?.id) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to typing, yet another example, even with JSDoc

logger.warn(
'useQuery options.singleDocData will pass to true in a next version of cozy-client, please add it now to prevent any problem in the future.'
)
Expand Down
4 changes: 2 additions & 2 deletions packages/cozy-client/types/hooks/useQuery.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ export default useQuery;
/**
* Fetches a queryDefinition and returns the queryState
*
* @param {QueryDefinition} queryDefinition - Definition created with Q()
* @param {QueryDefinition|(() => QueryDefinition)} queryDefinition - Definition created with Q()
* @param {import("../types").QueryOptions} options - Options created with Q()
* @returns {import("../types").UseQueryReturnValue}
*/
declare function useQuery(queryDefinition: QueryDefinition, options: import("../types").QueryOptions): import("../types").UseQueryReturnValue;
declare function useQuery(queryDefinition: QueryDefinition | (() => QueryDefinition), options: import("../types").QueryOptions): import("../types").UseQueryReturnValue;
import { QueryDefinition } from "../queries/dsl";
6 changes: 6 additions & 0 deletions packages/cozy-stack-client/src/CozyStackClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import MicroEE from 'microee'
import errors, { FetchError } from './errors'
import logger from './logger'
import PromiseCache from './promise-cache'
import {
NEXTCLOUD_FILES_DOCTYPE,
NextcloudFilesCollection
} from './NextcloudFilesCollection'

const normalizeUri = uriArg => {
let uri = uriArg
Expand Down Expand Up @@ -100,6 +104,8 @@ class CozyStackClient {
return new ShortcutsCollection(this)
case APPS_REGISTRY_DOCTYPE:
return new AppsRegistryCollection(this)
case NEXTCLOUD_FILES_DOCTYPE:
return new NextcloudFilesCollection(this)
default:
return new DocumentCollection(doctype, this)
}
Expand Down
10 changes: 2 additions & 8 deletions packages/cozy-stack-client/src/FileCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import pick from 'lodash/pick'
import { MangoQueryOptions } from './mangoIndex'

import DocumentCollection, { normalizeDoc } from './DocumentCollection'
import { uri, slugify, formatBytes } from './utils'
import { uri, slugify, formatBytes, forceDownload } from './utils'
import { FetchError } from './errors'
import { dontThrowNotFoundError } from './Collection'
import { getIllegalCharacters } from './getIllegalCharacter'
Expand Down Expand Up @@ -659,13 +659,7 @@ class FileCollection extends DocumentCollection {
* @param {string} filename - The file name to download
*/
forceFileDownload = (href, filename) => {
const element = document.createElement('a')
element.setAttribute('href', href)
element.setAttribute('download', filename)
element.style.display = 'none'
document.body.appendChild(element)
element.click()
document.body.removeChild(element)
forceDownload(href, filename)
}

/**
Expand Down
62 changes: 62 additions & 0 deletions packages/cozy-stack-client/src/NextcloudFilesCollection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import DocumentCollection from './DocumentCollection'
import { forceDownload } from './utils'

const NEXTCLOUD_FILES_DOCTYPE = 'io.cozy.remote.nextcloud.files'

const normalizeDoc = DocumentCollection.normalizeDoctypeJsonApi(
NEXTCLOUD_FILES_DOCTYPE
)
const normalizeNextcloudFile = (sourceAccount, path) => file => {
const extendedAttributes = {
...file.attributes,
path: `${path}${path.endsWith('/') ? '' : '/'}${file.attributes.name}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stack does not return the path like the other files?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nop, but maybe she could do it

Copy link
Contributor

@paultranvan paultranvan May 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it should for consistency, @nono what do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it makes the response bigger, and they are quite easy to compute client-side, so I decided to not include them.

cozyMetadata: {
...file.attributes.cozyMetadata,
sourceAccount
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to manually set the sourceAccount? I feel like this should be up to the client to set it, just like the other cozyMetadata attributes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for convenience, because in order to use all the routes in the stack we need to have the Nextcloud path and sourceAccount pair. This is only present in the shortcut describing the Nextcloud. To reuse the same action as the io.cozy.files, I've introduced it into the file so that you only have to pass a file as a parameter as expected

}
}

return {
...normalizeDoc(file, NEXTCLOUD_FILES_DOCTYPE),
...extendedAttributes
}
}

class NextcloudFilesCollection extends DocumentCollection {
constructor(stackClient) {
super(NEXTCLOUD_FILES_DOCTYPE, stackClient)
}

async find(selector) {
if (selector.sourceAccount && selector.path) {
const resp = await this.stackClient.fetchJSON(
'GET',
`/remote/nextcloud/${selector.sourceAccount}/${selector.path}`
)

return {
data: resp.data.map(
normalizeNextcloudFile(selector.sourceAccount, selector.path)
)
}
}
throw new Error('Not implemented')
}

/**
* Download a file from a Nextcloud server
*
*/
async download(file) {
const res = await this.stackClient.fetch(
'GET',
`/remote/nextcloud/${file.cozyMetadata.sourceAccount}/${file.path}?Dl=1`
)
const blob = await res.blob()
const href = URL.createObjectURL(blob)
const filename = file.path.split('/').pop()
forceDownload(href, filename)
}
}

export { NextcloudFilesCollection, NEXTCLOUD_FILES_DOCTYPE }
16 changes: 16 additions & 0 deletions packages/cozy-stack-client/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,19 @@ export const formatBytes = (bytes, decimals = 2) => {

return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}

/**
* Force a download from the given href
*
* @param {string} href - The link to download
* @param {string} filename - The file name to download
*/
export const forceDownload = (href, filename) => {
const element = document.createElement('a')
element.setAttribute('href', href)
element.setAttribute('download', filename)
element.style.display = 'none'
document.body.appendChild(element)
element.click()
document.body.removeChild(element)
}
Loading