diff --git a/src/services/updateCreationTimeWithExif.ts b/src/services/updateCreationTimeWithExif.ts new file mode 100644 index 000000000..f0c6b169d --- /dev/null +++ b/src/services/updateCreationTimeWithExif.ts @@ -0,0 +1,82 @@ +import { SetProgressTracker } from 'components/FixLargeThumbnail'; +import { + changeFileCreationTime, + updateExistingFilePubMetadata, +} from 'utils/file'; +import { logError } from 'utils/sentry'; +import localForage from 'utils/storage/localForage'; +import downloadManager from './downloadManager'; +import { getLocalFiles, File, updatePublicMagicMetadata } from './fileService'; +import { getLocalTrash, getTrashedFiles } from './trashService'; +import { getExifDataFromURL } from './upload/exifService'; + +const CREATION_TIME_UPDATED_FILES_TABLE = 'creation-time-updated-file-table'; + +export async function getCreationTimeUpdatedFiles() { + return ( + (await localForage.getItem( + CREATION_TIME_UPDATED_FILES_TABLE + )) ?? [] + ); +} + +export async function setCreationTimeUpdatedFiles(creationTimeUpdatedFiles) { + return await localForage.setItem( + CREATION_TIME_UPDATED_FILES_TABLE, + creationTimeUpdatedFiles + ); +} + +export async function getFilesPendingCreationTimeUpdate() { + const files = await getLocalFiles(); + const trash = await getLocalTrash(); + const trashFiles = getTrashedFiles(trash); + const allFiles = [...files, ...trashFiles]; + const updateFiles = new Set(await getCreationTimeUpdatedFiles()); + + const pendingFiles = allFiles.filter((file) => !updateFiles.has(file.id)); + return pendingFiles; +} + +export async function updateCreationTimeWithExif( + filesToBeUpdated: File[], + setProgressTracker: SetProgressTracker +) { + let completedWithError = false; + try { + if (filesToBeUpdated.length === 0) { + return completedWithError; + } + const updatedFiles = await getCreationTimeUpdatedFiles(); + setProgressTracker({ current: 0, total: filesToBeUpdated.length }); + for (const [index, file] of filesToBeUpdated.entries()) { + try { + const fileURL = await downloadManager.getFile(file); + const exifData = await getExifDataFromURL(fileURL); + if (exifData?.creationTime) { + let updatedFile = await changeFileCreationTime( + file, + exifData.creationTime + ); + updatedFile = ( + await updatePublicMagicMetadata([updatedFile]) + )[0]; + updateExistingFilePubMetadata(file, updatedFile); + } + setProgressTracker({ + current: index, + total: filesToBeUpdated.length, + }); + updatedFiles.push(file.id); + setCreationTimeUpdatedFiles(updatedFiles); + } catch (e) { + logError(e, 'failed to updated a CreationTime With Exif'); + completedWithError = true; + } + } + } catch (e) { + logError(e, 'update CreationTime With Exif failed'); + completedWithError = true; + } + return completedWithError; +} diff --git a/src/services/upload/exifService.ts b/src/services/upload/exifService.ts index cacd81f2c..447b727ef 100644 --- a/src/services/upload/exifService.ts +++ b/src/services/upload/exifService.ts @@ -36,6 +36,23 @@ export async function getExifData( } } +export async function getExifDataFromURL(url: string): Promise { + try { + const exifData = await exifr.parse(url, EXIF_TAGS_NEEDED); + if (!exifData) { + return { location: NULL_LOCATION, creationTime: null }; + } + const parsedEXIFData = { + location: getEXIFLocation(exifData), + creationTime: getUNIXTime(exifData), + }; + return parsedEXIFData; + } catch (e) { + logError(e, 'error reading exif data'); + // ignore exif parsing errors + } +} + function getUNIXTime(exifData: any) { const dateTime: Date = exifData.DateTimeOriginal ?? exifData.CreateDate ?? exifData.ModifyDate;