func (r *Record) CheckData(begin, end int64) error {
	if !r.Meets(begin, end) {
		return cfg.ErrGet
	}
	log.Println(r.Recstr(), r.IsSpam())
	if len(r.Recstr()) > cfg.RecordLimit<<10 || r.IsSpam() {
		log.Printf("warning:%s/%s:too large or spam record", r.Datfile, r.Idstr())
		errr := r.Remove()
		if errr != nil {
			log.Println(errr)
		}
		return cfg.ErrSpam
	}
	return nil
}
CheckData makes records from res and checks its records meets condisions of args.
adds the rec to cache if meets conditions.
if spam or big data, remove the rec from disk.
returns count of added records to the cache and spam/getting error.
package record
import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"regexp"
	"strconv"
	"strings"
	"encoding/json"
	"github.com/boltdb/bolt"
	"github.com/shingetsu-gou/shingetsu-gou/cfg"
	"github.com/shingetsu-gou/shingetsu-gou/db"
	"github.com/shingetsu-gou/shingetsu-gou/node"
	"github.com/shingetsu-gou/shingetsu-gou/util"
)
var cachedRule *util.RegexpList
type DB struct {
	*Head
	Body    string
	Deleted bool
}
func (d *DB) Del(tx *bolt.Tx) {
	if err := db.Del(tx, "record", d.Head.ToKey()); err != nil {
		log.Println(err)
	}
}
func (d *DB) Put(tx *bolt.Tx) error {
	return db.Put(tx, "record", d.Head.ToKey(), d)
}
func GetFromDB(tx *bolt.Tx, h *Head) (*DB, error) {
	d := DB{}
	_, err := db.Get(tx, "record", h.ToKey(), &d)
	return &d, err
}
func GetFromDBs(tx *bolt.Tx, datfile string) ([]*DB, error) {
	var cnt []*DB
	bdatfile := make([]byte, len(datfile)+1)
	copy(bdatfile, datfile)
	bdatfile[len(datfile)] = 0x0
	b := tx.Bucket([]byte("record"))
	if b == nil {
		return nil, errors.New("bucket not found record")
	}
	c := b.Cursor()
	for k, v := c.Seek(bdatfile); bytes.HasPrefix(k, bdatfile); k, v = c.Next() {
		str := DB{}
		if err := json.Unmarshal(v, &str); err != nil {
			return nil, err
		}
		cnt = append(cnt, &str)
	}
	return cnt, nil
}
func ForEach(tx *bolt.Tx, eachDo func(*DB) error) error {
	b := tx.Bucket([]byte("record"))
	if b == nil {
		return errors.New("bucket not found record")
	}
	c := b.Cursor()
	d := DB{}
	for k, v := c.First(); k != nil; k, v = c.Next() {
		if errr := json.Unmarshal(v, &d); errr != nil {
			return errr
		}
		if errr := eachDo(&d); errr != nil {
			return errr
		}
	}
	return nil
}
type Record struct {
	*Head
	contents map[string]string
	keyOrder []string
}
func NewIDstr(datfile, idstr string) (*Record, error) {
	if idstr == "" {
		return New(datfile, "", 0), nil
	}
	buf := strings.Split(idstr, "_")
	if len(buf) != 2 {
		err := errors.New("bad format")
		log.Println(idstr, ":bad format")
		return nil, err
	}
	var stamp int64
	var err error
	if stamp, err = strconv.ParseInt(buf[0], 10, 64); err != nil {
		log.Println(idstr, ":bad format")
		return nil, err
	}
	return New(datfile, buf[1], stamp), nil
}
func New(datfile, id string, stamp int64) *Record {
	return &Record{
		Head: &Head{
			Datfile: datfile,
			Stamp:   stamp,
			ID:      id,
		},
	}
}
func (r *Record) CopyHead() Head {
	return Head{
		Datfile: r.Datfile,
		Stamp:   r.Stamp,
		ID:      r.ID,
	}
}
func Make(line string) (*Record, error) {
	line = strings.TrimRight(line, "\r\n")
	buf := strings.Split(line, "<>")
	if len(buf) <= 2 || buf[0] == "" || buf[1] == "" || buf[2] == "" {
		err := errors.New("illegal format")
		return nil, err
	}
	idstr := buf[0] + "_" + buf[1]
	dec := util.FileDecode(buf[2])
	if dec == "" || !strings.HasPrefix(buf[2], "thread_") {
		err := errors.New("illegal format " + buf[2])
		return nil, err
	}
	buf[2] = util.FileEncode("thread", dec)
	vr, err := NewIDstr(buf[2], idstr)
	if err != nil {
		return nil, err
	}
	if err := vr.Parse(line); err != nil {
		log.Println(err)
		return nil, err
	}
	return vr, nil
}
func (r *Record) bodystr() string {
	rs := make([]string, len(r.contents))
	for i, k := range r.keyOrder {
		rs[i] = k + ":" + r.contents[k]
	}
	return strings.Join(rs, "<>")
}
func (r *Record) HasBodyValue(k string) bool {
	if _, exist := r.contents[k]; exist {
		return true
	}
	return false
}
func (r *Record) GetBodyValue(k string, def string) string {
	if v, exist := r.contents[k]; exist {
		return v
	}
	return def
}
func (r *Record) Recstr() string {
	return fmt.Sprintf("%d<>%s<>%s", r.Stamp, r.ID, r.bodystr())
}
func (r *Record) Parse(recstr string) error {
	var err error
	recstr = strings.TrimRight(recstr, "\r\n")
	tmp := strings.Split(recstr, "<>")
	if len(tmp) < 2 {
		errr := errors.New(recstr + ":bad format")
		log.Println(errr)
		return errr
	}
	stamp, err := strconv.ParseInt(tmp[0], 10, 64)
	if err != nil {
		log.Println(tmp[0], "bad format")
		return err
	}
	if r.Stamp == 0 {
		r.Stamp = stamp
	}
	if r.Stamp != stamp {
		log.Println("stamp unmatch")
		return errors.New("stamp unmatch")
	}
	if r.ID == "" {
		r.ID = tmp[1]
	}
	if r.ID != tmp[1] {
		log.Println("ID unmatch")
		return errors.New("stamp unmatch")
	}
	r.contents = make(map[string]string)
	r.keyOrder = nil
	for _, kv := range tmp[2:] {
		buf := strings.SplitN(kv, ":", 2)
		if len(buf) < 2 {
			continue
		}
		buf[1] = strings.Replace(buf[1], "<br>", "\n", -1)
		buf[1] = strings.Replace(buf[1], "<", "&lt;", -1)
		buf[1] = strings.Replace(buf[1], ">", "&gt;", -1)
		buf[1] = strings.Replace(buf[1], "\n", "<br>", -1)
		if util.HasString(r.keyOrder, buf[0]) {
			err := errors.New("duplicate keys")
			log.Println(err)
			return err
		}
		r.keyOrder = append(r.keyOrder, buf[0])
		r.contents[buf[0]] = buf[1]
	}
	return nil
}
func (r *Record) Load() error {
	if !r.Exists() {
		err := r.Remove()
		if err != nil {
			log.Println(err)
		}
		return errors.New("file not found")
	}
	var d *DB
	err := db.DB.View(func(tx *bolt.Tx) error {
		var err error
		d, err = GetFromDB(tx, r.Head)
		return err
	})
	if err != nil {
		log.Println(err)
		return err
	}
	return r.Parse(fmt.Sprintf("%d<>%s<>%s", r.Stamp, r.ID, d.Body))
}
func (r *Record) ShortPubkey() string {
	if v, exist := r.contents["pubkey"]; exist {
		return util.CutKey(v)
	}
	return ""
}
func (r *Record) Build(stamp int64, body map[string]string, passwd string) string {
	r.contents = make(map[string]string)
	r.Stamp = stamp
	for key, value := range body {
		if value == "" {
			continue
		}
		r.contents[key] = value
		r.keyOrder = append(r.keyOrder, key)
	}
	if passwd != "" {
		k, err := util.MakePrivateKey(passwd)
		if err == nil {
			pubkey, _ := k.GetKeys()
			md := util.MD5digest(r.bodystr())
			sign := k.Sign(md)
			r.contents["pubkey"] = pubkey
			r.contents["sign"] = sign
			r.contents["target"] = strings.Join(r.keyOrder, ",")
			r.keyOrder = append(r.keyOrder, "pubkey")
			r.keyOrder = append(r.keyOrder, "sign")
			r.keyOrder = append(r.keyOrder, "target")
		}
	}
	id := util.MD5digest(r.bodystr())
	r.ID = id
	return r.ID
}
func (r *Record) md5check() bool {
	return util.MD5digest(r.bodystr()) == r.ID
}
func (r *Record) AttachPath(thumbnailSize string) string {
	suffix := r.GetBodyValue("suffix", "")
	if suffix == "" {
		return ""
	}
	reg := regexp.MustCompile(`[^-_.A-Za-z0-9]`)
	reg.ReplaceAllString(suffix, "")
	if thumbnailSize != "" {
		return "s" + r.Idstr() + "." + thumbnailSize + "." + suffix
	}
	return r.Idstr() + "." + suffix
}
func (r *Record) SyncTX(tx *bolt.Tx, deleted bool) error {
	has, err := db.HasKey(tx, "record", r.Head.ToKey())
	if err != nil {
		log.Println(err)
	}
	if has {
		return nil
	}
	d := DB{
		Head:    r.Head,
		Body:    r.bodystr(),
		Deleted: deleted,
	}
	return d.Put(tx)
}
func (r *Record) Sync() {
	err := db.DB.Update(func(tx *bolt.Tx) error {
		return r.SyncTX(tx, false)
	})
	if err != nil {
		log.Print(err)
	}
}
func (r *Record) Getbody() string {
	err := r.Load()
	if err != nil {
		log.Println(err)
	}
	return r.Recstr()
}
func (r *Record) Meets(begin, end int64) bool {
	md5ok := r.md5check()
	if begin > r.Stamp || (end > 0 && r.Stamp > end) {
		log.Println("stamp range NG", begin, end, r.Stamp)
		return false
	}
	if !md5ok {
		log.Println("md5 NG")
		return false
	}
	return true
}
func (r *Record) IsSpam() bool {
	if cachedRule == nil {
		cachedRule = util.NewRegexpList(cfg.SpamList)
	}
	return cachedRule.Check(r.Recstr())
}
func (r *Record) MakeAttachLink(sakuHost string) string {
	if r.GetBodyValue("attach", "") == "" {
		return ""
	}
	url := fmt.Sprintf("http://%s/thread.cgi/%s/%s/%d.%s",
		sakuHost, r.Datfile, r.ID, r.Stamp, r.GetBodyValue("suffix", cfg.SuffixTXT))
	return "<br><br>[Attached]<br>" + url
}
func (r *Record) GetData(n *node.Node) error {
	res, err := n.Talk(fmt.Sprintf("/get/%s/%d/%s", r.Datfile, r.Stamp, r.ID), nil)
	if len(res) == 0 {
		err = errors.New("no response")
	}
	if err != nil {
		log.Println(err)
		return cfg.ErrGet
	}
	if err = r.Parse(res[0]); err != nil {
		return cfg.ErrGet
	}
	r.Sync()
	return r.CheckData(-1, -1)
}