music: decouple settings somewhat
Try to decouple the stateful music settings object from the stateless internals of the music loader. This should make unit testing far easier.
This commit is contained in:
parent
0ad7a8955a
commit
9ae6b20fd1
8 changed files with 133 additions and 167 deletions
|
@ -36,6 +36,8 @@ import org.oxycblt.auxio.music.cache.CacheRepository
|
|||
import org.oxycblt.auxio.music.device.DeviceLibrary
|
||||
import org.oxycblt.auxio.music.device.RawSong
|
||||
import org.oxycblt.auxio.music.fs.MediaStoreExtractor
|
||||
import org.oxycblt.auxio.music.info.Name
|
||||
import org.oxycblt.auxio.music.metadata.Separators
|
||||
import org.oxycblt.auxio.music.metadata.TagExtractor
|
||||
import org.oxycblt.auxio.music.user.MutableUserLibrary
|
||||
import org.oxycblt.auxio.music.user.UserLibrary
|
||||
|
@ -223,7 +225,8 @@ constructor(
|
|||
private val mediaStoreExtractor: MediaStoreExtractor,
|
||||
private val tagExtractor: TagExtractor,
|
||||
private val deviceLibraryFactory: DeviceLibrary.Factory,
|
||||
private val userLibraryFactory: UserLibrary.Factory
|
||||
private val userLibraryFactory: UserLibrary.Factory,
|
||||
private val musicSettings: MusicSettings
|
||||
) : MusicRepository {
|
||||
private val updateListeners = mutableListOf<MusicRepository.UpdateListener>()
|
||||
private val indexingListeners = mutableListOf<MusicRepository.IndexingListener>()
|
||||
|
@ -356,6 +359,8 @@ constructor(
|
|||
}
|
||||
|
||||
private suspend fun indexImpl(worker: MusicRepository.IndexingWorker, withCache: Boolean) {
|
||||
// TODO: Find a way to break up this monster of a method, preferably as another class.
|
||||
|
||||
val start = System.currentTimeMillis()
|
||||
// Make sure we have permissions before going forward. Theoretically this would be better
|
||||
// done at the UI level, but that intertwines logic and display too much.
|
||||
|
@ -365,6 +370,17 @@ constructor(
|
|||
throw NoAudioPermissionException()
|
||||
}
|
||||
|
||||
// Obtain configuration information
|
||||
val constraints =
|
||||
MediaStoreExtractor.Constraints(musicSettings.excludeNonMusic, musicSettings.musicDirs)
|
||||
val separators = Separators.from(musicSettings.separators)
|
||||
val nameFactory =
|
||||
if (musicSettings.intelligentSorting) {
|
||||
Name.Known.IntelligentFactory
|
||||
} else {
|
||||
Name.Known.SimpleFactory
|
||||
}
|
||||
|
||||
// Begin with querying MediaStore and the music cache. The former is needed for Auxio
|
||||
// to figure out what songs are (probably) on the device, and the latter will be needed
|
||||
// for discovery (described later). These have no shared state, so they are done in
|
||||
|
@ -376,7 +392,7 @@ constructor(
|
|||
worker.scope.async {
|
||||
val query =
|
||||
try {
|
||||
mediaStoreExtractor.query()
|
||||
mediaStoreExtractor.query(constraints)
|
||||
} catch (e: Exception) {
|
||||
// Normally, errors in an async call immediately bubble up to the Looper
|
||||
// and crash the app. Thus, we have to wrap any error into a Result
|
||||
|
@ -445,7 +461,8 @@ constructor(
|
|||
worker.scope.async(Dispatchers.Default) {
|
||||
val deviceLibrary =
|
||||
try {
|
||||
deviceLibraryFactory.create(completeSongs, processedSongs)
|
||||
deviceLibraryFactory.create(
|
||||
completeSongs, processedSongs, separators, nameFactory)
|
||||
} catch (e: Exception) {
|
||||
processedSongs.close(e)
|
||||
return@async Result.failure(e)
|
||||
|
@ -518,7 +535,7 @@ constructor(
|
|||
logD("Awaiting DeviceLibrary creation")
|
||||
val deviceLibrary = deviceLibraryJob.await().getOrThrow()
|
||||
logD("Starting UserLibrary creation")
|
||||
val userLibrary = userLibraryFactory.create(rawPlaylists, deviceLibrary)
|
||||
val userLibrary = userLibraryFactory.create(rawPlaylists, deviceLibrary, nameFactory)
|
||||
|
||||
// Loading process is functionally done, indicate such
|
||||
logD(
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.oxycblt.auxio.music.Artist
|
|||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicRepository
|
||||
import org.oxycblt.auxio.music.MusicSettings
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.fs.contentResolverSafe
|
||||
import org.oxycblt.auxio.music.fs.useQuery
|
||||
|
@ -110,19 +109,19 @@ interface DeviceLibrary {
|
|||
suspend fun create(
|
||||
rawSongs: Channel<RawSong>,
|
||||
processedSongs: Channel<RawSong>,
|
||||
separators: Separators,
|
||||
nameFactory: Name.Known.Factory
|
||||
): DeviceLibraryImpl
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: MusicSettings) :
|
||||
DeviceLibrary.Factory {
|
||||
class DeviceLibraryFactoryImpl @Inject constructor() : DeviceLibrary.Factory {
|
||||
override suspend fun create(
|
||||
rawSongs: Channel<RawSong>,
|
||||
processedSongs: Channel<RawSong>
|
||||
processedSongs: Channel<RawSong>,
|
||||
separators: Separators,
|
||||
nameFactory: Name.Known.Factory
|
||||
): DeviceLibraryImpl {
|
||||
val nameFactory = Name.Known.Factory.from(musicSettings)
|
||||
val separators = Separators.from(musicSettings)
|
||||
|
||||
val songGrouping = mutableMapOf<Music.UID, SongImpl>()
|
||||
val albumGrouping = mutableMapOf<RawAlbum.Key, Grouping<RawAlbum, SongImpl>>()
|
||||
val artistGrouping = mutableMapOf<RawArtist.Key, Grouping<RawArtist, Music>>()
|
||||
|
|
|
@ -24,12 +24,11 @@ import dagger.Provides
|
|||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.oxycblt.auxio.music.MusicSettings
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class FsModule {
|
||||
@Provides
|
||||
fun mediaStoreExtractor(@ApplicationContext context: Context, musicSettings: MusicSettings) =
|
||||
MediaStoreExtractor.from(context, musicSettings)
|
||||
fun mediaStoreExtractor(@ApplicationContext context: Context) =
|
||||
MediaStoreExtractor.from(context)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ import androidx.core.database.getStringOrNull
|
|||
import java.io.File
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.yield
|
||||
import org.oxycblt.auxio.music.MusicSettings
|
||||
import org.oxycblt.auxio.music.cache.Cache
|
||||
import org.oxycblt.auxio.music.device.RawSong
|
||||
import org.oxycblt.auxio.music.info.Date
|
||||
|
@ -50,9 +49,11 @@ interface MediaStoreExtractor {
|
|||
/**
|
||||
* Query the media database.
|
||||
*
|
||||
* @param constraints Configuration parameter to restrict what music should be ignored when
|
||||
* querying.
|
||||
* @return A new [Query] returned from the media database.
|
||||
*/
|
||||
suspend fun query(): Query
|
||||
suspend fun query(constraints: Constraints): Query
|
||||
|
||||
/**
|
||||
* Consume the [Cursor] loaded after [query].
|
||||
|
@ -84,46 +85,44 @@ interface MediaStoreExtractor {
|
|||
fun populateTags(rawSong: RawSong)
|
||||
}
|
||||
|
||||
data class Constraints(val excludeNonMusic: Boolean, val musicDirs: MusicDirectories)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a framework-backed instance.
|
||||
*
|
||||
* @param context [Context] required.
|
||||
* @param musicSettings [MusicSettings] required.
|
||||
* @return A new [MediaStoreExtractor] that will work best on the device's API level.
|
||||
*/
|
||||
fun from(context: Context, musicSettings: MusicSettings): MediaStoreExtractor =
|
||||
fun from(context: Context): MediaStoreExtractor =
|
||||
when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ->
|
||||
Api30MediaStoreExtractor(context, musicSettings)
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ->
|
||||
Api29MediaStoreExtractor(context, musicSettings)
|
||||
else -> Api21MediaStoreExtractor(context, musicSettings)
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Api30MediaStoreExtractor(context)
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Api29MediaStoreExtractor(context)
|
||||
else -> Api21MediaStoreExtractor(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class BaseMediaStoreExtractor(
|
||||
protected val context: Context,
|
||||
private val musicSettings: MusicSettings
|
||||
) : MediaStoreExtractor {
|
||||
final override suspend fun query(): MediaStoreExtractor.Query {
|
||||
private abstract class BaseMediaStoreExtractor(protected val context: Context) :
|
||||
MediaStoreExtractor {
|
||||
final override suspend fun query(
|
||||
constraints: MediaStoreExtractor.Constraints
|
||||
): MediaStoreExtractor.Query {
|
||||
val start = System.currentTimeMillis()
|
||||
|
||||
val args = mutableListOf<String>()
|
||||
var selector = BASE_SELECTOR
|
||||
|
||||
// Filter out audio that is not music, if enabled.
|
||||
if (musicSettings.excludeNonMusic) {
|
||||
if (constraints.excludeNonMusic) {
|
||||
logD("Excluding non-music")
|
||||
selector += " AND ${MediaStore.Audio.AudioColumns.IS_MUSIC}=1"
|
||||
}
|
||||
|
||||
// Set up the projection to follow the music directory configuration.
|
||||
val dirs = musicSettings.musicDirs
|
||||
if (dirs.dirs.isNotEmpty()) {
|
||||
if (constraints.musicDirs.dirs.isNotEmpty()) {
|
||||
selector += " AND "
|
||||
if (!dirs.shouldInclude) {
|
||||
if (!constraints.musicDirs.shouldInclude) {
|
||||
logD("Excluding directories in selector")
|
||||
// Without a NOT, the query will be restricted to the specified paths, resulting
|
||||
// in the "Include" mode. With a NOT, the specified paths will not be included,
|
||||
|
@ -134,10 +133,10 @@ private abstract class BaseMediaStoreExtractor(
|
|||
|
||||
// Specifying the paths to filter is version-specific, delegate to the concrete
|
||||
// implementations.
|
||||
for (i in dirs.dirs.indices) {
|
||||
if (addDirToSelector(dirs.dirs[i], args)) {
|
||||
for (i in constraints.musicDirs.dirs.indices) {
|
||||
if (addDirToSelector(constraints.musicDirs.dirs[i], args)) {
|
||||
selector +=
|
||||
if (i < dirs.dirs.lastIndex) {
|
||||
if (i < constraints.musicDirs.dirs.lastIndex) {
|
||||
"$dirSelectorTemplate OR "
|
||||
} else {
|
||||
dirSelectorTemplate
|
||||
|
@ -362,8 +361,7 @@ private abstract class BaseMediaStoreExtractor(
|
|||
// Note: The separation between version-specific backends may not be the cleanest. To preserve
|
||||
// speed, we only want to add redundancy on known issues, not with possible issues.
|
||||
|
||||
private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSettings) :
|
||||
BaseMediaStoreExtractor(context, musicSettings) {
|
||||
private class Api21MediaStoreExtractor(context: Context) : BaseMediaStoreExtractor(context) {
|
||||
override val projection: Array<String>
|
||||
get() =
|
||||
super.projection +
|
||||
|
@ -447,10 +445,8 @@ private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSet
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private abstract class BaseApi29MediaStoreExtractor(
|
||||
context: Context,
|
||||
musicSettings: MusicSettings
|
||||
) : BaseMediaStoreExtractor(context, musicSettings) {
|
||||
private abstract class BaseApi29MediaStoreExtractor(context: Context) :
|
||||
BaseMediaStoreExtractor(context) {
|
||||
override val projection: Array<String>
|
||||
get() =
|
||||
super.projection +
|
||||
|
@ -512,8 +508,7 @@ private abstract class BaseApi29MediaStoreExtractor(
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private class Api29MediaStoreExtractor(context: Context, musicSettings: MusicSettings) :
|
||||
BaseApi29MediaStoreExtractor(context, musicSettings) {
|
||||
private class Api29MediaStoreExtractor(context: Context) : BaseApi29MediaStoreExtractor(context) {
|
||||
|
||||
override val projection: Array<String>
|
||||
get() = super.projection + arrayOf(MediaStore.Audio.AudioColumns.TRACK)
|
||||
|
@ -553,8 +548,7 @@ private class Api29MediaStoreExtractor(context: Context, musicSettings: MusicSet
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
private class Api30MediaStoreExtractor(context: Context, musicSettings: MusicSettings) :
|
||||
BaseApi29MediaStoreExtractor(context, musicSettings) {
|
||||
private class Api30MediaStoreExtractor(context: Context) : BaseApi29MediaStoreExtractor(context) {
|
||||
override val projection: Array<String>
|
||||
get() =
|
||||
super.projection +
|
||||
|
|
|
@ -23,12 +23,11 @@ import androidx.annotation.StringRes
|
|||
import androidx.annotation.VisibleForTesting
|
||||
import java.text.CollationKey
|
||||
import java.text.Collator
|
||||
import org.oxycblt.auxio.music.MusicSettings
|
||||
|
||||
/**
|
||||
* The name of a music item.
|
||||
*
|
||||
* This class automatically implements
|
||||
* This class automatically implements advanced sorting heuristics for music naming,
|
||||
*
|
||||
* @author Alexander Capehart
|
||||
*/
|
||||
|
@ -80,7 +79,7 @@ sealed interface Name : Comparable<Name> {
|
|||
is Unknown -> 1
|
||||
}
|
||||
|
||||
interface Factory {
|
||||
sealed interface Factory {
|
||||
/**
|
||||
* Create a new instance of [Name.Known]
|
||||
*
|
||||
|
@ -88,22 +87,16 @@ sealed interface Name : Comparable<Name> {
|
|||
* @param sort The raw sort name obtained from the music item
|
||||
*/
|
||||
fun parse(raw: String, sort: String?): Known
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Creates a new instance from the **current state** of the given [MusicSettings]'s
|
||||
* user-defined name configuration.
|
||||
*
|
||||
* @param settings The [MusicSettings] to use.
|
||||
* @return A [Factory] instance reflecting the configuration state.
|
||||
*/
|
||||
fun from(settings: MusicSettings) =
|
||||
if (settings.intelligentSorting) {
|
||||
IntelligentKnownName.Factory
|
||||
} else {
|
||||
SimpleKnownName.Factory
|
||||
}
|
||||
}
|
||||
/** Produces a simple [Known] with basic sorting heuristics that are locale-independent. */
|
||||
data object SimpleFactory : Factory {
|
||||
override fun parse(raw: String, sort: String?) = SimpleKnownName(raw, sort)
|
||||
}
|
||||
|
||||
/** Produces an intelligent [Known] with advanced, but more fragile heuristics. */
|
||||
data object IntelligentFactory : Factory {
|
||||
override fun parse(raw: String, sort: String?) = IntelligentKnownName(raw, sort)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,7 +130,6 @@ private val punctRegex by lazy { Regex("[\\p{Punct}+]") }
|
|||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@VisibleForTesting
|
||||
data class SimpleKnownName(override val raw: String, override val sort: String?) : Name.Known() {
|
||||
override val sortTokens = listOf(parseToken(sort ?: raw))
|
||||
|
||||
|
@ -148,10 +140,6 @@ data class SimpleKnownName(override val raw: String, override val sort: String?)
|
|||
// Always use lexicographic mode since we aren't parsing any numeric components
|
||||
return SortToken(collationKey, SortToken.Type.LEXICOGRAPHIC)
|
||||
}
|
||||
|
||||
data object Factory : Name.Known.Factory {
|
||||
override fun parse(raw: String, sort: String?) = SimpleKnownName(raw, sort)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,7 +147,6 @@ data class SimpleKnownName(override val raw: String, override val sort: String?)
|
|||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
@VisibleForTesting
|
||||
data class IntelligentKnownName(override val raw: String, override val sort: String?) :
|
||||
Name.Known() {
|
||||
override val sortTokens = parseTokens(sort ?: raw)
|
||||
|
@ -208,10 +195,6 @@ data class IntelligentKnownName(override val raw: String, override val sort: Str
|
|||
}
|
||||
}
|
||||
|
||||
data object Factory : Name.Known.Factory {
|
||||
override fun parse(raw: String, sort: String?) = IntelligentKnownName(raw, sort)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TOKEN_REGEX by lazy { Regex("(\\d+)|(\\D+)") }
|
||||
}
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
|
||||
package org.oxycblt.auxio.music.metadata
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import org.oxycblt.auxio.music.MusicSettings
|
||||
|
||||
/**
|
||||
* Defines the user-specified parsing of multi-value tags. This should be used to parse any tags
|
||||
* that may be delimited with a separator character.
|
||||
|
@ -45,15 +42,12 @@ interface Separators {
|
|||
const val AND = '&'
|
||||
|
||||
/**
|
||||
* Creates a new instance from the **current state** of the given [MusicSettings]'s
|
||||
* user-defined separator configuration.
|
||||
* Creates a new instance from a string of separator characters to use.
|
||||
*
|
||||
* @param settings The [MusicSettings] to use.
|
||||
* @return A new [Separators] instance reflecting the configuration state.
|
||||
* @param chars The separator characters to use. Each character in the string will be
|
||||
* checked for when splitting a string list.
|
||||
* @return A new [Separators] instance reflecting the separators.
|
||||
*/
|
||||
fun from(settings: MusicSettings) = from(settings.separators)
|
||||
|
||||
@VisibleForTesting
|
||||
fun from(chars: String) =
|
||||
if (chars.isNotEmpty()) {
|
||||
CharSeparators(chars.toSet())
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.lang.Exception
|
|||
import javax.inject.Inject
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicRepository
|
||||
import org.oxycblt.auxio.music.MusicSettings
|
||||
import org.oxycblt.auxio.music.Playlist
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.device.DeviceLibrary
|
||||
|
@ -82,7 +81,8 @@ interface UserLibrary {
|
|||
*/
|
||||
suspend fun create(
|
||||
rawPlaylists: List<RawPlaylist>,
|
||||
deviceLibrary: DeviceLibrary
|
||||
deviceLibrary: DeviceLibrary,
|
||||
nameFactory: Name.Known.Factory
|
||||
): MutableUserLibrary
|
||||
}
|
||||
}
|
||||
|
@ -139,9 +139,7 @@ interface MutableUserLibrary : UserLibrary {
|
|||
suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>): Boolean
|
||||
}
|
||||
|
||||
class UserLibraryFactoryImpl
|
||||
@Inject
|
||||
constructor(private val playlistDao: PlaylistDao, private val musicSettings: MusicSettings) :
|
||||
class UserLibraryFactoryImpl @Inject constructor(private val playlistDao: PlaylistDao) :
|
||||
UserLibrary.Factory {
|
||||
override suspend fun query() =
|
||||
try {
|
||||
|
@ -155,22 +153,22 @@ constructor(private val playlistDao: PlaylistDao, private val musicSettings: Mus
|
|||
|
||||
override suspend fun create(
|
||||
rawPlaylists: List<RawPlaylist>,
|
||||
deviceLibrary: DeviceLibrary
|
||||
deviceLibrary: DeviceLibrary,
|
||||
nameFactory: Name.Known.Factory
|
||||
): MutableUserLibrary {
|
||||
val nameFactory = Name.Known.Factory.from(musicSettings)
|
||||
val playlistMap = mutableMapOf<Music.UID, PlaylistImpl>()
|
||||
for (rawPlaylist in rawPlaylists) {
|
||||
val playlistImpl = PlaylistImpl.fromRaw(rawPlaylist, deviceLibrary, nameFactory)
|
||||
playlistMap[playlistImpl.uid] = playlistImpl
|
||||
}
|
||||
return UserLibraryImpl(playlistDao, playlistMap, musicSettings)
|
||||
return UserLibraryImpl(playlistDao, playlistMap, nameFactory)
|
||||
}
|
||||
}
|
||||
|
||||
private class UserLibraryImpl(
|
||||
private val playlistDao: PlaylistDao,
|
||||
private val playlistMap: MutableMap<Music.UID, PlaylistImpl>,
|
||||
private val musicSettings: MusicSettings
|
||||
private val nameFactory: Name.Known.Factory
|
||||
) : MutableUserLibrary {
|
||||
override fun hashCode() = playlistMap.hashCode()
|
||||
|
||||
|
@ -186,7 +184,7 @@ private class UserLibraryImpl(
|
|||
override fun findPlaylist(name: String) = playlistMap.values.find { it.name.raw == name }
|
||||
|
||||
override suspend fun createPlaylist(name: String, songs: List<Song>): Playlist? {
|
||||
val playlistImpl = PlaylistImpl.from(name, songs, Name.Known.Factory.from(musicSettings))
|
||||
val playlistImpl = PlaylistImpl.from(name, songs, nameFactory)
|
||||
synchronized(this) { playlistMap[playlistImpl.uid] = playlistImpl }
|
||||
val rawPlaylist =
|
||||
RawPlaylist(
|
||||
|
@ -209,9 +207,7 @@ private class UserLibraryImpl(
|
|||
val playlistImpl =
|
||||
synchronized(this) {
|
||||
requireNotNull(playlistMap[playlist.uid]) { "Cannot rename invalid playlist" }
|
||||
.also {
|
||||
playlistMap[it.uid] = it.edit(name, Name.Known.Factory.from(musicSettings))
|
||||
}
|
||||
.also { playlistMap[it.uid] = it.edit(name, nameFactory) }
|
||||
}
|
||||
|
||||
return try {
|
||||
|
|
|
@ -18,30 +18,14 @@
|
|||
|
||||
package org.oxycblt.auxio.music.info
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.oxycblt.auxio.music.MusicSettings
|
||||
|
||||
class NameTest {
|
||||
@Test
|
||||
fun name_simple_from_settings() {
|
||||
val musicSettings = mockk<MusicSettings> { every { intelligentSorting } returns false }
|
||||
assertTrue(Name.Known.Factory.from(musicSettings) is SimpleKnownName.Factory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_intelligent_from_settings() {
|
||||
val musicSettings = mockk<MusicSettings> { every { intelligentSorting } returns true }
|
||||
assertTrue(Name.Known.Factory.from(musicSettings) is IntelligentKnownName.Factory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_simple_withoutPunct() {
|
||||
val name = SimpleKnownName("Loveless", null)
|
||||
val name = Name.Known.SimpleFactory.parse("Loveless", null)
|
||||
assertEquals("Loveless", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("L", name.thumb)
|
||||
|
@ -52,7 +36,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_simple_withPunct() {
|
||||
val name = SimpleKnownName("alt-J", null)
|
||||
val name = Name.Known.SimpleFactory.parse("alt-J", null)
|
||||
assertEquals("alt-J", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("A", name.thumb)
|
||||
|
@ -63,7 +47,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_simple_oopsAllPunct() {
|
||||
val name = SimpleKnownName("!!!", null)
|
||||
val name = Name.Known.SimpleFactory.parse("!!!", null)
|
||||
assertEquals("!!!", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("!", name.thumb)
|
||||
|
@ -74,7 +58,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_simple_spacedPunct() {
|
||||
val name = SimpleKnownName("& Yet & Yet", null)
|
||||
val name = Name.Known.SimpleFactory.parse("& Yet & Yet", null)
|
||||
assertEquals("& Yet & Yet", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("Y", name.thumb)
|
||||
|
@ -85,7 +69,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_simple_withSort() {
|
||||
val name = SimpleKnownName("The Smile", "Smile")
|
||||
val name = Name.Known.SimpleFactory.parse("The Smile", "Smile")
|
||||
assertEquals("The Smile", name.raw)
|
||||
assertEquals("Smile", name.sort)
|
||||
assertEquals("S", name.thumb)
|
||||
|
@ -96,7 +80,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_withoutArticle_withoutNumerics() {
|
||||
val name = IntelligentKnownName("Loveless", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("Loveless", null)
|
||||
assertEquals("Loveless", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("L", name.thumb)
|
||||
|
@ -107,7 +91,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_withoutArticle_withSpacedStartNumerics() {
|
||||
val name = IntelligentKnownName("15 Step", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("15 Step", null)
|
||||
assertEquals("15 Step", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("#", name.thumb)
|
||||
|
@ -121,7 +105,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_withoutArticle_withPackedStartNumerics() {
|
||||
val name = IntelligentKnownName("23Kid", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("23Kid", null)
|
||||
assertEquals("23Kid", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("#", name.thumb)
|
||||
|
@ -135,7 +119,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_withoutArticle_withSpacedMiddleNumerics() {
|
||||
val name = IntelligentKnownName("Foo 1 2 Bar", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("Foo 1 2 Bar", null)
|
||||
assertEquals("Foo 1 2 Bar", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("F", name.thumb)
|
||||
|
@ -158,7 +142,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_withoutArticle_withPackedMiddleNumerics() {
|
||||
val name = IntelligentKnownName("Foo12Bar", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("Foo12Bar", null)
|
||||
assertEquals("Foo12Bar", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("F", name.thumb)
|
||||
|
@ -175,7 +159,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_withoutArticle_withSpacedEndNumerics() {
|
||||
val name = IntelligentKnownName("Foo 1", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("Foo 1", null)
|
||||
assertEquals("Foo 1", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("F", name.thumb)
|
||||
|
@ -189,7 +173,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_withoutArticle_withPackedEndNumerics() {
|
||||
val name = IntelligentKnownName("Error404", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("Error404", null)
|
||||
assertEquals("Error404", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("E", name.thumb)
|
||||
|
@ -203,7 +187,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_withThe_withoutNumerics() {
|
||||
val name = IntelligentKnownName("The National Anthem", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("The National Anthem", null)
|
||||
assertEquals("The National Anthem", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("N", name.thumb)
|
||||
|
@ -214,7 +198,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_withAn_withoutNumerics() {
|
||||
val name = IntelligentKnownName("An Eagle in Your Mind", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("An Eagle in Your Mind", null)
|
||||
assertEquals("An Eagle in Your Mind", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("E", name.thumb)
|
||||
|
@ -225,7 +209,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_withA_withoutNumerics() {
|
||||
val name = IntelligentKnownName("A Song For Our Fathers", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("A Song For Our Fathers", null)
|
||||
assertEquals("A Song For Our Fathers", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("S", name.thumb)
|
||||
|
@ -236,7 +220,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withPunct_withoutArticle_withoutNumerics() {
|
||||
val name = IntelligentKnownName("alt-J", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("alt-J", null)
|
||||
assertEquals("alt-J", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("A", name.thumb)
|
||||
|
@ -247,7 +231,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_oopsAllPunct_withoutArticle_withoutNumerics() {
|
||||
val name = IntelligentKnownName("!!!", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("!!!", null)
|
||||
assertEquals("!!!", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("!", name.thumb)
|
||||
|
@ -258,7 +242,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withoutPunct_shortArticle_withNumerics() {
|
||||
val name = IntelligentKnownName("the 1", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("the 1", null)
|
||||
assertEquals("the 1", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("#", name.thumb)
|
||||
|
@ -269,7 +253,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_spacedPunct_withoutArticle_withoutNumerics() {
|
||||
val name = IntelligentKnownName("& Yet & Yet", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("& Yet & Yet", null)
|
||||
assertEquals("& Yet & Yet", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("Y", name.thumb)
|
||||
|
@ -280,7 +264,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withPunct_withoutArticle_withNumerics() {
|
||||
val name = IntelligentKnownName("Design : 2 : 3", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("Design : 2 : 3", null)
|
||||
assertEquals("Design : 2 : 3", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("D", name.thumb)
|
||||
|
@ -300,7 +284,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_oopsAllPunct_withoutArticle_oopsAllNumerics() {
|
||||
val name = IntelligentKnownName("2 + 2 = 5", null)
|
||||
val name = Name.Known.IntelligentFactory.parse("2 + 2 = 5", null)
|
||||
assertEquals("2 + 2 = 5", name.raw)
|
||||
assertEquals(null, name.sort)
|
||||
assertEquals("#", name.thumb)
|
||||
|
@ -323,7 +307,7 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_intelligent_withSort() {
|
||||
val name = IntelligentKnownName("The Smile", "Smile")
|
||||
val name = Name.Known.IntelligentFactory.parse("The Smile", "Smile")
|
||||
assertEquals("The Smile", name.raw)
|
||||
assertEquals("Smile", name.sort)
|
||||
assertEquals("S", name.thumb)
|
||||
|
@ -334,40 +318,40 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_equals_simple() {
|
||||
val a = SimpleKnownName("The Same", "Same")
|
||||
val b = SimpleKnownName("The Same", "Same")
|
||||
val a = Name.Known.SimpleFactory.parse("The Same", "Same")
|
||||
val b = Name.Known.SimpleFactory.parse("The Same", "Same")
|
||||
assertEquals(a, b)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_equals_differentSort() {
|
||||
val a = SimpleKnownName("The Same", "Same")
|
||||
val b = SimpleKnownName("The Same", null)
|
||||
val a = Name.Known.SimpleFactory.parse("The Same", "Same")
|
||||
val b = Name.Known.SimpleFactory.parse("The Same", null)
|
||||
assertNotEquals(a, b)
|
||||
assertNotEquals(a.hashCode(), b.hashCode())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_equals_intelligent_differentTokens() {
|
||||
val a = IntelligentKnownName("The Same", "Same")
|
||||
val b = IntelligentKnownName("Same", "Same")
|
||||
val a = Name.Known.IntelligentFactory.parse("The Same", "Same")
|
||||
val b = Name.Known.IntelligentFactory.parse("Same", "Same")
|
||||
assertNotEquals(a, b)
|
||||
assertNotEquals(a.hashCode(), b.hashCode())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_compareTo_simple_withoutSort_withoutArticle_withoutNumeric() {
|
||||
val a = SimpleKnownName("A", null)
|
||||
val b = SimpleKnownName("B", null)
|
||||
val a = Name.Known.SimpleFactory.parse("A", null)
|
||||
val b = Name.Known.SimpleFactory.parse("B", null)
|
||||
assertEquals(-1, a.compareTo(b))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_compareTo_simple_withoutSort_withArticle_withoutNumeric() {
|
||||
val a = SimpleKnownName("A Brain in a Bottle", null)
|
||||
val b = SimpleKnownName("Acid Rain", null)
|
||||
val c = SimpleKnownName("Boralis / Contrastellar", null)
|
||||
val d = SimpleKnownName("Breathe In", null)
|
||||
val a = Name.Known.SimpleFactory.parse("A Brain in a Bottle", null)
|
||||
val b = Name.Known.SimpleFactory.parse("Acid Rain", null)
|
||||
val c = Name.Known.SimpleFactory.parse("Boralis / Contrastellar", null)
|
||||
val d = Name.Known.SimpleFactory.parse("Breathe In", null)
|
||||
assertEquals(-1, a.compareTo(b))
|
||||
assertEquals(-1, a.compareTo(c))
|
||||
assertEquals(-1, a.compareTo(d))
|
||||
|
@ -375,40 +359,40 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_compareTo_simple_withSort_withoutArticle_withNumeric() {
|
||||
val a = SimpleKnownName("15 Step", null)
|
||||
val b = SimpleKnownName("128 Harps", null)
|
||||
val c = SimpleKnownName("1969", null)
|
||||
val a = Name.Known.SimpleFactory.parse("15 Step", null)
|
||||
val b = Name.Known.SimpleFactory.parse("128 Harps", null)
|
||||
val c = Name.Known.SimpleFactory.parse("1969", null)
|
||||
assertEquals(1, a.compareTo(b))
|
||||
assertEquals(-1, a.compareTo(c))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_compareTo_simple_withPartialSort() {
|
||||
val a = SimpleKnownName("A", "C")
|
||||
val b = SimpleKnownName("B", null)
|
||||
val a = Name.Known.SimpleFactory.parse("A", "C")
|
||||
val b = Name.Known.SimpleFactory.parse("B", null)
|
||||
assertEquals(1, a.compareTo(b))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_compareTo_simple_withSort() {
|
||||
val a = SimpleKnownName("D", "A")
|
||||
val b = SimpleKnownName("C", "B")
|
||||
val a = Name.Known.SimpleFactory.parse("D", "A")
|
||||
val b = Name.Known.SimpleFactory.parse("C", "B")
|
||||
assertEquals(-1, a.compareTo(b))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_compareTo_intelligent_withoutSort_withoutArticle_withoutNumeric() {
|
||||
val a = IntelligentKnownName("A", null)
|
||||
val b = IntelligentKnownName("B", null)
|
||||
val a = Name.Known.IntelligentFactory.parse("A", null)
|
||||
val b = Name.Known.IntelligentFactory.parse("B", null)
|
||||
assertEquals(-1, a.compareTo(b))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_compareTo_intelligent_withoutSort_withArticle_withoutNumeric() {
|
||||
val a = IntelligentKnownName("A Brain in a Bottle", null)
|
||||
val b = IntelligentKnownName("Acid Rain", null)
|
||||
val c = IntelligentKnownName("Boralis / Contrastellar", null)
|
||||
val d = IntelligentKnownName("Breathe In", null)
|
||||
val a = Name.Known.IntelligentFactory.parse("A Brain in a Bottle", null)
|
||||
val b = Name.Known.IntelligentFactory.parse("Acid Rain", null)
|
||||
val c = Name.Known.IntelligentFactory.parse("Boralis / Contrastellar", null)
|
||||
val d = Name.Known.IntelligentFactory.parse("Breathe In", null)
|
||||
assertEquals(1, a.compareTo(b))
|
||||
assertEquals(1, a.compareTo(c))
|
||||
assertEquals(-1, a.compareTo(d))
|
||||
|
@ -416,9 +400,9 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_compareTo_intelligent_withoutSort_withoutArticle_withNumeric() {
|
||||
val a = IntelligentKnownName("15 Step", null)
|
||||
val b = IntelligentKnownName("128 Harps", null)
|
||||
val c = IntelligentKnownName("1969", null)
|
||||
val a = Name.Known.IntelligentFactory.parse("15 Step", null)
|
||||
val b = Name.Known.IntelligentFactory.parse("128 Harps", null)
|
||||
val c = Name.Known.IntelligentFactory.parse("1969", null)
|
||||
assertEquals(-1, a.compareTo(b))
|
||||
assertEquals(-1, b.compareTo(c))
|
||||
assertEquals(-2, a.compareTo(c))
|
||||
|
@ -426,15 +410,15 @@ class NameTest {
|
|||
|
||||
@Test
|
||||
fun name_compareTo_intelligent_withPartialSort_withoutArticle_withoutNumeric() {
|
||||
val a = SimpleKnownName("A", "C")
|
||||
val b = SimpleKnownName("B", null)
|
||||
val a = Name.Known.SimpleFactory.parse("A", "C")
|
||||
val b = Name.Known.SimpleFactory.parse("B", null)
|
||||
assertEquals(1, a.compareTo(b))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun name_compareTo_intelligent_withSort_withoutArticle_withoutNumeric() {
|
||||
val a = IntelligentKnownName("D", "A")
|
||||
val b = IntelligentKnownName("C", "B")
|
||||
val a = Name.Known.IntelligentFactory.parse("D", "A")
|
||||
val b = Name.Known.IntelligentFactory.parse("C", "B")
|
||||
assertEquals(-1, a.compareTo(b))
|
||||
}
|
||||
|
||||
|
@ -447,7 +431,7 @@ class NameTest {
|
|||
@Test
|
||||
fun name_compareTo_mixed() {
|
||||
val a = Name.Unknown(0)
|
||||
val b = IntelligentKnownName("A", null)
|
||||
val b = Name.Known.IntelligentFactory.parse("A", null)
|
||||
assertEquals(-1, a.compareTo(b))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue