Repository navigation
Expand file tree
/
Copy pathfs.go
More file actions
220 lines (181 loc) · 5.23 KB
/
Copy pathfs.go
File metadata and controls
220 lines (181 loc) · 5.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package fs
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/dominikbraun/timetrace/config"
)
const (
rootDirName = ".timetrace"
projectsDirName = "projects"
recordsDirName = "records"
)
const (
recordDirLayout = "2006-01-02"
recordFilepathLayout = "15-04.json"
recordBackupFilepathLayout = "15-04.json.bak"
)
type Fs struct {
config *config.Config
sanitizer *strings.Replacer
}
func New(config *config.Config) *Fs {
return &Fs{
config: config,
sanitizer: strings.NewReplacer("/", "-", "\\", "-"),
}
}
// ProjectFilepath returns the filepath of the project with the given key.
func (fs *Fs) ProjectFilepath(key string) string {
key = fs.sanitizer.Replace(key)
name := fmt.Sprintf("%s.json", key)
return filepath.Join(fs.projectsDir(), name)
}
// ProjectBackupFilepath return the filepath of the backup project with the
// given key.
func (fs *Fs) ProjectBackupFilepath(key string) string {
key = fs.sanitizer.Replace(key)
name := fmt.Sprintf("%s.json.bak", key)
return filepath.Join(fs.projectsDir(), name)
}
// ProjectFilepaths returns all non-backup project filepaths sorted alphabetically.
func (fs *Fs) ProjectFilepaths() ([]string, error) {
dir := fs.projectsDir()
items, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
var filepaths []string
for _, item := range items {
if item.IsDir() {
continue
}
itemName := item.Name()
if strings.HasSuffix(itemName, ".bak") {
continue
}
filepaths = append(filepaths, filepath.Join(dir, itemName))
}
sort.Strings(filepaths)
return filepaths, nil
}
// RecordFilepath returns the filepath of the record with the given start time.
//
// Note that the start time also has to contain the date as this determines the
// directory the project is stored in.
func (fs *Fs) RecordFilepath(start time.Time) string {
name := start.Format(recordFilepathLayout)
return filepath.Join(fs.RecordDirFromDate(start), name)
}
func (fs *Fs) RecordBackupFilepath(start time.Time) string {
name := start.Format(recordBackupFilepathLayout)
return filepath.Join(fs.RecordDirFromDate(start), name)
}
// RecordFilepaths returns all non-backup record filepaths within the given
// directory sorted by the given function.
//
// The directory can be obtained using functions like recordDir or RecordDirs.
// If you have a record date, use RecordDirFromDate to get the directory name.
//
// The less function allows you to sort the records. Assume three record files:
//
// - timetrace/records/2021-05-01/08-00.json
// - timetrace/records/2021-05-01/10-00.json
// - timetrace/records/2021-05-01/11-30.json
//
// The following call to RecordFilepaths will return the paths of those records
// sorted from newest to oldest:
//
// latestRecords, err := RecordFilepaths(dir, func (a, b string) bool {
// timeA, _ := time.Parse("15-04.json", a)
// timeB, _ := time.Parse("15-04.json", b)
// return timeA.Before(timeB)
// })
//
// This can be used to determine the latest record in a given record directory.
func (fs *Fs) RecordFilepaths(dir string, less func(a, b string) bool) ([]string, error) {
items, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
filepaths := make([]string, 0)
for _, item := range items {
if item.IsDir() {
continue
}
itemName := item.Name()
if strings.HasSuffix(itemName, ".bak") {
continue
}
filepaths = append(filepaths, filepath.Join(dir, itemName))
}
sort.Slice(filepaths, func(i, j int) bool {
return less(filepaths[i], filepaths[j])
})
return filepaths, nil
}
// RecordDirs returns all record directories sorted alphabetically. This can be
// used to determine the latest record directory and obtain the latest record
// within that directory using RecordFilepaths.
//
// Note that all timetrace directories have to exist for RecordDirs to work.
func (fs *Fs) RecordDirs() ([]string, error) {
items, err := ioutil.ReadDir(fs.recordsDir())
if err != nil {
return nil, err
}
var dirs []string
for _, item := range items {
if !item.IsDir() {
continue
}
dirs = append(dirs, filepath.Join(fs.recordsDir(), item.Name()))
}
sort.Strings(dirs)
return dirs, nil
}
func (fs *Fs) RecordDirFromDate(date time.Time) string {
dir := date.Format(recordDirLayout)
return fs.recordDir(dir)
}
// EnsureDirectories creates all required timetrace directories. If they already
// exist, nothing happens.
func (fs *Fs) EnsureDirectories() error {
dirs := []string{
fs.projectsDir(),
fs.recordsDir(),
fs.recordsInitSubDir(),
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0777); err != nil {
return err
}
}
return nil
}
func (fs *Fs) EnsureRecordDir(date time.Time) error {
return os.MkdirAll(fs.RecordDirFromDate(date), 0777)
}
func (fs *Fs) recordDir(name string) string {
return filepath.Join(fs.recordsDir(), name)
}
func (fs *Fs) projectsDir() string {
return filepath.Join(fs.rootDir(), projectsDirName)
}
func (fs *Fs) recordsDir() string {
return filepath.Join(fs.rootDir(), recordsDirName)
}
func (fs *Fs) recordsInitSubDir() string {
return fs.RecordDirFromDate(time.Now())
}
func (fs *Fs) rootDir() string {
if fs.config.Store != "" {
return os.ExpandEnv(fs.config.Store)
}
homeDir, _ := os.UserHomeDir()
return filepath.Join(homeDir, rootDirName)
}