Skip to content

Commit 35c7149

Browse files
author
heyanlin
committed
番源2.0 80%
1 parent 46f8d1a commit 35c7149

File tree

5 files changed

+267
-0
lines changed

5 files changed

+267
-0
lines changed

app/src/main/java/com/heyanle/easybangumi4/base/json/JsonFileProvider.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,12 @@ class JsonFileProvider {
5959
type = typeOf<JsExtensionProviderV2.IndexItem>().javaType
6060
)
6161

62+
// /storage/emulated/0/Android/data/com.heyanle.easybangumi4/files/extension_v2/repository.jsonl
63+
val extensionRepository: JsonlFileHelper<String> = JsonlFileHelper(
64+
folder = UniFile.fromFile(File(APP.getFilePath("extension_v2")))!!,
65+
name = "repository.jsonl",
66+
scope = scope,
67+
type = typeOf<String>().javaType
68+
)
69+
6270
}

app/src/main/java/com/heyanle/easybangumi4/case/ExtensionCase.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.heyanle.easybangumi4.case
22

33

4+
import com.heyanle.easybangumi4.base.DataResult
45
import com.heyanle.easybangumi4.plugin.extension.ExtensionController
56
import com.heyanle.easybangumi4.plugin.extension.ExtensionInfo
67
import com.heyanle.easybangumi4.plugin.extension.IExtensionController
@@ -9,6 +10,7 @@ import kotlinx.coroutines.flow.StateFlow
910
import kotlinx.coroutines.flow.filter
1011
import kotlinx.coroutines.flow.first
1112
import kotlinx.coroutines.flow.map
13+
import java.io.File
1214

1315
/**
1416
* Created by heyanlin on 2023/10/25.
@@ -34,4 +36,8 @@ class ExtensionCase(
3436
suspend fun awaitExtension(): Collection<ExtensionInfo> {
3537
return flowExtension().first()
3638
}
39+
40+
suspend fun appendOrUpdate(
41+
file: File,
42+
): DataResult<Unit> {}
3743
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.heyanle.easybangumi4.plugin.extension.remote
2+
3+
import com.heyanle.easybangumi4.base.DataResult
4+
import com.heyanle.easybangumi4.base.json.JsonFileProvider
5+
import com.heyanle.easybangumi4.utils.downloadTo
6+
import com.heyanle.easybangumi4.utils.jsonTo
7+
import kotlinx.coroutines.CoroutineScope
8+
import kotlinx.coroutines.Deferred
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.SupervisorJob
11+
import kotlinx.coroutines.async
12+
import kotlinx.coroutines.awaitAll
13+
import kotlinx.coroutines.flow.MutableStateFlow
14+
import kotlinx.coroutines.flow.SharingStarted
15+
import kotlinx.coroutines.flow.asStateFlow
16+
import kotlinx.coroutines.flow.collectLatest
17+
import kotlinx.coroutines.flow.map
18+
import kotlinx.coroutines.flow.stateIn
19+
import kotlinx.coroutines.flow.update
20+
import kotlinx.coroutines.launch
21+
import java.io.File
22+
import java.util.concurrent.ConcurrentHashMap
23+
import kotlin.collections.map
24+
25+
/**
26+
* Created by heyanlin on 2025/8/14.
27+
*/
28+
class ExtensionRemoteController(
29+
jsonFileProvider: JsonFileProvider,
30+
private val cachePath: String,
31+
) {
32+
33+
private val dispatcher = Dispatchers.IO
34+
private val scope = CoroutineScope(SupervisorJob() + dispatcher)
35+
36+
private val repository = jsonFileProvider.extensionRepository
37+
38+
data class RemoteInfoState(
39+
val loading: Boolean = true,
40+
// repository: remoteInfo
41+
val remoteInfo: Map<String, RemoteInfo> = emptyMap()
42+
)
43+
44+
private val _remote = MutableStateFlow(RemoteInfoState())
45+
val remote = _remote.asStateFlow()
46+
47+
48+
val repositoryState = repository.flow.map {
49+
it.okOrNull()?:emptyList()
50+
}.stateIn(scope, SharingStarted.Lazily, emptyList())
51+
52+
private val remoteInfoTemp = ConcurrentHashMap<String, List<RemoteInfo>>()
53+
54+
fun refresh() {
55+
remoteInfoTemp.clear()
56+
scope.launch {
57+
load(repositoryState.value)
58+
}
59+
}
60+
61+
init {
62+
scope.launch {
63+
repositoryState.collectLatest {
64+
load(it)
65+
}
66+
}
67+
}
68+
69+
fun updateRepository(list: List<String>) {
70+
repository.set(list)
71+
}
72+
73+
private suspend fun load(repository: List<String>) {
74+
val map = hashMapOf<String, RemoteInfo>()
75+
val res = repository.map {
76+
getRemote(it)
77+
}.awaitAll()
78+
res.reversed().forEach {
79+
it.forEach { remoteInfo ->
80+
map[remoteInfo.key] = remoteInfo
81+
}
82+
}
83+
_remote.update {
84+
RemoteInfoState(
85+
loading = false,
86+
remoteInfo = map
87+
)
88+
}
89+
}
90+
91+
private suspend fun getRemote(repository: String): Deferred<List<RemoteInfo>> {
92+
return scope.async {
93+
val temp = remoteInfoTemp[repository]
94+
if (temp != null) {
95+
return@async temp
96+
}
97+
val jsonlFile = File(cachePath, "remote_extension/${System.currentTimeMillis()}.jsonl")
98+
repository.downloadTo(jsonlFile.absolutePath)
99+
if (jsonlFile.exists()) {
100+
val res = arrayListOf<RemoteInfo>()
101+
jsonlFile.bufferedReader().use {
102+
var line = it.readLine()
103+
while (!line.isNullOrEmpty()) {
104+
val remote = line.jsonTo<RemoteInfo>()
105+
if (remote != null) {
106+
res.add(remote)
107+
}
108+
line = it.readLine()
109+
}
110+
}
111+
return@async res
112+
} else {
113+
return@async emptyList()
114+
}
115+
}
116+
}
117+
118+
119+
120+
121+
122+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.heyanle.easybangumi4.plugin.extension.remote
2+
3+
import com.heyanle.easybangumi4.base.DataResult
4+
import com.heyanle.easybangumi4.case.ExtensionCase
5+
import com.heyanle.easybangumi4.plugin.extension.provider.JsExtensionProvider
6+
import com.heyanle.easybangumi4.plugin.extension.provider.JsExtensionProviderV2
7+
import com.heyanle.easybangumi4.plugin.js.extension.JSExtensionCryLoader
8+
import com.heyanle.easybangumi4.utils.downloadTo
9+
import io.ktor.utils.io.errors.IOException
10+
import kotlinx.coroutines.CoroutineScope
11+
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.SupervisorJob
13+
import kotlinx.coroutines.async
14+
import kotlinx.coroutines.flow.MutableStateFlow
15+
import kotlinx.coroutines.flow.asStateFlow
16+
import kotlinx.coroutines.flow.collect
17+
import kotlinx.coroutines.flow.combine
18+
import kotlinx.coroutines.flow.update
19+
import kotlinx.coroutines.launch
20+
import java.io.File
21+
22+
/**
23+
* Created by heyanlin on 2025/8/14.
24+
*/
25+
class ExtensionRepoController(
26+
private val extensionCase: ExtensionCase,
27+
private val remoteController: ExtensionRemoteController,
28+
private val cache: String
29+
) {
30+
31+
32+
private val dispatcher = Dispatchers.IO
33+
private val scope = CoroutineScope(SupervisorJob() + dispatcher)
34+
35+
36+
data class State(
37+
val loading: Boolean = true,
38+
val remoteLocalInfo: Map<String, ExtensionRemoteLocalInfo> = emptyMap(),
39+
)
40+
private val _state = MutableStateFlow<State> (State())
41+
val state = _state.asStateFlow()
42+
43+
init {
44+
scope.launch {
45+
combine(
46+
extensionCase.flowExtensionState(),
47+
remoteController.remote
48+
) { local, remote ->
49+
if (local.loading) {
50+
_state.update {
51+
it.copy(
52+
loading = true
53+
)
54+
}
55+
} else {
56+
val map = mutableMapOf<String, ExtensionRemoteLocalInfo>()
57+
val keyList = local.extensionInfoMap.keys + remote.remoteInfo.keys
58+
keyList.forEach {
59+
val remote = remote.remoteInfo[it]
60+
val local = local.extensionInfoMap[it]
61+
val remoteLocalInfo = ExtensionRemoteLocalInfo(
62+
remoteInfo = remote,
63+
localInfo = local
64+
)
65+
map[it] = remoteLocalInfo
66+
}
67+
_state.update {
68+
it.copy(
69+
loading = false,
70+
remoteLocalInfo = map
71+
)
72+
}
73+
74+
}
75+
}.collect()
76+
}
77+
}
78+
79+
suspend fun appendOrUpdate(
80+
remoteInfo: RemoteInfo
81+
): DataResult<Unit> {
82+
return scope.async {
83+
val url = remoteInfo.url
84+
val file = File(cache, remoteInfo.key)
85+
url.downloadTo(file.absolutePath)
86+
if (!file.exists()) {
87+
return@async DataResult.Error<Unit>(
88+
"下载失败",
89+
throwable = IOException("downloadError")
90+
)
91+
}
92+
val buffer = ByteArray(JSExtensionCryLoader.FIRST_LINE_MARK.size)
93+
val size = file.inputStream().use {
94+
it.read(buffer)
95+
}
96+
val isCry = size == JSExtensionCryLoader.FIRST_LINE_MARK.size && buffer.contentEquals(JSExtensionCryLoader.FIRST_LINE_MARK)
97+
val targetFile = if (isCry) {
98+
File(cache, remoteInfo.key + ".${JsExtensionProviderV2.EXTENSION_CRY_SUFFIX}")
99+
} else {
100+
File(cache, remoteInfo.key + ".${JsExtensionProviderV2.EXTENSION_SUFFIX}")
101+
}
102+
file.renameTo(targetFile)
103+
return@async extensionCase.appendOrUpdate(targetFile)
104+
}.await()
105+
}
106+
107+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.heyanle.easybangumi4.plugin.extension.remote
2+
3+
import com.heyanle.easybangumi4.plugin.extension.ExtensionInfo
4+
5+
/**
6+
* Created by heyanlin on 2025/8/14.
7+
*/
8+
data class RemoteInfo (
9+
val key: String = "",
10+
val icon: String = "",
11+
val url: String = "",
12+
val label: String = "",
13+
val versionCode: Int = -1,
14+
val versionName: String = "",
15+
)
16+
17+
data class ExtensionRemoteLocalInfo(
18+
val remoteInfo: RemoteInfo? = null,
19+
val localInfo: ExtensionInfo? = null,
20+
) {
21+
val onlyRemote = remoteInfo != null && localInfo == null
22+
val onlyLocal = remoteInfo == null && localInfo != null
23+
val hasUpdate: Boolean = remoteInfo != null && localInfo != null && remoteInfo.versionCode > localInfo.versionCode
24+
}

0 commit comments

Comments
 (0)