mirror of
https://github.com/immich-app/immich.git
synced 2026-03-22 13:29:21 +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 { RouteKey } from 'src/enum';
|
||||||
import { AuthRequest } from 'src/middleware/auth.guard';
|
import { AuthRequest } from 'src/middleware/auth.guard';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||||
import { AssetMediaService } from 'src/services/asset-media.service';
|
import { AssetMediaService } from 'src/services/asset-media.service';
|
||||||
import { ImmichFile, UploadFile, UploadFiles } from 'src/types';
|
import { ImmichFile, UploadFile, UploadFiles } from 'src/types';
|
||||||
import { asUploadRequest, mapToUploadFile } from 'src/utils/asset.util';
|
import { asUploadRequest, mapToUploadFile } from 'src/utils/asset.util';
|
||||||
@@ -54,6 +55,7 @@ export class FileUploadInterceptor implements NestInterceptor {
|
|||||||
constructor(
|
constructor(
|
||||||
private reflect: Reflector,
|
private reflect: Reflector,
|
||||||
private assetService: AssetMediaService,
|
private assetService: AssetMediaService,
|
||||||
|
private storageRepository: StorageRepository,
|
||||||
private logger: LoggingRepository,
|
private logger: LoggingRepository,
|
||||||
) {
|
) {
|
||||||
this.logger.setContext(FileUploadInterceptor.name);
|
this.logger.setContext(FileUploadInterceptor.name);
|
||||||
@@ -125,7 +127,18 @@ export class FileUploadInterceptor implements NestInterceptor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!this.isAssetUploadFile(file)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +149,13 @@ export class FileUploadInterceptor implements NestInterceptor {
|
|||||||
hash.destroy();
|
hash.destroy();
|
||||||
callback(error);
|
callback(error);
|
||||||
} else {
|
} 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);
|
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) {
|
stat(filepath: string) {
|
||||||
return fs.stat(filepath);
|
return fs.stat(filepath);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export const newStorageRepositoryMock = (): Mocked<RepositoryInterface<StorageRe
|
|||||||
walk: vitest.fn().mockImplementation(async function* () {}),
|
walk: vitest.fn().mockImplementation(async function* () {}),
|
||||||
rename: vitest.fn(),
|
rename: vitest.fn(),
|
||||||
copyFile: vitest.fn(),
|
copyFile: vitest.fn(),
|
||||||
|
datasync: vitest.fn(),
|
||||||
utimes: vitest.fn(),
|
utimes: vitest.fn(),
|
||||||
watch: vitest.fn().mockImplementation(makeMockWatcher({})),
|
watch: vitest.fn().mockImplementation(makeMockWatcher({})),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user