203 lines
4.6 KiB
Go
203 lines
4.6 KiB
Go
package changelist
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/docker/distribution/uuid"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// FileChangelist stores all the changes as files
|
|
type FileChangelist struct {
|
|
dir string
|
|
}
|
|
|
|
// NewFileChangelist is a convenience method for returning FileChangeLists
|
|
func NewFileChangelist(dir string) (*FileChangelist, error) {
|
|
logrus.Debug("Making dir path: ", dir)
|
|
err := os.MkdirAll(dir, 0700)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &FileChangelist{dir: dir}, nil
|
|
}
|
|
|
|
// getFileNames reads directory, filtering out child directories
|
|
func getFileNames(dirName string) ([]os.FileInfo, error) {
|
|
var dirListing, fileInfos []os.FileInfo
|
|
dir, err := os.Open(dirName)
|
|
if err != nil {
|
|
return fileInfos, err
|
|
}
|
|
defer dir.Close()
|
|
dirListing, err = dir.Readdir(0)
|
|
if err != nil {
|
|
return fileInfos, err
|
|
}
|
|
for _, f := range dirListing {
|
|
if f.IsDir() {
|
|
continue
|
|
}
|
|
fileInfos = append(fileInfos, f)
|
|
}
|
|
sort.Sort(fileChanges(fileInfos))
|
|
return fileInfos, nil
|
|
}
|
|
|
|
// Read a JSON formatted file from disk; convert to TUFChange struct
|
|
func unmarshalFile(dirname string, f os.FileInfo) (*TUFChange, error) {
|
|
c := &TUFChange{}
|
|
raw, err := ioutil.ReadFile(filepath.Join(dirname, f.Name()))
|
|
if err != nil {
|
|
return c, err
|
|
}
|
|
err = json.Unmarshal(raw, c)
|
|
if err != nil {
|
|
return c, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// List returns a list of sorted changes
|
|
func (cl FileChangelist) List() []Change {
|
|
var changes []Change
|
|
fileInfos, err := getFileNames(cl.dir)
|
|
if err != nil {
|
|
return changes
|
|
}
|
|
for _, f := range fileInfos {
|
|
c, err := unmarshalFile(cl.dir, f)
|
|
if err != nil {
|
|
logrus.Warn(err.Error())
|
|
continue
|
|
}
|
|
changes = append(changes, c)
|
|
}
|
|
return changes
|
|
}
|
|
|
|
// Add adds a change to the file change list
|
|
func (cl FileChangelist) Add(c Change) error {
|
|
cJSON, err := json.Marshal(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.Generate())
|
|
return ioutil.WriteFile(filepath.Join(cl.dir, filename), cJSON, 0644)
|
|
}
|
|
|
|
// Remove deletes the changes found at the given indices
|
|
func (cl FileChangelist) Remove(idxs []int) error {
|
|
fileInfos, err := getFileNames(cl.dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
remove := make(map[int]struct{})
|
|
for _, i := range idxs {
|
|
remove[i] = struct{}{}
|
|
}
|
|
for i, c := range fileInfos {
|
|
if _, ok := remove[i]; ok {
|
|
file := filepath.Join(cl.dir, c.Name())
|
|
if err := os.Remove(file); err != nil {
|
|
logrus.Errorf("could not remove change %d: %s", i, err.Error())
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Clear clears the change list
|
|
// N.B. archiving not currently implemented
|
|
func (cl FileChangelist) Clear(archive string) error {
|
|
dir, err := os.Open(cl.dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dir.Close()
|
|
files, err := dir.Readdir(0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, f := range files {
|
|
os.Remove(filepath.Join(cl.dir, f.Name()))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close is a no-op
|
|
func (cl FileChangelist) Close() error {
|
|
// Nothing to do here
|
|
return nil
|
|
}
|
|
|
|
// Location returns the file path to the changelist
|
|
func (cl FileChangelist) Location() string {
|
|
return cl.dir
|
|
}
|
|
|
|
// NewIterator creates an iterator from FileChangelist
|
|
func (cl FileChangelist) NewIterator() (ChangeIterator, error) {
|
|
fileInfos, err := getFileNames(cl.dir)
|
|
if err != nil {
|
|
return &FileChangeListIterator{}, err
|
|
}
|
|
return &FileChangeListIterator{dirname: cl.dir, collection: fileInfos}, nil
|
|
}
|
|
|
|
// IteratorBoundsError is an Error type used by Next()
|
|
type IteratorBoundsError int
|
|
|
|
// Error implements the Error interface
|
|
func (e IteratorBoundsError) Error() string {
|
|
return fmt.Sprintf("Iterator index (%d) out of bounds", e)
|
|
}
|
|
|
|
// FileChangeListIterator is a concrete instance of ChangeIterator
|
|
type FileChangeListIterator struct {
|
|
index int
|
|
dirname string
|
|
collection []os.FileInfo
|
|
}
|
|
|
|
// Next returns the next Change in the FileChangeList
|
|
func (m *FileChangeListIterator) Next() (item Change, err error) {
|
|
if m.index >= len(m.collection) {
|
|
return nil, IteratorBoundsError(m.index)
|
|
}
|
|
f := m.collection[m.index]
|
|
m.index++
|
|
item, err = unmarshalFile(m.dirname, f)
|
|
return
|
|
}
|
|
|
|
// HasNext indicates whether iterator is exhausted
|
|
func (m *FileChangeListIterator) HasNext() bool {
|
|
return m.index < len(m.collection)
|
|
}
|
|
|
|
type fileChanges []os.FileInfo
|
|
|
|
// Len returns the length of a file change list
|
|
func (cs fileChanges) Len() int {
|
|
return len(cs)
|
|
}
|
|
|
|
// Less compares the names of two different file changes
|
|
func (cs fileChanges) Less(i, j int) bool {
|
|
return cs[i].Name() < cs[j].Name()
|
|
}
|
|
|
|
// Swap swaps the position of two file changes
|
|
func (cs fileChanges) Swap(i, j int) {
|
|
tmp := cs[i]
|
|
cs[i] = cs[j]
|
|
cs[j] = tmp
|
|
}
|