markdown: only update template when file changed (#1909)

Signed-off-by: Tw <tw19881113@gmail.com>
This commit is contained in:
Tw 2017-11-05 01:36:59 +08:00 committed by Toby Allen
parent 689591ef01
commit 5cca9cc18e
4 changed files with 71 additions and 16 deletions

View File

@ -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.

View File

@ -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:

View File

@ -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"},
},
}}},
}

View File

@ -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 {