src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/daemon/client.go (about)

     1  package daemon
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"sync"
     7  
     8  	"src.elv.sh/pkg/daemon/daemondefs"
     9  	"src.elv.sh/pkg/daemon/internal/api"
    10  	"src.elv.sh/pkg/rpc"
    11  	"src.elv.sh/pkg/store/storedefs"
    12  )
    13  
    14  const retriesOnShutdown = 3
    15  
    16  var (
    17  	// ErrDaemonUnreachable is returned when the daemon cannot be reached after
    18  	// several retries.
    19  	ErrDaemonUnreachable = errors.New("daemon offline")
    20  )
    21  
    22  // Implementation of the Client interface.
    23  type client struct {
    24  	sockPath  string
    25  	rpcClient *rpc.Client
    26  	waits     sync.WaitGroup
    27  }
    28  
    29  // NewClient creates a new Client instance that talks to the socket. Connection
    30  // creation is deferred to the first request.
    31  func NewClient(sockPath string) daemondefs.Client {
    32  	return &client{sockPath, nil, sync.WaitGroup{}}
    33  }
    34  
    35  // SockPath returns the socket path that the Client talks to. If the client is
    36  // nil, it returns an empty string.
    37  func (c *client) SockPath() string {
    38  	return c.sockPath
    39  }
    40  
    41  // ResetConn resets the current connection. A new connection will be established
    42  // the next time a request is made. If the client is nil, it does nothing.
    43  func (c *client) ResetConn() error {
    44  	if c.rpcClient == nil {
    45  		return nil
    46  	}
    47  	rc := c.rpcClient
    48  	c.rpcClient = nil
    49  	return rc.Close()
    50  }
    51  
    52  // Close waits for all outstanding requests to finish and close the connection.
    53  // If the client is nil, it does nothing and returns nil.
    54  func (c *client) Close() error {
    55  	c.waits.Wait()
    56  	return c.ResetConn()
    57  }
    58  
    59  func (c *client) call(f string, req, res any) error {
    60  	c.waits.Add(1)
    61  	defer c.waits.Done()
    62  
    63  	for attempt := 0; attempt < retriesOnShutdown; attempt++ {
    64  		if c.rpcClient == nil {
    65  			conn, err := net.Dial("unix", c.sockPath)
    66  			if err != nil {
    67  				return err
    68  			}
    69  			c.rpcClient = rpc.NewClient(conn)
    70  		}
    71  
    72  		err := c.rpcClient.Call(api.ServiceName+"."+f, req, res)
    73  		if err == rpc.ErrShutdown {
    74  			// Clear rpcClient so as to reconnect next time
    75  			c.rpcClient = nil
    76  			continue
    77  		} else {
    78  			return err
    79  		}
    80  	}
    81  	return ErrDaemonUnreachable
    82  }
    83  
    84  // Convenience methods for RPC methods. These are quite repetitive; when the
    85  // number of RPC calls grow above some threshold, a code generator should be
    86  // written to generate them.
    87  
    88  func (c *client) Version() (int, error) {
    89  	req := &api.VersionRequest{}
    90  	res := &api.VersionResponse{}
    91  	err := c.call("Version", req, res)
    92  	return res.Version, err
    93  }
    94  
    95  func (c *client) Pid() (int, error) {
    96  	req := &api.PidRequest{}
    97  	res := &api.PidResponse{}
    98  	err := c.call("Pid", req, res)
    99  	return res.Pid, err
   100  }
   101  
   102  func (c *client) NextCmdSeq() (int, error) {
   103  	req := &api.NextCmdRequest{}
   104  	res := &api.NextCmdSeqResponse{}
   105  	err := c.call("NextCmdSeq", req, res)
   106  	return res.Seq, err
   107  }
   108  
   109  func (c *client) AddCmd(text string) (int, error) {
   110  	req := &api.AddCmdRequest{Text: text}
   111  	res := &api.AddCmdResponse{}
   112  	err := c.call("AddCmd", req, res)
   113  	return res.Seq, err
   114  }
   115  
   116  func (c *client) DelCmd(seq int) error {
   117  	req := &api.DelCmdRequest{Seq: seq}
   118  	res := &api.DelCmdResponse{}
   119  	err := c.call("DelCmd", req, res)
   120  	return err
   121  }
   122  
   123  func (c *client) Cmd(seq int) (string, error) {
   124  	req := &api.CmdRequest{Seq: seq}
   125  	res := &api.CmdResponse{}
   126  	err := c.call("Cmd", req, res)
   127  	return res.Text, err
   128  }
   129  
   130  func (c *client) CmdsWithSeq(from, upto int) ([]storedefs.Cmd, error) {
   131  	req := &api.CmdsWithSeqRequest{From: from, Upto: upto}
   132  	res := &api.CmdsWithSeqResponse{}
   133  	err := c.call("CmdsWithSeq", req, res)
   134  	return res.Cmds, err
   135  }
   136  
   137  func (c *client) NextCmd(from int, prefix string) (storedefs.Cmd, error) {
   138  	req := &api.NextCmdRequest{From: from, Prefix: prefix}
   139  	res := &api.NextCmdResponse{}
   140  	err := c.call("NextCmd", req, res)
   141  	return storedefs.Cmd{Text: res.Text, Seq: res.Seq}, err
   142  }
   143  
   144  func (c *client) PrevCmd(upto int, prefix string) (storedefs.Cmd, error) {
   145  	req := &api.PrevCmdRequest{Upto: upto, Prefix: prefix}
   146  	res := &api.PrevCmdResponse{}
   147  	err := c.call("PrevCmd", req, res)
   148  	return storedefs.Cmd{Text: res.Text, Seq: res.Seq}, err
   149  }
   150  
   151  func (c *client) AddDir(dir string, incFactor float64) error {
   152  	req := &api.AddDirRequest{Dir: dir, IncFactor: incFactor}
   153  	res := &api.AddDirResponse{}
   154  	err := c.call("AddDir", req, res)
   155  	return err
   156  }
   157  
   158  func (c *client) DelDir(dir string) error {
   159  	req := &api.DelDirRequest{Dir: dir}
   160  	res := &api.DelDirResponse{}
   161  	err := c.call("DelDir", req, res)
   162  	return err
   163  }
   164  
   165  func (c *client) Dirs(blacklist map[string]struct{}) ([]storedefs.Dir, error) {
   166  	req := &api.DirsRequest{Blacklist: blacklist}
   167  	res := &api.DirsResponse{}
   168  	err := c.call("Dirs", req, res)
   169  	return res.Dirs, err
   170  }