diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index b23eeb97..e78e0cca 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -10,45 +10,40 @@ import ( "github.com/Dreamacro/clash/component/profile" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" + + bolt "go.etcd.io/bbolt" ) var ( initOnce sync.Once fileMode os.FileMode = 0666 defaultCache *CacheFile -) -type cache struct { - Selected map[string]string -} + bucketSelected = []byte("selected") +) // CacheFile store and update the cache file type CacheFile struct { - path string - model *cache - buf *bytes.Buffer - mux sync.Mutex + db *bolt.DB } func (c *CacheFile) SetSelected(group, selected string) { if !profile.StoreSelected.Load() { return - } - - c.mux.Lock() - defer c.mux.Unlock() - - model := c.element() - - model.Selected[group] = selected - c.buf.Reset() - if err := gob.NewEncoder(c.buf).Encode(model); err != nil { - log.Warnln("[CacheFile] encode gob failed: %s", err.Error()) + } else if c.db == nil { 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()) + err := c.db.Batch(func(t *bolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketSelected) + if err != nil { + return err + } + return bucket.Put([]byte(group), []byte(selected)) + }) + + if err != nil { + log.Warnln("[CacheFile] write cache to %s failed: %s", c.db.Path(), err.Error()) return } } @@ -56,46 +51,80 @@ func (c *CacheFile) SetSelected(group, selected string) { func (c *CacheFile) SelectedMap() map[string]string { if !profile.StoreSelected.Load() { return nil + } else if c.db == nil { + return nil } - c.mux.Lock() - defer c.mux.Unlock() - - model := c.element() - mapping := map[string]string{} - for k, v := range model.Selected { - mapping[k] = v - } + c.db.View(func(t *bolt.Tx) error { + bucket := t.Bucket(bucketSelected) + if bucket == nil { + return nil + } + + c := bucket.Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + mapping[string(k)] = string(v) + } + return nil + }) return mapping } -func (c *CacheFile) element() *cache { - if c.model != nil { - return c.model - } +func (c *CacheFile) Close() error { + return c.db.Close() +} +func migrateCache() { + defer func() { + db, err := bolt.Open(C.Path.Cache(), fileMode, nil) + if err != nil { + log.Warnln("[CacheFile] can't open cache file: %s", err.Error()) + } + defaultCache = &CacheFile{ + db: db, + } + }() + + buf, err := ioutil.ReadFile(C.Path.OldCache()) + if err != nil { + return + } + defer os.Remove(C.Path.OldCache()) + + // read old cache file + type cache struct { + Selected map[string]string + } model := &cache{ Selected: map[string]string{}, } + bufReader := bytes.NewBuffer(buf) + gob.NewDecoder(bufReader).Decode(model) - if buf, err := ioutil.ReadFile(c.path); err == nil { - bufReader := bytes.NewBuffer(buf) - gob.NewDecoder(bufReader).Decode(model) + // write to new cache file + db, err := bolt.Open(C.Path.Cache(), fileMode, nil) + if err != nil { + return } - - c.model = model - return c.model + defer db.Close() + db.Batch(func(t *bolt.Tx) error { + bucket, err := t.CreateBucketIfNotExists(bucketSelected) + if err != nil { + return err + } + for group, selected := range model.Selected { + if err := bucket.Put([]byte(group), []byte(selected)); err != nil { + return err + } + } + return nil + }) } // Cache return singleton of CacheFile func Cache() *CacheFile { - initOnce.Do(func() { - defaultCache = &CacheFile{ - path: C.Path.Cache(), - buf: &bytes.Buffer{}, - } - }) + initOnce.Do(migrateCache) return defaultCache } diff --git a/constant/path.go b/constant/path.go index ba0e8c23..781cc5f7 100644 --- a/constant/path.go +++ b/constant/path.go @@ -55,6 +55,10 @@ func (p *path) MMDB() string { return P.Join(p.homeDir, "Country.mmdb") } -func (p *path) Cache() string { +func (p *path) OldCache() string { return P.Join(p.homeDir, ".cache") } + +func (p *path) Cache() string { + return P.Join(p.homeDir, "cache.db") +} diff --git a/go.mod b/go.mod index e955449b..dc79d77a 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/oschwald/geoip2-golang v1.5.0 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 + go.etcd.io/bbolt v1.3.6 go.uber.org/atomic v1.9.0 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f diff --git a/go.sum b/go.sum index d1d97cfc..1fbae8ce 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -87,6 +89,7 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=