diff options
Diffstat (limited to 'lib/ipc')
-rw-r--r-- | lib/ipc/socket.go | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/lib/ipc/socket.go b/lib/ipc/socket.go new file mode 100644 index 00000000..27692a9b --- /dev/null +++ b/lib/ipc/socket.go @@ -0,0 +1,146 @@ +package ipc + +import ( + "bufio" + "errors" + "fmt" + "net" + "net/url" + "os" + "path" + "strings" + "sync/atomic" + "time" + + "git.sr.ht/~rjarry/aerc/log" + "github.com/kyoh86/xdg" +) + +type AercServer struct { + listener net.Listener + OnMailto func(addr *url.URL) error + OnMbox func(source string) error +} + +func StartServer() (*AercServer, error) { + sockpath := path.Join(xdg.RuntimeDir(), "aerc.sock") + // remove the socket if it is not connected to a session + if err := ConnectAndExec(""); err != nil { + os.Remove(sockpath) + } + log.Debugf("Starting Unix server: %s", sockpath) + l, err := net.Listen("unix", sockpath) + if err != nil { + return nil, err + } + as := &AercServer{listener: l} + // TODO: stash clients and close them on exit... bleh racey + go func() { + defer log.PanicHandler() + + for { + conn, err := l.Accept() + if err != nil { + if !strings.Contains(err.Error(), + "use of closed network connection") { + // TODO: Something more useful, in some + // cases, on wednesdays, after 2 PM, + // I guess? + log.Errorf("Closing Unix server: %v", err) + } + return + } + go func() { + defer log.PanicHandler() + + as.handleClient(conn) + }() + } + }() + return as, nil +} + +func (as *AercServer) Close() { + as.listener.Close() +} + +var lastId int64 = 0 // access via atomic + +func (as *AercServer) handleClient(conn net.Conn) { + clientId := atomic.AddInt64(&lastId, 1) + log.Debugf("unix:%d accepted connection", clientId) + scanner := bufio.NewScanner(conn) + err := conn.SetDeadline(time.Now().Add(1 * time.Minute)) + if err != nil { + log.Errorf("failed to set deadline: %v", err) + } + for scanner.Scan() { + err = conn.SetDeadline(time.Now().Add(1 * time.Minute)) + if err != nil { + log.Errorf("failed to update deadline: %v", err) + } + msg := scanner.Text() + log.Tracef("unix:%d got message %s", clientId, msg) + if !strings.ContainsRune(msg, ':') { + _, innererr := conn.Write([]byte("error: invalid command\n")) + if innererr != nil { + log.Errorf("failed to write error message: %v", innererr) + } + continue + } + prefix := msg[:strings.IndexRune(msg, ':')] + var err error + switch prefix { + case "mailto": + mailto, err := url.Parse(msg) + if err != nil { + _, innererr := conn.Write([]byte(fmt.Sprintf("error: %v\n", err))) + if innererr != nil { + log.Errorf("failed to write error message: %v", innererr) + } + break + } + if as.OnMailto != nil { + err = as.OnMailto(mailto) + if err != nil { + log.Errorf("mailto failed: %v", err) + } + } + case "mbox": + if as.OnMbox != nil { + err = as.OnMbox(msg) + } + } + if err != nil { + _, err = conn.Write([]byte(fmt.Sprintf("result: %v\n", err))) + if err != nil { + log.Errorf("failed to send error: %v") + } + } else { + _, err = conn.Write([]byte("result: success\n")) + if err != nil { + log.Errorf("failed to send successmessage: %v") + } + } + } + log.Tracef("unix:%d closed connection", clientId) +} + +func ConnectAndExec(msg string) error { + sockpath := path.Join(xdg.RuntimeDir(), "aerc.sock") + conn, err := net.Dial("unix", sockpath) + if err != nil { + return err + } + _, err = conn.Write([]byte(msg + "\n")) + if err != nil { + return fmt.Errorf("failed to send message: %w", err) + } + scanner := bufio.NewScanner(conn) + if !scanner.Scan() { + return errors.New("No response from server") + } + result := scanner.Text() + fmt.Println(result) + return nil +} |