From db06f67e932b4ec272dee0b9e52318ffe05690e9 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 22 Apr 2025 15:41:15 +0800 Subject: [PATCH] 1.15.4 --- app/build.gradle.kts | 4 +- .../server/core/routes/api/ApiRoute.kt | 5 - .../core/routes/api/webdav/WebDavRoute.kt | 33 ++++- .../routes/api/webdav/utils/WebDavFile.kt | 16 ++- .../routes/api/webdav/utils/WebDavManager.kt | 6 +- .../donut/mixfile/server/core/utils/Util.kt | 29 +++-- .../java/com/donut/mixfile/ui/routes/About.kt | 4 +- .../com/donut/mixfile/ui/routes/Settings.kt | 2 +- .../donut/mixfile/ui/routes/webdav/Dialogs.kt | 119 +++++++----------- .../com/donut/mixfile/util/ComposeUtil.kt | 6 +- .../com/donut/mixfile/util/file/FileCard.kt | 4 +- .../com/donut/mixfile/util/file/FileImport.kt | 62 +++------ .../com/donut/mixfile/util/file/FileUtil.kt | 33 +++++ 13 files changed, 164 insertions(+), 159 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8348b25..063f4b2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 { diff --git a/app/src/main/java/com/donut/mixfile/server/core/routes/api/ApiRoute.kt b/app/src/main/java/com/donut/mixfile/server/core/routes/api/ApiRoute.kt index 447c570..91afe0f 100644 --- a/app/src/main/java/com/donut/mixfile/server/core/routes/api/ApiRoute.kt +++ b/app/src/main/java/com/donut/mixfile/server/core/routes/api/ApiRoute.kt @@ -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()) } diff --git a/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/WebDavRoute.kt b/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/WebDavRoute.kt index 956b81d..ede61a6 100644 --- a/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/WebDavRoute.kt +++ b/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/WebDavRoute.kt @@ -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 = """ diff --git a/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/utils/WebDavFile.kt b/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/utils/WebDavFile.kt index 901086b..b8acae2 100644 --- a/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/utils/WebDavFile.kt +++ b/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/utils/WebDavFile.kt @@ -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" { diff --git a/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/utils/WebDavManager.kt b/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/utils/WebDavManager.kt index 478361a..39ebebd 100644 --- a/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/utils/WebDavManager.kt +++ b/app/src/main/java/com/donut/mixfile/server/core/routes/api/webdav/utils/WebDavManager.kt @@ -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 "" } } diff --git a/app/src/main/java/com/donut/mixfile/server/core/utils/Util.kt b/app/src/main/java/com/donut/mixfile/server/core/utils/Util.kt index 65b93a7..cfe531a 100644 --- a/app/src/main/java/com/donut/mixfile/server/core/utils/Util.kt +++ b/app/src/main/java/com/donut/mixfile/server/core/utils/Util.kt @@ -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 = ('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 } } diff --git a/app/src/main/java/com/donut/mixfile/ui/routes/About.kt b/app/src/main/java/com/donut/mixfile/ui/routes/About.kt index c078ed6..a9b2649 100644 --- a/app/src/main/java/com/donut/mixfile/ui/routes/About.kt +++ b/app/src/main/java/com/donut/mixfile/ui/routes/About.kt @@ -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 } diff --git a/app/src/main/java/com/donut/mixfile/ui/routes/Settings.kt b/app/src/main/java/com/donut/mixfile/ui/routes/Settings.kt index 6d53e8d..8dc848c 100644 --- a/app/src/main/java/com/donut/mixfile/ui/routes/Settings.kt +++ b/app/src/main/java/com/donut/mixfile/ui/routes/Settings.kt @@ -187,7 +187,7 @@ val MixSettings = MixNavPage( useSystemPlayer = it } - SettingButton(text = "网页端/WebDav访问密码") { + SettingButton(text = "网页端/WebDAV访问密码") { setWebPassword() } diff --git a/app/src/main/java/com/donut/mixfile/ui/routes/webdav/Dialogs.kt b/app/src/main/java/com/donut/mixfile/ui/routes/webdav/Dialogs.kt index 521991d..522fff7 100644 --- a/app/src/main/java/com/donut/mixfile/ui/routes/webdav/Dialogs.kt +++ b/app/src/main/java/com/donut/mixfile/ui/routes/webdav/Dialogs.kt @@ -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() } diff --git a/app/src/main/java/com/donut/mixfile/util/ComposeUtil.kt b/app/src/main/java/com/donut/mixfile/util/ComposeUtil.kt index afefd3b..86d3ff0 100644 --- a/app/src/main/java/com/donut/mixfile/util/ComposeUtil.kt +++ b/app/src/main/java/com/donut/mixfile/util/ComposeUtil.kt @@ -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 diff --git a/app/src/main/java/com/donut/mixfile/util/file/FileCard.kt b/app/src/main/java/com/donut/mixfile/util/file/FileCard.kt index f526dbb..e062818 100644 --- a/app/src/main/java/com/donut/mixfile/util/file/FileCard.kt +++ b/app/src/main/java/com/donut/mixfile/util/file/FileCard.kt @@ -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 } diff --git a/app/src/main/java/com/donut/mixfile/util/file/FileImport.kt b/app/src/main/java/com/donut/mixfile/util/file/FileImport.kt index 8df23f5..44ca11c 100644 --- a/app/src/main/java/com/donut/mixfile/util/file/FileImport.kt +++ b/app/src/main/java/com/donut/mixfile/util/file/FileImport.kt @@ -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, name: String) { val strData = fileList.toJSONString() @@ -141,20 +132,22 @@ fun showImportConfirmWindow(fileList: List) { } } +suspend fun loadFileList(url: String, progress: ProgressContent): List { + 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 = 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? { - 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 -} \ No newline at end of file diff --git a/app/src/main/java/com/donut/mixfile/util/file/FileUtil.kt b/app/src/main/java/com/donut/mixfile/util/file/FileUtil.kt index 191ba1e..4b6de70 100644 --- a/app/src/main/java/com/donut/mixfile/util/file/FileUtil.kt +++ b/app/src/main/java/com/donut/mixfile/util/file/FileUtil.kt @@ -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,