2023-11-05 14:36:33 +01:00
|
|
|
package file
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
2024-05-22 10:00:10 +02:00
|
|
|
"fmt"
|
2023-11-05 14:36:33 +01:00
|
|
|
"io"
|
2023-11-20 14:14:09 +01:00
|
|
|
"io/fs"
|
2023-11-05 14:36:33 +01:00
|
|
|
"os"
|
2023-11-20 14:14:09 +01:00
|
|
|
"path/filepath"
|
2023-11-05 14:36:33 +01:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2024-02-06 09:03:57 +01:00
|
|
|
"git.andreafazzi.eu/andrea/probo/pkg/models"
|
|
|
|
"git.andreafazzi.eu/andrea/probo/pkg/store"
|
2023-11-13 21:01:12 +01:00
|
|
|
"gopkg.in/yaml.v2"
|
2023-11-05 14:36:33 +01:00
|
|
|
)
|
|
|
|
|
2023-11-18 11:12:07 +01:00
|
|
|
type QuizFileStore = FileStore[*models.Quiz, *store.QuizStore]
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-18 11:12:07 +01:00
|
|
|
func NewQuizFileStore(config *FileStoreConfig[*models.Quiz, *store.QuizStore]) (*QuizFileStore, error) {
|
|
|
|
return NewFileStore[*models.Quiz, *store.QuizStore](config, store.NewQuizStore())
|
|
|
|
}
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-20 14:14:09 +01:00
|
|
|
func NewDefaultQuizFileStore() (*QuizFileStore, error) {
|
|
|
|
return NewQuizFileStore(
|
|
|
|
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
|
|
|
|
FilePathConfig: FilePathConfig{GetDefaultQuizzesDir(), "quiz", ".md"},
|
|
|
|
IndexDirFunc: DefaultQuizIndexDirFunc,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func DefaultQuizIndexDirFunc(s *QuizFileStore) error {
|
|
|
|
files, err := os.ReadDir(s.Dir)
|
2023-11-18 11:12:07 +01:00
|
|
|
if err != nil {
|
2023-11-20 14:14:09 +01:00
|
|
|
return err
|
2023-11-18 11:12:07 +01:00
|
|
|
}
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-20 14:14:09 +01:00
|
|
|
entityFiles := make([]fs.DirEntry, 0)
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-20 14:14:09 +01:00
|
|
|
for _, file := range files {
|
|
|
|
filename := file.Name()
|
|
|
|
if !file.IsDir() && strings.HasSuffix(filename, s.FileSuffix) {
|
|
|
|
entityFiles = append(entityFiles, file)
|
|
|
|
}
|
2023-11-18 11:12:07 +01:00
|
|
|
}
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-20 14:14:09 +01:00
|
|
|
for _, file := range entityFiles {
|
|
|
|
filename := file.Name()
|
|
|
|
fullPath := filepath.Join(s.Dir, filename)
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-20 14:14:09 +01:00
|
|
|
content, err := os.ReadFile(fullPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-20 14:14:09 +01:00
|
|
|
var entity = new(models.Quiz)
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-20 14:14:09 +01:00
|
|
|
err = entity.Unmarshal(content)
|
|
|
|
if err != nil {
|
2024-05-22 10:00:10 +02:00
|
|
|
return fmt.Errorf("An error occurred unmarshalling %v: %v", filename, err)
|
2023-11-20 14:14:09 +01:00
|
|
|
}
|
2023-11-18 11:12:07 +01:00
|
|
|
|
2023-11-20 14:14:09 +01:00
|
|
|
var errQuizAlreadyPresent *store.ErrQuizAlreadyPresent
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-21 15:12:13 +01:00
|
|
|
mEntity, err := s.Storer.Create(entity)
|
2023-11-20 14:14:09 +01:00
|
|
|
if err != nil && !errors.As(err, &errQuizAlreadyPresent) {
|
|
|
|
return err
|
|
|
|
}
|
2023-11-18 11:12:07 +01:00
|
|
|
|
2023-11-20 14:14:09 +01:00
|
|
|
if entity.ID == "" {
|
|
|
|
writeQuizHeader(fullPath, &models.Meta{
|
|
|
|
ID: mEntity.ID,
|
|
|
|
CreatedAt: time.Now(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
s.SetPath(mEntity, fullPath)
|
2023-11-18 11:12:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2023-11-20 14:14:09 +01:00
|
|
|
|
2023-11-05 14:36:33 +01:00
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
func writeQuizHeader(path string, meta *models.Meta) (*models.Meta, error) {
|
|
|
|
readMeta, err := readQuizHeader(path)
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if readMeta == nil {
|
2023-11-13 21:01:12 +01:00
|
|
|
file, err := os.Open(path)
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-11-13 21:01:12 +01:00
|
|
|
defer file.Close()
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
var buffer bytes.Buffer
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
header, err := yaml.Marshal(meta)
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
2023-11-13 21:01:12 +01:00
|
|
|
return nil, err
|
2023-11-05 14:36:33 +01:00
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
_, err = buffer.WriteString("---\n" + string(header) + "---\n")
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
_, err = io.Copy(&buffer, file)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-11-05 14:36:33 +01:00
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
file, err = os.Create(path)
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-11-13 21:01:12 +01:00
|
|
|
defer file.Close()
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
_, err = io.Copy(file, &buffer)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-11-05 14:36:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
return meta, nil
|
|
|
|
}
|
2023-11-05 14:36:33 +01:00
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
func readQuizHeader(path string) (*models.Meta, error) {
|
|
|
|
data, err := os.ReadFile(path)
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-11-13 21:01:12 +01:00
|
|
|
meta, _, err := models.ParseMetaHeaderFromMarkdown(string(data))
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-11-13 21:01:12 +01:00
|
|
|
return meta, nil
|
2023-11-05 14:36:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func addMetaHeaderToMarkdown(content string, meta *models.Meta) (string, error) {
|
|
|
|
var buffer bytes.Buffer
|
|
|
|
|
|
|
|
header, err := yaml.Marshal(meta)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
_, err = buffer.WriteString("---\n" + string(header) + "---\n")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = buffer.WriteString(content)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return buffer.String(), nil
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
func removeQuizHeader(path string) (*models.Meta, error) {
|
|
|
|
file, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
var buffer bytes.Buffer
|
|
|
|
|
|
|
|
reader := bufio.NewReader(file)
|
|
|
|
|
|
|
|
var meta models.Meta
|
2023-11-05 14:36:33 +01:00
|
|
|
var line string
|
2023-11-13 21:01:12 +01:00
|
|
|
var sb strings.Builder
|
2023-11-05 14:36:33 +01:00
|
|
|
for {
|
2023-11-13 21:01:12 +01:00
|
|
|
line, err = reader.ReadString('\n')
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
2023-11-13 21:01:12 +01:00
|
|
|
return nil, err
|
2023-11-05 14:36:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if strings.TrimSpace(line) == "---" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
2023-11-13 21:01:12 +01:00
|
|
|
line, err = reader.ReadString('\n')
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
2023-11-13 21:01:12 +01:00
|
|
|
return nil, err
|
2023-11-05 14:36:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if strings.TrimSpace(line) == "---" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
sb.WriteString(line)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = yaml.Unmarshal([]byte(sb.String()), &meta)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
_, err = io.Copy(&buffer, reader)
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
file, err = os.Create(path)
|
2023-11-05 14:36:33 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
_, err = io.Copy(file, &buffer)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:01:12 +01:00
|
|
|
return &meta, nil
|
2023-11-05 14:36:33 +01:00
|
|
|
}
|