github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/experimental/clashapi/cachefile/cache.go (about) 1 package cachefile 2 3 import ( 4 "net/netip" 5 "os" 6 "strings" 7 "sync" 8 "time" 9 10 "github.com/inazumav/sing-box/adapter" 11 "github.com/sagernet/sing/common" 12 13 "go.etcd.io/bbolt" 14 ) 15 16 var ( 17 bucketSelected = []byte("selected") 18 bucketExpand = []byte("group_expand") 19 bucketMode = []byte("clash_mode") 20 21 bucketNameList = []string{ 22 string(bucketSelected), 23 string(bucketExpand), 24 string(bucketMode), 25 } 26 27 cacheIDDefault = []byte("default") 28 ) 29 30 var _ adapter.ClashCacheFile = (*CacheFile)(nil) 31 32 type CacheFile struct { 33 DB *bbolt.DB 34 cacheID []byte 35 saveAccess sync.RWMutex 36 saveDomain map[netip.Addr]string 37 saveAddress4 map[string]netip.Addr 38 saveAddress6 map[string]netip.Addr 39 saveMetadataTimer *time.Timer 40 } 41 42 func Open(path string, cacheID string) (*CacheFile, error) { 43 const fileMode = 0o666 44 options := bbolt.Options{Timeout: time.Second} 45 db, err := bbolt.Open(path, fileMode, &options) 46 switch err { 47 case bbolt.ErrInvalid, bbolt.ErrChecksum, bbolt.ErrVersionMismatch: 48 if err = os.Remove(path); err != nil { 49 break 50 } 51 db, err = bbolt.Open(path, 0o666, &options) 52 } 53 if err != nil { 54 return nil, err 55 } 56 var cacheIDBytes []byte 57 if cacheID != "" { 58 cacheIDBytes = append([]byte{0}, []byte(cacheID)...) 59 } 60 err = db.Batch(func(tx *bbolt.Tx) error { 61 return tx.ForEach(func(name []byte, b *bbolt.Bucket) error { 62 if name[0] == 0 { 63 return b.ForEachBucket(func(k []byte) error { 64 bucketName := string(k) 65 if !(common.Contains(bucketNameList, bucketName)) { 66 _ = b.DeleteBucket(name) 67 } 68 return nil 69 }) 70 } else { 71 bucketName := string(name) 72 if !(common.Contains(bucketNameList, bucketName) || strings.HasPrefix(bucketName, fakeipBucketPrefix)) { 73 _ = tx.DeleteBucket(name) 74 } 75 } 76 return nil 77 }) 78 }) 79 if err != nil { 80 return nil, err 81 } 82 return &CacheFile{ 83 DB: db, 84 cacheID: cacheIDBytes, 85 saveDomain: make(map[netip.Addr]string), 86 saveAddress4: make(map[string]netip.Addr), 87 saveAddress6: make(map[string]netip.Addr), 88 }, nil 89 } 90 91 func (c *CacheFile) LoadMode() string { 92 var mode string 93 c.DB.View(func(t *bbolt.Tx) error { 94 bucket := t.Bucket(bucketMode) 95 if bucket == nil { 96 return nil 97 } 98 var modeBytes []byte 99 if len(c.cacheID) > 0 { 100 modeBytes = bucket.Get(c.cacheID) 101 } else { 102 modeBytes = bucket.Get(cacheIDDefault) 103 } 104 mode = string(modeBytes) 105 return nil 106 }) 107 return mode 108 } 109 110 func (c *CacheFile) StoreMode(mode string) error { 111 return c.DB.Batch(func(t *bbolt.Tx) error { 112 bucket, err := t.CreateBucketIfNotExists(bucketMode) 113 if err != nil { 114 return err 115 } 116 if len(c.cacheID) > 0 { 117 return bucket.Put(c.cacheID, []byte(mode)) 118 } else { 119 return bucket.Put(cacheIDDefault, []byte(mode)) 120 } 121 }) 122 } 123 124 func (c *CacheFile) bucket(t *bbolt.Tx, key []byte) *bbolt.Bucket { 125 if c.cacheID == nil { 126 return t.Bucket(key) 127 } 128 bucket := t.Bucket(c.cacheID) 129 if bucket == nil { 130 return nil 131 } 132 return bucket.Bucket(key) 133 } 134 135 func (c *CacheFile) createBucket(t *bbolt.Tx, key []byte) (*bbolt.Bucket, error) { 136 if c.cacheID == nil { 137 return t.CreateBucketIfNotExists(key) 138 } 139 bucket, err := t.CreateBucketIfNotExists(c.cacheID) 140 if bucket == nil { 141 return nil, err 142 } 143 return bucket.CreateBucketIfNotExists(key) 144 } 145 146 func (c *CacheFile) LoadSelected(group string) string { 147 var selected string 148 c.DB.View(func(t *bbolt.Tx) error { 149 bucket := c.bucket(t, bucketSelected) 150 if bucket == nil { 151 return nil 152 } 153 selectedBytes := bucket.Get([]byte(group)) 154 if len(selectedBytes) > 0 { 155 selected = string(selectedBytes) 156 } 157 return nil 158 }) 159 return selected 160 } 161 162 func (c *CacheFile) StoreSelected(group, selected string) error { 163 return c.DB.Batch(func(t *bbolt.Tx) error { 164 bucket, err := c.createBucket(t, bucketSelected) 165 if err != nil { 166 return err 167 } 168 return bucket.Put([]byte(group), []byte(selected)) 169 }) 170 } 171 172 func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) { 173 c.DB.View(func(t *bbolt.Tx) error { 174 bucket := c.bucket(t, bucketExpand) 175 if bucket == nil { 176 return nil 177 } 178 expandBytes := bucket.Get([]byte(group)) 179 if len(expandBytes) == 1 { 180 isExpand = expandBytes[0] == 1 181 loaded = true 182 } 183 return nil 184 }) 185 return 186 } 187 188 func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error { 189 return c.DB.Batch(func(t *bbolt.Tx) error { 190 bucket, err := c.createBucket(t, bucketExpand) 191 if err != nil { 192 return err 193 } 194 if isExpand { 195 return bucket.Put([]byte(group), []byte{1}) 196 } else { 197 return bucket.Put([]byte(group), []byte{0}) 198 } 199 }) 200 } 201 202 func (c *CacheFile) Close() error { 203 return c.DB.Close() 204 }