Skip to content

Commit

Permalink
feat: Handle shouldReplaceFile at the ContentScript level
Browse files Browse the repository at this point in the history
It is now possible to give shouldReplaceFile function to saveFiles at
ContentScript level like this :

```javascript
await this.saveFiles([{
 filename: 'testfile.pdf',
 fileurl: '...',
 shouldReplaceFile: (existingFile, entry) => {
  // return true when the file should be replaced
 }
}])
```

or

```javascript
await this.saveFiles([{
 filename: 'testfile.pdf',
 fileurl: '...'
}], {
   shouldReplaceFile: (existingFile, entry) => {
    // return true when the file should be replaced
   }
})
```
  • Loading branch information
doubleface authored and doubleface committed Dec 21, 2023
1 parent f6eecf8 commit 7fa3e2e
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 3 deletions.
42 changes: 39 additions & 3 deletions packages/cozy-clisk/src/contentscript/ContentScript.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { blobToBase64, callStringFunction } from './utils'
import { wrapTimerFactory } from '../libs/wrapTimer'
import ky from 'ky/umd'
import cliskPackageJson from '../../package.json'
import { calculateFileKey } from '../libs/utils'

const log = Minilog('ContentScript class')

Expand Down Expand Up @@ -479,8 +480,8 @@ export default class ContentScript {
* - download files when not filtered out
* - converts blob files to base64 uri to be serializable
*
* @param {Array} entries : list of file entries to save
* @param {object} options : saveFiles options
* @param {Array<import('../launcher/saveFiles').saveFilesEntry & {shouldReplaceFile: Function}>} entries : list of file entries to save
* @param {import('../launcher/saveFiles').saveFileOptions & {context: object, shouldReplaceFile: Function}} options : saveFiles options
*/
async saveFiles(entries, options) {
this.onlyIn(PILOT_TYPE, 'saveFiles')
Expand All @@ -493,7 +494,42 @@ export default class ContentScript {
'No bridge is defined, you should call ContentScript.init before using this method'
)
}
return await this.bridge.call('saveFiles', entries, options)

const updatedEntries = this.prepareSaveFileEntries(entries, options)

return await this.bridge.call('saveFiles', updatedEntries, options)
}

/**
* Prepare entries to be given to launcher saveFiles. Especially function attributes which will not be serialized to the launcher
*
* @param {Array<import('../launcher/saveFiles').saveFilesEntry & {shouldReplaceFile?: Function}>} entries
* @param {import('../launcher/saveFiles').saveFileOptions & {context: object, shouldReplaceFile?: Function}} options
*/
prepareSaveFileEntries(entries, options) {
const existingFilesIndex = options?.context?.existingFilesIndex || {}

const updatedEntries = [...entries]
for (const entry of updatedEntries) {
if (entry.forceReplaceFile === true || entry.forceReplaceFile === false) {
// entry.forceReplaceFile has priority over shouldReplaceFile function
continue
}
const shouldReplaceFileFn =
entry.shouldReplaceFile || options.shouldReplaceFile
if (shouldReplaceFileFn) {
const existingFile =
existingFilesIndex[calculateFileKey(entry, options.fileIdAttributes)]
entry.forceReplaceFile = shouldReplaceFileFn(
existingFile,
entry,
options
)
delete entry?.shouldReplaceFile
}
}
delete options?.shouldReplaceFile
return updatedEntries
}

/**
Expand Down
122 changes: 122 additions & 0 deletions packages/cozy-clisk/src/contentscript/ContentScript.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,128 @@ import ContentScript, { PILOT_TYPE } from './ContentScript'
import { Q } from 'cozy-client'

describe('ContentScript', () => {
describe('saveFiles', () => {
it('should call launcher saveFiles with given nominal options', async () => {
const contentScript = new ContentScript()
contentScript.setContentScriptType(PILOT_TYPE)
contentScript.bridge = {
call: jest.fn(),
emit: jest.fn()
}
await contentScript.saveFiles(
[
{
filename: 'testfile.pdf',
fileurl: 'https://filedownload.com/file1'
}
],
{ context: {} }
)
expect(contentScript.bridge.call).toHaveBeenCalledTimes(1)
expect(contentScript.bridge.call).toHaveBeenCalledWith(
'saveFiles',
[
{
filename: 'testfile.pdf',
fileurl: 'https://filedownload.com/file1'
}
],
{ context: {} }
)
})
it('should force file replace when shouldReplace file in options returns true', async () => {
const contentScript = new ContentScript()
contentScript.setContentScriptType(PILOT_TYPE)
contentScript.bridge = {
call: jest.fn(),
emit: jest.fn()
}
await contentScript.saveFiles(
[
{
filename: 'testfile.pdf',
fileurl: 'https://filedownload.com/file1'
},
{
filename: 'testfile2.pdf',
fileurl: 'https://filedownload.com/file2'
}
],
{
context: {},
shouldReplaceFile: (file, entry) => {
const result = entry.filename === 'testfile2.pdf'
return result
},
fileIdAttributes: ['filename']
}
)
expect(contentScript.bridge.call).toHaveBeenCalledTimes(1)
expect(contentScript.bridge.call).toHaveBeenCalledWith(
'saveFiles',
[
{
filename: 'testfile.pdf',
fileurl: 'https://filedownload.com/file1',
forceReplaceFile: false
},
{
filename: 'testfile2.pdf',
fileurl: 'https://filedownload.com/file2',
forceReplaceFile: true
}
],
{
context: {},
fileIdAttributes: ['filename']
}
)
})
it('should force file replace when shouldReplace file in entry returns true', async () => {
const contentScript = new ContentScript()
contentScript.setContentScriptType(PILOT_TYPE)
contentScript.bridge = {
call: jest.fn(),
emit: jest.fn()
}
await contentScript.saveFiles(
[
{
filename: 'testfile.pdf',
fileurl: 'https://filedownload.com/file1',
shouldReplaceFile: () => true
},
{
filename: 'testfile2.pdf',
fileurl: 'https://filedownload.com/file2'
}
],
{
context: {},
fileIdAttributes: ['filename']
}
)
expect(contentScript.bridge.call).toHaveBeenCalledTimes(1)
expect(contentScript.bridge.call).toHaveBeenCalledWith(
'saveFiles',
[
{
filename: 'testfile.pdf',
fileurl: 'https://filedownload.com/file1',
forceReplaceFile: true
},
{
filename: 'testfile2.pdf',
fileurl: 'https://filedownload.com/file2'
}
],
{
context: {},
fileIdAttributes: ['filename']
}
)
})
})
describe('queryAll', () => {
it('should convert the given query definition to a serializable object', async () => {
const contentScript = new ContentScript()
Expand Down
13 changes: 13 additions & 0 deletions packages/cozy-clisk/src/libs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,16 @@ export const dataUriToArrayBuffer = dataURI => {
}
return { contentType, arrayBuffer }
}

/**
* Calculate the file key from an entry given to saveFiles
*
* @param {import('../launcher/saveFiles').saveFilesEntry} entry - a savefiles entry
* @param {Array<string>} fileIdAttributes - list of entry attributes which will be used to identify the entry in a unique way
* @returns {string} - The resulting file key
*/
export const calculateFileKey = (entry, fileIdAttributes) =>
fileIdAttributes
.sort()
.map(key => entry?.[key])
.join('####')

0 comments on commit 7fa3e2e

Please sign in to comment.