package archive import ( "bufio" "fmt" "html" "io/ioutil" "math" "net/http" "net/url" "os" "regexp" "strings" ) const ( pageNotSet int = -1 ) type ArchiveState struct { svr *ArchiveServer log *LogSource logBestSlug string query string queryIsRegex bool ym YearMonth page int highestPage int } func NewArchiveState(svr *ArchiveServer) *ArchiveState { return &ArchiveState{ svr: svr, page: pageNotSet, } } func (this *ArchiveState) URL() string { if len(this.query) > 0 { if this.queryIsRegex { return fmt.Sprintf(`/%s/rx/%s`, this.logBestSlug, url.QueryEscape(this.query)) } else { return fmt.Sprintf(`/%s/search/%s`, this.logBestSlug, url.QueryEscape(this.query)) } } else { if this.page == pageNotSet { return fmt.Sprintf(`/%s/%s/%s`, this.logBestSlug, this.ym.Year, this.ym.Month) } else { return fmt.Sprintf(`/%s/%s/%s/page-%d`, this.logBestSlug, this.ym.Year, this.ym.Month, this.page) } } } func (this *ArchiveState) selectSource(log *LogSource, slug string) { this.log = log this.logBestSlug = slug } // renderView renders a single page of log entries. // - Mandatory: log, ym // - Optional: page func (this *ArchiveState) renderView(w http.ResponseWriter) { fname, err := this.svr.LogFile(this.log, this.ym) if err != nil { this.renderError(w, err.Error()) return } fc, err := ioutil.ReadFile(fname) if err != nil { this.renderError(w, err.Error()) return } lines := strings.Split(string(fc), "\n") // Work around #newpage: if the last line is blank, skip if len(lines) > 0 && len(lines[len(lines)-1]) == 0 { lines = lines[:len(lines)-1] } this.highestPage = int(math.Ceil(float64(len(lines))/float64(this.svr.cfg.LinesPerPage))) - 1 if this.page == pageNotSet || this.page > this.highestPage { this.page = this.highestPage } if this.page < 0 { this.page = 0 } startLine := this.svr.cfg.LinesPerPage * this.page endLine := this.svr.cfg.LinesPerPage * (this.page + 1) if endLine > len(lines) { endLine = len(lines) } output := "" for i := startLine; i < endLine; i += 1 { output += html.EscapeString(lines[i]) + "
\n" } this.renderTemplate(w, []byte(output)) } // renderSearch renders the search results. // - Mandatory: log, query, queryIsRegex func (this *ArchiveState) renderSearch(w http.ResponseWriter) { var matcher func(line string) bool if this.queryIsRegex { rx, err := regexp.Compile(`(?i)` + this.query) if err != nil { this.renderError(w, "Invalid regular expression "+this.query+" ("+err.Error()+")") return } matcher = rx.MatchString } else { queryLower := strings.ToLower(this.query) matcher = func(line string) bool { return strings.Contains(strings.ToLower(line), queryLower) } } this.renderTemplateHead(w) totalResults := 0 w.Write([]byte(``)) if totalResults == 0 { w.Write([]byte(`No search results for "` + html.EscapeString(this.query) + `"`)) } else { w.Write([]byte(`
Found ` + fmt.Sprintf("%d", totalResults) + ` total result(s).

`)) } this.renderTemplateFoot(w) } // renderError renders a plain text string, escaping it for HTML use. func (this *ArchiveState) renderError(w http.ResponseWriter, msg string) { this.renderTemplate(w, []byte(html.EscapeString(msg))) } func (this *ArchiveState) renderTemplate(w http.ResponseWriter, body []byte) { this.renderTemplateHead(w) w.Write(body) this.renderTemplateFoot(w) } func (this *ArchiveState) renderTemplateHead(w http.ResponseWriter) { w.Header().Set(`Content-Type`, `text/html; charset=UTF-8`) w.Header().Set(`X-UA-Compatible`, `IE=Edge`) w.WriteHeader(200) title := `Archives` if this.log != nil { title = this.log.Description + ` Archives` } showPageURLs := (this.log != nil && len(this.query) == 0) latestUrl := `/` if this.log != nil { latestUrl = fmt.Sprintf(`/%s/%d/%d`, url.PathEscape(this.logBestSlug), this.log.LatestDate().Year, this.log.LatestDate().Month) } w.Write([]byte(` ` + html.EscapeString(title) + `
`)) // Header ends } func (this *ArchiveState) renderTemplateFoot(w http.ResponseWriter) { w.Write([]byte(`
`)) }