diff --git a/src/main/kotlin/com/bolenpad/photophetch/device/AndroidConnector.kt b/src/main/kotlin/com/bolenpad/photophetch/device/AndroidConnector.kt index a7b0a09..e340bdc 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/device/AndroidConnector.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/device/AndroidConnector.kt @@ -90,32 +90,34 @@ class AndroidConnector( val check = runAdb(serial, "shell", "ls", dir, "2>/dev/null") if (check.contains("No such file") || check.isBlank()) continue - val statOutput = runAdb( + // Use `find` to get filenames, then `ls -la` for size/date. + // Avoids relying on `stat` which has inconsistent flag support across + // Android versions (Samsung/toybox vs GNU coreutils). + val findOutput = runAdb( serial, "shell", - "find", dir, "-maxdepth", "1", "-type", "f", - "-exec", "stat", "-c", "%s %Y %n", "{}", ";" + "find", dir, "-maxdepth", "1", "-type", "f" ) - for (line in statOutput.lines()) { - val trimmed = line.trim() - if (trimmed.isBlank()) continue - val parts = trimmed.split(" ", limit = 3) - if (parts.size < 3) continue + for (line in findOutput.lines()) { + val path = line.trim() + if (path.isBlank() || path.contains("No such file")) continue - val sizeBytes = parts[0].toLongOrNull() ?: continue - val epochSecs = parts[1].toLongOrNull() ?: continue - val path = parts[2] val filename = path.substringAfterLast("/") val ext = filename.substringAfterLast(".", "").lowercase() - if (ext !in mediaExtensions) continue + // Get size and modification time via ls -la on the specific file + val lsLine = runAdb(serial, "shell", "ls", "-la", path).trim() + // ls -la format: -rw-rw---- 1 root everybody 3821234 2026-07-15 14:22 /sdcard/... + val sizeBytes = parseLsSize(lsLine) ?: 0L + val dateTaken = parseLsDate(lsLine) ?: java.time.Instant.EPOCH + photos.add( PhonePhoto( devicePath = path, filename = filename, sizeBytes = sizeBytes, - dateTaken = Instant.ofEpochSecond(epochSecs), + dateTaken = dateTaken, exifMake = device.displayName, exifModel = device.displayName, mediaType = if (ext in setOf("mp4", "mov", "3gp", "avi")) @@ -173,6 +175,36 @@ class AndroidConnector( private fun requireSerial(device: DeviceInfo): String = device.adbSerial ?: throw DeviceException("No ADB serial for device: ${device.displayName}") + /** + * Parse file size from `ls -la` output line. + * Format: -rw-rw---- 1 root everybody 3821234 2026-07-15 14:22 filename + * Size is the 5th whitespace-separated token. + */ + private fun parseLsSize(lsLine: String): Long? { + val parts = lsLine.trim().split(Regex("\\s+")) + // Size is at index 4 (0-based): permissions, links, user, group, SIZE, date, time, name + return parts.getOrNull(4)?.toLongOrNull() + } + + /** + * Parse modification date from `ls -la` output line. + * Date token is at index 5 (yyyy-MM-dd) and time at index 6 (HH:mm). + */ + private fun parseLsDate(lsLine: String): java.time.Instant? { + return try { + val parts = lsLine.trim().split(Regex("\\s+")) + val datePart = parts.getOrNull(5) ?: return null + val timePart = parts.getOrNull(6) ?: "00:00" + val ldt = java.time.LocalDateTime.parse( + "$datePart $timePart", + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + ) + ldt.atZone(java.time.ZoneId.systemDefault()).toInstant() + } catch (_: Exception) { + null + } + } + private fun runAdb(serial: String?, vararg args: String): String { val cmd = buildList { add(adbBin)