Files
hetty/pkg/proj/proj.go

167 lines
3.7 KiB
Go
Raw Normal View History

2020-10-11 17:09:39 +02:00
package proj
import (
2020-10-29 20:54:17 +01:00
"context"
2020-10-11 17:09:39 +02:00
"errors"
"fmt"
2020-10-29 20:54:17 +01:00
"log"
2020-10-11 17:09:39 +02:00
"regexp"
2020-10-29 20:54:17 +01:00
"sync"
2020-10-11 17:09:39 +02:00
)
2021-04-25 16:23:53 +02:00
type (
OnProjectOpenFn func(name string) error
OnProjectCloseFn func(name string) error
)
2020-10-29 20:54:17 +01:00
2020-10-11 17:09:39 +02:00
// Service is used for managing projects.
2022-01-01 16:11:49 +01:00
type Service interface {
Open(ctx context.Context, name string) (Project, error)
Close() error
Delete(name string) error
ActiveProject() (Project, error)
Projects() ([]Project, error)
OnProjectOpen(fn OnProjectOpenFn)
OnProjectClose(fn OnProjectCloseFn)
}
type service struct {
2020-10-29 20:54:17 +01:00
repo Repository
activeProject string
onProjectOpenFns []OnProjectOpenFn
onProjectCloseFns []OnProjectCloseFn
mu sync.RWMutex
2020-10-11 17:09:39 +02:00
}
type Project struct {
Name string
IsActive bool
}
var (
ErrNoProject = errors.New("proj: no open project")
2020-10-29 20:54:17 +01:00
ErrNoSettings = errors.New("proj: settings not found")
2020-10-11 17:09:39 +02:00
ErrInvalidName = errors.New("proj: invalid name, must be alphanumeric or whitespace chars")
)
var nameRegexp = regexp.MustCompile(`^[\w\d\s]+$`)
// NewService returns a new Service.
2022-01-01 16:11:49 +01:00
func NewService(repo Repository) (Service, error) {
return &service{
2020-10-29 20:54:17 +01:00
repo: repo,
2020-10-11 17:09:39 +02:00
}, nil
}
// Close closes the currently open project database (if there is one).
2022-01-01 16:11:49 +01:00
func (svc *service) Close() error {
2020-10-29 20:54:17 +01:00
svc.mu.Lock()
defer svc.mu.Unlock()
closedProject := svc.activeProject
2021-04-25 16:23:53 +02:00
2020-10-29 20:54:17 +01:00
if err := svc.repo.Close(); err != nil {
2021-04-25 16:23:53 +02:00
return fmt.Errorf("proj: could not close project: %w", err)
2020-10-11 17:09:39 +02:00
}
2020-10-29 20:54:17 +01:00
svc.activeProject = ""
svc.emitProjectClosed(closedProject)
2020-10-11 17:09:39 +02:00
return nil
}
// Delete removes a project database file from disk (if there is one).
2022-01-01 16:11:49 +01:00
func (svc *service) Delete(name string) error {
2020-10-11 17:09:39 +02:00
if name == "" {
return errors.New("proj: name cannot be empty")
}
2021-04-25 16:23:53 +02:00
2020-10-29 20:54:17 +01:00
if svc.activeProject == name {
2020-10-11 17:09:39 +02:00
return fmt.Errorf("proj: project (%v) is active", name)
}
2020-10-29 20:54:17 +01:00
if err := svc.repo.DeleteProject(name); err != nil {
2021-04-25 16:23:53 +02:00
return fmt.Errorf("proj: could not delete project: %w", err)
2020-10-11 17:09:39 +02:00
}
return nil
}
// Open opens a database identified with `name`. If a database with this
// identifier doesn't exist yet, it will be automatically created.
2022-01-01 16:11:49 +01:00
func (svc *service) Open(ctx context.Context, name string) (Project, error) {
2020-10-11 17:09:39 +02:00
if !nameRegexp.MatchString(name) {
return Project{}, ErrInvalidName
}
2020-10-29 20:54:17 +01:00
svc.mu.Lock()
defer svc.mu.Unlock()
if err := svc.repo.Close(); err != nil {
2021-04-25 16:23:53 +02:00
return Project{}, fmt.Errorf("proj: could not close previously open database: %w", err)
2020-10-11 17:09:39 +02:00
}
2020-10-29 20:54:17 +01:00
if err := svc.repo.OpenProject(name); err != nil {
2021-04-25 16:23:53 +02:00
return Project{}, fmt.Errorf("proj: could not open database: %w", err)
2020-10-11 17:09:39 +02:00
}
2020-10-29 20:54:17 +01:00
svc.activeProject = name
svc.emitProjectOpened()
2020-10-11 17:09:39 +02:00
return Project{
Name: name,
IsActive: true,
}, nil
}
2022-01-01 16:11:49 +01:00
func (svc *service) ActiveProject() (Project, error) {
2020-10-29 20:54:17 +01:00
activeProject := svc.activeProject
if activeProject == "" {
2020-10-11 17:09:39 +02:00
return Project{}, ErrNoProject
}
return Project{
2020-10-29 20:54:17 +01:00
Name: activeProject,
2020-10-11 17:09:39 +02:00
}, nil
}
2022-01-01 16:11:49 +01:00
func (svc *service) Projects() ([]Project, error) {
2020-10-29 20:54:17 +01:00
projects, err := svc.repo.Projects()
2020-10-11 17:09:39 +02:00
if err != nil {
2021-04-25 16:23:53 +02:00
return nil, fmt.Errorf("proj: could not get projects: %w", err)
2020-10-11 17:09:39 +02:00
}
2020-10-29 20:54:17 +01:00
return projects, nil
}
2022-01-01 16:11:49 +01:00
func (svc *service) OnProjectOpen(fn OnProjectOpenFn) {
2020-10-29 20:54:17 +01:00
svc.mu.Lock()
defer svc.mu.Unlock()
svc.onProjectOpenFns = append(svc.onProjectOpenFns, fn)
}
2022-01-01 16:11:49 +01:00
func (svc *service) OnProjectClose(fn OnProjectCloseFn) {
2020-10-29 20:54:17 +01:00
svc.mu.Lock()
defer svc.mu.Unlock()
svc.onProjectCloseFns = append(svc.onProjectCloseFns, fn)
}
2022-01-01 16:11:49 +01:00
func (svc *service) emitProjectOpened() {
2020-10-29 20:54:17 +01:00
for _, fn := range svc.onProjectOpenFns {
if err := fn(svc.activeProject); err != nil {
log.Printf("[ERROR] Could not execute onProjectOpen function: %v", err)
2020-10-11 17:09:39 +02:00
}
}
2020-10-29 20:54:17 +01:00
}
2020-10-11 17:09:39 +02:00
2022-01-01 16:11:49 +01:00
func (svc *service) emitProjectClosed(name string) {
2020-10-29 20:54:17 +01:00
for _, fn := range svc.onProjectCloseFns {
if err := fn(name); err != nil {
log.Printf("[ERROR] Could not execute onProjectClose function: %v", err)
}
}
2020-10-11 17:09:39 +02:00
}