From 9f94b21687bea056a5bdd308e74a7648275f22c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 12 Aug 2023 19:33:43 +0800 Subject: [PATCH] platform: Add group expand status --- adapter/experimental.go | 2 + experimental/clashapi/cachefile/cache.go | 49 ++++++++++---- experimental/clashapi/server.go | 2 +- experimental/libbox/command.go | 1 + experimental/libbox/command_group.go | 84 ++++++++++++++++++++++++ experimental/libbox/command_server.go | 2 + experimental/libbox/service.go | 3 + 7 files changed, 131 insertions(+), 12 deletions(-) diff --git a/adapter/experimental.go b/adapter/experimental.go index f25d70a4..9afdcb7b 100644 --- a/adapter/experimental.go +++ b/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 } diff --git a/experimental/clashapi/cachefile/cache.go b/experimental/clashapi/cachefile/cache.go index 561ae0a9..e135a742 100644 --- a/experimental/clashapi/cachefile/cache.go +++ b/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() } diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index b1e8f181..c8a55c58 100644 --- a/experimental/clashapi/server.go +++ b/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" diff --git a/experimental/libbox/command.go b/experimental/libbox/command.go index dff012a2..7824a87d 100644 --- a/experimental/libbox/command.go +++ b/experimental/libbox/command.go @@ -8,4 +8,5 @@ const ( CommandGroup CommandSelectOutbound CommandURLTest + CommandGroupExpand ) diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go index fef043cb..afb20859 100644 --- a/experimental/libbox/command_group.go +++ b/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) +} diff --git a/experimental/libbox/command_server.go b/experimental/libbox/command_server.go index 1aeb12af..ba2d573e 100644 --- a/experimental/libbox/command_server.go +++ b/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) } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 14f0479c..a24a0e87 100644 --- a/experimental/libbox/service.go +++ b/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,