1package changelist 2 3import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "sort" 10 "time" 11 12 "github.com/docker/distribution/uuid" 13 "github.com/sirupsen/logrus" 14) 15 16// FileChangelist stores all the changes as files 17type FileChangelist struct { 18 dir string 19} 20 21// NewFileChangelist is a convenience method for returning FileChangeLists 22func NewFileChangelist(dir string) (*FileChangelist, error) { 23 logrus.Debug("Making dir path: ", dir) 24 err := os.MkdirAll(dir, 0700) 25 if err != nil { 26 return nil, err 27 } 28 return &FileChangelist{dir: dir}, nil 29} 30 31// getFileNames reads directory, filtering out child directories 32func getFileNames(dirName string) ([]os.FileInfo, error) { 33 var dirListing, fileInfos []os.FileInfo 34 dir, err := os.Open(dirName) 35 if err != nil { 36 return fileInfos, err 37 } 38 defer dir.Close() 39 dirListing, err = dir.Readdir(0) 40 if err != nil { 41 return fileInfos, err 42 } 43 for _, f := range dirListing { 44 if f.IsDir() { 45 continue 46 } 47 fileInfos = append(fileInfos, f) 48 } 49 sort.Sort(fileChanges(fileInfos)) 50 return fileInfos, nil 51} 52 53// Read a JSON formatted file from disk; convert to TUFChange struct 54func unmarshalFile(dirname string, f os.FileInfo) (*TUFChange, error) { 55 c := &TUFChange{} 56 raw, err := ioutil.ReadFile(filepath.Join(dirname, f.Name())) 57 if err != nil { 58 return c, err 59 } 60 err = json.Unmarshal(raw, c) 61 if err != nil { 62 return c, err 63 } 64 return c, nil 65} 66 67// List returns a list of sorted changes 68func (cl FileChangelist) List() []Change { 69 var changes []Change 70 fileInfos, err := getFileNames(cl.dir) 71 if err != nil { 72 return changes 73 } 74 for _, f := range fileInfos { 75 c, err := unmarshalFile(cl.dir, f) 76 if err != nil { 77 logrus.Warn(err.Error()) 78 continue 79 } 80 changes = append(changes, c) 81 } 82 return changes 83} 84 85// Add adds a change to the file change list 86func (cl FileChangelist) Add(c Change) error { 87 cJSON, err := json.Marshal(c) 88 if err != nil { 89 return err 90 } 91 filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.Generate()) 92 return ioutil.WriteFile(filepath.Join(cl.dir, filename), cJSON, 0644) 93} 94 95// Remove deletes the changes found at the given indices 96func (cl FileChangelist) Remove(idxs []int) error { 97 fileInfos, err := getFileNames(cl.dir) 98 if err != nil { 99 return err 100 } 101 remove := make(map[int]struct{}) 102 for _, i := range idxs { 103 remove[i] = struct{}{} 104 } 105 for i, c := range fileInfos { 106 if _, ok := remove[i]; ok { 107 file := filepath.Join(cl.dir, c.Name()) 108 if err := os.Remove(file); err != nil { 109 logrus.Errorf("could not remove change %d: %s", i, err.Error()) 110 } 111 } 112 } 113 return nil 114} 115 116// Clear clears the change list 117// N.B. archiving not currently implemented 118func (cl FileChangelist) Clear(archive string) error { 119 dir, err := os.Open(cl.dir) 120 if err != nil { 121 return err 122 } 123 defer dir.Close() 124 files, err := dir.Readdir(0) 125 if err != nil { 126 return err 127 } 128 for _, f := range files { 129 os.Remove(filepath.Join(cl.dir, f.Name())) 130 } 131 return nil 132} 133 134// Close is a no-op 135func (cl FileChangelist) Close() error { 136 // Nothing to do here 137 return nil 138} 139 140// Location returns the file path to the changelist 141func (cl FileChangelist) Location() string { 142 return cl.dir 143} 144 145// NewIterator creates an iterator from FileChangelist 146func (cl FileChangelist) NewIterator() (ChangeIterator, error) { 147 fileInfos, err := getFileNames(cl.dir) 148 if err != nil { 149 return &FileChangeListIterator{}, err 150 } 151 return &FileChangeListIterator{dirname: cl.dir, collection: fileInfos}, nil 152} 153 154// IteratorBoundsError is an Error type used by Next() 155type IteratorBoundsError int 156 157// Error implements the Error interface 158func (e IteratorBoundsError) Error() string { 159 return fmt.Sprintf("Iterator index (%d) out of bounds", e) 160} 161 162// FileChangeListIterator is a concrete instance of ChangeIterator 163type FileChangeListIterator struct { 164 index int 165 dirname string 166 collection []os.FileInfo 167} 168 169// Next returns the next Change in the FileChangeList 170func (m *FileChangeListIterator) Next() (item Change, err error) { 171 if m.index >= len(m.collection) { 172 return nil, IteratorBoundsError(m.index) 173 } 174 f := m.collection[m.index] 175 m.index++ 176 item, err = unmarshalFile(m.dirname, f) 177 return 178} 179 180// HasNext indicates whether iterator is exhausted 181func (m *FileChangeListIterator) HasNext() bool { 182 return m.index < len(m.collection) 183} 184 185type fileChanges []os.FileInfo 186 187// Len returns the length of a file change list 188func (cs fileChanges) Len() int { 189 return len(cs) 190} 191 192// Less compares the names of two different file changes 193func (cs fileChanges) Less(i, j int) bool { 194 return cs[i].Name() < cs[j].Name() 195} 196 197// Swap swaps the position of two file changes 198func (cs fileChanges) Swap(i, j int) { 199 tmp := cs[i] 200 cs[i] = cs[j] 201 cs[j] = tmp 202} 203