mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-07 22:31:17 -04:00
Implemented error handling for cases when the selected file is not a ZIM file.
* We retrieve the file name from the incoming Uri using `DocumentFile` (the official way to handle Uris) and check if it contains `.zim` or `.zimma` before proceeding with the copy/move operation. If the file name is null, we copy/move the file first and then validate it as a ZIM file using `libkiwix`. If valid, the file opens in our reader; otherwise, we revert the copy/move operation. * Added test cases for this new functionality.
This commit is contained in:
parent
ab3d8ffd75
commit
a45f94add7
@ -28,6 +28,7 @@ import android.view.View
|
|||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
@ -40,6 +41,7 @@ import org.kiwix.kiwixmobile.R.layout
|
|||||||
import org.kiwix.kiwixmobile.core.R
|
import org.kiwix.kiwixmobile.core.R
|
||||||
import org.kiwix.kiwixmobile.core.extensions.deleteFile
|
import org.kiwix.kiwixmobile.core.extensions.deleteFile
|
||||||
import org.kiwix.kiwixmobile.core.extensions.isFileExist
|
import org.kiwix.kiwixmobile.core.extensions.isFileExist
|
||||||
|
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
||||||
import org.kiwix.kiwixmobile.core.settings.StorageCalculator
|
import org.kiwix.kiwixmobile.core.settings.StorageCalculator
|
||||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
||||||
import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
|
import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower
|
||||||
@ -69,7 +71,8 @@ class CopyMoveFileHandler @Inject constructor(
|
|||||||
private var lifecycleScope: CoroutineScope? = null
|
private var lifecycleScope: CoroutineScope? = null
|
||||||
private var progressBar: ProgressBar? = null
|
private var progressBar: ProgressBar? = null
|
||||||
private var progressBarTextView: TextView? = null
|
private var progressBarTextView: TextView? = null
|
||||||
private var isMoveOperation = false
|
var isMoveOperation = false
|
||||||
|
private var shouldValidateZimFile: Boolean = false
|
||||||
private var fileSystemDisposable: Disposable? = null
|
private var fileSystemDisposable: Disposable? = null
|
||||||
|
|
||||||
private val copyMoveTitle: String by lazy {
|
private val copyMoveTitle: String by lazy {
|
||||||
@ -87,7 +90,12 @@ class CopyMoveFileHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showMoveFileToPublicDirectoryDialog(uri: Uri? = null, documentFile: DocumentFile? = null) {
|
fun showMoveFileToPublicDirectoryDialog(
|
||||||
|
uri: Uri? = null,
|
||||||
|
documentFile: DocumentFile? = null,
|
||||||
|
shouldValidateZimFile: Boolean = false
|
||||||
|
) {
|
||||||
|
this.shouldValidateZimFile = shouldValidateZimFile
|
||||||
setSelectedFileAndUri(uri, documentFile)
|
setSelectedFileAndUri(uri, documentFile)
|
||||||
if (!sharedPreferenceUtil.copyMoveZimFilePermissionDialog) {
|
if (!sharedPreferenceUtil.copyMoveZimFilePermissionDialog) {
|
||||||
showMoveToPublicDirectoryPermissionDialog()
|
showMoveToPublicDirectoryPermissionDialog()
|
||||||
@ -210,50 +218,63 @@ class CopyMoveFileHandler @Inject constructor(
|
|||||||
showProgressDialog()
|
showProgressDialog()
|
||||||
copyFile(sourceUri, destinationFile)
|
copyFile(sourceUri, destinationFile)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
notifyFileOperationSuccess(destinationFile)
|
notifyFileOperationSuccess(destinationFile, sourceUri)
|
||||||
}
|
}
|
||||||
} catch (ignore: Exception) {
|
} catch (ignore: Exception) {
|
||||||
ignore.printStackTrace()
|
ignore.printStackTrace()
|
||||||
handleFileOperationError(ignore.message, destinationFile)
|
handleFileOperationError(
|
||||||
|
activity.getString(R.string.copy_file_error_message, ignore.message),
|
||||||
|
destinationFile
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UnsafeCallOnNullableType")
|
||||||
private fun moveZimFileToPublicAppDirectory() {
|
private fun moveZimFileToPublicAppDirectory() {
|
||||||
lifecycleScope?.launch {
|
lifecycleScope?.launch {
|
||||||
val destinationFile = getDestinationFile()
|
val destinationFile = getDestinationFile()
|
||||||
try {
|
try {
|
||||||
val sourceUri = selectedFileUri ?: throw FileNotFoundException("Selected file not found")
|
val sourceUri = selectedFileUri ?: throw FileNotFoundException("Selected file not found")
|
||||||
showProgressDialog()
|
showProgressDialog()
|
||||||
var moveSuccess = false
|
val moveSuccess = selectedFile?.parentFile?.uri?.let { parentUri ->
|
||||||
if (tryMoveWithDocumentContract(sourceUri)) {
|
tryMoveWithDocumentContract(
|
||||||
moveSuccess = true
|
sourceUri,
|
||||||
} else {
|
parentUri,
|
||||||
moveSuccess = true
|
DocumentFile.fromFile(File(sharedPreferenceUtil.prefStorage)).uri
|
||||||
|
)
|
||||||
|
} ?: run {
|
||||||
copyFile(sourceUri, destinationFile)
|
copyFile(sourceUri, destinationFile)
|
||||||
deleteSourceFile(sourceUri)
|
true
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (moveSuccess) {
|
if (moveSuccess) {
|
||||||
notifyFileOperationSuccess(destinationFile)
|
notifyFileOperationSuccess(destinationFile, sourceUri)
|
||||||
} else {
|
} else {
|
||||||
handleFileOperationError("File move failed", destinationFile)
|
handleFileOperationError(
|
||||||
|
activity.getString(R.string.move_file_error_message, "File move failed"),
|
||||||
|
destinationFile
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ignore: Exception) {
|
} catch (ignore: Exception) {
|
||||||
ignore.printStackTrace()
|
ignore.printStackTrace()
|
||||||
handleFileOperationError(ignore.message, destinationFile)
|
handleFileOperationError(
|
||||||
|
activity.getString(R.string.move_file_error_message, ignore.message),
|
||||||
|
destinationFile
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UnsafeCallOnNullableType")
|
fun tryMoveWithDocumentContract(
|
||||||
private fun tryMoveWithDocumentContract(selectedUri: Uri): Boolean {
|
selectedUri: Uri,
|
||||||
|
sourceParentFolderUri: Uri,
|
||||||
|
destinationFolderUri: Uri
|
||||||
|
): Boolean {
|
||||||
return try {
|
return try {
|
||||||
val contentResolver = activity.contentResolver
|
val contentResolver = activity.contentResolver
|
||||||
if (documentCanMove(selectedUri, contentResolver)) {
|
if (documentCanMove(selectedUri, contentResolver)) {
|
||||||
val sourceParentFolderUri = selectedFile?.parentFile!!.uri
|
|
||||||
val destinationFolderUri = DocumentFile.fromFile(File(sharedPreferenceUtil.prefStorage)).uri
|
|
||||||
|
|
||||||
DocumentsContract.moveDocument(
|
DocumentsContract.moveDocument(
|
||||||
contentResolver,
|
contentResolver,
|
||||||
@ -289,17 +310,12 @@ class CopyMoveFileHandler @Inject constructor(
|
|||||||
return flags and DocumentsContract.Document.FLAG_SUPPORTS_MOVE != 0
|
return flags and DocumentsContract.Document.FLAG_SUPPORTS_MOVE != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleFileOperationError(
|
fun handleFileOperationError(
|
||||||
errorMessage: String?,
|
errorMessage: String?,
|
||||||
destinationFile: File
|
destinationFile: File
|
||||||
) {
|
) {
|
||||||
dismissProgressDialog()
|
dismissProgressDialog()
|
||||||
val userFriendlyMessage = if (isMoveOperation) {
|
fileCopyMoveCallback?.onError("$errorMessage").also {
|
||||||
activity.getString(R.string.move_file_error_message, errorMessage)
|
|
||||||
} else {
|
|
||||||
activity.getString(R.string.copy_file_error_message, errorMessage)
|
|
||||||
}
|
|
||||||
fileCopyMoveCallback?.onError(userFriendlyMessage).also {
|
|
||||||
// Clean up the destination file if an error occurs
|
// Clean up the destination file if an error occurs
|
||||||
lifecycleScope?.launch {
|
lifecycleScope?.launch {
|
||||||
destinationFile.deleteFile()
|
destinationFile.deleteFile()
|
||||||
@ -307,16 +323,55 @@ class CopyMoveFileHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyFileOperationSuccess(destinationFile: File) {
|
suspend fun notifyFileOperationSuccess(destinationFile: File, sourceUri: Uri) {
|
||||||
|
if (shouldValidateZimFile && !isValidZimFile(destinationFile)) {
|
||||||
|
handleInvalidZimFile(destinationFile, sourceUri)
|
||||||
|
return
|
||||||
|
}
|
||||||
dismissProgressDialog()
|
dismissProgressDialog()
|
||||||
if (isMoveOperation) {
|
if (isMoveOperation) {
|
||||||
|
deleteSourceFile(sourceUri)
|
||||||
fileCopyMoveCallback?.onFileMoved(destinationFile)
|
fileCopyMoveCallback?.onFileMoved(destinationFile)
|
||||||
} else {
|
} else {
|
||||||
fileCopyMoveCallback?.onFileCopied(destinationFile)
|
fileCopyMoveCallback?.onFileCopied(destinationFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteSourceFile(uri: Uri) {
|
fun handleInvalidZimFile(destinationFile: File, sourceUri: Uri) {
|
||||||
|
val errorMessage = activity.getString(R.string.error_file_invalid)
|
||||||
|
if (isMoveOperation) {
|
||||||
|
val moveSuccessful = tryMoveWithDocumentContract(
|
||||||
|
destinationFile.toUri(),
|
||||||
|
destinationFile.parentFile.toUri(),
|
||||||
|
sourceUri
|
||||||
|
)
|
||||||
|
|
||||||
|
if (moveSuccessful) {
|
||||||
|
// If files is moved back using the documentContract then show the error message to user
|
||||||
|
dismissProgressDialog()
|
||||||
|
fileCopyMoveCallback?.onError(errorMessage)
|
||||||
|
} else {
|
||||||
|
// Show error message and delete the moved file if move failed.
|
||||||
|
handleFileOperationError(errorMessage, destinationFile)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For copy operation, show error message and delete the copied file.
|
||||||
|
handleFileOperationError(errorMessage, destinationFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun isValidZimFile(destinationFile: File): Boolean {
|
||||||
|
return try {
|
||||||
|
// create archive object, and check if it has the mainEntry or not to validate the ZIM file.
|
||||||
|
val archive = ZimReaderSource(destinationFile).createArchive()
|
||||||
|
archive?.hasMainEntry() == true
|
||||||
|
} catch (ignore: Exception) {
|
||||||
|
// if it is a invalid ZIM file
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteSourceFile(uri: Uri) {
|
||||||
try {
|
try {
|
||||||
DocumentsContract.deleteDocument(activity.applicationContext.contentResolver, uri)
|
DocumentsContract.deleteDocument(activity.applicationContext.contentResolver, uri)
|
||||||
} catch (ignore: Exception) {
|
} catch (ignore: Exception) {
|
||||||
@ -413,7 +468,7 @@ class CopyMoveFileHandler @Inject constructor(
|
|||||||
progressBarDialog?.show()
|
progressBarDialog?.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dismissProgressDialog() {
|
fun dismissProgressDialog() {
|
||||||
if (progressBarDialog?.isShowing == true) {
|
if (progressBarDialog?.isShowing == true) {
|
||||||
progressBarDialog?.dismiss()
|
progressBarDialog?.dismiss()
|
||||||
}
|
}
|
||||||
|
@ -415,9 +415,19 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
|
|||||||
DocumentFile.fromSingleUri(requireActivity(), uri)
|
DocumentFile.fromSingleUri(requireActivity(), uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If the file is not valid, it shows an error message and stops further processing.
|
||||||
|
// If the file name is not found, then let them to copy the file
|
||||||
|
// and we will handle this later.
|
||||||
|
val fileName = documentFile?.name
|
||||||
|
if (fileName != null && !FileUtils.isValidZimFile(fileName)) {
|
||||||
|
activity.toast(string.error_file_invalid)
|
||||||
|
return
|
||||||
|
}
|
||||||
copyMoveFileHandler?.showMoveFileToPublicDirectoryDialog(
|
copyMoveFileHandler?.showMoveFileToPublicDirectoryDialog(
|
||||||
uri,
|
uri,
|
||||||
documentFile
|
documentFile,
|
||||||
|
// pass if fileName is null then we will validate it after copying/moving
|
||||||
|
fileName == null
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
getZimFileFromUri(uri)?.let(::navigateToReaderFragment)
|
getZimFileFromUri(uri)?.let(::navigateToReaderFragment)
|
||||||
|
@ -19,17 +19,22 @@
|
|||||||
package org.kiwix.kiwixmobile.localLibrary
|
package org.kiwix.kiwixmobile.localLibrary
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.net.Uri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import io.mockk.Runs
|
import io.mockk.Runs
|
||||||
import io.mockk.clearAllMocks
|
import io.mockk.clearAllMocks
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkConstructor
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
import io.mockk.spyk
|
import io.mockk.spyk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
@ -46,6 +51,9 @@ import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CanWrite4Gb
|
|||||||
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CannotWrite4GbFile
|
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CannotWrite4GbFile
|
||||||
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.DetectingFileSystem
|
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.DetectingFileSystem
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import org.kiwix.kiwixmobile.core.R
|
||||||
|
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
||||||
|
import org.kiwix.libzim.Archive
|
||||||
|
|
||||||
class CopyMoveFileHandlerTest {
|
class CopyMoveFileHandlerTest {
|
||||||
private lateinit var fileHandler: CopyMoveFileHandler
|
private lateinit var fileHandler: CopyMoveFileHandler
|
||||||
@ -61,6 +69,8 @@ class CopyMoveFileHandlerTest {
|
|||||||
private val storageFile: File = mockk(relaxed = true)
|
private val storageFile: File = mockk(relaxed = true)
|
||||||
private val selectedFile: DocumentFile = mockk(relaxed = true)
|
private val selectedFile: DocumentFile = mockk(relaxed = true)
|
||||||
private val storagePath = "storage/0/emulated/Android/media/org.kiwix.kiwixmobile"
|
private val storagePath = "storage/0/emulated/Android/media/org.kiwix.kiwixmobile"
|
||||||
|
private val destinationFile = mockk<File>()
|
||||||
|
private val sourceUri = mockk<Uri>()
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
@ -76,6 +86,7 @@ class CopyMoveFileHandlerTest {
|
|||||||
setLifeCycleScope(testScope)
|
setLifeCycleScope(testScope)
|
||||||
setFileCopyMoveCallback(this@CopyMoveFileHandlerTest.fileCopyMoveCallback)
|
setFileCopyMoveCallback(this@CopyMoveFileHandlerTest.fileCopyMoveCallback)
|
||||||
}
|
}
|
||||||
|
every { destinationFile.canRead() } returns true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -292,6 +303,91 @@ class CopyMoveFileHandlerTest {
|
|||||||
every { fat32Checker.fileSystemStates.value } returns fileSystemState
|
every { fat32Checker.fileSystemStates.value } returns fileSystemState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun notifyFileOperationSuccessShouldCallOnFileMovedIfValidZIMFileAndIsMoveOperationIsTrue() =
|
||||||
|
runTest {
|
||||||
|
fileHandler = spyk(fileHandler)
|
||||||
|
coEvery { fileHandler.isValidZimFile(destinationFile) } returns true
|
||||||
|
fileHandler.isMoveOperation = true
|
||||||
|
|
||||||
|
fileHandler.notifyFileOperationSuccess(destinationFile, sourceUri)
|
||||||
|
|
||||||
|
verify { fileCopyMoveCallback.onFileMoved(destinationFile) }
|
||||||
|
verify { fileHandler.dismissProgressDialog() }
|
||||||
|
coVerify { fileHandler.deleteSourceFile(sourceUri) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun notifyFileOperationSuccessShouldCallOnFileCopiedIfValidZIMFileAndIsMoveOperationIsFalse() =
|
||||||
|
runTest {
|
||||||
|
fileHandler = spyk(fileHandler)
|
||||||
|
coEvery { fileHandler.isValidZimFile(destinationFile) } returns true
|
||||||
|
fileHandler.isMoveOperation = false
|
||||||
|
|
||||||
|
fileHandler.notifyFileOperationSuccess(destinationFile, sourceUri)
|
||||||
|
|
||||||
|
verify { fileCopyMoveCallback.onFileCopied(destinationFile) }
|
||||||
|
verify { fileHandler.dismissProgressDialog() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `notifyFileOperationSuccess should handle invalid ZIM file`() = runTest {
|
||||||
|
mockkConstructor(Archive::class)
|
||||||
|
val archiveMock = mockk<Archive>(relaxed = true)
|
||||||
|
every { constructedWith<Archive>(any()) } returns archiveMock
|
||||||
|
coEvery { fileHandler.isValidZimFile(destinationFile) } returns false
|
||||||
|
|
||||||
|
fileHandler.notifyFileOperationSuccess(destinationFile, sourceUri)
|
||||||
|
|
||||||
|
verify { fileHandler.handleInvalidZimFile(destinationFile, sourceUri) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `handleInvalidZimFile should call onError if move is successful`() {
|
||||||
|
every { fileHandler.tryMoveWithDocumentContract(any(), any(), any()) } returns true
|
||||||
|
fileHandler.isMoveOperation = true
|
||||||
|
|
||||||
|
fileHandler.handleInvalidZimFile(destinationFile, sourceUri)
|
||||||
|
|
||||||
|
verify { fileHandler.dismissProgressDialog() }
|
||||||
|
verify { fileCopyMoveCallback.onError(activity.getString(R.string.error_file_invalid)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `handleInvalidZimFile should delete file and show error if move fails`() {
|
||||||
|
every { fileHandler.tryMoveWithDocumentContract(any(), any(), any()) } returns false
|
||||||
|
fileHandler.isMoveOperation = true
|
||||||
|
|
||||||
|
fileHandler.handleInvalidZimFile(destinationFile, sourceUri)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
fileHandler.handleFileOperationError(
|
||||||
|
activity.getString(R.string.error_file_invalid),
|
||||||
|
destinationFile
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `isValidZimFile should return true if ZIM file has main entry`() = runTest {
|
||||||
|
val archive: Archive = mockk()
|
||||||
|
every { archive.hasMainEntry() } returns true
|
||||||
|
coEvery { ZimReaderSource(destinationFile).createArchive() } returns archive
|
||||||
|
|
||||||
|
val result = fileHandler.isValidZimFile(destinationFile)
|
||||||
|
|
||||||
|
assertTrue(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `isValidZimFile should return false if ZIM file creation fails`() = runTest {
|
||||||
|
coEvery { ZimReaderSource(destinationFile).createArchive() } throws Exception()
|
||||||
|
|
||||||
|
val result = fileHandler.isValidZimFile(destinationFile)
|
||||||
|
|
||||||
|
assertFalse(result)
|
||||||
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
fun dispose() {
|
fun dispose() {
|
||||||
fileHandler.dispose()
|
fileHandler.dispose()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user