mirror of
https://github.com/amir1376/ab-download-manager.git
synced 2025-02-20 11:43:24 +08:00
refactor file utils and fix open file creates subprocess in windows
This commit is contained in:
parent
935d61f08c
commit
f9d19b5019
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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{
|
||||
|
@ -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 }
|
||||
}
|
@ -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 }
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user