mirror of
https://github.com/fumiama/copymanga.git
synced 2026-06-04 23:10:23 +08:00
v2.3.0
新增 1. 增强型数据访问选项 2. 漫画下载数量并发限制 修复 1. 同时下载过多漫画时失败 (fix #67) 2. 主页滑动横幅后刷新闪退 (fix #65) 优化 1. 网络不佳时自动打开增强型数据访问 2. 不再反复读取代理状态
This commit is contained in:
2
.idea/dictionaries/fumiama.xml
generated
2
.idea/dictionaries/fumiama.xml
generated
@@ -2,10 +2,12 @@
|
||||
<dictionary name="fumiama">
|
||||
<words>
|
||||
<w>alphae</w>
|
||||
<w>comandy</w>
|
||||
<w>downloaders</w>
|
||||
<w>grps</w>
|
||||
<w>imgs</w>
|
||||
<w>kohima</w>
|
||||
<w>libcomandy</w>
|
||||
<w>lowpan</w>
|
||||
<w>mangacopy</w>
|
||||
<w>mangafuna</w>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
@@ -8,8 +9,8 @@ android {
|
||||
applicationId 'top.fumiama.copymanga'
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 34
|
||||
versionCode 57
|
||||
versionName '2.2.9'
|
||||
versionCode 58
|
||||
versionName '2.3.0'
|
||||
resourceConfigurations += ['zh', 'zh-rCN']
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -46,6 +47,11 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}*/
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
@@ -78,7 +84,7 @@ dependencies {
|
||||
implementation 'com.github.yalantis:ucrop:2.2.6'
|
||||
implementation 'com.to.aboomy:pager2banner:1.0.1'
|
||||
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||
kapt 'com.github.bumptech.glide:compiler:4.16.0'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'com.github.vovaksenov99:OverscrollableScrollView:1.0'
|
||||
implementation 'com.liaoinstan.springview:library:1.7.0'
|
||||
@@ -86,4 +92,5 @@ dependencies {
|
||||
implementation 'com.lapism:search:2.4.1@aar'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
|
||||
implementation 'com.airbnb.android:lottie:6.4.0'
|
||||
implementation 'net.java.dev.jna:jna:5.14.0'
|
||||
}
|
||||
|
||||
BIN
app/libs/arm64-v8a/libjnidispatch.so
Normal file
BIN
app/libs/arm64-v8a/libjnidispatch.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libjnidispatch.so
Normal file
BIN
app/libs/armeabi-v7a/libjnidispatch.so
Normal file
Binary file not shown.
BIN
app/libs/x86/libjnidispatch.so
Normal file
BIN
app/libs/x86/libjnidispatch.so
Normal file
Binary file not shown.
BIN
app/libs/x86_64/libjnidispatch.so
Normal file
BIN
app/libs/x86_64/libjnidispatch.so
Normal file
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
package top.fumiama.copymanga.json;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ComandyCapsule {
|
||||
public int code;
|
||||
public String method;
|
||||
public String url;
|
||||
public HashMap<String, Object> headers;
|
||||
public String data;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package top.fumiama.copymanga.json;
|
||||
|
||||
public class ComandyVersion {
|
||||
public String body;
|
||||
}
|
||||
@@ -10,9 +10,7 @@ import androidx.core.animation.doOnEnd
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.android.synthetic.main.content_main.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.fumiama.copymanga.MainActivity
|
||||
import top.fumiama.copymanga.tools.ui.UITools
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
@@ -48,8 +46,8 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() {
|
||||
isFirstInflate = true
|
||||
Log.d("MyNBRF", "destroyed")
|
||||
}
|
||||
suspend fun showKanban() = withContext(Dispatchers.Main) {
|
||||
if (disableAnimation) return@withContext
|
||||
fun showKanban() {
|
||||
if (disableAnimation) return
|
||||
(activity?:(MainActivity.mainWeakReference?.get()))?.apply {cmaini?.post {
|
||||
if(cmaini?.visibility == View.GONE) {
|
||||
Log.d("MyNBRF", "show: start, set h: ${window?.decorView?.height}")
|
||||
@@ -62,8 +60,8 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() {
|
||||
Log.d("MyNBRF", "show: end")
|
||||
}
|
||||
private var isHideRunning = AtomicBoolean()
|
||||
suspend fun hideKanban() = withContext(Dispatchers.Main) {
|
||||
if (disableAnimation) return@withContext
|
||||
fun hideKanban() {
|
||||
if (disableAnimation) return
|
||||
(activity?:(MainActivity.mainWeakReference?.get()))?.apply { cmaini?.post {
|
||||
if(!isHideRunning.get() && cmaini?.visibility == View.VISIBLE) {
|
||||
isHideRunning.set(true)
|
||||
|
||||
117
app/src/main/java/top/fumiama/copymanga/tools/http/Comandy.kt
Normal file
117
app/src/main/java/top/fumiama/copymanga/tools/http/Comandy.kt
Normal file
@@ -0,0 +1,117 @@
|
||||
package top.fumiama.copymanga.tools.http
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.gson.Gson
|
||||
import com.sun.jna.Library
|
||||
import com.sun.jna.Native
|
||||
import top.fumiama.copymanga.MainActivity
|
||||
import top.fumiama.copymanga.json.ComandyVersion
|
||||
import top.fumiama.dmzj.copymanga.R
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
interface Comandy : Library {
|
||||
// fun add_dns(para: String?, is_ipv6: Int): String?
|
||||
|
||||
fun request(para: String): String?
|
||||
|
||||
companion object {
|
||||
private var isInInit = AtomicBoolean(false)
|
||||
var instance: Comandy? = null
|
||||
get() {
|
||||
//Log.d("MyComandy", "get instance @$field")
|
||||
if (field != null) return field
|
||||
field = libraryFile?.absolutePath?.let { Native.load(it, Comandy::class.java) }?:return null
|
||||
//Log.d("MyComandy", "init instance @$field")
|
||||
return field
|
||||
}
|
||||
private var mUseComandy: Boolean? = null
|
||||
val useComandy: Boolean
|
||||
get() {
|
||||
if (isInInit.get()) {
|
||||
Log.d("MyComandy", "block useComandy for isInInit")
|
||||
return false
|
||||
}
|
||||
if (mUseComandy != true && DownloadTools.failTimes.get() >= 2) {
|
||||
mUseComandy = true
|
||||
return true
|
||||
}
|
||||
if (mUseComandy != null) return mUseComandy!!
|
||||
MainActivity.mainWeakReference?.get()?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it).apply {
|
||||
val b = getBoolean("settings_cat_net_sw_use_comandy", false)
|
||||
Log.d("MyComandy", "use comandy: $b")
|
||||
mUseComandy = b
|
||||
return b
|
||||
}
|
||||
}
|
||||
mUseComandy = false
|
||||
return false
|
||||
}
|
||||
private val libraryFile: File?
|
||||
get() {
|
||||
if (isInInit.get()) return null
|
||||
isInInit.set(true)
|
||||
Log.d("MyComandy", "start to download/check lib")
|
||||
val prefix = when (Build.SUPPORTED_ABIS[0]) {
|
||||
"arm64-v8a" -> "aarch64"
|
||||
"armeabi-v7a" -> "armv7a"
|
||||
"x86_64" -> "x86_64"
|
||||
"x86" -> "i686"
|
||||
else -> null
|
||||
}?:return null
|
||||
Log.d("MyComandy", "arch: $prefix")
|
||||
return MainActivity.mainWeakReference?.get()?.let { ma ->
|
||||
var f = File(ma.filesDir, "libs")
|
||||
if (!f.exists()) f.mkdirs()
|
||||
f = File(f, "libcomandy.so")
|
||||
var remoteVersion = 0
|
||||
if (f.exists()) {
|
||||
DownloadTools.getHttpContent(ma.getString(R.string.comandy_version_url), -1)?.let dataLet@{
|
||||
try {
|
||||
val body = Gson().fromJson(it.decodeToString(), ComandyVersion::class.java)?.body
|
||||
if (body?.startsWith("Version: ") == true) {
|
||||
remoteVersion = body.substring(9).toInt()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
val myVersion = ma.getPreferences(AppCompatActivity.MODE_PRIVATE).getInt("comandy_version", 0)
|
||||
if (myVersion >= remoteVersion) {
|
||||
Log.d("MyComandy", "lib version $myVersion is latest")
|
||||
isInInit.set(false)
|
||||
return@let f
|
||||
}
|
||||
Log.d("MyComandy", "lib version $myVersion <= latest $remoteVersion, update...")
|
||||
}
|
||||
}
|
||||
DownloadTools.getHttpContent(ma.getString(R.string.comandy_download_url).format(prefix), -1)?.let {
|
||||
if(f.exists()) f.delete()
|
||||
try {
|
||||
GZIPInputStream(ByteArrayInputStream(it)).use { dataIn ->
|
||||
f.outputStream().use { dataOut ->
|
||||
dataIn.copyTo(dataOut)
|
||||
}
|
||||
}
|
||||
if (remoteVersion > 0) ma.getPreferences(AppCompatActivity.MODE_PRIVATE).edit {
|
||||
putInt("comandy_version", remoteVersion)
|
||||
apply()
|
||||
}
|
||||
Log.d("MyComandy", "update success")
|
||||
isInInit.set(false)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
if(f.exists()) f.delete()
|
||||
}
|
||||
}
|
||||
return@let if(f.exists()) f else null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package top.fumiama.copymanga.tools.http
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.load.model.ModelLoader
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import com.google.gson.Gson
|
||||
import top.fumiama.copymanga.MainActivity
|
||||
import top.fumiama.copymanga.json.ComandyCapsule
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
@GlideModule
|
||||
class ComandyGlideModule: AppGlideModule() {
|
||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||
super.registerComponents(context, glide, registry)
|
||||
registry.prepend(GlideUrl::class.java, ByteBuffer::class.java, ComandyGlideUrlFactory())
|
||||
registry.prepend(String::class.java, ByteBuffer::class.java, ComandyStringFactory())
|
||||
}
|
||||
|
||||
inner class ComandyDataFetcher(private val model: GlideUrl): DataFetcher<ByteBuffer> {
|
||||
constructor(model: String): this(GlideUrl(model))
|
||||
override fun loadData(
|
||||
priority: Priority,
|
||||
callback: DataFetcher.DataCallback<in ByteBuffer>
|
||||
) {
|
||||
val capsule = ComandyCapsule()
|
||||
capsule.url = model.toStringUrl()
|
||||
capsule.method = "GET"
|
||||
if (model.headers.isNotEmpty()) {
|
||||
capsule.headers = hashMapOf()
|
||||
model.headers.forEach { (k, v) -> capsule.headers[k] = v }
|
||||
}
|
||||
try {
|
||||
val para = Gson().toJson(capsule)
|
||||
MainActivity.mainWeakReference?.get()?.runOnUiThread {
|
||||
Log.d("MyCGM", "request: $para")
|
||||
}
|
||||
Comandy.instance!!.request(para).let { result ->
|
||||
Gson().fromJson(result, ComandyCapsule::class.java)!!.let {
|
||||
if (it.code != 200) {
|
||||
callback.onLoadFailed(IllegalArgumentException("HTTP${it.code} ${
|
||||
it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() }
|
||||
}"))
|
||||
return
|
||||
}
|
||||
callback.onDataReady(ByteBuffer.wrap(Base64.decode(it.data, Base64.DEFAULT)))
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
callback.onLoadFailed(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun cleanup() { }
|
||||
|
||||
override fun cancel() { }
|
||||
|
||||
override fun getDataClass(): Class<ByteBuffer> {
|
||||
return ByteBuffer::class.java
|
||||
}
|
||||
|
||||
override fun getDataSource(): DataSource {
|
||||
return DataSource.REMOTE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class ComandyGlideUrlModelLoader: ModelLoader<GlideUrl, ByteBuffer> {
|
||||
override fun buildLoadData(
|
||||
model: GlideUrl,
|
||||
width: Int,
|
||||
height: Int,
|
||||
options: Options
|
||||
): ModelLoader.LoadData<ByteBuffer> {
|
||||
return ModelLoader.LoadData(ObjectKey(model), ComandyDataFetcher(model))
|
||||
}
|
||||
|
||||
override fun handles(model: GlideUrl): Boolean {
|
||||
return Comandy.useComandy && Comandy.instance != null && model.toURL().let { it.protocol == "https" }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class ComandyStringModelLoader: ModelLoader<String, ByteBuffer> {
|
||||
override fun buildLoadData(
|
||||
model: String,
|
||||
width: Int,
|
||||
height: Int,
|
||||
options: Options
|
||||
): ModelLoader.LoadData<ByteBuffer> {
|
||||
return ModelLoader.LoadData(ObjectKey(model), ComandyDataFetcher(model))
|
||||
}
|
||||
|
||||
override fun handles(model: String): Boolean {
|
||||
return Comandy.useComandy && Comandy.instance != null && model.startsWith("https://")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class ComandyGlideUrlFactory: ModelLoaderFactory<GlideUrl, ByteBuffer> {
|
||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<GlideUrl, ByteBuffer> {
|
||||
return ComandyGlideUrlModelLoader()
|
||||
}
|
||||
|
||||
override fun teardown() { }
|
||||
|
||||
}
|
||||
|
||||
inner class ComandyStringFactory: ModelLoaderFactory<String, ByteBuffer> {
|
||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<String, ByteBuffer> {
|
||||
return ComandyStringModelLoader()
|
||||
}
|
||||
|
||||
override fun teardown() { }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,21 @@
|
||||
package top.fumiama.copymanga.tools.http
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.fumiama.copymanga.MainActivity
|
||||
import top.fumiama.copymanga.json.ComandyCapsule
|
||||
import top.fumiama.dmzj.copymanga.R
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.FutureTask
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
object DownloadTools {
|
||||
val app_ver = MainActivity.mainWeakReference?.get()?.let { main ->
|
||||
@@ -20,6 +25,7 @@ object DownloadTools {
|
||||
}!!
|
||||
val pc_ua = MainActivity.mainWeakReference?.get()!!.getString(R.string.pc_ua).format(app_ver)
|
||||
val referer = MainActivity.mainWeakReference?.get()!!.getString(R.string.referer).format(app_ver)
|
||||
val failTimes = AtomicInteger(0)
|
||||
fun getApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null, timeout: Int = 20000) =
|
||||
url.let {
|
||||
val connection = URL(url).openConnection() as HttpURLConnection
|
||||
@@ -45,10 +51,37 @@ object DownloadTools {
|
||||
}
|
||||
setRequestProperty("platform", "3")
|
||||
}
|
||||
Log.d("Mydl", "getConnection: $url\n${connection.requestProperties.map { "${it.key}: ${it.value}" }.joinToString("\n")}")
|
||||
Log.d("MyDT", "getConnection: $url\n${connection.requestProperties.map { "${it.key}: ${it.value}" }.joinToString("\n")}")
|
||||
connection
|
||||
}
|
||||
|
||||
fun getComandyApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null) =
|
||||
run {
|
||||
val capsule = ComandyCapsule()
|
||||
capsule.url = url
|
||||
capsule.method = method
|
||||
capsule.headers = hashMapOf()
|
||||
capsule.headers["host"] = url.substringAfter("://").substringBefore("/")
|
||||
ua?.let { capsule.headers["user-agent"] = it }
|
||||
refer?.let { capsule.headers["referer"] = it }
|
||||
capsule.headers["source"] = "copyApp"
|
||||
capsule.headers["webp"] = "1"
|
||||
MainActivity.mainWeakReference?.get()?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it).apply {
|
||||
capsule.headers["region"] = if(!getBoolean("settings_cat_net_sw_use_foreign", false)) "1" else "0"
|
||||
}
|
||||
it.getPreferences(Context.MODE_PRIVATE).apply {
|
||||
capsule.headers["version"] = app_ver
|
||||
getString("token", "")?.let { tk ->
|
||||
capsule.headers["authorization"] = "Token $tk"
|
||||
}
|
||||
}
|
||||
}
|
||||
capsule.headers["platform"] = "3"
|
||||
Log.d("MyDT", "getComandyConnection: $url\n${capsule.headers.map { "${it.key}: ${it.value}" }.joinToString("\n")}")
|
||||
capsule
|
||||
}
|
||||
|
||||
private fun getNormalConnection(url: String, method: String = "GET", ua: String? = null, timeout: Int = 20000) =
|
||||
url.let {
|
||||
val connection = URL(url).openConnection() as HttpURLConnection
|
||||
@@ -61,20 +94,75 @@ object DownloadTools {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getComandyNormalConnection(url: String, method: String = "GET", ua: String? = null) =
|
||||
run {
|
||||
val capsule = ComandyCapsule()
|
||||
capsule.url = url
|
||||
capsule.method = method
|
||||
capsule.headers = hashMapOf()
|
||||
capsule.headers["host"] = url.substringAfter("://").substringBefore("/")
|
||||
ua?.let { capsule.headers["user-agent"] = it }
|
||||
capsule
|
||||
}
|
||||
|
||||
suspend fun getHttpContent(u: String, refer: String? = null, ua: String? = null): ByteArray =
|
||||
withContext(Dispatchers.IO) {
|
||||
if (Comandy.useComandy) {
|
||||
getComandyApiConnection(u, "GET", refer, ua).let { capsule ->
|
||||
val para = Gson().toJson(capsule)
|
||||
//Log.d("MyDT", "comandy request: $para")
|
||||
Comandy.instance?.request(para)?.let { result ->
|
||||
//Log.d("MyDT", "comandy reply: $result")
|
||||
Gson().fromJson(result, ComandyCapsule::class.java)!!.let {
|
||||
if (it.code != 200) throw IllegalArgumentException("HTTP${it.code} ${
|
||||
it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() }
|
||||
}")
|
||||
Base64.decode(it.data, Base64.DEFAULT)
|
||||
}
|
||||
}
|
||||
}.let { if(it?.isNotEmpty() == true ) return@withContext it }
|
||||
}
|
||||
failTimes.incrementAndGet()
|
||||
getApiConnection(u, "GET", refer, ua).let {
|
||||
val ret = it.inputStream.readBytes()
|
||||
it.disconnect()
|
||||
Log.d("Mydl", "getHttpContent: ${ret.size} bytes")
|
||||
Log.d("MyDT", "getHttpContent: ${ret.size} bytes")
|
||||
failTimes.decrementAndGet()
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
fun getHttpContent(u: String, readSize: Int): ByteArray? {
|
||||
Log.d("Mydl", "getHttp: $u")
|
||||
var ret: ByteArray? = null
|
||||
val task = FutureTask(Callable {
|
||||
Log.d("MyDT", "getHttp: $u")
|
||||
val task = prepare(u, readSize)
|
||||
Thread(task).start()
|
||||
return try {
|
||||
task.get()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
if (Comandy.useComandy) failTimes.incrementAndGet()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun prepare(u: String, readSize: Int = -1) = run {
|
||||
Log.d("MyDT", "prepareHttp: $u")
|
||||
FutureTask(if (Comandy.useComandy) Callable{
|
||||
try {
|
||||
Comandy.instance?.request(Gson().toJson(
|
||||
getComandyNormalConnection(u, "GET"))
|
||||
)?.let { result ->
|
||||
Gson().fromJson(result, ComandyCapsule::class.java)?.let {
|
||||
if (it.code != 200) null
|
||||
else it.data?.let { d -> Base64.decode(d, Base64.DEFAULT) }
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
null
|
||||
}
|
||||
} else Callable {
|
||||
var ret: ByteArray? = null
|
||||
try {
|
||||
val connection = getNormalConnection(u, "GET")
|
||||
val ci = connection.inputStream
|
||||
@@ -87,57 +175,10 @@ object DownloadTools {
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
return@Callable ret
|
||||
ret
|
||||
})
|
||||
Thread(task).start()
|
||||
return try {
|
||||
task.get()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/*fun touch(url: String?): FutureTask<ByteArray?>? =
|
||||
url?.let {
|
||||
Log.d("Mydl", "touchHttp: $it")
|
||||
var ret: ByteArray? = null
|
||||
val task = FutureTask(Callable {
|
||||
try {
|
||||
val connection = getNormalConnection(it, "GET")
|
||||
|
||||
val ci = connection?.inputStream
|
||||
ret = ci?.readBytes()
|
||||
ci?.close()
|
||||
connection?.disconnect()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
return@Callable ret
|
||||
})
|
||||
Thread(task).start()
|
||||
task
|
||||
}*/
|
||||
|
||||
fun prepare(url: String?): FutureTask<ByteArray?>? =
|
||||
url?.let {
|
||||
Log.d("Mydl", "prepareHttp: $it")
|
||||
val task = FutureTask(Callable {
|
||||
var ret: ByteArray? = null
|
||||
try {
|
||||
val connection = getNormalConnection(it, "GET")
|
||||
connection.inputStream?.use { ci ->
|
||||
ret = ci.readBytes()
|
||||
}
|
||||
connection.disconnect()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
return@Callable ret
|
||||
})
|
||||
task
|
||||
}
|
||||
|
||||
/*private fun replaceChineseCharacters(string: String?) : String? {
|
||||
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.M) return string
|
||||
else return string?.replace(Regex("(?<=/)[\\w\\s\\d\\u4e00-\\u9fa5.-]+(?=/?)")) { match ->
|
||||
@@ -146,14 +187,31 @@ object DownloadTools {
|
||||
}*/
|
||||
|
||||
fun requestWithBody(url: String, method: String, body: ByteArray, refer: String? = null, ua: String? = null): ByteArray? {
|
||||
Log.d("Mydl", "$method Http: $url")
|
||||
Log.d("MyDT", "$method Http: $url")
|
||||
var ret: ByteArray? = null
|
||||
val task = FutureTask(Callable {
|
||||
val task = FutureTask(if(Comandy.useComandy) Callable{
|
||||
try {
|
||||
getApiConnection(url, method, refer, ua)?.apply {
|
||||
val capsule = getComandyApiConnection(url, method, refer, ua)
|
||||
capsule.data = body.decodeToString()
|
||||
Comandy.instance?.request(Gson().toJson(capsule))?.let { result ->
|
||||
Gson().fromJson(result, ComandyCapsule::class.java)?.let {
|
||||
if (it.code != 200) null
|
||||
else it.data?.let { d -> Base64.decode(d, Base64.DEFAULT) }
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
else Callable {
|
||||
failTimes.incrementAndGet()
|
||||
try {
|
||||
getApiConnection(url, method, refer, ua).apply {
|
||||
outputStream.write(body)
|
||||
ret = inputStream.readBytes()
|
||||
disconnect()
|
||||
failTimes.decrementAndGet()
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
|
||||
@@ -30,26 +30,34 @@ class Proxy(id: Int, private val apiRegex: Regex, keyID: Int? = null) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var mUseImageProxy: Boolean? = null
|
||||
val useImageProxy: Boolean
|
||||
get() {
|
||||
if (mUseImageProxy != null) return mUseImageProxy!!
|
||||
MainActivity.mainWeakReference?.get()?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it).apply {
|
||||
val b = getBoolean("settings_cat_net_sw_use_img_proxy", false)
|
||||
Log.d("MyProxy", "use image proxy: $b")
|
||||
mUseImageProxy = b
|
||||
return b
|
||||
}
|
||||
}
|
||||
mUseImageProxy = false
|
||||
return false
|
||||
}
|
||||
private var mUseApiProxy: Boolean? = null
|
||||
val useApiProxy: Boolean
|
||||
get() {
|
||||
if (mUseApiProxy != null) return mUseApiProxy!!
|
||||
MainActivity.mainWeakReference?.get()?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it).apply {
|
||||
val b = getBoolean("settings_cat_net_sw_use_api_proxy", false)
|
||||
Log.d("MyProxy", "use api proxy: $b")
|
||||
mUseApiProxy = b
|
||||
return b
|
||||
}
|
||||
}
|
||||
mUseApiProxy = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.android.synthetic.main.button_tbutton.*
|
||||
import kotlinx.android.synthetic.main.button_tbutton.view.*
|
||||
@@ -21,6 +22,7 @@ import kotlinx.android.synthetic.main.line_chapter.view.*
|
||||
import kotlinx.android.synthetic.main.line_horizonal_empty.view.*
|
||||
import kotlinx.android.synthetic.main.widget_downloadbar.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.fumiama.copymanga.json.ChapterStructure
|
||||
@@ -38,6 +40,7 @@ import top.fumiama.copymanga.views.LazyScrollView
|
||||
import top.fumiama.dmzj.copymanga.R
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class ComicDlHandler(looper: Looper, private val th: WeakReference<ComicDlFragment>, private val vols: Array<VolumeStructure>, private val comicName: String, private val groupNames: Array<String>?):Handler(looper) {
|
||||
constructor(looper: Looper, th: WeakReference<ComicDlFragment>, comicName: String) : this(looper, th, arrayOf(), comicName, null) {
|
||||
@@ -63,6 +66,7 @@ class ComicDlHandler(looper: Looper, private val th: WeakReference<ComicDlFragme
|
||||
var downloading = false
|
||||
private var urlArray = arrayOf<String>()
|
||||
private var uuidArray = arrayOf<String>()
|
||||
private val maxBatch = that?.activity?.let { PreferenceManager.getDefaultSharedPreferences(it) }?.getInt("settings_cat_md_sb_max_batch", 16)?:16
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun handleMessage(msg: Message) {
|
||||
@@ -265,12 +269,15 @@ class ComicDlHandler(looper: Looper, private val th: WeakReference<ComicDlFragme
|
||||
}
|
||||
|
||||
private suspend fun downloadChapterPages() = withContext(Dispatchers.IO) {
|
||||
val totalInDownload = AtomicInteger(0)
|
||||
tbtnlist.forEach { i ->
|
||||
if(i.isChecked) {
|
||||
withContext(Dispatchers.Main) {
|
||||
i.isEnabled = false
|
||||
}
|
||||
i.url?.let {
|
||||
while (totalInDownload.get() >= maxBatch) delay(1000)
|
||||
totalInDownload.incrementAndGet()
|
||||
launch {
|
||||
mangaDlTools.downloadChapterInVol(
|
||||
it,
|
||||
@@ -278,7 +285,7 @@ class ComicDlHandler(looper: Looper, private val th: WeakReference<ComicDlFragme
|
||||
i.caption?:"null",
|
||||
i.index
|
||||
)
|
||||
}
|
||||
}.invokeOnCompletion { totalInDownload.decrementAndGet() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
|
||||
-1 -> {
|
||||
homeF?.apply {
|
||||
swiperefresh?.isRefreshing = msg.obj as Boolean
|
||||
lifecycleScope.launch { if(msg.obj as Boolean) showKanban() else hideKanban() }
|
||||
if(msg.obj as Boolean) showKanban() else hideKanban()
|
||||
}
|
||||
}
|
||||
//0 -> setLayouts()
|
||||
@@ -281,8 +281,8 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
|
||||
withContext(Dispatchers.IO) {
|
||||
homeF?.showKanban()
|
||||
fhib?.isAutoPlay = false
|
||||
fhib?.adapter?.notifyDataSetChanged()
|
||||
index = null
|
||||
fhib?.adapter?.notifyDataSetChanged()
|
||||
fhib = null
|
||||
indexLines = arrayOf()
|
||||
this@HomeHandler.sendEmptyMessage(6) //removeAllViews
|
||||
|
||||
@@ -5,8 +5,10 @@ import android.util.Base64
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.fumiama.copymanga.json.ComandyCapsule
|
||||
import top.fumiama.copymanga.json.LoginInfoStructure
|
||||
import top.fumiama.copymanga.tools.api.CMApi
|
||||
import top.fumiama.copymanga.tools.http.Comandy
|
||||
import top.fumiama.copymanga.tools.http.DownloadTools
|
||||
import top.fumiama.dmzj.copymanga.R
|
||||
import java.net.URLEncoder
|
||||
@@ -16,29 +18,37 @@ class Member(private val pref: SharedPreferences, private val getString: (Int) -
|
||||
val hasLogin: Boolean get() = pref.getString("token", "")?.isNotEmpty()?:false
|
||||
suspend fun login(username: String, pwd: String, salt: Int): LoginInfoStructure = withContext(Dispatchers.IO) {
|
||||
var err = ""
|
||||
getLoginConnection(username, pwd, salt).apply {
|
||||
if (Comandy.useComandy) getComandyLoginConnection(username, pwd, salt).let { capsule ->
|
||||
try {
|
||||
Comandy.instance?.request(Gson().toJson(capsule))?.let { result ->
|
||||
Gson().fromJson(result, ComandyCapsule::class.java)!!.let {
|
||||
if (it.code != 200) {
|
||||
val l = LoginInfoStructure()
|
||||
l.code = it.code
|
||||
l.message = it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() }
|
||||
return@withContext l
|
||||
}
|
||||
Base64.decode(it.data, Base64.DEFAULT)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
err = e.message.toString()
|
||||
null
|
||||
}
|
||||
}?.let {
|
||||
try {
|
||||
saveInfo(it)
|
||||
} catch (e: Exception) {
|
||||
err = e.message.toString()
|
||||
}
|
||||
}
|
||||
else getLoginConnection(username, pwd, salt).apply {
|
||||
inputStream.use {
|
||||
it?.readBytes()?.let { data ->
|
||||
data.inputStream().use { dataIn ->
|
||||
try {
|
||||
Gson().fromJson<LoginInfoStructure>(
|
||||
dataIn.reader(), LoginInfoStructure::class.java
|
||||
)?.let { info ->
|
||||
if(info.code == 200) {
|
||||
pref.edit()?.apply {
|
||||
putString("token", info.results?.token)
|
||||
putString("user_id", info.results?.user_id)
|
||||
putString("username", info.results?.username)
|
||||
putString("nickname", info.results?.nickname)
|
||||
apply()
|
||||
return@withContext info()
|
||||
}
|
||||
}
|
||||
return@withContext info
|
||||
}?: run { err = getString(R.string.login_parse_json_error) }
|
||||
} catch (e: Exception) {
|
||||
err = data.decodeToString()
|
||||
}
|
||||
try {
|
||||
saveInfo(data)
|
||||
} catch (e: Exception) {
|
||||
err = e.message.toString()
|
||||
}
|
||||
}?: run { err = getString(R.string.login_get_conn_failed) }
|
||||
}
|
||||
@@ -101,6 +111,26 @@ class Member(private val pref: SharedPreferences, private val getString: (Int) -
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveInfo(data: ByteArray) = data.inputStream().use { dataIn ->
|
||||
try {
|
||||
Gson().fromJson(dataIn.reader(), LoginInfoStructure::class.java)?.let { l ->
|
||||
if(l.code == 200) {
|
||||
pref.edit()?.apply {
|
||||
putString("token", l.results?.token)
|
||||
putString("user_id", l.results?.user_id)
|
||||
putString("username", l.results?.username)
|
||||
putString("nickname", l.results?.nickname)
|
||||
apply()
|
||||
return@use info()
|
||||
}
|
||||
}
|
||||
return@use l
|
||||
}?: throw Exception(getString(R.string.login_parse_json_error))
|
||||
} catch (e: Exception) {
|
||||
throw Exception(data.decodeToString(), e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLoginConnection(username: String, pwd: String, salt: Int) =
|
||||
getString(R.string.loginApiUrl).format(CMApi.myHostApiUrl).let {
|
||||
CMApi.apiProxy?.wrap(it)?:it
|
||||
@@ -117,4 +147,20 @@ class Member(private val pref: SharedPreferences, private val getString: (Int) -
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getComandyLoginConnection(username: String, pwd: String, salt: Int) =
|
||||
getString(R.string.loginApiUrl).format(CMApi.myHostApiUrl).let {
|
||||
CMApi.apiProxy?.wrap(it)?:it
|
||||
}.let {
|
||||
DownloadTools.getComandyApiConnection(it, "POST").apply {
|
||||
pref.apply {
|
||||
headers["content-type"] = "application/x-www-form-urlencoded;charset=utf-8"
|
||||
headers["platform"] = "3"
|
||||
headers["accept"] = "application/json"
|
||||
val r = if(!getBoolean("settings_cat_net_sw_use_foreign", false)) "1" else "0"
|
||||
val pwdEncoded = Base64.encode("$pwd-$salt".toByteArray(), Base64.DEFAULT).decodeToString()
|
||||
data = "username=${URLEncoder.encode(username, Charset.defaultCharset().name())}&password=$pwdEncoded&salt=$salt&platform=3&authorization=Token+&version=1.4.4&source=copyApp®ion=$r&webp=1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
<string name="apiProxyApiUrl">https://copymanga.azurewebsites.net/api/api?code=%1$s&url=%2$s</string>
|
||||
<string name="imgResolutionKeyID">settings_cat_net_sb_image_resolution</string>
|
||||
|
||||
<string name="comandy_version_url">https://gitea.seku.su/api/v1/repos/fumiama/comandy/releases/tags/default</string>
|
||||
<string name="comandy_download_url">"https://gitea.seku.su/fumiama/comandy/releases/download/default/%1$s_libcomandy.so.gz"</string>
|
||||
|
||||
<string name="complete">已完结</string>
|
||||
|
||||
<string name="TRANSPORT_WIFI">WIFI</string>
|
||||
@@ -151,6 +154,8 @@
|
||||
<string name="settings_cat_net_sm_use_cellar">打开后不再在开始阅读时提示</string>
|
||||
<string name="settings_cat_net_sw_use_foreign">使用海外线路</string>
|
||||
<string name="settings_cat_net_sm_use_foreign">不管使用什么线路, API访问均是海外, 只有图片CDN可能会变化(也可能不变), 请酌情选择使用</string>
|
||||
<string name="settings_cat_net_sw_use_comandy">增强型数据访问</string>
|
||||
<string name="settings_cat_net_sm_use_comandy">使用经过优化的请求方法访问服务器</string>
|
||||
<string name="settings_cat_net_et_title_api_url">请求API网址</string>
|
||||
<string name="settings_cat_net_et_summary_api_url">一般无需更改,除非拷贝漫画官方更改网址,默认:&hosturl;</string>
|
||||
<string name="settings_cat_net_sw_use_api_proxy">使用API代理(重启生效)</string>
|
||||
@@ -175,6 +180,8 @@
|
||||
<string name="settings_cat_md">漫画下载</string>
|
||||
<string name="settings_cat_md_sw_show_0m_manga">显示未下载漫画</string>
|
||||
<string name="settings_cat_md_sm_show_0m_manga">打开后将在我的下载显示所有浏览过详情页的漫画,旧版下载永远全部显示</string>
|
||||
<string name="settings_cat_md_sb_max_batch">漫画下载并发限制</string>
|
||||
<string name="settings_cat_md_sm_max_batch">默认为16</string>
|
||||
|
||||
<string name="login_null_username">用户名为空</string>
|
||||
<string name="login_null_pwd">密码为空</string>
|
||||
|
||||
@@ -44,6 +44,12 @@
|
||||
<PreferenceCategory
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/settings_cat_net">
|
||||
<SwitchPreferenceCompat
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="settings_cat_net_sw_use_comandy"
|
||||
app:selectable="true"
|
||||
app:summary="@string/settings_cat_net_sm_use_comandy"
|
||||
app:title="@string/settings_cat_net_sw_use_comandy" />
|
||||
<ListPreference
|
||||
android:max="1500"
|
||||
app:iconSpaceReserved="false"
|
||||
@@ -146,5 +152,14 @@
|
||||
app:selectable="true"
|
||||
app:summary="@string/settings_cat_md_sm_show_0m_manga"
|
||||
app:title="@string/settings_cat_md_sw_show_0m_manga" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="16"
|
||||
android:max="32"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="settings_cat_md_sb_max_batch"
|
||||
app:min="1"
|
||||
app:showSeekBarValue="true"
|
||||
app:summary="@string/settings_cat_md_sm_max_batch"
|
||||
app:title="@string/settings_cat_md_sb_max_batch" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
||||
@@ -10,7 +10,7 @@ buildscript {
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.2.2'
|
||||
classpath 'com.android.tools.build:gradle:8.3.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
|
||||
|
||||
Reference in New Issue
Block a user