import { IMediaStorage, MediaData } from '@waha/core/media/IMediaStorage'; import { SECOND } from '@waha/structures/enums.dto'; import * as fsp from 'fs/promises'; import * as path from 'path'; import { Logger } from 'pino'; import fs = require('fs'); import { fileExists } from '@waha/utils/files'; import { deleteAsync } from 'del'; // eslint-disable-next-line @typescript-eslint/no-var-requires const writeFileAtomic = require('write-file-atomic'); /** * Save files locally using the filesystem */ export class MediaLocalStorage implements IMediaStorage { private readonly lifetimeMs: number; constructor( protected log: Logger, private filesFolder: string, private baseUrl: string, lifetimeSeconds: number, ) { this.lifetimeMs = lifetimeSeconds * SECOND; if (this.lifetimeMs === 0) { this.log.info('Files lifetime is 0, files will not be removed'); } } async init() { return; } async exists(data: MediaData): Promise { const filepath = this.getFullPath(data); return await fileExists(filepath); } public async save(buffer: Buffer, data: MediaData): Promise { const filepath = this.getFullPath(data); const folder = path.dirname(filepath); await fsp.mkdir(folder, { recursive: true }); await writeFileAtomic(filepath, buffer); this.postponeRemoval(filepath); return true; } public async getStorageData(data: MediaData) { const filename = this.getKey(data); const url = this.baseUrl + filename; return { url }; } async purge() { if (this.lifetimeMs === 0) { this.log.info('No need to purge files with lifetime 0'); return; } if (fs.existsSync(this.filesFolder)) { deleteAsync([`${this.filesFolder}/*`], { force: true }).then((paths) => { if (paths.length === 0) { return; } this.log.info('Deleted files and directories:\n', paths.join('\n')); }); } else { fs.mkdirSync(this.filesFolder); this.log.info(`Directory '${this.filesFolder}' created from scratch`); } } private getKey(data: MediaData) { return `${data.session}/${data.message.id}.${data.file.extension}`; } private getFullPath(data: MediaData) { const filepath = this.getKey(data); return path.resolve(`${this.filesFolder}/${filepath}`); } private postponeRemoval(filepath: string) { if (this.lifetimeMs === 0) { return; } setTimeout( () => fs.unlink(filepath, () => { this.log.info(`File ${filepath} was removed`); }), this.lifetimeMs, ); } async close() { return; } }