diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt index 3fee8efe4..a68b04b4b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt @@ -29,9 +29,9 @@ import org.oxycblt.musikr.Indexer import org.oxycblt.musikr.IndexingProgress import org.oxycblt.musikr.Library import org.oxycblt.musikr.Music +import org.oxycblt.musikr.MutableLibrary import org.oxycblt.musikr.Playlist import org.oxycblt.musikr.Song -import org.oxycblt.musikr.model.MutableLibrary import org.oxycblt.musikr.tag.Interpretation import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.interpret.Separators diff --git a/app/src/main/java/org/oxycblt/musikr/Indexer.kt b/app/src/main/java/org/oxycblt/musikr/Indexer.kt index 1957eaad7..cadf40979 100644 --- a/app/src/main/java/org/oxycblt/musikr/Indexer.kt +++ b/app/src/main/java/org/oxycblt/musikr/Indexer.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import org.oxycblt.musikr.fs.MusicLocation -import org.oxycblt.musikr.model.MutableLibrary import org.oxycblt.musikr.pipeline.EvaluateStep import org.oxycblt.musikr.pipeline.ExploreStep import org.oxycblt.musikr.pipeline.ExtractStep diff --git a/app/src/main/java/org/oxycblt/musikr/Music.kt b/app/src/main/java/org/oxycblt/musikr/Music.kt index 2cb54d450..18219547e 100644 --- a/app/src/main/java/org/oxycblt/musikr/Music.kt +++ b/app/src/main/java/org/oxycblt/musikr/Music.kt @@ -61,6 +61,18 @@ interface Library { fun findPlaylistByName(name: String): Playlist? } +interface MutableLibrary : Library { + suspend fun createPlaylist(name: String, songs: List): MutableLibrary + + suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary + + suspend fun addToPlaylist(playlist: Playlist, songs: List): MutableLibrary + + suspend fun rewritePlaylist(playlist: Playlist, songs: List): MutableLibrary + + suspend fun deletePlaylist(playlist: Playlist): MutableLibrary +} + /** * Abstract music data. This contains universal information about all concrete music * implementations, such as identification information and names. diff --git a/app/src/main/java/org/oxycblt/musikr/model/AlbumImpl.kt b/app/src/main/java/org/oxycblt/musikr/model/AlbumImpl.kt new file mode 100644 index 000000000..612ef1968 --- /dev/null +++ b/app/src/main/java/org/oxycblt/musikr/model/AlbumImpl.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 Auxio Project + * AlbumImpl.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.musikr.model + +import org.oxycblt.auxio.music.MusicType +import org.oxycblt.auxio.util.update +import org.oxycblt.musikr.Album +import org.oxycblt.musikr.Artist +import org.oxycblt.musikr.Music +import org.oxycblt.musikr.Song +import org.oxycblt.musikr.cover.Cover +import org.oxycblt.musikr.tag.Date +import org.oxycblt.musikr.tag.interpret.PreAlbum + +interface AlbumCore { + val preAlbum: PreAlbum + val songs: List + + fun resolveArtists(): List +} + +/** + * Library-backed implementation of [Album]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +class AlbumImpl(private val core: AlbumCore) : Album { + private val preAlbum = core.preAlbum + + override val uid = + // Attempt to use a MusicBrainz ID first before falling back to a hashed UID. + preAlbum.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ALBUMS, it) } + ?: Music.UID.auxio(MusicType.ALBUMS) { + // Hash based on only names despite the presence of a date to increase stability. + // I don't know if there is any situation where an artist will have two albums with + // the exact same name, but if there is, I would love to know. + update(preAlbum.rawName) + update(preAlbum.preArtists.map { it.rawName }) + } + override val name = preAlbum.name + override val releaseType = preAlbum.releaseType + override val durationMs = core.songs.sumOf { it.durationMs } + override val dateAdded = core.songs.minOf { it.dateAdded } + override val cover = Cover.multi(core.songs) + override val dates: Date.Range? = + core.songs.mapNotNull { it.date }.ifEmpty { null }?.run { Date.Range(min(), max()) } + + override val artists: List + get() = core.resolveArtists() + + override val songs = core.songs + + private val hashCode = 31 * (31 * uid.hashCode() + preAlbum.hashCode()) + songs.hashCode() + + override fun hashCode() = hashCode + + override fun equals(other: Any?) = + other is AlbumImpl && uid == other.uid && preAlbum == other.preAlbum && songs == other.songs + + override fun toString() = "Album(uid=$uid, name=$name)" +} diff --git a/app/src/main/java/org/oxycblt/musikr/model/ArtistImpl.kt b/app/src/main/java/org/oxycblt/musikr/model/ArtistImpl.kt new file mode 100644 index 000000000..22b724a50 --- /dev/null +++ b/app/src/main/java/org/oxycblt/musikr/model/ArtistImpl.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024 Auxio Project + * ArtistImpl.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.musikr.model + +import org.oxycblt.auxio.music.MusicType +import org.oxycblt.auxio.util.update +import org.oxycblt.musikr.Album +import org.oxycblt.musikr.Artist +import org.oxycblt.musikr.Genre +import org.oxycblt.musikr.Music +import org.oxycblt.musikr.Song +import org.oxycblt.musikr.cover.Cover +import org.oxycblt.musikr.tag.interpret.PreArtist + +interface ArtistCore { + val preArtist: PreArtist + val songs: Set + val albums: Set + + fun resolveGenres(): Set +} + +/** + * Library-backed implementation of [Artist]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +class ArtistImpl(private val core: ArtistCore) : Artist { + override val uid = + // Attempt to use a MusicBrainz ID first before falling back to a hashed UID. + core.preArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ARTISTS, it) } + ?: Music.UID.auxio(MusicType.ARTISTS) { update(core.preArtist.rawName) } + override val name = core.preArtist.name + + override val songs = core.songs + override var explicitAlbums = core.albums + override var implicitAlbums = core.songs.mapTo(mutableSetOf()) { it.album } - core.albums + + override val genres: List + get() = core.resolveGenres().toList() + + override val durationMs = core.songs.sumOf { it.durationMs } + override val cover = Cover.multi(core.songs) + + private val hashCode = + 31 * (31 * uid.hashCode() + core.preArtist.hashCode()) * core.songs.hashCode() + + override fun hashCode() = hashCode + + override fun equals(other: Any?) = + other is ArtistImpl && + uid == other.uid && + core.preArtist == other.core.preArtist && + songs == other.songs + + override fun toString() = "Artist(uid=$uid, name=$name)" +} diff --git a/app/src/main/java/org/oxycblt/musikr/model/DeviceMusicImpl.kt b/app/src/main/java/org/oxycblt/musikr/model/DeviceMusicImpl.kt deleted file mode 100644 index e56821e1d..000000000 --- a/app/src/main/java/org/oxycblt/musikr/model/DeviceMusicImpl.kt +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (c) 2023 Auxio Project - * DeviceMusicImpl.kt is part of Auxio. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.oxycblt.musikr.model - -import org.oxycblt.auxio.music.MusicType -import org.oxycblt.auxio.util.update -import org.oxycblt.musikr.Album -import org.oxycblt.musikr.Artist -import org.oxycblt.musikr.Genre -import org.oxycblt.musikr.Music -import org.oxycblt.musikr.Song -import org.oxycblt.musikr.cover.Cover -import org.oxycblt.musikr.tag.Date -import org.oxycblt.musikr.tag.interpret.PreAlbum -import org.oxycblt.musikr.tag.interpret.PreArtist -import org.oxycblt.musikr.tag.interpret.PreGenre -import org.oxycblt.musikr.tag.interpret.PreSong - -interface SongHandle { - val preSong: PreSong - - fun resolveAlbum(): Album - - fun resolveArtists(): List - - fun resolveGenres(): List -} - -/** - * Library-backed implementation of [Song]. - * - * @author Alexander Capehart (OxygenCobalt) - */ -class SongImpl(private val handle: SongHandle) : Song { - private val preSong = handle.preSong - - override val uid = preSong.computeUid() - override val name = preSong.name - override val track = preSong.track - override val disc = preSong.disc - override val date = preSong.date - override val uri = preSong.uri - override val path = preSong.path - override val mimeType = preSong.mimeType - override val size = preSong.size - override val durationMs = preSong.durationMs - override val replayGainAdjustment = preSong.replayGainAdjustment - override val lastModified = preSong.lastModified - override val dateAdded = preSong.dateAdded - override val cover = Cover.single(this) - override val album: Album - get() = handle.resolveAlbum() - - override val artists: List - get() = handle.resolveArtists() - - override val genres: List - get() = handle.resolveGenres() - - private val hashCode = 31 * uid.hashCode() + preSong.hashCode() - - override fun hashCode() = hashCode - - override fun equals(other: Any?) = - other is SongImpl && uid == other.uid && preSong == other.preSong - - override fun toString() = "Song(uid=$uid, name=$name)" -} - -interface AlbumHandle { - val preAlbum: PreAlbum - val songs: List - - fun resolveArtists(): List -} - -/** - * Library-backed implementation of [Album]. - * - * @author Alexander Capehart (OxygenCobalt) - */ -class AlbumImpl(private val handle: AlbumHandle) : Album { - private val preAlbum = handle.preAlbum - - override val uid = - // Attempt to use a MusicBrainz ID first before falling back to a hashed UID. - preAlbum.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ALBUMS, it) } - ?: Music.UID.auxio(MusicType.ALBUMS) { - // Hash based on only names despite the presence of a date to increase stability. - // I don't know if there is any situation where an artist will have two albums with - // the exact same name, but if there is, I would love to know. - update(preAlbum.rawName) - update(preAlbum.preArtists.map { it.rawName }) - } - override val name = preAlbum.name - override val releaseType = preAlbum.releaseType - override val durationMs = handle.songs.sumOf { it.durationMs } - override val dateAdded = handle.songs.minOf { it.dateAdded } - override val cover = Cover.multi(handle.songs) - override val dates: Date.Range? = - handle.songs.mapNotNull { it.date }.ifEmpty { null }?.run { Date.Range(min(), max()) } - - override val artists: List - get() = handle.resolveArtists() - - override val songs = handle.songs - - private val hashCode = 31 * (31 * uid.hashCode() + preAlbum.hashCode()) + songs.hashCode() - - override fun hashCode() = hashCode - - override fun equals(other: Any?) = - other is AlbumImpl && uid == other.uid && preAlbum == other.preAlbum && songs == other.songs - - override fun toString() = "Album(uid=$uid, name=$name)" -} - -interface ArtistHandle { - val preArtist: PreArtist - val songs: Set - val albums: Set - - fun resolveGenres(): Set -} - -/** - * Library-backed implementation of [Artist]. - * - * @author Alexander Capehart (OxygenCobalt) - */ -class ArtistImpl(private val handle: ArtistHandle) : Artist { - override val uid = - // Attempt to use a MusicBrainz ID first before falling back to a hashed UID. - handle.preArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicType.ARTISTS, it) } - ?: Music.UID.auxio(MusicType.ARTISTS) { update(handle.preArtist.rawName) } - override val name = handle.preArtist.name - - override val songs = handle.songs - override var explicitAlbums = handle.albums - override var implicitAlbums = handle.songs.mapTo(mutableSetOf()) { it.album } - handle.albums - - override val genres: List - get() = handle.resolveGenres().toList() - - override val durationMs = handle.songs.sumOf { it.durationMs } - override val cover = Cover.multi(handle.songs) - - private val hashCode = - 31 * (31 * uid.hashCode() + handle.preArtist.hashCode()) * handle.songs.hashCode() - - override fun hashCode() = hashCode - - override fun equals(other: Any?) = - other is ArtistImpl && - uid == other.uid && - handle.preArtist == other.handle.preArtist && - songs == other.songs - - override fun toString() = "Artist(uid=$uid, name=$name)" -} - -interface GenreHandle { - val preGenre: PreGenre - val songs: Set - val artists: Set -} - -/** - * Library-backed implementation of [Genre]. - * - * @author Alexander Capehart (OxygenCobalt) - */ -class GenreImpl(private val handle: GenreHandle) : Genre { - override val uid = Music.UID.auxio(MusicType.GENRES) { update(handle.preGenre.rawName) } - override val name = handle.preGenre.name - - override val songs = mutableSetOf() - override val artists = mutableSetOf() - override val durationMs = handle.songs.sumOf { it.durationMs } - override val cover = Cover.multi(handle.songs) - - private val hashCode = - 31 * (31 * uid.hashCode() + handle.preGenre.hashCode()) + songs.hashCode() - - override fun hashCode() = hashCode - - override fun equals(other: Any?) = - other is GenreImpl && - uid == other.uid && - handle.preGenre == other.handle.preGenre && - songs == other.songs - - override fun toString() = "Genre(uid=$uid, name=$name)" -} diff --git a/app/src/main/java/org/oxycblt/musikr/model/GenreImpl.kt b/app/src/main/java/org/oxycblt/musikr/model/GenreImpl.kt new file mode 100644 index 000000000..d629b8778 --- /dev/null +++ b/app/src/main/java/org/oxycblt/musikr/model/GenreImpl.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 Auxio Project + * GenreImpl.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.musikr.model + +import org.oxycblt.auxio.music.MusicType +import org.oxycblt.auxio.util.update +import org.oxycblt.musikr.Artist +import org.oxycblt.musikr.Genre +import org.oxycblt.musikr.Music +import org.oxycblt.musikr.Song +import org.oxycblt.musikr.cover.Cover +import org.oxycblt.musikr.tag.interpret.PreGenre + +interface GenreCore { + val preGenre: PreGenre + val songs: Set + val artists: Set +} + +/** + * Library-backed implementation of [Genre]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +class GenreImpl(private val core: GenreCore) : Genre { + override val uid = Music.UID.auxio(MusicType.GENRES) { update(core.preGenre.rawName) } + override val name = core.preGenre.name + + override val songs = mutableSetOf() + override val artists = mutableSetOf() + override val durationMs = core.songs.sumOf { it.durationMs } + override val cover = Cover.multi(core.songs) + + private val hashCode = 31 * (31 * uid.hashCode() + core.preGenre.hashCode()) + songs.hashCode() + + override fun hashCode() = hashCode + + override fun equals(other: Any?) = + other is GenreImpl && + uid == other.uid && + core.preGenre == other.core.preGenre && + songs == other.songs + + override fun toString() = "Genre(uid=$uid, name=$name)" +} diff --git a/app/src/main/java/org/oxycblt/musikr/model/Library.kt b/app/src/main/java/org/oxycblt/musikr/model/Library.kt deleted file mode 100644 index 124204e43..000000000 --- a/app/src/main/java/org/oxycblt/musikr/model/Library.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2024 Auxio Project - * Library.kt is part of Auxio. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.oxycblt.musikr.model - -import javax.inject.Inject -import org.oxycblt.musikr.Album -import org.oxycblt.musikr.Artist -import org.oxycblt.musikr.Genre -import org.oxycblt.musikr.Library -import org.oxycblt.musikr.Music -import org.oxycblt.musikr.Playlist -import org.oxycblt.musikr.Song -import org.oxycblt.musikr.fs.Path -import org.oxycblt.musikr.graph.AlbumVertex -import org.oxycblt.musikr.graph.ArtistVertex -import org.oxycblt.musikr.graph.GenreVertex -import org.oxycblt.musikr.graph.MusicGraph -import org.oxycblt.musikr.graph.SongVertex - -interface MutableLibrary : Library { - suspend fun createPlaylist(name: String, songs: List): MutableLibrary - - suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary - - suspend fun addToPlaylist(playlist: Playlist, songs: List): MutableLibrary - - suspend fun rewritePlaylist(playlist: Playlist, songs: List): MutableLibrary - - suspend fun deletePlaylist(playlist: Playlist): MutableLibrary -} - -interface LibraryFactory { - fun create(graph: MusicGraph): MutableLibrary -} - -class LibraryFactoryImpl @Inject constructor() : LibraryFactory { - override fun create(graph: MusicGraph): MutableLibrary { - val songs = - graph.songVertex.mapTo(mutableSetOf()) { vertex -> - SongImpl(SongVertexHandle(vertex)).also { vertex.tag = it } - } - val albums = - graph.albumVertex.mapTo(mutableSetOf()) { vertex -> - AlbumImpl(AlbumVertexHandle(vertex)).also { vertex.tag = it } - } - val artists = - graph.artistVertex.mapTo(mutableSetOf()) { vertex -> - ArtistImpl(ArtistVertexHandle(vertex)).also { vertex.tag = it } - } - val genres = - graph.genreVertex.mapTo(mutableSetOf()) { vertex -> - GenreImpl(GenreVertexHandle(vertex)).also { vertex.tag = it } - } - return LibraryImpl(songs, albums, artists, genres) - } - - private class SongVertexHandle(private val vertex: SongVertex) : SongHandle { - override val preSong = vertex.preSong - - override fun resolveAlbum() = vertex.albumVertex.tag as Album - - override fun resolveArtists() = vertex.artistVertices.map { it.tag as Artist } - - override fun resolveGenres() = vertex.genreVertices.map { it.tag as Genre } - } - - private class AlbumVertexHandle(private val vertex: AlbumVertex) : AlbumHandle { - override val preAlbum = vertex.preAlbum - - override val songs = vertex.songVertices.map { SongImpl(SongVertexHandle(it)) } - - override fun resolveArtists() = vertex.artistVertices.map { it.tag as Artist } - } - - private class ArtistVertexHandle(private val vertex: ArtistVertex) : ArtistHandle { - override val preArtist = vertex.preArtist - - override val songs = vertex.songVertices.mapTo(mutableSetOf()) { it.tag as Song } - - override val albums = vertex.albumVertices.mapTo(mutableSetOf()) { it.tag as Album } - - override fun resolveGenres() = - vertex.genreVertices.mapTo(mutableSetOf()) { it.tag as Genre } - } - - private class GenreVertexHandle(vertex: GenreVertex) : GenreHandle { - override val preGenre = vertex.preGenre - - override val songs = vertex.songVertices.mapTo(mutableSetOf()) { it.tag as Song } - - override val artists = vertex.artistVertices.mapTo(mutableSetOf()) { it.tag as Artist } - } -} - -class LibraryImpl( - override val songs: Collection, - override val albums: Collection, - override val artists: Collection, - override val genres: Collection -) : MutableLibrary { - override val playlists = emptySet() - - private val songUidMap = songs.associateBy { it.uid } - private val albumUidMap = albums.associateBy { it.uid } - private val artistUidMap = artists.associateBy { it.uid } - private val genreUidMap = genres.associateBy { it.uid } - private val playlistUidMap = playlists.associateBy { it.uid } - - override fun findSong(uid: Music.UID) = songUidMap[uid] - - override fun findSongByPath(path: Path) = songs.find { it.path == path } - - override fun findAlbum(uid: Music.UID) = albumUidMap[uid] - - override fun findArtist(uid: Music.UID) = artistUidMap[uid] - - override fun findGenre(uid: Music.UID) = genreUidMap[uid] - - override fun findPlaylist(uid: Music.UID) = playlistUidMap[uid] - - override fun findPlaylistByName(name: String) = playlists.find { it.name.raw == name } - - override suspend fun createPlaylist(name: String, songs: List): MutableLibrary { - return this - } - - override suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary { - return this - } - - override suspend fun addToPlaylist(playlist: Playlist, songs: List): MutableLibrary { - return this - } - - override suspend fun rewritePlaylist(playlist: Playlist, songs: List): MutableLibrary { - return this - } - - override suspend fun deletePlaylist(playlist: Playlist): MutableLibrary { - return this - } -} diff --git a/app/src/main/java/org/oxycblt/musikr/model/LibraryFactory.kt b/app/src/main/java/org/oxycblt/musikr/model/LibraryFactory.kt new file mode 100644 index 000000000..f823fee76 --- /dev/null +++ b/app/src/main/java/org/oxycblt/musikr/model/LibraryFactory.kt @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 Auxio Project + * LibraryFactory.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.musikr.model + +import javax.inject.Inject +import org.oxycblt.musikr.Album +import org.oxycblt.musikr.Artist +import org.oxycblt.musikr.Genre +import org.oxycblt.musikr.MutableLibrary +import org.oxycblt.musikr.Song +import org.oxycblt.musikr.graph.AlbumVertex +import org.oxycblt.musikr.graph.ArtistVertex +import org.oxycblt.musikr.graph.GenreVertex +import org.oxycblt.musikr.graph.MusicGraph +import org.oxycblt.musikr.graph.SongVertex + +interface LibraryFactory { + fun create(graph: MusicGraph): MutableLibrary +} + +class LibraryFactoryImpl @Inject constructor() : LibraryFactory { + override fun create(graph: MusicGraph): MutableLibrary { + val songs = + graph.songVertex.mapTo(mutableSetOf()) { vertex -> + SongImpl(SongVertexCore(vertex)).also { vertex.tag = it } + } + val albums = + graph.albumVertex.mapTo(mutableSetOf()) { vertex -> + AlbumImpl(AlbumVertexCore(vertex)).also { vertex.tag = it } + } + val artists = + graph.artistVertex.mapTo(mutableSetOf()) { vertex -> + ArtistImpl(ArtistVertexCore(vertex)).also { vertex.tag = it } + } + val genres = + graph.genreVertex.mapTo(mutableSetOf()) { vertex -> + GenreImpl(GenreVertexCore(vertex)).also { vertex.tag = it } + } + return LibraryImpl(songs, albums, artists, genres) + } + + private class SongVertexCore(private val vertex: SongVertex) : SongCore { + override val preSong = vertex.preSong + + override fun resolveAlbum() = vertex.albumVertex.tag as Album + + override fun resolveArtists() = vertex.artistVertices.map { it.tag as Artist } + + override fun resolveGenres() = vertex.genreVertices.map { it.tag as Genre } + } + + private class AlbumVertexCore(private val vertex: AlbumVertex) : AlbumCore { + override val preAlbum = vertex.preAlbum + + override val songs = vertex.songVertices.map { SongImpl(SongVertexCore(it)) } + + override fun resolveArtists() = vertex.artistVertices.map { it.tag as Artist } + } + + private class ArtistVertexCore(private val vertex: ArtistVertex) : ArtistCore { + override val preArtist = vertex.preArtist + + override val songs = vertex.songVertices.mapTo(mutableSetOf()) { it.tag as Song } + + override val albums = vertex.albumVertices.mapTo(mutableSetOf()) { it.tag as Album } + + override fun resolveGenres() = + vertex.genreVertices.mapTo(mutableSetOf()) { it.tag as Genre } + } + + private class GenreVertexCore(vertex: GenreVertex) : GenreCore { + override val preGenre = vertex.preGenre + + override val songs = vertex.songVertices.mapTo(mutableSetOf()) { it.tag as Song } + + override val artists = vertex.artistVertices.mapTo(mutableSetOf()) { it.tag as Artist } + } +} diff --git a/app/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt b/app/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt new file mode 100644 index 000000000..f3d5a08d9 --- /dev/null +++ b/app/src/main/java/org/oxycblt/musikr/model/LibraryImpl.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 Auxio Project + * LibraryImpl.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.musikr.model + +import org.oxycblt.musikr.Music +import org.oxycblt.musikr.MutableLibrary +import org.oxycblt.musikr.Playlist +import org.oxycblt.musikr.Song +import org.oxycblt.musikr.fs.Path + +class LibraryImpl( + override val songs: Collection, + override val albums: Collection, + override val artists: Collection, + override val genres: Collection +) : MutableLibrary { + override val playlists = emptySet() + + private val songUidMap = songs.associateBy { it.uid } + private val albumUidMap = albums.associateBy { it.uid } + private val artistUidMap = artists.associateBy { it.uid } + private val genreUidMap = genres.associateBy { it.uid } + private val playlistUidMap = playlists.associateBy { it.uid } + + override fun findSong(uid: Music.UID) = songUidMap[uid] + + override fun findSongByPath(path: Path) = songs.find { it.path == path } + + override fun findAlbum(uid: Music.UID) = albumUidMap[uid] + + override fun findArtist(uid: Music.UID) = artistUidMap[uid] + + override fun findGenre(uid: Music.UID) = genreUidMap[uid] + + override fun findPlaylist(uid: Music.UID) = playlistUidMap[uid] + + override fun findPlaylistByName(name: String) = playlists.find { it.name.raw == name } + + override suspend fun createPlaylist(name: String, songs: List): MutableLibrary { + return this + } + + override suspend fun renamePlaylist(playlist: Playlist, name: String): MutableLibrary { + return this + } + + override suspend fun addToPlaylist(playlist: Playlist, songs: List): MutableLibrary { + return this + } + + override suspend fun rewritePlaylist(playlist: Playlist, songs: List): MutableLibrary { + return this + } + + override suspend fun deletePlaylist(playlist: Playlist): MutableLibrary { + return this + } +} diff --git a/app/src/main/java/org/oxycblt/musikr/model/SongImpl.kt b/app/src/main/java/org/oxycblt/musikr/model/SongImpl.kt new file mode 100644 index 000000000..f0bde5620 --- /dev/null +++ b/app/src/main/java/org/oxycblt/musikr/model/SongImpl.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 Auxio Project + * SongImpl.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.musikr.model + +import org.oxycblt.musikr.Album +import org.oxycblt.musikr.Artist +import org.oxycblt.musikr.Genre +import org.oxycblt.musikr.Song +import org.oxycblt.musikr.cover.Cover +import org.oxycblt.musikr.tag.interpret.PreSong + +interface SongCore { + val preSong: PreSong + + fun resolveAlbum(): Album + + fun resolveArtists(): List + + fun resolveGenres(): List +} + +/** + * Library-backed implementation of [Song]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +class SongImpl(private val handle: SongCore) : Song { + private val preSong = handle.preSong + + override val uid = preSong.computeUid() + override val name = preSong.name + override val track = preSong.track + override val disc = preSong.disc + override val date = preSong.date + override val uri = preSong.uri + override val path = preSong.path + override val mimeType = preSong.mimeType + override val size = preSong.size + override val durationMs = preSong.durationMs + override val replayGainAdjustment = preSong.replayGainAdjustment + override val lastModified = preSong.lastModified + override val dateAdded = preSong.dateAdded + override val cover = Cover.single(this) + override val album: Album + get() = handle.resolveAlbum() + + override val artists: List + get() = handle.resolveArtists() + + override val genres: List + get() = handle.resolveGenres() + + private val hashCode = 31 * uid.hashCode() + preSong.hashCode() + + override fun hashCode() = hashCode + + override fun equals(other: Any?) = + other is SongImpl && uid == other.uid && preSong == other.preSong + + override fun toString() = "Song(uid=$uid, name=$name)" +} diff --git a/app/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt b/app/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt index 02165e385..605c40d5a 100644 --- a/app/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt +++ b/app/src/main/java/org/oxycblt/musikr/pipeline/EvaluateStep.kt @@ -26,9 +26,9 @@ import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import org.oxycblt.musikr.MutableLibrary import org.oxycblt.musikr.graph.MusicGraph import org.oxycblt.musikr.model.LibraryFactory -import org.oxycblt.musikr.model.MutableLibrary import org.oxycblt.musikr.tag.Interpretation import org.oxycblt.musikr.tag.interpret.TagInterpreter