Merge pull request #3909 from omnivore-app/fix/android-save-content-to-files

Save Android offline content to files instead of in DB
This commit is contained in:
Jackson Harper
2024-05-08 11:14:41 +08:00
committed by GitHub
9 changed files with 87 additions and 44 deletions

View File

@ -6,6 +6,8 @@ import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.core.network.loadLibraryItemContent
import app.omnivore.omnivore.core.network.saveLibraryItemContentToFile
import app.omnivore.omnivore.core.network.savedItem
import app.omnivore.omnivore.core.network.savedItemUpdates
import app.omnivore.omnivore.core.network.search
@ -46,6 +48,7 @@ suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 20): S
}
val savedItems = syncResult.items.map {
saveLibraryItemContentToFile(it.id, it.content)
val savedItem = SavedItem(
savedItemId = it.id,
title = it.title,
@ -67,7 +70,6 @@ suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 20): S
isArchived = it.isArchived,
contentReader = it.contentReader.rawValue,
wordsCount = it.wordsCount,
content = it.content
)
val labels = it.labels?.map { label ->
SavedItemLabel(
@ -116,8 +118,11 @@ suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 20): S
suspend fun DataService.isSavedItemContentStoredInDB(slug: String): Boolean {
val existingItem = db.savedItemDao().getSavedItemWithLabelsAndHighlights(slug)
val content = existingItem?.savedItem?.content ?: ""
return content.length > 10
existingItem?.savedItem?.savedItemId?.let { savedItemId ->
val htmlContent = loadLibraryItemContent(savedItemId)
return (htmlContent ?: "").length > 10
}
return false
}
suspend fun DataService.fetchSavedItemContent(slug: String) {

View File

@ -27,7 +27,9 @@ import app.omnivore.omnivore.core.network.createHighlight
import app.omnivore.omnivore.core.network.createNewLabel
import app.omnivore.omnivore.core.network.deleteHighlights
import app.omnivore.omnivore.core.network.deleteSavedItem
import app.omnivore.omnivore.core.network.loadLibraryItemContent
import app.omnivore.omnivore.core.network.mergeHighlights
import app.omnivore.omnivore.core.network.saveLibraryItemContentToFile
import app.omnivore.omnivore.core.network.savedItem
import app.omnivore.omnivore.core.network.savedItemLabels
import app.omnivore.omnivore.core.network.savedItemUpdates
@ -219,8 +221,11 @@ class LibraryRepositoryImpl @Inject constructor(
override suspend fun isSavedItemContentStoredInDB(slug: String): Boolean {
val existingItem = savedItemDao.getSavedItemWithLabelsAndHighlights(slug)
val content = existingItem?.savedItem?.content ?: ""
return content.length > 10
existingItem?.savedItem?.savedItemId?.let { savedItemId ->
val htmlContent = loadLibraryItemContent(savedItemId)
return (htmlContent ?: "").length > 10
}
return false
}
override suspend fun deleteSavedItem(itemID: String) {
@ -416,6 +421,7 @@ class LibraryRepositoryImpl @Inject constructor(
}
val savedItems = syncResult.items.map {
saveLibraryItemContentToFile(it.id, it.content)
val savedItem = SavedItem(
savedItemId = it.id,
title = it.title,
@ -436,7 +442,6 @@ class LibraryRepositoryImpl @Inject constructor(
slug = it.slug,
isArchived = it.isArchived,
contentReader = it.contentReader.rawValue,
content = it.content,
wordsCount = it.wordsCount
)
val labels = it.labels?.map { label ->

View File

@ -27,7 +27,7 @@ import app.omnivore.omnivore.core.database.entities.ViewerDao
HighlightChange::class,
SavedItemAndSavedItemLabelCrossRef::class,
SavedItemAndHighlightCrossRef::class],
version = 27,
version = 28,
exportSchema = true
)
abstract class OmnivoreDatabase : RoomDatabase() {

View File

@ -26,9 +26,7 @@ data class SavedItem(
val slug: String,
var isArchived: Boolean,
val contentReader: String? = null,
val content: String? = null,
val createdId: String? = null,
val htmlContent: String? = null,
val language: String? = null,
val listenPositionIndex: Int? = null,
val listenPositionOffset: Double? = null,

View File

@ -80,6 +80,8 @@ suspend fun Networker.savedItem(slug: String): SavedItemQueryResponse {
localPDFPath = localFile.toPath().toString()
}
saveLibraryItemContentToFile(article.articleFields.id, article.articleFields.content)
val savedItem = SavedItem(
savedItemId = article.articleFields.id,
title = article.articleFields.title,
@ -100,7 +102,6 @@ suspend fun Networker.savedItem(slug: String): SavedItemQueryResponse {
slug = article.articleFields.slug,
isArchived = article.articleFields.isArchived,
contentReader = article.articleFields.contentReader.rawValue,
content = article.articleFields.content,
wordsCount = article.articleFields.wordsCount,
localPDFPath = localPDFPath
)

View File

@ -1,11 +1,16 @@
package app.omnivore.omnivore.core.network
import android.os.Environment
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.graphql.generated.SearchQuery
import com.apollographql.apollo3.api.Optional
import androidx.core.content.ContextCompat
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
data class LibrarySearchQueryResponse(
val cursor: String?, val items: List<LibrarySearchItem>
@ -31,6 +36,7 @@ suspend fun Networker.search(
val itemList = result.data?.search?.onSearchSuccess?.edges ?: listOf()
val searchItems = itemList.map {
saveLibraryItemContentToFile(it.node.id, it.node.content)
LibrarySearchItem(item = SavedItem(
savedItemId = it.node.id,
title = it.node.title,
@ -51,7 +57,6 @@ suspend fun Networker.search(
slug = it.node.slug,
isArchived = it.node.isArchived,
contentReader = it.node.contentReader.rawValue,
content = it.node.content,
wordsCount = it.node.wordsCount
), labels = (it.node.labels ?: listOf()).map { label ->
SavedItemLabel(
@ -89,3 +94,33 @@ suspend fun Networker.search(
return LibrarySearchQueryResponse(null, listOf())
}
}
fun saveLibraryItemContentToFile(libraryItemId: String, content: String?): Boolean {
return try {
content?.let { content ->
val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
val file = File(directory, "${libraryItemId}.html")
FileOutputStream(file).use { it.write(content.toByteArray()) }
return false
}
false
} catch (e: Exception) {
e.printStackTrace()
false
}
}
fun loadLibraryItemContent(libraryItemId: String): String? {
return try {
val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
val file = File(directory, "${libraryItemId}.html")
if (file.exists()) {
return FileInputStream(file).bufferedReader().use { it.readText() }
} else {
null
}
} catch (e: Exception) {
e.printStackTrace()
null
}
}

View File

@ -73,7 +73,6 @@ class PDFReaderViewModel @Inject constructor(
htmlContent = "",
highlights = item.highlights,
contentStatus = "SUCCEEDED",
objectID = "",
labelsJSONString = Gson().toJson(item.labels)
)
@ -103,10 +102,9 @@ class PDFReaderViewModel @Inject constructor(
override fun onComplete(output: File) {
val articleContent = ArticleContent(
title = article.title,
htmlContent = article.content ?: "",
htmlContent = "",
highlights = articleQueryResult.highlights,
contentStatus = "SUCCEEDED",
objectID = "",
labelsJSONString = Gson().toJson(articleQueryResult.labels)
)

View File

@ -37,7 +37,6 @@ data class ArticleContent(
val htmlContent: String,
val highlights: List<Highlight>,
val contentStatus: String, // ArticleContentStatus,
val objectID: String?, // whatever the Room Equivalent of objectID is
val labelsJSONString: String
) {
fun highlightsJSONString(): String {

View File

@ -39,6 +39,7 @@ import app.omnivore.omnivore.core.datastore.prefersWebHighContrastText
import app.omnivore.omnivore.core.datastore.volumeForScroll
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.network.createNewLabel
import app.omnivore.omnivore.core.network.loadLibraryItemContent
import app.omnivore.omnivore.core.network.saveUrl
import app.omnivore.omnivore.core.network.savedItem
import app.omnivore.omnivore.feature.components.HighlightColor
@ -263,35 +264,36 @@ class WebReaderViewModel @Inject constructor(
private suspend fun loadItemFromDB(slug: String) {
withContext(Dispatchers.IO) {
val persistedItem =
dataService.db.savedItemDao().getSavedItemWithLabelsAndHighlights(slug)
val persistedItem = dataService.db.savedItemDao().getSavedItemWithLabelsAndHighlights(slug)
val savedItemId = persistedItem?.savedItem?.savedItemId
if (savedItemId != null) {
val htmlContent = loadLibraryItemContent(savedItemId)
if (htmlContent != null) {
val articleContent = ArticleContent(
title = persistedItem.savedItem.title,
htmlContent = htmlContent,
highlights = persistedItem.highlights,
contentStatus = "SUCCEEDED",
labelsJSONString = Gson().toJson(persistedItem.labels)
)
if (persistedItem?.savedItem?.content != null) {
val articleContent = ArticleContent(
title = persistedItem.savedItem.title,
htmlContent = persistedItem.savedItem.content,
highlights = persistedItem.highlights,
contentStatus = "SUCCEEDED",
objectID = "",
labelsJSONString = Gson().toJson(persistedItem.labels)
)
val webReaderParams = WebReaderParams(
persistedItem.savedItem,
articleContent,
persistedItem.labels
)
val webReaderParams = WebReaderParams(
persistedItem.savedItem,
articleContent,
persistedItem.labels
)
Log.d("sync", "data loaded from db")
eventTracker.track(
"link_read",
com.posthog.android.Properties()
.putValue("linkID", webReaderParams.item.savedItemId)
.putValue("slug", webReaderParams.item.slug)
.putValue("originalArticleURL", webReaderParams.item.pageURLString)
.putValue("loaded_from", "db")
)
webReaderParamsLiveData.postValue(webReaderParams)
Log.d("sync", "data loaded from db")
eventTracker.track(
"link_read",
com.posthog.android.Properties()
.putValue("linkID", webReaderParams.item.savedItemId)
.putValue("slug", webReaderParams.item.slug)
.putValue("originalArticleURL", webReaderParams.item.pageURLString)
.putValue("loaded_from", "db")
)
webReaderParamsLiveData.postValue(webReaderParams)
}
}
isLoading = false
}
@ -301,13 +303,13 @@ class WebReaderViewModel @Inject constructor(
val articleQueryResult = networker.savedItem(slug)
val article = articleQueryResult.item ?: return null
val htmlContent = loadLibraryItemContent(article.savedItemId)
val articleContent = ArticleContent(
title = article.title,
htmlContent = article.content ?: "",
htmlContent = htmlContent ?: "",
highlights = articleQueryResult.highlights,
contentStatus = articleQueryResult.state,
objectID = "",
labelsJSONString = Gson().toJson(articleQueryResult.labels)
)