krz/skunky-art

Alternative privacy frontend for DeviantArt.

clone: git clone https://gitbay.org/krz/skunky-art.git

v1.3.7: static/templates-noembed.go · raw

  1//go:build !embed
  2
  3package static
  4
  5import (
  6	"bytes"
  7	"io/fs"
  8	"os"
  9	"strings"
 10	"time"
 11)
 12
 13// Templates is the in-memory asset filesystem populated by CopyTemplatesToMemory.
 14var Templates FS
 15
 16type file struct {
 17	path    string
 18	name    string
 19	content []byte
 20}
 21
 22var templateNames = []string{}
 23var templates = make(map[string][]file)
 24
 25// StaticPath is the directory assets are read from at startup.
 26var StaticPath string
 27
 28// CopyTemplatesToMemory reads every asset under StaticPath into memory. It exits
 29// the process on failure, since the frontend cannot serve anything without them.
 30func CopyTemplatesToMemory() {
 31	baseDir, err := os.ReadDir(StaticPath)
 32	try(err)
 33
 34	for _, c := range baseDir {
 35		if c.IsDir() {
 36			templateNames = append(templateNames, c.Name())
 37
 38			var filePath strings.Builder
 39			filePath.WriteString(StaticPath)
 40			filePath.WriteString("/")
 41			filePath.WriteString(c.Name())
 42
 43			dir, err := os.ReadDir(filePath.String())
 44			try(err)
 45
 46			filePath.WriteString("/")
 47			for _, cd := range dir {
 48				f, err := os.ReadFile(filePath.String() + cd.Name())
 49				try(err)
 50				templates[c.Name()] = append(templates[c.Name()], file{
 51					content: f,
 52					name:    cd.Name(),
 53					path:    c.Name() + "/" + cd.Name(),
 54				})
 55			}
 56		}
 57	}
 58}
 59
 60// FS serves the in-memory assets. It implements the subset of fs.FS that
 61// template.ParseFS requires.
 62type FS struct{}
 63
 64// Open returns the asset stored at name, or an fs.PathError if there is none.
 65func (FS) Open(name string) (fs.File, error) {
 66	for i, l := 0, len(templateNames); i < l; i++ {
 67		for _, x := range templates[templateNames[i]] {
 68			if x.content != nil && name == x.path {
 69				return &File{
 70					name:    x.path,
 71					content: bytes.NewBuffer(x.content),
 72				}, nil
 73			}
 74		}
 75	}
 76	return nil, &fs.PathError{}
 77}
 78
 79// Glob returns the paths of every asset in the directory named by pattern's
 80// first segment, or an fs.PathError if none match.
 81func (FS) Glob(pattern string) ([]string, error) {
 82	trimmed := strings.Split(pattern, "/")
 83	var matches = []string{}
 84	for x, s := range templates {
 85		for i, l := 0, len(s); i < l && trimmed[0] == x; i++ {
 86			s := s[i]
 87			matches = append(matches, s.path)
 88		}
 89	}
 90	if len(matches) != 0 {
 91		return matches, nil
 92	}
 93	return nil, &fs.PathError{}
 94}
 95
 96func try(err error) {
 97	if err != nil {
 98		println(err.Error())
 99		os.Exit(1)
100	}
101}
102
103// fileInfo is a minimal fs.FileInfo. Assets are held in memory and never stat'd
104// for anything but their name, so the remaining fields report fixed values.
105//
106// Based on https://github.com/psanford/memfs; required for templates.ParseFS to
107// work correctly.
108type fileInfo struct {
109	name string
110}
111
112// Name returns the asset's path.
113func (fi fileInfo) Name() string {
114	return fi.name
115}
116
117// Size reports a fixed placeholder size; callers here never use it.
118func (fi fileInfo) Size() int64 {
119	return 4096
120}
121
122// Mode reports no mode bits: in-memory assets have no filesystem permissions.
123func (fileInfo) Mode() fs.FileMode {
124	return 0
125}
126
127// ModTime reports the zero time, as in-memory assets are never modified.
128func (fileInfo) ModTime() time.Time {
129	return time.Time{}
130}
131
132// IsDir always reports false: only files are stored, never directories.
133func (fileInfo) IsDir() bool {
134	return false
135}
136
137// Sys returns nil, as there is no underlying data source.
138func (fileInfo) Sys() any {
139	return nil
140}
141
142// File is a read-once handle to an in-memory asset.
143type File struct {
144	name    string
145	content *bytes.Buffer
146	closed  bool
147}
148
149// Stat returns the file's fileInfo. It never fails.
150func (f *File) Stat() (fs.FileInfo, error) {
151	return fileInfo{
152		name: f.name,
153	}, nil
154}
155
156// Read consumes the asset's contents, reporting fs.ErrClosed once closed.
157func (f *File) Read(b []byte) (int, error) {
158	if f.closed {
159		return 0, fs.ErrClosed
160	}
161	return f.content.Read(b)
162}
163
164// Close marks the file closed. Closing twice reports fs.ErrClosed.
165func (f *File) Close() error {
166	if f.closed {
167		return fs.ErrClosed
168	}
169	f.closed = true
170	return nil
171}