fix: tag update race condition (#25371)

This commit is contained in:
Daniel Dietzler
2026-01-21 09:30:45 -06:00
committed by GitHub
parent 248cb86143
commit 184f1a6d32
13 changed files with 84 additions and 36 deletions
+19 -11
View File
@@ -254,6 +254,8 @@ export class MetadataService extends BaseService {
}
}
const tags = this.getTagList(exifTags);
const exifData: Insertable<AssetExifTable> = {
assetId: asset.id,
@@ -296,6 +298,8 @@ export class MetadataService extends BaseService {
// grouping
livePhotoCID: (exifTags.ContentIdentifier || exifTags.MediaGroupUUID) ?? null,
autoStackId: this.getAutoStackId(exifTags),
tags: tags.length > 0 ? tags : null,
};
const isSidewards = exifTags.Orientation && this.isOrientationSidewards(exifTags.Orientation);
@@ -316,9 +320,10 @@ export class MetadataService extends BaseService {
width: asset.width == null ? assetWidth : undefined,
height: asset.height == null ? assetHeight : undefined,
}),
this.applyTagList(asset, exifTags),
];
await this.applyTagList(asset);
if (this.isMotionPhoto(asset, exifTags)) {
promises.push(this.applyMotionPhotos(asset, exifTags, dates, stats));
}
@@ -405,35 +410,35 @@ export class MetadataService extends BaseService {
@OnEvent({ name: 'AssetTag' })
async handleTagAsset({ assetId }: ArgOf<'AssetTag'>) {
await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id: assetId, tags: true } });
await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id: assetId } });
}
@OnEvent({ name: 'AssetUntag' })
async handleUntagAsset({ assetId }: ArgOf<'AssetUntag'>) {
await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id: assetId, tags: true } });
await this.jobRepository.queue({ name: JobName.SidecarWrite, data: { id: assetId } });
}
@OnJob({ name: JobName.SidecarWrite, queue: QueueName.Sidecar })
async handleSidecarWrite(job: JobOf<JobName.SidecarWrite>): Promise<JobStatus> {
const { id, tags } = job;
const { id } = job;
const asset = await this.assetJobRepository.getForSidecarWriteJob(id);
if (!asset) {
return JobStatus.Failed;
}
const lockedProperties = await this.assetJobRepository.getLockedPropertiesForMetadataExtraction(id);
const tagsList = (asset.tags || []).map((tag) => tag.value);
const { sidecarFile } = getAssetFiles(asset.files);
const sidecarPath = sidecarFile?.path || `${asset.originalPath}.xmp`;
const { description, dateTimeOriginal, latitude, longitude, rating } = _.pick(
const { description, dateTimeOriginal, latitude, longitude, rating, tags } = _.pick(
{
description: asset.exifInfo.description,
dateTimeOriginal: asset.exifInfo.dateTimeOriginal,
latitude: asset.exifInfo.latitude,
longitude: asset.exifInfo.longitude,
rating: asset.exifInfo.rating,
tags: asset.exifInfo.tags,
},
lockedProperties,
);
@@ -446,7 +451,7 @@ export class MetadataService extends BaseService {
GPSLatitude: latitude,
GPSLongitude: longitude,
Rating: rating,
TagsList: tags ? tagsList : undefined,
TagsList: tags?.length ? tags : undefined,
},
_.isUndefined,
);
@@ -560,11 +565,14 @@ export class MetadataService extends BaseService {
return tags;
}
private async applyTagList(asset: { id: string; ownerId: string }, exifTags: ImmichTags) {
const tags = this.getTagList(exifTags);
const results = await upsertTags(this.tagRepository, { userId: asset.ownerId, tags });
private async applyTagList({ id, ownerId }: { id: string; ownerId: string }) {
const asset = await this.assetRepository.getById(id, { exifInfo: true });
const results = await upsertTags(this.tagRepository, {
userId: ownerId,
tags: asset?.exifInfo?.tags ?? [],
});
await this.tagRepository.replaceAssetTags(
asset.id,
id,
results.map((tag) => tag.id),
);
}