diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6251229..5e9fb8c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,8 +14,8 @@ android { applicationId = "com.donut.mixfile" minSdk = 24 targetSdk = 34 - versionCode = 35 - versionName = "1.4.4" + versionCode = 36 + versionName = "1.4.5" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/assets/assets/index-BN0yumEX.js b/app/src/main/assets/assets/index-DKGuaJ-j.js similarity index 99% rename from app/src/main/assets/assets/index-BN0yumEX.js rename to app/src/main/assets/assets/index-DKGuaJ-j.js index 2bf1989..2818671 100644 --- a/app/src/main/assets/assets/index-BN0yumEX.js +++ b/app/src/main/assets/assets/index-DKGuaJ-j.js @@ -299,7 +299,7 @@ To suppress this warning, you need to explicitly provide the \`palette.${t}Chann font-size: max(.6rem, 14px); } `;let aa=null,la=null;function aS(e){const[t,n]=fe([]),[r,o]=fe("");if(aa=n,la=o,t.length===0)return null;const i=r.split(` -`).length===t.length;return $(Qa,{open:!0,children:$(sS,{className:"shadow",children:[i?$("h3",{className:"file-card animate__animated animate__bounceIn",children:[t.length," 个文件全部上传成功"]}):$("h3",{children:[t.length," 个文件正在上传"]}),$("div",{class:"content",children:t.map((s,a)=>$(iS,{file:s},a))}),i&&$(ia.CopyToClipboard,{className:"file-card animate__animated animate__bounceIn",text:r,onCopy:()=>{zo("复制成功!")},children:$(jn,{variant:"outlined",children:"全部复制"})}),$(jn,{variant:"contained",onClick:()=>{aa([]),la("")},children:i?"关闭":"取消"})]})})}function Qc(e){aa(t=>[...t,...e])}function lS(e){la(t=>`${t} +`).length===t.length;return $(Qa,{open:!0,children:$(sS,{className:"shadow",children:[i?$("h3",{className:"file-card animate__animated animate__bounceIn",children:[t.length," 个文件全部上传成功"]}):$("h3",{children:[t.length," 个文件正在上传"]}),$("div",{class:"content",children:t.map((s,a)=>$(iS,{file:s},a))}),$(ia.CopyToClipboard,{className:"file-card animate__animated animate__bounceIn",text:r,onCopy:()=>{zo("复制成功!")},children:$(jn,{variant:"outlined",children:"全部复制"})}),$(jn,{variant:"contained",onClick:()=>{aa([]),la("")},children:i?"关闭":"取消"})]})})}function Qc(e){aa(t=>[...t,...e])}function lS(e){la(t=>`${t} ${e}`.trim())}class cS{constructor(t){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=t,this.count=0}get canAcquire(){return this.countthis.queue.push(t))}release(){const t=this.queue.shift();t?setTimeout(t,0):this.count--}}const Bt="_default";class uS{constructor(t=1){this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=t}hasSemaphoreInstance(t=Bt){return!!this.semaphoreInstances[t]}getSemaphoreInstance(t=Bt){return this.hasSemaphoreInstance(t)||(this.semaphoreInstances[t]=new cS(this.maxConcurrent)),this.semaphoreInstances[t]}tidy(t=Bt){this.hasSemaphoreInstance(t)&&this.getSemaphoreInstance(t).count===0&&delete this.semaphoreInstances[t]}canAcquire(t=Bt){return this.getSemaphoreInstance(t).canAcquire}acquire(t=Bt){return this.getSemaphoreInstance(t).acquire()}release(t=Bt){this.getSemaphoreInstance(t).release(),this.tidy(t)}count(t=Bt){return this.hasSemaphoreInstance(t)?this.getSemaphoreInstance(t).count:0}hasTasks(t=Bt){return this.count(t)>0}async request(t,n=Bt){try{return await this.acquire(n),await t()}finally{this.release(n)}}async requestIfAvailable(t,n=Bt){return this.canAcquire(n)?this.request(t,n):null}}const dS=cn.div` display: flex; flex-wrap: wrap; diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html index c00bbf6..fbb5114 100644 --- a/app/src/main/assets/index.html +++ b/app/src/main/assets/index.html @@ -6,7 +6,7 @@ MixFile - + diff --git a/app/src/main/java/com/donut/mixfile/server/Client.kt b/app/src/main/java/com/donut/mixfile/server/Client.kt index 6e80fc1..062ac3a 100644 --- a/app/src/main/java/com/donut/mixfile/server/Client.kt +++ b/app/src/main/java/com/donut/mixfile/server/Client.kt @@ -22,7 +22,7 @@ var UPLOAD_RETRY_TIMES by cachedMutableOf(3, "UPLOAD_RETRY_TIMES") val uploadClient = HttpClient(CIO).config { install(ContentNegotiation) { gson() - register(ContentType.Text.Html, GsonConverter(GsonBuilder().create())) + register(ContentType.Any, GsonConverter(GsonBuilder().create())) } install(HttpRequestRetry) { maxRetries = UPLOAD_RETRY_TIMES.toInt() diff --git a/app/src/main/java/com/donut/mixfile/server/Uploader.kt b/app/src/main/java/com/donut/mixfile/server/Uploader.kt index d15a70c..bdf3e23 100644 --- a/app/src/main/java/com/donut/mixfile/server/Uploader.kt +++ b/app/src/main/java/com/donut/mixfile/server/Uploader.kt @@ -22,7 +22,7 @@ abstract class Uploader(val name: String) { open val referer = "" open val chunkSize = 1024L * 1024L - abstract suspend fun doUpload(fileData: ByteArray): String? + abstract suspend fun doUpload(fileData: ByteArray): String companion object { val urlTransforms = mutableMapOf String>() @@ -52,7 +52,7 @@ abstract class Uploader(val name: String) { } } - suspend fun upload(head: ByteArray, fileData: ByteArray, key: ByteArray): String? { + suspend fun upload(head: ByteArray, fileData: ByteArray, key: ByteArray): String { val encryptedData = encryptBytes(head, fileData, key) try { return doUpload(encryptedData) diff --git a/app/src/main/java/com/donut/mixfile/server/routes/UploadRoute.kt b/app/src/main/java/com/donut/mixfile/server/routes/UploadRoute.kt index ff66382..bffb076 100644 --- a/app/src/main/java/com/donut/mixfile/server/routes/UploadRoute.kt +++ b/app/src/main/java/com/donut/mixfile/server/routes/UploadRoute.kt @@ -96,7 +96,7 @@ suspend fun uploadFile( fileIndex++ tasks.add(async { try { - val url = uploader.upload(head, fileData, secret) ?: return@async null + val url = uploader.upload(head, fileData, secret) fileList[currentIndex] = url withContext(Dispatchers.Main) { uploadTask.progress.updateProgress(channel.totalBytesRead, fileSize) @@ -114,7 +114,6 @@ suspend fun uploadFile( MixFile(chunkSize = chunkSize, version = 0, fileList = fileList, fileSize = fileSize) val mixFileUrl = uploader.upload(head, mixFile.toBytes(), secret) - ?: return@coroutineScope null return@coroutineScope mixFileUrl } } \ No newline at end of file diff --git a/app/src/main/java/com/donut/mixfile/server/uploaders/A3Uploader.kt b/app/src/main/java/com/donut/mixfile/server/uploaders/A3Uploader.kt index acba5d4..cad9588 100644 --- a/app/src/main/java/com/donut/mixfile/server/uploaders/A3Uploader.kt +++ b/app/src/main/java/com/donut/mixfile/server/uploaders/A3Uploader.kt @@ -14,7 +14,7 @@ object A3Uploader : Uploader("线路A3") { override val referer: String get() = "" - override suspend fun doUpload(fileData: ByteArray): String? { + override suspend fun doUpload(fileData: ByteArray): String { val result = uploadClient.submitFormWithBinaryData("https://pic.2xb.cn/uppic.php?type=qq", formData { add("file", fileData, fileFormHeaders()) @@ -22,7 +22,7 @@ object A3Uploader : Uploader("线路A3") { }.body() val code = result.get("code").asInt if (code != 200) { - return null + throw Exception("上传失败: $code") } return result.get("url").asString 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 b36c920..222a41a 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 @@ -118,11 +118,11 @@ val MixSettings = MixNavPage( color = MaterialTheme.colorScheme.primary ) Slider( - value = UPLOAD_TASK_COUNT.toFloat() / 100f, - steps = 100, + value = UPLOAD_TASK_COUNT.toFloat() / 10f, + steps = 10, modifier = Modifier.fillMaxWidth(), onValueChange = { - UPLOAD_TASK_COUNT = (it * 100).toLong().coerceAtLeast(1) + UPLOAD_TASK_COUNT = (it * 10).toLong().coerceAtLeast(1) } ) } @@ -148,11 +148,11 @@ val MixSettings = MixNavPage( color = MaterialTheme.colorScheme.primary ) Slider( - value = UPLOAD_RETRY_TIMES.toFloat() / 20f, - steps = 20, + value = UPLOAD_RETRY_TIMES.toFloat() / 10f, + steps = 10, modifier = Modifier.fillMaxWidth(), onValueChange = { - UPLOAD_RETRY_TIMES = (it * 20).toLong().coerceAtLeast(0) + UPLOAD_RETRY_TIMES = (it * 10).toLong().coerceAtLeast(0) } ) } @@ -181,6 +181,27 @@ val MixSettings = MixNavPage( ) { enablePreview = it } + SettingButton(text = "上传线路: $currentUploader") { + selectUploader() + } + if (getCurrentUploader() == CustomUploader) { + OutlinedTextField(value = CUSTOM_UPLOAD_URL, onValueChange = { + CUSTOM_UPLOAD_URL = it + }, label = { Text(text = "请求地址") }, modifier = Modifier.fillMaxWidth()) + OutlinedTextField(value = CUSTOM_REFERER, onValueChange = { + CUSTOM_REFERER = it + }, label = { Text(text = "referer") }, modifier = Modifier.fillMaxWidth()) + Text( + color = Color.Gray, + text = """ + 自定义线路请自行实现,app会使用put方式发送请求 + 请求体为图片二进制,成功请返回200状态码,内容直接返回url + 失败返回403或500(会重试)状态码 + 另外需要实现get方法返回填充图片,推荐gif格式 + 图片尺寸不宜过大,否则影响上传速度 + """.trimIndent() + ) + } HorizontalDivider() ElevatedButton(onClick = { MixDialogBuilder("确定清除记录?").apply { @@ -207,27 +228,6 @@ val MixSettings = MixNavPage( Text(text = "省电限制未设置!") } } - SettingButton(text = "上传线路: $currentUploader") { - selectUploader() - } - if (getCurrentUploader() == CustomUploader) { - OutlinedTextField(value = CUSTOM_UPLOAD_URL, onValueChange = { - CUSTOM_UPLOAD_URL = it - }, label = { Text(text = "请求地址") }, modifier = Modifier.fillMaxWidth()) - OutlinedTextField(value = CUSTOM_REFERER, onValueChange = { - CUSTOM_REFERER = it - }, label = { Text(text = "referer") }, modifier = Modifier.fillMaxWidth()) - Text( - color = Color.Gray, - text = """ - 自定义线路请自行实现,app会使用put方式发送请求 - 请求体为图片二进制,成功请返回200状态码,内容直接返回url - 失败返回403或500(会重试)状态码 - 另外需要实现get方法返回填充图片,推荐gif格式 - 图片尺寸不宜过大,否则影响上传速度 - """.trimIndent() - ) - } } fun selectUploader() { diff --git a/app/src/main/java/com/donut/mixfile/ui/routes/UploadDialog.kt b/app/src/main/java/com/donut/mixfile/ui/routes/UploadDialog.kt index 28e4647..881d79c 100644 --- a/app/src/main/java/com/donut/mixfile/ui/routes/UploadDialog.kt +++ b/app/src/main/java/com/donut/mixfile/ui/routes/UploadDialog.kt @@ -76,7 +76,7 @@ fun showUploadTaskWindow() { @Composable fun UploadDialogCard() { - AnimatedVisibility(visible = uploadTasks.any { it.uploading }) { + AnimatedVisibility(visible = uploadTasks.isNotEmpty()) { ElevatedCard( modifier = Modifier .fillMaxWidth(), @@ -91,14 +91,26 @@ fun UploadDialogCard() { .padding(10.dp), verticalAlignment = Alignment.CenterVertically ) { - Text( - text = "${uploadTasks.filter { it.uploading }.size} 个文件正在上传中", - modifier = Modifier, - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - color = colorScheme.primary - ) - CircularProgressIndicator(modifier = Modifier.size(20.dp)) + val uploading = uploadTasks.filter { it.uploading }.size + if (uploading > 0) { + Text( + text = "$uploading 个文件正在上传中", + modifier = Modifier, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = colorScheme.primary + ) + CircularProgressIndicator(modifier = Modifier.size(20.dp)) + } else { + val failed = uploadTasks.filter { !it.uploading }.size + Text( + text = "$failed 个文件上传失败", + modifier = Modifier, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = colorScheme.error + ) + } } } } diff --git a/app/src/main/java/com/donut/mixfile/ui/routes/home/Home.kt b/app/src/main/java/com/donut/mixfile/ui/routes/home/Home.kt index f2613dd..16f0ba2 100644 --- a/app/src/main/java/com/donut/mixfile/ui/routes/home/Home.kt +++ b/app/src/main/java/com/donut/mixfile/ui/routes/home/Home.kt @@ -153,10 +153,10 @@ val Home = MixNavPage( fun tryResolveFileList(text: String): Boolean { val textList = text.split("\n").map { it.trim() }.filter { it.isNotEmpty() } val fileList = textList.mapNotNull { resolveMixShareInfo(it) } - if (fileList.isEmpty()){ + if (fileList.isEmpty()) { return false } - if (fileList.size == 1){ + if (fileList.size == 1) { showFileInfoDialog(fileList.first()) return true } diff --git a/app/src/main/java/com/donut/mixfile/util/CommonUtil.kt b/app/src/main/java/com/donut/mixfile/util/CommonUtil.kt index b1dbf19..4615df9 100644 --- a/app/src/main/java/com/donut/mixfile/util/CommonUtil.kt +++ b/app/src/main/java/com/donut/mixfile/util/CommonUtil.kt @@ -8,20 +8,8 @@ import android.net.Uri import android.os.Build import android.provider.OpenableColumns import android.util.Log -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Close -import androidx.compose.material3.Icon -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier import com.donut.mixfile.app import com.donut.mixfile.appScope -import com.donut.mixfile.ui.theme.colorScheme import com.github.amr.mimetypes.MimeTypes import io.ktor.client.request.forms.FormBuilder import io.ktor.http.Headers @@ -307,6 +295,8 @@ fun decompressGzip(compressed: ByteArray): String { } } +fun readRawFile(id: Int) = app.resources.openRawResource(id).readBytes() + fun isValidUri(uriString: String): Boolean { try { diff --git a/app/src/main/java/com/donut/mixfile/util/file/FileDataLog.kt b/app/src/main/java/com/donut/mixfile/util/file/FileDataLog.kt index 4b4baa4..f9dde10 100644 --- a/app/src/main/java/com/donut/mixfile/util/file/FileDataLog.kt +++ b/app/src/main/java/com/donut/mixfile/util/file/FileDataLog.kt @@ -25,7 +25,7 @@ data class FileDataLog( val size: Long, @JsonAdapter(TimestampAdapter::class) val time: Date = Date(), - var category: String = "默认", + var category: String = currentCategory, ) { init { diff --git a/app/src/test/java/com/donut/mixfile/ExampleUnitTest.kt b/app/src/test/java/com/donut/mixfile/ExampleUnitTest.kt index 9b6f734..e5eef9d 100644 --- a/app/src/test/java/com/donut/mixfile/ExampleUnitTest.kt +++ b/app/src/test/java/com/donut/mixfile/ExampleUnitTest.kt @@ -1,8 +1,6 @@ package com.donut.mixfile -import com.donut.mixfile.server.uploaders.hidden.A2Uploader -import com.donut.mixfile.server.uploaders.hidden.sCode -import com.donut.mixfile.ui.routes.home.UploadTask +import com.donut.mixfile.util.file.encodeHex import kotlinx.coroutines.runBlocking import org.junit.Test