diff --git a/src/main/kotlin/com/bolenpad/photophetch/ui/AppState.kt b/src/main/kotlin/com/bolenpad/photophetch/ui/AppState.kt index 848cca7..8805946 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/ui/AppState.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/ui/AppState.kt @@ -183,20 +183,28 @@ class AppState(private val configStore: ConfigStore) { val tmpDir = java.nio.file.Paths.get(System.getProperty("java.io.tmpdir"), "photophetch-thumbs") if (!tmpDir.toFile().exists()) tmpDir.toFile().mkdirs() - // Only fetch photos not already cached - val needed = photos.filter { photo -> - val ext = photo.filename.substringAfterLast(".", "").lowercase() - if (ext in setOf("mp4", "mov", "3gp", "avi", "mkv", "m4v", "webm")) return@filter false - val hash = (cacheKey + photo.devicePath).hashCode().toString(16) - val cachePath = cacheDir.resolve("$hash.jpg") - !cachePath.toFile().exists() || cachePath.toFile().length() == 0L - } + // Only fetch photos not already cached, sorted newest first + val needed = photos + .sortedByDescending { it.dateTaken } + .filter { photo -> + val ext = photo.filename.substringAfterLast(".", "").lowercase() + if (ext in setOf("mp4", "mov", "3gp", "avi", "mkv", "m4v", "webm")) return@filter false + val hash = (cacheKey + photo.devicePath).hashCode().toString(16) + val cachePath = cacheDir.resolve("$hash.jpg") + !cachePath.toFile().exists() || cachePath.toFile().length() == 0L + } - if (needed.isEmpty()) return - log.info("Pre-fetching ${needed.size} iOS thumbnails...") + if (needed.isEmpty()) { + log.info("iOS thumbnails: all ${photos.size} already cached") + return + } + log.info("iOS thumbnails: pre-fetching ${needed.size} / ${photos.size} (${photos.size - needed.size} already cached)") + + var loaded = 0 + val batchSize = 30 // Build pairs JSON and run batch-thumbs command - needed.chunked(30).forEach { batch -> + needed.chunked(batchSize).forEachIndexed { batchIdx, batch -> val pairs = batch.joinToString(",") { photo -> val ext = photo.filename.substringAfterLast(".", "").lowercase() val hash = (cacheKey + photo.devicePath).hashCode().toString(16) @@ -210,11 +218,12 @@ class AppState(private val configStore: ConfigStore) { try { val process = ProcessBuilder(connector.pythonBin, connector.helperScript, "batch-thumbs", pairsJson) .redirectErrorStream(false).start() - process.inputStream.readBytes() // stdout - process.errorStream.readBytes() // stderr + process.inputStream.readBytes() + process.errorStream.readBytes() process.waitFor() // Run sips on each successfully pulled file + var batchLoaded = 0 batch.forEach { photo -> val ext = photo.filename.substringAfterLast(".", "").lowercase() val hash = (cacheKey + photo.devicePath).hashCode().toString(16) @@ -227,13 +236,16 @@ class AppState(private val configStore: ConfigStore) { sips.inputStream.readBytes() sips.waitFor() tmpFile.toFile().delete() + if (cachePath.toFile().exists()) batchLoaded++ } } + loaded += batchLoaded + log.info("iOS thumbnails: $loaded / ${needed.size} loaded (batch ${batchIdx + 1}/${(needed.size + batchSize - 1) / batchSize})") } catch (e: Exception) { - log.warn("iOS thumbnail batch failed: ${e.message}") + log.warn("iOS thumbnail batch ${batchIdx + 1} failed: ${e.message}") } } - log.info("iOS thumbnail pre-fetch complete") + log.info("iOS thumbnails: done — $loaded / ${needed.size} loaded successfully") } fun selectFolder(folderName: String?) {