1
0
mirror of https://github.com/fumiama/copymanga.git synced 2026-06-12 19:58:35 +08:00
优化
1. 错误打印
This commit is contained in:
源文雨
2025-06-06 23:04:11 +08:00
parent 159e2ad976
commit cd68a5056d
10 changed files with 136 additions and 108 deletions

View File

@@ -12,8 +12,8 @@ android {
minSdkVersion 23 minSdkVersion 23
//noinspection OldTargetApi //noinspection OldTargetApi
targetSdkVersion 34 targetSdkVersion 34
versionCode 77 versionCode 78
versionName '2.5.4' versionName '2.5.5'
resourceConfigurations += ['zh', 'zh-rCN'] resourceConfigurations += ['zh', 'zh-rCN']
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -26,7 +26,8 @@ class LoginActivity : AppCompatActivity() {
alblogin.setOnClickListener { alblogin.setOnClickListener {
lifecycleScope.launch { lifecycleScope.launch {
val salt = Random.nextInt(10000) val salt = Random.nextInt(10000)
val username = altusrnm.text?.toString() ?: run { val username = altusrnm.text?.toString()
if (username.isNullOrEmpty()) {
Toast.makeText( Toast.makeText(
this@LoginActivity, this@LoginActivity,
R.string.login_null_username, R.string.login_null_username,
@@ -34,7 +35,8 @@ class LoginActivity : AppCompatActivity() {
).show() ).show()
return@launch return@launch
} }
val pwd = altpwd.text?.toString() ?: run { val pwd = altpwd.text?.toString()
if (pwd.isNullOrEmpty()) {
Toast.makeText(this@LoginActivity, R.string.login_null_pwd, Toast.LENGTH_SHORT) Toast.makeText(this@LoginActivity, R.string.login_null_pwd, Toast.LENGTH_SHORT)
.show() .show()
return@launch return@launch
@@ -50,14 +52,23 @@ class LoginActivity : AppCompatActivity() {
finish() finish()
return@launch return@launch
} }
val l = MainActivity.member?.login(username, pwd, salt) try {
Log.d("MyLA", "login return code: ${l?.code}") val l = MainActivity.member?.login(username, pwd, salt)
if (l?.code == 200) { Log.d("MyLA", "login return code: ${l?.code}")
MainActivity.mainWeakReference?.get()?.refreshUserInfo() if (l?.code == 200) {
finish() MainActivity.mainWeakReference?.get()?.refreshUserInfo()
return@launch finish()
return@launch
}
Toast.makeText(this@LoginActivity, "错误码${l?.code}: ${l?.message}", Toast.LENGTH_LONG).show()
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(
this@LoginActivity,
"${e::class.simpleName} ${e.message}",
Toast.LENGTH_SHORT
).show()
} }
Toast.makeText(this@LoginActivity, l?.message, Toast.LENGTH_LONG).show()
} }
} }
if (Config.general_enable_transparent_system_bar.value) { if (Config.general_enable_transparent_system_bar.value) {

View File

@@ -84,7 +84,9 @@ class Book(val path: String, private val getString: (Int) -> String, private val
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
MainActivity.mainWeakReference?.get()?.apply { MainActivity.mainWeakReference?.get()?.apply {
runOnUiThread { Toast.makeText(this, e.message, Toast.LENGTH_SHORT).show() } withContext(Dispatchers.Main) {
runOnUiThread { Toast.makeText(this@apply, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show() }
}
} }
} }
} }

View File

@@ -15,7 +15,7 @@ class Shelf(private val getString: (Int) -> String) {
private val delApiUrl get() = "${apiUrl}s?platform=${Config.platform.value}" private val delApiUrl get() = "${apiUrl}s?platform=${Config.platform.value}"
suspend fun add(comicId: String): String = withContext(Dispatchers.IO) { suspend fun add(comicId: String): String = withContext(Dispatchers.IO) {
if (comicId.isEmpty()) { if (comicId.isEmpty()) {
return@withContext "空漫画ID" throw IllegalArgumentException("空漫画ID")
} }
val body = buildString { val body = buildString {
append("comic_id=") append("comic_id=")
@@ -24,19 +24,15 @@ class Shelf(private val getString: (Int) -> String) {
append("") append("")
append(Config.token.value) append(Config.token.value)
} }
return@withContext try { val re = Config.myHostApiUrl.request(
val re = Config.myHostApiUrl.request( addApiUrl, body.encodeToByteArray(), "POST",
addApiUrl, body.encodeToByteArray(), "POST", "application/x-www-form-urlencoded;charset=utf-8")
"application/x-www-form-urlencoded;charset=utf-8") Gson().fromJson(re, ReturnBase::class.java).message
Gson().fromJson(re, ReturnBase::class.java).message
} catch (e: Exception) {
e.message?:e::class.simpleName?:e.toString()
}
} }
suspend fun del(vararg bookIds: Int): String = withContext(Dispatchers.IO) { suspend fun del(vararg bookIds: Int): String = withContext(Dispatchers.IO) {
if (bookIds.isEmpty()) { if (bookIds.isEmpty()) {
return@withContext "空ID列表" throw IllegalArgumentException("空ID列表")
} }
val body = buildString { val body = buildString {
bookIds.forEach { bookIds.forEach {
@@ -47,14 +43,10 @@ class Shelf(private val getString: (Int) -> String) {
append("authorization=Token+") append("authorization=Token+")
append(Config.token.value) append(Config.token.value)
} }
return@withContext try { val re = Config.myHostApiUrl.request(
val re = Config.myHostApiUrl.request( delApiUrl, body.encodeToByteArray(),
delApiUrl, body.encodeToByteArray(), "DELETE", "application/x-www-form-urlencoded;charset=utf-8")
"DELETE", "application/x-www-form-urlencoded;charset=utf-8") Gson().fromJson(re, ReturnBase::class.java).message
Gson().fromJson(re, ReturnBase::class.java).message
} catch (e: Exception) {
e.message?:e::class.simpleName?:e.toString()
}
} }
suspend fun query(pathWord: String): BookQueryStructure? = withContext(Dispatchers.IO) { suspend fun query(pathWord: String): BookQueryStructure? = withContext(Dispatchers.IO) {

View File

@@ -3,6 +3,10 @@ package top.fumiama.copymanga.api.network
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import com.google.gson.Gson import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import top.fumiama.copymanga.MainActivity import top.fumiama.copymanga.MainActivity
import top.fumiama.copymanga.api.Config.apiProxy import top.fumiama.copymanga.api.Config.apiProxy
import top.fumiama.copymanga.api.Config.networkApiUrl import top.fumiama.copymanga.api.Config.networkApiUrl
@@ -13,13 +17,10 @@ import top.fumiama.copymanga.json.NetworkStructure
import top.fumiama.copymanga.json.ReturnBase import top.fumiama.copymanga.json.ReturnBase
import top.fumiama.copymanga.net.DownloadTools import top.fumiama.copymanga.net.DownloadTools
import top.fumiama.dmzj.copymanga.R import top.fumiama.dmzj.copymanga.R
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
class Api { class Api {
private var mHostApiUrls = mutableListOf<String>() private var mHostApiUrls = mutableListOf<String>()
private var mu = ReentrantReadWriteLock() private var mu = Mutex()
fun getApis(): Array<String> { fun getApis(): Array<String> {
return mHostApiUrls.toTypedArray() return mHostApiUrls.toTypedArray()
@@ -28,12 +29,12 @@ class Api {
suspend fun init() { suspend fun init() {
if (mHostApiUrls.isNotEmpty()) return if (mHostApiUrls.isNotEmpty()) return
if (reverseProxyUrl.value.isNotEmpty() && reverseProxyUrl.value != proxyUrl) { if (reverseProxyUrl.value.isNotEmpty() && reverseProxyUrl.value != proxyUrl) {
mu.write { mHostApiUrls = mutableListOf(reverseProxyUrl.value) } mu.withLock { mHostApiUrls = mutableListOf(reverseProxyUrl.value) }
Log.d("MyApi", "myHostApiUrl set reverse proxy to ${reverseProxyUrl.value}") Log.d("MyApi", "myHostApiUrl set reverse proxy to ${reverseProxyUrl.value}")
return return
} }
MainActivity.mainWeakReference?.get()?.apply { MainActivity.mainWeakReference?.get()?.apply {
mu.write { mu.withLock {
if (mHostApiUrls.isNotEmpty()) return if (mHostApiUrls.isNotEmpty()) return
try { try {
val d = get(getString(R.string.networkApiUrl).format(platform.value), networkApiUrl.value) val d = get(getString(R.string.networkApiUrl).format(platform.value), networkApiUrl.value)
@@ -48,15 +49,19 @@ class Api {
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
mHostApiUrls = mutableListOf(networkApiUrl.value) mHostApiUrls = mutableListOf(networkApiUrl.value)
runOnUiThread { withContext(Dispatchers.Main) {
Toast.makeText(this, e.message, Toast.LENGTH_SHORT).show() runOnUiThread {
Toast.makeText(this@apply, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show()
}
} }
} }
if (mHostApiUrls.isEmpty()) { if (mHostApiUrls.isEmpty()) {
mHostApiUrls = mutableListOf(networkApiUrl.value) mHostApiUrls = mutableListOf(networkApiUrl.value)
Log.d("MyApi", "myHostApiUrl set default ${mHostApiUrls[0]}") Log.d("MyApi", "myHostApiUrl set default ${mHostApiUrls[0]}")
runOnUiThread { withContext(Dispatchers.Main) {
Toast.makeText(this, "无法获取API列表", Toast.LENGTH_SHORT).show() runOnUiThread {
Toast.makeText(this@apply, "无法获取API列表", Toast.LENGTH_SHORT).show()
}
} }
} }
} }
@@ -65,7 +70,7 @@ class Api {
} }
// get throw error on non-json or non-200 or empty apis, path: /api/v3/xxx, return json string // get throw error on non-json or non-200 or empty apis, path: /api/v3/xxx, return json string
suspend fun get(path: String, forceApi: String? = null): String { suspend fun get(path: String, forceApi: String? = null): String {
val apis = if (forceApi == null) mu.read { mHostApiUrls.toTypedArray() } else arrayOf(forceApi) val apis = if (forceApi == null) mu.withLock { mHostApiUrls.toTypedArray() } else arrayOf(forceApi)
if (apis.isEmpty()) { if (apis.isEmpty()) {
throw NoSuchElementException("API列表为空") throw NoSuchElementException("API列表为空")
} }
@@ -78,13 +83,22 @@ class Api {
}?: DownloadTools.getApiContent(u)).decodeToString() }?: DownloadTools.getApiContent(u)).decodeToString()
r = Gson().fromJson(ret, ReturnBase::class.java) r = Gson().fromJson(ret, ReturnBase::class.java)
if (r!!.code != 200) { if (r!!.code != 200) {
mu.write { mHostApiUrls.remove(api) } withContext(Dispatchers.Main) {
MainActivity.mainWeakReference?.get()?.apply {
runOnUiThread {
Toast.makeText(this, "错误码${r?.code?:-1}, 信息: ${r?.message?:"空"}", Toast.LENGTH_SHORT).show()
}
}
}
} else { } else {
return ret return ret
} }
} catch (e: Exception) { } catch (e: Exception) {
mu.write { mHostApiUrls.remove(api) } mu.withLock {
if (i >= apis.size-1) { // throw lase exception if (mHostApiUrls.size <= 1) return@withLock
mHostApiUrls.remove(api)
}
if (i >= apis.size-1) { // throw last exception
throw e throw e
} }
} }
@@ -93,12 +107,12 @@ class Api {
} }
// request throw error on non-json or non-200 or empty apis, path: /api/v3/xxx, return json string // request throw error on non-json or non-200 or empty apis, path: /api/v3/xxx, return json string
suspend fun request(path: String, body: ByteArray, method: String, contentType: String, forceApi: String? = null): String { suspend fun request(path: String, body: ByteArray, method: String, contentType: String, forceApi: String? = null): String {
val apis = if (forceApi == null) mu.read { mHostApiUrls } else mutableListOf(forceApi) val apis = if (forceApi == null) mu.withLock { mHostApiUrls } else mutableListOf(forceApi)
if (apis.isEmpty()) { if (apis.isEmpty()) {
throw NoSuchElementException("API列表为空") throw NoSuchElementException("API列表为空")
} }
var r: ReturnBase? = null var r: ReturnBase? = null
apis.forEach { api -> apis.forEachIndexed { i, api ->
val u = "https://$api$path" val u = "https://$api$path"
try { try {
val ret = (apiProxy?.comancry(u) { val ret = (apiProxy?.comancry(u) {
@@ -106,21 +120,27 @@ class Api {
}?: DownloadTools.requestApiWithBody(u, method, body, contentType)).decodeToString() }?: DownloadTools.requestApiWithBody(u, method, body, contentType)).decodeToString()
r = Gson().fromJson(ret, ReturnBase::class.java) r = Gson().fromJson(ret, ReturnBase::class.java)
if (r!!.code != 200) { if (r!!.code != 200) {
mu.write { withContext(Dispatchers.Main) {
if (mHostApiUrls.size <= 1) return@write MainActivity.mainWeakReference?.get()?.apply {
mHostApiUrls.remove(api) runOnUiThread {
Toast.makeText(this, "错误码${r?.code?:-1}, 信息: ${r?.message?:"空"}", Toast.LENGTH_SHORT).show()
}
}
} }
} else { } else {
return ret return ret
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
mu.write { mu.withLock {
if (mHostApiUrls.size <= 1) return@write if (mHostApiUrls.size <= 1) return@withLock
mHostApiUrls.remove(api) mHostApiUrls.remove(api)
} }
if (i >= apis.size-1) { // throw last exception
throw e
}
} }
} }
throw IllegalStateException("错误码${r!!.code}, 信息: ${r!!.message}") throw IllegalStateException("错误码${r?.code?:-1}, 信息: ${r?.message?:"空"}")
} }
} }

View File

@@ -70,7 +70,7 @@ object Update {
Toast.makeText(this@apply, "下载成功", Toast.LENGTH_SHORT).show() Toast.makeText(this@apply, "下载成功", Toast.LENGTH_SHORT).show()
info.dismiss() info.dismiss()
install(it, this@apply) install(it, this@apply)
} else runOnUiThread { } else {
Toast.makeText(this@apply, "文件损坏", Toast.LENGTH_SHORT).show() Toast.makeText(this@apply, "文件损坏", Toast.LENGTH_SHORT).show()
info.dismiss() info.dismiss()
} }

View File

@@ -2,6 +2,7 @@ package top.fumiama.copymanga.api.user
import android.util.Base64 import android.util.Base64
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import top.fumiama.copymanga.api.Config import top.fumiama.copymanga.api.Config
@@ -14,14 +15,7 @@ class Member(private val getString: (Int) -> String) {
val hasLogin: Boolean get() = Config.token.value?.isNotEmpty() ?: false val hasLogin: Boolean get() = Config.token.value?.isNotEmpty() ?: false
suspend fun login(username: String, pwd: String, salt: Int): LoginInfoStructure = suspend fun login(username: String, pwd: String, salt: Int): LoginInfoStructure =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
return@withContext try { return@withContext saveInfo(postLogin(username, pwd, salt))
saveInfo(postLogin(username, pwd, salt))
} catch (e: Exception) {
val l = LoginInfoStructure()
l.code = 400
l.message = e.message.toString()
l
}
} }
/** /**
@@ -32,30 +26,13 @@ class Member(private val getString: (Int) -> String) {
*/ */
suspend fun info(): LoginInfoStructure = withContext(Dispatchers.IO) { suspend fun info(): LoginInfoStructure = withContext(Dispatchers.IO) {
if (!hasLogin) { if (!hasLogin) {
val l = LoginInfoStructure() throw IllegalArgumentException(getString(R.string.noLogin))
l.code = 449
l.message = getString(R.string.noLogin)
return@withContext l
}
try {
val u = getString(R.string.memberInfoApiUrl)
.format(Config.platform.value)
try {
val l = Gson().fromJson(Config.myHostApiUrl.get(u), LoginInfoStructure::class.java)
if (l.code == 200) Config.avatar.value = l.results.avatar
l
} catch (e: Exception) {
val l = LoginInfoStructure()
l.code = 450
l.message = "${getString(R.string.login_get_avatar_failed)}: ${e.message}"
l
}
} catch (e: Exception) {
val l = LoginInfoStructure()
l.code = 450
l.message = "${getString(R.string.login_get_avatar_failed)}: ${e.message}"
l
} }
val u = getString(R.string.memberInfoApiUrl)
.format(Config.platform.value)
val l = Gson().fromJson(Config.myHostApiUrl.get(u), LoginInfoStructure::class.java)
if (l.code == 200) Config.avatar.value = l.results.avatar
l
} }
suspend fun logout() = withContext(Dispatchers.IO) { suspend fun logout() = withContext(Dispatchers.IO) {
@@ -78,8 +55,8 @@ class Member(private val getString: (Int) -> String) {
} }
return@use l return@use l
} ?: throw Exception(getString(R.string.login_parse_json_error)) } ?: throw Exception(getString(R.string.login_parse_json_error))
} catch (e: Exception) { } catch (e: JsonSyntaxException) {
throw Exception(data.decodeToString(), e) throw JsonSyntaxException(data.decodeToString(), e)
} }
} }

View File

@@ -207,7 +207,7 @@ object DownloadTools {
Log.d("MyDT", "getHttpContent: ${ret.size} bytes") Log.d("MyDT", "getHttpContent: ${ret.size} bytes")
if (!u.startsWith("https://$proxyUrl") && if (!u.startsWith("https://$proxyUrl") &&
conn.getHeaderField("Content-type") != "application/json") { conn.getHeaderField("Content-type") != "application/json") {
throw IllegalStateException("请求错误: ${ret.decodeToString()}") throw IllegalStateException("非JSON返回: ${ret.decodeToString()}")
} }
decodeBody(ret, conn.getHeaderField("Content-Encoding")?:"") decodeBody(ret, conn.getHeaderField("Content-Encoding")?:"")
} }

View File

@@ -20,6 +20,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import top.fumiama.copymanga.MainActivity import top.fumiama.copymanga.MainActivity
import top.fumiama.copymanga.api.Config
import top.fumiama.copymanga.api.manga.Book import top.fumiama.copymanga.api.manga.Book
import top.fumiama.copymanga.api.manga.Reader import top.fumiama.copymanga.api.manga.Reader
import top.fumiama.copymanga.strings.Chinese import top.fumiama.copymanga.strings.Chinese
@@ -194,8 +195,10 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
activity?.runOnUiThread { withContext(Dispatchers.Main) {
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() activity?.runOnUiThread {
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show()
}
} }
} }
} }
@@ -217,29 +220,45 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
book?.uuid?.let { uuid -> book?.uuid?.let { uuid ->
this@BookFragment.lbbsub?.setOnClickListener { this@BookFragment.lbbsub?.setOnClickListener {
lifecycleScope.launch clickLaunch@ { lifecycleScope.launch clickLaunch@ {
if (Config.token.value.isNullOrEmpty()) {
Toast.makeText(context, R.string.noLogin, Toast.LENGTH_SHORT).show()
return@clickLaunch
}
if (this@BookFragment.lbbsub.text != getString(R.string.button_sub)) { if (this@BookFragment.lbbsub.text != getString(R.string.button_sub)) {
mBookHandler?.collect?.let { collect -> mBookHandler?.collect?.let { collect ->
if (collect < 0) return@clickLaunch if (collect < 0) return@clickLaunch
val re = MainActivity.shelf?.del(collect) try {
Toast.makeText(context, re, Toast.LENGTH_SHORT).show() val re = MainActivity.shelf?.del(collect)
if (re == "请求成功") { Toast.makeText(context, re, Toast.LENGTH_SHORT).show()
this@BookFragment.lbbsub.apply { if (re == "请求成功") {
setText(R.string.button_sub) this@BookFragment.lbbsub.apply {
val color = MaterialColors.getColor(this, R.attr.colorPrimarySurface) setText(R.string.button_sub)
backgroundTintList = ColorStateList.valueOf(color) val color = MaterialColors.getColor(this, R.attr.colorPrimarySurface)
backgroundTintList = ColorStateList.valueOf(color)
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show()
} }
} }
} }
return@clickLaunch return@clickLaunch
} }
val re = MainActivity.shelf?.add(uuid) try {
Toast.makeText(context, re, Toast.LENGTH_SHORT).show() val re = MainActivity.shelf?.add(uuid)
if (re == "修改成功") { Toast.makeText(context, re, Toast.LENGTH_SHORT).show()
queryCollect() if (re == "修改成功") {
this@BookFragment.lbbsub.apply { queryCollect()
setText(R.string.button_sub_subscribed) this@BookFragment.lbbsub.apply {
val color = MaterialColors.getColor(this, R.attr.colorButtonNormal) setText(R.string.button_sub_subscribed)
backgroundTintList = ColorStateList.valueOf(color) val color = MaterialColors.getColor(this, R.attr.colorButtonNormal)
backgroundTintList = ColorStateList.valueOf(color)
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show()
} }
} }
} }

View File

@@ -52,10 +52,17 @@ class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) {
if(netInfo != tb.transportStringNull && netInfo != tb.transportStringError) if(netInfo != tb.transportStringNull && netInfo != tb.transportStringError)
MainActivity.member?.apply { lifecycleScope.launch { MainActivity.member?.apply { lifecycleScope.launch {
Config.myHostApiUrl.init() Config.myHostApiUrl.init()
info().let { l -> try {
if (l.code != 200 && l.code != 449) { info().let { l ->
Toast.makeText(context, l.message, Toast.LENGTH_SHORT).show() if (l.code != 200 && l.code != 449) {
logout() Toast.makeText(context, l.message, Toast.LENGTH_SHORT).show()
logout()
}
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show()
} }
} }
} } } }