-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathclientserver.go
169 lines (149 loc) · 3.71 KB
/
clientserver.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package kpmenulib
import (
"encoding/gob"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
"path/filepath"
"time"
)
// Packet is the data sent by the client to the server listener
type Packet struct {
CliArguments []string
}
// StartClient sends a packet to the server listener
func StartClient() error {
port, err := getPort()
if err != nil {
return err
}
conn, err := net.Dial("tcp", "localhost:"+port)
if err != nil {
return err
}
defer conn.Close()
// Send the packet
enc := gob.NewEncoder(conn)
err = enc.Encode(Packet{CliArguments: os.Args[1:]})
return err
}
// StartServer starts to listen for client packets
func StartServer(m *Menu) (err error) {
if m.Configuration.Flags.Daemon {
log.Printf("Executing as daemon")
}
if m.Configuration.General.NoCache && !m.Configuration.Flags.Daemon {
// Directly execute kpmenu
if fatal := Execute(m); fatal == true {
os.Exit(1) // Set exit code to 1 and exit
}
} else {
// Handle packet request
handlePacket := func(packet Packet) bool {
log.Printf("received a client call with args \"%v\"", packet.CliArguments)
m.CliArguments = packet.CliArguments
return Show(m)
}
// Execute kpmenu for the first time, if not a daemon
exit := false
if !m.Configuration.Flags.Daemon {
exit = Execute(m)
}
// If exit is false (cache on) listen for client calls
if !exit {
err = setupListener(m, handlePacket)
}
}
return
}
func setupListener(m *Menu, handlePacket func(Packet) bool) error {
// Listen for client calls
listener, err := net.Listen("tcp", ":0")
if err != nil {
return err
}
tcpListener := listener.(*net.TCPListener)
defer tcpListener.Close()
// Get used port
_, port, _ := net.SplitHostPort(listener.Addr().String())
// Save port
if err := savePort(port); err != nil {
return err
}
exit := false
for !exit {
if !m.Configuration.Flags.Daemon {
// If not a daemon prepare cache time
remainingCacheTime := m.Configuration.General.CacheTimeout - int(time.Now().Sub(m.CacheStart).Seconds())
tcpListener.SetDeadline(time.Now().Add(time.Second * time.Duration(remainingCacheTime)))
}
// Listen to calls
conn, err := listener.Accept()
if err != nil {
netErr := err.(*net.OpError)
if netErr.Timeout() {
log.Print("cache timed out")
return nil
}
return err
}
defer conn.Close()
// Go routine to handle input
ch := make(chan Packet)
errCh := make(chan error)
go func(ch chan Packet, errCh chan error) {
dec := gob.NewDecoder(conn)
var packet Packet
err := dec.Decode(&packet)
if err != nil {
if err != io.EOF {
errCh <- err
} else {
return
}
}
ch <- packet
}(ch, errCh)
// Handle received input
timeout := time.Tick(3 * time.Second) // Timeout of 3 seconds - to avoid problems
select {
case packet := <-ch:
// Received the data
fatal := handlePacket(packet)
exit = (fatal && !m.Configuration.Flags.Daemon)
break
case err := <-errCh:
// Received an error
return err
case <-timeout:
// Timed out
log.Printf("received request is timed out")
}
}
return nil
}
func makeCacheFolder() error {
if err := os.MkdirAll(filepath.Join(os.Getenv("HOME"), ".cache/kpmenu/"), 0755); err != nil {
return fmt.Errorf("failed to make cache folder: %v", err)
}
return nil
}
func savePort(port string) (err error) {
if err = makeCacheFolder(); err == nil {
if err = ioutil.WriteFile(
filepath.Join(os.Getenv("HOME"), ".cache/kpmenu/server.port"),
[]byte(port),
0644,
); err != nil {
return fmt.Errorf("failed to make server port cache file: %v", err)
}
}
return err
}
func getPort() (string, error) {
data, err := ioutil.ReadFile(filepath.Join(os.Getenv("HOME"), ".cache/kpmenu/server.port"))
return string(data), err
}