mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 18:56:44 -04:00
Merge pull request #4176 from kiwix/Fixes#4175
Fixed: The ZIM file was displayed on the library screen but failed to open (the ZIM file was located in the SD card's trash folder).
This commit is contained in:
commit
bff8a97055
@ -20,6 +20,7 @@ package eu.mhutti1.utils.storage
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -52,7 +53,9 @@ object StorageDeviceUtils {
|
|||||||
// In the Play Store variant, we can only access app-specific directories,
|
// In the Play Store variant, we can only access app-specific directories,
|
||||||
// so scanning other directories is unnecessary, wastes resources,
|
// so scanning other directories is unnecessary, wastes resources,
|
||||||
// and increases the scanning time.
|
// and increases the scanning time.
|
||||||
if (sharedPreferenceUtil?.isNotPlayStoreBuildWithAndroid11OrAbove() == true) {
|
if (sharedPreferenceUtil?.isPlayStoreBuild == false ||
|
||||||
|
Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
||||||
|
) {
|
||||||
addAll(externalMountPointDevices())
|
addAll(externalMountPointDevices())
|
||||||
addAll(externalFilesDirsDevices(context, false))
|
addAll(externalFilesDirsDevices(context, false))
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,10 @@ class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
|
|||||||
.toList()
|
.toList()
|
||||||
.toFlowable()
|
.toFlowable()
|
||||||
.flatMap { booksList ->
|
.flatMap { booksList ->
|
||||||
completableFromCoroutine { removeBooksThatDoNotExist(booksList.toMutableList()) }
|
completableFromCoroutine {
|
||||||
|
removeBooksThatAreInTrashFolder(booksList)
|
||||||
|
removeBooksThatDoNotExist(booksList.toMutableList())
|
||||||
|
}
|
||||||
.andThen(io.reactivex.rxjava3.core.Flowable.just(booksList))
|
.andThen(io.reactivex.rxjava3.core.Flowable.just(booksList))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,7 +71,9 @@ class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
|
|||||||
bookOnDiskEntity to exists
|
bookOnDiskEntity to exists
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.filter(Pair<BookOnDiskEntity, Boolean>::second)
|
.filter { (bookOnDiskEntity, exists) ->
|
||||||
|
exists && !isInTrashFolder(bookOnDiskEntity.zimReaderSource.toDatabase())
|
||||||
|
}
|
||||||
.map(Pair<BookOnDiskEntity, Boolean>::first)
|
.map(Pair<BookOnDiskEntity, Boolean>::first)
|
||||||
.toList()
|
.toList()
|
||||||
.toFlowable()
|
.toFlowable()
|
||||||
@ -150,6 +155,15 @@ class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
|
|||||||
delete(books.filterNot { it.zimReaderSource.exists() })
|
delete(books.filterNot { it.zimReaderSource.exists() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the existing books from database which are showing on the library screen.
|
||||||
|
private fun removeBooksThatAreInTrashFolder(books: List<BookOnDiskEntity>) {
|
||||||
|
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<BookOnDiskEntity>) {
|
private fun delete(books: List<BookOnDiskEntity>) {
|
||||||
box.remove(books)
|
box.remove(books)
|
||||||
}
|
}
|
||||||
|
@ -48,11 +48,16 @@ class FileSearch @Inject constructor(private val context: Context) {
|
|||||||
private fun scanMediaStore() = mutableListOf<File>().apply {
|
private fun scanMediaStore() = mutableListOf<File>().apply {
|
||||||
queryMediaStore()
|
queryMediaStore()
|
||||||
?.forEachRow { cursor ->
|
?.forEachRow { cursor ->
|
||||||
File(cursor.get<String>(MediaColumns.DATA)).takeIf(File::canRead)
|
File(cursor.get<String>(MediaColumns.DATA))
|
||||||
?.also { add(it) }
|
.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
|
private fun queryMediaStore() = context.contentResolver
|
||||||
.query(
|
.query(
|
||||||
Files.getContentUri("external"),
|
Files.getContentUri("external"),
|
||||||
@ -86,8 +91,8 @@ class FileSearch @Inject constructor(private val context: Context) {
|
|||||||
private fun scanDirectory(directory: String): List<File> {
|
private fun scanDirectory(directory: String): List<File> {
|
||||||
return File(directory).walk()
|
return File(directory).walk()
|
||||||
.onEnter { dir ->
|
.onEnter { dir ->
|
||||||
// Excluding the "data," "obb," and "Trash" folders from scanning is justified for
|
// Excluding the "data," "obb," "hidden folders," and "Trash" folders from scanning is
|
||||||
// several reasons. The "Trash" folder contains deleted files,
|
// justified for several reasons. The "Trash" folder contains deleted files,
|
||||||
// making it unnecessary for scanning. Additionally,
|
// making it unnecessary for scanning. Additionally,
|
||||||
// the "data" and "obb" folders are specifically designed for the
|
// the "data" and "obb" folders are specifically designed for the
|
||||||
// app's private directory, and users usually do not store ZIM files there.
|
// 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.
|
// which are irrelevant to our application.
|
||||||
!dir.name.equals(".Trash", ignoreCase = true) &&
|
!dir.name.equals(".Trash", ignoreCase = true) &&
|
||||||
!dir.name.equals("data", 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 {
|
}.filter {
|
||||||
it.extension.isAny(*zimFileExtensions)
|
it.extension.isAny(*zimFileExtensions)
|
||||||
}.toList()
|
}.toList()
|
||||||
|
@ -81,7 +81,17 @@ internal class NewBookDaoTest {
|
|||||||
verify { box.remove(listOf(deletedEntity)) }
|
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<BookOnDiskEntity, BookOnDiskEntity> {
|
Pair<BookOnDiskEntity, BookOnDiskEntity> {
|
||||||
val query: Query<BookOnDiskEntity> = mockk()
|
val query: Query<BookOnDiskEntity> = mockk()
|
||||||
every { box.query().build() } returns query
|
every { box.query().build() } returns query
|
||||||
@ -89,6 +99,10 @@ internal class NewBookDaoTest {
|
|||||||
val zimReaderSourceThatDoesNotExist = mockk<ZimReaderSource>()
|
val zimReaderSourceThatDoesNotExist = mockk<ZimReaderSource>()
|
||||||
coEvery { zimReaderSourceThatExists.exists() } returns true
|
coEvery { zimReaderSourceThatExists.exists() } returns true
|
||||||
coEvery { zimReaderSourceThatDoesNotExist.exists() } returns false
|
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 entityThatExists = bookOnDiskEntity(zimReaderSource = zimReaderSourceThatExists)
|
||||||
val entityThatDoesNotExist =
|
val entityThatDoesNotExist =
|
||||||
bookOnDiskEntity(zimReaderSource = zimReaderSourceThatDoesNotExist)
|
bookOnDiskEntity(zimReaderSource = zimReaderSourceThatDoesNotExist)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user