//printPageNavi renders page_navi.txt, part for paging. func (t *threadCGI) printPageNavi(path string, page int, ca *thread.Cache, id string) { len := ca.Len() first := len / cfg.ThreadPageSize if len%cfg.ThreadPageSize == 0 { first++ } pages := make([]int, first+1) for i := 0; i <= first; i++ { pages[i] = i } s := struct { Page int CacheLen int Path string ID string First int ThreadCGI string Message cgi.Message ThreadPageSize int Pages []int }{ page, len, path, id, first, cfg.ThreadURL, t.M, cfg.ThreadPageSize, pages, } cgi.RenderTemplate("page_navi", s, t.WR) }
//MakeDat makes dat lines of 2ch from cache. func MakeDat(ca *thread.Cache, board, host string) []string { recs := ca.LoadRecords(record.Alive) dat := make([]string, len(recs)) table := mch.NewResTable(ca) i := 0 for _, datfile := range recs.Keys() { rec := recs[datfile] err := rec.Load() if err != nil { log.Println(err) continue } name := rec.GetBodyValue("name", "") if name == "" { name = "名無しさん" } if rec.GetBodyValue("pubkey", "") != "" { name += "◆" + rec.GetBodyValue("pubkey", "")[:10] } comment := fmt.Sprintf("%s<>%s<>%s<>%s<>", name, rec.GetBodyValue("main", ""), util.Datestr2ch(rec.Stamp), MakeBody(rec, host, board, table)) if i == 0 { comment += util.FileDecode(ca.Datfile) } dat[i] = comment i++ } return dat }
//printThreadBody renders body(records list) part of thread page with paging. func (t *threadCGI) printThreadBody(id string, nPage int, ca *thread.Cache) { recs := ca.LoadRecords(record.Alive) ids := recs.Keys() fmt.Fprintln(t.WR, "</p>\n<dl id=\"records\">") from := len(ids) - cfg.ThreadPageSize*(nPage+1) to := len(ids) - cfg.ThreadPageSize*(nPage) if from < 0 { from = 0 } if to < 0 { to = 0 } var inrange []string switch { case id != "": inrange = ids case nPage > 0: inrange = ids[from:to] default: inrange = ids[from:] } for _, k := range inrange { rec := recs.Get(k, nil) if (id == "" || rec.ID[:8] == id) && rec.Load() == nil { t.printRecord(ca, rec) } } fmt.Fprintln(t.WR, "</dl>") }
//appendRSS appends cache ca to rss with contents,url to records,stamp,attached file. 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) } }
//getWithRange gets records with range using node n and adds to cache after checking them. //if no records exist in cache, uses head //return true if gotten records>0 func getWithRange(n *node.Node, c *thread.Cache, dm *Manager) bool { got := false for { from, to := dm.Get(n) if from <= 0 { return got } var okcount int ress, err := n.Talk(fmt.Sprintf("/get/%s/%d-%d", c.Datfile, from, to), nil) if err != nil { dm.Finished(n, false) return false } err = db.DB.Update(func(tx *bolt.Tx) error { for _, res := range ress { errf := c.CheckData(tx, res, -1, "", from, to) if errf == nil { okcount++ } } return nil }) if err != nil { log.Println(err) } dm.Finished(n, true) log.Println(c.Datfile, okcount, "records were saved from", n.Nodestr) got = okcount > 0 } }
//NewResTable creates ane returns a resTable instance. func NewResTable(ca *thread.Cache) *ResTable { r := &ResTable{ make(map[string]int), make([]string, ca.Len()+1), } recs := ca.LoadRecords(record.Alive) for i, k := range recs.Keys() { rec := recs.Get(k, nil) r.Num2id[i+1] = rec.ID[:8] r.ID2num[rec.ID[:8]] = i + 1 } return r }
//RemoveFileForm render remove_form_form page. func (c *CGI) RemoveFileForm(ca *thread.Cache, title string) { s := struct { Cache *thread.Cache CacheSize int64 Title string Defaults }{ ca, ca.Size(), title, *c.Defaults(), } RenderTemplate("remove_file_form", s, c.WR) }
//makeOneRow makes one row of CSV depending on c. 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()) case "size": return strconv.FormatInt(ca.Size(), 10) case "tag": return user.String(ca.Datfile) case "sugtag": return suggest.String(ca.Datfile) } return "" }
//NewManger sets recs as finished recs and returns DownloadManager obj. func NewManger(ca *thread.Cache) *Manager { if d, exist := managers[ca.Datfile]; exist { log.Println(ca.Datfile, "is downloading") return d } recs := ca.LoadRecords(record.All) dm := &Manager{ datfile: ca.Datfile, recs: make(map[string]*targetRec), } for k := range recs { dm.recs[k] = &targetRec{ finished: true, } } return dm }
//printThreadHead renders head part of thread page with cookie. func (t *threadCGI) printThreadHead(path, id string, page int, ca *thread.Cache, rss string) error { switch { case ca.HasRecord(): if !t.IsBot() { download.GetCache(true, ca) } else { log.Println("bot detected, not get cache") } case t.CheckGetCache(): ca.Subscribe() if t.Req.FormValue("search_new_file") == "" { download.GetCache(true, ca) } default: t.Print404(nil, id) return errors.New("no records") } var access string var newcookie []*http.Cookie if ca.HasRecord() && id == "" && page == 0 { cookie, err := t.Req.Cookie("access") if err == nil { access = cookie.Value } else { log.Println(err) } newcookie = t.setCookie(ca, access) } t.Header(path, rss, newcookie, false) return nil }
//bg waits for at least one record in the cache. func bg(c *thread.Cache, wg *sync.WaitGroup) { w := 2 * time.Second newest, err := recentlist.Newest(c.Datfile) var done chan struct{} go func() { wg.Wait() done <- struct{}{} }() if err == nil && (newest.Stamp == c.Stamp()) { return } for { select { case <-done: return case <-time.After(w): w += time.Second if c.HasRecord() || w >= 5*time.Second { return } } } }
//printThreadTop renders toppart of thread page. func (t *threadCGI) printThreadTop(path, id string, nPage int, ca *thread.Cache) { var lastrec *record.Record var resAnchor string recs := ca.LoadRecords(record.Alive) ids := recs.Keys() if ca.HasRecord() && nPage == 0 && id == "" && len(ids) > 0 { lastrec = recs[ids[len(ids)-1]] resAnchor = t.ResAnchor(lastrec.ID[:8], cfg.ThreadURL, t.Path(), false) } s := struct { Path string Cache *thread.Cache Lastrec *record.Record ResAnchor template.HTML cgi.Defaults }{ path, ca, lastrec, template.HTML(resAnchor), *t.Defaults(), } cgi.RenderTemplate("thread_top", s, t.WR) }
//setFromCache adds cache.datfile/timestamp pair if not exists. func setFromCache(tx *bolt.Tx, ca *thread.Cache) { _, err := getTime(tx, ca.Datfile) if err == nil { return } var firstStamp int64 rec := ca.LoadRecords(record.Alive) switch { case !ca.HasRecord(): firstStamp = ca.RecentStamp() case len(rec) > 0: firstStamp = rec[rec.Keys()[0]].Stamp default: firstStamp = time.Now().Add(-24 * time.Hour).Unix() } for { _, err = getThread(tx, firstStamp) if err != nil { break } firstStamp++ } setEntry(tx, firstStamp, ca.Datfile) }