diff --git a/caddyhttp/markdown/markdown.go b/caddyhttp/markdown/markdown.go index 6352346a7..ace2d411e 100644 --- a/caddyhttp/markdown/markdown.go +++ b/caddyhttp/markdown/markdown.go @@ -68,8 +68,13 @@ type Config struct { // Template(s) to render with Template *template.Template - // a pair of template's name and its underlying file path - TemplateFiles map[string]string + // a pair of template's name and its underlying file information + TemplateFiles map[string]*cachedFileInfo +} + +type cachedFileInfo struct { + path string + fi os.FileInfo } // ServeHTTP implements the http.Handler interface. diff --git a/caddyhttp/markdown/setup.go b/caddyhttp/markdown/setup.go index 84aa3c76b..45ca71330 100644 --- a/caddyhttp/markdown/setup.go +++ b/caddyhttp/markdown/setup.go @@ -62,7 +62,7 @@ func markdownParse(c *caddy.Controller) ([]*Config, error) { Extensions: make(map[string]struct{}), Template: GetDefaultTemplate(), IndexFiles: []string{}, - TemplateFiles: make(map[string]string), + TemplateFiles: make(map[string]*cachedFileInfo), } // Get the path scope @@ -133,7 +133,9 @@ func loadParams(c *caddy.Controller, mdc *Config) error { return c.Errf("default template parse error: %v", err) } - mdc.TemplateFiles[""] = fpath + mdc.TemplateFiles[""] = &cachedFileInfo{ + path: fpath, + } return nil case 2: fpath := filepath.ToSlash(filepath.Clean(cfg.Root + string(filepath.Separator) + tArgs[1])) @@ -142,7 +144,9 @@ func loadParams(c *caddy.Controller, mdc *Config) error { return c.Errf("template parse error: %v", err) } - mdc.TemplateFiles[tArgs[0]] = fpath + mdc.TemplateFiles[tArgs[0]] = &cachedFileInfo{ + path: fpath, + } return nil } case "templatedir": @@ -164,7 +168,9 @@ func loadParams(c *caddy.Controller, mdc *Config) error { return c.Errf("glob %q failed: %v", pattern, err) } for _, path := range paths { - mdc.TemplateFiles[filepath.Base(path)] = path + mdc.TemplateFiles[filepath.Base(path)] = &cachedFileInfo{ + path: path, + } } return nil default: diff --git a/caddyhttp/markdown/setup_test.go b/caddyhttp/markdown/setup_test.go index 058a0bb0d..0f58f566f 100644 --- a/caddyhttp/markdown/setup_test.go +++ b/caddyhttp/markdown/setup_test.go @@ -77,7 +77,7 @@ func TestMarkdownParse(t *testing.T) { Styles: []string{"/resources/css/blog.css"}, Scripts: []string{"/resources/js/blog.js"}, Template: GetDefaultTemplate(), - TemplateFiles: make(map[string]string), + TemplateFiles: make(map[string]*cachedFileInfo), }}}, {`markdown /blog { ext .md @@ -88,8 +88,8 @@ func TestMarkdownParse(t *testing.T) { ".md": {}, }, Template: setDefaultTemplate("./testdata/tpl_with_include.html"), - TemplateFiles: map[string]string{ - "": "testdata/tpl_with_include.html", + TemplateFiles: map[string]*cachedFileInfo{ + "": {path: "testdata/tpl_with_include.html"}, }, }}}, } diff --git a/caddyhttp/markdown/template.go b/caddyhttp/markdown/template.go index f11e74348..ff8ad9b96 100644 --- a/caddyhttp/markdown/template.go +++ b/caddyhttp/markdown/template.go @@ -17,6 +17,8 @@ package markdown import ( "bytes" "io/ioutil" + "os" + "sync" "text/template" "github.com/mholt/caddy/caddyhttp/httpserver" @@ -41,6 +43,8 @@ func (d Data) Include(filename string, args ...interface{}) (string, error) { return httpserver.ContextInclude(filename, d, d.Root) } +var templateUpdateMu sync.RWMutex + // execTemplate executes a template given a requestPath, template, and metadata func execTemplate(c *Config, mdata metadata.Metadata, meta map[string]string, files []FileInfo, ctx httpserver.Context) ([]byte, error) { mdData := Data{ @@ -51,18 +55,43 @@ func execTemplate(c *Config, mdata metadata.Metadata, meta map[string]string, fi Meta: meta, Files: files, } - templateName := mdata.Template - // reload template on every request for now - // TODO: cache templates by a general plugin - if templateFile, ok := c.TemplateFiles[templateName]; ok { - err := SetTemplate(c.Template, templateName, templateFile) - if err != nil { - return nil, err + + updateTemplate := func() error { + templateUpdateMu.Lock() + defer templateUpdateMu.Unlock() + + templateFile, ok := c.TemplateFiles[templateName] + if !ok { + return nil } + + currentFileInfo, err := os.Lstat(templateFile.path) + if err != nil { + return err + } + + if !fileChanged(currentFileInfo, templateFile.fi) { + return nil + } + + // update template due to file changes + err = SetTemplate(c.Template, templateName, templateFile.path) + if err != nil { + return err + } + + templateFile.fi = currentFileInfo + return nil + } + + if err := updateTemplate(); err != nil { + return nil, err } b := new(bytes.Buffer) + templateUpdateMu.RLock() + defer templateUpdateMu.RUnlock() if err := c.Template.ExecuteTemplate(b, templateName, mdData); err != nil { return nil, err } @@ -70,6 +99,21 @@ func execTemplate(c *Config, mdata metadata.Metadata, meta map[string]string, fi return b.Bytes(), nil } +func fileChanged(new, old os.FileInfo) bool { + // never checked before + if old == nil { + return true + } + + if new.Size() != old.Size() || + new.Mode() != old.Mode() || + new.ModTime() != old.ModTime() { + return true + } + + return false +} + // SetTemplate reads in the template with the filename provided. If the file does not exist or is not parsable, it will return an error. func SetTemplate(t *template.Template, name, filename string) error {