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