repository.go (5633B)
1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "io/fs" 8 "os" 9 "path" 10 "path/filepath" 11 "strings" 12 "time" 13 ) 14 15 type FSRepository struct { 16 rootPath string 17 } 18 19 func NewFSRepository(rootPath string) FSRepository { 20 return FSRepository{rootPath} 21 } 22 23 func (repo FSRepository) RunningEntry(group Group, entryTime *TimeContext) (Entry, error) { 24 entryPath := path.Join(repo.rootPath, group.Name, activeFile) 25 if _, err := os.Stat(entryPath); os.IsNotExist(err) { 26 return Entry{}, err 27 } 28 29 data, err := os.ReadFile(entryPath) 30 if err != nil { 31 return Entry{}, err 32 } 33 34 firstLine := strings.Split(string(data), "\n")[0] 35 start, _ := entryTime.ParseEntry(firstLine) 36 37 timeRange, err := NewTimeRange(start, time.Now()) 38 if err != nil { 39 return Entry{}, err 40 } 41 42 return Entry{ 43 TimeRange: timeRange, 44 Completed: false, 45 }, nil 46 } 47 48 func (r FSRepository) Save(group Group, entry Entry) error { 49 // TODO(tms) 22.10.22: maybe ask if user want create new folder (fe: typo) 50 groupPath := path.Join(r.rootPath, group.Name) 51 if err := os.MkdirAll(groupPath, os.ModePerm); err != nil { 52 return err 53 } 54 55 entryPath := path.Join(r.rootPath, group.Name, r.EntryName(entry)) 56 if err := os.WriteFile(entryPath, []byte(r.EntryData(entry)), os.ModePerm); err != nil { 57 return err 58 } 59 60 return nil 61 } 62 63 func (repo FSRepository) EntryData(entry Entry) string { 64 if entry.Completed { 65 return fmt.Sprintf("%s\n%s\n", entry.TimeRange.Start.Format(DataTimeLayout), entry.TimeRange.Stop.Format(DataTimeLayout)) 66 } 67 68 return fmt.Sprintf("%s\n", entry.TimeRange.Start.Format(DataTimeLayout)) 69 } 70 71 func (r FSRepository) EntryName(entry Entry) string { 72 if entry.Completed { 73 return strings.Join([]string{entry.TimeRange.Start.Format(FileNameLayout), entry.TimeRange.Stop.Format(FileNameLayout)}, "_") 74 } 75 76 return activeFile 77 } 78 79 func (repo FSRepository) Remove(group Group, entry Entry) error { 80 entryPath := path.Join(repo.rootPath, group.Name, repo.EntryName(entry)) 81 err := os.Remove(entryPath) 82 if err != nil { 83 return err 84 } 85 86 groupPath := path.Join(repo.rootPath, group.Name) 87 if empty, _ := repo.IsEmpty(groupPath); empty { 88 err = os.Remove(groupPath) 89 if err != nil { 90 return err 91 } 92 } 93 94 return nil 95 } 96 97 func (repo FSRepository) IsEmpty(groupPath string) (bool, error) { 98 f, err := os.Open(groupPath) 99 if err != nil { 100 return false, err 101 } 102 defer f.Close() 103 104 _, err = f.Readdirnames(1) // Or f.Readdir(1) 105 if err == io.EOF { 106 return true, nil 107 } 108 109 return false, err // Either not empty or error, suits both cases 110 } 111 112 func (repo FSRepository) ExistsEntry(group Group, entry Entry) bool { 113 fullPath := path.Join(repo.rootPath, group.Name, repo.EntryName(entry)) 114 if _, err := os.Stat(fullPath); !os.IsNotExist(err) { 115 return true 116 } 117 return false 118 } 119 120 func (repo FSRepository) Exists(group Group) bool { 121 groupPath := path.Join(repo.rootPath, group.Name) 122 if _, err := os.Stat(groupPath); !os.IsNotExist(err) { 123 return true 124 } 125 return false 126 } 127 128 func (repo FSRepository) ListEntries(group Group, entryTime *TimeContext) ([]Entry, error) { 129 if !repo.Exists(group) { 130 return []Entry{}, errors.New("Group '" + group.Name + "' does not exist") 131 } 132 133 var entries []Entry 134 groupPath := path.Join(repo.rootPath, group.Name) 135 err := filepath.WalkDir(groupPath, func(path string, d fs.DirEntry, err error) error { 136 if d.IsDir() { 137 if path == groupPath { 138 return nil 139 } 140 return fs.SkipDir 141 } 142 143 entry, err := repo.find(path, entryTime) 144 if err != nil { 145 fmt.Fprintln(os.Stderr, err) 146 os.Exit(1) 147 } 148 entries = append(entries, entry) 149 return nil 150 }) 151 if err != nil { 152 return []Entry{}, err 153 } 154 155 return entries, nil 156 } 157 158 func (repo FSRepository) ListGroups(groupPath string) ([]Group, error) { 159 var groups []Group 160 err := filepath.WalkDir(path.Join(repo.rootPath, groupPath), func(path string, d fs.DirEntry, err error) error { 161 if d.IsDir() { 162 if path == groupPath { 163 return nil 164 } 165 gp := strings.TrimPrefix(path, repo.rootPath) 166 gp = strings.TrimPrefix(gp, string(os.PathSeparator)) 167 group := NewGroup(gp) 168 groups = append(groups, group) 169 } 170 return nil 171 }) 172 if err != nil { 173 return []Group{}, err 174 } 175 176 return groups, nil 177 } 178 179 func (repo FSRepository) find(entryPath string, timeContext *TimeContext) (Entry, error) { 180 if _, err := os.Stat(entryPath); os.IsNotExist(err) { 181 return Entry{}, err 182 } 183 184 data, err := os.ReadFile(entryPath) 185 if err != nil { 186 return Entry{}, err 187 } 188 189 // TODO(tms) 11.11.22: format check 190 base := path.Base(entryPath) 191 192 lines := strings.Split(string(data), "\n") 193 194 if base == activeFile { 195 start, _ := timeContext.ParseEntry(lines[0]) 196 timeRange, err := NewTimeRange(start, start.Add(time.Second)) 197 return Entry{Completed: false, TimeRange: timeRange}, err 198 } else { 199 start, _ := timeContext.ParseEntry(lines[0]) 200 stop, _ := timeContext.ParseEntry(lines[1]) 201 timeRange, err := NewTimeRange(start, stop) 202 return Entry{Completed: true, TimeRange: timeRange}, err 203 } 204 } 205 206 func (repo FSRepository) Move(from Group, entries []Entry, to Group) (int, error) { 207 counter := 0 208 209 if !repo.Exists(from) { 210 return counter, errors.New("Group '" + from.Name + "' does not exist") 211 } 212 213 if !repo.Exists(to) { 214 return counter, errors.New("Group '" + from.Name + "' does not exist") 215 } 216 217 for _, entry := range entries { 218 entryPath := path.Join(repo.rootPath, from.Name, repo.EntryName(entry)) 219 if !repo.ExistsEntry(from, entry) { 220 return counter, errors.New("Entry '" + entryPath + "' does not exist") 221 } 222 223 newEntryPath := path.Join(repo.rootPath, to.Name, repo.EntryName(entry)) 224 225 err := os.Rename(entryPath, newEntryPath) 226 if err != nil { 227 return counter, err 228 } 229 230 counter = counter + 1 231 } 232 233 return counter, nil 234 }