diff --git a/src/main/kotlin/com/bolenpad/photophetch/transfer/Copier.kt b/src/main/kotlin/com/bolenpad/photophetch/transfer/Copier.kt index e90f922..5c1ae72 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/transfer/Copier.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/transfer/Copier.kt @@ -63,14 +63,15 @@ class Copier( photosToImport.forEachIndexed { idx, photo -> onProgress(idx, photosToImport.size, photo.filename) - // Determine destination directory — flat or preserve subfolder + // Determine destination directory val destDir = if (job.preserveFolderStructure) { - // devicePath is like /sdcard/DCIM/Camera/IMG_001.jpg - // Extract the subfolder name relative to DCIM root - val subFolder = photo.devicePath - .substringAfter("/DCIM/", "") - .substringBeforeLast("/", "") - .ifBlank { "Camera" } + // Always use sourceFolder regardless of how many folders are in the job + val subFolder = photo.sourceFolder.ifBlank { + photo.devicePath + .substringAfter("/DCIM/", "") + .substringBeforeLast("/", "") + .ifBlank { "Camera" } + } stagingDir.resolve(subFolder).also { it.toFile().mkdirs() } } else { stagingDir @@ -113,26 +114,34 @@ class Copier( pulledFile to false } - // Step 3: Hash the destination file + // Step 3: Hash the destination file (always — gives us a local checksum on record) val destHash = sha256(finalFile.inputStream()) + val destSize = finalFile.toFile().length() // Step 4: Get remote hash for cross-check (Android only; iOS returns null) val remoteHash = connector.remoteChecksum(device, photo) - // Verification: if we have a remote hash, compare; otherwise mark verified - // (the copy itself is the verification for iOS since we have no remote hash) - val verified = when { + val verified: Boolean + val verificationNote: String? + + when { wasConverted -> { - // HEIC was converted — we can't compare against original hash. - // We accept the file if it exists and is non-zero. - finalFile.exists() && finalFile.toFile().length() > 0 + // HEIC converted — can't compare hashes, check file is non-empty + verified = destSize > 0 + verificationNote = if (verified) "Converted HEIC→JPEG" else null } remoteHash != null -> { - remoteHash.equals(destHash, ignoreCase = true) + // Full hash comparison + verified = remoteHash.equals(destHash, ignoreCase = true) + verificationNote = null } else -> { - // No remote hash — verify that file is non-empty and readable - finalFile.exists() && finalFile.toFile().length() > 0 + // No remote hash — verify size matches what ADB reported + // (sizeBytes from ls -la, so we know what the device said) + val sizeMatch = photo.sizeBytes == 0L || // unknown size is OK + destSize == photo.sizeBytes + verified = destSize > 0 && sizeMatch + verificationNote = if (verified) "Size verified (${destSize / 1024} KB)" else null } } @@ -145,7 +154,7 @@ class Copier( convertedFromHeic = wasConverted, error = when { !verified && remoteHash != null -> "Hash mismatch — file may be corrupt" - !verified -> "Copy failed — file missing or empty" + !verified -> "Copy failed — file missing or size mismatch" else -> null }, ) diff --git a/src/main/kotlin/com/bolenpad/photophetch/ui/VerifyScreen.kt b/src/main/kotlin/com/bolenpad/photophetch/ui/VerifyScreen.kt index 444fad6..b020ba5 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/ui/VerifyScreen.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/ui/VerifyScreen.kt @@ -256,7 +256,7 @@ private fun CopyResultRow(result: CopyResult) { when { isFailed -> result.error ?: "Failed" isVerifiedWithHash -> "SHA-256 verified · ${result.photo.sizeBytes / 1024} KB" - else -> "Copied · ${result.photo.sizeBytes / 1024} KB (no remote checksum)" + else -> "Copied · ${result.photo.sizeBytes / 1024} KB · local SHA-256: ${result.destSha256.take(12)}…" }, style = MaterialTheme.typography.labelSmall, color = when {