package filewatch import ( "io/ioutil" "log" "net/http" "os" "strconv" "time" "github.com/gorilla/websocket" ) // Adapted from https://github.com/gorilla/websocket // Copyright (c) 2013 The Gorilla WebSocket Authors // https://github.com/gorilla/websocket/blob/master/examples/filewatch/main.go const ( // Time allowed to write the file to the client. writeWait = 10 * time.Second // Time allowed to read the next pong message from the client. pongWait = 60 * time.Second // Send pings to client with this period. Must be less than pongWait. pingPeriod = (pongWait * 9) / 10 // Poll file for changes with this period. filePeriod = 10 * time.Second ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } func readFileIfModified(filename string, lastMod time.Time) ([]byte, time.Time, error) { fi, err := os.Stat(filename) if err != nil { return nil, lastMod, err } if !fi.ModTime().After(lastMod) { return nil, lastMod, nil } p, err := ioutil.ReadFile(filename) if err != nil { return nil, fi.ModTime(), err } return p, fi.ModTime(), nil } func reader(ws *websocket.Conn) { defer ws.Close() 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, filename string, lastMod time.Time) { lastError := "" pingTicker := time.NewTicker(pingPeriod) fileTicker := time.NewTicker(filePeriod) defer func() { pingTicker.Stop() fileTicker.Stop() ws.Close() }() for { select { case <-fileTicker.C: var p []byte var err error p, lastMod, err = readFileIfModified(filename, lastMod) if err != nil { if s := err.Error(); s != lastError { lastError = s p = []byte(lastError) } } else { lastError = "" } if p != nil { ws.SetWriteDeadline(time.Now().Add(writeWait)) if err := ws.WriteMessage(websocket.TextMessage, p); err != nil { return } } case <-pingTicker.C: ws.SetWriteDeadline(time.Now().Add(writeWait)) if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil { return } } } } func ServeWebsocketFileWatcher(filename string, 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 } var lastMod time.Time if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err == nil { lastMod = time.Unix(0, n) } go writer(ws, filename, lastMod) reader(ws) }