From 077908080ecb8d388058ad3b918cecc5348c7ee9 Mon Sep 17 00:00:00 2001 From: Sean Mac Gillicuddy Date: Thu, 9 Jan 2020 11:22:32 +0000 Subject: [PATCH] #1663 Improve FileSystem detection - read from mount and fallback to file writing --- .../kiwixmobile/di/modules/KiwixModule.kt | 13 ++++ .../kiwixmobile/zim_manager/Fat32Checker.kt | 40 +++++------ .../zim_manager/FileSystemChecker.kt | 29 ++++++++ .../FileWritingFileSystemChecker.kt | 49 +++++++++++++ .../zim_manager/MountFileSystemChecker.kt | 72 +++++++++++++++++++ 5 files changed, 179 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/FileSystemChecker.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/FileWritingFileSystemChecker.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/MountFileSystemChecker.kt diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/KiwixModule.kt b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/KiwixModule.kt index a045e9f1a..5df1f937b 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/KiwixModule.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/KiwixModule.kt @@ -22,7 +22,11 @@ import android.content.Context import android.location.LocationManager import dagger.Module import dagger.Provides +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.di.KiwixScope +import org.kiwix.kiwixmobile.zim_manager.Fat32Checker +import org.kiwix.kiwixmobile.zim_manager.FileWritingFileSystemChecker +import org.kiwix.kiwixmobile.zim_manager.MountFileSystemChecker @Module object KiwixModule { @@ -31,4 +35,13 @@ object KiwixModule { @JvmStatic internal fun provideLocationManager(context: Context): LocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + + @Provides + @KiwixScope + @JvmStatic + internal fun provideFat32Checker(sharedPreferenceUtil: SharedPreferenceUtil): Fat32Checker = + Fat32Checker( + sharedPreferenceUtil, + listOf(MountFileSystemChecker(), FileWritingFileSystemChecker()) + ) } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/Fat32Checker.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/Fat32Checker.kt index c9781d01b..ebda4d01b 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/Fat32Checker.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/Fat32Checker.kt @@ -18,7 +18,6 @@ package org.kiwix.kiwixmobile.zim_manager import android.os.FileObserver -import android.util.Log import io.reactivex.Flowable import io.reactivex.functions.BiFunction import io.reactivex.processors.BehaviorProcessor @@ -28,11 +27,15 @@ import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4G import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrite4GbFile import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.NotEnoughSpaceFor4GbFile import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.Unknown +import org.kiwix.kiwixmobile.zim_manager.FileSystemCapability.CANNOT_WRITE_4GB +import org.kiwix.kiwixmobile.zim_manager.FileSystemCapability.CAN_WRITE_4GB +import org.kiwix.kiwixmobile.zim_manager.FileSystemCapability.INCONCLUSIVE import java.io.File -import java.io.RandomAccessFile -import javax.inject.Inject -class Fat32Checker @Inject constructor(sharedPreferenceUtil: SharedPreferenceUtil) { +class Fat32Checker constructor( + sharedPreferenceUtil: SharedPreferenceUtil, + private val fileSystemCheckers: List +) { val fileSystemStates: BehaviorProcessor = BehaviorProcessor.create() private var fileObserver: FileObserver? = null private val requestCheckSystemFileType = BehaviorProcessor.createDefault(Unit) @@ -59,10 +62,7 @@ class Fat32Checker @Inject constructor(sharedPreferenceUtil: SharedPreferenceUti private fun fileObserver(it: String?): FileObserver { return object : FileObserver(it, MOVED_FROM or DELETE) { - override fun onEvent( - event: Int, - path: String? - ) { + override fun onEvent(event: Int, path: String?) { requestCheckSystemFileType.onNext(Unit) } }.apply { startWatching() } @@ -77,20 +77,16 @@ class Fat32Checker @Inject constructor(sharedPreferenceUtil: SharedPreferenceUti } private fun canCreate4GbFile(storage: String): Boolean { - val path = "$storage/large_file_test.txt" - File(path).deleteIfExists() - try { - RandomAccessFile(path, "rw").use { - it.setLength(FOUR_GIGABYTES_IN_BYTES) - return@canCreate4GbFile true + fileSystemCheckers.forEach { + when (it.checkFilesystemSupports4GbFiles(storage)) { + CAN_WRITE_4GB -> return@canCreate4GbFile true + CANNOT_WRITE_4GB -> return@canCreate4GbFile false + INCONCLUSIVE -> { + /*do nothing*/ + } } - } catch (e: Exception) { - e.printStackTrace() - Log.d("Fat32Checker", e.message) - return false - } finally { - File(path).deleteIfExists() } + return false } companion object { @@ -105,7 +101,3 @@ class Fat32Checker @Inject constructor(sharedPreferenceUtil: SharedPreferenceUti object Unknown : FileSystemState() } } - -private fun File.deleteIfExists() { - if (exists()) delete() -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/FileSystemChecker.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/FileSystemChecker.kt new file mode 100644 index 000000000..90bc43875 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/FileSystemChecker.kt @@ -0,0 +1,29 @@ +/* + * Kiwix Android + * Copyright (c) 2020 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.zim_manager + +interface FileSystemChecker { + fun checkFilesystemSupports4GbFiles(path: String): FileSystemCapability +} + +enum class FileSystemCapability { + CAN_WRITE_4GB, + CANNOT_WRITE_4GB, + INCONCLUSIVE +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/FileWritingFileSystemChecker.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/FileWritingFileSystemChecker.kt new file mode 100644 index 000000000..769ade285 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/FileWritingFileSystemChecker.kt @@ -0,0 +1,49 @@ +/* + * Kiwix Android + * Copyright (c) 2020 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.zim_manager + +import android.util.Log +import org.kiwix.kiwixmobile.zim_manager.FileSystemCapability.CANNOT_WRITE_4GB +import org.kiwix.kiwixmobile.zim_manager.FileSystemCapability.CAN_WRITE_4GB +import java.io.File +import java.io.RandomAccessFile + +class FileWritingFileSystemChecker : FileSystemChecker { + override fun checkFilesystemSupports4GbFiles(path: String): FileSystemCapability { + with(File("$path/large_file_test.txt")) { + deleteIfExists() + try { + RandomAccessFile(this.path, "rw").use { + it.setLength(Fat32Checker.FOUR_GIGABYTES_IN_BYTES) + return@checkFilesystemSupports4GbFiles CAN_WRITE_4GB + } + } catch (e: Exception) { + e.printStackTrace() + Log.d("Fat32Checker", e.message) + return@checkFilesystemSupports4GbFiles CANNOT_WRITE_4GB + } finally { + deleteIfExists() + } + } + } +} + +private fun File.deleteIfExists() { + if (exists()) delete() +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/MountFileSystemChecker.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/MountFileSystemChecker.kt new file mode 100644 index 000000000..d7adcb7ef --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/MountFileSystemChecker.kt @@ -0,0 +1,72 @@ +/* + * Kiwix Android + * Copyright (c) 2020 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.zim_manager + +import org.kiwix.kiwixmobile.zim_manager.FileSystemCapability.CANNOT_WRITE_4GB +import org.kiwix.kiwixmobile.zim_manager.FileSystemCapability.CAN_WRITE_4GB +import org.kiwix.kiwixmobile.zim_manager.FileSystemCapability.INCONCLUSIVE +import java.io.File + +class MountFileSystemChecker : FileSystemChecker { + override fun checkFilesystemSupports4GbFiles(path: String) = + recursivelyDetermineFilesystem(mountPoints(), path) + + private fun recursivelyDetermineFilesystem( + mountPoints: List, + path: String + ): FileSystemCapability = + mountPoints.maxBy { it.matchCount(path) } + ?.takeIf { it.matchCount(path) > 0 } + ?.let { + when { + it.isVirtual -> recursivelyDetermineFilesystem(mountPoints, it.device) + it.supports4GBFiles -> CAN_WRITE_4GB + it.doesNotSupport4GBFiles -> CANNOT_WRITE_4GB + else -> INCONCLUSIVE + } + } ?: INCONCLUSIVE + + private fun mountPoints() = + File("proc/mounts") + .takeIf(File::exists) + ?.readLines() + ?.map { MountInfo(it.split(" ")) } + ?: emptyList() +} + +data class MountInfo(val device: String, val mountPoint: String, val fileSystem: String) { + constructor(split: List) : this(split[0], split[1], split[2]) + + fun matchCount(storage: String) = storage.split("/") + .zip(mountPoint.split("/")) + .fold(0, { acc, pair -> + if (pair.first == pair.second) acc + 1 + else acc + }) + + val isVirtual = VIRTUAL_FILE_SYSTEMS.contains(fileSystem) + val supports4GBFiles = SUPPORTS_4GB_FILE_SYSTEMS.contains(fileSystem) + val doesNotSupport4GBFiles = DOES_NOT_SUPPORT_4GB_FILE_SYSTEMS.contains(fileSystem) + + companion object { + private val VIRTUAL_FILE_SYSTEMS = listOf("fuse", "sdcardfs", "tmpfs") + private val SUPPORTS_4GB_FILE_SYSTEMS = listOf("ext4", "exfat") + private val DOES_NOT_SUPPORT_4GB_FILE_SYSTEMS = listOf("fat32") + } +}