From c3f67d4dc53e39533ebdea2aa033c2451bfd5eaf Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Thu, 21 Dec 2023 20:55:49 -0700 Subject: [PATCH] music: fix m3u export Wasn't correctly writing and also naively relative-izing paths. Those should be fixed now, I hope. --- .../org/oxycblt/auxio/music/external/M3U.kt | 62 +++++++++++++------ .../java/org/oxycblt/auxio/music/fs/Fs.kt | 8 ++- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/music/external/M3U.kt b/app/src/main/java/org/oxycblt/auxio/music/external/M3U.kt index 1ef194819..6e99d0925 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/external/M3U.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/external/M3U.kt @@ -20,19 +20,19 @@ package org.oxycblt.auxio.music.external import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext -import org.oxycblt.auxio.music.Playlist import java.io.BufferedReader +import java.io.BufferedWriter import java.io.File import java.io.InputStream import java.io.InputStreamReader +import java.io.OutputStream import javax.inject.Inject +import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.fs.Components import org.oxycblt.auxio.music.fs.Path import org.oxycblt.auxio.music.metadata.correctWhitespace import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.util.logE -import java.io.BufferedWriter -import java.io.OutputStream /** * Minimal M3U file format implementation. @@ -53,10 +53,11 @@ interface M3U { /** * Writes the given [playlist] to the given [outputStream] in the M3U format,. + * * @param playlist The playlist to write. * @param outputStream The stream to write the M3U file to. * @param workingDirectory The directory that the M3U file is contained in. This is used to - * create relative paths to where the M3U file is assumed to be stored. + * create relative paths to where the M3U file is assumed to be stored. */ fun write(playlist: Playlist, outputStream: OutputStream, workingDirectory: Path) } @@ -128,17 +129,13 @@ class M3UImpl @Inject constructor(@ApplicationContext private val context: Conte } } - override fun write( - playlist: Playlist, - outputStream: OutputStream, - workingDirectory: Path - ) { + override fun write(playlist: Playlist, outputStream: OutputStream, workingDirectory: Path) { val writer = outputStream.bufferedWriter() // Try to be as compliant to the spec as possible while also cramming it full of extensions // I imagine other players will use. writer.writeLine("#EXTM3U") writer.writeLine("#EXTENC:UTF-8") - writer.writeLine("#PLAYLIST:${playlist.name}") + writer.writeLine("#PLAYLIST:${playlist.name.resolve(context)}") for (song in playlist.songs) { val relativePath = song.path.components.relativeTo(workingDirectory.components) writer.writeLine("#EXTINF:${song.durationMs},${song.name.resolve(context)}") @@ -147,6 +144,7 @@ class M3UImpl @Inject constructor(@ApplicationContext private val context: Conte writer.writeLine("#EXTGEN:${song.genres.resolveNames(context)}") writer.writeLine(relativePath.toString()) } + writer.flush() } private fun BufferedWriter.writeLine(line: String) { @@ -154,9 +152,7 @@ class M3UImpl @Inject constructor(@ApplicationContext private val context: Conte newLine() } - private fun Components.absoluteTo( - workingDirectory: Components - ): Components { + private fun Components.absoluteTo(workingDirectory: Components): Components { var absoluteComponents = workingDirectory for (component in components) { when (component) { @@ -172,18 +168,44 @@ class M3UImpl @Inject constructor(@ApplicationContext private val context: Conte return absoluteComponents } - private fun Components.relativeTo( - workingDirectory: Components - ): Components { - var relativeComponents = Components.parse(".") + private fun Components.relativeTo(workingDirectory: Components): Components { + // We want to find the common prefix of the working directory and path, and then + // and them combine them with the correct relative elements to make sure they + // resolve the same. var commonIndex = 0 while (commonIndex < components.size && commonIndex < workingDirectory.components.size && components[commonIndex] == workingDirectory.components[commonIndex]) { ++commonIndex - relativeComponents = relativeComponents.child("..") } - return relativeComponents.concat(depth(commonIndex)) - } + var relativeComponents = Components.parse(".") + + // TODO: Simplify this logic + when { + commonIndex == components.size && commonIndex == workingDirectory.components.size -> { + // The paths are the same. This shouldn't occur. + } + commonIndex == components.size -> { + // The working directory is deeper in the path, backtrack. + for (i in 0..workingDirectory.components.size - commonIndex - 1) { + relativeComponents = relativeComponents.child("..") + } + } + commonIndex == workingDirectory.components.size -> { + // Working directory is shallower than the path, can just append the + // non-common remainder of the path + relativeComponents = relativeComponents.child(depth(commonIndex)) + } + else -> { + // The paths are siblings. Backtrack and append as needed. + for (i in 0..workingDirectory.components.size - commonIndex - 1) { + relativeComponents = relativeComponents.child("..") + } + relativeComponents = relativeComponents.child(depth(commonIndex)) + } + } + + return relativeComponents + } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/Fs.kt b/app/src/main/java/org/oxycblt/auxio/music/fs/Fs.kt index 989c72263..78e173f8d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/Fs.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/fs/Fs.kt @@ -122,8 +122,9 @@ value class Components private constructor(val components: List) { } /** - * Removes the first [n] elements of the path, effectively resulting in a path that is n - * levels deep. + * Removes the first [n] elements of the path, effectively resulting in a path that is n levels + * deep. + * * @param n The number of elements to remove. * @return The new [Components] instance. */ @@ -131,10 +132,11 @@ value class Components private constructor(val components: List) { /** * Concatenates this [Components] instance with another. + * * @param other The [Components] instance to concatenate with. * @return The new [Components] instance. */ - fun concat(other: Components) = Components(components + other.components) + fun child(other: Components) = Components(components + other.components) companion object { /**