func (a *adminCGI) postDeleteMessage(ca *thread.Cache, rec *record.Record) {
	stamp := time.Now().Unix()
	body := make(map[string]string)
	for _, key := range []string{"name", "body"} {
		if v := a.Req.FormValue(key); v != "" {
			body[key] = util.Escape(v)
		}
	}
	body["remove_stamp"] = strconv.FormatInt(rec.Stamp, 10)
	body["remove_id"] = rec.ID
	passwd := a.Req.FormValue("passwd")
	id := rec.Build(stamp, body, passwd)
	rec.Sync()
	recentlist.Append(rec.Head)
	go manager.TellUpdate(ca.Datfile, stamp, id, nil)
}
postDeleteMessage tells others deletion of a record.
and adds to updateList and recentlist.
package admin
import (
	"fmt"
	"html"
	"log"
	"math/rand"
	"net/http"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"time"
	"errors"
	"github.com/shingetsu-gou/shingetsu-gou/cfg"
	"github.com/shingetsu-gou/shingetsu-gou/cgi"
	"github.com/shingetsu-gou/shingetsu-gou/myself"
	"github.com/shingetsu-gou/shingetsu-gou/node"
	"github.com/shingetsu-gou/shingetsu-gou/node/manager"
	"github.com/shingetsu-gou/shingetsu-gou/recentlist"
	"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"
)
var adminSID = ""
func Setup(s *cgi.LoggingServeMux) {
	s.RegistCompressHandler(cfg.AdminURL+"/status", printStatus)
	s.RegistCompressHandler(cfg.AdminURL+"/edittag", printEdittag)
	s.RegistCompressHandler(cfg.AdminURL+"/savetag", saveTagCGI)
	s.RegistCompressHandler(cfg.AdminURL+"/search", printSearch)
	s.RegistCompressHandler(cfg.AdminURL+"/", execCmd)
}
func execCmd(w http.ResponseWriter, r *http.Request) {
	a, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	cmd := a.Req.FormValue("cmd")
	rmFiles := a.Req.Form["file"]
	rmRecords := a.Req.Form["record"]
	switch cmd {
	case "rdel":
		a.printDeleteRecord(rmFiles, rmRecords)
	case "fdel":
		a.printDeleteFile(rmFiles)
	case "xrdel":
		a.doDeleteRecord(rmFiles, rmRecords, a.Req.FormValue("dopost"))
	case "xfdel":
		a.doDeleteFile(rmFiles)
	}
}
func printSearch(w http.ResponseWriter, r *http.Request) {
	a, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	query := a.Req.FormValue("query")
	if query == "" {
		a.Header(a.M["search"], "", nil, true)
		fmt.Fprintf(a.WR, "<p>%s</p>", a.M["desc_search"])
		a.printSearchForm("")
		a.Footer(nil)
	} else {
		a.printSearchResult(query)
	}
}
func printStatus(w http.ResponseWriter, r *http.Request) {
	a, err := new(w, r)
	if err != nil {
		log.Println()
		return
	}
	records := 0
	var size int64
	for _, ca := range thread.AllCaches() {
		records += ca.Len(record.All)
		size += ca.Size()
	}
	var mem runtime.MemStats
	runtime.ReadMemStats(&mem)
	var port0 string
	switch myself.GetStatus() {
	case cfg.Normal:
		port0 = a.M["opened"]
	case cfg.UPnP:
		port0 = "UPnP"
	case cfg.Port0:
		port0 = a.M["port0"]
	case cfg.Disconnected:
		port0 = a.M["disconnected"]
	}
	s := map[string]string{
		"known_nodes":       strconv.Itoa(manager.NodeLen()),
		"linked_nodes":      strconv.Itoa(manager.ListLen()),
		"files":             strconv.Itoa(thread.Len()),
		"records":           strconv.Itoa(records),
		"cache_size":        fmt.Sprintf("%.1f%s", float64(size)/1024/1024, a.M["mb"]),
		"self_node":         node.Me(false).Nodestr,
		"alloc_mem":         fmt.Sprintf("%.1f%s", float64(mem.Alloc)/1024/1024, a.M["mb"]),
		"connection_status": port0,
	}
	ns := map[string][]string{
		"known_nodes":  manager.GetNodestrSlice(),
		"linked_nodes": manager.GetNodestrSliceInList(),
	}
	d := struct {
		Status     map[string]string
		NodeStatus map[string][]string
		Message    cgi.Message
	}{
		s,
		ns,
		a.M,
	}
	a.Header(a.M["status"], "", nil, true)
	cgi.RenderTemplate("status", d, a.WR)
	a.Footer(nil)
}
func printEdittag(w http.ResponseWriter, r *http.Request) {
	a, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	datfile := a.Req.FormValue("file")
	strTitle := util.FileDecode(datfile)
	ca := thread.NewCache(datfile)
	datfile = html.EscapeString(datfile)
	if !ca.Exists() {
		a.Print404(nil, "")
		return
	}
	d := struct {
		Message  cgi.Message
		AdminCGI string
		Datfile  string
		Tags     string
		Sugtags  tag.Slice
		Usertags tag.Slice
	}{
		a.M,
		cfg.AdminURL,
		datfile,
		user.String(ca.Datfile),
		suggest.Get(ca.Datfile, nil),
		user.GetByThread(ca.Datfile),
	}
	a.Header(fmt.Sprintf("%s: %s", a.M["edit_tag"], strTitle), "", nil, true)
	cgi.RenderTemplate("edit_tag", d, a.WR)
	a.Footer(nil)
}
func saveTagCGI(w http.ResponseWriter, r *http.Request) {
	a, err := new(w, r)
	if err != nil {
		log.Println(err)
		return
	}
	datfile := a.Req.FormValue("file")
	tags := a.Req.FormValue("tag")
	if datfile == "" {
		return
	}
	ca := thread.NewCache(datfile)
	if !ca.Exists() {
		a.Print404(nil, "")
	}
	tl := strings.Fields(tags)
	user.Set(datfile, tl)
	var next string
	title := util.StrEncode(util.FileDecode(datfile))
	if strings.HasPrefix(datfile, "thread_") {
		next = cfg.ThreadURL + "/" + title
	} else {
		next = "/"
	}
	a.Print302(next)
}
type adminCGI struct {
	*cgi.CGI
}
func new(w http.ResponseWriter, r *http.Request) (*adminCGI, error) {
	c, err := cgi.NewCGI(w, r)
	if err != nil {
		return nil, err
	}
	a := adminCGI{
		CGI: c,
	}
	if !a.IsAdmin() {
		a.Print403()
		return nil, errors.New("permission denied")
	}
	return &a, nil
}
func (a *adminCGI) makeSid() string {
	var r string
	for i := 0; i < 4; i++ {
		r += strconv.Itoa(rand.Int())
	}
	adminSID = util.MD5digest(r)
	return adminSID
}
func (a *adminCGI) checkSid() bool {
	sid := a.Req.FormValue("sid")
	r := (adminSID != "" && adminSID == sid)
	adminSID = ""
	return r
}
type DeleteRecord struct {
	Message  cgi.Message
	AdminCGI string
	Datfile  string
	Records  []*record.Record
	Sid      string
}
func (a *adminCGI) printDeleteRecord(rmFiles []string, records []string) {
	if rmFiles == nil || records == nil {
		a.Print404(nil, "")
		return
	}
	datfile := rmFiles[0]
	sid := a.makeSid()
	recs := make([]*record.Record, len(records))
	var err error
	for i, v := range records {
		recs[i], err = record.NewIDstr(datfile, v)
		if err != nil {
			log.Println(err)
		}
	}
	d := DeleteRecord{
		a.M,
		cfg.AdminURL,
		datfile,
		recs,
		sid,
	}
	a.Header(a.M["del_record"], "", nil, true)
	cgi.RenderTemplate("delete_record", d, a.WR)
	a.Footer(nil)
}
func (a *adminCGI) doDeleteRecord(rmFiles []string, records []string, dopost string) {
	if a.Req.Method != "POST" || !a.checkSid() {
		a.Print404(nil, "")
		return
	}
	if rmFiles == nil || records == nil {
		a.Print404(nil, "")
		return
	}
	datfile := rmFiles[0]
	next := "/"
	title := util.StrEncode(util.FileDecode(datfile))
	if strings.HasPrefix(title, "thread_") {
		next = cfg.ThreadURL + "/" + title
	}
	ca := thread.NewCache(datfile)
	for _, r := range records {
		rec, err := record.NewIDstr(datfile, r)
		if err != nil || rec.Remove() == nil && dopost != "" {
			a.postDeleteMessage(ca, rec)
			a.Print302(next)
			return
		}
	}
	a.Print302(next)
}