timefiles/lib/projects.go

142 lines
3.1 KiB
Go

package lib
import (
"encoding/json"
"os"
"path/filepath"
)
// Project entity. In addition to the JSON properties as defined by the specification, it holds activities of the
// project organized by title and by ID in a map.
type Project struct {
ID string `json:"id"`
Title string `json:"title"`
activitiesByTitle map[string]*Activity
activitiesByID map[string]*Activity
activitiesLoaded bool
}
// NewProject creates a new project instance.
func NewProject() *Project {
return &Project{
activitiesByID: make(map[string]*Activity),
activitiesByTitle: make(map[string]*Activity),
}
}
// GetProjects retrieves all stored project instances. It does not load related data like activities or times.
func GetProjects() ([]*Project, error) {
folder, folderErr := getProjectFolder()
if folderErr != nil {
return nil, folderErr
}
dirErr := os.MkdirAll(folder, 0o750)
if dirErr != nil {
return nil, dirErr
}
files, filesErr := os.ReadDir(folder)
if filesErr != nil {
return nil, filesErr
}
projects := []*Project{}
for _, file := range files {
project, fileErr := readProject(filepath.Join(folder, file.Name()))
if fileErr != nil {
return nil, fileErr
}
projects = append(projects, project)
}
return projects, nil
}
// GetProject retrieves a single project by its ID. It does not load related data like activities or times.
func GetProject(id string) (*Project, error) {
file, fileErr := getProjectFile(id)
if fileErr != nil {
return nil, fileErr
}
project, err := readProject(file)
if err != nil {
return nil, err
}
project.ID = id
return project, nil
}
// SetProject persists a project.
func SetProject(project *Project) error {
uuid, uuidErr := newUUID()
if uuidErr != nil {
return uuidErr
}
project.ID = uuid
file, fileErr := getProjectFile(uuid)
if fileErr != nil {
return fileErr
}
return writeProject(file, project)
}
// readProject reads a project from a file and converts it into an entity instance.
func readProject(filename string) (*Project, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
project := NewProject()
if err := json.NewDecoder(file).Decode(&project); err != nil {
return nil, err
}
return project, nil
}
// writeProject writes a project instance to a file.
func writeProject(filename string, project *Project) error {
dirErr := os.MkdirAll(filepath.Dir(filename), 0o750)
if dirErr != nil {
return dirErr
}
file, fileErr := os.Create(filename)
if fileErr != nil {
return fileErr
}
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
jsonErr := encoder.Encode(project)
if jsonErr != nil {
return jsonErr
}
return nil
}
// getProjectFolder returns the folder to store project entity files in.
func getProjectFolder() (string, error) {
return getDataDir("projects")
}
// getProjectFile returns the file name for a project entity instance.
func getProjectFile(name string) (string, error) {
folder, err := getProjectFolder()
if err != nil {
return "", err
}
return filepath.Join(folder, name+".json"), nil
}