Servers database v2 (#177)

* New server db schema

Adds support for multiple addresses and users per server

* Fix crash when the only available server is deleted and app is restarted

* Set serverId as foreign key in User and ServerAddress

* Format using ktlint

* Bump ServerDatabase version to 2
This commit is contained in:
Jarne Demeulemeester 2022-11-01 21:15:42 +01:00 committed by GitHub
parent 4ab0a96740
commit d3b4fe6ea3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 202 additions and 40 deletions

View file

@ -11,7 +11,7 @@ import dev.jdtech.jellyfin.adapters.HomeEpisodeListAdapter
import dev.jdtech.jellyfin.adapters.ServerGridAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.models.Server
import dev.jdtech.jellyfin.models.User
import java.util.UUID
import org.jellyfin.sdk.model.api.BaseItemDto

View file

@ -5,8 +5,8 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.databinding.ServerItemBinding
import dev.jdtech.jellyfin.models.Server
class ServerGridAdapter(
private val onClickListener: OnClickListener,

View file

@ -2,8 +2,13 @@ package dev.jdtech.jellyfin.database
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import dev.jdtech.jellyfin.models.Server
import dev.jdtech.jellyfin.models.ServerAddress
import dev.jdtech.jellyfin.models.User
@Database(entities = [Server::class], version = 1, exportSchema = false)
@Database(entities = [Server::class, ServerAddress::class, User::class], version = 2, exportSchema = false)
@TypeConverters(Converters::class)
abstract class ServerDatabase : RoomDatabase() {
abstract val serverDatabaseDao: ServerDatabaseDao
}

View file

@ -5,12 +5,25 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import dev.jdtech.jellyfin.models.Server
import dev.jdtech.jellyfin.models.ServerAddress
import dev.jdtech.jellyfin.models.ServerWithAddresses
import dev.jdtech.jellyfin.models.ServerWithAddressesAndUsers
import dev.jdtech.jellyfin.models.User
import java.util.UUID
@Dao
interface ServerDatabaseDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(server: Server)
fun insertServer(server: Server)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertServerAddress(address: ServerAddress)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(user: User)
@Update
fun update(server: Server)
@ -18,6 +31,21 @@ interface ServerDatabaseDao {
@Query("select * from servers where id = :id")
fun get(id: String): Server?
@Query("select * from users where id = :id")
fun getUser(id: UUID): User?
@Transaction
@Query("select * from servers where id = :id")
fun getServerWithAddresses(id: String): ServerWithAddresses
@Transaction
@Query("select * from servers where id = :id")
fun getServerWithUsers(id: String): ServerWithAddresses
@Transaction
@Query("select * from servers where id = :id")
fun getServerWithAddressesAndUsers(id: String): ServerWithAddressesAndUsers?
@Query("delete from servers")
fun clear()

View file

@ -9,7 +9,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.ServerDatabaseDao
import java.util.UUID
import javax.inject.Singleton
@Module
@ -26,11 +25,14 @@ object ApiModule {
val serverId = sharedPreferences.getString("selectedServer", null)
if (serverId != null) {
val server = serverDatabase.get(serverId) ?: return jellyfinApi
val serverWithAddressesAndUsers = serverDatabase.getServerWithAddressesAndUsers(serverId) ?: return jellyfinApi
val server = serverWithAddressesAndUsers.server
val serverAddress = serverWithAddressesAndUsers.addresses.firstOrNull { it.id == server.currentServerAddressId } ?: return jellyfinApi
val user = serverWithAddressesAndUsers.users.firstOrNull { it.id == server.currentUserId } ?: return jellyfinApi
jellyfinApi.apply {
api.baseUrl = server.address
api.accessToken = server.accessToken
userId = UUID.fromString(server.userId)
api.baseUrl = serverAddress.address
api.accessToken = user.accessToken
userId = user.id
}
}

View file

@ -5,7 +5,7 @@ import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.models.Server
import dev.jdtech.jellyfin.viewmodels.ServerSelectViewModel
import java.lang.IllegalStateException

View file

@ -1,15 +1,14 @@
package dev.jdtech.jellyfin.database
package dev.jdtech.jellyfin.models
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.util.UUID
@Entity(tableName = "servers")
data class Server(
@PrimaryKey
val id: String,
val name: String,
val address: String,
val userId: String,
val userName: String,
val accessToken: String,
val currentServerAddressId: UUID?,
val currentUserId: UUID?,
)

View file

@ -0,0 +1,26 @@
package dev.jdtech.jellyfin.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import java.util.UUID
@Entity(
tableName = "serverAddresses",
foreignKeys = [
ForeignKey(
entity = Server::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("serverId"),
onDelete = ForeignKey.CASCADE
)
]
)
data class ServerAddress(
@PrimaryKey
val id: UUID,
@ColumnInfo(index = true)
val serverId: String,
val address: String
)

View file

@ -0,0 +1,19 @@
package dev.jdtech.jellyfin.models
import androidx.room.Embedded
import androidx.room.Relation
data class ServerWithAddresses(
@Embedded
val server: Server,
@Relation(
parentColumn = "id",
entityColumn = "serverId"
)
val addresses: List<ServerAddress>,
@Relation(
parentColumn = "currentUserId",
entityColumn = "id"
)
val user: User?
)

View file

@ -0,0 +1,19 @@
package dev.jdtech.jellyfin.models
import androidx.room.Embedded
import androidx.room.Relation
data class ServerWithAddressesAndUsers(
@Embedded
val server: Server,
@Relation(
parentColumn = "id",
entityColumn = "serverId"
)
val addresses: List<ServerAddress>,
@Relation(
parentColumn = "id",
entityColumn = "serverId"
)
val users: List<User>
)

View file

@ -0,0 +1,14 @@
package dev.jdtech.jellyfin.models
import androidx.room.Embedded
import androidx.room.Relation
data class ServerWithUsers(
@Embedded
val server: Server,
@Relation(
parentColumn = "id",
entityColumn = "serverId"
)
val users: List<User>
)

View file

@ -1,8 +1,27 @@
package dev.jdtech.jellyfin.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import java.util.UUID
data class User(
val id: UUID,
val name: String
@Entity(
tableName = "users",
foreignKeys = [
ForeignKey(
entity = Server::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("serverId"),
onDelete = ForeignKey.CASCADE
)
]
)
data class User(
@PrimaryKey
val id: UUID,
val name: String,
@ColumnInfo(index = true)
val serverId: String,
val accessToken: String? = null
)

View file

@ -8,9 +8,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.BaseApplication
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.models.DiscoveredServer
import dev.jdtech.jellyfin.models.Server
import javax.inject.Inject
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers

View file

@ -8,9 +8,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.BaseApplication
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.models.Server
import dev.jdtech.jellyfin.models.ServerAddress
import dev.jdtech.jellyfin.models.User
import java.util.UUID
import javax.inject.Inject
import kotlin.Exception
import kotlinx.coroutines.Dispatchers
@ -60,7 +62,8 @@ constructor(
_usersState.emit(UsersState.Loading)
try {
val publicUsers by jellyfinApi.userApi.getPublicUsers()
val users = publicUsers.map { User(it.id, it.name.orEmpty()) }
val users =
publicUsers.map { User(id = it.id, name = it.name.orEmpty(), serverId = it.serverId!!) }
_usersState.emit(UsersState.Users(users))
} catch (e: Exception) {
_usersState.emit(UsersState.Users(emptyList()))
@ -88,16 +91,29 @@ constructor(
val serverInfo by jellyfinApi.systemApi.getPublicSystemInfo()
val server = Server(
serverInfo.id!!,
serverInfo.serverName!!,
jellyfinApi.api.baseUrl!!,
authenticationResult.user?.id.toString(),
authenticationResult.user?.name!!,
authenticationResult.accessToken!!
val user = User(
id = authenticationResult.user!!.id,
name = authenticationResult.user!!.name!!,
serverId = serverInfo.id!!,
accessToken = authenticationResult.accessToken!!
)
insert(server)
val serverAddress = ServerAddress(
id = UUID.randomUUID(),
serverId = serverInfo.id!!,
address = jellyfinApi.api.baseUrl!!
)
val server = Server(
id = serverInfo.id!!,
name = serverInfo.serverName!!,
currentServerAddressId = serverAddress.id,
currentUserId = user.id,
)
insertServer(server)
insertServerAddress(serverAddress)
insertUser(user)
val spEdit = sharedPreferences.edit()
spEdit.putString("selectedServer", server.id)
@ -125,9 +141,21 @@ constructor(
*
* @param server The server
*/
private suspend fun insert(server: Server) {
private suspend fun insertServer(server: Server) {
withContext(Dispatchers.IO) {
database.insert(server)
database.insertServer(server)
}
}
private suspend fun insertServerAddress(address: ServerAddress) {
withContext(Dispatchers.IO) {
database.insertServerAddress(address)
}
}
private suspend fun insertUser(user: User) {
withContext(Dispatchers.IO) {
database.insertUser(user)
}
}
}

View file

@ -5,9 +5,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.Server
import dev.jdtech.jellyfin.database.ServerDatabaseDao
import java.util.UUID
import dev.jdtech.jellyfin.models.Server
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
@ -40,16 +39,20 @@ constructor(
fun connectToServer(server: Server) {
viewModelScope.launch {
val serverWithAddressesAndUsers = database.getServerWithAddressesAndUsers(server.id)!!
val serverAddress = serverWithAddressesAndUsers.addresses.firstOrNull { it.id == server.currentServerAddressId } ?: return@launch
val user = serverWithAddressesAndUsers.users.firstOrNull { it.id == server.currentUserId } ?: return@launch
jellyfinApi.apply {
api.baseUrl = serverAddress.address
api.accessToken = user.accessToken
userId = user.id
}
val spEdit = sharedPreferences.edit()
spEdit.putString("selectedServer", server.id)
spEdit.apply()
jellyfinApi.apply {
api.baseUrl = server.address
api.accessToken = server.accessToken
userId = UUID.fromString(server.userId)
}
_navigateToMain.emit(true)
}
}

View file

@ -7,7 +7,7 @@
<variable
name="server"
type="dev.jdtech.jellyfin.database.Server" />
type="dev.jdtech.jellyfin.models.Server" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout