2017-12-29 05:27:51 +08:00
|
|
|
package daemon
|
2017-06-19 22:53:53 +08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"net/rpc"
|
2017-06-20 06:38:20 +08:00
|
|
|
"sync"
|
2017-11-11 20:30:27 +08:00
|
|
|
|
|
|
|
"github.com/elves/elvish/store/storedefs"
|
2017-06-19 22:53:53 +08:00
|
|
|
)
|
|
|
|
|
2017-12-23 06:34:39 +08:00
|
|
|
const retriesOnShutdown = 3
|
2017-06-19 22:53:53 +08:00
|
|
|
|
2017-12-23 06:34:39 +08:00
|
|
|
var (
|
|
|
|
// ErrClientNotInitialized is returned when the Client is not initialized.
|
|
|
|
ErrClientNotInitialized = errors.New("client not initialized")
|
|
|
|
// ErrDaemonUnreachable is returned when the daemon cannot be reached after
|
|
|
|
// several retries.
|
|
|
|
ErrDaemonUnreachable = errors.New("daemon offline")
|
|
|
|
)
|
|
|
|
|
|
|
|
// Client is a client to the Elvish daemon. A nil *Client is safe to use.
|
2017-06-19 22:53:53 +08:00
|
|
|
type Client struct {
|
|
|
|
sockPath string
|
|
|
|
rpcClient *rpc.Client
|
2017-06-20 06:38:20 +08:00
|
|
|
waits sync.WaitGroup
|
2017-06-19 22:53:53 +08:00
|
|
|
}
|
|
|
|
|
2017-12-29 05:41:36 +08:00
|
|
|
var _ storedefs.Store = (*Client)(nil)
|
|
|
|
|
2017-12-23 06:25:07 +08:00
|
|
|
// NewClient creates a new Client instance that talks to the socket. Connection
|
|
|
|
// creation is deferred to the first request.
|
2017-06-20 05:44:44 +08:00
|
|
|
func NewClient(sockPath string) *Client {
|
2017-06-20 06:38:20 +08:00
|
|
|
return &Client{sockPath, nil, sync.WaitGroup{}}
|
|
|
|
}
|
|
|
|
|
2017-12-23 06:34:39 +08:00
|
|
|
// SockPath returns the socket path that the Client talks to. If the client is
|
|
|
|
// nil, it returns an empty string.
|
2017-06-21 08:54:11 +08:00
|
|
|
func (c *Client) SockPath() string {
|
2017-12-23 06:34:39 +08:00
|
|
|
if c == nil {
|
|
|
|
return ""
|
|
|
|
}
|
2017-06-21 08:54:11 +08:00
|
|
|
return c.sockPath
|
|
|
|
}
|
|
|
|
|
2017-12-23 22:14:29 +08:00
|
|
|
// ResetConn resets the current connection. A new connection will be established
|
|
|
|
// the next time a request is made. If the client is nil, it does nothing.
|
|
|
|
func (c *Client) ResetConn() error {
|
|
|
|
if c == nil || c.rpcClient == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
rc := c.rpcClient
|
|
|
|
c.rpcClient = nil
|
|
|
|
return rc.Close()
|
|
|
|
}
|
|
|
|
|
2017-12-23 06:25:07 +08:00
|
|
|
// Close waits for all outstanding requests to finish and close the connection.
|
2017-12-23 06:34:39 +08:00
|
|
|
// If the client is nil, it does nothing and returns nil.
|
2017-11-11 20:30:27 +08:00
|
|
|
func (c *Client) Close() error {
|
2017-12-23 06:34:39 +08:00
|
|
|
if c == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2017-11-11 20:30:27 +08:00
|
|
|
c.waits.Wait()
|
2017-12-23 22:14:29 +08:00
|
|
|
return c.ResetConn()
|
2017-11-11 20:30:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) call(f string, req, res interface{}) error {
|
2017-12-23 06:34:39 +08:00
|
|
|
if c == nil {
|
|
|
|
return ErrClientNotInitialized
|
|
|
|
}
|
2017-12-23 06:25:07 +08:00
|
|
|
c.waits.Add(1)
|
|
|
|
defer c.waits.Done()
|
|
|
|
|
2017-12-23 06:34:39 +08:00
|
|
|
for attempt := 0; attempt < retriesOnShutdown; attempt++ {
|
|
|
|
if c.rpcClient == nil {
|
|
|
|
conn, err := dial(c.sockPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
c.rpcClient = rpc.NewClient(conn)
|
|
|
|
}
|
2017-06-19 22:53:53 +08:00
|
|
|
|
2017-12-23 06:34:39 +08:00
|
|
|
err := c.rpcClient.Call(ServiceName+"."+f, req, res)
|
|
|
|
if err == rpc.ErrShutdown {
|
|
|
|
// Clear rpcClient so as to reconnect next time
|
|
|
|
c.rpcClient = nil
|
|
|
|
continue
|
|
|
|
} else {
|
2017-07-13 17:50:52 +08:00
|
|
|
return err
|
|
|
|
}
|
2017-06-19 22:53:53 +08:00
|
|
|
}
|
2017-12-23 06:34:39 +08:00
|
|
|
return ErrDaemonUnreachable
|
2017-06-19 22:53:53 +08:00
|
|
|
}
|
2017-11-11 20:30:27 +08:00
|
|
|
|
2017-12-23 06:25:07 +08:00
|
|
|
// Convenience methods for RPC methods. These are quite repetitive; when the
|
|
|
|
// number of RPC calls grow above some threshold, a code generator should be
|
|
|
|
// written to generate them.
|
2017-11-11 20:30:27 +08:00
|
|
|
|
|
|
|
func (c *Client) Version() (int, error) {
|
|
|
|
req := &VersionRequest{}
|
|
|
|
res := &VersionResponse{}
|
|
|
|
err := c.call("Version", req, res)
|
|
|
|
return res.Version, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Pid() (int, error) {
|
|
|
|
req := &PidRequest{}
|
|
|
|
res := &PidResponse{}
|
|
|
|
err := c.call("Pid", req, res)
|
|
|
|
return res.Pid, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) NextCmdSeq() (int, error) {
|
|
|
|
req := &NextCmdRequest{}
|
|
|
|
res := &NextCmdSeqResponse{}
|
|
|
|
err := c.call("NextCmdSeq", req, res)
|
|
|
|
return res.Seq, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) AddCmd(text string) (int, error) {
|
|
|
|
req := &AddCmdRequest{text}
|
|
|
|
res := &AddCmdResponse{}
|
|
|
|
err := c.call("AddCmd", req, res)
|
|
|
|
return res.Seq, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Cmd(seq int) (string, error) {
|
|
|
|
req := &CmdRequest{seq}
|
|
|
|
res := &CmdResponse{}
|
|
|
|
err := c.call("Cmd", req, res)
|
|
|
|
return res.Text, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Cmds(from, upto int) ([]string, error) {
|
|
|
|
req := &CmdsRequest{from, upto}
|
|
|
|
res := &CmdsResponse{}
|
|
|
|
err := c.call("Cmds", req, res)
|
|
|
|
return res.Cmds, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) NextCmd(from int, prefix string) (int, string, error) {
|
|
|
|
req := &NextCmdRequest{from, prefix}
|
|
|
|
res := &NextCmdResponse{}
|
|
|
|
err := c.call("NextCmd", req, res)
|
|
|
|
return res.Seq, res.Text, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) PrevCmd(upto int, prefix string) (int, string, error) {
|
|
|
|
req := &PrevCmdRequest{upto, prefix}
|
|
|
|
res := &PrevCmdResponse{}
|
|
|
|
err := c.call("PrevCmd", req, res)
|
|
|
|
return res.Seq, res.Text, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) AddDir(dir string, incFactor float64) error {
|
|
|
|
req := &AddDirRequest{dir, incFactor}
|
|
|
|
res := &AddDirResponse{}
|
|
|
|
err := c.call("AddDir", req, res)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Dirs(blacklist map[string]struct{}) ([]storedefs.Dir, error) {
|
|
|
|
req := &DirsRequest{blacklist}
|
|
|
|
res := &DirsResponse{}
|
|
|
|
err := c.call("Dirs", req, res)
|
|
|
|
return res.Dirs, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) SharedVar(name string) (string, error) {
|
|
|
|
req := &SharedVarRequest{name}
|
|
|
|
res := &SharedVarResponse{}
|
|
|
|
err := c.call("SharedVar", req, res)
|
|
|
|
return res.Value, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) SetSharedVar(name, value string) error {
|
|
|
|
req := &SetSharedVarRequest{name, value}
|
|
|
|
res := &SetSharedVarResponse{}
|
|
|
|
return c.call("SetSharedVar", req, res)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) DelSharedVar(name string) error {
|
|
|
|
req := &DelSharedVarRequest{}
|
|
|
|
res := &DelSharedVarResponse{}
|
|
|
|
return c.call("DelSharedVar", req, res)
|
|
|
|
}
|