Feature: support store group selected node to cache (enable by default)
This commit is contained in:
parent
aa81193d5b
commit
14bbf6eedc
6 changed files with 183 additions and 1 deletions
115
component/profile/cachefile/cache.go
Normal file
115
component/profile/cachefile/cache.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package cachefile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/component/profile"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
)
|
||||
|
||||
var (
|
||||
initOnce sync.Once
|
||||
fileMode os.FileMode = 0666
|
||||
defaultCache *CacheFile
|
||||
)
|
||||
|
||||
type cache struct {
|
||||
Selected map[string]string
|
||||
}
|
||||
|
||||
// CacheFile store and update the cache file
|
||||
type CacheFile struct {
|
||||
path string
|
||||
model *cache
|
||||
enc *gob.Encoder
|
||||
buf *bytes.Buffer
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func (c *CacheFile) SetSelected(group, selected string) {
|
||||
if !profile.StoreSelected.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
model, err := c.element()
|
||||
if err != nil {
|
||||
log.Warnln("[CacheFile] read cache %s failed: %s", c.path, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
model.Selected[group] = selected
|
||||
c.buf.Reset()
|
||||
if err := c.enc.Encode(model); err != nil {
|
||||
log.Warnln("[CacheFile] encode gob failed: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(c.path, c.buf.Bytes(), fileMode); err != nil {
|
||||
log.Warnln("[CacheFile] write cache to %s failed: %s", c.path, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CacheFile) SelectedMap() map[string]string {
|
||||
if !profile.StoreSelected.Load() {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
model, err := c.element()
|
||||
if err != nil {
|
||||
log.Warnln("[CacheFile] read cache %s failed: %s", c.path, err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
mapping := map[string]string{}
|
||||
for k, v := range model.Selected {
|
||||
mapping[k] = v
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
|
||||
func (c *CacheFile) element() (*cache, error) {
|
||||
if c.model != nil {
|
||||
return c.model, nil
|
||||
}
|
||||
|
||||
model := &cache{
|
||||
Selected: map[string]string{},
|
||||
}
|
||||
|
||||
if buf, err := ioutil.ReadFile(c.path); err == nil {
|
||||
bufReader := bytes.NewBuffer(buf)
|
||||
dec := gob.NewDecoder(bufReader)
|
||||
if err := dec.Decode(model); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
c.model = model
|
||||
return c.model, nil
|
||||
}
|
||||
|
||||
// Cache return singleton of CacheFile
|
||||
func Cache() *CacheFile {
|
||||
initOnce.Do(func() {
|
||||
buf := &bytes.Buffer{}
|
||||
defaultCache = &CacheFile{
|
||||
path: C.Path.Cache(),
|
||||
buf: buf,
|
||||
enc: gob.NewEncoder(buf),
|
||||
}
|
||||
})
|
||||
|
||||
return defaultCache
|
||||
}
|
10
component/profile/profile.go
Normal file
10
component/profile/profile.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
// StoreSelected is a global switch for storing selected proxy to cache
|
||||
StoreSelected = atomic.NewBool(true)
|
||||
)
|
|
@ -73,6 +73,11 @@ type FallbackFilter struct {
|
|||
Domain []string `yaml:"domain"`
|
||||
}
|
||||
|
||||
// Profile config
|
||||
type Profile struct {
|
||||
StoreSelected bool `yaml:"store-selected"`
|
||||
}
|
||||
|
||||
// Experimental config
|
||||
type Experimental struct{}
|
||||
|
||||
|
@ -82,6 +87,7 @@ type Config struct {
|
|||
DNS *DNS
|
||||
Experimental *Experimental
|
||||
Hosts *trie.DomainTrie
|
||||
Profile *Profile
|
||||
Rules []C.Rule
|
||||
Users []auth.AuthUser
|
||||
Proxies map[string]C.Proxy
|
||||
|
@ -129,6 +135,7 @@ type RawConfig struct {
|
|||
Hosts map[string]string `yaml:"hosts"`
|
||||
DNS RawDNS `yaml:"dns"`
|
||||
Experimental Experimental `yaml:"experimental"`
|
||||
Profile Profile `yaml:"profile"`
|
||||
Proxy []map[string]interface{} `yaml:"proxies"`
|
||||
ProxyGroup []map[string]interface{} `yaml:"proxy-groups"`
|
||||
Rule []string `yaml:"rules"`
|
||||
|
@ -145,7 +152,7 @@ func Parse(buf []byte) (*Config, error) {
|
|||
}
|
||||
|
||||
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||
// config with some default value
|
||||
// config with default value
|
||||
rawCfg := &RawConfig{
|
||||
AllowLan: false,
|
||||
BindAddress: "*",
|
||||
|
@ -169,6 +176,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||
"8.8.8.8",
|
||||
},
|
||||
},
|
||||
Profile: Profile{
|
||||
StoreSelected: true,
|
||||
},
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(buf, &rawCfg); err != nil {
|
||||
|
@ -182,6 +192,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||
config := &Config{}
|
||||
|
||||
config.Experimental = &rawCfg.Experimental
|
||||
config.Profile = &rawCfg.Profile
|
||||
|
||||
general, err := parseGeneral(rawCfg)
|
||||
if err != nil {
|
||||
|
|
|
@ -56,3 +56,7 @@ func (p *path) Resolve(path string) string {
|
|||
func (p *path) MMDB() string {
|
||||
return P.Join(p.homeDir, "Country.mmdb")
|
||||
}
|
||||
|
||||
func (p *path) Cache() string {
|
||||
return P.Join(p.homeDir, ".cache")
|
||||
}
|
||||
|
|
|
@ -6,9 +6,13 @@ import (
|
|||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/adapters/provider"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
"github.com/Dreamacro/clash/component/profile"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
|
@ -72,6 +76,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||
updateDNS(cfg.DNS)
|
||||
updateHosts(cfg.Hosts)
|
||||
updateExperimental(cfg)
|
||||
updateProfile(cfg)
|
||||
}
|
||||
|
||||
func GetGeneral() *config.General {
|
||||
|
@ -209,3 +214,38 @@ func updateUsers(users []auth.AuthUser) {
|
|||
log.Infoln("Authentication of local server updated")
|
||||
}
|
||||
}
|
||||
|
||||
func updateProfile(cfg *config.Config) {
|
||||
profileCfg := cfg.Profile
|
||||
|
||||
profile.StoreSelected.Store(profileCfg.StoreSelected)
|
||||
if profileCfg.StoreSelected {
|
||||
patchSelectGroup(cfg.Proxies)
|
||||
}
|
||||
}
|
||||
|
||||
func patchSelectGroup(proxies map[string]C.Proxy) {
|
||||
mapping := cachefile.Cache().SelectedMap()
|
||||
if mapping == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for name, proxy := range proxies {
|
||||
outbound, ok := proxy.(*outbound.Proxy)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
selector, ok := outbound.ProxyAdapter.(*outboundgroup.Selector)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
selected, exist := mapping[name]
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
|
||||
selector.Set(selected)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/Dreamacro/clash/adapters/outbound"
|
||||
"github.com/Dreamacro/clash/adapters/outboundgroup"
|
||||
"github.com/Dreamacro/clash/component/profile/cachefile"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
|
@ -91,6 +92,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
cachefile.Cache().SetSelected(proxy.Name(), req.Name)
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue