Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 80 additions & 13 deletions core/src/main/golang/native/config/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,58 +10,75 @@ import (
"os"
P "path"
"runtime"
"strconv"
"strings"
"time"

"cfa/native/app"

"github.com/metacubex/mihomo/adapter/provider"
clashHttp "github.com/metacubex/mihomo/component/http"
RB "github.com/metacubex/mihomo/rules/bundle"
)

type Status struct {
Action string `json:"action"`
Args []string `json:"args"`
Progress int `json:"progress"`
MaxProgress int `json:"max"`
Action string `json:"action"`
Args []string `json:"args"`
Progress int `json:"progress"`
MaxProgress int `json:"max"`
SubUpload *int64 `json:"subUpload,omitempty"`
SubDownload *int64 `json:"subDownload,omitempty"`
SubTotal *int64 `json:"subTotal,omitempty"`
SubExpire *int64 `json:"subExpire,omitempty"`
SubUpdateInterval *int64 `json:"subUpdateInterval,omitempty"`
}

func openUrl(ctx context.Context, url string) (io.ReadCloser, error) {
type fetchHeader struct {
SubscriptionUserInfo string
ProfileUpdateInterval string
}

func openUrl(ctx context.Context, url string) (io.ReadCloser, fetchHeader, error) {
response, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"Tabby/" + app.VersionName()}}, nil)

if err != nil {
return nil, err
return nil, fetchHeader{}, err
}

return response.Body, nil
return response.Body, fetchHeader{
SubscriptionUserInfo: response.Header.Get("subscription-userinfo"),
ProfileUpdateInterval: response.Header.Get("profile-update-interval"),
}, nil
}

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

func fetch(url *U.URL, file string) error {
func fetch(url *U.URL, file string) (fetchHeader, error) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

var reader io.ReadCloser
var header fetchHeader
var err error

switch url.Scheme {
case "http", "https":
reader, err = openUrl(ctx, url.String())
reader, header, err = openUrl(ctx, url.String())
case "content":
reader, err = openContent(url.String())
default:
err = fmt.Errorf("unsupported scheme %s of %s", url.Scheme, url)
}

if err != nil {
return err
return fetchHeader{}, err
}

defer reader.Close()

return writeFile(file, reader)
return header, writeFile(file, reader)
}

func writeFile(file string, reader io.Reader) error {
Expand All @@ -82,6 +99,53 @@ func writeFile(file string, reader io.Reader) error {
return err
}

// parseProfileUpdateInterval parses the profile update interval (in hours) from the header.
func parseProfileUpdateInterval(value string) (int64, bool) {
hours, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
if err != nil {
return 0, false
}

if hours <= 0 {
return 0, true
}

interval := time.Duration(hours) * time.Hour

return int64(interval / time.Millisecond), true
}

func reportSubscriptionInfo(header fetchHeader, reportStatus func(string)) {
userinfo := header.SubscriptionUserInfo
updateIntervalHeader := header.ProfileUpdateInterval
if userinfo == "" && updateIntervalHeader == "" {
return
}

status := Status{
Action: "SubscriptionInfo",
Args: []string{},
Progress: -1,
MaxProgress: -1,
}

if userinfo != "" {
info := provider.NewSubscriptionInfo(userinfo)
expire := info.Expire * 1000
status.SubUpload = &info.Upload
status.SubDownload = &info.Download
status.SubTotal = &info.Total
status.SubExpire = &expire
}

if interval, ok := parseProfileUpdateInterval(updateIntervalHeader); ok {
status.SubUpdateInterval = &interval
}

bytes, _ := json.Marshal(&status)
reportStatus(string(bytes))
}

func FetchAndValid(
path string,
url string,
Expand All @@ -105,9 +169,12 @@ func FetchAndValid(

reportStatus(string(bytes))

if err := fetch(url, configPath); err != nil {
header, err := fetch(url, configPath)
if err != nil {
return err
}

reportSubscriptionInfo(header, reportStatus)
}

defer runtime.GC()
Expand Down Expand Up @@ -166,7 +233,7 @@ func FetchAndValid(
}
}

_ = fetch(url, ps)
_, _ = fetch(url, ps)
})

bytes, _ := json.Marshal(&Status{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ data class FetchStatus(
val args: List<String>,
val progress: Int,
val max: Int,
val subUpload: Long = 0L,
val subDownload: Long = 0L,
val subTotal: Long = 0L,
val subExpire: Long = 0L,
val subUpdateInterval: Long = 0L,
) : Parcelable {
enum class Action {
FetchConfiguration,
FetchProviders,
SubscriptionInfo,
Verifying,
}
}
2 changes: 0 additions & 2 deletions service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.kaidl.runtime)
implementation(libs.rikkax.multiprocess)
implementation(libs.okhttp.client)
implementation(libs.okhttp.interceptor)

ksp(libs.kaidl.compiler)
ksp(libs.androidx.room.compiler)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.github.kr328.clash.service

import android.content.Context
import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.service.data.Database
import com.github.kr328.clash.service.data.Imported
import com.github.kr328.clash.service.data.ImportedDao
import com.github.kr328.clash.service.data.Pending
import com.github.kr328.clash.service.data.PendingDao
Expand All @@ -12,11 +10,9 @@ import com.github.kr328.clash.service.remote.IFetchObserver
import com.github.kr328.clash.service.remote.IProfileManager
import com.github.kr328.clash.service.store.ServiceStore
import com.github.kr328.clash.service.util.directoryLastModified
import com.github.kr328.clash.service.util.fetchSubscriptionUserInfo
import com.github.kr328.clash.service.util.generateProfileUUID
import com.github.kr328.clash.service.util.importedDir
import com.github.kr328.clash.service.util.pendingDir
import com.github.kr328.clash.service.util.sendProfileChanged
import java.io.FileNotFoundException
import kotlin.uuid.Uuid
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -146,38 +142,6 @@ class ProfileManager(private val context: Context) :

override suspend fun update(uuid: Uuid) {
scheduleUpdate(uuid, true)
ImportedDao().queryByUUID(uuid)?.let {
if (it.type == Profile.Type.Url && it.source.startsWith("https://", true)) {
updateFlow(it)
}
}
}

suspend fun updateFlow(old: Imported) {
try {
val userInfo = context.fetchSubscriptionUserInfo(old.source) ?: return
val new =
Imported(
old.uuid,
old.name,
old.type,
old.source,
old.interval,
userInfo.upload,
userInfo.download,
userInfo.total,
userInfo.expire,
old.createdAt,
ageSecretKey = old.ageSecretKey,
)

ImportedDao().update(new)

PendingDao().remove(new.uuid)
context.sendProfileChanged(new.uuid)
} catch (e: Exception) {
Log.e("Update profile flow failed: ${e.message}", e)
}
}

override suspend fun commit(uuid: Uuid, callback: IFetchObserver?) {
Expand Down
Loading