mirror of
https://github.com/InvertGeek/MixFile.git
synced 2026-06-04 10:29:19 +08:00
1.15.4
This commit is contained in:
@@ -15,8 +15,8 @@ android {
|
||||
applicationId = "com.donut.mixfile"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 111
|
||||
versionName = "1.15.3"
|
||||
versionCode = 112
|
||||
versionName = "1.15.4"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
||||
@@ -4,8 +4,6 @@ import com.alibaba.fastjson2.toJSONString
|
||||
import com.donut.mixfile.server.core.MixFileServer
|
||||
import com.donut.mixfile.server.core.mixBasicAuth
|
||||
import com.donut.mixfile.server.core.routes.api.webdav.getWebDAVRoute
|
||||
import com.donut.mixfile.server.core.utils.getHeader
|
||||
import com.donut.mixfile.server.core.utils.isNotNull
|
||||
import com.donut.mixfile.server.core.utils.resolveMixShareInfo
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.response.respond
|
||||
@@ -26,9 +24,6 @@ fun MixFileServer.getAPIRoute(): Route.() -> Unit {
|
||||
put("/upload/{name?}", getUploadRoute())
|
||||
|
||||
get("/upload_history") {
|
||||
if (getHeader("origin").isNotNull()) {
|
||||
return@get call.respondText("此接口禁止跨域", status = HttpStatusCode.Forbidden)
|
||||
}
|
||||
call.respond(getFileHistory())
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,16 @@ import com.donut.mixfile.server.core.routes.api.webdav.utils.WebDavFile
|
||||
import com.donut.mixfile.server.core.routes.api.webdav.utils.WebDavManager
|
||||
import com.donut.mixfile.server.core.routes.api.webdav.utils.normalizePath
|
||||
import com.donut.mixfile.server.core.routes.api.webdav.utils.toDavPath
|
||||
import com.donut.mixfile.server.core.utils.bean.MixShareInfo
|
||||
import com.donut.mixfile.server.core.utils.getHeader
|
||||
import com.donut.mixfile.server.core.utils.resolveMixShareInfo
|
||||
import com.donut.mixfile.server.core.utils.sanitizeWebDavFileName
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.decodeURLQueryComponent
|
||||
import io.ktor.server.request.contentLength
|
||||
import io.ktor.server.request.path
|
||||
import io.ktor.server.request.receiveChannel
|
||||
import io.ktor.server.request.uri
|
||||
import io.ktor.server.response.header
|
||||
@@ -26,23 +29,29 @@ import io.ktor.server.routing.RoutingHandler
|
||||
import io.ktor.server.routing.method
|
||||
import io.ktor.server.routing.route
|
||||
|
||||
const val API_PATH = "/api/webdav"
|
||||
|
||||
val RoutingContext.davPath: String
|
||||
get() = normalizePath(
|
||||
(call.parameters.getAll("path") ?: emptyList()).joinToString("/")
|
||||
)
|
||||
get() = normalizePath(call.request.path().substringAfter(API_PATH))
|
||||
|
||||
val RoutingContext.davParentPath: String
|
||||
get() = davPath.substringBeforeLast("/", "")
|
||||
|
||||
val RoutingContext.davFileName: String
|
||||
get() = davPath.substringAfterLast("/")
|
||||
get() = davPath.substringAfterLast("/").sanitizeWebDavFileName()
|
||||
|
||||
val RoutingContext.davShareInfo: MixShareInfo?
|
||||
get() = resolveMixShareInfo(
|
||||
davPath.substringAfterLast(
|
||||
"/"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
suspend fun RoutingContext.handleCopy(keep: Boolean, webDavManager: WebDavManager) {
|
||||
val overwrite = getHeader("overwrite").contentEquals("T")
|
||||
val destination = getHeader("destination")?.decodeURLQueryComponent().let {
|
||||
it?.substringAfter("/api/webdav/")
|
||||
it?.substringAfter(API_PATH)
|
||||
}.toDavPath()
|
||||
if (destination.isBlank()) {
|
||||
call.respond(HttpStatusCode.BadRequest)
|
||||
@@ -116,6 +125,18 @@ fun MixFileServer.getWebDAVRoute(): Route.() -> Unit {
|
||||
call.respond(HttpStatusCode.Conflict)
|
||||
return@webdav
|
||||
}
|
||||
val shareInfo = davShareInfo
|
||||
if (shareInfo != null) {
|
||||
val node = WebDavFile(
|
||||
name = shareInfo.fileName,
|
||||
size = shareInfo.fileSize,
|
||||
shareInfoData = shareInfo.toString()
|
||||
)
|
||||
webDav.addFileNode(davParentPath, node)
|
||||
call.respond(HttpStatusCode.Created)
|
||||
webDav.saveData()
|
||||
return@webdav
|
||||
}
|
||||
val node = WebDavFile(isFolder = true, name = davFileName)
|
||||
webDav.addFileNode(davParentPath, node)
|
||||
call.respond(HttpStatusCode.Created)
|
||||
@@ -149,7 +170,7 @@ fun MixFileServer.getWebDAVRoute(): Route.() -> Unit {
|
||||
val xmlFileList = fileList.toMutableList().apply {
|
||||
add(0, WebDavFile(davParentPath.substringAfterLast("/"), isFolder = true))
|
||||
}.joinToString(separator = "") {
|
||||
it.toXML(call.request.uri)
|
||||
it.toXML(normalizePath(call.request.uri))
|
||||
}
|
||||
val text = """
|
||||
<D:multistatus xmlns:D="DAV:">
|
||||
|
||||
@@ -3,7 +3,10 @@ package com.donut.mixfile.server.core.routes.api.webdav.utils
|
||||
import com.alibaba.fastjson2.annotation.JSONField
|
||||
import com.donut.mixfile.server.core.utils.hashSHA256
|
||||
import com.donut.mixfile.server.core.utils.parseFileMimeType
|
||||
import com.donut.mixfile.server.core.utils.sanitizeWebDavFileName
|
||||
import com.donut.mixfile.server.core.utils.toHex
|
||||
import io.ktor.http.decodeURLQueryComponent
|
||||
import io.ktor.http.encodeURLPath
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
@@ -11,13 +14,20 @@ import java.util.TimeZone
|
||||
|
||||
// WebDAV 文件类,包含额外属性
|
||||
class WebDavFile(
|
||||
val name: String,
|
||||
var name: String,
|
||||
val size: Long = 0,
|
||||
val shareInfoData: String = "",
|
||||
val isFolder: Boolean = false,
|
||||
var lastModified: Long = System.currentTimeMillis()
|
||||
) {
|
||||
|
||||
init {
|
||||
if (name.isNotEmpty()) {
|
||||
name = name.sanitizeWebDavFileName().decodeURLQueryComponent()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is WebDavFile) return false
|
||||
@@ -38,7 +48,7 @@ class WebDavFile(
|
||||
if (isFolder) {
|
||||
return xml("D:response") {
|
||||
"D:href" {
|
||||
-"/${normalizePath("$path/$name")}/"
|
||||
-"/${normalizePath("$path/$name")}/".encodeURLPath(encodeEncoded = true)
|
||||
}
|
||||
"D:propstat" {
|
||||
"D:prop" {
|
||||
@@ -63,7 +73,7 @@ class WebDavFile(
|
||||
}
|
||||
return xml("D:response") {
|
||||
"D:href" {
|
||||
-"/${normalizePath(path)}/${name}"
|
||||
-"/${normalizePath(path)}/${name}".encodeURLPath(encodeEncoded = true)
|
||||
}
|
||||
"D:propstat" {
|
||||
"D:prop" {
|
||||
|
||||
@@ -85,6 +85,7 @@ open class WebDavManager {
|
||||
val parentPath = normalizedPath.substringBeforeLast('/', "")
|
||||
val name = normalizedPath.substringAfterLast('/')
|
||||
val fileList = WEBDAV_DATA[parentPath] ?: return
|
||||
println("remove ${name} ${fileList.joinToString { it.name }}")
|
||||
synchronized(fileList) {
|
||||
val node = fileList.firstOrNull { it.name.contentEquals(name) }
|
||||
if (node != null) {
|
||||
@@ -150,11 +151,10 @@ fun String?.toDavPath() = normalizePath(this ?: "")
|
||||
|
||||
fun normalizePath(path: String): String {
|
||||
if (path.isBlank()) return ""
|
||||
val encoded = path.encodeURLPath()
|
||||
val uri = try {
|
||||
URI(encoded)
|
||||
URI(path)
|
||||
} catch (_: Exception) {
|
||||
URI.create("http://dummyhost/$encoded").also { uri ->
|
||||
URI("http://dummyhost/${path.encodeURLPath()}").also { uri ->
|
||||
if (uri.path == null) return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,35 +22,48 @@ import kotlinx.coroutines.launch
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.net.MalformedURLException
|
||||
import java.net.ServerSocket
|
||||
import java.net.URL
|
||||
import java.net.URI
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import kotlin.random.Random
|
||||
|
||||
fun String.getFileExtension(): String {
|
||||
val index = this.lastIndexOf('.')
|
||||
return if (index == -1) "" else this.substring(index + 1).lowercase()
|
||||
}
|
||||
|
||||
fun String.sanitizeWebDavFileName(): String {
|
||||
val illegalChars = "[/\\\\:*?\"<>|%&#@]".toRegex()
|
||||
|
||||
|
||||
return this
|
||||
.replace(illegalChars, " ")
|
||||
.trim()
|
||||
.replace("\\s+".toRegex(), "_")
|
||||
.takeLast(255)
|
||||
.ifEmpty { "unnamed_file" }
|
||||
}
|
||||
|
||||
|
||||
fun genRandomString(
|
||||
length: Int = 32,
|
||||
charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
||||
): String {
|
||||
return (1..length)
|
||||
.map { kotlin.random.Random.nextInt(0, charPool.size) }
|
||||
.map { Random.nextInt(0, charPool.size) }
|
||||
.map(charPool::get)
|
||||
.joinToString("")
|
||||
}
|
||||
|
||||
fun isValidURL(urlString: String): Boolean {
|
||||
return try {
|
||||
val url = URL(urlString)
|
||||
val uri = URI.create(urlString)
|
||||
|
||||
// 获取协议和主机名
|
||||
val protocol = url.protocol
|
||||
val host = url.host
|
||||
val protocol = uri.scheme
|
||||
val host = uri.host
|
||||
|
||||
// 检查协议和主机名是否为空
|
||||
if (protocol.isNullOrBlank() || host.isNullOrBlank()) {
|
||||
@@ -58,8 +71,8 @@ fun isValidURL(urlString: String): Boolean {
|
||||
}
|
||||
|
||||
// 可选:限制协议类型
|
||||
protocol in listOf("http", "https", "ftp")
|
||||
} catch (e: MalformedURLException) {
|
||||
protocol in listOf("http", "https")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.ui.nav.MixNavPage
|
||||
import com.donut.mixfile.ui.theme.colorScheme
|
||||
import com.donut.mixfile.updateChecker
|
||||
import com.donut.mixfile.util.UseEffect
|
||||
import com.donut.mixfile.util.AsyncEffect
|
||||
import com.donut.mixfile.util.cachedMutableOf
|
||||
import com.donut.mixfile.util.formatFileSize
|
||||
import com.donut.mixfile.util.getAppVersionName
|
||||
@@ -212,7 +212,7 @@ fun showUpdateDialog() {
|
||||
setContent {
|
||||
var latestVersion: String? by remember { mutableStateOf(null) }
|
||||
|
||||
UseEffect {
|
||||
AsyncEffect {
|
||||
ignoreError {
|
||||
latestVersion = updateChecker.latestVersion
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ val MixSettings = MixNavPage(
|
||||
useSystemPlayer = it
|
||||
}
|
||||
|
||||
SettingButton(text = "网页端/WebDav访问密码") {
|
||||
SettingButton(text = "网页端/WebDAV访问密码") {
|
||||
setWebPassword()
|
||||
}
|
||||
|
||||
|
||||
@@ -18,27 +18,19 @@ import com.donut.mixfile.server.core.utils.resolveMixShareInfo
|
||||
import com.donut.mixfile.server.mixFileServer
|
||||
import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.ui.theme.colorScheme
|
||||
import com.donut.mixfile.util.UseEffect
|
||||
import com.donut.mixfile.util.AsyncEffect
|
||||
import com.donut.mixfile.util.errorDialog
|
||||
import com.donut.mixfile.util.file.doUploadFile
|
||||
import com.donut.mixfile.util.file.loadDataWithMaxSize
|
||||
import com.donut.mixfile.util.file.loadFileList
|
||||
import com.donut.mixfile.util.file.localClient
|
||||
|
||||
import com.donut.mixfile.util.file.toDataLog
|
||||
import com.donut.mixfile.util.formatFileSize
|
||||
import com.donut.mixfile.util.getCurrentTime
|
||||
import com.donut.mixfile.util.objects.ProgressContent
|
||||
import com.donut.mixfile.util.showErrorDialog
|
||||
import com.donut.mixfile.util.showToast
|
||||
import io.ktor.client.plugins.onDownload
|
||||
import io.ktor.client.request.prepareGet
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.contentLength
|
||||
import io.ktor.http.isSuccess
|
||||
import io.ktor.utils.io.readRemaining
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.io.readByteArray
|
||||
|
||||
fun clearWebDavData() {
|
||||
MixDialogBuilder("确定清空webdav数据?").apply {
|
||||
@@ -107,58 +99,31 @@ fun exportWebDavData() {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadWebDavData(url: String, progressContent: ProgressContent): ByteArray? {
|
||||
try {
|
||||
return localClient.prepareGet {
|
||||
url(url)
|
||||
onDownload(progressContent.ktorListener)
|
||||
}.execute {
|
||||
if (!it.status.isSuccess()) {
|
||||
val text = if ((it.contentLength()
|
||||
?: (1024 * 1024)) < 1024 * 500
|
||||
) it.bodyAsText() else "未知错误"
|
||||
throw Exception("下载失败: ${text}")
|
||||
}
|
||||
if ((it.contentLength() ?: 0) > 1024 * 1024 * 50) {
|
||||
throw Exception("文件过大")
|
||||
}
|
||||
val data = it.bodyAsChannel().readRemaining(1024 * 1024 * 50).readByteArray()
|
||||
return@execute data
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
showErrorDialog(e, "解析分享列表失败!")
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun importFileListToWebDav(url: String) {
|
||||
val progress = ProgressContent()
|
||||
MixDialogBuilder("解析中").apply {
|
||||
setContent {
|
||||
UseEffect {
|
||||
val fileList = loadFileList(url, progress)
|
||||
if (fileList == null) {
|
||||
showToast("解析分享列表失败!")
|
||||
closeDialog()
|
||||
return@UseEffect
|
||||
}
|
||||
fileList.forEach {
|
||||
//创建分类文件夹
|
||||
mixFileServer.webDav.addFileNode(
|
||||
"",
|
||||
WebDavFile(it.category, isFolder = true)
|
||||
)
|
||||
mixFileServer.webDav.addFileNode(
|
||||
it.category,
|
||||
WebDavFile(it.name, shareInfoData = it.shareInfoData, size = it.size)
|
||||
)
|
||||
}
|
||||
mixFileServer.webDav.saveData()
|
||||
showToast("导入完成")
|
||||
withContext(Dispatchers.Main) {
|
||||
closeDialog()
|
||||
AsyncEffect {
|
||||
errorDialog("解析文件失败") {
|
||||
val dav = mixFileServer.webDav
|
||||
val fileList = loadFileList(url, progress)
|
||||
fileList.forEach {
|
||||
//创建分类文件夹
|
||||
dav.addFileNode(
|
||||
"",
|
||||
WebDavFile(it.category, isFolder = true)
|
||||
)
|
||||
dav.addFileNode(
|
||||
it.category,
|
||||
WebDavFile(it.name, shareInfoData = it.shareInfoData, size = it.size)
|
||||
)
|
||||
}
|
||||
dav.saveData()
|
||||
showToast("导入完成")
|
||||
withContext(Dispatchers.Main) {
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
progress.LoadingContent()
|
||||
@@ -227,24 +192,26 @@ fun importWebDavData(url: String) {
|
||||
val progress = ProgressContent()
|
||||
MixDialogBuilder("导入中").apply {
|
||||
setContent {
|
||||
UseEffect {
|
||||
val webDavData = loadWebDavData(url, progress)
|
||||
if (webDavData == null) {
|
||||
showToast("下载文件失败!")
|
||||
closeDialog()
|
||||
return@UseEffect
|
||||
AsyncEffect {
|
||||
errorDialog("导入失败") {
|
||||
val webDavData = loadDataWithMaxSize(url, progress)
|
||||
if (webDavData == null) {
|
||||
showToast("下载文件失败!")
|
||||
closeDialog()
|
||||
return@AsyncEffect
|
||||
}
|
||||
val dav = mixFileServer.webDav
|
||||
val data = dav.parseDataFromBytes(webDavData)
|
||||
data.keys.forEach { key ->
|
||||
val fileList = dav.WEBDAV_DATA.getOrDefault(key, mutableSetOf())
|
||||
val dataFileList = data.getOrDefault(key, mutableSetOf())
|
||||
fileList.removeAll(dataFileList)
|
||||
fileList.addAll(dataFileList)
|
||||
dav.WEBDAV_DATA[key] = fileList
|
||||
}
|
||||
dav.saveData()
|
||||
showToast("导入成功!")
|
||||
}
|
||||
val dav = mixFileServer.webDav
|
||||
val data = dav.parseDataFromBytes(webDavData)
|
||||
data.keys.forEach { key ->
|
||||
val fileList = dav.WEBDAV_DATA.getOrDefault(key, mutableSetOf())
|
||||
val dataFileList = data.getOrDefault(key, mutableSetOf())
|
||||
fileList.removeAll(dataFileList)
|
||||
fileList.addAll(dataFileList)
|
||||
dav.WEBDAV_DATA[key] = fileList
|
||||
}
|
||||
dav.saveData()
|
||||
showToast("导入成功!")
|
||||
withContext(Dispatchers.Main) {
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ fun OnResume(block: () -> Unit) {
|
||||
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun UseEffect(
|
||||
fun AsyncEffect(
|
||||
vararg keys: Any?,
|
||||
block: suspend CoroutineScope.() -> Unit,
|
||||
) {
|
||||
@@ -157,10 +157,10 @@ fun UseEffect(
|
||||
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun UseEffect(
|
||||
fun AsyncEffect(
|
||||
block: suspend CoroutineScope.() -> Unit,
|
||||
) {
|
||||
UseEffect(Unit, block = block)
|
||||
AsyncEffect(Unit, block = block)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -38,7 +38,7 @@ import androidx.compose.ui.unit.sp
|
||||
import com.donut.mixfile.server.core.utils.parseFileMimeType
|
||||
import com.donut.mixfile.server.serverStarted
|
||||
import com.donut.mixfile.ui.theme.colorScheme
|
||||
import com.donut.mixfile.util.UseEffect
|
||||
import com.donut.mixfile.util.AsyncEffect
|
||||
import com.donut.mixfile.util.cachedMutableOf
|
||||
import com.donut.mixfile.util.formatFileSize
|
||||
import com.donut.mixfile.util.formatTime
|
||||
@@ -98,7 +98,7 @@ fun PreviewCard(
|
||||
verticalArrangement = Arrangement.Bottom
|
||||
) {
|
||||
var loadImg by remember { mutableStateOf(false) }
|
||||
UseEffect {
|
||||
AsyncEffect {
|
||||
delay(300)
|
||||
loadImg = true
|
||||
}
|
||||
|
||||
@@ -22,23 +22,14 @@ import com.donut.mixfile.server.core.utils.hashSHA256
|
||||
import com.donut.mixfile.server.core.utils.parseFileMimeType
|
||||
import com.donut.mixfile.server.core.utils.toHex
|
||||
import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.util.UseEffect
|
||||
import com.donut.mixfile.util.AsyncEffect
|
||||
import com.donut.mixfile.util.errorDialog
|
||||
import com.donut.mixfile.util.formatFileSize
|
||||
import com.donut.mixfile.util.getCurrentTime
|
||||
import com.donut.mixfile.util.objects.ProgressContent
|
||||
import com.donut.mixfile.util.showErrorDialog
|
||||
import com.donut.mixfile.util.showToast
|
||||
import io.ktor.client.plugins.onDownload
|
||||
import io.ktor.client.request.prepareGet
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.contentLength
|
||||
import io.ktor.http.isSuccess
|
||||
import io.ktor.utils.io.readRemaining
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.io.readByteArray
|
||||
|
||||
fun exportFileList(fileList: Collection<FileDataLog>, name: String) {
|
||||
val strData = fileList.toJSONString()
|
||||
@@ -141,20 +132,22 @@ fun showImportConfirmWindow(fileList: List<FileDataLog>) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadFileList(url: String, progress: ProgressContent): List<FileDataLog> {
|
||||
val fileListData = loadDataWithMaxSize(url, progress)
|
||||
return decompressGzip(fileListData).into()
|
||||
}
|
||||
|
||||
fun importFileList(url: String) {
|
||||
val progress = ProgressContent()
|
||||
MixDialogBuilder("解析中").apply {
|
||||
setContent {
|
||||
UseEffect {
|
||||
val fileList = loadFileList(url, progress)
|
||||
if (fileList == null) {
|
||||
showToast("解析分享列表失败!")
|
||||
closeDialog()
|
||||
return@UseEffect
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
showFileList(fileList.toList())
|
||||
closeDialog()
|
||||
AsyncEffect {
|
||||
errorDialog("解析文件失败") {
|
||||
val fileList: List<FileDataLog> = loadFileList(url, progress)
|
||||
withContext(Dispatchers.Main) {
|
||||
showFileList(fileList.toList())
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
progress.LoadingContent()
|
||||
@@ -164,30 +157,3 @@ fun importFileList(url: String) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suspend fun loadFileList(url: String, progressContent: ProgressContent): Array<FileDataLog>? {
|
||||
try {
|
||||
return localClient.prepareGet {
|
||||
url(url)
|
||||
onDownload(progressContent.ktorListener)
|
||||
}.execute {
|
||||
if (!it.status.isSuccess()) {
|
||||
val text = if ((it.contentLength()
|
||||
?: (1024 * 1024)) < 1024 * 500
|
||||
) it.bodyAsText() else "未知错误"
|
||||
throw Exception("下载失败: ${text}")
|
||||
}
|
||||
if ((it.contentLength() ?: 0) > 1024 * 1024 * 50) {
|
||||
throw Exception("文件过大")
|
||||
}
|
||||
val data = it.bodyAsChannel().readRemaining(1024 * 1024 * 50).readByteArray()
|
||||
val extractedData = decompressGzip(data)
|
||||
return@execute extractedData.into()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
showErrorDialog(e, "解析分享列表失败!")
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import com.donut.mixfile.util.errorDialog
|
||||
import com.donut.mixfile.util.getFileName
|
||||
import com.donut.mixfile.util.getFileSize
|
||||
import com.donut.mixfile.util.objects.ProgressContent
|
||||
import com.donut.mixfile.util.showErrorDialog
|
||||
import com.donut.mixfile.util.showToast
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
@@ -52,6 +53,7 @@ import io.ktor.http.contentLength
|
||||
import io.ktor.http.isSuccess
|
||||
import io.ktor.util.encodeBase64
|
||||
import io.ktor.utils.io.jvm.javaio.toInputStream
|
||||
import io.ktor.utils.io.readRemaining
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -62,6 +64,7 @@ import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.io.readByteArray
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@@ -239,6 +242,36 @@ fun String.sanitizeFileName(): String {
|
||||
.ifEmpty { "unnamed_file" }
|
||||
}
|
||||
|
||||
suspend fun loadDataWithMaxSize(
|
||||
url: String,
|
||||
progressContent: ProgressContent,
|
||||
limit: Long = 1024 * 1024 * 50
|
||||
): ByteArray {
|
||||
try {
|
||||
return localClient.prepareGet {
|
||||
url(url)
|
||||
onDownload(progressContent.ktorListener)
|
||||
}.execute {
|
||||
if (!it.status.isSuccess()) {
|
||||
val text = if ((it.contentLength()
|
||||
?: (1024 * 1024)) < 1024 * 500
|
||||
) it.bodyAsText() else "未知错误"
|
||||
throw Exception("下载失败: ${text}")
|
||||
}
|
||||
if ((it.contentLength() ?: 0) > limit) {
|
||||
throw Exception("文件过大")
|
||||
}
|
||||
val data = it.bodyAsChannel().readRemaining(limit).readByteArray()
|
||||
return@execute data
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
showErrorDialog(e, "下载数据失败!")
|
||||
}
|
||||
}
|
||||
throw Exception("下载数据失败")
|
||||
}
|
||||
|
||||
|
||||
suspend fun saveFileToStorage(
|
||||
url: String,
|
||||
|
||||
Reference in New Issue
Block a user