Fix Android file listing: replace stat with ls -la for compatibility
Samsung (and other Android vendors using toybox) do not support GNU stat -c flag syntax. Switch to find for file discovery + ls -la per file for size/date metadata, which works on all Android versions.
This commit is contained in:
@@ -90,32 +90,34 @@ class AndroidConnector(
|
|||||||
val check = runAdb(serial, "shell", "ls", dir, "2>/dev/null")
|
val check = runAdb(serial, "shell", "ls", dir, "2>/dev/null")
|
||||||
if (check.contains("No such file") || check.isBlank()) continue
|
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",
|
serial, "shell",
|
||||||
"find", dir, "-maxdepth", "1", "-type", "f",
|
"find", dir, "-maxdepth", "1", "-type", "f"
|
||||||
"-exec", "stat", "-c", "%s %Y %n", "{}", ";"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for (line in statOutput.lines()) {
|
for (line in findOutput.lines()) {
|
||||||
val trimmed = line.trim()
|
val path = line.trim()
|
||||||
if (trimmed.isBlank()) continue
|
if (path.isBlank() || path.contains("No such file")) continue
|
||||||
val parts = trimmed.split(" ", limit = 3)
|
|
||||||
if (parts.size < 3) continue
|
|
||||||
|
|
||||||
val sizeBytes = parts[0].toLongOrNull() ?: continue
|
|
||||||
val epochSecs = parts[1].toLongOrNull() ?: continue
|
|
||||||
val path = parts[2]
|
|
||||||
val filename = path.substringAfterLast("/")
|
val filename = path.substringAfterLast("/")
|
||||||
val ext = filename.substringAfterLast(".", "").lowercase()
|
val ext = filename.substringAfterLast(".", "").lowercase()
|
||||||
|
|
||||||
if (ext !in mediaExtensions) continue
|
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(
|
photos.add(
|
||||||
PhonePhoto(
|
PhonePhoto(
|
||||||
devicePath = path,
|
devicePath = path,
|
||||||
filename = filename,
|
filename = filename,
|
||||||
sizeBytes = sizeBytes,
|
sizeBytes = sizeBytes,
|
||||||
dateTaken = Instant.ofEpochSecond(epochSecs),
|
dateTaken = dateTaken,
|
||||||
exifMake = device.displayName,
|
exifMake = device.displayName,
|
||||||
exifModel = device.displayName,
|
exifModel = device.displayName,
|
||||||
mediaType = if (ext in setOf("mp4", "mov", "3gp", "avi"))
|
mediaType = if (ext in setOf("mp4", "mov", "3gp", "avi"))
|
||||||
@@ -173,6 +175,36 @@ class AndroidConnector(
|
|||||||
private fun requireSerial(device: DeviceInfo): String =
|
private fun requireSerial(device: DeviceInfo): String =
|
||||||
device.adbSerial ?: throw DeviceException("No ADB serial for device: ${device.displayName}")
|
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 {
|
private fun runAdb(serial: String?, vararg args: String): String {
|
||||||
val cmd = buildList {
|
val cmd = buildList {
|
||||||
add(adbBin)
|
add(adbBin)
|
||||||
|
|||||||
Reference in New Issue
Block a user