Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.makd.afinity.data.database.dao.AbsDownloadDao
import com.makd.afinity.data.database.dao.AudiobookshelfDao
import com.makd.afinity.data.database.dao.BoxSetCacheDao
import com.makd.afinity.data.database.dao.EpisodeDao
Expand Down Expand Up @@ -35,6 +36,7 @@ import com.makd.afinity.data.database.entities.AfinitySegmentDto
import com.makd.afinity.data.database.entities.AfinityShowDto
import com.makd.afinity.data.database.entities.AfinitySourceDto
import com.makd.afinity.data.database.entities.AfinityTrickplayInfoDto
import com.makd.afinity.data.database.entities.AbsDownloadEntity
import com.makd.afinity.data.database.entities.AudiobookshelfAddressEntity
import com.makd.afinity.data.database.entities.AudiobookshelfConfigEntity
import com.makd.afinity.data.database.entities.AudiobookshelfItemEntity
Expand Down Expand Up @@ -99,8 +101,9 @@ import com.makd.afinity.data.models.user.User
JellyseerrAddressEntity::class,
AudiobookshelfAddressEntity::class,
JellyfinStatsCacheEntity::class,
AbsDownloadEntity::class,
],
version = 36,
version = 39,
exportSchema = false,
)
@TypeConverters(AfinityTypeConverters::class)
Expand Down Expand Up @@ -150,6 +153,8 @@ abstract class AfinityDatabase : RoomDatabase() {

abstract fun jellyfinStatsDao(): JellyfinStatsDao

abstract fun absDownloadDao(): AbsDownloadDao

companion object {
@Volatile private var INSTANCE: AfinityDatabase? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,62 @@ object DatabaseMigrations {
}
}

val MIGRATION_36_37 =
object : Migration(36, 37) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS abs_downloads (
id TEXT NOT NULL PRIMARY KEY,
libraryItemId TEXT NOT NULL,
episodeId TEXT,
jellyfinServerId TEXT NOT NULL,
jellyfinUserId TEXT NOT NULL,
title TEXT NOT NULL,
authorName TEXT,
mediaType TEXT NOT NULL,
coverUrl TEXT,
duration REAL NOT NULL,
status TEXT NOT NULL,
progress REAL NOT NULL,
bytesDownloaded INTEGER NOT NULL,
totalBytes INTEGER NOT NULL,
tracksTotal INTEGER NOT NULL,
tracksDownloaded INTEGER NOT NULL,
error TEXT,
createdAt INTEGER NOT NULL,
updatedAt INTEGER NOT NULL,
localDirPath TEXT,
serializedSession TEXT
)
"""
.trimIndent()
)
db.execSQL(
"""
CREATE UNIQUE INDEX IF NOT EXISTS index_abs_downloads_item
ON abs_downloads (libraryItemId, episodeId, jellyfinServerId, jellyfinUserId)
"""
.trimIndent()
)
}
}

val MIGRATION_37_38 =
object : Migration(37, 38) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE audiobookshelf_items ADD COLUMN serializedEpisodes TEXT")
}
}

val MIGRATION_38_39 =
object : Migration(38, 39) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE abs_downloads ADD COLUMN episodeDescription TEXT")
db.execSQL("ALTER TABLE abs_downloads ADD COLUMN publishedAt INTEGER")
}
}

val ALL_MIGRATIONS =
arrayOf(
MIGRATION_1_2,
Expand Down Expand Up @@ -749,5 +805,8 @@ object DatabaseMigrations {
MIGRATION_33_34,
MIGRATION_34_35,
MIGRATION_35_36,
MIGRATION_36_37,
MIGRATION_37_38,
MIGRATION_38_39,
)
}
166 changes: 166 additions & 0 deletions app/src/main/java/com/makd/afinity/data/database/dao/AbsDownloadDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package com.makd.afinity.data.database.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.makd.afinity.data.database.entities.AbsDownloadEntity
import com.makd.afinity.data.models.audiobookshelf.AbsDownloadStatus
import kotlinx.coroutines.flow.Flow
import java.util.UUID

@Dao
interface AbsDownloadDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(entity: AbsDownloadEntity)

@Query("SELECT * FROM abs_downloads WHERE id = :id")
suspend fun getById(id: UUID): AbsDownloadEntity?

@Query(
"""SELECT * FROM abs_downloads
WHERE libraryItemId = :libraryItemId
AND episodeId IS NULL
AND jellyfinServerId = :serverId
AND jellyfinUserId = :userId
LIMIT 1"""
)
suspend fun getDownloadForBook(
libraryItemId: String,
serverId: String,
userId: String,
): AbsDownloadEntity?

@Query(
"""SELECT * FROM abs_downloads
WHERE libraryItemId = :libraryItemId
AND episodeId = :episodeId
AND jellyfinServerId = :serverId
AND jellyfinUserId = :userId
LIMIT 1"""
)
suspend fun getDownloadForEpisode(
libraryItemId: String,
episodeId: String,
serverId: String,
userId: String,
): AbsDownloadEntity?

@Query(
"""SELECT * FROM abs_downloads
WHERE libraryItemId = :libraryItemId
AND episodeId IS NOT NULL
AND status = 'COMPLETED'
AND jellyfinServerId = :serverId
AND jellyfinUserId = :userId
ORDER BY updatedAt DESC
LIMIT 1"""
)
suspend fun getFirstCompletedEpisodeForItem(
libraryItemId: String,
serverId: String,
userId: String,
): AbsDownloadEntity?

@Query(
"""SELECT * FROM abs_downloads
WHERE libraryItemId = :libraryItemId
AND episodeId IS NOT NULL
AND status = 'COMPLETED'
AND jellyfinServerId = :serverId
AND jellyfinUserId = :userId
ORDER BY updatedAt DESC"""
)
suspend fun getCompletedEpisodesForItem(
libraryItemId: String,
serverId: String,
userId: String,
): List<AbsDownloadEntity>

@Query(
"""SELECT * FROM abs_downloads
WHERE status IN ('QUEUED', 'DOWNLOADING')
AND jellyfinServerId = :serverId
AND jellyfinUserId = :userId
ORDER BY createdAt DESC"""
)
fun getActiveDownloadsFlow(serverId: String, userId: String): Flow<List<AbsDownloadEntity>>

@Query(
"""SELECT * FROM abs_downloads
WHERE status = 'COMPLETED'
AND jellyfinServerId = :serverId
AND jellyfinUserId = :userId
ORDER BY updatedAt DESC"""
)
fun getCompletedDownloadsFlow(serverId: String, userId: String): Flow<List<AbsDownloadEntity>>

@Query(
"""SELECT * FROM abs_downloads
WHERE status = 'COMPLETED'
AND jellyfinServerId = :serverId
AND jellyfinUserId = :userId
ORDER BY updatedAt DESC"""
)
suspend fun getCompletedDownloads(serverId: String, userId: String): List<AbsDownloadEntity>

@Query(
"""SELECT COUNT(*) FROM abs_downloads
WHERE libraryItemId = :libraryItemId
AND episodeId IS NULL
AND status = 'COMPLETED'
AND jellyfinServerId = :serverId
AND jellyfinUserId = :userId"""
)
suspend fun isBookDownloaded(libraryItemId: String, serverId: String, userId: String): Int

@Query(
"""SELECT COUNT(*) FROM abs_downloads
WHERE libraryItemId = :libraryItemId
AND episodeId = :episodeId
AND status = 'COMPLETED'
AND jellyfinServerId = :serverId
AND jellyfinUserId = :userId"""
)
suspend fun isEpisodeDownloaded(
libraryItemId: String,
episodeId: String,
serverId: String,
userId: String,
): Int

@Query(
"""UPDATE abs_downloads
SET status = :status,
progress = :progress,
bytesDownloaded = :bytesDownloaded,
tracksDownloaded = :tracksDownloaded,
serializedSession = COALESCE(:serializedSession, serializedSession),
updatedAt = :updatedAt
WHERE id = :id"""
)
suspend fun updateProgress(
id: UUID,
status: AbsDownloadStatus,
progress: Float,
bytesDownloaded: Long,
tracksDownloaded: Int,
serializedSession: String?,
updatedAt: Long,
)

@Query("UPDATE abs_downloads SET status = :status, error = :error, updatedAt = :updatedAt WHERE id = :id")
suspend fun updateStatus(id: UUID, status: AbsDownloadStatus, error: String?, updatedAt: Long)

@Query("DELETE FROM abs_downloads WHERE id = :id")
suspend fun deleteById(id: UUID)

@Query(
"""SELECT COALESCE(SUM(bytesDownloaded), 0) FROM abs_downloads
WHERE status = 'COMPLETED'
AND jellyfinServerId = :serverId
AND jellyfinUserId = :userId"""
)
suspend fun getTotalBytesForServer(serverId: String, userId: String): Long
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.makd.afinity.data.database.entities

import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import com.makd.afinity.data.models.audiobookshelf.AbsDownloadStatus
import java.util.UUID

@Entity(
tableName = "abs_downloads",
indices = [
Index(
value = ["libraryItemId", "episodeId", "jellyfinServerId", "jellyfinUserId"],
unique = true,
)
],
)
data class AbsDownloadEntity(
@PrimaryKey val id: UUID,
val libraryItemId: String,
val episodeId: String?,
val jellyfinServerId: String,
val jellyfinUserId: String,
val title: String,
val authorName: String?,
val mediaType: String,
val coverUrl: String?,
val duration: Double,
val status: AbsDownloadStatus,
val progress: Float,
val bytesDownloaded: Long,
val totalBytes: Long,
val tracksTotal: Int,
val tracksDownloaded: Int,
val error: String?,
val createdAt: Long,
val updatedAt: Long,
val localDirPath: String?,
val serializedSession: String?,
val episodeDescription: String? = null,
val publishedAt: Long? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ data class AudiobookshelfItemEntity(
val addedAt: Long?,
val updatedAt: Long?,
val cachedAt: Long,
val serializedEpisodes: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.makd.afinity.data.models.audiobookshelf

import java.util.UUID

data class AbsDownloadInfo(
val id: UUID,
val libraryItemId: String,
val episodeId: String?,
val title: String,
val authorName: String?,
val mediaType: String,
val coverUrl: String?,
val duration: Double,
val status: AbsDownloadStatus,
val progress: Float,
val bytesDownloaded: Long,
val totalBytes: Long,
val tracksTotal: Int,
val tracksDownloaded: Int,
val error: String?,
val createdAt: Long,
val updatedAt: Long,
val localDirPath: String?,
val episodeDescription: String? = null,
val publishedAt: Long? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.makd.afinity.data.models.audiobookshelf

enum class AbsDownloadStatus {
QUEUED,
DOWNLOADING,
COMPLETED,
FAILED,
CANCELLED,
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ private fun BaseItemDto.toAfinitySources(baseUrl: String): List<AfinitySource> =
isExternal = mediaStream.isExternal,
path =
if (
mediaStream.isExternal &&
!mediaStream.deliveryUrl.isNullOrBlank()
mediaStream.isExternal && !mediaStream.deliveryUrl.isNullOrBlank()
) {
baseUrl + mediaStream.deliveryUrl
} else {
Expand Down Expand Up @@ -117,9 +116,7 @@ fun BaseItemDto.toAfinityMovie(baseUrl: String): AfinityMovie {
trickplayInfo =
trickplay
?.flatMap { (_, widthMap) ->
widthMap.map { (width, info) ->
width.toString() to info.toAfinityTrickplayInfo()
}
widthMap.map { (width, info) -> width to info.toAfinityTrickplayInfo() }
}
?.toMap(),
providerIds = providerIds?.mapNotNull { (key, value) -> value?.let { key to it } }?.toMap(),
Expand Down Expand Up @@ -259,9 +256,7 @@ fun BaseItemDto.toAfinityEpisode(baseUrl: String): AfinityEpisode? {
trickplayInfo =
trickplay
?.flatMap { (_, widthMap) ->
widthMap.map { (width, info) ->
width.toString() to info.toAfinityTrickplayInfo()
}
widthMap.map { (width, info) -> width to info.toAfinityTrickplayInfo() }
}
?.toMap(),
providerIds =
Expand Down
Loading