@@ -28,7 +28,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
2828import kotlinx.coroutines.flow.flowOn
2929import kotlinx.coroutines.flow.map
3030import kotlinx.coroutines.launch
31- import kotlinx.coroutines.runBlocking
31+ import kotlinx.coroutines.sync.Mutex
32+ import kotlinx.coroutines.sync.withLock
3233import kotlinx.coroutines.withContext
3334import org.kiwix.kiwixmobile.core.CoreApp
3435import org.kiwix.kiwixmobile.core.R
@@ -70,6 +71,8 @@ class LibkiwixBookmarks @Inject constructor(
7071 private var bookmarksChanged: Boolean = false
7172 private var bookmarkList: List <LibkiwixBookmarkItem > = arrayListOf ()
7273 private var libraryBooksList: List <String > = arrayListOf ()
74+ private val initMutex = Mutex ()
75+ private var initialized: Boolean = false
7376
7477 @Suppress(" InjectDispatcher" )
7578 private val bookmarkListFlow: MutableStateFlow <List <LibkiwixBookmarkItem >> by lazy {
@@ -101,17 +104,29 @@ class LibkiwixBookmarks @Inject constructor(
101104 File (" $bookmarksFolderPath /library.xml" )
102105 }
103106
104- init {
105- // Check if bookmark folder exist if not then create the folder first.
106- if (runBlocking { ! File (bookmarksFolderPath).isFileExist() }) File (bookmarksFolderPath).mkdir()
107- // Check if library file exist if not then create the file to save the library with book information.
108- if (runBlocking { ! libraryFile.isFileExist() }) libraryFile.createNewFile()
109- // set up manager to read the library from this file
110- manager.readFile(libraryFile.canonicalPath)
111- // Check if bookmark file exist if not then create the file to save the bookmarks.
112- if (runBlocking { ! bookmarkFile.isFileExist() }) bookmarkFile.createNewFile()
113- // set up manager to read the bookmarks from this file
114- manager.readBookmarkFile(bookmarkFile.canonicalPath)
107+ /* *
108+ * Ensure initialization runs once. This method performs all file I/O and manager setup
109+ * on Dispatchers.IO so it won't block the main thread. It is safe to call multiple times.
110+ */
111+ private suspend fun ensureInitialized (dispatcher : CoroutineDispatcher = Dispatchers .IO ) {
112+ if (initialized) return
113+
114+ initMutex.withLock {
115+ if (initialized) return
116+ withContext(dispatcher) {
117+ // Check if bookmark folder exist if not then create the folder first.
118+ if (! File (bookmarksFolderPath).isFileExist()) File (bookmarksFolderPath).mkdir()
119+ // Check if library file exist if not then create the file to save the library with book information.
120+ if (! libraryFile.isFileExist()) libraryFile.createNewFile()
121+ // set up manager to read the library from this file
122+ manager.readFile(libraryFile.canonicalPath)
123+ // Check if bookmark file exist if not then create the file to save the bookmarks.
124+ if (! bookmarkFile.isFileExist()) bookmarkFile.createNewFile()
125+ // set up manager to read the bookmarks from this file
126+ manager.readBookmarkFile(bookmarkFile.canonicalPath)
127+ initialized = true
128+ }
129+ }
115130 }
116131
117132 fun bookmarks (): Flow <List <Page >> =
@@ -124,14 +139,16 @@ class LibkiwixBookmarks @Inject constructor(
124139 deleteBookmarks(pagesToDelete as List <LibkiwixBookmarkItem >)
125140
126141 @Suppress(" InjectDispatcher" )
127- suspend fun getCurrentZimBookmarksUrl (zimFileReader : ZimFileReader ? ): List <String > =
128- withContext(Dispatchers .IO ) {
129- return @withContext zimFileReader?.let { reader ->
142+ suspend fun getCurrentZimBookmarksUrl (zimFileReader : ZimFileReader ? ): List <String > {
143+ ensureInitialized()
144+ return withContext(Dispatchers .IO ) {
145+ zimFileReader?.let { reader ->
130146 getBookmarksList()
131147 .filter { it.zimId == reader.id }
132148 .map(LibkiwixBookmarkItem ::bookmarkUrl)
133149 }.orEmpty()
134150 }
151+ }
135152
136153 @Suppress(" InjectDispatcher" )
137154 fun bookmarkUrlsForCurrentBook (zimId : String ): Flow <List <String >> =
@@ -150,6 +167,7 @@ class LibkiwixBookmarks @Inject constructor(
150167 libkiwixBookmarkItem : LibkiwixBookmarkItem ,
151168 shouldWriteBookmarkToFile : Boolean = true
152169 ) {
170+ ensureInitialized()
153171 if (! isBookMarkExist(libkiwixBookmarkItem)) {
154172 addBookToLibraryIfNotExist(libkiwixBookmarkItem.libKiwixBook)
155173 val bookmark =
@@ -178,7 +196,8 @@ class LibkiwixBookmarks @Inject constructor(
178196 }
179197
180198 suspend fun addBookToLibrary (file : File ? = null, archive : Archive ? = null) {
181- try {
199+ ensureInitialized()
200+ runCatching {
182201 bookmarksChanged = true
183202 val book =
184203 Book ().apply {
@@ -190,10 +209,10 @@ class LibkiwixBookmarks @Inject constructor(
190209 }
191210 addBookToLibraryIfNotExist(book)
192211 updateFlowBookmarkList()
193- } catch (ignore : Exception ) {
212+ }.onFailure {
194213 Log .e(
195214 TAG ,
196- " Error: Couldn't add the book to library.\n Original exception = $ignore "
215+ " Error: Couldn't add the book to library.\n Original exception = $it "
197216 )
198217 }
199218 }
@@ -228,14 +247,13 @@ class LibkiwixBookmarks @Inject constructor(
228247 bookmarks : List <LibkiwixBookmarkItem >,
229248 dispatcher : CoroutineDispatcher = Dispatchers .IO
230249 ) {
231- bookmarks.map { library.removeBookmark(it.zimId, it.bookmarkUrl) }
232- .also {
233- CoroutineScope (dispatcher).launch {
234- writeBookMarksAndSaveLibraryToFile()
235- updateFlowBookmarkList()
236- removeBookFromLibraryIfNoRelatedBookmarksAreStored(dispatcher, bookmarks)
237- }
238- }
250+ CoroutineScope (dispatcher).launch {
251+ ensureInitialized()
252+ bookmarks.map { library.removeBookmark(it.zimId, it.bookmarkUrl) }
253+ writeBookMarksAndSaveLibraryToFile()
254+ updateFlowBookmarkList()
255+ removeBookFromLibraryIfNoRelatedBookmarksAreStored(dispatcher, bookmarks)
256+ }
239257 }
240258
241259 fun deleteBookmark (bookId : String , bookmarkUrl : String ) {
@@ -255,6 +273,7 @@ class LibkiwixBookmarks @Inject constructor(
255273 dispatcher : CoroutineDispatcher ,
256274 deletedBookmarks : List <LibkiwixBookmarkItem >
257275 ) {
276+ ensureInitialized()
258277 withContext(dispatcher) {
259278 val currentBookmarks = getBookmarksList()
260279 val deletedZimIds = deletedBookmarks.map { it.zimId }.distinct()
@@ -263,6 +282,7 @@ class LibkiwixBookmarks @Inject constructor(
263282 val stillExists = currentBookmarks.any { it.zimId == zimId }
264283 if (! stillExists) {
265284 library.removeBookById(zimId)
285+ libraryBooksList = library.booksIds.toList()
266286 Log .d(TAG , " Removed book from library since no bookmarks exist for: $zimId " )
267287 }
268288 }
@@ -275,6 +295,7 @@ class LibkiwixBookmarks @Inject constructor(
275295 * to prevent potential data loss and ensures that the library holds the updated ZIM file paths and favicons.
276296 */
277297 private suspend fun writeBookMarksAndSaveLibraryToFile () {
298+ ensureInitialized()
278299 // Save the library, which contains ZIM file paths and favicons, to a file.
279300 library.writeToFile(libraryFile.canonicalPath)
280301
@@ -286,6 +307,7 @@ class LibkiwixBookmarks @Inject constructor(
286307
287308 @Suppress(" ReturnCount" )
288309 private suspend fun getBookmarksList (): List <LibkiwixBookmarkItem > {
310+ ensureInitialized()
289311 if (! bookmarksChanged && bookmarkList.isNotEmpty()) {
290312 // No changes, return the cached data
291313 return bookmarkList.distinctBy(LibkiwixBookmarkItem ::bookmarkUrl)
@@ -400,6 +422,7 @@ class LibkiwixBookmarks @Inject constructor(
400422
401423 // Export the `bookmark.xml` file to the `Download/org.kiwix/` directory of internal storage.
402424 suspend fun exportBookmark () {
425+ ensureInitialized()
403426 try {
404427 val bookmarkDestinationFile = exportedFile(" bookmark.xml" )
405428 bookmarkFile.inputStream().use { inputStream ->
@@ -438,6 +461,7 @@ class LibkiwixBookmarks @Inject constructor(
438461 }
439462
440463 suspend fun importBookmarks (bookmarkFile : File ) {
464+ ensureInitialized()
441465 // Create a temporary library manager to import the bookmarks.
442466 val tempLibrary = Library ()
443467 Manager (tempLibrary).apply {
0 commit comments