music: interpret m3u paths as relative & absolute

Resolves #673
This commit is contained in:
Alexander Capehart 2024-05-26 21:50:34 -06:00
parent 0f691ee65b
commit 27e39b6c10
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 44 additions and 18 deletions

View file

@ -164,7 +164,8 @@ constructor(
}
val deviceLibrary = musicRepository.deviceLibrary ?: return@launch
val songs = importedPlaylist.paths.mapNotNull(deviceLibrary::findSongByPath)
val songs = importedPlaylist.paths.mapNotNull {
it.firstNotNullOfOrNull(deviceLibrary::findSongByPath) }
if (songs.isEmpty()) {
logE("No songs found")

View file

@ -76,7 +76,9 @@ data class ExportConfig(val absolute: Boolean, val windowsPaths: Boolean)
* @see ExternalPlaylistManager
* @see M3U
*/
data class ImportedPlaylist(val name: String?, val paths: List<Path>)
data class ImportedPlaylist(val name: String?, val paths: List<PossiblePaths>)
typealias PossiblePaths = List<Path>
class ExternalPlaylistManagerImpl
@Inject

View file

@ -75,7 +75,7 @@ interface M3U {
class M3UImpl @Inject constructor(@ApplicationContext private val context: Context) : M3U {
override fun read(stream: InputStream, workingDirectory: Path): ImportedPlaylist? {
val reader = BufferedReader(InputStreamReader(stream))
val paths = mutableListOf<Path>()
val paths = mutableListOf<PossiblePaths>()
var name: String? = null
consumeFile@ while (true) {
@ -112,39 +112,62 @@ class M3UImpl @Inject constructor(@ApplicationContext private val context: Conte
}
// There is basically no formal specification of file paths in M3U, and it differs
// based on the US that generated it. These are the paths though that I assume most
// programs will generate.
val components =
// based on the programs that generated it. These are the paths though that I assume
// most programs will generate. Note that we do end up proposing multiple
// interpretations
val possibilities =
when {
path.startsWith('/') -> {
// Unix absolute path. Note that we still assume this absolute path is in
// the same volume as the M3U file. There's no sane way to map the volume
// to the phone's volumes, so this is the only thing we can do.
Components.parseUnix(path)
val absoluteInterpretation = Components.parseUnix(path)
val relativeInterpretation = absoluteInterpretation.absoluteTo(workingDirectory.components)
listOf(absoluteInterpretation, relativeInterpretation)
}
path.startsWith("./") -> {
// Unix relative path, resolve it
Components.parseUnix(path).absoluteTo(workingDirectory.components)
val absoluteInterpretation = Components.parseUnix(path)
val relativeInterpretation = absoluteInterpretation.absoluteTo(workingDirectory.components)
listOf(relativeInterpretation, absoluteInterpretation)
}
path.matches(WINDOWS_VOLUME_PREFIX_REGEX) -> {
// Windows absolute path, we should get rid of the volume prefix, but
// otherwise
// the rest should be fine. Again, we have to disregard what the volume
// actually
// is since there's no sane way to map it to the phone's volumes.
Components.parseWindows(path.substring(2))
// otherwise the rest should be fine. Again, we have to disregard what the
// volume actually is since there's no sane way to map it to the phone's volumes.
val absoluteInterpretation = Components.parseWindows(path.substring(2))
val relativeInterpretation = absoluteInterpretation.absoluteTo(workingDirectory.components)
listOf(absoluteInterpretation, relativeInterpretation)
}
path.startsWith("\\") -> {
// Weird unix/windows hybrid absolute path that appears sometimes
val absoluteInterpretation = Components.parseWindows(path)
val relativeInterpretation = absoluteInterpretation.absoluteTo(workingDirectory.components)
listOf(absoluteInterpretation, relativeInterpretation)
}
path.startsWith(".\\") -> {
// Windows relative path, we need to remove the .\\ prefix
Components.parseWindows(path).absoluteTo(workingDirectory.components)
// Windows-style relative path
val absoluteInterpretation = Components.parseWindows(path)
val relativeInterpretation = absoluteInterpretation.absoluteTo(workingDirectory.components)
listOf(relativeInterpretation, absoluteInterpretation)
}
else -> {
// No clue, parse by all separators and assume it's relative.
Components.parseAny(path).absoluteTo(workingDirectory.components)
// No clue, just go wild and assume all possible combinations.
val unixAbsoluteInterpretation = Components.parseUnix(path)
val unixRelativeInterpretation = unixAbsoluteInterpretation.absoluteTo(workingDirectory.components)
val windowsAbsoluteInterpretation = Components.parseWindows(path)
val windowsRelativeInterpretation = windowsAbsoluteInterpretation.absoluteTo(workingDirectory.components)
listOf(unixRelativeInterpretation, unixAbsoluteInterpretation,
windowsRelativeInterpretation, windowsAbsoluteInterpretation)
}
}
paths.add(Path(workingDirectory.volume, components))
paths.add(possibilities.map { Path(workingDirectory.volume, it) })
}
return if (paths.isNotEmpty()) {