Skip to content

Commit

Permalink
feat: Clean non-important offline files when too old
Browse files Browse the repository at this point in the history
As we store offline accessible files in the device's storage, we want
to prevent them to fill the entire device's storage

We chose to keep only files that were accessed in the previous month.
All other files would be deleted

Important files are never deleted as long as they exist on the Cozy
instance
  • Loading branch information
Ldoppea committed Aug 26, 2024
1 parent 2ce9a88 commit 71229ad
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 6 deletions.
44 changes: 44 additions & 0 deletions src/app/domain/io.cozy.files/importantFiles.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { differenceInMonths } from 'date-fns'
import RNFS from 'react-native-fs'

import CozyClient from 'cozy-client'
import type { FileDocument } from 'cozy-client/types/types'
import Minilog from 'cozy-minilog'

import { downloadFile } from '/app/domain/io.cozy.files/offlineFiles'
Expand All @@ -12,6 +16,7 @@ import { getErrorMessage } from '/libs/functions/getErrorMessage'
const log = Minilog('📁 Offline Files')

const IMPORTANT_FILES_DOWNLOAD_DELAY_IN_MS = 100
const NB_OF_MONTH_BEFORE_EXPIRATION = 1

export const makeImportantFilesAvailableOfflineInBackground = (
client: CozyClient
Expand All @@ -37,8 +42,47 @@ const makeImportantFilesAvailableOffline = async (
log.debug('Start downloading important files for offline support')
const importantFiles = await getImportantFiles(client)

await cleanOldNonImportantFiles(importantFiles)

for (const importantFile of importantFiles) {
log.debug(`Start downloading file ${importantFile._id}`)
await downloadFile(importantFile, client)
}
}

const cleanOldNonImportantFiles = async (
importantFiles: FileDocument[]
): Promise<void> => {
try {
const offlineFiles = await getOfflineFilesConfiguration()
const offlineFilesMap = Array.from(offlineFiles)

const importantFilesIds = importantFiles.map(file => file._id)

const now = new Date()

for (const [, offlineFile] of offlineFilesMap) {
const lastOpened = offlineFile.lastOpened
if (
!importantFilesIds.includes(offlineFile.id) &&
(differenceInMonths(lastOpened, now) > NB_OF_MONTH_BEFORE_EXPIRATION ||
!lastOpened)
) {
log.debug(
`Remove old unimportant file ${
offlineFile.id
} (last opened on ${lastOpened.toString()})`
)
if (await RNFS.exists(offlineFile.path)) {
await RNFS.unlink(offlineFile.path)
}
await removeOfflineFileFromConfiguration(offlineFile.id)
}
}
} catch (error) {
const errorMessage = getErrorMessage(error)
log.error(
`Something went wrong while cleaning non-important files: ${errorMessage}`
)
}
}
27 changes: 25 additions & 2 deletions src/app/domain/io.cozy.files/offlineFiles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
import {
addOfflineFileToConfiguration,
getOfflineFileFromConfiguration,
OfflineFile
OfflineFile,
updateLastOpened
} from '/app/domain/io.cozy.files/offlineFilesConfiguration'
import {
getDownloadUrlById,
Expand All @@ -22,7 +23,8 @@ jest.mock('react-native-file-viewer', () => ({
}))
jest.mock('/app/domain/io.cozy.files/offlineFilesConfiguration', () => ({
addOfflineFileToConfiguration: jest.fn(),
getOfflineFileFromConfiguration: jest.fn()
getOfflineFileFromConfiguration: jest.fn(),
updateLastOpened: jest.fn()
}))
jest.mock('/app/domain/io.cozy.files/remoteFiles', () => ({
getFileById: jest.fn(),
Expand Down Expand Up @@ -139,6 +141,27 @@ describe('offlineFiles', () => {
expect(addOfflineFileToConfiguration).not.toHaveBeenCalled()
})

it('should update lastOpened attribute if file exists', async () => {
mockRemoteFile({
_id: 'SOME_FILE_ID',
_rev: 'SOME_REV',
name: 'SOME_FILE_NAME'
})
mockFileFromConfiguration({
id: 'SOME_FILE_ID',
rev: 'SOME_REV',
path: 'SOME_EXISTING_PATH_FROM_CACHE',
lastOpened: new Date()
})
const file = {
_id: 'SOME_FILE_ID',
name: 'SOME_FILE_NAME'
} as unknown as FileDocument
await downloadFile(file, mockClient)

expect(updateLastOpened).toHaveBeenCalledWith('SOME_FILE_ID')
})

it('should download file if existing but with different rev', async () => {
mockRemoteFile({
_id: 'SOME_FILE_ID',
Expand Down
4 changes: 3 additions & 1 deletion src/app/domain/io.cozy.files/offlineFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import Minilog from 'cozy-minilog'

import {
addOfflineFileToConfiguration,
getOfflineFileFromConfiguration
getOfflineFileFromConfiguration,
updateLastOpened
} from '/app/domain/io.cozy.files/offlineFilesConfiguration'
import {
getDownloadUrlById,
Expand Down Expand Up @@ -50,6 +51,7 @@ export const downloadFile = async (

if (existingFile && existingFile.rev === remoteFile._rev) {
log.debug(`File ${file._id} already exist, returning existing one`)
await updateLastOpened(file._id)
return existingFile.path
}

Expand Down
26 changes: 23 additions & 3 deletions src/app/domain/io.cozy.files/offlineFilesConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface OfflineFile {
id: string
rev: string
path: string
lastOpened: Date
}

export type OfflineFilesConfiguration = Map<string, OfflineFile>
Expand All @@ -20,11 +21,16 @@ export const getOfflineFilesConfiguration =
return files
}

export const addOfflineFileToConfiguration = async (file: OfflineFile) => {
export const addOfflineFileToConfiguration = async (
file: Omit<OfflineFile, 'lastOpened'>
): Promise<void> => {
const files = await getOfflineFilesConfiguration()

files.set(file.id, file)

files.set(file.id, {
...file,
lastOpened: new Date()
})

const filesArray = Array.from(files.entries())

return storeData(CozyPersistedStorageKeys.OfflineFiles, filesArray)
Expand Down Expand Up @@ -53,3 +59,17 @@ export const getOfflineFileFromConfiguration = async (

return undefined
}

export const updateLastOpened = async (fileId: string): Promise<void> => {
const file = await getOfflineFileFromConfiguration(fileId)

if (!file) {
throw new Error(
`Cannot update 'lastOpened' attribute for not existing file ${fileId}`
)
}

file.lastOpened = new Date()

await addOfflineFileToConfiguration(file)
}

0 comments on commit 71229ad

Please sign in to comment.