diff --git a/caddyhugo.go b/caddyhugo.go index d7bd9ac..36730c4 100644 --- a/caddyhugo.go +++ b/caddyhugo.go @@ -75,9 +75,13 @@ func (ch *CaddyHugo) docFilename(orig string) string { // Publish really renders new content into the public directory func (ch *CaddyHugo) Publish() error { + err := ch.persistAllEdits() + if err != nil { + return err + } cmd := exec.Command("hugo") cmd.Dir = ch.Dir - _, err := cmd.CombinedOutput() + _, err = cmd.CombinedOutput() if err != nil { return err } diff --git a/client.go b/client.go index 5c3f536..65ee767 100644 --- a/client.go +++ b/client.go @@ -1,14 +1,14 @@ package caddyhugo import ( - "encoding/base64" "fmt" "io/ioutil" "net/http" - "os" "path" "sync" + "github.com/spf13/afero" + "git.stephensearles.com/stephen/acedoc" "git.stephensearles.com/stephen/idleshut" ) @@ -18,7 +18,7 @@ type editSession struct { docname string filename string doc *acedoc.Document - tmpdir string + tmpfs afero.Fs mtx sync.Mutex } @@ -38,14 +38,11 @@ func (ch *CaddyHugo) newEditSession(docName string) (*editSession, error) { return nil, err } - draftPrefix := fmt.Sprintf("draft-%s", base64.RawURLEncoding.EncodeToString([]byte(docName))) - tmpdir := path.Join(os.TempDir(), draftPrefix) - es := &editSession{ docname: docName, filename: filename, doc: acedoc.NewString(string(contents)), - tmpdir: tmpdir, + tmpfs: afero.NewCopyOnWriteFs(afero.NewOsFs(), afero.NewMemMapFs()), } err = es.doc.LogToFile(path.Join(ch.Dir, "logs", docName)) @@ -56,7 +53,10 @@ func (ch *CaddyHugo) newEditSession(docName string) (*editSession, error) { ch.docs[filename] = es - ch.renderDraft(es) + err = ch.renderDraft(es) + if err != nil { + return nil, err + } return es, nil } @@ -70,8 +70,32 @@ func (ch *CaddyHugo) renderDraft(es *editSession) error { } } - proc = idleshut.New(HugoCmdProcessConfig(ch, es, f)) + cfg, err := HugoInternalProcessConfig(ch, es, f) + if err != nil { + return fmt.Errorf("rendering draft: %v", err) + } + + proc = idleshut.New(cfg) + return proc.Start() +} + +func (ch *CaddyHugo) persistAllEdits() error { + ch.mtx.Lock() + defer ch.mtx.Unlock() + for _, es := range ch.docs { + err := ch.persistEditsForSession(es) + if err != nil { + return err + } + } + return nil +} +func (ch *CaddyHugo) persistEditsForSession(es *editSession) error { + err := afero.WriteFile(afero.NewOsFs(), es.filename, []byte(es.doc.Contents()), 0644) + if err != nil { + return err + } return nil } diff --git a/deltas.go b/deltas.go index 7ab91fd..5955bfb 100644 --- a/deltas.go +++ b/deltas.go @@ -16,11 +16,6 @@ type DeltaConn interface { WriteJSON(v interface{}) error } -const ( - IdleWebsocketTimeout = 10 * time.Minute - WebsocketFileTicker = 1 * time.Second -) - func (ch *CaddyHugo) ObserveLTime(ltime uint64) uint64 { ch.mtx.Lock() diff --git a/http.go b/http.go index 2a17c96..ca46bba 100644 --- a/http.go +++ b/http.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "os" "path" "path/filepath" "strings" @@ -12,6 +13,7 @@ import ( "git.stephensearles.com/stephen/caddy-hugo2/assets" "github.com/mholt/caddy" "github.com/mholt/caddy/caddyhttp/httpserver" + "github.com/spf13/afero" ) func (ch *CaddyHugo) ServeHTTPWithNext(next httpserver.Handler, c *caddy.Controller, w http.ResponseWriter, r *http.Request) (int, error) { @@ -199,7 +201,7 @@ func (ch *CaddyHugo) serveDraft(w http.ResponseWriter, r *http.Request) (int, er r.URL.Path = strings.ToLower(r.URL.Path) prefix := "/hugo/draft/" + encoded - r.URL.Path = r.URL.Path[len(prefix):] + r.URL.Path = path.Join("public", r.URL.Path[len(prefix):]) page := ch.HugoSites.GetContentPage(ch.docFilename(name)) if page == nil { @@ -208,7 +210,22 @@ func (ch *CaddyHugo) serveDraft(w http.ResponseWriter, r *http.Request) (int, er } r.URL.Path = page.RelPermalink() - http.FileServer(http.Dir(docref.tmpdir)).ServeHTTP(w, r) + http.FileServer(aferoHTTP{afero.NewBasePathFs(docref.tmpfs, path.Join(ch.Dir, "public"))}).ServeHTTP(w, r) return 200, nil } + +type aferoHTTP struct { + afero.Fs +} + +func (a aferoHTTP) Open(name string) (http.File, error) { + af, err := a.Fs.Open(name) + if os.IsExist(err) { + err = os.ErrExist + } + if os.IsNotExist(err) { + err = os.ErrNotExist + } + return af, err +} diff --git a/hugo.go b/hugo.go index 36c06cd..f05d8b3 100644 --- a/hugo.go +++ b/hugo.go @@ -2,13 +2,22 @@ package caddyhugo import ( "fmt" - "io/ioutil" - "os" - "os/exec" + "path" + "time" + + "github.com/gohugoio/hugo/deps" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/hugolib" + "github.com/spf13/afero" "git.stephensearles.com/stephen/idleshut" ) +const ( + IdleWebsocketTimeout = 10 * time.Minute + WebsocketFileTicker = 1 * time.Second +) + type HugoInteractor interface { Render(srcdir, workdir string) HugoRenderer } @@ -19,39 +28,37 @@ type HugoRenderer interface { Stop() error } -func HugoCmdProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) idleshut.Config { - cmd := exec.Command("hugo", "--watch", "-D", "-d", es.tmpdir) - cmd.Dir = es.tmpdir +func HugoInternalProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) (idleshut.Config, error) { + + var err error + hugoCfg := &deps.DepsCfg{Fs: hugofs.NewFrom(es.tmpfs, ch.HugoCfg.Cfg)} + fmt.Println(ch.Dir) + hugoCfg.Cfg, err = hugolib.LoadConfig(es.tmpfs, "", path.Join(ch.Dir, "config.toml")) + if err != nil { + return idleshut.Config{}, fmt.Errorf("caddy-hugo: loading site configuration: %v", err) + } + + hugoCfg.Cfg.Set("workingDir", ch.Dir) + hugoSites, err := hugolib.NewHugoSites(*hugoCfg) + if err != nil { + return idleshut.Config{}, fmt.Errorf("caddy-hugo: initializing site: %v", err) + } + + err = hugoSites.Build(hugolib.BuildCfg{ResetState: true}) + if err != nil { + return idleshut.Config{}, fmt.Errorf("caddy-hugo: building site: %v", err) + } return idleshut.Config{ TickDuration: WebsocketFileTicker, MaxIdleTicks: uint(IdleWebsocketTimeout/WebsocketFileTicker) + 1, - Start: func() error { - - err := cmd.Start() - if err != nil { - return fmt.Errorf("error starting hugo: %v", err) - } - - return nil - }, Stop: func() error { - if cmd == nil { - return nil - } - - err := cmd.Process.Signal(os.Interrupt) - if err != nil { - return fmt.Errorf("error signalling hugo to stop: %v", err) - } - err = cmd.Wait() - if err != nil { - return fmt.Errorf("error waiting for hugo to stop: %v", err) - return err - } + ch.persistEditsForSession(es) es.doc.Close() - os.RemoveAll(es.tmpdir) + + ch.mtx.Lock() + defer ch.mtx.Unlock() delete(ch.docs, es.filename) return nil @@ -60,7 +67,17 @@ func HugoCmdProcessConfig(ch *CaddyHugo, es *editSession, touchFn func()) idlesh fmt.Println("error processing draft:", err) }, Tick: func() error { - return ioutil.WriteFile(es.filename, []byte(es.doc.Contents()), 0644) + err := afero.WriteFile(es.tmpfs, es.filename, []byte(es.doc.Contents()), 0644) + if err != nil { + return err + } + + err = hugoSites.Build(hugolib.BuildCfg{ResetState: true}) + if err != nil { + return err + } + + return nil }, - } + }, nil }