Skip to content

Commit 46f8d1a

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

File tree

20 files changed

+748
-42
lines changed

20 files changed

+748
-42
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package com.heyanle.easybangumi4.base.json
44
import com.heyanle.easybangumi4.APP
55
import com.heyanle.easybangumi4.cartoon.entity.CartoonDownloadReq
66
import com.heyanle.easybangumi4.cartoon.entity.CartoonTag
7+
import com.heyanle.easybangumi4.plugin.extension.provider.JsExtensionProviderV2
78
import com.heyanle.easybangumi4.plugin.source.SourceConfig
89
import com.heyanle.easybangumi4.utils.CoroutineProvider
910
import com.heyanle.easybangumi4.utils.getFilePath
@@ -50,4 +51,12 @@ class JsonFileProvider {
5051
type = typeOf<List<CartoonDownloadReq>>().javaType
5152
)
5253

54+
// /storage/emulated/0/Android/data/com.heyanle.easybangumi4/files/extension_v2/index.jsonl
55+
val extensionIndex: JsonlFileHelper<JsExtensionProviderV2.IndexItem> = JsonlFileHelper(
56+
folder = UniFile.fromFile(File(APP.getFilePath("extension_v2")))!!,
57+
name = "index.jsonl",
58+
scope = scope,
59+
type = typeOf<JsExtensionProviderV2.IndexItem>().javaType
60+
)
61+
5362
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package com.heyanle.easybangumi4.base.json
2+
3+
import com.heyanle.easybangumi4.base.DataResult
4+
import com.heyanle.easybangumi4.utils.jsonTo
5+
import com.heyanle.easybangumi4.utils.toJson
6+
import com.hippo.unifile.UniFile
7+
import kotlinx.coroutines.CoroutineScope
8+
import kotlinx.coroutines.flow.MutableStateFlow
9+
import kotlinx.coroutines.flow.asStateFlow
10+
import kotlinx.coroutines.flow.filterIsInstance
11+
import kotlinx.coroutines.flow.map
12+
import kotlinx.coroutines.flow.update
13+
import kotlinx.coroutines.launch
14+
import java.lang.reflect.Type
15+
16+
/**
17+
* https://github.com/easybangumiorg/EasyBangumi
18+
*
19+
* Copyright 2025 easybangumi.org and contributors
20+
*
21+
* Licensed under the Apache License, Version 2.0 (the "License");
22+
* you may not use this file except in compliance with the License.
23+
* You may obtain a copy of the License at
24+
*
25+
* http://www.apache.org/licenses/LICENSE-2.0
26+
*/
27+
class JsonlFileHelper <T : Any>(
28+
val folder: UniFile,
29+
val name: String,
30+
val scope: CoroutineScope,
31+
val type: Type,
32+
) {
33+
34+
35+
private val _flow = MutableStateFlow<DataResult<List<T>>>(DataResult.Loading())
36+
val flow = _flow.asStateFlow()
37+
val requestFlow = flow.filterIsInstance<DataResult.Ok<List<T>>>().map { it.data }
38+
39+
private val tempFileName = "${name}.temp"
40+
41+
init {
42+
scope.launch {
43+
val jsonFile = folder.createFile(name)
44+
if (jsonFile == null || !jsonFile.canRead()){
45+
_flow.update {
46+
DataResult.error("json file create failed or can't read")
47+
}
48+
return@launch
49+
}
50+
var data = jsonFile.openInputStream().use {
51+
it.bufferedReader().lineSequence()
52+
}.map {
53+
it.jsonTo<T>(type)
54+
}.filterNotNull().toList()
55+
// if (data == null){
56+
// val tempFile = folder.findFile(tempFileName)
57+
// data = if (tempFile != null && tempFile.canRead()){
58+
// val tempString = tempFile.openInputStream().use {
59+
// it.bufferedReader().readText()
60+
// }
61+
// tempString.jsonTo<T>(type) ?: def
62+
// } else {
63+
// def
64+
// }
65+
// }
66+
_flow.update {
67+
DataResult.ok(data)
68+
}
69+
}
70+
}
71+
72+
fun trySave (){
73+
scope.launch {
74+
val data = flow.value.okOrNull() ?: return@launch
75+
var tempFile = folder.findFile(tempFileName)
76+
var needWriteTemp = true
77+
if (tempFile != null) {
78+
needWriteTemp = tempFile.delete()
79+
}
80+
if (needWriteTemp) {
81+
tempFile = folder.createFile(tempFileName)
82+
if (tempFile == null){
83+
needWriteTemp = false
84+
} else {
85+
tempFile.openOutputStream().bufferedWriter().use {
86+
data.forEach { t ->
87+
val line = t.toJson(type)
88+
if (line != null) {
89+
it.write(line)
90+
it.newLine()
91+
}
92+
}
93+
}
94+
}
95+
}
96+
folder.findFile(name)?.delete()
97+
if (!needWriteTemp || tempFile == null){
98+
val jsonFile = folder.createFile(name)
99+
if (jsonFile != null && jsonFile.canWrite()) {
100+
jsonFile.openOutputStream().bufferedWriter().use {
101+
data.forEach { t ->
102+
val line = t.toJson(type)
103+
if (line != null) {
104+
it.write(line)
105+
it.newLine()
106+
}
107+
}
108+
}
109+
}
110+
} else {
111+
tempFile.renameTo(name)
112+
}
113+
}
114+
}
115+
116+
fun set(data: List<T>){
117+
_flow.update {
118+
DataResult.ok(data)
119+
}
120+
trySave()
121+
}
122+
123+
fun getOrDef(): List<T> {
124+
return flow.value.okOrNull() ?: emptyList()
125+
}
126+
fun getOrNull(): List<T>? {
127+
return flow.value.okOrNull()
128+
}
129+
130+
fun update(
131+
block:(List<T>) -> List<T>
132+
){
133+
val data = getOrDef()
134+
set( block(data))
135+
}
136+
137+
138+
139+
}

app/src/main/java/com/heyanle/easybangumi4/cartoon/story/local/source/LocalSourceComponent.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import com.heyanle.easybangumi4.plugin.source.SourceException
88
import com.heyanle.easybangumi4.source_api.SourceResult
99
import com.heyanle.easybangumi4.source_api.component.ComponentWrapper
1010
import com.heyanle.easybangumi4.source_api.component.detailed.DetailedComponent
11+
import com.heyanle.easybangumi4.source_api.component.page.PageComponent
12+
import com.heyanle.easybangumi4.source_api.component.page.SourcePage
1113
import com.heyanle.easybangumi4.source_api.component.play.PlayComponent
1214
import com.heyanle.easybangumi4.source_api.component.search.SearchComponent
1315
import com.heyanle.easybangumi4.source_api.entity.Cartoon
@@ -28,7 +30,7 @@ import kotlinx.coroutines.flow.map
2830
* Created by heyanle on 2024/7/7.
2931
* https://github.com/heyanLE
3032
*/
31-
class LocalSourceComponent : ComponentWrapper(), PlayComponent, DetailedComponent {
33+
class LocalSourceComponent : ComponentWrapper(), PlayComponent, DetailedComponent, PageComponent {
3234

3335
val cartoonStoryController: CartoonStoryController by Inject.injectLazy()
3436

@@ -158,4 +160,27 @@ class LocalSourceComponent : ComponentWrapper(), PlayComponent, DetailedComponen
158160
}
159161
}
160162

163+
override fun getPages(): List<SourcePage> {
164+
return PageComponent.NonLabelSinglePage(
165+
SourcePage.SingleCartoonPage.WithCover(
166+
label = "",
167+
firstKey = {1},
168+
load = {
169+
val result = cartoonStoryController.storyItemList.firstOrNull { it !is DataResult.Loading }
170+
if (result is DataResult.Ok) {
171+
return@WithCover SourceResult.Complete(null to result.data.map {
172+
it.cartoonLocalItem.cartoonCover
173+
})
174+
175+
} else if (result is DataResult.Error) {
176+
return@WithCover SourceResult.Error<Pair<Int?, List<CartoonCover>>>(result.throwable ?: SourceException(result.errorMsg))
177+
} else {
178+
throw IllegalStateException("unexpected result type: $result")
179+
}
180+
181+
}
182+
)
183+
)
184+
}
185+
161186
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.heyanle.easybangumi4.case
33

44
import com.heyanle.easybangumi4.plugin.extension.ExtensionController
55
import com.heyanle.easybangumi4.plugin.extension.ExtensionInfo
6+
import com.heyanle.easybangumi4.plugin.extension.IExtensionController
67
import kotlinx.coroutines.flow.Flow
78
import kotlinx.coroutines.flow.StateFlow
89
import kotlinx.coroutines.flow.filter
@@ -16,7 +17,7 @@ class ExtensionCase(
1617
private val extensionController: ExtensionController
1718
) {
1819

19-
fun flowExtensionState(): StateFlow<ExtensionController.ExtensionState> {
20+
fun flowExtensionState(): StateFlow<IExtensionController.ExtensionState> {
2021
return extensionController.state
2122
}
2223

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

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

3+
import com.heyanle.easybangumi4.plugin.source.ISourceController
34
import com.heyanle.easybangumi4.plugin.source.SourceController
45
import com.heyanle.easybangumi4.plugin.source.bundle.SourceBundle
56
import kotlinx.coroutines.flow.Flow
@@ -23,7 +24,7 @@ class SourceStateCase(
2324
return sourceController.sourceBundle.filterIsInstance()
2425
}
2526

26-
fun flowState(): StateFlow<SourceController.SourceInfoState> {
27+
fun flowState(): StateFlow<ISourceController.SourceInfoState> {
2728
return sourceController.sourceInfo
2829
}
2930

app/src/main/java/com/heyanle/easybangumi4/plugin/extension/ExtensionController.kt

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class ExtensionController(
3535
val jsExtensionFolder: String,
3636
private val cacheFolder: String,
3737
//private val extensionLoader: ExtensionLoader
38-
) {
38+
): IExtensionController {
3939

4040
companion object {
4141
private const val TAG = "ExtensionController"
@@ -46,14 +46,11 @@ class ExtensionController(
4646
private val scope = CoroutineScope(SupervisorJob() + dispatcher)
4747

4848

49-
data class ExtensionState(
50-
val loading: Boolean = true,
51-
val extensionInfoMap: Map<String, ExtensionInfo> = emptyMap()
52-
)
53-
private val _state = MutableStateFlow<ExtensionState>(
54-
ExtensionState()
49+
50+
private val _state = MutableStateFlow<IExtensionController.ExtensionState>(
51+
IExtensionController.ExtensionState()
5552
)
56-
val state = _state.asStateFlow()
53+
override val state = _state.asStateFlow()
5754

5855
private var firstLoad = true
5956

@@ -106,7 +103,7 @@ class ExtensionController(
106103
// 首次必须所有 Provider 都加载完才算加载完
107104
if (firstLoad &&
108105
(installedAppExtensionProviderState.loading || fileApkExtensionProviderState.loading || fileJsExtensionProviderState.loading)) {
109-
return@combine ExtensionState(
106+
return@combine IExtensionController.ExtensionState(
110107
loading = true,
111108
extensionInfoMap = emptyMap()
112109
)
@@ -122,7 +119,7 @@ class ExtensionController(
122119
fileJsExtensionProviderState.extensionMap.forEach {
123120
map[it.key] = it.value
124121
}
125-
ExtensionState(
122+
IExtensionController.ExtensionState(
126123
loading = installedAppExtensionProviderState.loading && fileApkExtensionProviderState.loading && fileJsExtensionProviderState.loading,
127124
extensionInfoMap = map
128125
)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.heyanle.easybangumi4.plugin.extension
2+
3+
import android.content.Context
4+
import com.heyanle.easybangumi4.base.json.JsonFileProvider
5+
import com.heyanle.easybangumi4.crash.SourceCrashController
6+
import com.heyanle.easybangumi4.plugin.extension.provider.JsExtensionProviderV2
7+
import com.heyanle.easybangumi4.plugin.js.runtime.JSRuntimeProvider
8+
import kotlinx.coroutines.CoroutineScope
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.SupervisorJob
11+
import kotlinx.coroutines.flow.MutableStateFlow
12+
import kotlinx.coroutines.flow.asStateFlow
13+
import kotlinx.coroutines.flow.collectLatest
14+
import kotlinx.coroutines.flow.update
15+
import kotlinx.coroutines.launch
16+
17+
/**
18+
* https://github.com/easybangumiorg/EasyBangumi
19+
*
20+
* Copyright 2025 easybangumi.org and contributors
21+
*
22+
* Licensed under the Apache License, Version 2.0 (the "License");
23+
* you may not use this file except in compliance with the License.
24+
* You may obtain a copy of the License at
25+
*
26+
* http://www.apache.org/licenses/LICENSE-2.0
27+
*
28+
* 1. 只保留 js 源
29+
* 2. 去除 file observer 相关的一系列逻辑,改为纯监听 index 文件列表
30+
*/
31+
class ExtensionControllerV2(
32+
private val context: Context,
33+
val jsExtensionFolder: String,
34+
private val cacheFolder: String,
35+
private val jsonFileProvider: JsonFileProvider,
36+
): IExtensionController {
37+
38+
companion object {
39+
private const val TAG = "ExtensionControllerV2"
40+
}
41+
42+
private val dispatcher = Dispatchers.IO
43+
private val scope = CoroutineScope(SupervisorJob() + dispatcher)
44+
45+
private val _state = MutableStateFlow<IExtensionController.ExtensionState>(
46+
IExtensionController.ExtensionState()
47+
)
48+
override val state = _state.asStateFlow()
49+
50+
private val jsRuntimeProvider = JSRuntimeProvider(2)
51+
private val jsExtensionProviderV2: JsExtensionProviderV2 =
52+
JsExtensionProviderV2(
53+
jsonFileProvider,
54+
jsExtensionFolder,
55+
dispatcher,
56+
jsRuntimeProvider,
57+
)
58+
59+
fun init() {
60+
SourceCrashController.onExtensionStart()
61+
jsExtensionProviderV2.init()
62+
SourceCrashController.onExtensionEnd()
63+
scope.launch {
64+
jsExtensionProviderV2.flow.collectLatest { state ->
65+
_state.update {
66+
it.copy(
67+
loading = state.loading,
68+
extensionInfoMap = state.extensionMap
69+
)
70+
}
71+
}
72+
}
73+
}
74+
75+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.heyanle.easybangumi4.plugin.extension
2+
3+
import kotlinx.coroutines.flow.MutableStateFlow
4+
import kotlinx.coroutines.flow.StateFlow
5+
import kotlinx.coroutines.flow.asStateFlow
6+
7+
/**
8+
* https://github.com/easybangumiorg/EasyBangumi
9+
*
10+
* Copyright 2025 easybangumi.org and contributors
11+
*
12+
* Licensed under the Apache License, Version 2.0 (the "License");
13+
* you may not use this file except in compliance with the License.
14+
* You may obtain a copy of the License at
15+
*
16+
* http://www.apache.org/licenses/LICENSE-2.0
17+
*/
18+
interface IExtensionController {
19+
20+
data class ExtensionState(
21+
val loading: Boolean = true,
22+
val extensionInfoMap: Map<String, ExtensionInfo> = emptyMap()
23+
)
24+
25+
val state: StateFlow<ExtensionState>
26+
}

0 commit comments

Comments
 (0)