@@ -20,12 +20,13 @@ package org.oxycblt.auxio.music
2020
2121import android.content.Context
2222import androidx.core.content.edit
23+ import androidx.core.net.toUri
2324import dagger.hilt.android.qualifiers.ApplicationContext
2425import java.util.UUID
2526import javax.inject.Inject
2627import org.oxycblt.auxio.R
2728import org.oxycblt.auxio.settings.Settings
28- import org.oxycblt.musikr.fs.MusicLocation
29+ import org.oxycblt.musikr.fs.Location
2930import timber.log.Timber as L
3031
3132/* *
@@ -37,7 +38,9 @@ interface MusicSettings : Settings<MusicSettings.Listener> {
3738 /* * The current library revision. */
3839 var revision: UUID ?
3940 /* * The locations of music to load. */
40- var musicLocations: List <MusicLocation >
41+ var musicLocations: List <Location .Opened >
42+ /* * The locations to exclude from music loading. */
43+ var excludedLocations: List <Location .Unopened >
4144 /* * Whether to exclude non-music audio files from the music library. */
4245 val excludeNonMusic: Boolean
4346 /* * Whether to ignore hidden files and directories during music loading. */
@@ -74,24 +77,39 @@ class MusicSettingsImpl @Inject constructor(@ApplicationContext private val cont
7477 }
7578 }
7679
77- override var musicLocations: List <MusicLocation >
80+ override var musicLocations: List <Location . Opened >
7881 get() {
7982 val locations =
8083 sharedPreferences.getString(getString(R .string.set_key_music_locations), null )
8184 ? : return emptyList()
82- return MusicLocation .existing(context, locations )
85+ return locations.toOpenedLocations( )
8386 }
8487 set(value) {
8588 sharedPreferences.edit {
86- putString(
87- getString(R .string.set_key_music_locations), MusicLocation .toString(value))
89+ putString(getString(R .string.set_key_music_locations), value.stringify())
8890 commit()
8991 // Sometimes changing this setting just won't actually trigger the listener.
9092 // Only this one. No idea why.
9193 listener?.onMusicLocationsChanged()
9294 }
9395 }
9496
97+ override var excludedLocations: List <Location .Unopened >
98+ get() {
99+ val locations =
100+ sharedPreferences.getString(getString(R .string.set_key_excluded_locations), null )
101+ ? : return emptyList()
102+ return locations.toUnopenedLocations()
103+ }
104+ set(value) {
105+ sharedPreferences.edit {
106+ putString(
107+ getString(R .string.set_key_excluded_locations), value.stringifyLocations())
108+ commit()
109+ listener?.onMusicLocationsChanged()
110+ }
111+ }
112+
95113 override val excludeNonMusic: Boolean
96114 get() = sharedPreferences.getBoolean(getString(R .string.set_key_exclude_non_music), true )
97115
@@ -119,7 +137,8 @@ class MusicSettingsImpl @Inject constructor(@ApplicationContext private val cont
119137 // TODO: Differentiate "hard reloads" (Need the cache) and "Soft reloads"
120138 // (just need to manipulate data)
121139 when (key) {
122- getString(R .string.set_key_music_locations) -> {
140+ getString(R .string.set_key_music_locations),
141+ getString(R .string.set_key_excluded_locations) -> {
123142 L .d(" Dispatching music locations change" )
124143 listener.onMusicLocationsChanged()
125144 }
@@ -136,4 +155,55 @@ class MusicSettingsImpl @Inject constructor(@ApplicationContext private val cont
136155 }
137156 }
138157 }
158+
159+ private fun List<Location.Opened>.stringify (): String =
160+ joinToString(separator = " ;" ) { it.uri.toString().replace(" ;" , " \\ ;" ) }
161+
162+ private fun String.toOpenedLocations (): List <Location .Opened > =
163+ splitEscaped { it == ' ;' }
164+ .mapNotNull { Location .Unopened .from(context, it.toUri())?.open(context) }
165+
166+ private fun List<Location>.stringifyLocations (): String =
167+ joinToString(separator = " ;" ) { it.uri.toString().replace(" ;" , " \\ ;" ) }
168+
169+ private fun String.toUnopenedLocations (): List <Location .Unopened > =
170+ splitEscaped { it == ' ;' }.mapNotNull { Location .Unopened .from(context, it.toUri()) }
171+
172+ private inline fun String.splitEscaped (selector : (Char ) -> Boolean ): List <String > {
173+ val split = mutableListOf<String >()
174+ var currentString = " "
175+ var i = 0
176+
177+ while (i < length) {
178+ val a = get(i)
179+ val b = getOrNull(i + 1 )
180+
181+ if (selector(a)) {
182+ // Non-escaped separator, split the string here, making sure any stray whitespace
183+ // is removed.
184+ split.add(currentString)
185+ currentString = " "
186+ i++
187+ continue
188+ }
189+
190+ if (b != null && a == ' \\ ' && selector(b)) {
191+ // Is an escaped character, add the non-escaped variant and skip two
192+ // characters to move on to the next one.
193+ currentString + = b
194+ i + = 2
195+ } else {
196+ // Non-escaped, increment normally.
197+ currentString + = a
198+ i++
199+ }
200+ }
201+
202+ if (currentString.isNotEmpty()) {
203+ // Had an in-progress split string that is now terminated, add it.
204+ split.add(currentString)
205+ }
206+
207+ return split
208+ }
139209}
0 commit comments