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  }