-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.go
162 lines (136 loc) · 3.24 KB
/
server.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
package main
import (
"errors"
"fmt"
"io"
"log"
"math"
"net/http"
"os"
"path/filepath"
"sync"
"time"
"github.com/fsnotify/fsnotify"
)
func filewatch(srcDir string, distDir string) {
// Create new watcher.
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
// Start listening for events.
go watchLoop(watcher, srcDir, distDir)
// Add a path.
err = watcher.Add(srcDir)
if err != nil {
log.Fatal(err)
}
<-make(chan struct{}) // Block forever
}
func watchLoop(w *fsnotify.Watcher, srcDir string, distDir string) {
var (
// Wait 100ms for new events; each new event resets the timer.
waitFor = 100 * time.Millisecond
// Keep track of the timers, as path → timer.
mu sync.Mutex
timers = make(map[string]*time.Timer)
// Callback we run.
buildEvent = func(e fsnotify.Event) {
// Ignore the build directory
baseDir := filepath.Base(e.Name)
if baseDir == distDir {
return
}
Build(srcDir)
// Don't need to remove the timer if you don't have a lot of files.
mu.Lock()
delete(timers, e.Name)
mu.Unlock()
}
)
for {
select {
// Read from Errors.
case err, ok := <-w.Errors:
if !ok { // Channel was closed (i.e. Watcher.Close() was called).
return
}
log.Printf("error: %v", err)
// Read from Events.
case e, ok := <-w.Events:
if !ok { // Channel was closed (i.e. Watcher.Close() was called).
return
}
log.Printf("event: %v", e)
// Get timer.
mu.Lock()
t, ok := timers[e.Name]
mu.Unlock()
// No timer yet, so create one.
if !ok {
t = time.AfterFunc(math.MaxInt64, func() { buildEvent(e) })
t.Stop()
mu.Lock()
timers[e.Name] = t
mu.Unlock()
}
// Reset the timer for this path, so it will start from 100ms again.
t.Reset(waitFor)
}
}
}
func checkLive(w http.ResponseWriter, err error) {
if err != nil {
log.Print(err.Error())
http.Error(w, http.StatusText(500), 500)
return
}
}
func (app App) getLivePage(w http.ResponseWriter, r *http.Request) {
// get the path from the url
cleanPath := filepath.Clean(r.URL.Path)
fp := filepath.Join(app.DistDir, cleanPath+".html")
// return the index page if the path is empty
if cleanPath == "/" {
fp = filepath.Join(app.DistDir, "index.html")
}
// 404 if the requested page doesn't exist
if _, err := os.Stat(fp); os.IsNotExist(err) {
http.NotFound(w, r)
return
}
// 404 if the requested page is a directory
if info, err := os.Stat(fp); err == nil && info.IsDir() {
http.NotFound(w, r)
return
}
// read the markdown file
fileData, err := os.ReadFile(fp)
checkLive(w, err)
w.Header().Set("Content-Type", "text/html")
w.Write(fileData)
}
func ping(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "pong")
}
func LiveServer(srcDir string, port string) {
app, err := InitApp(srcDir)
if err != nil {
log.Fatal(err)
panic(err)
}
go filewatch(app.SrcDir, app.DistDir)
Build(app.SrcDir)
// serve pages
mux := http.NewServeMux()
mux.HandleFunc("/", app.getLivePage)
mux.HandleFunc("/ping", ping)
err = http.ListenAndServe(":"+port, mux)
// handle server closing
if errors.Is(err, http.ErrServerClosed) {
fmt.Printf("server closed\n")
} else if err != nil {
fmt.Printf("server error: %v\n", err)
}
}