Skip to content
This repository was archived by the owner on Aug 25, 2022. It is now read-only.

Commit 06af30c

Browse files
author
Ricki Hastings
committed
Initial commit, basic functionality
0 parents  commit 06af30c

File tree

6 files changed

+347
-0
lines changed

6 files changed

+347
-0
lines changed

connection.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"encoding/json"
7+
"github.com/gorilla/websocket"
8+
)
9+
10+
type connection struct {
11+
websocket *websocket.Conn
12+
files []string
13+
send chan []byte
14+
}
15+
16+
func stringInSlice(a string, list []string) bool {
17+
for _, b := range list {
18+
if b == a {
19+
return true
20+
}
21+
}
22+
23+
return false
24+
}
25+
26+
func (c *connection) reader() {
27+
for {
28+
_, in, err := c.websocket.ReadMessage()
29+
if err != nil {
30+
fmt.Println(err)
31+
break
32+
}
33+
34+
var m message
35+
e := json.Unmarshal(in, &m)
36+
37+
if e != nil {
38+
fmt.Println(e)
39+
break
40+
}
41+
42+
c.handle(&m)
43+
}
44+
45+
c.websocket.Close()
46+
}
47+
48+
func (c *connection) writer() {
49+
for in := range c.send {
50+
err := c.websocket.WriteMessage(websocket.TextMessage, in)
51+
if err != nil {
52+
fmt.Println(err)
53+
break
54+
}
55+
}
56+
57+
c.websocket.Close()
58+
}
59+
60+
func (c *connection) handle(m *message) {
61+
if m.Command == "watching" {
62+
c.files = strings.Split(m.Data, ",")
63+
64+
var out message = message{"watching", "true"}
65+
output, jerr := json.Marshal(out)
66+
67+
if jerr != nil {
68+
fmt.Println(jerr)
69+
return
70+
}
71+
72+
c.send <- output
73+
// create a response and send it back
74+
}
75+
}

hub.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package main
2+
3+
type hub struct {
4+
// Registered connections.
5+
connections map[*connection]bool
6+
7+
// Inbound messages from the connections.
8+
broadcast chan []byte
9+
10+
// Register requests from the connections.
11+
register chan *connection
12+
13+
// Unregister requests from connections.
14+
unregister chan *connection
15+
}
16+
17+
var h = hub{
18+
broadcast: make(chan []byte),
19+
register: make(chan *connection),
20+
unregister: make(chan *connection),
21+
connections: make(map[*connection]bool),
22+
}
23+
24+
func (h *hub) run() {
25+
for {
26+
select {
27+
case c := <-h.register:
28+
h.connections[c] = true
29+
case c := <-h.unregister:
30+
delete(h.connections, c)
31+
close(c.send)
32+
case m := <-h.broadcast:
33+
for c := range h.connections {
34+
select {
35+
case c.send <- m:
36+
default:
37+
delete(h.connections, c)
38+
close(c.send)
39+
}
40+
}
41+
}
42+
}
43+
}

main.go

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"fmt"
6+
"strings"
7+
"net/http"
8+
"encoding/json"
9+
"github.com/gorilla/websocket"
10+
"github.com/go-martini/martini"
11+
"github.com/howeyc/fsnotify"
12+
)
13+
14+
type message struct {
15+
Command string `json:"command"`
16+
Data string `json:"data"`
17+
}
18+
19+
var (
20+
folder string = "public"
21+
upgrader = &websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}
22+
)
23+
24+
func main() {
25+
go h.run()
26+
SetupServices()
27+
}
28+
29+
func wsHandler(w http.ResponseWriter, r *http.Request) {
30+
ws, err := upgrader.Upgrade(w, r, nil)
31+
if err != nil {
32+
return
33+
}
34+
35+
c := &connection{send: make(chan []byte, 256), websocket: ws}
36+
h.register <- c
37+
38+
defer func() {
39+
h.unregister <- c
40+
}()
41+
42+
go c.writer()
43+
c.reader()
44+
}
45+
46+
func fileModified(ev *fsnotify.FileEvent) {
47+
fn := strings.Replace(ev.Name, folder, "", 1)[1:]
48+
49+
for v := range h.connections {
50+
if stringInSlice(fn, v.files) {
51+
fmt.Println("Reloading", fn, "notifying clients")
52+
53+
var out message = message{"changed", fn}
54+
output, jerr := json.Marshal(out)
55+
56+
if jerr != nil {
57+
fmt.Println(jerr)
58+
return
59+
}
60+
61+
v.send <- output
62+
// create a message to send to the client to tell them to reload x file
63+
}
64+
}
65+
}
66+
67+
func SetupServices() {
68+
c := make(chan bool)
69+
70+
go func() {
71+
SetupMartini()
72+
c <- true
73+
}()
74+
// here we setup martini
75+
76+
SetupWatcher()
77+
// here we can setup fsnotify without worrying about martini blocking us
78+
79+
<-c
80+
// wait till martini is done
81+
}
82+
83+
func SetupMartini() {
84+
m := martini.Classic()
85+
m.Get("/websocket", wsHandler)
86+
m.Run()
87+
}
88+
89+
func SetupWatcher() {
90+
watcher, err := fsnotify.NewWatcher()
91+
if err != nil {
92+
log.Fatal(err)
93+
}
94+
// if there is any errors back out
95+
96+
done := make(chan bool)
97+
98+
go func() {
99+
for {
100+
select {
101+
case ev := <-watcher.Event:
102+
if ev.IsModify() {
103+
fileModified(ev)
104+
}
105+
case err := <-watcher.Error:
106+
fmt.Println("error:", err)
107+
}
108+
}
109+
}()
110+
// setup the event loop to keep us going
111+
112+
err = watcher.Watch(folder)
113+
if err != nil {
114+
log.Fatal(err)
115+
}
116+
// setup the watcher and wait for errors
117+
118+
<-done
119+
// wait till we're done
120+
121+
watcher.Close()
122+
}

public/cssmate.js

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
(function() {
2+
/**
3+
* cssmate
4+
*
5+
* Ricki Hastings (c) 2014 - https://github.com/rickihastings
6+
* https://github.com/rickihastings/cssmate
7+
*/
8+
9+
var connection;
10+
var files = [];
11+
var tags = {};
12+
var config = {
13+
disabled: false,
14+
hostname: '10.0.33.34',
15+
port: 3000,
16+
}
17+
18+
function init() {
19+
getCSSDeps();
20+
websocketConnect();
21+
}
22+
23+
function getCSSDeps() {
24+
var links = document.getElementsByTagName('link');
25+
26+
for (var i = 0, len = links && links.length; i < len; ++i) {
27+
var link = links[i],
28+
filename = link.href.replace(link.baseURI, '');
29+
30+
tags[filename] = link;
31+
files.push(filename);
32+
}
33+
// find out css dependencies
34+
}
35+
36+
function websocketConnect() {
37+
connection = new WebSocket('ws://' + config.hostname + ':' + config.port + '/websocket');
38+
39+
connection.onopen = function() {
40+
connection.send(JSON.stringify({command: 'watching', data: files.join(',')}));
41+
}
42+
// when the connection is open, send some data to the server
43+
44+
connection.onerror = function (error) {
45+
websocketConnect();
46+
}
47+
// log errors
48+
49+
connection.onmessage = function (e) {
50+
var json;
51+
52+
try {
53+
json = JSON.parse(e.data);
54+
} catch (e) {
55+
console.log('Failed to parse output, your cssmate binary is botched!');
56+
}
57+
58+
if (!json || !json.command || !json.data) {
59+
return;
60+
}
61+
62+
switch (json.command) {
63+
case 'watching':
64+
if (!json.data) {
65+
config.disabled = true;
66+
}
67+
break;
68+
case 'changed':
69+
reloadStylesheet(json.data);
70+
break;
71+
}
72+
}
73+
// log messages from the server
74+
}
75+
76+
function reloadStylesheet(filename) {
77+
if (!tags[filename]) {
78+
console.log('Apparently', filename, 'has been changed but we don\'t have any record of having it?')
79+
return;
80+
}
81+
82+
tags[filename].href = filename + '?id=' + new Date().getMilliseconds();
83+
// we whack a timestamp on the end so the browser knows its different and reloads it
84+
}
85+
86+
init();
87+
})();

public/index.html

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Test</title>
5+
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
9+
<link rel="stylesheet" type="text/css" href="main.css" id="test" />
10+
</head>
11+
<body>
12+
<h3>Hi there</h3>
13+
Edit the css file in public/main.css to see my CSS update in realtime :)
14+
</body>
15+
<script type="text/javascript" src="cssmate.js"></script>
16+
</html>

public/main.css

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
body {
2+
background: black;
3+
color: white;
4+
}

0 commit comments

Comments
 (0)