Skip to content

Commit

Permalink
feat: Add downloadFile method in file model
Browse files Browse the repository at this point in the history
For the Flagship app offline feature, we want to make some files
available offline

To make this possible, we want the cozy-apps to download files as usual
except when they are hosted in the FlagshipApp

In that scenario, we want the cozy-app to call the new `downloadFile`
intent

Also to ease future compatibility, we want to implement a new method
hosted in the `file` model instead of in the collection as before

So in order to handle offline files, the cozy-app will now need to call
`downloadFile()` method from `models/file`
  • Loading branch information
Ldoppea committed Sep 9, 2024
1 parent c19d486 commit ad9b123
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 1 deletion.
31 changes: 31 additions & 0 deletions packages/cozy-client/src/models/file.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isFlagshipApp } from 'cozy-device-helper'

import get from 'lodash/get'
import isString from 'lodash/isString'
import has from 'lodash/has'
Expand Down Expand Up @@ -654,3 +656,32 @@ export const fetchBlobFileById = async (client, fileId) => {

return fileBlob
}

/**
* Download the requested file
*
* This method can be used in a web page context or in a WebView hosted by a Flagship app
*
* When used in a FlagshipApp WebView context, then the action is redirected to the host app
* that will process the download
*
* @param {object} params - The download parameters
* @param {CozyClient} params.client - Instance of CozyClient
* @param {import("../types").IOCozyFile} params.file - io.cozy.files metadata of the document to downloaded
* @param {string} [params.url] - Blob url that should be used to download encrypted files
* @param {import('cozy-intent').WebviewService} [params.webviewIntent] - webviewIntent that can be used to redirect the download to host Flagship app
*
* @returns {Promise<any>}
*/
export const downloadFile = async ({ client, file, url, webviewIntent }) => {
const filesCollection = client.collection(DOCTYPE_FILES)

if (isFlagshipApp() && webviewIntent && !isEncrypted(file)) {
return await webviewIntent.call('downloadFile', file)
}

if (isEncrypted(file)) {
return filesCollection.forceFileDownload(url, file.name)
}
return filesCollection.download(file)
}
90 changes: 89 additions & 1 deletion packages/cozy-client/src/models/file.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { isFlagshipApp } from 'cozy-device-helper'

import * as fileModel from './file'
import { Qualification } from './document/qualification'
import { QueryDefinition } from '../queries/dsl'
const CozyClient = require('cozy-client/dist/CozyClient').default
const CozyStackClient = require('cozy-stack-client').default

jest.mock('cozy-stack-client')
jest.mock('cozy-device-helper', () => ({
isFlagshipApp: jest.fn()
}))

const cozyClient = new CozyClient({
stackClient: new CozyStackClient()
Expand All @@ -19,6 +24,8 @@ const fetchFileContentByIdSpy = jest.fn().mockName('fetchFileContentById')
const moveSpy = jest.fn().mockName('move')
const moveToCozySpy = jest.fn().mockName('moveToCozy')
const moveFromCozySpy = jest.fn().mockName('moveFromCozy')
const downloadFromCozySpy = jest.fn().mockName('downloadFromCozy')
const forceFileDownloadFromCozySpy = jest.fn().mockName('forceFileDownload')

beforeAll(() => {
cozyClient.stackClient.collection.mockReturnValue({
Expand All @@ -31,10 +38,16 @@ beforeAll(() => {
fetchFileContentById: fetchFileContentByIdSpy,
move: moveSpy,
moveToCozy: moveToCozySpy,
moveFromCozy: moveFromCozySpy
moveFromCozy: moveFromCozySpy,
download: downloadFromCozySpy,
forceFileDownload: forceFileDownloadFromCozySpy
})
})

beforeEach(() => {
jest.clearAllMocks()
})

describe('File Model', () => {
it('should test if a file is a note or not', () => {
const fileDocument = {
Expand Down Expand Up @@ -860,3 +873,78 @@ describe('File qualification', () => {
})
})
})

describe('downloadFile', () => {
it('should handle download in web page', async () => {
const file = {
_id: 'SOME_FILE_ID',
_type: 'io.cozy.file',
name: 'SOME_FILE_NAME'
}

await fileModel.downloadFile({
// @ts-ignore
client: cozyClient,
// @ts-ignore
file,
webviewIntent: null
})

expect(downloadFromCozySpy).toHaveBeenCalledWith(file)
})

it('should handle download in Flagship app', async () => {
isFlagshipApp.mockReturnValue(true)
const webviewIntent = {
call: jest.fn()
}

const file = {
_id: 'SOME_FILE_ID',
_type: 'io.cozy.file',
name: 'SOME_FILE_NAME'
}

await fileModel.downloadFile({
// @ts-ignore
client: cozyClient,
// @ts-ignore
file,
// @ts-ignore
webviewIntent
})

expect(downloadFromCozySpy).not.toHaveBeenCalled()
expect(webviewIntent.call).toHaveBeenCalledWith('downloadFile', file)
})

it('should download encrypted files from web page as this is not supported yet by Flagship app', async () => {
isFlagshipApp.mockReturnValue(true)
const webviewIntent = {
call: jest.fn()
}

const file = {
_id: 'SOME_FILE_ID',
_type: 'io.cozy.file',
name: 'SOME_FILE_NAME',
encrypted: true
}

await fileModel.downloadFile({
// @ts-ignore
client: cozyClient,
// @ts-ignore
file,
url: 'SOME_URL',
// @ts-ignore
webviewIntent
})

expect(forceFileDownloadFromCozySpy).toHaveBeenCalledWith(
'SOME_URL',
'SOME_FILE_NAME'
)
expect(webviewIntent.call).not.toHaveBeenCalled()
})
})
1 change: 1 addition & 0 deletions packages/cozy-client/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ import { QueryDefinition } from './queries/dsl'
/**
* @typedef {object} FileDocument - An io.cozy.files document
* @property {string} _id - Id of the file
* @property {string} _rev - Rev of the file
* @property {FilesDoctype} _type - Doctype of the file
* @property {string} dir_id - Id of the parent folder
* @property {string} [path] - Path of the file
Expand Down
1 change: 1 addition & 0 deletions packages/cozy-stack-client/src/FileCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import logger from './logger'
*
* @typedef {object} FileDocument
* @property {string} _id - Id of the file
* @property {string} _rev - Rev of the file
* @property {FileAttributes} attributes - Attributes of the file
* @property {object} meta - Meta
* @property {object} relationships - Relationships
Expand Down

0 comments on commit ad9b123

Please sign in to comment.