mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:25:32 -04:00
refactor: dedicated query for asset migration job (#17631)
This commit is contained in:
parent
26f0ea4cb5
commit
21becbf1b0
@ -1,7 +1,7 @@
|
|||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { dirname, join, resolve } from 'node:path';
|
import { dirname, join, resolve } from 'node:path';
|
||||||
import { APP_MEDIA_LOCATION } from 'src/constants';
|
import { APP_MEDIA_LOCATION } from 'src/constants';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { StorageAsset } from 'src/database';
|
||||||
import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum';
|
import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum';
|
||||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
@ -28,6 +28,8 @@ export interface MoveRequest {
|
|||||||
export type GeneratedImageType = AssetPathType.PREVIEW | AssetPathType.THUMBNAIL | AssetPathType.FULLSIZE;
|
export type GeneratedImageType = AssetPathType.PREVIEW | AssetPathType.THUMBNAIL | AssetPathType.FULLSIZE;
|
||||||
export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDEO;
|
export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDEO;
|
||||||
|
|
||||||
|
type ThumbnailPathEntity = { id: string; ownerId: string };
|
||||||
|
|
||||||
let instance: StorageCore | null;
|
let instance: StorageCore | null;
|
||||||
|
|
||||||
export class StorageCore {
|
export class StorageCore {
|
||||||
@ -84,19 +86,19 @@ export class StorageCore {
|
|||||||
return join(APP_MEDIA_LOCATION, folder);
|
return join(APP_MEDIA_LOCATION, folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getPersonThumbnailPath(person: { id: string; ownerId: string }) {
|
static getPersonThumbnailPath(person: ThumbnailPathEntity) {
|
||||||
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`);
|
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getImagePath(asset: AssetEntity, type: GeneratedImageType, format: ImageFormat) {
|
static getImagePath(asset: ThumbnailPathEntity, type: GeneratedImageType, format: ImageFormat) {
|
||||||
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}-${type}.${format}`);
|
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}-${type}.${format}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getEncodedVideoPath(asset: AssetEntity) {
|
static getEncodedVideoPath(asset: ThumbnailPathEntity) {
|
||||||
return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${asset.id}.mp4`);
|
return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${asset.id}.mp4`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getAndroidMotionPath(asset: AssetEntity, uuid: string) {
|
static getAndroidMotionPath(asset: ThumbnailPathEntity, uuid: string) {
|
||||||
return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${uuid}-MP.mp4`);
|
return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${uuid}-MP.mp4`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +116,7 @@ export class StorageCore {
|
|||||||
return normalizedPath.startsWith(normalizedAppMediaLocation);
|
return normalizedPath.startsWith(normalizedAppMediaLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveAssetImage(asset: AssetEntity, pathType: GeneratedImageType, format: ImageFormat) {
|
async moveAssetImage(asset: StorageAsset, pathType: GeneratedImageType, format: ImageFormat) {
|
||||||
const { id: entityId, files } = asset;
|
const { id: entityId, files } = asset;
|
||||||
const oldFile = getAssetFile(files, pathType);
|
const oldFile = getAssetFile(files, pathType);
|
||||||
return this.moveFile({
|
return this.moveFile({
|
||||||
@ -125,7 +127,7 @@ export class StorageCore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveAssetVideo(asset: AssetEntity) {
|
async moveAssetVideo(asset: StorageAsset) {
|
||||||
return this.moveFile({
|
return this.moveFile({
|
||||||
entityId: asset.id,
|
entityId: asset.id,
|
||||||
pathType: AssetPathType.ENCODED_VIDEO,
|
pathType: AssetPathType.ENCODED_VIDEO,
|
||||||
|
@ -121,6 +121,13 @@ export type UserAdmin = User & {
|
|||||||
metadata: UserMetadataItem[];
|
metadata: UserMetadataItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StorageAsset = {
|
||||||
|
id: string;
|
||||||
|
ownerId: string;
|
||||||
|
files: AssetFile[];
|
||||||
|
encodedVideoPath: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type Asset = {
|
export type Asset = {
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
@ -90,6 +90,31 @@ where
|
|||||||
or "assets"."thumbhash" is null
|
or "assets"."thumbhash" is null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- AssetJobRepository.getForMigrationJob
|
||||||
|
select
|
||||||
|
"assets"."id",
|
||||||
|
"assets"."ownerId",
|
||||||
|
"assets"."encodedVideoPath",
|
||||||
|
(
|
||||||
|
select
|
||||||
|
coalesce(json_agg(agg), '[]')
|
||||||
|
from
|
||||||
|
(
|
||||||
|
select
|
||||||
|
"asset_files"."id",
|
||||||
|
"asset_files"."path",
|
||||||
|
"asset_files"."type"
|
||||||
|
from
|
||||||
|
"asset_files"
|
||||||
|
where
|
||||||
|
"asset_files"."assetId" = "assets"."id"
|
||||||
|
) as agg
|
||||||
|
) as "files"
|
||||||
|
from
|
||||||
|
"assets"
|
||||||
|
where
|
||||||
|
"assets"."id" = $1
|
||||||
|
|
||||||
-- AssetJobRepository.getForStorageTemplateJob
|
-- AssetJobRepository.getForStorageTemplateJob
|
||||||
select
|
select
|
||||||
"assets"."id",
|
"assets"."id",
|
||||||
|
@ -77,6 +77,16 @@ export class AssetJobRepository {
|
|||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
|
getForMigrationJob(id: string) {
|
||||||
|
return this.db
|
||||||
|
.selectFrom('assets')
|
||||||
|
.select(['assets.id', 'assets.ownerId', 'assets.encodedVideoPath'])
|
||||||
|
.select(withFiles)
|
||||||
|
.where('assets.id', '=', id)
|
||||||
|
.executeTakeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
private storageTemplateAssetQuery() {
|
private storageTemplateAssetQuery() {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('assets')
|
.selectFrom('assets')
|
||||||
|
@ -191,13 +191,14 @@ describe(MediaService.name, () => {
|
|||||||
|
|
||||||
describe('handleAssetMigration', () => {
|
describe('handleAssetMigration', () => {
|
||||||
it('should fail if asset does not exist', async () => {
|
it('should fail if asset does not exist', async () => {
|
||||||
|
mocks.assetJob.getForMigrationJob.mockResolvedValue(void 0);
|
||||||
await expect(sut.handleAssetMigration({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
|
await expect(sut.handleAssetMigration({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
|
||||||
|
|
||||||
expect(mocks.move.getByEntity).not.toHaveBeenCalled();
|
expect(mocks.move.getByEntity).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should move asset files', async () => {
|
it('should move asset files', async () => {
|
||||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
mocks.assetJob.getForMigrationJob.mockResolvedValue(assetStub.image);
|
||||||
mocks.move.create.mockResolvedValue({
|
mocks.move.create.mockResolvedValue({
|
||||||
entityId: assetStub.image.id,
|
entityId: assetStub.image.id,
|
||||||
id: 'move-id',
|
id: 'move-id',
|
||||||
|
@ -121,7 +121,7 @@ export class MediaService extends BaseService {
|
|||||||
@OnJob({ name: JobName.MIGRATE_ASSET, queue: QueueName.MIGRATION })
|
@OnJob({ name: JobName.MIGRATE_ASSET, queue: QueueName.MIGRATION })
|
||||||
async handleAssetMigration({ id }: JobOf<JobName.MIGRATE_ASSET>): Promise<JobStatus> {
|
async handleAssetMigration({ id }: JobOf<JobName.MIGRATE_ASSET>): Promise<JobStatus> {
|
||||||
const { image } = await this.getConfig({ withCache: true });
|
const { image } = await this.getConfig({ withCache: true });
|
||||||
const [asset] = await this.assetRepository.getByIds([id], { files: true });
|
const asset = await this.assetJobRepository.getForMigrationJob(id);
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
return JobStatus.FAILED;
|
return JobStatus.FAILED;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user