load article content from sql

This commit is contained in:
Satindar Dhillon 2023-01-12 13:38:34 -08:00
parent ffeacab409
commit 8319fb67ca
7 changed files with 119 additions and 23 deletions

View file

@ -1,12 +1,14 @@
package app.omnivore.omnivore
import android.content.Context
import android.util.Log
import androidx.room.Room
import app.omnivore.omnivore.networking.Networker
import app.omnivore.omnivore.networking.savedItem
import app.omnivore.omnivore.networking.savedItemUpdates
import app.omnivore.omnivore.persistence.AppDatabase
import app.omnivore.omnivore.persistence.entities.SavedItem
import app.omnivore.omnivore.persistence.entities.SavedItemAndHighlightCrossRef
import app.omnivore.omnivore.persistence.entities.SavedItemAndSavedItemLabelCrossRef
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import javax.inject.Inject
@ -89,7 +91,28 @@ suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 15): S
suspend fun DataService.syncSavedItemContent(slug: String) {
val syncResult = networker.savedItem(slug)
// syncResult.item
val savedItem = syncResult.item ?: return
db.savedItemDao().insert(savedItem)
// Persist Labels
db.savedItemLabelDao().insertAll(syncResult.labels)
val labelCrossRefs = syncResult.labels.map {
SavedItemAndSavedItemLabelCrossRef(savedItemLabelId = it.savedItemLabelId, savedItemId = savedItem.savedItemId)
}
db.savedItemAndSavedItemLabelCrossRefDao().insertAll(labelCrossRefs)
// Persist Highlights
db.highlightDao().insertAll(syncResult.highlights)
val highlightCrossRefs = syncResult.highlights.map {
SavedItemAndHighlightCrossRef(highlightId = it.highlightId, savedItemId = savedItem.savedItemId)
}
db.savedItemAndHighlightCrossRefDao().insertAll(highlightCrossRefs)
Log.d("sync", "saved content for item with id: ${savedItem.savedItemId}")
}
suspend fun DataService.syncOfflineItemsWithServerIfNeeded() {

View file

@ -9,13 +9,17 @@ import app.omnivore.omnivore.persistence.entities.*
Viewer::class,
SavedItem::class,
SavedItemLabel::class,
SavedItemAndSavedItemLabelCrossRef::class
Highlight::class,
SavedItemAndSavedItemLabelCrossRef::class,
SavedItemAndHighlightCrossRef::class
],
version = 2
)
abstract class AppDatabase : RoomDatabase() {
abstract fun viewerDao(): ViewerDao
abstract fun savedItemDao(): SavedItemDao
abstract fun highlightDao(): HighlightDao
abstract fun savedItemLabelDao(): SavedItemLabelDao
abstract fun savedItemAndSavedItemLabelCrossRefDao(): SavedItemAndSavedItemLabelCrossRefDao
abstract fun savedItemAndHighlightCrossRefDao(): SavedItemAndHighlightCrossRefDao
}

View file

@ -1,7 +1,6 @@
package app.omnivore.omnivore.persistence.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.*
import java.time.LocalDate
import java.util.Date
@ -9,7 +8,7 @@ import java.util.Date
data class Highlight(
@PrimaryKey val highlightId: String,
val annotation: String?,
val createdAt: Date?,
val createdAt: String?,
val createdByMe: Boolean,
val markedForDeletion: Boolean, // default false
val patch: String,
@ -18,9 +17,45 @@ data class Highlight(
val serverSyncStatus: Int, // default 0
val shortId: String,
val suffix: String?,
val updatedAt: LocalDate?
val updatedAt: String?
// has many SavedItemLabels (inverse: labels have many highlights)
// has one savedItem (inverse: savedItem has many highlights
// has a UserProfile (no inverse)
)
@Entity(primaryKeys = ["highlightId", "savedItemId"])
data class SavedItemAndHighlightCrossRef(
val highlightId: String,
val savedItemId: String
)
@Dao
interface SavedItemAndHighlightCrossRefDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(items: List<SavedItemAndHighlightCrossRef>)
}
data class SavedItemWithLabelsAndHighlights(
@Embedded val savedItem: SavedItem,
@Relation(
parentColumn = "savedItemId",
entityColumn = "savedItemLabelId",
associateBy = Junction(SavedItemAndSavedItemLabelCrossRef::class)
)
val labels: List<SavedItemLabel>,
@Relation(
parentColumn = "savedItemId",
entityColumn = "highlightId",
associateBy = Junction(SavedItemAndHighlightCrossRef::class)
)
val highlights: List<Highlight>
)
@Dao
interface HighlightDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(items: List<Highlight>)
}

View file

@ -92,9 +92,15 @@ interface SavedItemDao {
@Query("SELECT * FROM savedItem")
fun getAll(): List<SavedItem>
@Query("SELECT * FROM savedItem WHERE slug = :slug")
fun getSavedItemWithLabelsAndHighlights(slug: String): SavedItemWithLabelsAndHighlights?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(items: List<SavedItem>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(item: SavedItem)
@Query("DELETE FROM savedItem WHERE savedItemId = :itemID")
fun deleteById(itemID: String)

View file

@ -1,6 +1,5 @@
package app.omnivore.omnivore.persistence.entities
import androidx.lifecycle.LiveData
import androidx.room.*
@Entity

View file

@ -83,6 +83,12 @@ class LibraryViewModel @Inject constructor(
private suspend fun performItemSync(cursor: String?, since: String, count: Int, startTime: String) {
dataService.syncOfflineItemsWithServerIfNeeded()
val result = dataService.sync(since = since, cursor = cursor)
// TODO: Defer this until later?
for (slug in result.savedItemSlugs) {
dataService.syncSavedItemContent(slug)
}
val totalCount = count + result.count
Log.d("sync", "fetched ${result.count} items")

View file

@ -5,6 +5,7 @@ import androidx.compose.foundation.ScrollState
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.DataService
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.persistence.entities.SavedItem
@ -12,10 +13,7 @@ import app.omnivore.omnivore.networking.*
import app.omnivore.omnivore.ui.library.SavedItemAction
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.*
import java.util.*
import javax.inject.Inject
@ -31,6 +29,7 @@ data class AnnotationWebViewMessage(
@HiltViewModel
class WebReaderViewModel @Inject constructor(
private val datastoreRepo: DatastoreRepository,
private val dataService: DataService,
private val networker: Networker
): ViewModel() {
var lastJavascriptActionLoopUUID: UUID = UUID.randomUUID()
@ -47,23 +46,47 @@ class WebReaderViewModel @Inject constructor(
fun loadItem(slug: String) {
viewModelScope.launch {
val articleQueryResult = networker.savedItem(slug)
// Attempt to load from db first
withContext(Dispatchers.IO) {
val persistedItem = dataService.db.savedItemDao().getSavedItemWithLabelsAndHighlights(slug)
val article = articleQueryResult.item ?: return@launch
if (persistedItem?.savedItem != null) {
val articleContent = ArticleContent(
title = persistedItem.savedItem.title,
htmlContent = persistedItem.savedItem.content ?: "",
highlights = persistedItem.highlights,
contentStatus = "SUCCEEDED",
objectID = "",
labelsJSONString = Gson().toJson(persistedItem.labels)
)
val articleContent = ArticleContent(
title = article.title,
htmlContent = article.content ?: "",
highlights = articleQueryResult.highlights,
contentStatus = "SUCCEEDED",
objectID = "",
labelsJSONString = Gson().toJson(articleQueryResult.labels)
)
webReaderParamsLiveData.value = WebReaderParams(article, articleContent)
Log.d("sync", "data loaded from db")
webReaderParamsLiveData.postValue(WebReaderParams(persistedItem.savedItem, articleContent))
} else {
loadItemFromServer(slug)
}
}
}
}
private suspend fun loadItemFromServer(slug: String) {
val articleQueryResult = networker.savedItem(slug)
val article = articleQueryResult.item ?: return
val articleContent = ArticleContent(
title = article.title,
htmlContent = article.content ?: "",
highlights = articleQueryResult.highlights,
contentStatus = "SUCCEEDED",
objectID = "",
labelsJSONString = Gson().toJson(articleQueryResult.labels)
)
Log.d("sync", "data loaded from server")
webReaderParamsLiveData.postValue(WebReaderParams(article, articleContent))
}
fun handleSavedItemAction(itemID: String, action: SavedItemAction) {
when (action) {
SavedItemAction.Delete -> {