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