diff --git a/ArchiveServer.go b/ArchiveServer.go index 2e7e717..cd69bf3 100644 --- a/ArchiveServer.go +++ b/ArchiveServer.go @@ -1,6 +1,7 @@ package archive import ( + "errors" "fmt" "regexp" "time" @@ -48,3 +49,17 @@ func NewArchiveServer(cfg *Config) (*ArchiveServer, error) { rxSearchRx: regexp.MustCompile(`^/([^/]+)/rx/(.*)$`), }, nil } + +var ErrNoLogForMonth = errors.New("No logs for the selected month.") + +func (this *ArchiveServer) LogFile(ls *LogSource, ym YearMonth) (string, error) { + ymIndex := ym.Index() + + for _, fl := range ls.FileLocation { + if fl.StartMonth.Index() <= ymIndex && (fl.EndMonth == nil || fl.EndMonth.Index() >= ymIndex) { + return time.Date(ym.Year, ym.Month, 1, 0, 0, 0, 0, this.timezone).Format(fl.LogFilePath), nil + } + } + + return "", ErrNoLogForMonth +} diff --git a/ArchiveState.go b/ArchiveState.go index a477345..4838cfd 100644 --- a/ArchiveState.go +++ b/ArchiveState.go @@ -3,7 +3,14 @@ package archive import ( "fmt" "html/template" + "io/ioutil" + "math" "net/http" + "strings" +) + +const ( + pageNotSet int = -1 ) type ArchiveState struct { @@ -18,18 +25,60 @@ type ArchiveState struct { } func NewArchiveState(svr *ArchiveServer) *ArchiveState { - return &ArchiveState{svr: svr} + return &ArchiveState{ + svr: svr, + page: pageNotSet, + } } -func (this *ArchiveState) selectSource(log *LogSource) error { - logBestSlug, err := this.svr.bestSlugFor(log) - if err != nil { - return err +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) { + if this.log == nil { + this.renderError(w, "Invalid log source selected") + return } - this.log = log - this.logBestSlug = logBestSlug - return nil + 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") + + 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 += template.HTMLEscapeString(lines[i]) + "
\n" + } + + this.renderTemplate(w, []byte(output)) } func (this *ArchiveState) renderError(w http.ResponseWriter, msg string) { @@ -105,12 +154,13 @@ func (this *ArchiveState) renderTemplateHead(w http.ResponseWriter) { // Generate month dropdown options lastY := -1 - for ympair := this.log.EarliestDate(); !ympair.Equals(this.log.LatestDate()); ympair = ympair.Next() { + limit := this.log.LatestDate().Next() // one off the end + for ympair := this.log.EarliestDate(); !ympair.Equals(limit); ympair = ympair.Next() { if ympair.Year != lastY { if lastY != -1 { w.Write([]byte(``)) } - w.Write([]byte(``)) + w.Write([]byte(``)) lastY = ympair.Year } diff --git a/Router.go b/Router.go index c03f94d..a61fbf2 100644 --- a/Router.go +++ b/Router.go @@ -7,6 +7,7 @@ import ( "net/http" "net/url" "strconv" + "time" ) func (this *ArchiveServer) lookupSourceByNumericString(slug string) *LogSource { @@ -92,8 +93,17 @@ func (this *ArchiveServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else if r.URL.Path == `/download` { arc.renderError(w, "Not implemented.") // FIXME - } else if this.rxViewRoot.MatchString(r.URL.Path) { - arc.renderError(w, "Not implemented.") // FIXME + } else if matches := this.rxViewRoot.FindStringSubmatch(r.URL.Path); len(matches) > 0 { + if ls := this.lookupSource(matches[1]); ls != nil { + arc.selectSource(ls, matches[1]) + y, _ := strconv.Atoi(matches[2]) + m, _ := strconv.Atoi(matches[3]) + arc.ym = YearMonth{Year: y, Month: time.Month(m)} // 1-based months + arc.renderView(w) + + } else { + arc.renderError(w, fmt.Sprintf("Unknown source '%s'", matches[1])) + } } else if this.rxViewPage.MatchString(r.URL.Path) { arc.renderError(w, "Not implemented.") // FIXME diff --git a/YearMonth.go b/YearMonth.go index 4495392..0c4e4c0 100644 --- a/YearMonth.go +++ b/YearMonth.go @@ -26,3 +26,9 @@ func (ym YearMonth) Next() YearMonth { return YearMonth{Year: ym.Year, Month: ym.Month + 1} } } + +// Index returns a single int that can be used to compare this YearMonth with +// other YearMonth objects. +func (ym YearMonth) Index() int { + return (ym.Year * 12) + (int(ym.Month) - 1) +}