mirror of
https://github.com/immich-app/immich.git
synced 2026-03-22 08:49:57 +03:00
fix(server): sync files to disk
Ensure that all files are flushed after they've been written. At current, files are not explicitly flushed to disk, which can cause data corruption. In extreme circumstances, it's possible that uploaded files may not ever be persisted at all.
This commit is contained in:
@@ -10,6 +10,7 @@ import { UploadFieldName } from 'src/dtos/asset-media.dto';
|
||||
import { RouteKey } from 'src/enum';
|
||||
import { AuthRequest } from 'src/middleware/auth.guard';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||
import { AssetMediaService } from 'src/services/asset-media.service';
|
||||
import { ImmichFile, UploadFile, UploadFiles } from 'src/types';
|
||||
import { asUploadRequest, mapToUploadFile } from 'src/utils/asset.util';
|
||||
@@ -54,6 +55,7 @@ export class FileUploadInterceptor implements NestInterceptor {
|
||||
constructor(
|
||||
private reflect: Reflector,
|
||||
private assetService: AssetMediaService,
|
||||
private storageRepository: StorageRepository,
|
||||
private logger: LoggingRepository,
|
||||
) {
|
||||
this.logger.setContext(FileUploadInterceptor.name);
|
||||
@@ -125,7 +127,18 @@ export class FileUploadInterceptor implements NestInterceptor {
|
||||
});
|
||||
|
||||
if (!this.isAssetUploadFile(file)) {
|
||||
this.defaultStorage._handleFile(request, file, callback);
|
||||
this.defaultStorage._handleFile(request, file, (error, info) => {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
// Multer does not sync files to disk after writing.
|
||||
//
|
||||
// TODO: use `flush: true` in multer when available: https://github.com/expressjs/multer/issues/1381
|
||||
this.storageRepository
|
||||
.datasync(info!.path!)
|
||||
.then(() => callback(null, info!))
|
||||
.catch((error) => callback(error));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,7 +149,13 @@ export class FileUploadInterceptor implements NestInterceptor {
|
||||
hash.destroy();
|
||||
callback(error);
|
||||
} else {
|
||||
callback(null, { ...info, checksum: hash.digest() });
|
||||
this.storageRepository
|
||||
.datasync(info!.path!)
|
||||
.then(() => callback(null, { ...info, checksum: hash.digest() }))
|
||||
.catch((error) => {
|
||||
hash.destroy();
|
||||
callback(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,6 +54,15 @@ export class StorageRepository {
|
||||
return fs.copyFile(source, target);
|
||||
}
|
||||
|
||||
async datasync(filepath: string) {
|
||||
const handle = await fs.open(filepath, 'r');
|
||||
try {
|
||||
await handle.datasync();
|
||||
} finally {
|
||||
await handle.close();
|
||||
}
|
||||
}
|
||||
|
||||
stat(filepath: string) {
|
||||
return fs.stat(filepath);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ export const newStorageRepositoryMock = (): Mocked<RepositoryInterface<StorageRe
|
||||
walk: vitest.fn().mockImplementation(async function* () {}),
|
||||
rename: vitest.fn(),
|
||||
copyFile: vitest.fn(),
|
||||
datasync: vitest.fn(),
|
||||
utimes: vitest.fn(),
|
||||
watch: vitest.fn().mockImplementation(makeMockWatcher({})),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user