-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathipc.go
124 lines (98 loc) · 2.76 KB
/
ipc.go
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
package bspc
import (
"bytes"
"errors"
"fmt"
"io"
"net"
"strings"
)
var errInvalidUnixSocket = errors.New("invalid unix socket")
type ipcCommand string
// intoMessage adds NULL to the end of every word in the command.
// This is necessary because bspwm's C code expects it.
func (ic ipcCommand) intoMessage() string {
var msg string
words := strings.Split(string(ic), " ")
for _, w := range words {
msg += w + "\x00"
}
return msg
}
// TODO: Try using monkey-patching to facilitate unit testing for this: var resolveAddr = func() {//...} and then replacing that in the test file and here.
func newUnixSocketAddress(path string) (*net.UnixAddr, error) {
addr, err := net.ResolveUnixAddr("unixgram", path)
if err != nil {
return nil, fmt.Errorf("failed to resolve unix address: %v", err)
}
return addr, nil
}
type ipcConn struct {
socketAddr *net.UnixAddr
socketConn *net.UnixConn
}
func newIPCConn(unixSocketAddr *net.UnixAddr) (ipcConn, error) {
// TODO: For this line too
conn, err := net.DialUnix("unix", nil, unixSocketAddr)
if err != nil {
return ipcConn{}, fmt.Errorf("%w: %v", errInvalidUnixSocket, err)
}
return ipcConn{
socketAddr: unixSocketAddr,
socketConn: conn,
}, nil
}
func (ipc ipcConn) Send(cmd ipcCommand) error {
// TODO: For this line too
if _, err := ipc.socketConn.Write([]byte(cmd.intoMessage())); err != nil {
return fmt.Errorf("failed to send message: %v", err)
}
return nil
}
func (ipc ipcConn) Receive() ([]byte, error) {
const maxBufferSize = 512
var msg []byte
for buffer := make([]byte, maxBufferSize); ; buffer = make([]byte, maxBufferSize) {
// TODO: For this line too
_, _, err := ipc.socketConn.ReadFromUnix(buffer)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, fmt.Errorf("failed to receive response: %v", err)
}
msg = append(msg, buffer...)
}
return bytes.Trim(msg, "\x00"), nil
}
func (ipc ipcConn) ReceiveAsync() (chan []byte, chan error) {
var (
resCh = make(chan []byte)
errCh = make(chan error, 1)
)
const maxBufferSize = 512
go func(resCh chan []byte, errCh chan error) {
for buffer := make([]byte, maxBufferSize); ; buffer = make([]byte, maxBufferSize) {
_, _, err := ipc.socketConn.ReadFromUnix(buffer)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
errCh <- fmt.Errorf("failed to receive response: %v", err)
break
}
if len(buffer) == 0 {
errCh <- errors.New("response was empty")
break
}
buffer = bytes.Trim(buffer, "\x00")
for _, res := range bytes.Split(buffer, []byte("\n")) { // This is needed because events sent in quick succession will be "glued" together, sometimes.
resCh <- res
}
}
}(resCh, errCh)
return resCh, errCh
}
func (ipc ipcConn) Close() error {
return ipc.socketConn.Close()
}