package main import ( "bytes" "html/template" "log" "net/http" "os" "path/filepath" "sort" "strconv" "strings" "time" ) func init() { pageTypeHandlers = append(pageTypeHandlers, &calendarsHandler{}) } // calendarsHandler renders folders whose .page-settings declares // type = calendars: the maintenance table and the sync status. Markdown // content and the listing stay visible, as in the fitness folder. type calendarsHandler struct{} func (c *calendarsHandler) redirect(root, fsPath, urlPath string, r *http.Request) (string, bool) { return "", false } func (c *calendarsHandler) handle(root, fsPath, urlPath string, r *http.Request) *specialPage { s := readPageSettings(fsPath) if s == nil || s.Type != "calendars" { return nil } return &specialPage{Content: renderCalendarsPage(fsPath), SuppressTOC: true} } type calFileRow struct { Name string Roles string Events string Updated string // file modification time Error string // last sync or parse error } type calPageData struct { HasMaintenance bool Maintenance []maintSeriesRow Calendars []calFileRow // Sync is only shown for the configured calendars folder. IsSyncDir bool SyncEnabled bool LastRun string LastError string } var calPageTmpl = template.Must(template.ParseFS(assets, "assets/calendar/page.html")) func renderCalendarsPage(dir string) template.HTML { s := readCalendarSettings(dir) data := calPageData{ HasMaintenance: len(s.Maintenance) > 0, Maintenance: maintenanceSeries(dir, s.Maintenance, time.Now()), IsSyncDir: calendarsDir != "" && dir == calendarsDir, SyncEnabled: s.CaldavURL != "", } var results map[string]calSyncResult if data.IsSyncDir { var lastRun time.Time lastRun, data.LastError, results = calSyncSnapshot() if !lastRun.IsZero() { data.LastRun = lastRun.Format("2006-01-02 15:04") } } roles := map[string][]string{} for role, names := range map[string][]string{"diary": s.Diary, "weight": s.Weight, "maintenance": s.Maintenance} { for _, n := range names { roles[n] = append(roles[n], role) } } entries, _ := os.ReadDir(dir) for _, e := range entries { name := e.Name() if e.IsDir() || strings.HasPrefix(name, ".") || !strings.EqualFold(filepath.Ext(name), ".ics") { continue } name = strings.TrimSuffix(name, filepath.Ext(name)) sort.Strings(roles[name]) row := calFileRow{Name: name, Roles: strings.Join(roles[name], ", "), Error: results[name].Error} if info, err := e.Info(); err == nil { row.Updated = info.ModTime().Format("2006-01-02 15:04") } if f := loadCalendar(filepath.Join(dir, e.Name())); f != nil { row.Events = strconv.Itoa(f.events) } else if row.Error == "" { row.Error = "cannot parse file" } data.Calendars = append(data.Calendars, row) delete(results, name) } // Calendars whose download failed before a file ever existed. for name, r := range results { if r.Error != "" { data.Calendars = append(data.Calendars, calFileRow{Name: name, Error: r.Error}) } } sort.SliceStable(data.Calendars, func(i, j int) bool { return data.Calendars[i].Name < data.Calendars[j].Name }) var buf bytes.Buffer if err := calPageTmpl.Execute(&buf, data); err != nil { log.Printf("calendars page template: %v", err) return "" } return template.HTML(buf.String()) }