Fix folder structure and improve no-remote-hash verification
Copier: preserveFolderStructure now uses photo.sourceFolder directly (always correct) instead of parsing devicePath, and applies regardless of how many source folders are in the job. Verification without remote hash: compare file size against ADB-reported size (from ls -la) in addition to checking non-empty. Record local SHA-256 so there's always a checksum on file even without a remote one. VerifyScreen: show 'local SHA-256: abc123...' for copied-without-remote- hash files instead of '(no remote checksum)'.
This commit is contained in:
@@ -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
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user