2020-07-14 20:42:03 +03:00
|
|
|
// 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 (
|
2020-07-31 23:17:15 +03:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2020-07-14 20:42:03 +03:00
|
|
|
"sync"
|
|
|
|
|
2020-07-31 23:17:15 +03:00
|
|
|
"github.com/golang/protobuf/proto"
|
2020-07-14 20:42:03 +03:00
|
|
|
reluipb "golang.org/x/build/cmd/relui/protos"
|
|
|
|
)
|
|
|
|
|
|
|
|
// store is a persistence adapter for saving data.
|
|
|
|
type store interface {
|
|
|
|
AddWorkflow(workflow *reluipb.Workflow) error
|
2020-09-24 21:01:35 +03:00
|
|
|
BuildableTask(workflowId, id string) *reluipb.BuildableTask
|
|
|
|
Workflow(id string) *reluipb.Workflow
|
|
|
|
Workflows() []*reluipb.Workflow
|
2020-07-14 20:42:03 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 23:17:15 +03:00
|
|
|
var _ store = (*fileStore)(nil)
|
2020-07-14 20:42:03 +03:00
|
|
|
|
2020-07-31 23:17:15 +03:00
|
|
|
// 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.
|
2020-08-26 23:54:03 +03:00
|
|
|
// Set persistDir to an empty string to disable saving and loading from the filesystem.
|
2020-07-31 23:17:15 +03:00
|
|
|
persistDir string
|
2020-07-14 20:42:03 +03:00
|
|
|
}
|
|
|
|
|
2020-07-31 23:17:15 +03:00
|
|
|
// 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
|
|
|
|
}
|
2020-07-14 20:42:03 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-24 21:01:35 +03:00
|
|
|
// Workflows returns all reluipb.Workflows stored.
|
2020-07-31 23:17:15 +03:00
|
|
|
func (f *fileStore) Workflows() []*reluipb.Workflow {
|
|
|
|
return f.localStorage().GetWorkflows()
|
|
|
|
}
|
|
|
|
|
2020-09-24 21:01:35 +03:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2020-07-31 23:17:15 +03:00
|
|
|
// 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
|
2020-07-14 20:42:03 +03:00
|
|
|
}
|
2020-08-26 23:54:03 +03:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|