From dc32a3a3d738dd6c69c63f7c220deb3de16a6859 Mon Sep 17 00:00:00 2001 From: Kyle Bolen Date: Thu, 30 Jul 2026 12:48:15 +0000 Subject: [PATCH] Fix EXIF verification for non-Apple devices + sticky preserve folder EXIF verification: any non-blank Make is now considered verified. Only absent/empty Make triggers 'Origin unknown' warning. Samsung, Google, etc. are all legitimate camera makes. Also cache exifMake to avoid calling exiftool twice per file. Preserve folder structure: add defaultPreserveFolderStructure to AppConfig. Toggling in ImportScreen saves it as the new default. prepareImport() reads from config instead of always resetting to true. --- .../kotlin/com/bolenpad/photophetch/model/AppConfig.kt | 2 ++ .../kotlin/com/bolenpad/photophetch/transfer/Copier.kt | 9 ++++----- src/main/kotlin/com/bolenpad/photophetch/ui/AppState.kt | 9 +++++++-- .../kotlin/com/bolenpad/photophetch/ui/ImportScreen.kt | 5 ++++- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/bolenpad/photophetch/model/AppConfig.kt b/src/main/kotlin/com/bolenpad/photophetch/model/AppConfig.kt index 381750f..8345ac1 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/model/AppConfig.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/model/AppConfig.kt @@ -17,6 +17,8 @@ data class AppConfig( val defaultStripLivePhotoMotion: Boolean = true, /** Default: do not delete from phone (safer default) */ val defaultDeleteAfterVerify: Boolean = false, + /** Default: preserve folder structure on import */ + val defaultPreserveFolderStructure: Boolean = true, /** Whether the user has been warned about HEIC phone settings */ val heicWarningDismissed: Boolean = false, /** Path to adb binary, or null to use PATH */ diff --git a/src/main/kotlin/com/bolenpad/photophetch/transfer/Copier.kt b/src/main/kotlin/com/bolenpad/photophetch/transfer/Copier.kt index 5680242..d52acb1 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/transfer/Copier.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/transfer/Copier.kt @@ -154,6 +154,8 @@ class Copier( } } + val exifMake = readExifMake(finalFile) + CopyResult( photo = photo, destinationPath = finalFile, @@ -161,11 +163,8 @@ class Copier( destSha256 = destHash, verified = verified, convertedFromHeic = wasConverted, - exifVerified = readExifMake(finalFile)?.let { make -> - make.contains("apple", ignoreCase = true) || - make.contains(photo.exifMake ?: "apple", ignoreCase = true) - }, - exifMake = readExifMake(finalFile), + exifVerified = exifMake?.let { it.isNotBlank() }, + exifMake = exifMake, error = when { !verified && remoteHash != null -> "Hash mismatch — file may be corrupt" !verified -> "Copy failed — file missing or size mismatch" diff --git a/src/main/kotlin/com/bolenpad/photophetch/ui/AppState.kt b/src/main/kotlin/com/bolenpad/photophetch/ui/AppState.kt index 0d8a5cb..4daab70 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/ui/AppState.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/ui/AppState.kt @@ -371,11 +371,11 @@ class AppState(private val configStore: ConfigStore) { StagingPath.uniqueFolderName(Paths.get(cfg.stagingRoot), baseName) } else baseName - // Multi-folder selection → preserve structure by default + // Multi-folder selection → preserve structure; otherwise use saved config default val multipleSourceFolders = _selectedPhotos.value.map { it.sourceFolder }.distinct().size > 1 _importConfig.update { it.copy( folderName = folderName, - preserveFolderStructure = multipleSourceFolders, + preserveFolderStructure = if (multipleSourceFolders) true else cfg.defaultPreserveFolderStructure, ) } navigateTo(Screen.IMPORT_CONFIG) } @@ -469,6 +469,11 @@ class AppState(private val configStore: ConfigStore) { saveConfig() } + fun updateDefaultPreserveFolderStructure(value: Boolean) { + _config.update { it.copy(defaultPreserveFolderStructure = value) } + saveConfig() + } + fun dismissHeicWarning() { _config.update { it.copy(heicWarningDismissed = true) } saveConfig() diff --git a/src/main/kotlin/com/bolenpad/photophetch/ui/ImportScreen.kt b/src/main/kotlin/com/bolenpad/photophetch/ui/ImportScreen.kt index 0af4962..accc3b0 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/ui/ImportScreen.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/ui/ImportScreen.kt @@ -154,7 +154,10 @@ fun ImportScreen(state: AppState) { label = "Preserve folder structure", description = "Files go into subfolders matching their source (Camera/, Screenshots/, etc.).", checked = importConfig.preserveFolderStructure, - onCheckedChange = { state.updateImportConfig { copy(preserveFolderStructure = it) } }, + onCheckedChange = { + state.updateImportConfig { copy(preserveFolderStructure = it) } + state.updateDefaultPreserveFolderStructure(it) + }, ) ToggleRow( label = "Delete from phone after verification",