60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { createHash } from 'node:crypto';
|
|
import { promises as fsp } from 'node:fs';
|
|
import { createWriteStream, statSync, existsSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { pipeline } from 'node:stream/promises';
|
|
import { ATT_TMP_DIR } from 'src/config/attachment.config';
|
|
import { casPathFor, getAbsolutePath } from 'src/modules/attachments/utils/cas.util';
|
|
|
|
export type SaveResult = { sha256: string, storage_path: string, size: number };
|
|
|
|
@Injectable()
|
|
export class DiskStorageService {
|
|
// async exists(storagePathRel: string) {
|
|
// try {
|
|
// statSync(getAbsolutePath(storagePathRel));
|
|
// return true;
|
|
// } catch {
|
|
// return false;
|
|
// }
|
|
// }
|
|
|
|
//adds file and hash it
|
|
async saveStreamAndHash(input: NodeJS.ReadableStream): Promise<SaveResult> {
|
|
// 1- writing in ROOT:/_tmp while streaming and hashing
|
|
const tmpDir = ATT_TMP_DIR();
|
|
await fsp.mkdir(tmpDir, { recursive: true });
|
|
const tmpPath = join(tmpDir, `up_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
|
|
|
const hash = createHash('sha256');
|
|
const tmpOut = createWriteStream(tmpPath);
|
|
input.on('data', (chunk) => hash.update(chunk));
|
|
await pipeline(input, tmpOut); //await end of writing stream
|
|
|
|
const sha = hash.digest('hex');
|
|
const rel = casPathFor(sha);
|
|
const finalAbs = getAbsolutePath(rel);
|
|
|
|
// 2- is there is no destination => move (atomic renaming on the same volume)
|
|
if (!existsSync(finalAbs)) {
|
|
await fsp.mkdir(dirname(finalAbs), { recursive: true });
|
|
try {
|
|
await fsp.rename(tmpPath, finalAbs);
|
|
} catch (e) {
|
|
//if someone is faster and used the same hash
|
|
if (existsSync(finalAbs)) {
|
|
await fsp.rm(tmpPath, { force: true });
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
} else {
|
|
//remove duplicata if already exists
|
|
await fsp.rm(tmpPath, { force: true });
|
|
}
|
|
|
|
const size = statSync(finalAbs).size;
|
|
return { sha256: sha, storage_path: rel, size };
|
|
}
|
|
} |