diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts index 420fedfc53e6f..ed9ac284fe130 100644 --- a/server/src/entities/asset.entity.ts +++ b/server/src/entities/asset.entity.ts @@ -399,5 +399,8 @@ export function searchAssetBuilder(kysely: Kysely, options: AssetSearchBuild ) .$if(!!options.withExif, withExifInner) .$if(!!(options.withFaces || options.withPeople || options.personIds), (qb) => qb.select(withFacesAndPeople)) - .$if(!options.withDeleted, (qb) => qb.where('assets.deletedAt', 'is', null)); + .$if(!options.withDeleted, (qb) => qb.where('assets.deletedAt', 'is', null)) + .$if(!options.withNullLocalDateTime, (qb) => qb.where('assets.localDateTime', 'is not', null)) + .$if(!options.withNullFileCreatedAt, (qb) => qb.where('assets.fileCreatedAt', 'is not', null)) + .$if(!options.withNullFileModifiedAt, (qb) => qb.where('assets.fileModifiedAt', 'is not', null)); } diff --git a/server/src/interfaces/search.interface.ts b/server/src/interfaces/search.interface.ts index bb76ff7b1fd04..d6a109e89a7cb 100644 --- a/server/src/interfaces/search.interface.ts +++ b/server/src/interfaces/search.interface.ts @@ -63,6 +63,9 @@ export interface SearchStatusOptions { status?: AssetStatus; withArchived?: boolean; withDeleted?: boolean; + withNullLocalDateTime?: boolean; + withNullFileCreatedAt?: boolean; + withNullFileModifiedAt?: boolean; } export interface SearchOneToOneRelationOptions { diff --git a/server/src/queries/asset.repository.sql b/server/src/queries/asset.repository.sql index cbed1817368d4..3431ce7540b67 100644 --- a/server/src/queries/asset.repository.sql +++ b/server/src/queries/asset.repository.sql @@ -263,9 +263,8 @@ with from "assets" where - "assets"."fileCreatedAt" <= $2 - and "assets"."deletedAt" is null - and "assets"."isVisible" = $3 + "assets"."deletedAt" is null + and "assets"."isVisible" = $2 and "assets"."fileCreatedAt" is not null and "assets"."fileModifiedAt" is not null and "assets"."localDateTime" is not null @@ -303,17 +302,16 @@ from "asset_stack"."id" ) as "stacked_assets" on "asset_stack"."id" is not null where - "assets"."fileCreatedAt" <= $2 - and ( + ( "asset_stack"."primaryAssetId" = "assets"."id" or "assets"."stackId" is null ) and "assets"."deletedAt" is null - and "assets"."isVisible" = $3 + and "assets"."isVisible" = $2 and "assets"."fileCreatedAt" is not null and "assets"."fileModifiedAt" is not null and "assets"."localDateTime" is not null - and date_trunc($4, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $5 + and date_trunc($3, "localDateTime" at time zone 'UTC') at time zone 'UTC' = $4 order by "assets"."localDateTime" desc diff --git a/server/src/repositories/asset.repository.ts b/server/src/repositories/asset.repository.ts index 6f7982b026c6b..713cbf9eac6b9 100644 --- a/server/src/repositories/asset.repository.ts +++ b/server/src/repositories/asset.repository.ts @@ -49,8 +49,6 @@ import { anyUuid, asUuid, mapUpsertColumns } from 'src/utils/database'; import { globToSqlPattern } from 'src/utils/misc'; import { Paginated, PaginationOptions, paginationHelper } from 'src/utils/pagination'; -const ASSET_CUTOFF_DATE = new Date('9000-01-01'); - @Injectable() export class AssetRepository implements IAssetRepository { constructor(@InjectKysely() private db: Kysely) {} @@ -607,7 +605,6 @@ export class AssetRepository implements IAssetRepository { return this.db .selectFrom('assets') .selectAll('assets') - .where('assets.fileCreatedAt', '<=', ASSET_CUTOFF_DATE) .$call(withExif) .where('ownerId', '=', anyUuid(userIds)) .where('isVisible', '=', true) @@ -626,7 +623,6 @@ export class AssetRepository implements IAssetRepository { .with('assets', (qb) => qb .selectFrom('assets') - .where('assets.fileCreatedAt', '<=', ASSET_CUTOFF_DATE) .select(truncatedDate(options.size).as('timeBucket')) .$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED)) .where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null) @@ -680,7 +676,6 @@ export class AssetRepository implements IAssetRepository { async getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise { return hasPeople(this.db, options.personId ? [options.personId] : undefined) .selectAll('assets') - .where('assets.fileCreatedAt', '<=', ASSET_CUTOFF_DATE) .$call(withExif) .$if(!!options.albumId, (qb) => withAlbums(qb, { albumId: options.albumId })) .$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!))) diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index 5eb341d377b18..9fde8305df8e8 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -25,8 +25,6 @@ import { mimeTypes } from 'src/utils/mime-types'; import { handlePromiseError } from 'src/utils/misc'; import { usePagination } from 'src/utils/pagination'; -const ASSET_IMPORT_DATE = new Date('9999-12-31'); - @Injectable() export class LibraryService extends BaseService { private watchLibraries = false; @@ -381,9 +379,9 @@ export class LibraryService extends BaseService { checksum: this.cryptoRepository.hashSha1(`path:${assetPath}`), originalPath: assetPath, - fileCreatedAt: ASSET_IMPORT_DATE, - fileModifiedAt: ASSET_IMPORT_DATE, - localDateTime: ASSET_IMPORT_DATE, + fileCreatedAt: null, + fileModifiedAt: null, + localDateTime: null, // TODO: device asset id is deprecated, remove it deviceAssetId: `${basename(assetPath)}`.replaceAll(/\s+/g, ''), deviceId: 'Library Import', diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index 2e448ef8f18c2..f3a8335d1f518 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -4,7 +4,6 @@ import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime'; import { Insertable } from 'kysely'; import _ from 'lodash'; import { Duration } from 'luxon'; -import { Stats } from 'node:fs'; import { constants } from 'node:fs/promises'; import path from 'node:path'; import { SystemConfig } from 'src/config'; @@ -172,22 +171,13 @@ export class MetadataService extends BaseService { asset.fileModifiedAt = stats.mtime; } - const { dateTimeOriginal, localDateTime, timeZone, modifyDate, fileCreatedAt, fileModifiedAt } = this.getDates( - asset, - exifTags, - stats, - ); + const { dateTimeOriginal, localDateTime, timeZone, modifyDate } = this.getDates(asset, exifTags); const { latitude, longitude, country, state, city } = await this.getGeo(exifTags, reverseGeocoding); const { width, height } = this.getImageDimensions(exifTags); - let fileCreatedAtDate = dateTimeOriginal; - let fileModifiedAtDate = modifyDate; - - if (asset.isExternal) { - fileCreatedAtDate = fileCreatedAt; - fileModifiedAtDate = fileModifiedAt; - } + const fileCreatedAtDate = dateTimeOriginal; + const fileModifiedAtDate = modifyDate; const exifData: Insertable = { assetId: asset.id, @@ -483,7 +473,7 @@ export class MetadataService extends BaseService { asset.fileModifiedAt = stat.mtime; } - const dates = this.getDates(asset, tags, stat); + const dates = this.getDates(asset, tags); motionAsset = await this.assetRepository.create({ id: motionAssetId, libraryId: asset.libraryId, @@ -601,7 +591,7 @@ export class MetadataService extends BaseService { } } - private getDates(asset: AssetEntity, exifTags: ImmichTags, stat: Stats): AssetDatesDto { + private getDates(asset: AssetEntity, exifTags: ImmichTags): AssetDatesDto { // 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`); @@ -629,16 +619,8 @@ export class MetadataService extends BaseService { this.logger.verbose(`Asset ${asset.id} has no time zone information`); } - let fileCreatedAt = asset.fileCreatedAt; - let fileModifiedAt = asset.fileModifiedAt; - - if (asset.isExternal) { - // With external assets we need to extract dates from the filesystem, this can't be done with uploades assets as that information is lost on upload - fileCreatedAt = stat.mtime; - fileModifiedAt = stat.mtime; - - this.logger.verbose(`External asset ${asset.id} has a file modification time of ${fileCreatedAt.toISOString()}`); - } + const fileCreatedAt = asset.fileCreatedAt; + const fileModifiedAt = asset.fileModifiedAt; let dateTimeOriginal = dateTime?.toDate(); let localDateTime = dateTime?.toDateTime().setZone('UTC', { keepLocalTime: true }).toJSDate();