gonf/pkg/files.go

196 lines
4 KiB
Go

package gonf
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"io"
"io/fs"
"log/slog"
"os"
"path/filepath"
)
var files []*FilePromise
func init() {
files = make([]*FilePromise, 0)
}
type FileType int
const (
FILE = iota
DIRECTORY
)
type FilePromise struct {
chain []Promise
contents Value
dirPermissions *Permissions
err error
filename Value
fileType FileType
permissions *Permissions
status Status
}
func Directory(filename any) *FilePromise {
return &FilePromise{
chain: nil,
contents: nil,
dirPermissions: nil,
err: nil,
filename: interfaceToTemplateValue(filename),
fileType: DIRECTORY,
permissions: nil,
status: DECLARED,
}
}
func File(filename any) *FilePromise {
return &FilePromise{
chain: nil,
contents: nil,
dirPermissions: nil,
err: nil,
filename: interfaceToTemplateValue(filename),
fileType: FILE,
permissions: nil,
status: DECLARED,
}
}
func (f *FilePromise) Contents(contents any) *FilePromise {
f.contents = interfaceToValue(contents)
return f
}
func (f *FilePromise) DirectoriesPermissions(p *Permissions) *FilePromise {
f.dirPermissions = p
return f
}
func (f *FilePromise) Permissions(p *Permissions) *FilePromise {
f.permissions = p
return f
}
func (f *FilePromise) Template(contents any) *FilePromise {
f.contents = interfaceToTemplateValue(contents)
return f
}
func (f *FilePromise) IfRepaired(p ...Promise) Promise {
f.chain = append(f.chain, p...)
return f
}
func (f *FilePromise) Promise() *FilePromise {
if f.status == DECLARED {
f.status = PROMISED
files = append(files, f)
}
return f
}
func (f *FilePromise) Resolve() {
filename := f.filename.String()
if f.status, f.err = makeDirectoriesHierarchy(filepath.Dir(filename), f.dirPermissions); f.err != nil {
return
}
switch f.fileType {
case DIRECTORY:
if f.status, f.err = makeDirectoriesHierarchy(filepath.Clean(filename), f.dirPermissions); f.err != nil {
return
}
case FILE:
if f.contents != nil {
var sumFile []byte
sumFile, f.err = sha256sumOfFile(filename)
if f.err != nil {
if !errors.Is(f.err, fs.ErrNotExist) {
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
f.status = BROKEN
return
}
}
contents := f.contents.Bytes()
sumContents := sha256sum(contents)
if !bytes.Equal(sumFile, sumContents) {
if f.err = writeFile(filename, contents); f.err != nil {
f.status = BROKEN
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
return
}
f.status = REPAIRED
}
}
default:
panic(fmt.Errorf("unknown File type enum value %d", f.fileType))
}
if f.permissions != nil {
var status Status
status, f.err = f.permissions.resolve(filename)
if f.status == PROMISED || status == BROKEN {
f.status = status
}
if f.err != nil {
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
return
}
}
if f.status == REPAIRED {
slog.Info("file", "filename", f.filename, "status", f.status)
for _, p := range f.chain {
p.Resolve()
}
} else {
f.status = KEPT
slog.Debug("file", "filename", f.filename, "status", f.status)
}
}
func (f FilePromise) Status() Status {
return f.status
}
func resolveFiles() (status Status) {
status = KEPT
for _, f := range files {
if f.status == PROMISED {
f.Resolve()
switch f.status {
case BROKEN:
return BROKEN
case REPAIRED:
status = REPAIRED
}
}
}
return
}
func sha256sumOfFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
func writeFile(filename string, contents []byte) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(contents)
return err
}