package library import ( "log" "os" "path/filepath" "strconv" "strings" "gitbay.org/cmc/gallery/internal/format" ) // Slugs that would collide with fixed routes. var reserved = map[string]bool{"img": true, "info": true, "timeline": true, "_": true} // Scan reads every album under root. Unreadable albums and files are logged and skipped. func Scan(root string, reg *format.Registry, cache *MetaCache) (*Library, error) { entries, err := os.ReadDir(root) if err != nil { return nil, err } lib := &Library{bySlug: map[string]*Album{}} keep := map[string]bool{} for _, e := range entries { name := e.Name() if !e.IsDir() || strings.HasPrefix(name, ".") { continue } if reserved[name] { log.Printf("skipping album %q: reserved name", name) continue } a, err := scanAlbum(root, name, reg, cache, keep) if err != nil { log.Printf("album %s: %v", name, err) lib.Incomplete = true continue } if len(a.Items) == 0 { continue } lib.Albums = append(lib.Albums, a) lib.bySlug[name] = a } sortAlbums(lib.Albums) lib.Timeline = buildTimeline(lib.Albums) if len(lib.Albums) == 0 { lib.Incomplete = true } if src, err := os.ReadFile(filepath.Join(root, "about.org")); err == nil { if _, html, err := parseOrg(src, "about.org"); err != nil { log.Printf("about.org: %v", err) } else { lib.About = html } } if lib.Incomplete { log.Printf("scan incomplete: keeping existing meta cache") } else if err := cache.save(keep); err != nil { log.Printf("meta cache: %v", err) } return lib, nil } func scanAlbum(root, slug string, reg *format.Registry, cache *MetaCache, keep map[string]bool) (*Album, error) { dir := filepath.Join(root, slug) entries, err := os.ReadDir(dir) if err != nil { return nil, err } a := &Album{Slug: slug, Title: slug, byName: map[string]int{}} sortBy, cover := "taken", "" if src, err := os.ReadFile(filepath.Join(dir, "album.org")); err == nil { kw, desc, err := parseOrg(src, filepath.Join(slug, "album.org")) if err != nil { log.Printf("%s/album.org: %v", slug, err) } else { if v := kw["title"]; v != "" { a.Title = v } a.Date, a.Description, cover = kw["date"], desc, kw["cover"] if v := kw["order"]; v != "" { if n, err := strconv.Atoi(v); err == nil { a.Order, a.HasOrder = n, true } else { log.Printf("%s/album.org: bad order %q", slug, v) } } switch v := kw["sort"]; v { case "", "taken": case "name": sortBy = v default: log.Printf("%s/album.org: bad sort %q", slug, v) } } } for _, e := range entries { name := e.Name() if e.IsDir() || strings.HasPrefix(name, ".") { continue } path := filepath.Join(dir, name) f := reg.Lookup(path) if f == nil { continue } fi, err := e.Info() if err != nil { log.Printf("%s/%s: %v", slug, name, err) continue } rel := slug + "/" + name m, ok := cache.get(rel, fi) if !ok { if m, err = f.Metadata(path); err != nil { log.Printf("%s: %v", rel, err) continue } cache.put(rel, fi, m) } keep[rel] = true taken := m.Taken if taken.IsZero() { taken = fi.ModTime() } a.Items = append(a.Items, &Item{ Album: slug, Name: name, Path: path, ModTime: fi.ModTime(), Size: fi.Size(), Taken: taken, Meta: m, Format: f, }) } sortItems(a.Items, sortBy) for i, it := range a.Items { a.byName[it.Name] = i } if i, ok := a.byName[cover]; ok { a.Cover = a.Items[i] } else if len(a.Items) > 0 { if cover != "" { log.Printf("%s/album.org: cover %q not found", slug, cover) } a.Cover = a.Items[0] } return a, nil }