blob: 8dc838c07b38b28570df5bd46aae5b149f839f16 [file] [log] [blame]
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/golang/protobuf/proto"
reluipb "golang.org/x/build/cmd/relui/protos"
)
// store is a persistence adapter for saving data.
type store interface {
AddWorkflow(workflow *reluipb.Workflow) error
BuildableTask(workflowId, id string) *reluipb.BuildableTask
Workflow(id string) *reluipb.Workflow
Workflows() []*reluipb.Workflow
}
var _ store = (*fileStore)(nil)
// newFileStore initializes a fileStore ready for use.
//
// If dir is set to an empty string (""), no data will be saved to disk.
func newFileStore(dir string) *fileStore {
return &fileStore{
persistDir: dir,
ls: new(reluipb.LocalStorage),
}
}
// fileStoreName is the name of the data file used by fileStore for persistence.
const fileStoreName = "local_storage.textpb"
// fileStore is a non-durable implementation of store that keeps everything in memory.
type fileStore struct {
mu sync.Mutex
ls *reluipb.LocalStorage
// persistDir is a path to a directory for saving application data in textproto format.
// Set persistDir to an empty string to disable saving and loading from the filesystem.
persistDir string
}
// AddWorkflow adds a workflow to the store, persisting changes to disk.
func (f *fileStore) AddWorkflow(w *reluipb.Workflow) error {
f.mu.Lock()
f.ls.Workflows = append(f.ls.Workflows, w)
f.mu.Unlock()
if err := f.persist(); err != nil {
return err
}
return nil
}
// Workflows returns all reluipb.Workflows stored.
func (f *fileStore) Workflows() []*reluipb.Workflow {
return f.localStorage().GetWorkflows()
}
// Workflow returns a single reluipb.Workflow found by its id. If it is not found, it returns nil.
func (f *fileStore) Workflow(id string) *reluipb.Workflow {
for _, w := range f.Workflows() {
if w.GetId() == id {
return w
}
}
return nil
}
// BuildableTask returns a single reluipb.BuildableTask found by the reluipb.Workflow id and its id.
// If it is not found, it returns nil.
func (f *fileStore) BuildableTask(workflowId, id string) *reluipb.BuildableTask {
wf := f.Workflow(workflowId)
for _, t := range wf.GetBuildableTasks() {
if t.GetId() == id {
return t
}
}
return nil
}
// localStorage returns a deep copy of data stored in fileStore.
func (f *fileStore) localStorage() *reluipb.LocalStorage {
f.mu.Lock()
defer f.mu.Unlock()
return proto.Clone(f.ls).(*reluipb.LocalStorage)
}
// persist saves fileStore state to persistDir/fileStoreName.
func (f *fileStore) persist() error {
if f.persistDir == "" {
return nil
}
if err := os.MkdirAll(f.persistDir, 0755); err != nil {
return fmt.Errorf("os.MkDirAll(%q, %v) = %w", f.persistDir, 0755, err)
}
dst := filepath.Join(f.persistDir, fileStoreName)
data := []byte(proto.MarshalTextString(f.localStorage()))
if err := ioutil.WriteFile(dst, data, 0644); err != nil {
return fmt.Errorf("ioutil.WriteFile(%q, _, %v) = %w", dst, 0644, err)
}
return nil
}
// load reads fileStore state from persistDir/fileStoreName.
func (f *fileStore) load() error {
if f.persistDir == "" {
return nil
}
path := filepath.Join(f.persistDir, fileStoreName)
b, err := ioutil.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("ioutil.ReadFile(%q) = _, %v", path, err)
}
f.mu.Lock()
defer f.mu.Unlock()
return proto.UnmarshalText(string(b), f.ls)
}