-
Notifications
You must be signed in to change notification settings - Fork 0
/
ws.go
139 lines (123 loc) · 2.7 KB
/
ws.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
package main
import (
"log"
"net/http"
"path/filepath"
"strings"
"time"
"github.com/fsnotify/fsnotify"
"github.com/gorilla/websocket"
)
const (
writeWait = 10 * time.Second
pongWait = 60 * time.Second
pingPeriod = (pongWait * 9) / 10
)
var (
upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
fmodChan = make(chan chan []byte)
)
func reader(ws *websocket.Conn, closed chan bool) {
defer ws.Close()
defer func() { closed <- true }()
ws.SetReadLimit(512)
ws.SetReadDeadline(time.Now().Add(pongWait))
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
_, _, err := ws.ReadMessage()
if err != nil {
break
}
}
}
func writer(ws *websocket.Conn, modified chan []byte, closed chan bool) {
pingTicker := time.NewTicker(pingPeriod)
defer func() {
pingTicker.Stop()
ws.Close()
}()
for {
select {
case fn := <-modified:
ws.SetWriteDeadline(time.Now().Add(writeWait))
if err := ws.WriteMessage(websocket.TextMessage, fn); err != nil {
return
}
case <-pingTicker.C:
ws.SetWriteDeadline(time.Now().Add(writeWait))
if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
return
}
case <-closed:
return
}
}
}
func serveWs(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
if _, ok := err.(websocket.HandshakeError); !ok {
log.Println(err)
}
return
}
closed := make(chan bool)
modified := make(chan []byte)
fmodChan <- modified
go writer(ws, modified, closed)
reader(ws, closed)
}
func initWatcher() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
// empty structs should occupy no memory
workers := make(map[chan []byte]struct{})
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("modified file:", filepath.Base(event.Name))
fn := strings.TrimRight(filepath.Base(event.Name), filepath.Ext(event.Name))
if fn == "" {
continue
}
for c := range workers {
select {
case c <- []byte(fn):
break
default:
// we can't check if it's actually closed
// but assume it is if it blocks
// TODO: fix that, because this will lead
// to hard to diagnose bugs.
delete(workers, c)
}
}
}
case err, ok := <-watcher.Errors:
if !ok {
log.Fatalln(err)
}
log.Println("error:", err)
case c := <-fmodChan:
workers[c] = struct{}{}
}
}
}()
err = watcher.Add(*path)
if err != nil {
log.Fatal(err)
}
<-done
}