Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ package org.kiwix.kiwixmobile.di.components
import dagger.Component
import org.kiwix.kiwixmobile.core.data.ObjectBoxDataMigrationHandler
import org.kiwix.kiwixmobile.core.di.components.CoreComponent
import org.kiwix.kiwixmobile.migration.di.module.MigrationModule
import org.kiwix.kiwixmobile.di.KiwixScope
import org.kiwix.kiwixmobile.di.components.ServiceComponent.Builder
import org.kiwix.kiwixmobile.di.modules.JNIModule
import org.kiwix.kiwixmobile.di.modules.KiwixModule
import org.kiwix.kiwixmobile.di.modules.KiwixViewModelModule
import org.kiwix.kiwixmobile.migration.di.module.DatabaseModule
import org.kiwix.kiwixmobile.migration.di.module.MigrationModule
import org.kiwix.kiwixmobile.storage.StorageSelectDialog
import org.kiwix.kiwixmobile.zimManager.OnlineLibraryManager

Expand All @@ -37,7 +36,6 @@ import org.kiwix.kiwixmobile.zimManager.OnlineLibraryManager
modules = [
KiwixViewModelModule::class,
KiwixModule::class,
JNIModule::class,
MigrationModule::class,
DatabaseModule::class
]
Expand Down
52 changes: 0 additions & 52 deletions app/src/main/java/org/kiwix/kiwixmobile/di/modules/JNIModule.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,14 @@ import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.data.remote.KiwixService.Companion.OPDS_LIBRARY_ENDPOINT
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.di.modules.ONLINE_BOOKS_LIBRARY
import org.kiwix.kiwixmobile.di.modules.ONLINE_BOOKS_MANAGER
import org.kiwix.libkiwix.Library
import org.kiwix.libkiwix.Manager
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserFactory
import java.io.StringReader
import javax.inject.Inject
import javax.inject.Named

@Suppress("UnusedPrivateProperty")
class OnlineLibraryManager @Inject constructor(
@Named(ONLINE_BOOKS_LIBRARY) private val library: Library,
@Named(ONLINE_BOOKS_MANAGER) private val manager: Manager,
) {
class OnlineLibraryManager @Inject constructor() {
var totalResult = 0
suspend fun parseOPDSStreamAndGetBooks(
content: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks.Companion.TAG
import org.kiwix.kiwixmobile.core.di.modules.LOCAL_BOOKS_LIBRARY
Expand All @@ -50,6 +52,8 @@ class LibkiwixBookOnDisk @Inject constructor(
@Named(LOCAL_BOOKS_MANAGER) private val manager: Manager,
private val sharedPreferenceUtil: SharedPreferenceUtil
) {
private val initMutex = Mutex()
private var isManagerInitialized = false
private var libraryBooksList: List<String> = arrayListOf()
private var localBooksList: List<LibkiwixBook> = arrayListOf()

Expand All @@ -73,18 +77,29 @@ class LibkiwixBookOnDisk @Inject constructor(
File("$localBookFolderPath/library.xml")
}

init {
// Check if ZIM files folder exist if not then create the folder first.
if (runBlocking { !File(localBookFolderPath).isFileExist() }) File(localBookFolderPath).mkdir()
// Check if library file exist if not then create the file to save the library with book information.
if (runBlocking { !libraryFile.isFileExist() }) libraryFile.createNewFile()
// set up manager to read the library from this file
manager.readFile(libraryFile.canonicalPath)
private suspend fun ensureInitialized(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
if (isManagerInitialized) return
initMutex.withLock {
if (isManagerInitialized) return@withLock true
withContext(dispatcher) {
// Check if ZIM files folder exist if not then create the folder first.
if (!File(localBookFolderPath).isFileExist()) {
File(localBookFolderPath).mkdirs()
}
// Check if library file exist if not then create the file to save the library with book information.
if (!libraryFile.isFileExist()) {
libraryFile.createNewFile()
}
// set up manager to read the library from this file
manager.readFile(libraryFile.canonicalPath)
isManagerInitialized = true
}
}
}

@Suppress("InjectDispatcher")
private val localBooksFlow: MutableStateFlow<List<LibkiwixBook>> by lazy {
MutableStateFlow<List<LibkiwixBook>>(emptyList()).also { flow ->
private val localBooksFlow: MutableStateFlow<List<LibkiwixBook>?> by lazy {
MutableStateFlow<List<LibkiwixBook>?>(null).also { flow ->
CoroutineScope(Dispatchers.IO).launch {
runCatching {
flow.emit(getBooksList())
Expand All @@ -95,6 +110,8 @@ class LibkiwixBookOnDisk @Inject constructor(

private suspend fun getBooksList(dispatcher: CoroutineDispatcher = Dispatchers.IO): List<LibkiwixBook> =
withContext(dispatcher) {
// if reading library failed, return empty list
ensureInitialized()
if (!booksChanged && localBooksList.isNotEmpty()) {
// No changes, return the cached data
return@withContext localBooksList.distinctBy(LibkiwixBook::path)
Expand All @@ -118,6 +135,7 @@ class LibkiwixBookOnDisk @Inject constructor(
@OptIn(ExperimentalCoroutinesApi::class)
fun books(dispatcher: CoroutineDispatcher = Dispatchers.IO) =
localBooksFlow
.filterNotNull()
.mapLatest { booksList ->
removeBooksThatAreInTrashFolder(booksList)
removeBooksThatDoNotExist(booksList.toMutableList())
Expand All @@ -143,6 +161,7 @@ class LibkiwixBookOnDisk @Inject constructor(
@Suppress("InjectDispatcher")
suspend fun insert(libkiwixBooks: List<Book>) {
withContext(Dispatchers.IO) {
ensureInitialized()
val existingBookIds = library.booksIds.toSet()
val existingBookPaths = existingBookIds
.mapNotNull { id -> library.getBookById(id)?.path }
Expand Down Expand Up @@ -207,6 +226,7 @@ class LibkiwixBookOnDisk @Inject constructor(

suspend fun delete(books: List<LibkiwixBook>) {
runCatching {
ensureInitialized()
books.forEach {
library.removeBookById(it.id)
}
Expand All @@ -217,6 +237,7 @@ class LibkiwixBookOnDisk @Inject constructor(

suspend fun delete(bookId: String) {
runCatching {
ensureInitialized()
library.removeBookById(bookId)
writeBookMarksAndSaveLibraryToFile()
updateLocalBooksFlow()
Expand All @@ -236,6 +257,7 @@ class LibkiwixBookOnDisk @Inject constructor(
* to prevent potential data loss and ensures that the library holds the updated ZIM file data.
*/
private suspend fun writeBookMarksAndSaveLibraryToFile() {
ensureInitialized()
// Save the library, which contains ZIM file data.
library.writeToFile(libraryFile.canonicalPath)
// set the bookmark change to true so that libkiwix will return the new data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.R
Expand Down Expand Up @@ -70,6 +71,8 @@ class LibkiwixBookmarks @Inject constructor(
private var bookmarksChanged: Boolean = false
private var bookmarkList: List<LibkiwixBookmarkItem> = arrayListOf()
private var libraryBooksList: List<String> = arrayListOf()
private val initMutex = Mutex()
private var initialized: Boolean = false

@Suppress("InjectDispatcher")
private val bookmarkListFlow: MutableStateFlow<List<LibkiwixBookmarkItem>> by lazy {
Expand Down Expand Up @@ -101,17 +104,29 @@ class LibkiwixBookmarks @Inject constructor(
File("$bookmarksFolderPath/library.xml")
}

init {
// Check if bookmark folder exist if not then create the folder first.
if (runBlocking { !File(bookmarksFolderPath).isFileExist() }) File(bookmarksFolderPath).mkdir()
// Check if library file exist if not then create the file to save the library with book information.
if (runBlocking { !libraryFile.isFileExist() }) libraryFile.createNewFile()
// set up manager to read the library from this file
manager.readFile(libraryFile.canonicalPath)
// Check if bookmark file exist if not then create the file to save the bookmarks.
if (runBlocking { !bookmarkFile.isFileExist() }) bookmarkFile.createNewFile()
// set up manager to read the bookmarks from this file
manager.readBookmarkFile(bookmarkFile.canonicalPath)
/**
* Ensure initialization runs once. This method performs all file I/O and manager setup
* on Dispatchers.IO so it won't block the main thread. It is safe to call multiple times.
*/
private suspend fun ensureInitialized(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
if (initialized) return

initMutex.withLock {
if (initialized) return
withContext(dispatcher) {
// Check if bookmark folder exist if not then create the folder first.
if (!File(bookmarksFolderPath).isFileExist()) File(bookmarksFolderPath).mkdir()
// Check if library file exist if not then create the file to save the library with book information.
if (!libraryFile.isFileExist()) libraryFile.createNewFile()
// set up manager to read the library from this file
manager.readFile(libraryFile.canonicalPath)
// Check if bookmark file exist if not then create the file to save the bookmarks.
if (!bookmarkFile.isFileExist()) bookmarkFile.createNewFile()
// set up manager to read the bookmarks from this file
manager.readBookmarkFile(bookmarkFile.canonicalPath)
initialized = true
}
}
}

fun bookmarks(): Flow<List<Page>> =
Expand All @@ -124,14 +139,16 @@ class LibkiwixBookmarks @Inject constructor(
deleteBookmarks(pagesToDelete as List<LibkiwixBookmarkItem>)

@Suppress("InjectDispatcher")
suspend fun getCurrentZimBookmarksUrl(zimFileReader: ZimFileReader?): List<String> =
withContext(Dispatchers.IO) {
return@withContext zimFileReader?.let { reader ->
suspend fun getCurrentZimBookmarksUrl(zimFileReader: ZimFileReader?): List<String> {
ensureInitialized()
return withContext(Dispatchers.IO) {
zimFileReader?.let { reader ->
getBookmarksList()
.filter { it.zimId == reader.id }
.map(LibkiwixBookmarkItem::bookmarkUrl)
}.orEmpty()
}
}

@Suppress("InjectDispatcher")
fun bookmarkUrlsForCurrentBook(zimId: String): Flow<List<String>> =
Expand All @@ -150,6 +167,7 @@ class LibkiwixBookmarks @Inject constructor(
libkiwixBookmarkItem: LibkiwixBookmarkItem,
shouldWriteBookmarkToFile: Boolean = true
) {
ensureInitialized()
if (!isBookMarkExist(libkiwixBookmarkItem)) {
addBookToLibraryIfNotExist(libkiwixBookmarkItem.libKiwixBook)
val bookmark =
Expand Down Expand Up @@ -178,7 +196,8 @@ class LibkiwixBookmarks @Inject constructor(
}

suspend fun addBookToLibrary(file: File? = null, archive: Archive? = null) {
try {
ensureInitialized()
runCatching {
bookmarksChanged = true
val book =
Book().apply {
Expand All @@ -190,10 +209,10 @@ class LibkiwixBookmarks @Inject constructor(
}
addBookToLibraryIfNotExist(book)
updateFlowBookmarkList()
} catch (ignore: Exception) {
}.onFailure {
Log.e(
TAG,
"Error: Couldn't add the book to library.\nOriginal exception = $ignore"
"Error: Couldn't add the book to library.\nOriginal exception = $it"
)
}
}
Expand Down Expand Up @@ -228,14 +247,13 @@ class LibkiwixBookmarks @Inject constructor(
bookmarks: List<LibkiwixBookmarkItem>,
dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
bookmarks.map { library.removeBookmark(it.zimId, it.bookmarkUrl) }
.also {
CoroutineScope(dispatcher).launch {
writeBookMarksAndSaveLibraryToFile()
updateFlowBookmarkList()
removeBookFromLibraryIfNoRelatedBookmarksAreStored(dispatcher, bookmarks)
}
}
CoroutineScope(dispatcher).launch {
ensureInitialized()
bookmarks.map { library.removeBookmark(it.zimId, it.bookmarkUrl) }
writeBookMarksAndSaveLibraryToFile()
updateFlowBookmarkList()
removeBookFromLibraryIfNoRelatedBookmarksAreStored(dispatcher, bookmarks)
}
}

fun deleteBookmark(bookId: String, bookmarkUrl: String) {
Expand All @@ -255,6 +273,7 @@ class LibkiwixBookmarks @Inject constructor(
dispatcher: CoroutineDispatcher,
deletedBookmarks: List<LibkiwixBookmarkItem>
) {
ensureInitialized()
withContext(dispatcher) {
val currentBookmarks = getBookmarksList()
val deletedZimIds = deletedBookmarks.map { it.zimId }.distinct()
Expand All @@ -263,6 +282,7 @@ class LibkiwixBookmarks @Inject constructor(
val stillExists = currentBookmarks.any { it.zimId == zimId }
if (!stillExists) {
library.removeBookById(zimId)
libraryBooksList = library.booksIds.toList()
Log.d(TAG, "Removed book from library since no bookmarks exist for: $zimId")
}
}
Expand All @@ -275,6 +295,7 @@ class LibkiwixBookmarks @Inject constructor(
* to prevent potential data loss and ensures that the library holds the updated ZIM file paths and favicons.
*/
private suspend fun writeBookMarksAndSaveLibraryToFile() {
ensureInitialized()
// Save the library, which contains ZIM file paths and favicons, to a file.
library.writeToFile(libraryFile.canonicalPath)

Expand All @@ -286,6 +307,7 @@ class LibkiwixBookmarks @Inject constructor(

@Suppress("ReturnCount")
private suspend fun getBookmarksList(): List<LibkiwixBookmarkItem> {
ensureInitialized()
if (!bookmarksChanged && bookmarkList.isNotEmpty()) {
// No changes, return the cached data
return bookmarkList.distinctBy(LibkiwixBookmarkItem::bookmarkUrl)
Expand Down Expand Up @@ -400,6 +422,7 @@ class LibkiwixBookmarks @Inject constructor(

// Export the `bookmark.xml` file to the `Download/org.kiwix/` directory of internal storage.
suspend fun exportBookmark() {
ensureInitialized()
try {
val bookmarkDestinationFile = exportedFile("bookmark.xml")
bookmarkFile.inputStream().use { inputStream ->
Expand Down Expand Up @@ -438,6 +461,7 @@ class LibkiwixBookmarks @Inject constructor(
}

suspend fun importBookmarks(bookmarkFile: File) {
ensureInitialized()
// Create a temporary library manager to import the bookmarks.
val tempLibrary = Library()
Manager(tempLibrary).apply {
Expand Down
Loading