Feature: support store group selected node to cache (enable by default)

This commit is contained in:
Dreamacro 2021-02-18 23:41:50 +08:00
parent aa81193d5b
commit 14bbf6eedc
6 changed files with 183 additions and 1 deletions

View 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
}

View 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)
)

View file

@ -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 {

View file

@ -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")
}

View file

@ -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)
}
}

View file

@ -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)
}