From 38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Thu, 12 May 2016 23:56:07 -0400 Subject: Moving to glide --- vendor/github.com/mattermost/rsc/blog/post/post.go | 534 +++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 vendor/github.com/mattermost/rsc/blog/post/post.go (limited to 'vendor/github.com/mattermost/rsc/blog/post/post.go') diff --git a/vendor/github.com/mattermost/rsc/blog/post/post.go b/vendor/github.com/mattermost/rsc/blog/post/post.go new file mode 100644 index 000000000..beae651f8 --- /dev/null +++ b/vendor/github.com/mattermost/rsc/blog/post/post.go @@ -0,0 +1,534 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package post + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "fmt" + "html/template" + "net/http" + "os" + "path" + "runtime/debug" + "sort" + "strings" + "time" + + "github.com/mattermost/rsc/appfs/fs" + "github.com/mattermost/rsc/appfs/proto" + "github.com/mattermost/rsc/blog/atom" +) + +func init() { + fs.Root = os.Getenv("HOME") + "/app/" + http.HandleFunc("/", serve) + http.Handle("/feeds/posts/default", http.RedirectHandler("/feed.atom", http.StatusFound)) +} + +var funcMap = template.FuncMap{ + "now": time.Now, + "date": timeFormat, +} + +func timeFormat(fmt string, t time.Time) string { + return t.Format(fmt) +} + +type blogTime struct { + time.Time +} + +var timeFormats = []string{ + time.RFC3339, + "Monday, January 2, 2006", + "January 2, 2006 15:00 -0700", +} + +func (t *blogTime) UnmarshalJSON(data []byte) (err error) { + str := string(data) + for _, f := range timeFormats { + tt, err := time.Parse(`"`+f+`"`, str) + if err == nil { + t.Time = tt + return nil + } + } + return fmt.Errorf("did not recognize time: %s", str) +} + +type PostData struct { + FileModTime time.Time + FileSize int64 + + Title string + Date blogTime + Name string + OldURL string + Summary string + Favorite bool + + Reader []string + + PlusAuthor string // Google+ ID of author + PlusPage string // Google+ Post ID for comment post + PlusAPIKey string // Google+ API key + PlusURL string + HostURL string // host URL + Comments bool + + article string +} + +func (d *PostData) canRead(user string) bool { + for _, r := range d.Reader { + if r == user { + return true + } + } + return false +} + +func (d *PostData) IsDraft() bool { + return d.Date.IsZero() || d.Date.After(time.Now()) +} + +// To find PlusPage value: +// https://www.googleapis.com/plus/v1/people/116810148281701144465/activities/public?key=AIzaSyB_JO6hyAJAL659z0Dmu0RUVVvTx02ZPMM +// + +const owner = "rsc@swtch.com" +const plusRsc = "116810148281701144465" +const plusKey = "AIzaSyB_JO6hyAJAL659z0Dmu0RUVVvTx02ZPMM" +const feedID = "tag:research.swtch.com,2012:research.swtch.com" + +var replacer = strings.NewReplacer( + "⁰", "0", + "¹", "1", + "²", "2", + "³", "3", + "⁴", "4", + "⁵", "5", + "⁶", "6", + "⁷", "7", + "⁸", "8", + "⁹", "9", + "ⁿ", "n", + "₀", "0", + "₁", "1", + "₂", "2", + "₃", "3", + "₄", "4", + "₅", "5", + "₆", "6", + "₇", "7", + "₈", "8", + "₉", "9", + "``", "“", + "''", "”", +) + +func serve(w http.ResponseWriter, req *http.Request) { + ctxt := fs.NewContext(req) + + defer func() { + if err := recover(); err != nil { + var buf bytes.Buffer + fmt.Fprintf(&buf, "panic: %s\n\n", err) + buf.Write(debug.Stack()) + ctxt.Criticalf("%s", buf.String()) + + http.Error(w, buf.String(), 500) + } + }() + + p := path.Clean("/" + req.URL.Path) +/* + if strings.Contains(req.Host, "appspot.com") { + http.Redirect(w, req, "http://research.swtch.com" + p, http.StatusFound) + } +*/ + if p != req.URL.Path { + http.Redirect(w, req, p, http.StatusFound) + return + } + + if p == "/feed.atom" { + atomfeed(w, req) + return + } + + if strings.HasPrefix(p, "/20") && strings.Contains(p[1:], "/") { + // Assume this is an old-style URL. + oldRedirect(ctxt, w, req, p) + } + + user := ctxt.User() + isOwner := ctxt.User() == owner || len(os.Args) >= 2 && os.Args[1] == "LISTEN_STDIN" + if p == "" || p == "/" || p == "/draft" { + if p == "/draft" && user == "?" { + ctxt.Criticalf("/draft loaded by %s", user) + notfound(ctxt, w, req) + return + } + toc(w, req, p == "/draft", isOwner, user) + return + } + + draft := false + if strings.HasPrefix(p, "/draft/") { + if user == "?" { + ctxt.Criticalf("/draft loaded by %s", user) + notfound(ctxt, w, req) + return + } + draft = true + p = p[len("/draft"):] + } + + if strings.Contains(p[1:], "/") { + notfound(ctxt, w, req) + return + } + + if strings.Contains(p, ".") { + // Let Google's front end servers cache static + // content for a short amount of time. + httpCache(w, 5*time.Minute) + ctxt.ServeFile(w, req, "blog/static/"+p) + return + } + + // Use just 'blog' as the cache path so that if we change + // templates, all the cached HTML gets invalidated. + var data []byte + pp := "bloghtml:"+p + if draft && !isOwner { + pp += ",user="+user + } + if key, ok := ctxt.CacheLoad(pp, "blog", &data); !ok { + meta, article, err := loadPost(ctxt, p, req) + if err != nil || meta.IsDraft() != draft || (draft && !isOwner && !meta.canRead(user)) { + ctxt.Criticalf("no %s for %s", p, user) + notfound(ctxt, w, req) + return + } + t := mainTemplate(ctxt) + template.Must(t.New("article").Parse(article)) + + var buf bytes.Buffer + meta.Comments = true + if err := t.Execute(&buf, meta); err != nil { + panic(err) + } + data = buf.Bytes() + ctxt.CacheStore(key, data) + } + w.Write(data) +} + +func notfound(ctxt *fs.Context, w http.ResponseWriter, req *http.Request) { + var buf bytes.Buffer + var data struct { + HostURL string + } + data.HostURL = hostURL(req) + t := mainTemplate(ctxt) + if err := t.Lookup("404").Execute(&buf, &data); err != nil { + panic(err) + } + w.WriteHeader(404) + w.Write(buf.Bytes()) +} + +func mainTemplate(c *fs.Context) *template.Template { + t := template.New("main") + t.Funcs(funcMap) + + main, _, err := c.Read("blog/main.html") + if err != nil { + panic(err) + } + style, _, _ := c.Read("blog/style.html") + main = append(main, style...) + _, err = t.Parse(string(main)) + if err != nil { + panic(err) + } + return t +} + +func loadPost(c *fs.Context, name string, req *http.Request) (meta *PostData, article string, err error) { + meta = &PostData{ + Name: name, + Title: "TITLE HERE", + PlusAuthor: plusRsc, + PlusAPIKey: plusKey, + HostURL: hostURL(req), + } + + art, fi, err := c.Read("blog/post/" + name) + if err != nil { + return nil, "", err + } + if bytes.HasPrefix(art, []byte("{\n")) { + i := bytes.Index(art, []byte("\n}\n")) + if i < 0 { + panic("cannot find end of json metadata") + } + hdr, rest := art[:i+3], art[i+3:] + if err := json.Unmarshal(hdr, meta); err != nil { + panic(fmt.Sprintf("loading %s: %s", name, err)) + } + art = rest + } + meta.FileModTime = fi.ModTime + meta.FileSize = fi.Size + + return meta, replacer.Replace(string(art)), nil +} + +type byTime []*PostData + +func (x byTime) Len() int { return len(x) } +func (x byTime) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byTime) Less(i, j int) bool { return x[i].Date.Time.After(x[j].Date.Time) } + +type TocData struct { + Draft bool + HostURL string + Posts []*PostData +} + +func toc(w http.ResponseWriter, req *http.Request, draft bool, isOwner bool, user string) { + c := fs.NewContext(req) + + var data []byte + keystr := fmt.Sprintf("blog:toc:%v", draft) + if req.FormValue("readdir") != "" { + keystr += ",readdir=" + req.FormValue("readdir") + } + if draft { + keystr += ",user="+user + } + + if key, ok := c.CacheLoad(keystr, "blog", &data); !ok { + c := fs.NewContext(req) + dir, err := c.ReadDir("blog/post") + if err != nil { + panic(err) + } + + if req.FormValue("readdir") == "1" { + fmt.Fprintf(w, "%d dir entries\n", len(dir)) + return + } + + postCache := map[string]*PostData{} + if data, _, err := c.Read("blogcache"); err == nil { + if err := json.Unmarshal(data, &postCache); err != nil { + c.Criticalf("unmarshal blogcache: %v", err) + } + } + + ch := make(chan *PostData, len(dir)) + const par = 20 + var limit = make(chan bool, par) + for i := 0; i < par; i++ { + limit <- true + } + for _, d := range dir { + if meta := postCache[d.Name]; meta != nil && meta.FileModTime.Equal(d.ModTime) && meta.FileSize == d.Size { + ch <- meta + continue + } + + <-limit + go func(d proto.FileInfo) { + defer func() { limit <- true }() + meta, _, err := loadPost(c, d.Name, req) + if err != nil { + // Should not happen: we just listed the directory. + c.Criticalf("loadPost %s: %v", d.Name, err) + return + } + ch <- meta + }(d) + } + for i := 0; i < par; i++ { + <-limit + } + close(ch) + postCache = map[string]*PostData{} + var all []*PostData + for meta := range ch { + postCache[meta.Name] = meta + if meta.IsDraft() == draft && (!draft || isOwner || meta.canRead(user)) { + all = append(all, meta) + } + } + sort.Sort(byTime(all)) + + if data, err := json.Marshal(postCache); err != nil { + c.Criticalf("marshal blogcache: %v", err) + } else if err := c.Write("blogcache", data); err != nil { + c.Criticalf("write blogcache: %v", err) + } + + var buf bytes.Buffer + t := mainTemplate(c) + if err := t.Lookup("toc").Execute(&buf, &TocData{draft, hostURL(req), all}); err != nil { + panic(err) + } + data = buf.Bytes() + c.CacheStore(key, data) + } + w.Write(data) +} + +func oldRedirect(ctxt *fs.Context, w http.ResponseWriter, req *http.Request, p string) { + m := map[string]string{} + if key, ok := ctxt.CacheLoad("blog:oldRedirectMap", "blog/post", &m); !ok { + dir, err := ctxt.ReadDir("blog/post") + if err != nil { + panic(err) + } + + for _, d := range dir { + meta, _, err := loadPost(ctxt, d.Name, req) + if err != nil { + // Should not happen: we just listed the directory. + panic(err) + } + m[meta.OldURL] = "/" + d.Name + } + + ctxt.CacheStore(key, m) + } + + if url, ok := m[p]; ok { + http.Redirect(w, req, url, http.StatusFound) + return + } + + notfound(ctxt, w, req) +} + +func hostURL(req *http.Request) string { + if strings.HasPrefix(req.Host, "localhost") { + return "http://localhost:8080" + } + return "http://research.swtch.com" +} + +func atomfeed(w http.ResponseWriter, req *http.Request) { + c := fs.NewContext(req) + + c.Criticalf("Header: %v", req.Header) + + var data []byte + if key, ok := c.CacheLoad("blog:atomfeed", "blog/post", &data); !ok { + dir, err := c.ReadDir("blog/post") + if err != nil { + panic(err) + } + + var all []*PostData + for _, d := range dir { + meta, article, err := loadPost(c, d.Name, req) + if err != nil { + // Should not happen: we just loaded the directory. + panic(err) + } + if meta.IsDraft() { + continue + } + meta.article = article + all = append(all, meta) + } + sort.Sort(byTime(all)) + + show := all + if len(show) > 10 { + show = show[:10] + for _, meta := range all[10:] { + if meta.Favorite { + show = append(show, meta) + } + } + } + + feed := &atom.Feed{ + Title: "research!rsc", + ID: feedID, + Updated: atom.Time(show[0].Date.Time), + Author: &atom.Person{ + Name: "Russ Cox", + URI: "https://plus.google.com/" + plusRsc, + Email: "rsc@swtch.com", + }, + Link: []atom.Link{ + {Rel: "self", Href: hostURL(req) + "/feed.atom"}, + }, + } + + for _, meta := range show { + t := template.New("main") + t.Funcs(funcMap) + main, _, err := c.Read("blog/atom.html") + if err != nil { + panic(err) + } + _, err = t.Parse(string(main)) + if err != nil { + panic(err) + } + template.Must(t.New("article").Parse(meta.article)) + var buf bytes.Buffer + if err := t.Execute(&buf, meta); err != nil { + panic(err) + } + + e := &atom.Entry{ + Title: meta.Title, + ID: feed.ID + "/" + meta.Name, + Link: []atom.Link{ + {Rel: "alternate", Href: meta.HostURL + "/" + meta.Name}, + }, + Published: atom.Time(meta.Date.Time), + Updated: atom.Time(meta.Date.Time), + Summary: &atom.Text{ + Type: "text", + Body: meta.Summary, + }, + Content: &atom.Text{ + Type: "html", + Body: buf.String(), + }, + } + + feed.Entry = append(feed.Entry, e) + } + + data, err = xml.Marshal(&feed) + if err != nil { + panic(err) + } + + c.CacheStore(key, data) + } + + // Feed readers like to hammer us; let Google cache the + // response to reduce the traffic we have to serve. + httpCache(w, 15*time.Minute) + + w.Header().Set("Content-Type", "application/atom+xml") + w.Write(data) +} + +func httpCache(w http.ResponseWriter, dt time.Duration) { + w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(dt.Seconds()))) +} -- cgit v1.2.3-1-g7c22