Phase 4: domain layer — ServerSettings, RemoteLibrary model, repository interface + impl
This commit is contained in:
parent
33fddc922b
commit
2a6b8a219d
5 changed files with 176 additions and 0 deletions
|
|
@ -34,12 +34,14 @@ import org.dueattendant149.bookshelf.data.repository.ColorPresetRepositoryImpl
|
|||
import org.dueattendant149.bookshelf.data.repository.FileSystemRepositoryImpl
|
||||
import org.dueattendant149.bookshelf.data.repository.HistoryRepositoryImpl
|
||||
import org.dueattendant149.bookshelf.data.repository.PermissionRepositoryImpl
|
||||
import org.dueattendant149.bookshelf.data.repository.RemoteLibraryRepositoryImpl
|
||||
import org.dueattendant149.bookshelf.domain.repository.BookRepository
|
||||
import org.dueattendant149.bookshelf.domain.repository.CategoryRepository
|
||||
import org.dueattendant149.bookshelf.domain.repository.ColorPresetRepository
|
||||
import org.dueattendant149.bookshelf.domain.repository.FileSystemRepository
|
||||
import org.dueattendant149.bookshelf.domain.repository.HistoryRepository
|
||||
import org.dueattendant149.bookshelf.domain.repository.PermissionRepository
|
||||
import org.dueattendant149.bookshelf.domain.repository.RemoteLibraryRepository
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -87,6 +89,12 @@ abstract class RepositoryModule {
|
|||
categoryRepositoryImpl: CategoryRepositoryImpl
|
||||
): CategoryRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindRemoteLibraryRepository(
|
||||
remoteLibraryRepositoryImpl: RemoteLibraryRepositoryImpl
|
||||
): RemoteLibraryRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindBookMapper(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Book's Story — free and open-source Material You eBook reader.
|
||||
* Copyright (C) 2024-2026 Acclorite
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dueattendant149.bookshelf.data.repository
|
||||
|
||||
import android.util.Log
|
||||
import org.dueattendant149.bookshelf.core.ui.UIText
|
||||
import org.dueattendant149.bookshelf.data.remote.bookshelfapi.BookshelfApiClientFactory
|
||||
import org.dueattendant149.bookshelf.data.remote.bookshelfapi.model.LibraryResponse
|
||||
import org.dueattendant149.bookshelf.data.settings.ServerSettings
|
||||
import org.dueattendant149.bookshelf.domain.model.library.Book
|
||||
import org.dueattendant149.bookshelf.domain.model.remote.RemoteLibrary
|
||||
import org.dueattendant149.bookshelf.domain.repository.RemoteLibraryRepository
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class RemoteLibraryRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
private val clientFactory: BookshelfApiClientFactory,
|
||||
private val serverSettings: ServerSettings,
|
||||
) : RemoteLibraryRepository {
|
||||
override suspend fun getLibraries(): Result<List<RemoteLibrary>> {
|
||||
return runCatching {
|
||||
val client = client() ?: return Result.success(emptyList())
|
||||
val response = client.getLibraries()
|
||||
if (!response.isSuccessful) {
|
||||
return Result.failure(RuntimeException("Failed to fetch libraries: ${response.code()}"))
|
||||
}
|
||||
response.body().orEmpty().map { it.toDomain() }
|
||||
}.onFailure {
|
||||
Log.e("RemoteLibraryRepo", "getLibraries failed", it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getBooks(libraryId: String): Result<List<Book>> {
|
||||
return searchLibrary(libraryId, "")
|
||||
}
|
||||
|
||||
override suspend fun searchLibrary(
|
||||
libraryId: String,
|
||||
query: String,
|
||||
): Result<List<Book>> {
|
||||
return runCatching {
|
||||
val client = client() ?: return Result.success(emptyList())
|
||||
val response = client.searchLibrary(libraryId, query)
|
||||
if (!response.isSuccessful) {
|
||||
return Result.failure(RuntimeException("Failed to search library: ${response.code()}"))
|
||||
}
|
||||
response.body().orEmpty().map { it.toBook() }
|
||||
}.onFailure {
|
||||
Log.e("RemoteLibraryRepo", "searchLibrary failed", it)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun client() = serverSettings.getBookshelfUrl()?.let { clientFactory.provideClient(it) }
|
||||
|
||||
private fun LibraryResponse.toDomain() =
|
||||
RemoteLibrary(
|
||||
id = id,
|
||||
name = name,
|
||||
mediaType = mediaType,
|
||||
itemCount = itemCount,
|
||||
)
|
||||
|
||||
private fun org.dueattendant149.bookshelf.data.remote.bookshelfapi.model.BookItemResponse.toBook() =
|
||||
Book(
|
||||
id = 0,
|
||||
title = title,
|
||||
author = UIText.StringValue(author),
|
||||
description = null,
|
||||
filePath = "",
|
||||
coverImage = null,
|
||||
scrollIndex = 0,
|
||||
scrollOffset = 0,
|
||||
progress = 0f,
|
||||
lastOpened = null,
|
||||
categories = emptyList(),
|
||||
remoteId = id,
|
||||
libraryId = libraryId,
|
||||
mediaType = mediaType,
|
||||
hasAudio = mediaType == "audiobook" || mediaType == "podcast",
|
||||
hasEbook = mediaType == "book",
|
||||
audioDuration = duration.toLong(),
|
||||
coverUrl = coverUrl,
|
||||
lastSyncedAt = System.currentTimeMillis(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Book's Story — free and open-source Material You eBook reader.
|
||||
* Copyright (C) 2024-2026 Acclorite
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dueattendant149.bookshelf.data.settings
|
||||
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import org.dueattendant149.bookshelf.data.local.data_store.DataStore
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ServerSettings
|
||||
@Inject
|
||||
constructor(
|
||||
private val dataStore: DataStore,
|
||||
) {
|
||||
private val bookshelfUrlKey = stringPreferencesKey("bookshelf_url")
|
||||
private val absUrlKey = stringPreferencesKey("abs_url")
|
||||
private val absTokenKey = stringPreferencesKey("abs_token")
|
||||
|
||||
suspend fun getBookshelfUrl(): String? = dataStore.getNullableData(bookshelfUrlKey)
|
||||
suspend fun setBookshelfUrl(value: String) = dataStore.putData(bookshelfUrlKey, value)
|
||||
|
||||
suspend fun getAbsUrl(): String? = dataStore.getNullableData(absUrlKey)
|
||||
suspend fun setAbsUrl(value: String) = dataStore.putData(absUrlKey, value)
|
||||
|
||||
suspend fun getAbsToken(): String? = dataStore.getNullableData(absTokenKey)
|
||||
suspend fun setAbsToken(value: String) = dataStore.putData(absTokenKey, value)
|
||||
|
||||
suspend fun hasCredentials(): Boolean =
|
||||
!getBookshelfUrl().isNullOrBlank() &&
|
||||
!getAbsUrl().isNullOrBlank() &&
|
||||
!getAbsToken().isNullOrBlank()
|
||||
|
||||
suspend fun clear() {
|
||||
setBookshelfUrl("")
|
||||
setAbsUrl("")
|
||||
setAbsToken("")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Book's Story — free and open-source Material You eBook reader.
|
||||
* Copyright (C) 2024-2026 Acclorite
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dueattendant149.bookshelf.domain.model.remote
|
||||
|
||||
/**
|
||||
* Audiobookshelf library exposed through bookshelf-api.
|
||||
*/
|
||||
data class RemoteLibrary(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val mediaType: String,
|
||||
val itemCount: Int,
|
||||
)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Book's Story — free and open-source Material You eBook reader.
|
||||
* Copyright (C) 2024-2026 Acclorite
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.dueattendant149.bookshelf.domain.repository
|
||||
|
||||
import org.dueattendant149.bookshelf.domain.model.library.Book
|
||||
import org.dueattendant149.bookshelf.domain.model.remote.RemoteLibrary
|
||||
|
||||
interface RemoteLibraryRepository {
|
||||
suspend fun getLibraries(): Result<List<RemoteLibrary>>
|
||||
suspend fun getBooks(libraryId: String): Result<List<Book>>
|
||||
suspend fun searchLibrary(libraryId: String, query: String): Result<List<Book>>
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue