diff --git a/core/src/main/java/eu/mhutti1/utils/storage/StorageDeviceUtils.kt b/core/src/main/java/eu/mhutti1/utils/storage/StorageDeviceUtils.kt index 0ed5fbd54..ee0eec2a3 100644 --- a/core/src/main/java/eu/mhutti1/utils/storage/StorageDeviceUtils.kt +++ b/core/src/main/java/eu/mhutti1/utils/storage/StorageDeviceUtils.kt @@ -20,6 +20,7 @@ package eu.mhutti1.utils.storage import android.content.Context import android.content.ContextWrapper +import android.os.Build import android.os.Environment import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -52,7 +53,9 @@ object StorageDeviceUtils { // In the Play Store variant, we can only access app-specific directories, // so scanning other directories is unnecessary, wastes resources, // and increases the scanning time. - if (sharedPreferenceUtil?.isNotPlayStoreBuildWithAndroid11OrAbove() == true) { + if (sharedPreferenceUtil?.isPlayStoreBuild == false || + Build.VERSION.SDK_INT < Build.VERSION_CODES.R + ) { addAll(externalMountPointDevices()) addAll(externalFilesDirsDevices(context, false)) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt index 7fc1ad4b8..b05d691ae 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt @@ -55,7 +55,10 @@ class NewBookDao @Inject constructor(private val box: Box) { .toList() .toFlowable() .flatMap { booksList -> - completableFromCoroutine { removeBooksThatDoNotExist(booksList.toMutableList()) } + completableFromCoroutine { + removeBooksThatAreInTrashFolder(booksList) + removeBooksThatDoNotExist(booksList.toMutableList()) + } .andThen(io.reactivex.rxjava3.core.Flowable.just(booksList)) } } @@ -68,7 +71,9 @@ class NewBookDao @Inject constructor(private val box: Box) { bookOnDiskEntity to exists } } - .filter(Pair::second) + .filter { (bookOnDiskEntity, exists) -> + exists && !isInTrashFolder(bookOnDiskEntity.zimReaderSource.toDatabase()) + } .map(Pair::first) .toList() .toFlowable() @@ -150,6 +155,15 @@ class NewBookDao @Inject constructor(private val box: Box) { delete(books.filterNot { it.zimReaderSource.exists() }) } + // Remove the existing books from database which are showing on the library screen. + private fun removeBooksThatAreInTrashFolder(books: List) { + delete(books.filter { isInTrashFolder(it.zimReaderSource.toDatabase()) }) + } + + // Check if any existing ZIM file showing on the library screen which is inside the trash folder. + private fun isInTrashFolder(filePath: String) = + Regex("/\\.Trash/").containsMatchIn(filePath) + private fun delete(books: List) { box.remove(books) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileSearch.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileSearch.kt index 3c1883684..b9e90ff24 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileSearch.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileSearch.kt @@ -48,11 +48,16 @@ class FileSearch @Inject constructor(private val context: Context) { private fun scanMediaStore() = mutableListOf().apply { queryMediaStore() ?.forEachRow { cursor -> - File(cursor.get(MediaColumns.DATA)).takeIf(File::canRead) - ?.also { add(it) } + File(cursor.get(MediaColumns.DATA)) + .takeIf { it.canRead() && isNotInTrashFolder(it) } + ?.also(::add) } } + // Exclude any file in trash folder. + private fun isNotInTrashFolder(it: File) = + !Regex("/\\.Trash/").containsMatchIn(it.path) + private fun queryMediaStore() = context.contentResolver .query( Files.getContentUri("external"), @@ -86,8 +91,8 @@ class FileSearch @Inject constructor(private val context: Context) { private fun scanDirectory(directory: String): List { return File(directory).walk() .onEnter { dir -> - // Excluding the "data," "obb," and "Trash" folders from scanning is justified for - // several reasons. The "Trash" folder contains deleted files, + // Excluding the "data," "obb," "hidden folders," and "Trash" folders from scanning is + // justified for several reasons. The "Trash" folder contains deleted files, // making it unnecessary for scanning. Additionally, // the "data" and "obb" folders are specifically designed for the // app's private directory, and users usually do not store ZIM files there. @@ -97,7 +102,8 @@ class FileSearch @Inject constructor(private val context: Context) { // which are irrelevant to our application. !dir.name.equals(".Trash", ignoreCase = true) && !dir.name.equals("data", ignoreCase = true) && - !dir.name.equals("obb", ignoreCase = true) + !dir.name.equals("obb", ignoreCase = true) && + !dir.name.startsWith(".", ignoreCase = true) }.filter { it.extension.isAny(*zimFileExtensions) }.toList() diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/dao/NewBookDaoTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/dao/NewBookDaoTest.kt index 91209c4d8..3fd8b7c65 100644 --- a/core/src/test/java/org/kiwix/kiwixmobile/core/dao/NewBookDaoTest.kt +++ b/core/src/test/java/org/kiwix/kiwixmobile/core/dao/NewBookDaoTest.kt @@ -81,7 +81,17 @@ internal class NewBookDaoTest { verify { box.remove(listOf(deletedEntity)) } } - private fun expectEmissionOfExistingAndNotExistingBook(): + @Test + fun `books removes entities whose files are in the trash folder`() { + runBlocking { + val (_, _) = expectEmissionOfExistingAndNotExistingBook(true) + val books = newBookDao.books().test() + delay(1000) + books.assertValues(emptyList()) + } + } + + private fun expectEmissionOfExistingAndNotExistingBook(isInTrashFolder: Boolean = false): Pair { val query: Query = mockk() every { box.query().build() } returns query @@ -89,6 +99,10 @@ internal class NewBookDaoTest { val zimReaderSourceThatDoesNotExist = mockk() coEvery { zimReaderSourceThatExists.exists() } returns true coEvery { zimReaderSourceThatDoesNotExist.exists() } returns false + every { + zimReaderSourceThatExists.toDatabase() + } returns if (isInTrashFolder) "/.Trash/test.zim" else "" + every { zimReaderSourceThatDoesNotExist.toDatabase() } returns "" val entityThatExists = bookOnDiskEntity(zimReaderSource = zimReaderSourceThatExists) val entityThatDoesNotExist = bookOnDiskEntity(zimReaderSource = zimReaderSourceThatDoesNotExist)