mirror of
https://github.com/InvertGeek/MixFile.git
synced 2026-06-01 17:10:49 +08:00
修复A2线路,优化网页端
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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.count<this.maxConcurrent}acquire(){return this.canAcquire?(this.count++,Promise.resolve()):new Promise(t=>this.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;
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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-BN0yumEX.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DKGuaJ-j.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BuZ9x1Ok.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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, (String) -> 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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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<JsonObject>()
|
||||
val code = result.get("code").asInt
|
||||
if (code != 200) {
|
||||
return null
|
||||
throw Exception("上传失败: $code")
|
||||
}
|
||||
|
||||
return result.get("url").asString
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user