mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 15:02:21 +02:00
refactor(android): move SAF CUE helpers out of MainActivitySafScan.kt
The CUE sheet helpers used by both SAF scans (FILE line extraction, the per-directory child lookup cache and the audio sibling resolver) move verbatim into SafCueScan.kt. rememberCueDirectoryListing becomes internal because the scans stay behind; the sibling extension list and the lookup builder are only used in the new file and stay private.
This commit is contained in:
1 parent
bb930827fa
commit
2af0fa95e5
2 files changed
+103
-95
No files matched your search
@@ -90,40 +90,6 @@ internal fun MainActivity.loadExistingFilesFromSnapshot(snapshotPath: String): M
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the audio filename referenced by a CUE sheet file.
|
||||
* Reads the FILE "name" TYPE line from the .cue text.
|
||||
* Returns just the filename (no path), or null if not found.
|
||||
*/
|
||||
internal fun MainActivity.extractCueAudioFileName(cueTempPath: String): String? {
|
||||
try {
|
||||
val lines = File(cueTempPath).readLines()
|
||||
for (line in lines) {
|
||||
val trimmed = line.trim().let { l ->
|
||||
if (l.startsWith("\uFEFF")) l.removePrefix("\uFEFF").trim() else l
|
||||
}
|
||||
if (trimmed.uppercase(Locale.ROOT).startsWith("FILE ")) {
|
||||
val rest = trimmed.substring(5).trim()
|
||||
val filename = if (rest.startsWith("\"")) {
|
||||
val endQuote = rest.indexOf('"', 1)
|
||||
if (endQuote > 0) rest.substring(1, endQuote) else rest
|
||||
} else {
|
||||
val parts = rest.split("\\s+".toRegex())
|
||||
if (parts.size >= 2) parts.dropLast(1).joinToString(" ") else rest
|
||||
}
|
||||
return filename.substringAfterLast("/").substringAfterLast("\\")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.w("SpotiFLAC", "Failed to extract audio filename from CUE: ${e.message}")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private val cueSiblingAudioExtensions = listOf(
|
||||
".flac", ".wav", ".ape", ".mp3", ".ogg", ".wv", ".m4a", ".mp4", ".aac"
|
||||
)
|
||||
|
||||
// Keep the SAF folder walk aligned with the backend's supported audio formats.
|
||||
// CUE files are handled separately.
|
||||
private val libraryScanAudioExtensions = setOf(
|
||||
@@ -131,36 +97,6 @@ internal fun MainActivity.extractCueAudioFileName(cueTempPath: String): String?
|
||||
".ape", ".wv", ".mpc", ".wav", ".aiff", ".aif", ".dsf", ".dff"
|
||||
)
|
||||
|
||||
internal fun MainActivity.getSafChildFileLookup(
|
||||
dir: DocumentFile,
|
||||
cache: MutableMap<String, Map<String, DocumentFile>>,
|
||||
): Map<String, DocumentFile> {
|
||||
val dirKey = dir.uri.toString()
|
||||
return cache.getOrPut(dirKey) { safChildFileLookup(listSafChildrenOrThrow(dir)) }
|
||||
}
|
||||
|
||||
private fun safChildFileLookup(children: List<SafChildEntry>): Map<String, DocumentFile> =
|
||||
buildMap {
|
||||
for (child in children) {
|
||||
if (child.isDirectory) continue
|
||||
val childName = child.name?.trim().orEmpty()
|
||||
if (childName.isBlank()) continue
|
||||
put(childName.lowercase(Locale.ROOT), child.doc)
|
||||
}
|
||||
}
|
||||
|
||||
/** Keeps a traversal listing for CUE sibling lookup so the directory is not
|
||||
* queried again; on network providers every listing is a round trip. */
|
||||
private fun rememberCueDirectoryListing(
|
||||
dir: DocumentFile,
|
||||
children: List<SafChildEntry>,
|
||||
cache: MutableMap<String, Map<String, DocumentFile>>,
|
||||
) {
|
||||
if (children.any { !it.isDirectory && it.name?.endsWith(".cue", ignoreCase = true) == true }) {
|
||||
cache[dir.uri.toString()] = safChildFileLookup(children)
|
||||
}
|
||||
}
|
||||
|
||||
// Provider round trips dominate on SD cards, USB drives and network shares.
|
||||
private const val SAF_LIST_CONCURRENCY = 4
|
||||
private const val SAF_READ_WORKERS = 6
|
||||
@@ -266,37 +202,6 @@ private fun <T, R> runSafReadsInOrder(
|
||||
}
|
||||
}
|
||||
|
||||
internal fun MainActivity.resolveCueAudioSibling(
|
||||
parentDir: DocumentFile,
|
||||
cueName: String,
|
||||
audioFileName: String?,
|
||||
childLookupCache: MutableMap<String, Map<String, DocumentFile>>,
|
||||
): DocumentFile? {
|
||||
val childLookup = getSafChildFileLookup(parentDir, childLookupCache)
|
||||
|
||||
val directMatch = audioFileName
|
||||
?.trim()
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.substringAfterLast("/")
|
||||
?.substringAfterLast("\\")
|
||||
?.lowercase(Locale.ROOT)
|
||||
?.let(childLookup::get)
|
||||
if (directMatch != null) {
|
||||
return directMatch
|
||||
}
|
||||
|
||||
val cueBaseName = cueName.substringBeforeLast('.').trim()
|
||||
if (cueBaseName.isBlank()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val cueBaseKey = cueBaseName.lowercase(Locale.ROOT)
|
||||
for (ext in cueSiblingAudioExtensions) {
|
||||
childLookup["$cueBaseKey$ext"]?.let { return it }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal data class SafChildEntry(
|
||||
val doc: DocumentFile,
|
||||
val name: String?,
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.zarz.spotiflac
|
||||
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
// CUE sheet helpers for the SAF library scan: the audio file a sheet names,
|
||||
// cached per-directory child lookups, and resolving the sheet's audio sibling.
|
||||
|
||||
/**
|
||||
* Extract the audio filename referenced by a CUE sheet file.
|
||||
* Reads the FILE "name" TYPE line from the .cue text.
|
||||
* Returns just the filename (no path), or null if not found.
|
||||
*/
|
||||
internal fun MainActivity.extractCueAudioFileName(cueTempPath: String): String? {
|
||||
try {
|
||||
val lines = File(cueTempPath).readLines()
|
||||
for (line in lines) {
|
||||
val trimmed = line.trim().let { l ->
|
||||
if (l.startsWith("\uFEFF")) l.removePrefix("\uFEFF").trim() else l
|
||||
}
|
||||
if (trimmed.uppercase(Locale.ROOT).startsWith("FILE ")) {
|
||||
val rest = trimmed.substring(5).trim()
|
||||
val filename = if (rest.startsWith("\"")) {
|
||||
val endQuote = rest.indexOf('"', 1)
|
||||
if (endQuote > 0) rest.substring(1, endQuote) else rest
|
||||
} else {
|
||||
val parts = rest.split("\\s+".toRegex())
|
||||
if (parts.size >= 2) parts.dropLast(1).joinToString(" ") else rest
|
||||
}
|
||||
return filename.substringAfterLast("/").substringAfterLast("\\")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.w("SpotiFLAC", "Failed to extract audio filename from CUE: ${e.message}")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private val cueSiblingAudioExtensions = listOf(
|
||||
".flac", ".wav", ".ape", ".mp3", ".ogg", ".wv", ".m4a", ".mp4", ".aac"
|
||||
)
|
||||
|
||||
internal fun MainActivity.getSafChildFileLookup(
|
||||
dir: DocumentFile,
|
||||
cache: MutableMap<String, Map<String, DocumentFile>>,
|
||||
): Map<String, DocumentFile> {
|
||||
val dirKey = dir.uri.toString()
|
||||
return cache.getOrPut(dirKey) { safChildFileLookup(listSafChildrenOrThrow(dir)) }
|
||||
}
|
||||
|
||||
private fun safChildFileLookup(children: List<SafChildEntry>): Map<String, DocumentFile> =
|
||||
buildMap {
|
||||
for (child in children) {
|
||||
if (child.isDirectory) continue
|
||||
val childName = child.name?.trim().orEmpty()
|
||||
if (childName.isBlank()) continue
|
||||
put(childName.lowercase(Locale.ROOT), child.doc)
|
||||
}
|
||||
}
|
||||
|
||||
/** Keeps a traversal listing for CUE sibling lookup so the directory is not
|
||||
* queried again; on network providers every listing is a round trip. */
|
||||
internal fun rememberCueDirectoryListing(
|
||||
dir: DocumentFile,
|
||||
children: List<SafChildEntry>,
|
||||
cache: MutableMap<String, Map<String, DocumentFile>>,
|
||||
) {
|
||||
if (children.any { !it.isDirectory && it.name?.endsWith(".cue", ignoreCase = true) == true }) {
|
||||
cache[dir.uri.toString()] = safChildFileLookup(children)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun MainActivity.resolveCueAudioSibling(
|
||||
parentDir: DocumentFile,
|
||||
cueName: String,
|
||||
audioFileName: String?,
|
||||
childLookupCache: MutableMap<String, Map<String, DocumentFile>>,
|
||||
): DocumentFile? {
|
||||
val childLookup = getSafChildFileLookup(parentDir, childLookupCache)
|
||||
|
||||
val directMatch = audioFileName
|
||||
?.trim()
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.substringAfterLast("/")
|
||||
?.substringAfterLast("\\")
|
||||
?.lowercase(Locale.ROOT)
|
||||
?.let(childLookup::get)
|
||||
if (directMatch != null) {
|
||||
return directMatch
|
||||
}
|
||||
|
||||
val cueBaseName = cueName.substringBeforeLast('.').trim()
|
||||
if (cueBaseName.isBlank()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val cueBaseKey = cueBaseName.lowercase(Locale.ROOT)
|
||||
for (ext in cueSiblingAudioExtensions) {
|
||||
childLookup["$cueBaseKey$ext"]?.let { return it }
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in new issue
Block a user