mirror of
https://github.com/InvertGeek/MixFile.git
synced 2026-06-03 10:02:07 +08:00
1.13.4
This commit is contained in:
@@ -15,8 +15,8 @@ android {
|
||||
applicationId = "com.donut.mixfile"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 103
|
||||
versionName = "1.13.3"
|
||||
versionCode = 104
|
||||
versionName = "1.13.4"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@
|
||||
<meta content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"
|
||||
name="viewport"/>
|
||||
<title>MixFile</title>
|
||||
<script type="module" crossorigin src="/assets/index-W8wx7JSe.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DrT-F2ns.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Ub7aM_kp.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.donut.mixfile.server
|
||||
|
||||
import com.donut.mixfile.server.core.utils.bean.MixShareInfo
|
||||
import com.donut.mixfile.ui.routes.home.getLocalServerAddress
|
||||
import com.donut.mixfile.ui.routes.home.serverAddress
|
||||
import com.donut.mixfile.util.getFileAccessUrl
|
||||
|
||||
val MixShareInfo.downloadUrl: String
|
||||
get() = getFileAccessUrl(getLocalServerAddress(), this.toString(), fileName)
|
||||
|
||||
|
||||
val MixShareInfo.lanUrl: String
|
||||
get() = getFileAccessUrl(serverAddress, this.toString(), fileName)
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ fun getCipher(mode: Int, key: ByteArray, iv: ByteArray): Cipher {
|
||||
return cipher
|
||||
}
|
||||
|
||||
suspend fun decryptAES(data: ByteReadChannel, key: ByteArray): ByteArray? {
|
||||
suspend fun decryptAES(data: ByteReadChannel, key: ByteArray): ByteArray {
|
||||
val iv = data.readRemaining(12).readByteArray()
|
||||
val cipher = getCipher(Cipher.DECRYPT_MODE, key, iv)
|
||||
val result = ByteArrayOutputStream()
|
||||
|
||||
@@ -28,8 +28,9 @@ import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
fun MixFileServer.getDownloadRoute(): RoutingHandler {
|
||||
return route@{
|
||||
val shareInfoData = call.request.queryParameters["s"]
|
||||
val referer = call.request.queryParameters["referer"]
|
||||
val shareInfoData = call.parameters["s"]
|
||||
val referer = call.parameters["referer"]
|
||||
var name = call.parameters["name"]
|
||||
if (shareInfoData == null) {
|
||||
call.respondText("分享信息为空", status = HttpStatusCode.InternalServerError)
|
||||
return@route
|
||||
@@ -47,12 +48,15 @@ fun MixFileServer.getDownloadRoute(): RoutingHandler {
|
||||
)
|
||||
return@route
|
||||
}
|
||||
if (name.isNullOrEmpty()) {
|
||||
name = shareInfo.fileName
|
||||
}
|
||||
var contentLength = shareInfo.fileSize
|
||||
val range: LongRange? = call.request.ranges()?.mergeToSingle(contentLength)
|
||||
call.response.apply {
|
||||
header(
|
||||
"Content-Disposition",
|
||||
"inline; filename=\"${shareInfo.fileName.encodeURL()}\""
|
||||
"inline; filename=\"${name.encodeURL()}\""
|
||||
)
|
||||
}
|
||||
var fileList = mixFile.fileList.map { it to 0 }
|
||||
@@ -85,25 +89,17 @@ private suspend fun MixFileServer.responseDownloadFileStream(
|
||||
val sortedTask = SortedTask(downloadTaskCount)
|
||||
val tasks = mutableListOf<Deferred<Unit>>()
|
||||
while (!isClosedForWrite && fileList.isNotEmpty()) {
|
||||
val currentMeta = fileList.removeAt(0)
|
||||
val currentFile = fileList.removeAt(0)
|
||||
val taskOrder = -fileList.size
|
||||
sortedTask.prepareTask(taskOrder)
|
||||
tasks.add(async {
|
||||
val url = currentMeta.first
|
||||
val (url, range) = currentFile
|
||||
val dataBytes =
|
||||
shareInfo.fetchFile(url, httpClient, referer ?: shareInfo.referer)
|
||||
val range = currentMeta.second
|
||||
if (dataBytes == null) {
|
||||
call.respondText(
|
||||
"下载失败",
|
||||
status = HttpStatusCode.InternalServerError
|
||||
)
|
||||
return@async
|
||||
}
|
||||
sortedTask.addTask(taskOrder) {
|
||||
val dataToWrite = when {
|
||||
range == 0 -> dataBytes
|
||||
range < 0 -> dataBytes.copyOfRange(0, -range)
|
||||
range < 0 -> dataBytes.copyOfRange(0, -range) //一般无 < 0 的情况
|
||||
else -> dataBytes.copyOfRange(range, dataBytes.size)
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -16,7 +16,8 @@ import io.ktor.server.routing.Routing
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.routing.put
|
||||
import io.ktor.server.routing.route
|
||||
import io.ktor.utils.io.jvm.javaio.toOutputStream
|
||||
import io.ktor.utils.io.copyTo
|
||||
import io.ktor.utils.io.jvm.javaio.toByteReadChannel
|
||||
|
||||
fun MixFileServer.getRoutes(): Routing.() -> Unit {
|
||||
|
||||
@@ -29,12 +30,12 @@ fun MixFileServer.getRoutes(): Routing.() -> Unit {
|
||||
call.respondBytesWriter(
|
||||
contentType = ContentType.parse(file.parseFileMimeType())
|
||||
) {
|
||||
fileStream.copyTo(this.toOutputStream())
|
||||
fileStream.toByteReadChannel().copyTo(this)
|
||||
}
|
||||
}
|
||||
route("/api") {
|
||||
get("/download", getDownloadRoute())
|
||||
put("/upload", getUploadRoute())
|
||||
get("/download/{name?}", getDownloadRoute())
|
||||
put("/upload/{name?}", getUploadRoute())
|
||||
get("/upload_history") {
|
||||
if (call.request.header("origin").isNotNull()) {
|
||||
return@get call.respondText("此接口禁止跨域", status = HttpStatusCode.Forbidden)
|
||||
@@ -42,7 +43,7 @@ fun MixFileServer.getRoutes(): Routing.() -> Unit {
|
||||
call.respond(getFileHistory())
|
||||
}
|
||||
get("/file_info") {
|
||||
val shareInfoStr = call.request.queryParameters["s"]
|
||||
val shareInfoStr = call.parameters["s"]
|
||||
if (shareInfoStr == null) {
|
||||
call.respondText("分享信息为空", status = HttpStatusCode.InternalServerError)
|
||||
return@get
|
||||
|
||||
@@ -29,8 +29,8 @@ import kotlin.math.ceil
|
||||
fun MixFileServer.getUploadRoute(): RoutingHandler {
|
||||
return route@{
|
||||
val key = generateRandomByteArray(32)
|
||||
val name = call.request.queryParameters["name"]
|
||||
val add = call.request.queryParameters["add"] ?: "true"
|
||||
val name = call.parameters["name"]
|
||||
val add = call.parameters["add"] ?: "true"
|
||||
if (name.isNullOrEmpty()) {
|
||||
call.respondText("需要文件名称", status = HttpStatusCode.InternalServerError)
|
||||
return@route
|
||||
|
||||
@@ -56,7 +56,7 @@ data class MixShareInfo(
|
||||
|
||||
private fun enc(input: String): String {
|
||||
val bytes = input.encodeToByteArray()
|
||||
val result = encryptAES(bytes, "123".hashMD5(), iv = "123".hashMD5().copyOf(12))
|
||||
val result = encryptAES(bytes, "123".hashMD5())
|
||||
return ENCODER.encode(result)
|
||||
}
|
||||
|
||||
@@ -81,10 +81,10 @@ data class MixShareInfo(
|
||||
url: String,
|
||||
client: HttpClient,
|
||||
referer: String = this.referer,
|
||||
): ByteArray? {
|
||||
): ByteArray {
|
||||
val transformedUrl = Uploader.transformUrl(url)
|
||||
val transformedReferer = Uploader.transformReferer(url, referer)
|
||||
val result: ByteArray? = client.config {
|
||||
val result: ByteArray = client.config {
|
||||
install(HttpRequestRetry) {
|
||||
maxRetries = 3
|
||||
retryOnException(retryOnTimeout = true)
|
||||
@@ -102,13 +102,11 @@ data class MixShareInfo(
|
||||
channel.discard(headSize.toLong())
|
||||
decryptAES(channel, ENCODER.decode(key))
|
||||
}
|
||||
if (result != null) {
|
||||
val hash = url.split("#").getOrNull(1)
|
||||
if (hash != null) {
|
||||
val currentHash = result.hashMixSHA256()
|
||||
if (!currentHash.contentEquals(hash)) {
|
||||
throw Exception("文件遭到篡改")
|
||||
}
|
||||
val hash = url.split("#").getOrNull(1)
|
||||
if (hash != null) {
|
||||
val currentHash = result.hashMixSHA256()
|
||||
if (!currentHash.contentEquals(hash)) {
|
||||
throw Exception("文件遭到篡改")
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -128,7 +126,7 @@ data class MixShareInfo(
|
||||
fun contentType(): String = fileName.parseFileMimeType()
|
||||
|
||||
suspend fun fetchMixFile(client: HttpClient): MixFile? {
|
||||
val decryptedBytes = fetchFile(url, client = client) ?: return null
|
||||
val decryptedBytes = fetchFile(url, client = client)
|
||||
return MixFile.fromBytes(decryptedBytes)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,20 +10,23 @@ import androidx.compose.ui.Modifier
|
||||
import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.ui.component.common.SingleSelectItemList
|
||||
import com.donut.mixfile.util.UseEffect
|
||||
import com.donut.mixfile.util.compareByName
|
||||
import com.donut.mixfile.util.file.favCategories
|
||||
import com.donut.mixfile.util.file.favorites
|
||||
import com.donut.mixfile.util.file.loadFileList
|
||||
import com.donut.mixfile.util.file.showFileList
|
||||
import com.donut.mixfile.util.objects.ProgressContent
|
||||
import com.donut.mixfile.util.showToast
|
||||
import com.donut.mixfile.util.sortByName
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
fun openCategorySelect(default: String = "", onSelect: (String) -> Unit) {
|
||||
MixDialogBuilder("收藏分类").apply {
|
||||
setContent {
|
||||
SingleSelectItemList(favCategories.toList().sortByName(), default) {
|
||||
SingleSelectItemList(
|
||||
favCategories.toList().sortedWith { str1, str2 -> str1.compareByName(str2) },
|
||||
default
|
||||
) {
|
||||
onSelect(it)
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.donut.mixfile.server.core.utils.resolveMixShareInfo
|
||||
import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.ui.component.common.SingleSelectItemList
|
||||
import com.donut.mixfile.ui.nav.MixNavPage
|
||||
@@ -45,6 +44,7 @@ import com.donut.mixfile.ui.theme.colorScheme
|
||||
import com.donut.mixfile.util.OnDispose
|
||||
import com.donut.mixfile.util.cachedMutableOf
|
||||
import com.donut.mixfile.util.catchError
|
||||
import com.donut.mixfile.util.compareByName
|
||||
import com.donut.mixfile.util.file.FileCardList
|
||||
import com.donut.mixfile.util.file.FileDataLog
|
||||
import com.donut.mixfile.util.file.downloadFile
|
||||
@@ -53,7 +53,6 @@ import com.donut.mixfile.util.file.selectAndUploadFile
|
||||
import com.donut.mixfile.util.file.showExportFileListDialog
|
||||
import com.donut.mixfile.util.file.showFileInfoDialog
|
||||
import com.donut.mixfile.util.formatFileSize
|
||||
import com.donut.mixfile.util.parseSortNum
|
||||
import com.donut.mixfile.util.showConfirmDialog
|
||||
import com.donut.mixfile.util.showToast
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -150,11 +149,11 @@ val Favorites = MixNavPage(
|
||||
sortJob?.cancel()
|
||||
sortJob = scope.launch(Dispatchers.IO) {
|
||||
catchError {
|
||||
val sorted = result.sortedBy {
|
||||
val sorted = result.sortedWith { file1, file2 ->
|
||||
if (!isActive) {
|
||||
throw Exception("canceled")
|
||||
throw Exception("排序取消")
|
||||
}
|
||||
it.name.parseSortNum()
|
||||
file1.name.compareByName(file2.name)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
if (resultCache == result) {
|
||||
@@ -262,10 +261,7 @@ val Favorites = MixNavPage(
|
||||
},
|
||||
Pair("全部下载") {
|
||||
selected.forEach {
|
||||
val shareInfo = resolveMixShareInfo(it.shareInfoData)
|
||||
if (shareInfo != null) {
|
||||
downloadFile(shareInfo)
|
||||
}
|
||||
downloadFile(it)
|
||||
}
|
||||
showDownloadTaskWindow()
|
||||
selected = emptySet()
|
||||
|
||||
@@ -100,7 +100,7 @@ fun MainTheme(
|
||||
window.statusBarColor = colorScheme.surface.toArgb()
|
||||
window.navigationBarColor = colorScheme.surface.toArgb()
|
||||
WindowCompat.getInsetsController(window, view).apply {
|
||||
isAppearanceLightNavigationBars = true
|
||||
isAppearanceLightStatusBars = true
|
||||
isAppearanceLightNavigationBars = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,17 +12,18 @@ import com.donut.mixfile.app
|
||||
import com.donut.mixfile.appScope
|
||||
import com.donut.mixfile.server.core.utils.genRandomString
|
||||
import com.donut.mixfile.server.core.utils.ignoreError
|
||||
import com.donut.mixfile.server.core.utils.isFalse
|
||||
import com.donut.mixfile.server.mixFileServer
|
||||
import com.donut.mixfile.ui.routes.home.getLocalServerAddress
|
||||
import io.ktor.http.URLBuilder
|
||||
import io.ktor.http.path
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.EOFException
|
||||
import java.math.BigInteger
|
||||
import java.net.Inet4Address
|
||||
import java.net.NetworkInterface
|
||||
import java.net.URL
|
||||
import java.net.URLEncoder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
@@ -145,22 +146,44 @@ fun <T> List<T>.at(index: Long): T {
|
||||
}
|
||||
|
||||
fun String.parseSortNum(): BigInteger {
|
||||
val digits = StringBuilder()
|
||||
|
||||
for (char in this) {
|
||||
if (char.isDigit()) {
|
||||
digits.append(char)
|
||||
}
|
||||
}
|
||||
return if (digits.isEmpty()) {
|
||||
BigInteger.ZERO
|
||||
} else {
|
||||
BigInteger(digits.toString())
|
||||
}
|
||||
val numberStr = this.filter { it.isDigit() }.ifEmpty { "0" }
|
||||
return BigInteger(numberStr)
|
||||
}
|
||||
|
||||
fun Iterable<String>.sortByName(): List<String> {
|
||||
return sortedBy { it.parseSortNum() }
|
||||
fun extractNumber(str: String, start: Int): Long {
|
||||
var result = 0L
|
||||
var i = start
|
||||
while (i < str.length && str[i].isDigit()) {
|
||||
result = result * 10 + (str[i] - '0')
|
||||
i++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun String.compareByName(str2: String): Int {
|
||||
var i1 = 0
|
||||
var i2 = 0
|
||||
val str1 = this
|
||||
while (i1 < str1.length && i2 < str2.length) {
|
||||
// 处理数字部分
|
||||
val char1 = str1[i1]
|
||||
val char2 = str2[i2]
|
||||
if (char1.isDigit() && char2.isDigit()) {
|
||||
val num1 =
|
||||
extractNumber(str1, i1).also { i1 += it.toString().length }
|
||||
val num2 =
|
||||
extractNumber(str2, i2).also { i2 += it.toString().length }
|
||||
//相等则继续提取下个数字进行比较
|
||||
if (num1 != num2) return num1.compareTo(num2)
|
||||
}
|
||||
// 处理非数字部分
|
||||
else {
|
||||
if (char1 != char2) return char1.compareTo(char2)
|
||||
i1++
|
||||
i2++
|
||||
}
|
||||
}
|
||||
return str1.length.compareTo(str2.length)
|
||||
}
|
||||
|
||||
fun <T> List<T>.at(index: Int): T {
|
||||
@@ -227,15 +250,6 @@ fun genRandomHexString(length: Int = 32) = genRandomString(length, ('0'..'9') +
|
||||
fun readRawFile(id: Int) = app.resources.openRawResource(id).readBytes()
|
||||
|
||||
|
||||
fun isValidUri(uriString: String): Boolean {
|
||||
try {
|
||||
val uri = Uri.parse(uriString)
|
||||
return uri != null && uri.scheme != null
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun showError(e: Throwable, tag: String = "") {
|
||||
Log.e(
|
||||
"error",
|
||||
@@ -248,26 +262,28 @@ fun getFileAccessUrl(
|
||||
shareInfo: String,
|
||||
fileName: String
|
||||
): String {
|
||||
return "${host}/api/download?s=${
|
||||
URLEncoder.encode(
|
||||
shareInfo,
|
||||
"UTF-8"
|
||||
)
|
||||
}&accessKey=${mixFileServer.accessKey}#${fileName}"
|
||||
return URLBuilder(host).apply {
|
||||
path("api", "download", fileName)
|
||||
fragment = fileName
|
||||
parameters.apply {
|
||||
append("s", shareInfo)
|
||||
if (mixFileServer.enableAccessKey) {
|
||||
append("accessKey", mixFileServer.accessKey)
|
||||
}
|
||||
}
|
||||
|
||||
}.buildString()
|
||||
}
|
||||
|
||||
fun getIpAddressInLocalNetwork(): String {
|
||||
val networkInterfaces = NetworkInterface.getNetworkInterfaces().iterator().asSequence()
|
||||
val localAddresses = networkInterfaces.flatMap {
|
||||
it.inetAddresses.asSequence()
|
||||
.filter { inetAddress ->
|
||||
inetAddress.isSiteLocalAddress && inetAddress?.hostAddress?.contains(":")
|
||||
.isFalse() &&
|
||||
inetAddress.hostAddress != "127.0.0.1"
|
||||
}
|
||||
.map { inetAddress -> inetAddress.hostAddress }
|
||||
}
|
||||
return localAddresses.firstOrNull() ?: "127.0.0.1"
|
||||
return NetworkInterface.getNetworkInterfaces()?.asSequence()
|
||||
?.filter { it.isUp }
|
||||
?.flatMap { it.inetAddresses.asSequence() }
|
||||
?.find { addr ->
|
||||
addr is Inet4Address &&
|
||||
addr.isSiteLocalAddress &&
|
||||
addr.hostAddress != "127.0.0.1"
|
||||
}?.hostAddress ?: "127.0.0.1"
|
||||
}
|
||||
|
||||
inline fun errorDialog(title: String, block: () -> Unit) {
|
||||
|
||||
@@ -41,6 +41,7 @@ import com.donut.mixfile.server.core.utils.isNotNull
|
||||
import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.ui.theme.MainTheme
|
||||
import com.donut.mixfile.ui.theme.colorScheme
|
||||
import com.donut.mixfile.util.objects.MixActivity
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -80,6 +81,9 @@ fun addComposeView(
|
||||
scheme: ColorScheme? = null,
|
||||
content: @Composable (removeView: () -> Unit) -> Unit
|
||||
): () -> Unit {
|
||||
if (MixActivity.firstActiveActivity() == null) {
|
||||
return {}
|
||||
}
|
||||
return addContentView(
|
||||
ComposeView(currentActivity).apply {
|
||||
setContent {
|
||||
|
||||
@@ -34,8 +34,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.donut.mixfile.server.core.utils.parseFileMimeType
|
||||
import com.donut.mixfile.server.core.utils.resolveMixShareInfo
|
||||
import com.donut.mixfile.server.downloadUrl
|
||||
import com.donut.mixfile.server.serverStarted
|
||||
import com.donut.mixfile.ui.routes.home.tryResolveFile
|
||||
import com.donut.mixfile.ui.theme.colorScheme
|
||||
@@ -85,18 +83,15 @@ fun PreviewCard(
|
||||
verticalArrangement = Arrangement.Bottom
|
||||
) {
|
||||
if (serverStarted) {
|
||||
val shareInfo = resolveMixShareInfo(fileDataLog.shareInfoData)
|
||||
if (shareInfo != null) {
|
||||
if (isImage && fileDataLog.size < 1024 * 1024 * 20 || isVideo) {
|
||||
ImageContent(
|
||||
shareInfo.downloadUrl,
|
||||
Modifier
|
||||
.height(200.dp)
|
||||
.fillMaxSize(),
|
||||
scale = ContentScale.Crop
|
||||
if (isImage && fileDataLog.size < 1024 * 1024 * 20 || isVideo) {
|
||||
ImageContent(
|
||||
fileDataLog.downloadUrl,
|
||||
Modifier
|
||||
.height(200.dp)
|
||||
.fillMaxSize(),
|
||||
scale = ContentScale.Crop
|
||||
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
@@ -135,7 +130,7 @@ fun FileCardList(
|
||||
cardList: List<FileDataLog>,
|
||||
selected: Set<FileDataLog> = setOf(),
|
||||
onClick: (FileDataLog) -> Unit = {
|
||||
tryResolveFile(it.shareInfoData)
|
||||
showFileInfoDialog(it)
|
||||
},
|
||||
longClick: (FileDataLog) -> Unit = {},
|
||||
) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField
|
||||
import com.donut.mixfile.server.core.utils.bean.MixShareInfo
|
||||
import com.donut.mixfile.server.core.utils.resolveMixShareInfo
|
||||
@@ -15,6 +14,7 @@ import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.ui.routes.autoAddFavorite
|
||||
import com.donut.mixfile.ui.routes.favorites.currentCategory
|
||||
import com.donut.mixfile.ui.routes.home.getLocalServerAddress
|
||||
import com.donut.mixfile.ui.routes.home.serverAddress
|
||||
import com.donut.mixfile.util.cachedMutableOf
|
||||
import com.donut.mixfile.util.getFileAccessUrl
|
||||
import com.donut.mixfile.util.showToast
|
||||
@@ -37,10 +37,18 @@ data class FileDataLog(
|
||||
category = category.trim()
|
||||
}
|
||||
|
||||
fun isSimilar(other: FileDataLog): Boolean {
|
||||
return other.shareInfoData.contentEquals(shareInfoData)
|
||||
}
|
||||
|
||||
@get:JSONField(serialize = false)
|
||||
val downloadUrl: String
|
||||
get() = getFileAccessUrl(getLocalServerAddress(), shareInfoData, name)
|
||||
|
||||
@get:JSONField(serialize = false)
|
||||
val lanUrl: String
|
||||
get() = getFileAccessUrl(serverAddress, shareInfoData, name)
|
||||
|
||||
fun updateDataList(
|
||||
list: List<FileDataLog>,
|
||||
action: (FileDataLog) -> FileDataLog
|
||||
@@ -98,7 +106,7 @@ data class FileDataLog(
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is FileDataLog) return false
|
||||
return shareInfoData.contentEquals(other.shareInfoData) && category.contentEquals(other.category)
|
||||
return isSimilar(other) && category.contentEquals(other.category)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -21,11 +19,8 @@ import androidx.core.net.toUri
|
||||
import com.donut.mixfile.activity.video.VideoActivity
|
||||
import com.donut.mixfile.app
|
||||
import com.donut.mixfile.currentActivity
|
||||
import com.donut.mixfile.server.core.utils.bean.MixShareInfo
|
||||
import com.donut.mixfile.server.core.utils.resolveMixShareInfo
|
||||
import com.donut.mixfile.server.core.utils.shareCode
|
||||
import com.donut.mixfile.server.downloadUrl
|
||||
import com.donut.mixfile.server.lanUrl
|
||||
import com.donut.mixfile.ui.component.common.MixDialogBuilder
|
||||
import com.donut.mixfile.ui.routes.favorites.importFileList
|
||||
import com.donut.mixfile.ui.routes.favorites.openCategorySelect
|
||||
@@ -36,6 +31,7 @@ import com.donut.mixfile.ui.routes.useSystemPlayer
|
||||
import com.donut.mixfile.ui.theme.colorScheme
|
||||
import com.donut.mixfile.util.copyToClipboard
|
||||
import com.donut.mixfile.util.formatFileSize
|
||||
import com.donut.mixfile.util.showToast
|
||||
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@@ -43,24 +39,23 @@ fun showFileInfoDialog(
|
||||
dataLog: FileDataLog,
|
||||
onDismiss: () -> Unit = {}
|
||||
) {
|
||||
var shareInfo = resolveMixShareInfo(dataLog.shareInfoData)!!
|
||||
val log = if (favorites.contains(dataLog)) {
|
||||
dataLog
|
||||
} else {
|
||||
favorites.firstOrNull { it.isSimilar(dataLog) }
|
||||
?: dataLog
|
||||
}
|
||||
val shareInfo = resolveMixShareInfo(log.shareInfoData)
|
||||
if (shareInfo == null) {
|
||||
showToast("解析文件分享码失败")
|
||||
return
|
||||
}
|
||||
MixDialogBuilder("文件信息", tag = "file-info-${shareInfo.url}").apply {
|
||||
onDismiss(onDismiss)
|
||||
setNegativeButton("复制分享码") {
|
||||
shareInfo.shareCode(useShortCode).copyToClipboard()
|
||||
}
|
||||
setContent {
|
||||
val log = remember(favorites) {
|
||||
if (favorites.contains(dataLog)) {
|
||||
dataLog
|
||||
} else {
|
||||
favorites.firstOrNull { it.shareInfoData.contentEquals(dataLog.shareInfoData) }
|
||||
?: dataLog
|
||||
}
|
||||
}
|
||||
LaunchedEffect(log) {
|
||||
shareInfo = resolveMixShareInfo(log.shareInfoData)!!
|
||||
}
|
||||
val fileName = log.name
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
@@ -73,12 +68,12 @@ fun showFileInfoDialog(
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
if (fileName.startsWith("__mixfile_list") || fileName.endsWith(".mix_list")) {
|
||||
AssistChip(onClick = {
|
||||
importFileList(shareInfo.downloadUrl)
|
||||
importFileList(log.downloadUrl)
|
||||
}, label = {
|
||||
Text(text = "文件列表", color = colorScheme.primary)
|
||||
})
|
||||
}
|
||||
if (!favorites.any { it.shareInfoData.contentEquals(log.shareInfoData) }) {
|
||||
if (!favorites.any { it.isSimilar(log) }) {
|
||||
AssistChip(onClick = {
|
||||
addFavoriteLog(log)
|
||||
}, label = {
|
||||
@@ -117,12 +112,12 @@ fun showFileInfoDialog(
|
||||
AssistChip(onClick = {
|
||||
if (useSystemPlayer) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.setDataAndType(shareInfo.downloadUrl.toUri(), "video/*")
|
||||
intent.setDataAndType(log.downloadUrl.toUri(), "video/*")
|
||||
currentActivity.startActivity(intent)
|
||||
return@AssistChip
|
||||
}
|
||||
val intent = Intent(app, VideoActivity::class.java).apply {
|
||||
putExtra("url", shareInfo.downloadUrl)
|
||||
putExtra("url", log.downloadUrl)
|
||||
putExtra("hash", shareInfo.url)
|
||||
}
|
||||
currentActivity.startActivity(intent)
|
||||
@@ -132,14 +127,14 @@ fun showFileInfoDialog(
|
||||
}
|
||||
if (shareInfo.contentType().startsWith("image/")) {
|
||||
AssistChip(onClick = {
|
||||
showImageDialog(shareInfo.downloadUrl)
|
||||
showImageDialog(log.downloadUrl)
|
||||
}, label = {
|
||||
Text(text = "查看图片", color = colorScheme.primary)
|
||||
})
|
||||
}
|
||||
|
||||
AssistChip(onClick = {
|
||||
shareInfo.lanUrl.copyToClipboard()
|
||||
log.lanUrl.copyToClipboard()
|
||||
}, label = {
|
||||
Text(text = "复制局域网地址", color = colorScheme.primary)
|
||||
})
|
||||
@@ -147,7 +142,7 @@ fun showFileInfoDialog(
|
||||
}
|
||||
}
|
||||
setPositiveButton("下载文件") {
|
||||
downloadFile(shareInfo)
|
||||
downloadFile(log)
|
||||
closeDialog()
|
||||
showDownloadTaskWindow()
|
||||
}
|
||||
@@ -169,7 +164,7 @@ fun InfoText(key: String, value: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadFile(shareInfo: MixShareInfo) {
|
||||
val task = DownloadTask(shareInfo.fileName, shareInfo.fileSize, shareInfo.downloadUrl)
|
||||
fun downloadFile(file: FileDataLog) {
|
||||
val task = DownloadTask(file.name, file.size, file.downloadUrl)
|
||||
task.start()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.donut.mixfile
|
||||
|
||||
import com.donut.mixfile.server.core.utils.resolveMixShareInfo
|
||||
import com.alibaba.fastjson2.to
|
||||
import com.alibaba.fastjson2.toJSONString
|
||||
import org.junit.Test
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import java.util.Date
|
||||
|
||||
//appScope.launch(Dispatchers.IO) {
|
||||
@@ -30,7 +29,11 @@ class ExampleUnitTest {
|
||||
data class User(
|
||||
val age: Int,
|
||||
val date: Date = Date()
|
||||
)
|
||||
) {
|
||||
init {
|
||||
println("init")
|
||||
}
|
||||
}
|
||||
|
||||
val map = mapOf(1 to "aa", 2 to "bb")
|
||||
|
||||
@@ -38,6 +41,7 @@ class ExampleUnitTest {
|
||||
@Test
|
||||
fun main() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user