aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ipc
diff options
context:
space:
mode:
authorMoritz Poldrack <git@moritz.sh>2023-03-04 10:56:43 +0100
committerRobin Jarry <robin@jarry.cc>2023-03-07 00:12:36 +0100
commit70dfd1bc40b633f83010ab1e5721a613f96efb87 (patch)
treee3d7a08e2eebfe1582fcc7b2325e9cdb8182d5fe /lib/ipc
parent8b26dc1d62c8ec258707527953bd0d2195684adf (diff)
downloadaerc-70dfd1bc40b633f83010ab1e5721a613f96efb87.tar.gz
socket: extract functionality into ipc package
The current model of files dumped into lib does not follow the general advice not to have "util" classes. Naming the package "lib" does not change that it is a random assortments of functions that have some utility. Extracts socket functionality into it's own package. Signed-off-by: Moritz Poldrack <git@moritz.sh> Acked-by: Robin Jarry <robin@jarry.cc>
Diffstat (limited to 'lib/ipc')
-rw-r--r--lib/ipc/socket.go146
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
+}