diff --git a/musikr/src/main/java/org/oxycblt/musikr/cache/Cache.kt b/musikr/src/main/java/org/oxycblt/musikr/cache/Cache.kt index fe1fbb42c..8c4c0e500 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cache/Cache.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cache/Cache.kt @@ -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) } +/** 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 } diff --git a/musikr/src/main/java/org/oxycblt/musikr/cache/db/DBCache.kt b/musikr/src/main/java/org/oxycblt/musikr/cache/db/DBCache.kt index d9a9ef1ff..6bc5f521d 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cache/db/DBCache.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cache/db/DBCache.kt @@ -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())