musikr: document cache api

This commit is contained in:
Alexander Capehart 2025-03-17 13:05:47 -06:00
parent f213c21225
commit a9707cbb33
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 98 additions and 0 deletions

View file

@ -22,28 +22,100 @@ import org.oxycblt.musikr.fs.device.DeviceFile
import org.oxycblt.musikr.metadata.Properties
import org.oxycblt.musikr.tag.parse.ParsedTags
/**
* An immutable repository for cached song metadata.
*
* Since file opening and metadata extraction sends to be quite slow on Android, a cache allows
* up-to-date metadata to be read from a local database, which tends to be far faster.
*
* This is a read-only interface for reading cached metadata and isn't expected by Musikr's public
* API, however there might be some use in external cache diagnostics by the client. For writing,
* see [MutableCache].
*/
interface Cache {
/**
* Read a [CachedSong] corresponding to the given [file] from the cache. This can result in
* several outcomes represented by [CacheResult].
*
* @param file the [DeviceFile] to read from the cache
* @return a [CacheResult] representing the result of the operation.
*/
suspend fun read(file: DeviceFile): CacheResult
}
/**
* A mutable repository for cached song metadata.
*
* Since file opening and metadata extraction sends to be quite slow on Android, a cache allows
* up-to-date metadata to be saved to a local database, which tends to be far faster.
*
* This is required by Musikr's public API for proper function.
*/
interface MutableCache : Cache {
/**
* Write a [CachedSong] to the cache.
*
* This should commit the metadata to the repository in such a way that it can be retrieved
* later by [read] using only the [DeviceFile].
*
* @param cachedSong the [CachedSong] to write to the cache
*/
suspend fun write(cachedSong: CachedSong)
/**
* Cleanup the cache by removing all [CachedSong]s that are not in the provided [excluding]
* list.
*
* This is paramount for any long-term persistence to maintain correct Date added metadata and
* to avoid having space taken up by useless data.
*
* @param excluding a list of [CachedSong]s to exclude from cleanup, analogous to the library
* created by the loader this cache is used with.
*/
suspend fun cleanup(excluding: List<CachedSong>)
}
/** A cached song entry containing the data needed by the rest of the loader. */
data class CachedSong(
/** The file this song corresponds to. */
val file: DeviceFile,
/** The properties of the song. */
val properties: Properties,
/** The parsed tags of the song. */
val tags: ParsedTags,
/**
* The cover ID of the song. Should be understandable by the [org.oxycblt.musikr.covers.Covers]
* implementation used.
*/
val coverId: String?,
/**
* The time the song was added to the cache. Used for date added values. Should not be used for
* cleanup since it is unlikely to be monotonic.
*/
val addedMs: Long
)
/** A result of a cache lookup. */
sealed interface CacheResult {
/**
* A cache entry was found.
*
* @param song the [CachedSong] that was found.
*/
data class Hit(val song: CachedSong) : CacheResult
/**
* A cache entry was not found.
*
* @param file the [DeviceFile] that could not be found in the cache.
*/
data class Miss(val file: DeviceFile) : CacheResult
/**
* A cache entry was found, but it's out of date compared to the [file] given.
*
* @param file the [DeviceFile] that was found in the cache.
* @param addedMs the time the song was added to the cache.
*/
data class Stale(val file: DeviceFile, val addedMs: Long) : CacheResult
}

View file

@ -27,6 +27,11 @@ import org.oxycblt.musikr.fs.device.DeviceFile
import org.oxycblt.musikr.metadata.Properties
import org.oxycblt.musikr.tag.parse.ParsedTags
/**
* An immutable [Cache] backed by an internal Room database.
*
* Create an instance with [from].
*/
class DBCache private constructor(private val readDao: CacheReadDao) : Cache {
override suspend fun read(file: DeviceFile): CacheResult {
val dbSong = readDao.selectSong(file.uri.toString()) ?: return CacheResult.Miss(file)
@ -66,12 +71,25 @@ class DBCache private constructor(private val readDao: CacheReadDao) : Cache {
}
companion object {
/**
* Create a new instance of [DBCache] from the given [context]. This instance should be a
* singleton, since it implicitly holds a Room database. As a result, you should only create
* EITHER a [DBCache] or a [MutableDBCache].
*
* @param context The context to use to create the Room database.
* @return A new instance of [DBCache].
*/
fun from(context: Context) = from(CacheDatabase.from(context))
internal fun from(db: CacheDatabase) = DBCache(db.readDao())
}
}
/**
* A mutable [Cache] backed by an internal Room database.
*
* Create an instance with [from].
*/
class MutableDBCache
private constructor(private val inner: DBCache, private val writeDao: CacheWriteDao) :
MutableCache {
@ -116,6 +134,14 @@ private constructor(private val inner: DBCache, private val writeDao: CacheWrite
}
companion object {
/**
* Create a new instance of [MutableDBCache] from the given [context]. This instance should
* be a singleton, since it implicitly holds a Room database. As a result, you should only
* create EITHER a [DBCache] or a [MutableDBCache].
*
* @param context The context to use to create the Room database.
* @return A new instance of [MutableDBCache].
*/
fun from(context: Context): MutableDBCache {
val db = CacheDatabase.from(context)
return MutableDBCache(DBCache.from(db), db.writeDao())