0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-02-11 01:18:24 -05:00

Merge branch 'feat/nullable-dates' of https://github.com/immich-app/immich into feat/inline-offline-check

This commit is contained in:
Jonathan Jogenfors 2025-02-08 01:08:31 +01:00
commit 92177576c1
5 changed files with 21 additions and 62 deletions

View file

@ -542,7 +542,6 @@ describe(AssetMediaService.name, () => {
it('should throw an error if the requested preview file does not exist', async () => { it('should throw an error if the requested preview file does not exist', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
assetMock.getById.mockResolvedValue({ assetMock.getById.mockResolvedValue({
...assetStub.image, ...assetStub.image,
files: [ files: [
@ -563,7 +562,6 @@ describe(AssetMediaService.name, () => {
it('should fall back to preview if the requested thumbnail file does not exist', async () => { it('should fall back to preview if the requested thumbnail file does not exist', async () => {
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id])); accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
assetMock.getById.mockResolvedValue({ assetMock.getById.mockResolvedValue({
...assetStub.image, ...assetStub.image,
files: [ files: [

View file

@ -574,8 +574,7 @@ export class LibraryService extends BaseService {
} }
const mtime = stat.mtime; const mtime = stat.mtime;
const isTimeUpdated = !asset.fileModifiedAt || mtime.toISOString() !== asset.fileModifiedAt.toISOString();
const isTimeUpdated = asset.fileModifiedAt === null || mtime.toISOString() !== asset.fileModifiedAt.toISOString();
if (isTimeUpdated) { if (isTimeUpdated) {
this.logger.verbose( this.logger.verbose(

View file

@ -1,4 +1,4 @@
import { BadRequestException, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ContainerDirectoryItem, ExifDateTime, Maybe, Tags } from 'exiftool-vendored'; import { ContainerDirectoryItem, ExifDateTime, Maybe, Tags } from 'exiftool-vendored';
import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime'; import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime';
import { Insertable } from 'kysely'; import { Insertable } from 'kysely';
@ -588,17 +588,7 @@ export class MetadataService extends BaseService {
} }
} }
private getDates(asset: AssetEntity, exifTags: ImmichTags): AssetDatesDto { private getDates(asset: AssetEntity, exifTags: ImmichTags) {
// We first assert that fileCreatedAt and fileModifiedAt are not null since that should be set to a non-null value before calling this function
if (asset.fileCreatedAt === null) {
this.logger.warn(`Asset ${asset.id} has no file creation date`);
throw new BadRequestException(`Asset ${asset.id} has no file creation date`);
}
if (asset.fileModifiedAt === null) {
this.logger.warn(`Asset ${asset.id} has no file modification date`);
throw new BadRequestException(`Asset ${asset.id} has no file modification date`);
}
const dateTime = firstDateTime(exifTags as Maybe<Tags>, EXIF_DATE_TAGS); const dateTime = firstDateTime(exifTags as Maybe<Tags>, EXIF_DATE_TAGS);
this.logger.verbose(`Asset ${asset.id} date time is ${dateTime}`); this.logger.verbose(`Asset ${asset.id} date time is ${dateTime}`);
@ -630,11 +620,7 @@ export class MetadataService extends BaseService {
localDateTime = earliestDate; localDateTime = earliestDate;
} }
if (localDateTime) { this.logger.verbose(`Asset ${asset.id} has a local time of ${localDateTime.toISOString()}`);
this.logger.verbose(`Asset ${asset.id} has a local time of ${localDateTime.toISOString()}`);
} else {
this.logger.verbose(`Asset ${asset.id} has no time set`);
}
let modifyDate = asset.fileModifiedAt; let modifyDate = asset.fileModifiedAt;
try { try {

View file

@ -310,12 +310,6 @@ export class StorageTemplateService extends BaseService {
const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const zone = asset.exifInfo?.timeZone || systemTimeZone; const zone = asset.exifInfo?.timeZone || systemTimeZone;
if (!asset.fileCreatedAt) {
this.logger.log(`Asset ${asset.id} is missing fileCreatedAt, skipping storage template migration`);
throw new Error(`Missing fileCreatedAt for asset ${asset.id}`);
}
const dt = DateTime.fromJSDate(asset.fileCreatedAt, { zone }); const dt = DateTime.fromJSDate(asset.fileCreatedAt, { zone });
for (const token of Object.values(storageTokens).flat()) { for (const token of Object.values(storageTokens).flat()) {

View file

@ -29,8 +29,6 @@ type TimeZoneTest = {
description: string; description: string;
serverTimeZone?: string; serverTimeZone?: string;
exifData: Record<string, any>; exifData: Record<string, any>;
fileCreatedAt: Date;
fileModifiedAt: Date;
expected: { expected: {
localDateTime: string; localDateTime: string;
dateTimeOriginal: string; dateTimeOriginal: string;
@ -60,8 +58,6 @@ describe(MetadataService.name, () => {
const timeZoneTests: TimeZoneTest[] = [ const timeZoneTests: TimeZoneTest[] = [
{ {
description: 'should handle no time zone information', description: 'should handle no time zone information',
fileCreatedAt: new Date('2022-01-01T00:00:00.000Z'),
fileModifiedAt: new Date('2022-01-01T00:00:00.000Z'),
exifData: { exifData: {
DateTimeOriginal: '2022:01:01 00:00:00', DateTimeOriginal: '2022:01:01 00:00:00',
}, },
@ -73,8 +69,6 @@ describe(MetadataService.name, () => {
}, },
{ {
description: 'should handle no time zone information and server behind UTC', description: 'should handle no time zone information and server behind UTC',
fileCreatedAt: new Date('2022-01-01T00:00:00.000Z'),
fileModifiedAt: new Date('2022-01-01T00:00:00.000Z'),
serverTimeZone: 'America/Los_Angeles', serverTimeZone: 'America/Los_Angeles',
exifData: { exifData: {
DateTimeOriginal: '2022:01:01 00:00:00', DateTimeOriginal: '2022:01:01 00:00:00',
@ -87,8 +81,6 @@ describe(MetadataService.name, () => {
}, },
{ {
description: 'should handle no time zone information and server ahead of UTC', description: 'should handle no time zone information and server ahead of UTC',
fileCreatedAt: new Date('2022-01-01T00:00:00.000Z'),
fileModifiedAt: new Date('2022-01-01T00:00:00.000Z'),
serverTimeZone: 'Europe/Brussels', serverTimeZone: 'Europe/Brussels',
exifData: { exifData: {
DateTimeOriginal: '2022:01:01 00:00:00', DateTimeOriginal: '2022:01:01 00:00:00',
@ -101,8 +93,6 @@ describe(MetadataService.name, () => {
}, },
{ {
description: 'should handle no time zone information and server ahead of UTC in the summer', description: 'should handle no time zone information and server ahead of UTC in the summer',
fileCreatedAt: new Date('2022-01-01T00:00:00.000Z'),
fileModifiedAt: new Date('2022-01-01T00:00:00.000Z'),
serverTimeZone: 'Europe/Brussels', serverTimeZone: 'Europe/Brussels',
exifData: { exifData: {
DateTimeOriginal: '2022:06:01 00:00:00', DateTimeOriginal: '2022:06:01 00:00:00',
@ -115,8 +105,6 @@ describe(MetadataService.name, () => {
}, },
{ {
description: 'should handle a +13:00 time zone', description: 'should handle a +13:00 time zone',
fileCreatedAt: new Date('2022-01-01T00:00:00.000Z'),
fileModifiedAt: new Date('2022-01-01T00:00:00.000Z'),
exifData: { exifData: {
DateTimeOriginal: '2022:01:01 00:00:00+13:00', DateTimeOriginal: '2022:01:01 00:00:00+13:00',
}, },
@ -128,32 +116,26 @@ describe(MetadataService.name, () => {
}, },
]; ];
it.each(timeZoneTests)( it.each(timeZoneTests)('$description', async ({ exifData, serverTimeZone, expected }) => {
'$description', process.env.TZ = serverTimeZone ?? undefined;
async ({ exifData, serverTimeZone, expected, fileCreatedAt, fileModifiedAt }) => {
// TODO: the TZ environment variable is no longer used, remove it
process.env.TZ = serverTimeZone ?? undefined;
const { filePath } = await createTestFile(exifData); const { filePath } = await createTestFile(exifData);
assetMock.getByIds.mockResolvedValue([ assetMock.getByIds.mockResolvedValue([{ id: 'asset-1', originalPath: filePath } as AssetEntity]);
{ id: 'asset-1', originalPath: filePath, fileCreatedAt, fileModifiedAt } as AssetEntity,
]);
await sut.handleMetadataExtraction({ id: 'asset-1' }); await sut.handleMetadataExtraction({ id: 'asset-1' });
expect(assetMock.upsertExif).toHaveBeenCalledWith( expect(assetMock.upsertExif).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
dateTimeOriginal: new Date(expected.dateTimeOriginal), dateTimeOriginal: new Date(expected.dateTimeOriginal),
timeZone: expected.timeZone, timeZone: expected.timeZone,
}), }),
); );
expect(assetMock.update).toHaveBeenCalledWith( expect(assetMock.update).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
localDateTime: new Date(expected.localDateTime), localDateTime: new Date(expected.localDateTime),
}), }),
); );
}, });
);
}); });
}); });