local.adapter.ts 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. import { StorageAdapter, SavedPath } from '../types';
  2. import { pipeline } from 'stream/promises';
  3. import * as fs from 'fs';
  4. import { mkdir, rename, unlink } from 'fs/promises';
  5. import * as path from 'path';
  6. const DEFAULT_LOCAL_ROOT = '/data/media';
  7. export class LocalStorageAdapter implements StorageAdapter {
  8. constructor(private readonly localRoot: string = DEFAULT_LOCAL_ROOT) {}
  9. private resolveAbsolutePath(
  10. localStoragePrefix: string,
  11. relativePath: string,
  12. ) {
  13. return path.resolve(this.localRoot, localStoragePrefix, relativePath);
  14. }
  15. private tempPath(targetPath: string) {
  16. return `${targetPath}.part`;
  17. }
  18. async put(
  19. relativePath: string,
  20. localStoragePrefix: string,
  21. stream: NodeJS.ReadableStream,
  22. ): Promise<{ savedPath: SavedPath }> {
  23. const targetPath = this.resolveAbsolutePath(
  24. localStoragePrefix,
  25. relativePath,
  26. );
  27. const targetDir = path.dirname(targetPath);
  28. await mkdir(targetDir, { recursive: true });
  29. const stagingPath = this.tempPath(targetPath);
  30. const writeStream = fs.createWriteStream(stagingPath, { flags: 'w' });
  31. try {
  32. await pipeline(stream, writeStream);
  33. await rename(stagingPath, targetPath);
  34. return { savedPath: targetPath };
  35. } catch (err) {
  36. await unlink(stagingPath).catch(() => undefined);
  37. throw err;
  38. }
  39. }
  40. async delete(
  41. relativePath: string[],
  42. localStoragePrefix: string,
  43. ): Promise<void> {
  44. for (const relative of relativePath) {
  45. const targetPath = this.resolveAbsolutePath(localStoragePrefix, relative);
  46. await unlink(targetPath).catch((err) => {
  47. if (err.code !== 'ENOENT') {
  48. throw err;
  49. }
  50. });
  51. }
  52. }
  53. }