Browse Source

platform: Add group expand status

世界 2 years ago
parent
commit
9f94b21687

+ 2 - 0
adapter/experimental.go

@@ -23,6 +23,8 @@ type ClashServer interface {
 type ClashCacheFile interface {
 	LoadSelected(group string) string
 	StoreSelected(group string, selected string) error
+	LoadGroupExpand(group string) (isExpand bool, loaded bool)
+	StoreGroupExpand(group string, expand bool) error
 	FakeIPStorage
 }
 

+ 38 - 11
experimental/clashapi/cachefile/cache.go

@@ -12,7 +12,10 @@ import (
 	"go.etcd.io/bbolt"
 )
 
-var bucketSelected = []byte("selected")
+var (
+	bucketSelected = []byte("selected")
+	bucketExpand   = []byte("group_expand")
+)
 
 var _ adapter.ClashCacheFile = (*CacheFile)(nil)
 
@@ -49,21 +52,15 @@ func Open(path string, cacheID string) (*CacheFile, error) {
 			if name[0] == 0 {
 				return b.ForEachBucket(func(k []byte) error {
 					bucketName := string(k)
-					if !(bucketName == string(bucketSelected)) {
-						delErr := b.DeleteBucket(name)
-						if delErr != nil {
-							return delErr
-						}
+					if !(bucketName == string(bucketSelected) || bucketName == string(bucketExpand)) {
+						_ = b.DeleteBucket(name)
 					}
 					return nil
 				})
 			} else {
 				bucketName := string(name)
-				if !(bucketName == string(bucketSelected) || strings.HasPrefix(bucketName, fakeipBucketPrefix)) {
-					delErr := tx.DeleteBucket(name)
-					if delErr != nil {
-						return delErr
-					}
+				if !(bucketName == string(bucketSelected) || bucketName == string(bucketExpand) || strings.HasPrefix(bucketName, fakeipBucketPrefix)) {
+					_ = tx.DeleteBucket(name)
 				}
 			}
 			return nil
@@ -129,6 +126,36 @@ func (c *CacheFile) StoreSelected(group, selected string) error {
 	})
 }
 
+func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) {
+	c.DB.View(func(t *bbolt.Tx) error {
+		bucket := c.bucket(t, bucketExpand)
+		if bucket == nil {
+			return nil
+		}
+		expandBytes := bucket.Get([]byte(group))
+		if len(expandBytes) == 1 {
+			isExpand = expandBytes[0] == 1
+			loaded = true
+		}
+		return nil
+	})
+	return
+}
+
+func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error {
+	return c.DB.Batch(func(t *bbolt.Tx) error {
+		bucket, err := c.createBucket(t, bucketExpand)
+		if err != nil {
+			return err
+		}
+		if isExpand {
+			return bucket.Put([]byte(group), []byte{1})
+		} else {
+			return bucket.Put([]byte(group), []byte{0})
+		}
+	})
+}
+
 func (c *CacheFile) Close() error {
 	return c.DB.Close()
 }

+ 1 - 1
experimental/clashapi/server.go

@@ -84,7 +84,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
 	if server.mode == "" {
 		server.mode = "rule"
 	}
-	if options.StoreSelected || options.StoreFakeIP {
+	if options.StoreSelected || options.StoreFakeIP || options.ExternalController == "" {
 		cachePath := os.ExpandEnv(options.CacheFile)
 		if cachePath == "" {
 			cachePath = "cache.db"

+ 1 - 0
experimental/libbox/command.go

@@ -8,4 +8,5 @@ const (
 	CommandGroup
 	CommandSelectOutbound
 	CommandURLTest
+	CommandGroupExpand
 )

+ 84 - 0
experimental/libbox/command_group.go

@@ -9,6 +9,7 @@ import (
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/common/urltest"
 	"github.com/sagernet/sing-box/outbound"
+	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/rw"
 	"github.com/sagernet/sing/service"
 )
@@ -18,6 +19,7 @@ type OutboundGroup struct {
 	Type       string
 	Selectable bool
 	Selected   string
+	isExpand   int8
 	items      []*OutboundGroupItem
 }
 
@@ -25,6 +27,19 @@ func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
 	return newIterator(g.items)
 }
 
+func (g *OutboundGroup) IsExpand() bool {
+	switch g.isExpand {
+	case -1:
+		return g.Selectable
+	case 0:
+		return false
+	case 1:
+		return true
+	default:
+		panic("unexpected expand value")
+	}
+}
+
 type OutboundGroupIterator interface {
 	Next() *OutboundGroup
 	HasNext() bool
@@ -114,6 +129,11 @@ func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
 			return nil, err
 		}
 
+		err = binary.Read(reader, binary.BigEndian, &group.isExpand)
+		if err != nil {
+			return nil, err
+		}
+
 		var itemLength uint16
 		err = binary.Read(reader, binary.BigEndian, &itemLength)
 		if err != nil {
@@ -152,6 +172,10 @@ func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
 
 func writeGroups(writer io.Writer, boxService *BoxService) error {
 	historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx)
+	var cacheFile adapter.ClashCacheFile
+	if clashServer := boxService.instance.Router().ClashServer(); clashServer != nil {
+		cacheFile = clashServer.CacheFile()
+	}
 
 	outbounds := boxService.instance.Router().Outbounds()
 	var iGroups []adapter.OutboundGroup
@@ -167,6 +191,15 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
 		group.Type = iGroup.Type()
 		_, group.Selectable = iGroup.(*outbound.Selector)
 		group.Selected = iGroup.Now()
+		if cacheFile != nil {
+			if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); !loaded {
+				group.isExpand = -1
+			} else if isExpand {
+				group.isExpand = 1
+			} else {
+				group.isExpand = 0
+			}
+		}
 
 		for _, itemTag := range iGroup.All() {
 			itemOutbound, isLoaded := boxService.instance.Router().Outbound(itemTag)
@@ -207,6 +240,10 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
 		if err != nil {
 			return err
 		}
+		err = binary.Write(writer, binary.BigEndian, group.isExpand)
+		if err != nil {
+			return err
+		}
 		err = binary.Write(writer, binary.BigEndian, uint16(len(group.items)))
 		if err != nil {
 			return err
@@ -232,3 +269,50 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
 	}
 	return nil
 }
+
+func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
+	conn, err := c.directConnect()
+	if err != nil {
+		return err
+	}
+	defer conn.Close()
+	err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand))
+	if err != nil {
+		return err
+	}
+	err = rw.WriteVString(conn, groupTag)
+	if err != nil {
+		return err
+	}
+	err = binary.Write(conn, binary.BigEndian, isExpand)
+	if err != nil {
+		return err
+	}
+	return readError(conn)
+}
+
+func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
+	defer conn.Close()
+	groupTag, err := rw.ReadVString(conn)
+	if err != nil {
+		return err
+	}
+	var isExpand bool
+	err = binary.Read(conn, binary.BigEndian, &isExpand)
+	if err != nil {
+		return err
+	}
+	service := s.service
+	if service == nil {
+		return writeError(conn, E.New("service not ready"))
+	}
+	if clashServer := service.instance.Router().ClashServer(); clashServer != nil {
+		if cacheFile := clashServer.CacheFile(); cacheFile != nil {
+			err = cacheFile.StoreGroupExpand(groupTag, isExpand)
+			if err != nil {
+				return writeError(conn, err)
+			}
+		}
+	}
+	return writeError(conn, nil)
+}

+ 2 - 0
experimental/libbox/command_server.go

@@ -154,6 +154,8 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
 		return s.handleSelectOutbound(conn)
 	case CommandURLTest:
 		return s.handleURLTest(conn)
+	case CommandGroupExpand:
+		return s.handleSetGroupExpand(conn)
 	default:
 		return E.New("unknown command: ", command)
 	}

+ 3 - 0
experimental/libbox/service.go

@@ -3,6 +3,7 @@ package libbox
 import (
 	"context"
 	"net/netip"
+	runtimeDebug "runtime/debug"
 	"syscall"
 
 	"github.com/sagernet/sing-box"
@@ -35,6 +36,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
 	if err != nil {
 		return nil, err
 	}
+	runtimeDebug.FreeOSMemory()
 	ctx, cancel := context.WithCancel(context.Background())
 	ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
 	ctx = service.ContextWithPtr(ctx, urltest.NewHistoryStorage())
@@ -49,6 +51,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
 		cancel()
 		return nil, E.Cause(err, "create service")
 	}
+	runtimeDebug.FreeOSMemory()
 	return &BoxService{
 		ctx:          ctx,
 		cancel:       cancel,