aboutsummaryrefslogtreecommitdiffstats
path: root/lib/socket.go
blob: 5a41d9102d220f49807bb65aa76be5e53b1b942c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package lib

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
}