Skip to content

Commit 8a0e818

Browse files
committed
Move subscription info fetch into Go
1 parent 2b909e2 commit 8a0e818

6 files changed

Lines changed: 144 additions & 188 deletions

File tree

core/src/main/golang/native/config/fetch.go

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,58 +10,75 @@ import (
1010
"os"
1111
P "path"
1212
"runtime"
13+
"strconv"
14+
"strings"
1315
"time"
1416

1517
"cfa/native/app"
1618

19+
"github.com/metacubex/mihomo/adapter/provider"
1720
clashHttp "github.com/metacubex/mihomo/component/http"
1821
RB "github.com/metacubex/mihomo/rules/bundle"
1922
)
2023

2124
type Status struct {
22-
Action string `json:"action"`
23-
Args []string `json:"args"`
24-
Progress int `json:"progress"`
25-
MaxProgress int `json:"max"`
25+
Action string `json:"action"`
26+
Args []string `json:"args"`
27+
Progress int `json:"progress"`
28+
MaxProgress int `json:"max"`
29+
SubUpload *int64 `json:"subUpload,omitempty"`
30+
SubDownload *int64 `json:"subDownload,omitempty"`
31+
SubTotal *int64 `json:"subTotal,omitempty"`
32+
SubExpire *int64 `json:"subExpire,omitempty"`
33+
SubUpdateInterval *int64 `json:"subUpdateInterval,omitempty"`
2634
}
2735

28-
func openUrl(ctx context.Context, url string) (io.ReadCloser, error) {
36+
type fetchHeader struct {
37+
SubscriptionUserInfo string
38+
ProfileUpdateInterval string
39+
}
40+
41+
func openUrl(ctx context.Context, url string) (io.ReadCloser, fetchHeader, error) {
2942
response, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"ClashMetaForAndroid/" + app.VersionName()}}, nil)
3043

3144
if err != nil {
32-
return nil, err
45+
return nil, fetchHeader{}, err
3346
}
3447

35-
return response.Body, nil
48+
return response.Body, fetchHeader{
49+
SubscriptionUserInfo: response.Header.Get("subscription-userinfo"),
50+
ProfileUpdateInterval: response.Header.Get("profile-update-interval"),
51+
}, nil
3652
}
3753

3854
func openContent(url string) (io.ReadCloser, error) {
3955
return app.OpenContent(url)
4056
}
4157

42-
func fetch(url *U.URL, file string) error {
58+
func fetch(url *U.URL, file string) (fetchHeader, error) {
4359
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
4460
defer cancel()
4561

4662
var reader io.ReadCloser
63+
var header fetchHeader
4764
var err error
4865

4966
switch url.Scheme {
5067
case "http", "https":
51-
reader, err = openUrl(ctx, url.String())
68+
reader, header, err = openUrl(ctx, url.String())
5269
case "content":
5370
reader, err = openContent(url.String())
5471
default:
5572
err = fmt.Errorf("unsupported scheme %s of %s", url.Scheme, url)
5673
}
5774

5875
if err != nil {
59-
return err
76+
return fetchHeader{}, err
6077
}
6178

6279
defer reader.Close()
6380

64-
return writeFile(file, reader)
81+
return header, writeFile(file, reader)
6582
}
6683

6784
func writeFile(file string, reader io.Reader) error {
@@ -82,6 +99,55 @@ func writeFile(file string, reader io.Reader) error {
8299
return err
83100
}
84101

102+
func parseProfileUpdateInterval(value string) (int64, bool) {
103+
hours, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
104+
if err != nil {
105+
return 0, false
106+
}
107+
108+
if hours <= 0 {
109+
return 0, true
110+
}
111+
112+
interval := time.Duration(hours) * time.Hour
113+
if interval < 15*time.Minute {
114+
interval = 15 * time.Minute
115+
}
116+
117+
return int64(interval / time.Millisecond), true
118+
}
119+
120+
func reportSubscriptionInfo(header fetchHeader, reportStatus func(string)) {
121+
userinfo := header.SubscriptionUserInfo
122+
updateIntervalHeader := header.ProfileUpdateInterval
123+
if userinfo == "" && updateIntervalHeader == "" {
124+
return
125+
}
126+
127+
status := Status{
128+
Action: "SubscriptionInfo",
129+
Args: []string{},
130+
Progress: -1,
131+
MaxProgress: -1,
132+
}
133+
134+
if userinfo != "" {
135+
info := provider.NewSubscriptionInfo(userinfo)
136+
expire := info.Expire * 1000
137+
status.SubUpload = &info.Upload
138+
status.SubDownload = &info.Download
139+
status.SubTotal = &info.Total
140+
status.SubExpire = &expire
141+
}
142+
143+
if interval, ok := parseProfileUpdateInterval(updateIntervalHeader); ok {
144+
status.SubUpdateInterval = &interval
145+
}
146+
147+
bytes, _ := json.Marshal(&status)
148+
reportStatus(string(bytes))
149+
}
150+
85151
func FetchAndValid(
86152
path string,
87153
url string,
@@ -105,9 +171,12 @@ func FetchAndValid(
105171

106172
reportStatus(string(bytes))
107173

108-
if err := fetch(url, configPath); err != nil {
174+
header, err := fetch(url, configPath)
175+
if err != nil {
109176
return err
110177
}
178+
179+
reportSubscriptionInfo(header, reportStatus)
111180
}
112181

113182
defer runtime.GC()
@@ -166,7 +235,7 @@ func FetchAndValid(
166235
}
167236
}
168237

169-
_ = fetch(url, ps)
238+
_, _ = fetch(url, ps)
170239
})
171240

172241
bytes, _ := json.Marshal(&Status{

core/src/main/java/com/github/kr328/clash/core/model/FetchStatus.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ data class FetchStatus(
1010
val action: Action,
1111
val args: List<String>,
1212
val progress: Int,
13-
val max: Int
13+
val max: Int,
14+
val subUpload: Long? = null,
15+
val subDownload: Long? = null,
16+
val subTotal: Long? = null,
17+
val subExpire: Long? = null,
18+
val subUpdateInterval: Long? = null,
1419
) : Parcelable {
1520
enum class Action {
1621
FetchConfiguration,
1722
FetchProviders,
23+
SubscriptionInfo,
1824
Verifying,
1925
}
2026

@@ -35,4 +41,4 @@ data class FetchStatus(
3541
return arrayOfNulls(size)
3642
}
3743
}
38-
}
44+
}

design/src/main/java/com/github/kr328/clash/design/PropertiesDesign.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ class PropertiesDesign(context: Context) : Design<PropertiesDesign.Request>(cont
178178
max = status.max
179179
progress = status.progress
180180
}
181+
FetchStatus.Action.SubscriptionInfo -> Unit
181182
FetchStatus.Action.Verifying -> {
182183
text = context.getString(R.string.verifying)
183184
isIndeterminate = false
@@ -186,4 +187,4 @@ class PropertiesDesign(context: Context) : Design<PropertiesDesign.Request>(cont
186187
}
187188
}
188189
}
189-
}
190+
}

service/build.gradle.kts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ dependencies {
1919
implementation(libs.androidx.room.ktx)
2020
implementation(libs.kaidl.runtime)
2121
implementation(libs.rikkax.multiprocess)
22-
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))
23-
24-
// define any required OkHttp artifacts without version
25-
implementation("com.squareup.okhttp3:okhttp")
26-
implementation("com.squareup.okhttp3:logging-interceptor")
2722
}
2823

2924
afterEvaluate {
@@ -33,4 +28,4 @@ afterEvaluate {
3328
sourceSets[it.name].java.srcDir(buildDir.resolve("generated/ksp/${it.name}/java"))
3429
}
3530
}
36-
}
31+
}

service/src/main/java/com/github/kr328/clash/service/ProfileManager.kt

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package com.github.kr328.clash.service
22

33
import android.content.Context
4-
import com.github.kr328.clash.common.log.Log
54
import com.github.kr328.clash.service.data.Database
6-
import com.github.kr328.clash.service.data.Imported
75
import com.github.kr328.clash.service.data.ImportedDao
86
import com.github.kr328.clash.service.data.Pending
97
import com.github.kr328.clash.service.data.PendingDao
@@ -20,10 +18,7 @@ import kotlinx.coroutines.CoroutineScope
2018
import kotlinx.coroutines.Dispatchers
2119
import kotlinx.coroutines.launch
2220
import kotlinx.coroutines.withContext
23-
import okhttp3.OkHttpClient
24-
import okhttp3.Request
2521
import java.io.FileNotFoundException
26-
import java.math.BigDecimal
2722
import java.util.*
2823

2924
class ProfileManager(private val context: Context) : IProfileManager,
@@ -134,83 +129,6 @@ class ProfileManager(private val context: Context) : IProfileManager,
134129

135130
override suspend fun update(uuid: UUID) {
136131
scheduleUpdate(uuid, true)
137-
ImportedDao().queryByUUID(uuid)?.let {
138-
if (it.type == Profile.Type.Url && it.source.startsWith("https://",true)) {
139-
updateFlow(it)
140-
}
141-
}
142-
}
143-
144-
suspend fun updateFlow(old: Imported) {
145-
try {
146-
val client = OkHttpClient()
147-
val versionName = context.packageManager.getPackageInfo(context.packageName, 0).versionName
148-
val request = Request.Builder()
149-
.url(old.source)
150-
.header("User-Agent", "ClashMetaForAndroid/$versionName")
151-
.build()
152-
153-
client.newCall(request).execute().use { response ->
154-
if (!response.isSuccessful || response.headers["subscription-userinfo"] == null) return
155-
156-
var upload: Long = 0
157-
var download: Long = 0
158-
var total: Long = 0
159-
var expire: Long = 0
160-
161-
val userinfo = response.headers["subscription-userinfo"]
162-
if (response.isSuccessful && userinfo != null) {
163-
164-
val flags = userinfo.split(";")
165-
for (flag in flags) {
166-
val info = flag.split("=")
167-
when {
168-
info[0].contains("upload") && info[1].isNotEmpty() -> upload =
169-
BigDecimal(info[1].split('.').first()).longValueExact()
170-
171-
info[0].contains("download") && info[1].isNotEmpty() -> download =
172-
BigDecimal(info[1].split('.').first()).longValueExact()
173-
174-
info[0].contains("total") && info[1].isNotEmpty() -> total =
175-
BigDecimal(info[1].split('.').first()).longValueExact()
176-
177-
info[0].contains("expire") && info[1].isNotEmpty() -> {
178-
if (info[1].isNotEmpty()) {
179-
expire = (info[1].toDouble()*1000).toLong()
180-
}
181-
}
182-
}
183-
}
184-
}
185-
186-
val new = Imported(
187-
old.uuid,
188-
old.name,
189-
old.type,
190-
old.source,
191-
old.interval,
192-
upload,
193-
download,
194-
total,
195-
expire,
196-
old?.createdAt ?: System.currentTimeMillis(),
197-
ageSecretKey = old.ageSecretKey
198-
)
199-
200-
if (old != null) {
201-
ImportedDao().update(new)
202-
} else {
203-
ImportedDao().insert(new)
204-
}
205-
206-
PendingDao().remove(new.uuid)
207-
context.sendProfileChanged(new.uuid)
208-
// println(response.body!!.string())
209-
}
210-
211-
} catch (e: Exception) {
212-
Log.w("Report fetch subscription-userinfo status: $e", e)
213-
}
214132
}
215133

216134
override suspend fun commit(uuid: UUID, callback: IFetchObserver?) {

0 commit comments

Comments
 (0)