internal/library/scan.go

151 lines · 3627 bytes

  1package library
  2
  3import (
  4	"log"
  5	"os"
  6	"path/filepath"
  7	"strconv"
  8	"strings"
  9
 10	"gitbay.org/cmc/gallery/internal/format"
 11)
 12
 13// Slugs that would collide with fixed routes.
 14var reserved = map[string]bool{"img": true, "info": true, "timeline": true, "_": true}
 15
 16// Scan reads every album under root. Unreadable albums and files are logged and skipped.
 17func Scan(root string, reg *format.Registry, cache *MetaCache) (*Library, error) {
 18	entries, err := os.ReadDir(root)
 19	if err != nil {
 20		return nil, err
 21	}
 22	lib := &Library{bySlug: map[string]*Album{}}
 23	keep := map[string]bool{}
 24	for _, e := range entries {
 25		name := e.Name()
 26		if !e.IsDir() || strings.HasPrefix(name, ".") {
 27			continue
 28		}
 29		if reserved[name] {
 30			log.Printf("skipping album %q: reserved name", name)
 31			continue
 32		}
 33		a, err := scanAlbum(root, name, reg, cache, keep)
 34		if err != nil {
 35			log.Printf("album %s: %v", name, err)
 36			lib.Incomplete = true
 37			continue
 38		}
 39		if len(a.Items) == 0 {
 40			continue
 41		}
 42		lib.Albums = append(lib.Albums, a)
 43		lib.bySlug[name] = a
 44	}
 45	sortAlbums(lib.Albums)
 46	lib.Timeline = buildTimeline(lib.Albums)
 47	if len(lib.Albums) == 0 {
 48		lib.Incomplete = true
 49	}
 50
 51	if src, err := os.ReadFile(filepath.Join(root, "about.org")); err == nil {
 52		if _, html, err := parseOrg(src, "about.org"); err != nil {
 53			log.Printf("about.org: %v", err)
 54		} else {
 55			lib.About = html
 56		}
 57	}
 58	if lib.Incomplete {
 59		log.Printf("scan incomplete: keeping existing meta cache")
 60	} else if err := cache.save(keep); err != nil {
 61		log.Printf("meta cache: %v", err)
 62	}
 63	return lib, nil
 64}
 65
 66func scanAlbum(root, slug string, reg *format.Registry, cache *MetaCache, keep map[string]bool) (*Album, error) {
 67	dir := filepath.Join(root, slug)
 68	entries, err := os.ReadDir(dir)
 69	if err != nil {
 70		return nil, err
 71	}
 72	a := &Album{Slug: slug, Title: slug, byName: map[string]int{}}
 73	sortBy, cover := "taken", ""
 74
 75	if src, err := os.ReadFile(filepath.Join(dir, "album.org")); err == nil {
 76		kw, desc, err := parseOrg(src, filepath.Join(slug, "album.org"))
 77		if err != nil {
 78			log.Printf("%s/album.org: %v", slug, err)
 79		} else {
 80			if v := kw["title"]; v != "" {
 81				a.Title = v
 82			}
 83			a.Date, a.Description, cover = kw["date"], desc, kw["cover"]
 84			if v := kw["order"]; v != "" {
 85				if n, err := strconv.Atoi(v); err == nil {
 86					a.Order, a.HasOrder = n, true
 87				} else {
 88					log.Printf("%s/album.org: bad order %q", slug, v)
 89				}
 90			}
 91			switch v := kw["sort"]; v {
 92			case "", "taken":
 93			case "name":
 94				sortBy = v
 95			default:
 96				log.Printf("%s/album.org: bad sort %q", slug, v)
 97			}
 98		}
 99	}
100
101	for _, e := range entries {
102		name := e.Name()
103		if e.IsDir() || strings.HasPrefix(name, ".") {
104			continue
105		}
106		path := filepath.Join(dir, name)
107		f := reg.Lookup(path)
108		if f == nil {
109			continue
110		}
111		fi, err := e.Info()
112		if err != nil {
113			log.Printf("%s/%s: %v", slug, name, err)
114			continue
115		}
116		rel := slug + "/" + name
117		m, ok := cache.get(rel, fi)
118		if !ok {
119			if m, err = f.Metadata(path); err != nil {
120				log.Printf("%s: %v", rel, err)
121				continue
122			}
123			cache.put(rel, fi, m)
124		}
125		keep[rel] = true
126		taken := m.Taken
127		if taken.IsZero() {
128			taken = fi.ModTime()
129		}
130		a.Items = append(a.Items, &Item{
131			Album: slug, Name: name, Path: path,
132			ModTime: fi.ModTime(), Size: fi.Size(),
133			Taken: taken,
134			Meta:  m, Format: f,
135		})
136	}
137
138	sortItems(a.Items, sortBy)
139	for i, it := range a.Items {
140		a.byName[it.Name] = i
141	}
142	if i, ok := a.byName[cover]; ok {
143		a.Cover = a.Items[i]
144	} else if len(a.Items) > 0 {
145		if cover != "" {
146			log.Printf("%s/album.org: cover %q not found", slug, cover)
147		}
148		a.Cover = a.Items[0]
149	}
150	return a, nil
151}