func (g *gatewayCGI) printIndex(doChange bool) {
	str := "index"
	title := g.M[str]
	if doChange {
		str = "change"
		title = g.M["changes"]
	}
	if g.Filter != "" {
		title = fmt.Sprintf("%s : %s", g.M["string"], g.Filter)
	}
	g.Header(title, "", nil, true)
	fmt.Fprintf(g.WR, "<p>%s</p>", g.M["desc_"+str])
	cl := thread.AllCaches()
	if doChange {
		sort.Sort(sort.Reverse(thread.NewSortByStamp(cl, false)))
	} else {
		sort.Sort(sort.Reverse(thread.NewSortByVelocity(cl)))
	}
	g.PrintIndexList(cl, str, true, false, g.Filter, g.Tag)
}
printIndex renders threads in disk.
id doChange threads are sorted by velocity.
package gateway
import (
	"encoding/csv"
	"encoding/hex"
	"errors"
	"fmt"
	"html"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"
	"github.com/shingetsu-gou/shingetsu-gou/cfg"
	"github.com/shingetsu-gou/shingetsu-gou/cgi"
	"github.com/shingetsu-gou/shingetsu-gou/record"
	"github.com/shingetsu-gou/shingetsu-gou/tag"
	"github.com/shingetsu-gou/shingetsu-gou/tag/suggest"
	"github.com/shingetsu-gou/shingetsu-gou/tag/user"
	"github.com/shingetsu-gou/shingetsu-gou/thread"
	"github.com/shingetsu-gou/shingetsu-gou/util"
)
const xslURL = "/rss1.xsl"
func Setup(s *cgi.LoggingServeMux) {
	s.RegistCompressHandler(cfg.GatewayURL+"/motd", printMotd)
	s.RegistCompressHandler(cfg.GatewayURL+"/mergedjs", printMergedJS)
	s.RegistCompressHandler(cfg.GatewayURL+"/rss", printRSS)
	s.RegistCompressHandler(cfg.GatewayURL+"/recent_rss", printRecentRSS)
	s.RegistCompressHandler(cfg.GatewayURL+"/index", printGatewayIndex)
	s.RegistCompressHandler(cfg.GatewayURL+"/changes", printIndexChanges)
	s.RegistCompressHandler(cfg.GatewayURL+"/recent", printRecent)
	s.RegistCompressHandler(cfg.GatewayURL+"/new", printNew)
	s.RegistCompressHandler(cfg.GatewayURL+"/thread", printGatewayThread)
	s.RegistCompressHandler(cfg.GatewayURL+"/", PrintTitle)
	s.RegistCompressHandler(cfg.GatewayURL+"/csv/index/", printCSV)
	s.RegistCompressHandler(cfg.GatewayURL+"/csv/changes/", printCSVChanges)
	s.RegistCompressHandler(cfg.GatewayURL+"/csv/recent/", printCSVRecent)
}
func printGatewayThread(w http.ResponseWriter, r *http.Request) {
	reg := regexp.MustCompile("^/gateway.cgi/(thread)/?([^/]*)$")
	m := reg.FindStringSubmatch(r.URL.Path)
	var uri string
	switch {
	case m == nil:
		PrintTitle(w, r)
		return
	case m[2] != "":
		uri = cfg.ThreadURL + "/" + util.StrEncode(m[2])
	case r.URL.RawQuery != "":
		uri = cfg.ThreadURL + "/" + r.URL.RawQuery
	default:
		PrintTitle(w, r)
		return
	}
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	g.Print302(uri)
}
func printCSV(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	g.renderCSV(thread.AllCaches())
}
func printCSVChanges(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	all := thread.AllCaches()
	sort.Sort(sort.Reverse(thread.NewSortByStamp(all, false)))
	g.renderCSV(all)
}
func printCSVRecent(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	if !g.IsFriend() && !g.IsAdmin() {
		g.Print403()
		return
	}
	cl := thread.MakeRecentCachelist()
	g.renderCSV(cl)
}
func printRecentRSS(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	rsss := cgi.NewRSS("UTF-8", "", fmt.Sprintf("%s - %s", g.M["recent"], g.M["logo"]),
		"http://"+g.Host(), "",
		"http://"+g.Host()+cfg.GatewayURL+"/"+"recent_rss", g.M["description"], xslURL)
	cl := thread.MakeRecentCachelist()
	for _, ca := range cl {
		title := util.Escape(util.FileDecode(ca.Datfile))
		tags := suggest.Get(ca.Datfile, nil)
		tags = append(tags, user.GetByThread(ca.Datfile)...)
		rsss.Append(cfg.ThreadURL[1:]+"/"+util.StrEncode(title),
			title, "", "", html.EscapeString(title), tags.GetTagstrSlice(),
			ca.RecentStamp(), false)
	}
	g.WR.Header().Set("Content-Type", "text/xml; charset=UTF-8")
	if rsss.Len() != 0 {
		g.WR.Header().Set("Last-Modified", g.RFC822Time(rsss.Feeds[0].Date))
	}
	rsss.MakeRSS1(g.WR)
}
func printRSS(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	rsss := cgi.NewRSS("UTF-8", "", g.M["logo"], "http://"+g.Host(), "",
		"http://"+g.Host()+cfg.GatewayURL+"/"+"rss", g.M["description"], xslURL)
	for _, ca := range thread.AllCaches() {
		g.appendRSS(rsss, ca)
	}
	g.WR.Header().Set("Content-Type", "text/xml; charset=UTF-8")
	if rsss.Len() != 0 {
		g.WR.Header().Set("Last-Modified", g.RFC822Time(rsss.Feeds[0].Date))
	}
	rsss.MakeRSS1(g.WR)
}
func printMergedJS(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	g.WR.Header().Set("Content-Type", "application/javascript; charset=UTF-8")
	g.WR.Header().Set("Last-Modified", g.RFC822Time(g.JC.GetLatest()))
	_, err = g.WR.Write([]byte(g.JC.GetContent()))
	if err != nil {
		log.Println(err)
	}
}
func printMotd(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	g.WR.Header().Set("Content-Type", "text/plain; charset=UTF-8")
	c, err := ioutil.ReadFile(cfg.Motd())
	if err != nil {
		log.Println(err)
		return
	}
	_, err = g.WR.Write(c)
	if err != nil {
		log.Println(err)
	}
}
func printNew(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	g.Header(g.M["new"], "", nil, true)
	g.PrintNewElementForm()
	g.Footer(nil)
}
func PrintTitle(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	if r.FormValue("cmd") != "" {
		g.jumpNewFile()
		return
	}
	all := thread.AllCaches()
	sort.Sort(sort.Reverse(thread.NewSortByStamp(all, false)))
	outputCachelist := make([]*thread.Cache, 0, thread.Len())
	for _, ca := range all {
		if time.Now().Unix() <= ca.Stamp()+cfg.TopRecentRange {
			outputCachelist = append(outputCachelist, ca)
		}
	}
	g.Header(g.M["logo"]+" - "+g.M["description"], "", nil, false)
	s := struct {
		Target        string
		Taglist       tag.Slice
		MchURL        string
		MchCategories []*mchCategory
		Types         string
		NoList        bool
		cgi.ListItem
		cgi.Defaults
	}{
		"changes",
		user.Get(),
		g.mchURL(""),
		g.mchCategories(),
		"thread",
		len(outputCachelist) == 0,
		*cgi.NewListItem(outputCachelist, false, "changes", false, g.Filter, g.Tag),
		*g.Defaults(),
	}
	cgi.RenderTemplate("top", s, g.WR)
	g.PrintNewElementForm()
	g.Footer(nil)
}
func printGatewayIndex(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	g.printIndex(false)
}
func printIndexChanges(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	g.printIndex(true)
}
func printRecent(w http.ResponseWriter, r *http.Request) {
	g, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	title := g.M["recent"]
	if g.Filter != "" {
		title = fmt.Sprintf("%s : %s", g.M["recent"], g.Filter)
	}
	g.Header(title, "", nil, true)
	fmt.Fprintf(g.WR, "<p>%s</p>", g.M["desc_recent"])
	cl := thread.MakeRecentCachelist()
	g.PrintIndexList(cl, "recent", true, false, g.Filter, g.Tag)
}
type gatewayCGI struct {
	*cgi.CGI
	Filter string
	Tag    string
}
func new(w http.ResponseWriter, r *http.Request) (*gatewayCGI, error) {
	c, err := cgi.NewCGI(w, r)
	if err != nil {
		return nil, err
	}
	a := gatewayCGI{
		CGI: c,
	}
	filter := r.FormValue("filter")
	tag := r.FormValue("tag")
	if filter != "" {
		a.Filter = strings.ToLower(filter)
	} else {
		a.Tag = strings.ToLower(tag)
	}
	if !a.CheckVisitor() {
		a.Print403()
		return nil, errors.New("permission denied")
	}
	return &a, nil
}
func (g *gatewayCGI) appendRSS(rsss *cgi.RSS, ca *thread.Cache) {
	now := time.Now().Unix()
	if ca.Stamp()+cfg.RSSRange < now {
		return
	}
	title := util.Escape(util.FileDecode(ca.Datfile))
	path := cfg.ThreadURL + "/" + util.StrEncode(title)
	recs := ca.LoadRecords(record.Alive)
	for _, r := range recs {
		if r.Stamp+cfg.RSSRange < now {
			continue
		}
		if err := r.Load(); err != nil {
			log.Println(err)
			continue
		}
		desc := cgi.RSSTextFormat(r.GetBodyValue("body", ""))
		content := g.rssHTMLFormat(r.GetBodyValue("body", ""), cfg.ThreadURL, title)
		if attach := r.GetBodyValue("attach", ""); attach != "" {
			suffix := r.GetBodyValue("suffix", "")
			if reg := regexp.MustCompile("^[0-9A-Za-z]+$"); !reg.MatchString(suffix) {
				suffix = cfg.SuffixTXT
			}
			content += fmt.Sprintf("\n    <p><a href=\"http://%s%s%s%s/%s/%d.%s\">%d.%s</a></p>",
				g.Host(), cfg.ThreadURL, "/", ca.Datfile, r.ID, r.Stamp, suffix, r.Stamp, suffix)
		}
		permpath := fmt.Sprintf("%s/%s", path[1:], r.ID[:8])
		rsss.Append(permpath, title, cgi.RSSTextFormat(r.GetBodyValue("name", "")), desc, content, user.GetStrings(ca.Datfile), r.Stamp, false)
	}
}
func (g *gatewayCGI) makeOneRow(c string, ca *thread.Cache, p, title string) string {
	switch c {
	case "file":
		return ca.Datfile
	case "stamp":
		return strconv.FormatInt(ca.Stamp(), 10)
	case "date":
		return time.Unix(ca.Stamp(), 0).String()
	case "path":
		return p
	case "uri":
		if g.Host() != "" && p != "" {
			return "http://" + g.Host() + p
		}
	case "type":
		return "thread"
	case "title":
		return title
	case "records":
		return strconv.Itoa(ca.Len(record.Alive))
	case "size":
		return strconv.FormatInt(ca.Size(), 10)
	case "tag":
		return user.String(ca.Datfile)
	case "sugtag":
		return suggest.String(ca.Datfile)
	}
	return ""
}
func (g *gatewayCGI) renderCSV(cl thread.Caches) {
	g.WR.Header().Set("Content-Type", "text/comma-separated-values;charset=UTF-8")
	p := strings.Split(g.Path(), "/")
	if len(p) < 3 {
		g.Print404(nil, "")
		return
	}
	cols := strings.Split(p[2], ",")
	cwr := csv.NewWriter(g.WR)
	for _, ca := range cl {
		title := util.FileDecode(ca.Datfile)
		p := cfg.ThreadURL + "/" + util.StrEncode(title)
		row := make([]string, len(cols))
		for i, c := range cols {
			row[i] = g.makeOneRow(c, ca, p, title)
		}
		err := cwr.Write(row)
		if err != nil {
			log.Println(err)
		}
	}
	cwr.Flush()
}