musikr: add date added support w/cache
This allows me to replicate something resembling date added support while reducing query load.
This commit is contained in:
parent
da76a03298
commit
4f920e922d
6 changed files with 46 additions and 21 deletions
|
@ -27,6 +27,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
import org.oxycblt.musikr.cache.Cache
|
||||
import org.oxycblt.musikr.cache.StoredCache
|
||||
import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
||||
|
||||
@Module
|
||||
|
@ -40,7 +41,7 @@ interface MusicModule {
|
|||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class MusikrShimModule {
|
||||
@Singleton @Provides fun cache(@ApplicationContext context: Context) = Cache.from(context)
|
||||
@Singleton @Provides fun storedCache(@ApplicationContext context: Context) = StoredCache.from(context)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
|
|
|
@ -37,8 +37,7 @@ import org.oxycblt.musikr.MutableLibrary
|
|||
import org.oxycblt.musikr.Playlist
|
||||
import org.oxycblt.musikr.Song
|
||||
import org.oxycblt.musikr.Storage
|
||||
import org.oxycblt.musikr.cache.Cache
|
||||
import org.oxycblt.musikr.cache.WriteOnlyCache
|
||||
import org.oxycblt.musikr.cache.StoredCache
|
||||
import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
||||
import org.oxycblt.musikr.tag.interpret.Naming
|
||||
import org.oxycblt.musikr.tag.interpret.Separators
|
||||
|
@ -214,7 +213,7 @@ class MusicRepositoryImpl
|
|||
@Inject
|
||||
constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val cache: Cache,
|
||||
private val storedCache: StoredCache,
|
||||
private val storedPlaylists: StoredPlaylists,
|
||||
private val musicSettings: MusicSettings
|
||||
) : MusicRepository {
|
||||
|
@ -363,7 +362,7 @@ constructor(
|
|||
|
||||
val currentRevision = musicSettings.revision
|
||||
val newRevision = currentRevision?.takeIf { withCache } ?: UUID.randomUUID()
|
||||
val cache = if (withCache) cache else WriteOnlyCache(cache)
|
||||
val cache = if (withCache) storedCache.full() else storedCache.writeOnly()
|
||||
val covers = MutableRevisionedStoredCovers(context, newRevision)
|
||||
val storage = Storage(cache, covers, storedPlaylists)
|
||||
val interpretation = Interpretation(nameFactory, separators)
|
||||
|
|
|
@ -27,30 +27,48 @@ abstract class Cache {
|
|||
internal abstract suspend fun read(file: DeviceFile, storedCovers: StoredCovers): CacheResult
|
||||
|
||||
internal abstract suspend fun write(song: RawSong)
|
||||
}
|
||||
|
||||
interface StoredCache {
|
||||
fun full(): Cache
|
||||
|
||||
fun writeOnly(): Cache
|
||||
|
||||
companion object {
|
||||
fun from(context: Context): Cache = CacheImpl(CacheDatabase.from(context).cachedSongsDao())
|
||||
fun from(context: Context): StoredCache = StoredCacheImpl(CacheDatabase.from(context))
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed interface CacheResult {
|
||||
data class Hit(val song: RawSong) : CacheResult
|
||||
|
||||
data class Miss(val file: DeviceFile) : CacheResult
|
||||
data class Miss(val file: DeviceFile, val dateAdded: Long?) : CacheResult
|
||||
}
|
||||
|
||||
private class StoredCacheImpl(private val database: CacheDatabase) : StoredCache {
|
||||
override fun full() = CacheImpl(database.cachedSongsDao())
|
||||
|
||||
override fun writeOnly() = WriteOnlyCacheImpl(database.cachedSongsDao())
|
||||
}
|
||||
|
||||
private class CacheImpl(private val cacheInfoDao: CacheInfoDao) : Cache() {
|
||||
override suspend fun read(file: DeviceFile, storedCovers: StoredCovers) =
|
||||
cacheInfoDao.selectSong(file.uri.toString(), file.lastModified)?.let {
|
||||
CacheResult.Hit(it.intoRawSong(file, storedCovers))
|
||||
} ?: CacheResult.Miss(file)
|
||||
override suspend fun read(file: DeviceFile, storedCovers: StoredCovers): CacheResult {
|
||||
val song = cacheInfoDao.selectSong(file.uri.toString()) ?:
|
||||
return CacheResult.Miss(file, null)
|
||||
if (song.dateModified != file.lastModified) {
|
||||
return CacheResult.Miss(file, song.dateAdded)
|
||||
}
|
||||
return CacheResult.Hit(song.intoRawSong(file, storedCovers))
|
||||
}
|
||||
|
||||
override suspend fun write(song: RawSong) =
|
||||
cacheInfoDao.updateSong(CachedSong.fromRawSong(song))
|
||||
}
|
||||
|
||||
class WriteOnlyCache(private val inner: Cache) : Cache() {
|
||||
override suspend fun read(file: DeviceFile, storedCovers: StoredCovers) = CacheResult.Miss(file)
|
||||
private class WriteOnlyCacheImpl(private val cacheInfoDao: CacheInfoDao) : Cache() {
|
||||
override suspend fun read(file: DeviceFile, storedCovers: StoredCovers) =
|
||||
CacheResult.Miss(file, cacheInfoDao.selectDateAdded(file.uri.toString()))
|
||||
|
||||
override suspend fun write(song: RawSong) = inner.write(song)
|
||||
override suspend fun write(song: RawSong) =
|
||||
cacheInfoDao.updateSong(CachedSong.fromRawSong(song))
|
||||
}
|
||||
|
|
|
@ -54,8 +54,11 @@ internal abstract class CacheDatabase : RoomDatabase() {
|
|||
|
||||
@Dao
|
||||
internal interface CacheInfoDao {
|
||||
@Query("SELECT * FROM CachedSong WHERE uri = :uri AND dateModified = :dateModified")
|
||||
suspend fun selectSong(uri: String, dateModified: Long): CachedSong?
|
||||
@Query("SELECT * FROM CachedSong WHERE uri = :uri")
|
||||
suspend fun selectSong(uri: String): CachedSong?
|
||||
|
||||
@Query("SELECT dateAdded FROM CachedSong WHERE uri = :uri")
|
||||
suspend fun selectDateAdded(uri: String): Long?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun updateSong(cachedSong: CachedSong)
|
||||
}
|
||||
|
@ -65,6 +68,7 @@ internal interface CacheInfoDao {
|
|||
internal data class CachedSong(
|
||||
@PrimaryKey val uri: String,
|
||||
val dateModified: Long,
|
||||
val dateAdded: Long,
|
||||
val mimeType: String,
|
||||
val durationMs: Long,
|
||||
val bitrateHz: Int,
|
||||
|
@ -117,7 +121,8 @@ internal data class CachedSong(
|
|||
genreNames = genreNames,
|
||||
replayGainTrackAdjustment = replayGainTrackAdjustment,
|
||||
replayGainAlbumAdjustment = replayGainAlbumAdjustment),
|
||||
coverId?.let { storedCovers.obtain(it) })
|
||||
coverId?.let { storedCovers.obtain(it) },
|
||||
dateAdded = dateAdded)
|
||||
|
||||
object Converters {
|
||||
@TypeConverter
|
||||
|
@ -137,6 +142,7 @@ internal data class CachedSong(
|
|||
CachedSong(
|
||||
uri = rawSong.file.uri.toString(),
|
||||
dateModified = rawSong.file.lastModified,
|
||||
dateAdded = rawSong.dateAdded,
|
||||
musicBrainzId = rawSong.tags.musicBrainzId,
|
||||
name = rawSong.tags.name,
|
||||
sortName = rawSong.tags.sortName,
|
||||
|
|
|
@ -63,6 +63,7 @@ private class ExtractStepImpl(
|
|||
private val storedCovers: MutableStoredCovers
|
||||
) : ExtractStep {
|
||||
override fun extract(nodes: Flow<ExploreNode>): Flow<ExtractedMusic> {
|
||||
val startTime = System.currentTimeMillis()
|
||||
val filterFlow =
|
||||
nodes.divert {
|
||||
when (it) {
|
||||
|
@ -127,7 +128,7 @@ private class ExtractStepImpl(
|
|||
.mapNotNull { fileWith ->
|
||||
val tags = tagParser.parse(fileWith.file, fileWith.with)
|
||||
val cover = fileWith.with.cover?.let { storedCovers.write(it) }
|
||||
RawSong(fileWith.file, fileWith.with.properties, tags, cover)
|
||||
RawSong(fileWith.file, fileWith.with.properties, tags, cover, startTime)
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
.buffer(Channel.UNLIMITED)
|
||||
|
@ -163,7 +164,8 @@ internal data class RawSong(
|
|||
val file: DeviceFile,
|
||||
val properties: Properties,
|
||||
val tags: ParsedTags,
|
||||
val cover: Cover?
|
||||
val cover: Cover?,
|
||||
val dateAdded: Long
|
||||
)
|
||||
|
||||
internal sealed interface ExtractedMusic {
|
||||
|
|
|
@ -65,8 +65,7 @@ private class TagInterpreterImpl(private val interpretation: Interpretation) : T
|
|||
size = song.file.size,
|
||||
format = Format.infer(song.file.mimeType, song.properties.mimeType),
|
||||
lastModified = song.file.lastModified,
|
||||
// TODO: Figure out what to do with date added
|
||||
dateAdded = song.file.lastModified,
|
||||
dateAdded = song.dateAdded,
|
||||
musicBrainzId = song.tags.musicBrainzId?.toUuidOrNull(),
|
||||
name = interpretation.naming.name(song.tags.name, song.tags.sortName),
|
||||
rawName = song.tags.name,
|
||||
|
|
Loading…
Reference in a new issue