refactor file utils and fix open file creates subprocess in windows

This commit is contained in:
AmirHossein Abdolmotallebi 2024-09-18 16:49:39 +03:30
parent 935d61f08c
commit f9d19b5019
12 changed files with 170 additions and 134 deletions

View File

@ -35,7 +35,7 @@ import ir.amirab.downloader.utils.OnDuplicateStrategy
import com.abdownloadmanager.integration.Integration
import com.abdownloadmanager.integration.IntegrationResult
import ir.amirab.downloader.exception.TooManyErrorException
import ir.amirab.util.FileUtils
import ir.amirab.util.osfileutil.FileUtils
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable

View File

@ -12,7 +12,7 @@ import com.abdownloadmanager.desktop.utils.mvi.ContainsEffects
import com.abdownloadmanager.desktop.utils.mvi.supportEffects
import androidx.compose.runtime.*
import com.arkivanov.decompose.ComponentContext
import ir.amirab.util.FileUtils
import ir.amirab.util.osfileutil.FileUtils
import ir.amirab.util.flow.createMutableStateFlowFromStateFlow
import kotlinx.coroutines.CoroutineScope
import org.koin.core.component.KoinComponent

View File

@ -1,6 +1,6 @@
package com.abdownloadmanager.desktop.utils
import ir.amirab.util.FileUtils
import ir.amirab.util.osfileutil.FileUtils
import ir.amirab.util.flow.mapStateFlow
import com.abdownloadmanager.utils.isValidUrl
import kotlinx.coroutines.CoroutineScope

View File

@ -1,6 +1,6 @@
package com.abdownloadmanager.updateapplier;
import ir.amirab.util.FileUtils
import ir.amirab.util.osfileutil.FileUtils
import com.abdownloadmanager.updatechecker.VersionData
import java.io.File
interface UpdateDownloader{

View File

@ -1,130 +0,0 @@
package ir.amirab.util
import ir.amirab.util.platform.Platform
import java.awt.Desktop
import java.io.File
import java.io.FileNotFoundException
import java.util.concurrent.TimeUnit
interface FileUtils {
fun openFile(file: File)
fun openFolderOfFile(file: File): Boolean
fun canWriteInThisFolder(folder: String): Boolean
companion object : FileUtils by getPlatformFileUtil()
}
private fun getPlatformFileUtil(): FileUtils {
return when (Platform.getCurrentPlatform()) {
Platform.Desktop.Windows -> WindowsFileUtils()
Platform.Desktop.Linux -> LinuxFileUtils()
Platform.Desktop.MacOS -> MacOsFileUtils()
Platform.Android -> DefaultFileUtils()
}
}
// it's better to move this to another module (or at least create multiple files) if it's going to be more complicated
/**
* it uses the jvm default and can be overridden to use fallback if jvm can't handle that
*/
private open class DefaultFileUtils : FileUtils {
override fun openFile(file: File) {
if (!file.exists()) {
throw FileNotFoundException("$file not found")
}
val desktop = Desktop.getDesktop() ?: return
desktop.open(file)
}
override fun openFolderOfFile(file: File): Boolean {
if (!file.exists()) {
throw FileNotFoundException("$file not found")
}
runCatching {
Desktop.getDesktop().browseFileDirectory(file)
return true
}
return fallBackOpenFolderOfFile(file.absoluteFile)
}
/**
* @param file it must be an absolute file!
*/
open fun fallBackOpenFolderOfFile(file: File): Boolean {
return false
}
override fun canWriteInThisFolder(folder: String): Boolean {
return runCatching {
File(folder).canUseThisAsFolder()
}.getOrElse { false }
}
private fun File.canUseThisAsFolder(): Boolean {
var current: File? = this
while (true) {
if (current == null) break
if (current.exists()) {
return current.isDirectory
}
current = current.parentFile
}
return false
}
}
private class WindowsFileUtils : DefaultFileUtils() {
override fun fallBackOpenFolderOfFile(file: File): Boolean {
return execAndWait(arrayOf("cmd", "/c", "explorer.exe", "/select,", file.path))
}
}
private class LinuxFileUtils : DefaultFileUtils() {
override fun fallBackOpenFolderOfFile(file: File): Boolean {
val dbusSendResult = execAndWait(
arrayOf(
"dbus-send",
"--print-reply",
"--dest=org.freedesktop.FileManager1",
"/org/freedesktop/FileManager1",
"org.freedesktop.FileManager1.ShowItems",
"array:string:file://${file.path}",
"string:"
)
)
if (dbusSendResult) {
return true
}
val xdgOpenResult = execAndWait(
arrayOf("xdg-open", file.parent)
)
return xdgOpenResult
}
}
private class MacOsFileUtils : DefaultFileUtils() {
override fun fallBackOpenFolderOfFile(file: File): Boolean {
return execAndWait(arrayOf("open", "-R", file.path))
}
}
/**
* this helper function is here to execute a command and waits for the process to finish and return the result based on exit code
* @param command the command
* @param waitFor maximum time allowed process finish ( in milliseconds )
* @return `true` when process exits with `0` exit code, `false` if the process fails with non-zero exit code or execution time exceeds the [waitFor]
*/
private fun execAndWait(
command: Array<String>,
waitFor: Long = 2_000,
): Boolean {
return runCatching {
val p = Runtime.getRuntime().exec(command)
val exited = p.waitFor(waitFor, TimeUnit.MILLISECONDS)
if (exited) {
p.exitValue() == 0
} else {
false
}
}.getOrElse { false }
}

View File

@ -0,0 +1,24 @@
package ir.amirab.util.osfileutil
import java.util.concurrent.TimeUnit
/**
* this helper function is here to execute a command and waits for the process to finish and return the result based on exit code
* @param command the command
* @param waitFor maximum time allowed process finish ( in milliseconds )
* @return `true` when process exits with `0` exit code, `false` if the process fails with non-zero exit code or execution time exceeds the [waitFor]
*/
internal fun execAndWait(
command: Array<String>,
waitFor: Long = 2_000,
): Boolean {
return runCatching {
val p = Runtime.getRuntime().exec(command)
val exited = p.waitFor(waitFor, TimeUnit.MILLISECONDS)
if (exited) {
p.exitValue() == 0
} else {
false
}
}.getOrElse { false }
}

View File

@ -0,0 +1,21 @@
package ir.amirab.util.osfileutil
import ir.amirab.util.platform.Platform
import java.io.File
interface FileUtils {
fun openFile(file: File): Boolean
fun openFolderOfFile(file: File): Boolean
fun canWriteInThisFolder(folder: String): Boolean
companion object : FileUtils by getPlatformFileUtil()
}
private fun getPlatformFileUtil(): FileUtils {
return when (Platform.getCurrentPlatform()) {
Platform.Desktop.Windows -> WindowsFileUtils()
Platform.Desktop.Linux -> LinuxFileUtils()
Platform.Desktop.MacOS -> MacOsFileUtils()
Platform.Android -> JVMFileUtils()
}
}

View File

@ -0,0 +1,39 @@
package ir.amirab.util.osfileutil
import java.io.File
import java.io.FileNotFoundException
abstract class FileUtilsBase : FileUtils {
override fun openFile(file: File): Boolean {
if (!file.exists()) {
throw FileNotFoundException("$file not found")
}
return openFileInternal(file)
}
override fun openFolderOfFile(file: File): Boolean {
val file = file.canonicalFile.absoluteFile
return openFolderOfFileInternal(file)
}
override fun canWriteInThisFolder(folder: String): Boolean {
return runCatching {
File(folder).canUseThisAsFolder()
}.getOrElse { false }
}
private fun File.canUseThisAsFolder(): Boolean {
var current: File? = this
while (true) {
if (current == null) break
if (current.exists()) {
return current.isDirectory
}
current = current.parentFile
}
return false
}
protected abstract fun openFileInternal(file: File): Boolean
protected abstract fun openFolderOfFileInternal(file: File): Boolean
}

View File

@ -0,0 +1,25 @@
package ir.amirab.util.osfileutil
import java.awt.Desktop
import java.io.File
/**
* it uses the jvm default.
*/
internal class JVMFileUtils : FileUtilsBase() {
override fun openFileInternal(file: File): Boolean {
runCatching {
Desktop.getDesktop().open(file)
return true
}
return false
}
override fun openFolderOfFileInternal(file: File): Boolean {
runCatching {
Desktop.getDesktop().browseFileDirectory(file)
return true
}
return false
}
}

View File

@ -0,0 +1,31 @@
package ir.amirab.util.osfileutil
import java.io.File
internal class LinuxFileUtils : FileUtilsBase() {
override fun openFileInternal(file: File): Boolean {
return execAndWait(arrayOf("xdg-open", file.path))
}
override fun openFolderOfFileInternal(file: File): Boolean {
val dbusSendResult = execAndWait(
arrayOf(
"dbus-send",
"--print-reply",
"--dest=org.freedesktop.FileManager1",
"/org/freedesktop/FileManager1",
"org.freedesktop.FileManager1.ShowItems",
"array:string:file://${file.path}",
"string:"
)
)
if (dbusSendResult) {
return true
}
val xdgOpenResult = execAndWait(
arrayOf("xdg-open", file.parent)
)
return xdgOpenResult
}
}

View File

@ -0,0 +1,13 @@
package ir.amirab.util.osfileutil
import java.io.File
internal class MacOsFileUtils : FileUtilsBase() {
override fun openFileInternal(file: File): Boolean {
return execAndWait(arrayOf("open", file.path))
}
override fun openFolderOfFileInternal(file: File): Boolean {
return execAndWait(arrayOf("open", "-R", file.path))
}
}

View File

@ -0,0 +1,13 @@
package ir.amirab.util.osfileutil
import java.io.File
internal class WindowsFileUtils : FileUtilsBase() {
override fun openFileInternal(file: File): Boolean {
return execAndWait(arrayOf("cmd", "/c", "start", "/B", "", file.path))
}
override fun openFolderOfFileInternal(file: File): Boolean {
return execAndWait(arrayOf("cmd", "/c", "explorer.exe", "/select,", file.path))
}
}