Skip to content

Commit ecb3e4e

Browse files
author
LucsT
committed
feat(cozy-clisk): Implementing selective file dowload and fixing metadata deduplication
1 parent e991292 commit ecb3e4e

File tree

8 files changed

+316
-111
lines changed

8 files changed

+316
-111
lines changed

packages/cozy-clisk/src/contentscript/ContentScript.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
// @ts-check
2+
<<<<<<< Updated upstream
3+
=======
4+
import get from 'lodash/get'
5+
import clone from 'lodash/clone'
6+
>>>>>>> Stashed changes
27
import waitFor from 'p-wait-for'
38
import Minilog from '@cozy/minilog'
49

@@ -527,7 +532,8 @@ export default class ContentScript {
527532
*/
528533
async storeFromWorker(obj) {
529534
// @ts-ignore Aucune surcharge ne correspond à cet appel.
530-
Object.assign(this.store, obj)
535+
//Object.assign(this.store, obj)
536+
this.store = clone(obj)
531537
}
532538

533539
onlyIn(csType, method) {

packages/cozy-clisk/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export { MessengerInterface } from './bridge/bridgeInterfaces'
55
export { default as ContentScript } from './contentscript/ContentScript'
66
export { default as addData } from './launcher/addData'
77
export { default as hydrateAndFilter } from './launcher/hydrateAndFilter'
8+
export { default as getFileIfExists } from './launcher/getFileIfExists'
89
export { default as saveFiles } from './launcher/saveFiles'
910
export { default as saveBills } from './launcher/saveBills'
1011
export { default as saveIdentity } from './launcher/saveIdentity'

packages/cozy-clisk/src/launcher.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { default as addData } from './launcher/addData'
22
export { default as hydrateAndFilter } from './launcher/hydrateAndFilter'
3+
export { default as getFileIfExists } from './launcher/getFileIfExists'
34
export { default as saveFiles } from './launcher/saveFiles'
45
export { default as saveBills } from './launcher/saveBills'
56
export { default as saveIdentity } from './launcher/saveIdentity'
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import Minilog from '@cozy/minilog'
2+
import { Q } from 'cozy-client'
3+
import get from 'lodash/get'
4+
5+
const log = Minilog('getFileIfExists')
6+
7+
module.exports = getFileIfExists
8+
9+
async function getFileIfExists({ client, entry, options, folderPath, slug }) {
10+
const fileIdAttributes = options.fileIdAttributes
11+
const sourceAccountIdentifier = options.sourceAccountIdentifier
12+
13+
const isReadyForFileMetadata =
14+
fileIdAttributes && slug && sourceAccountIdentifier
15+
if (isReadyForFileMetadata) {
16+
log.debug('Detecting file with metadata')
17+
const file = await getFileFromMetaData(
18+
client,
19+
entry,
20+
fileIdAttributes,
21+
sourceAccountIdentifier,
22+
slug
23+
)
24+
if (!file) {
25+
// no file with correct metadata, maybe the corresponding file already exist in the default
26+
// path from a previous version of the connector
27+
log.debug('Rolling back on detection by filename')
28+
return getFileFromPath(client, entry, folderPath)
29+
} else {
30+
return file
31+
}
32+
} else {
33+
log.debug('Rolling back on detection by filename')
34+
return getFileFromPath(client, entry, folderPath)
35+
}
36+
}
37+
38+
async function getFileFromMetaData(
39+
client,
40+
entry,
41+
fileIdAttributes,
42+
sourceAccountIdentifier,
43+
slug
44+
) {
45+
log.debug(
46+
`Checking existence of ${calculateFileKey(entry, fileIdAttributes)}`
47+
)
48+
const files = await client.queryAll(
49+
Q('io.cozy.files')
50+
.where({
51+
metadata: {
52+
fileIdAttributes: calculateFileKey(entry, fileIdAttributes)
53+
},
54+
trashed: false,
55+
cozyMetadata: {
56+
sourceAccountIdentifier,
57+
createdByApp: slug
58+
}
59+
})
60+
.indexFields([
61+
'metadata.fileIdAttributes',
62+
'trashed',
63+
'cozyMetadata.sourceAccountIdentifier',
64+
'cozyMetadata.createdByApp'
65+
])
66+
)
67+
if (files && files[0]) {
68+
if (files.length > 1) {
69+
log.warn(
70+
`Found ${files.length} files corresponding to ${calculateFileKey(
71+
entry,
72+
fileIdAttributes
73+
)}`
74+
)
75+
}
76+
return files[0]
77+
} else {
78+
log.debug('File not found')
79+
return false
80+
}
81+
}
82+
83+
async function getFileFromPath(client, entry, folderPath) {
84+
try {
85+
log.debug(`Checking existence of ${getFilePath({ entry, folderPath })}`)
86+
const result = await client
87+
.collection('io.cozy.files')
88+
.statByPath(getFilePath({ entry, folderPath }))
89+
return result.data
90+
} catch (err) {
91+
log.debug(err.message)
92+
return false
93+
}
94+
}
95+
96+
function getFilePath({ file, entry, folderPath }) {
97+
if (file) {
98+
return folderPath + '/' + getAttribute(file, 'name')
99+
} else if (entry) {
100+
return folderPath + '/' + getFileName(entry)
101+
}
102+
}
103+
104+
function getAttribute(obj, attribute) {
105+
return get(obj, `attributes.${attribute}`, get(obj, attribute))
106+
}
107+
108+
function calculateFileKey(entry, fileIdAttributes) {
109+
return fileIdAttributes
110+
.sort()
111+
.map(key => get(entry, key))
112+
.join('####')
113+
}
114+
115+
function getFileName(entry) {
116+
let filename
117+
if (entry.filename) {
118+
filename = entry.filename
119+
} else {
120+
log.error('Could not get a file name for the entry')
121+
return false
122+
}
123+
return sanitizeFileName(filename)
124+
}
125+
126+
function sanitizeFileName(filename) {
127+
return filename.replace(/^\.+$/, '').replace(/[/?<>\\:*|":]/g, '')
128+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import getFileIfExists from './getFileIfExists'
2+
3+
describe('getFileIfExists', function () {
4+
function setup() {
5+
const statByPath = jest.fn()
6+
const queryAll = jest.fn()
7+
const client = {
8+
queryAll,
9+
collection: () => ({
10+
statByPath
11+
})
12+
}
13+
return {
14+
statByPath,
15+
queryAll,
16+
client
17+
}
18+
}
19+
20+
it('should use metadata if fileIdAttributes, slug and sourceAccountIdentifier are present', async () => {
21+
const { client, queryAll } = setup()
22+
queryAll.mockResolvedValue([{ _id: 'abcd' }]) // Mocking the return with a simple file obj
23+
const file = await getFileIfExists({
24+
client,
25+
entry: {
26+
filename: 'filename'
27+
},
28+
options: {
29+
fileIdAttributes: ['filename'],
30+
sourceAccountIdentifier: 'Identifier'
31+
},
32+
folderPath: '/fakepath',
33+
slug: 'slug'
34+
})
35+
// Test that the Q request cotains our parameters
36+
expect(queryAll).toHaveBeenCalledWith(
37+
expect.objectContaining({
38+
selector: expect.objectContaining({
39+
cozyMetadata: expect.objectContaining({
40+
createdByApp: 'slug',
41+
sourceAccountIdentifier: 'Identifier'
42+
}),
43+
metadata: expect.objectContaining({ fileIdAttributes: 'filename' })
44+
})
45+
})
46+
)
47+
expect(file).toStrictEqual({ _id: 'abcd' })
48+
})
49+
50+
it('should roll back to filename when with metadata return nothing', async () => {
51+
const { client, queryAll, statByPath } = setup()
52+
queryAll.mockResolvedValue(undefined) // Mocking an empty return
53+
statByPath.mockResolvedValue({ data: { _id: 'abcd' } })
54+
const file = await getFileIfExists({
55+
client,
56+
entry: {
57+
filename: 'filename'
58+
},
59+
options: {
60+
fileIdAttributes: ['filename'],
61+
sourceAccountIdentifier: 'Identifier'
62+
},
63+
folderPath: '/fakepath',
64+
slug: 'slug'
65+
})
66+
expect(queryAll).toHaveBeenCalledWith(
67+
expect.objectContaining({
68+
selector: expect.objectContaining({
69+
cozyMetadata: expect.objectContaining({
70+
createdByApp: 'slug',
71+
sourceAccountIdentifier: 'Identifier'
72+
}),
73+
metadata: expect.objectContaining({ fileIdAttributes: 'filename' })
74+
})
75+
})
76+
)
77+
expect(statByPath).toHaveBeenCalledWith('/fakepath/filename')
78+
expect(file).toStrictEqual({ _id: 'abcd' })
79+
})
80+
81+
it('shoud use filename if sourceAccountIdentifier is missing', async () => {
82+
const { statByPath, client } = setup()
83+
statByPath.mockResolvedValue({ data: { _id: 'abcd' } })
84+
const file = await getFileIfExists({
85+
client,
86+
entry: {
87+
filename: 'filename'
88+
},
89+
options: { fileIdAttributes: ['filename'] },
90+
folderPath: '/fakepath',
91+
slug: 'slug'
92+
})
93+
expect(statByPath).toHaveBeenCalledWith('/fakepath/filename')
94+
expect(file).toStrictEqual({ _id: 'abcd' })
95+
})
96+
97+
it('shoud use filename if slug is missing', async () => {
98+
const { statByPath, client } = setup()
99+
statByPath.mockResolvedValue({ data: { _id: 'abcd' } })
100+
const file = await getFileIfExists({
101+
client,
102+
entry: {
103+
filename: 'filename'
104+
},
105+
options: {
106+
fileIdAttributes: ['filename'],
107+
sourceAccountIdentifier: 'Identifier'
108+
},
109+
folderPath: '/fakepath'
110+
})
111+
expect(statByPath).toHaveBeenCalledWith('/fakepath/filename')
112+
expect(file).toStrictEqual({ _id: 'abcd' })
113+
})
114+
it('shoud use filename if fileIdAttributes is missing', async () => {
115+
const { statByPath, client } = setup()
116+
statByPath.mockResolvedValue({ data: { _id: 'abcd' } })
117+
const file = await getFileIfExists({
118+
client,
119+
entry: {
120+
filename: 'filename'
121+
},
122+
options: { sourceAccountIdentifier: 'Identifier' },
123+
folderPath: '/fakepath',
124+
slug: 'slug'
125+
})
126+
expect(statByPath).toHaveBeenCalledWith('/fakepath/filename')
127+
expect(file).toStrictEqual({ _id: 'abcd' })
128+
})
129+
})

packages/cozy-clisk/src/launcher/saveBills.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import hydrateAndFilter from './hydrateAndFilter'
33
import addData from './addData'
44
import Minilog from '@cozy/minilog'
5-
import _ from 'lodash'
5+
import _, { lowerCase } from 'lodash'
66
const log = Minilog('saveBills')
77
const DOCTYPE = 'io.cozy.bills'
88

@@ -137,16 +137,22 @@ function convertCurrency(currency) {
137137
}
138138

139139
function checkRequiredAttributes(entries) {
140+
log.warn('Checking AAAABttributes')
140141
for (let entry of entries) {
141142
for (let attr in requiredAttributes) {
142143
if (entry[attr] == null) {
143144
throw new Error(
144145
`saveBills: an entry is missing the required ${attr} attribute`
145146
)
146147
}
148+
log.warn(attr)
147149
const checkFunction = requiredAttributes[attr]
148150
const isExpectedType = _(entry[attr])[checkFunction]()
149151
if (isExpectedType === false) {
152+
log.warn(entry.date)
153+
log.warn(typeof entry.date)
154+
log.warn(_.isDate(entry.date))
155+
log.warn(entry)
150156
throw new Error(
151157
`saveBills: an entry has a ${attr} which does not respect ${checkFunction}`
152158
)

packages/cozy-clisk/src/launcher/saveBills.spec.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,22 @@ describe('saveBills', function () {
5151
)
5252
})
5353
it('should check dates in bills', async () => {
54-
expect.assertions(1)
54+
//expect.assertions(1)
5555
await expect(async () => {
5656
return saveBills(
5757
[
5858
{
5959
filename: 'filename',
6060
fileurl: 'fileurl',
6161
amount: 12,
62-
date: 'nodate',
62+
// date: 'nodate',
63+
date: Date.now(),
6364
vendor: 'vendor',
65+
<<<<<<< Updated upstream
6466
fileDocument: { _id: 'testfileid' }
67+
=======
68+
//fileDocument: {_id: 'testfileid'}
69+
>>>>>>> Stashed changes
6570
}
6671
],
6772
{
@@ -72,13 +77,19 @@ describe('saveBills', function () {
7277
'saveBills: an entry has a date which does not respect isDate'
7378
)
7479
})
80+
<<<<<<< Updated upstream
7581
it('should convert valid date strings in bills', async () => {
7682
const result = await saveBills(
83+
=======
84+
it('should save a bills with required attribute', async () => {
85+
saveBills(
86+
>>>>>>> Stashed changes
7787
[
7888
{
7989
filename: 'filename',
8090
fileurl: 'fileurl',
8191
amount: 12,
92+
<<<<<<< Updated upstream
8293
date: '2012-09-02',
8394
vendor: 'vendor',
8495
fileDocument: { _id: 'testfileid' }
@@ -100,5 +111,15 @@ describe('saveBills', function () {
100111
vendor: 'vendor'
101112
}
102113
])
114+
=======
115+
// date: 'nodate',
116+
date: Date.now(),
117+
vendor: 'vendor',
118+
fileDocument: {_id: 'testfileid'}
119+
}
120+
]
121+
)
122+
123+
>>>>>>> Stashed changes
103124
})
104125
})

0 commit comments

Comments
 (0)