better idle and error handling

pull/8/head
Stephen Searles 8 years ago
parent d683b94c77
commit 4a5a20e49e
  1. 25
      caddyhugo.go
  2. 139
      templates.go
  3. 1
      testdir/caddyfile

@ -8,13 +8,13 @@ import (
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
_ "net/http/pprof"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"git.stephensearles.com/stephen/acedoc" "git.stephensearles.com/stephen/acedoc"
@ -62,17 +62,20 @@ type CaddyHugo struct {
func (ch *CaddyHugo) ObserveLTime(ltime uint64) uint64 { func (ch *CaddyHugo) ObserveLTime(ltime uint64) uint64 {
ch.mtx.Lock() ch.mtx.Lock()
defer ch.mtx.Unlock()
if ch.ltime < ltime { if ch.ltime < ltime {
ch.ltime = ltime ch.ltime = ltime
} }
ch.mtx.Unlock()
return ch.LTime() return ch.LTime()
} }
func (ch *CaddyHugo) LTime() uint64 { func (ch *CaddyHugo) LTime() uint64 {
return atomic.AddUint64(&ch.ltime, 1) ch.mtx.Lock()
defer ch.mtx.Unlock()
ch.ltime++
return ch.ltime
} }
func (ch *CaddyHugo) Setup(c *caddy.Controller) error { func (ch *CaddyHugo) Setup(c *caddy.Controller) error {
@ -261,6 +264,7 @@ func (ch *CaddyHugo) doc(r *http.Request) (*docref, error) {
defer ch.mtx.Unlock() defer ch.mtx.Unlock()
name := r.URL.Path[len("/hugo/edit/"):] name := r.URL.Path[len("/hugo/edit/"):]
name = filepath.Join(ch.Site.Root, name)
_, ok := ch.docs[name] _, ok := ch.docs[name]
if !ok { if !ok {
@ -363,7 +367,20 @@ func (ch *CaddyHugo) DeltaWebsocket(w http.ResponseWriter, r *http.Request) (int
return http.StatusBadRequest, err return http.StatusBadRequest, err
} }
const idlePing = 15 * time.Second
const idlePingShort = 1 * time.Millisecond
var timer *time.Timer
timer = time.AfterFunc(idlePing, func() {
conn.WriteJSON(Message{
Deltas: []acedoc.Delta{},
LTime: ch.LTime(),
})
timer.Reset(idlePing)
})
client := doc.doc.Client(acedoc.DeltaHandlerFunc(func(ds []acedoc.Delta) error { client := doc.doc.Client(acedoc.DeltaHandlerFunc(func(ds []acedoc.Delta) error {
timer.Reset(idlePing)
err := conn.WriteJSON(Message{ err := conn.WriteJSON(Message{
Deltas: ds, Deltas: ds,
LTime: ch.LTime(), LTime: ch.LTime(),
@ -390,11 +407,13 @@ func (ch *CaddyHugo) DeltaWebsocket(w http.ResponseWriter, r *http.Request) (int
fmt.Println(message) fmt.Println(message)
ch.ObserveLTime(message.LTime) ch.ObserveLTime(message.LTime)
timer.Reset(idlePingShort)
err = client.PushDeltas(message.Deltas...) err = client.PushDeltas(message.Deltas...)
if err != nil { if err != nil {
return http.StatusBadRequest, err return http.StatusBadRequest, err
} }
} }
} }

@ -17,10 +17,19 @@ func (t tmplData) Content() ([]string, error) {
var files []string var files []string
err := filepath.Walk(path.Join(t.Site.Root, "content"), func(name string, fi os.FileInfo, err error) error { err := filepath.Walk(path.Join(t.Site.Root, "content"), func(name string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if fi.IsDir() { if fi.IsDir() {
return nil return nil
} }
name, err = filepath.Rel(t.Site.Root, name)
if err != nil {
return err
}
files = append(files, name) files = append(files, name)
return nil return nil
}) })
@ -34,6 +43,33 @@ func (t tmplData) Content() ([]string, error) {
} }
func (t tmplData) ContentTypes() ([]string, error) { func (t tmplData) ContentTypes() ([]string, error) {
nameMap := map[string]struct{}{"default": struct{}{}}
names, err := t.contentTypes(path.Join(t.Site.Root, "archetypes"))
if err != nil {
return nil, err
}
for _, name := range names {
nameMap[name] = struct{}{}
}
names, err = t.contentTypes(path.Join(t.Site.Root, "themes", "hugo-theme-minos", "archetypes"))
if err != nil {
return nil, err
}
for _, name := range names {
nameMap[name] = struct{}{}
}
var out []string
for name := range nameMap {
out = append(out, name[:len(name)-len(filepath.Ext(name))])
}
return out, nil
}
func (t tmplData) contentTypes(dir string) ([]string, error) {
layoutDir, err := os.Open(path.Join(t.Site.Root, "archetypes")) layoutDir, err := os.Open(path.Join(t.Site.Root, "archetypes"))
if err != nil { if err != nil {
fmt.Println("opening layout dir", err) fmt.Println("opening layout dir", err)
@ -47,12 +83,7 @@ func (t tmplData) ContentTypes() ([]string, error) {
return nil, err return nil, err
} }
out := []string{"default"} return names, nil
for _, name := range names {
out = append(out, name[:len(name)-len(filepath.Ext(name))])
}
return out, nil
} }
type tmplData struct { type tmplData struct {
@ -109,28 +140,64 @@ var EditPage = `<html>
} }
</style> </style>
<link rel="stylesheet" href="/hugo/simplemde.css" /> <link rel="stylesheet" href="/hugo/simplemde.css" />
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/moment"></script>
<body> <body>
<div id="container" >
<div id="lastSaved">
<span v-if="ltime >serverLtime && (sendQueue.length > 0 || sentRecently.length > 0)">last saved ${ lastSaved.from(now) }, saving</span>
<span v-else>saved</span>
<span v-if="connectionError">, ${connectionError}</span>
</div>
<textarea id="editor">{{ .LoadContent }}</textarea> <textarea id="editor">{{ .LoadContent }}</textarea>
<div id="draft"><iframe src="{{ .IframeSource }}">Loading draft...</iframe></div> <div id="draft"><!--<iframe src="{{ .IframeSource }}">Loading draft...</iframe>--></div>
</div>
<script> <script>
var ltime = 0; var uiBindings = {
ltime: 0,
serverLtime: 0,
lastSaved: moment(),
now: moment(),
connectionError: null,
sendQueue: [],
sentRecently: [],
};
var app = new Vue({
el: "#container",
data: uiBindings,
delimiters: ["${", "}"],
});
function getLtime() { function getLtime() {
ltime++ uiBindings.ltime++
return ltime return uiBindings.ltime
}
function observeServer(l) {
uiBindings.serverLtime = l;
while (uiBindings.sentRecently.length > 0 && uiBindings.sentRecently[0].ltime < l) {
uiBindings.sentRecently.pop();
}
observe(l);
} }
function observe(l) { function observe(l) {
if (l > ltime) { if (l > uiBindings.ltime) {
ltime = l; uiBindings.now = moment();
uiBindings.lastSaved = moment();
uiBindings.ltime = l;
} }
} }
var editorElem = document.getElementById("editor"); var editorElem = document.getElementById("editor");
var editor = new SimpleMDE({element: editorElem, forceSync: true}); var editor = new SimpleMDE({element: editorElem, forceSync: true});
// Create WebSocket connection. // Create WebSocket connection.
const socket = new WebSocket('ws://' + location.host + location.pathname); var socket = connect();
var iframe = document.querySelector("#draft > iframe"); var iframe = document.querySelector("#draft > iframe");
@ -144,12 +211,30 @@ var EditPage = `<html>
iframe.contentWindow.location.reload(); iframe.contentWindow.location.reload();
} }
} }
}, 50); uiBindings.now = moment();
if (uiBindings.connectionError) {
socket = connect();
} else if (uiBindings.sendQueue.length > 0) {
var ltime = getLtime();
socket.send(JSON.stringify({
"deltas": uiBindings.sendQueue,
"ltime": ltime,
}));
uiBindings.sentRecently.push({
"ltime": ltime,
"sent": uiBindings.sendQueue,
});
uiBindings.sendQueue = [];
}
}, 500);
function connect() {
const socket = new WebSocket((location.protocol == "https:" ? 'wss://' : 'ws://') + location.host + location.pathname);
// Listen for messages // Listen for messages
socket.addEventListener('message', function (event) { socket.addEventListener('message', function (event) {
var message = JSON.parse(event.data); var message = JSON.parse(event.data);
observe(message.ltime); observeServer(message.ltime);
var deltas = []; var deltas = [];
deltas.push.apply(deltas, message.deltas); deltas.push.apply(deltas, message.deltas);
@ -174,10 +259,29 @@ var EditPage = `<html>
}) })
}); });
socket.addEventListener('open', function () {
uiBindings.connectionError = null;
});
socket.addEventListener('close', function () {
if (!uiBindings.connectionError) {
getLtime();
uiBindings.connectionError = "server connection closed, reconnecting...";
}
});
socket.addEventListener('error', function (err) { socket.addEventListener('error', function (err) {
if (!uiBindings.connectionError) {
uiBindings.connectionError = err;
getLtime();
}
console.log(err); console.log(err);
}); });
return socket;
}
editor.codemirror.on("change", function (cm, cmDelta) { editor.codemirror.on("change", function (cm, cmDelta) {
if (cmDelta.origin == "dontreflect") { if (cmDelta.origin == "dontreflect") {
return; return;
@ -187,10 +291,7 @@ var EditPage = `<html>
console.log(cmDelta, "=>", aceDelta) console.log(cmDelta, "=>", aceDelta)
sawChanges = sawChangesBumpsTo; sawChanges = sawChangesBumpsTo;
socket.send(JSON.stringify({ uiBindings.sendQueue.push(aceDelta);
"deltas": [aceDelta],
"ltime": getLtime(),
}))
}) })
function cmDeltaToAce(cmDelta) { function cmDeltaToAce(cmDelta) {

@ -2,4 +2,5 @@ localhost:8080 {
hugo hugo
root ./testsite root ./testsite
errors { * } errors { * }
pprof
} }

Loading…
Cancel
Save