From 8e4e49e1511ee520b504601d5e2f97f54d1b4396 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 1 Jul 2024 18:12:38 +0800 Subject: [PATCH 1/2] WIP: download android PDFs for offline to internal storage --- .../omnivore/core/data/LibrarySync.kt | 2 +- .../repository/impl/LibraryRepositoryImpl.kt | 2 +- .../omnivore/core/network/SavedItemQuery.kt | 19 ++------ .../omnivore/core/network/SearchQuery.kt | 43 ++++++++++++++++--- .../feature/reader/PDFReaderViewModel.kt | 19 +++++--- 5 files changed, 55 insertions(+), 30 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/LibrarySync.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/LibrarySync.kt index 7e246f2a42..a4b0528bb3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/LibrarySync.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/LibrarySync.kt @@ -49,7 +49,7 @@ suspend fun DataService.sync(context: Context, since: String, cursor: String?, l } val savedItems = syncResult.items.map { - if (!saveLibraryItemContentToFile(context, it.id, it.content)) { + if (!saveLibraryItemContentToFile(context, it.id, it.contentReader, it.content, it.url)) { return SavedItemSyncResult( hasError = true, errorString = "Error saving page content", diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/repository/impl/LibraryRepositoryImpl.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/repository/impl/LibraryRepositoryImpl.kt index 9830dcd692..8ba3fb95f8 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/repository/impl/LibraryRepositoryImpl.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/repository/impl/LibraryRepositoryImpl.kt @@ -430,7 +430,7 @@ class LibraryRepositoryImpl @Inject constructor( } val savedItems = syncResult.items.map { - saveLibraryItemContentToFile(context, it.id, it.content) + saveLibraryItemContentToFile(context, it.id, it.contentReader, it.content, it.url) val savedItem = SavedItem( savedItemId = it.id, title = it.title, diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SavedItemQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SavedItemQuery.kt index b124338c41..033d5ef651 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SavedItemQuery.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SavedItemQuery.kt @@ -7,6 +7,8 @@ import app.omnivore.omnivore.core.database.entities.SavedItem import app.omnivore.omnivore.core.database.entities.SavedItemLabel import app.omnivore.omnivore.graphql.generated.GetArticleQuery import app.omnivore.omnivore.graphql.generated.type.ContentReader +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.io.File import java.net.URL import java.nio.file.Files @@ -67,21 +69,7 @@ suspend fun Networker.savedItem(context: Context, slug: String): SavedItemQueryR ) } - var localPDFPath: String? = null - if (article.articleFields.contentReader == ContentReader.PDF) { - // download the PDF and save it locally - // article.articleFields.url - - val localFile = File.createTempFile("pdf-" + article.articleFields.id, ".pdf") - val url = URL(article.articleFields.url) - Log.d("pdf", "creating local file: $localFile") - - url.openStream() - .use { Files.copy(it, localFile.toPath(), StandardCopyOption.REPLACE_EXISTING) } - localPDFPath = localFile.toPath().toString() - } - - saveLibraryItemContentToFile(context, article.articleFields.id, article.articleFields.content) + saveLibraryItemContentToFile(context, article.articleFields.id, article.articleFields.contentReader, article.articleFields.content, article.articleFields.url) val savedItem = SavedItem( savedItemId = article.articleFields.id, @@ -104,7 +92,6 @@ suspend fun Networker.savedItem(context: Context, slug: String): SavedItemQueryR isArchived = article.articleFields.isArchived, contentReader = article.articleFields.contentReader.rawValue, wordsCount = article.articleFields.wordsCount, - localPDFPath = localPDFPath ) return SavedItemQueryResponse( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt index 00322da520..a296e95510 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt @@ -15,8 +15,18 @@ import java.io.FileOutputStream import android.Manifest import android.content.Context import android.content.Context.MODE_PRIVATE +import android.net.Uri import android.util.Log import androidx.compose.ui.platform.LocalContext +import androidx.core.content.FileProvider +import androidx.core.net.toUri +import app.omnivore.omnivore.graphql.generated.type.ContentReader +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.InputStream +import java.net.URL +import java.nio.file.Files +import java.nio.file.StandardCopyOption data class LibrarySearchQueryResponse( val cursor: String?, val items: List @@ -43,7 +53,7 @@ suspend fun Networker.search( val itemList = result.data?.search?.onSearchSuccess?.edges ?: listOf() val searchItems = itemList.map { - saveLibraryItemContentToFile(context, it.node.id, it.node.content) + saveLibraryItemContentToFile(context, it.node.id, it.node.contentReader, it.node.content, it.node.url) LibrarySearchItem(item = SavedItem( savedItemId = it.node.id, title = it.node.title, @@ -129,14 +139,35 @@ private fun readFromInternalStorage(context: Context, fileName: String): String? } } +fun getUriForInternalFile(context: Context, fileName: String): Uri { + val file = File(context.filesDir, fileName) + return file.toUri() +} + -fun saveLibraryItemContentToFile(context: Context, libraryItemId: String, content: String?): Boolean { +suspend fun saveLibraryItemContentToFile(context: Context, libraryItemId: String, contentReader: ContentReader, content: String?, contentUrl: String?): Boolean { return try { - content?.let { content -> - writeToInternalStorage(context, content = content, fileName = "${libraryItemId}.html", ) - return false + var localPDFPath: String? = null + if (contentReader == ContentReader.PDF) { + val localPDFPath = "${libraryItemId}.pdf" + withContext(Dispatchers.IO) { + val urlStream: InputStream = URL(contentUrl).openStream() + context.openFileOutput(localPDFPath, Context.MODE_PRIVATE).use { outputStream -> + urlStream.use { inputStream -> + inputStream.copyTo(outputStream) + } + } + Log.d("PDF", "File written successfully to internal storage.") + } + Log.d("PDF", "DOWNLOADING PDF TO LOCAL PDF PATH: ${localPDFPath}") + true + } else { + content?.let { content -> + writeToInternalStorage(context, content = content, fileName = "${libraryItemId}.html", ) + return true + } + false } - false } catch (e: Exception) { e.printStackTrace() false diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/PDFReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/PDFReaderViewModel.kt index 3ad22f8242..4c7129e970 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/PDFReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/PDFReaderViewModel.kt @@ -3,6 +3,8 @@ package app.omnivore.omnivore.feature.reader import android.content.Context import android.net.Uri import android.util.Log +import androidx.core.content.FileProvider +import androidx.core.net.toFile import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -20,6 +22,7 @@ import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput import app.omnivore.omnivore.core.database.entities.SavedItem +import app.omnivore.omnivore.core.network.getUriForInternalFile import com.apollographql.apollo3.api.Optional import com.google.gson.Gson import com.pspdfkit.annotations.Annotation @@ -57,17 +60,22 @@ class PDFReaderViewModel @Inject constructor( fun loadItem(slug: String, context: Context) { viewModelScope.launch { - loadItemFromDB(slug) + loadItemFromDB(slug, context) loadItemFromNetwork(slug, context) } } - private suspend fun loadItemFromDB(slug: String) { + + + private suspend fun loadItemFromDB(slug: String, context: Context) { withContext(Dispatchers.IO) { val persistedItem = dataService.db.savedItemDao().getSavedItemWithLabelsAndHighlights(slug) persistedItem?.let { item -> - item.savedItem.localPDF?.let { localPDF -> - val localFile = File(localPDF) + Log.d("PDF", " - persistedItem?.let { item -> ${item}") + Log.d("PDF", " - item.savedItem.localPDF -> ${item.savedItem.localPDF}") + + val localPdf = getUriForInternalFile(context,"${item.savedItem.savedItemId}.pdf") + val localFile = localPdf.toFile() if (localFile.exists()) { val articleContent = ArticleContent( @@ -82,10 +90,9 @@ class PDFReaderViewModel @Inject constructor( PDFReaderParams( item.savedItem, articleContent, - Uri.fromFile(localFile) + localPdf ) ) - } } } } From 09db09f85d7491cab6e85e8d24cd09fa39e0d906 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 1 Jul 2024 23:56:00 +0800 Subject: [PATCH 2/2] Skip download of PDFs that are already downloaded --- .../java/app/omnivore/omnivore/core/network/SearchQuery.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt index a296e95510..fbd5ff4b2c 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt @@ -150,6 +150,12 @@ suspend fun saveLibraryItemContentToFile(context: Context, libraryItemId: String var localPDFPath: String? = null if (contentReader == ContentReader.PDF) { val localPDFPath = "${libraryItemId}.pdf" + val file = File(context.filesDir, localPDFPath) + if (file.exists()) { + // TODO: there should really be a checksum check here + Log.d("PDF", "SKIPPING DOWNLOAD FOR LOCAL PDF PATH: ${localPDFPath}") + return true + } withContext(Dispatchers.IO) { val urlStream: InputStream = URL(contentUrl).openStream() context.openFileOutput(localPDFPath, Context.MODE_PRIVATE).use { outputStream ->