-
Notifications
You must be signed in to change notification settings - Fork 0
/
storage.go
138 lines (111 loc) · 2.92 KB
/
storage.go
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
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"time"
)
var (
errArticleDNE = errors.New("error: article does not exist")
)
type storage interface {
WriteArticle(title string, content []byte) error
ReadArticle(title string) ([]byte, error)
ReadArticleVersion(title, version string) ([]byte, error)
ListArticleVersions(title string) ([]string, error)
ListArticles() ([]string, error)
}
type localStorage struct {
baseDirectory string
}
func NewLocalStorage(base string) (storage, error) {
baseDirectory, err := filepath.Abs(base)
if err != nil {
return nil, fmt.Errorf("could not resolve absolute file path: %w", err)
}
return &localStorage{baseDirectory: baseDirectory}, nil
}
func (l *localStorage) WriteArticle(title string, content []byte) error {
if !l.exists(title) {
err := os.Mkdir(l.relpath(title), 0755)
if err != nil {
return fmt.Errorf("could not make directory: %w", err)
}
}
fname := l.relpath(title, ts())
err := os.WriteFile(fname, content, 0644)
if err != nil {
return fmt.Errorf("could not write file: %w", err)
}
newsym := fname + "_ptr"
err = os.Symlink(filepath.Base(fname), newsym)
if err != nil {
return fmt.Errorf("could not symlink: %w", err)
}
// is this atomic?
err = os.Rename(newsym, l.relpath(title, "current"))
if err != nil {
return fmt.Errorf("could not make symlink current %w", err)
}
// error here doesn't matter?
os.Remove(newsym)
return nil
}
func (l *localStorage) ReadArticle(title string) ([]byte, error) {
return l.ReadArticleVersion(title, "current")
}
func (l *localStorage) ReadArticleVersion(title, version string) ([]byte, error) {
if !l.exists(title) {
return nil, errArticleDNE
}
data, err := os.ReadFile(l.relpath(title, version))
if err != nil {
return nil, fmt.Errorf("could not read file: %w", err)
}
return data, nil
}
func (l *localStorage) ListArticleVersions(title string) ([]string, error) {
if !l.exists(title) {
return nil, errArticleDNE
}
entries, err := os.ReadDir(l.relpath(title))
if err != nil {
return nil, fmt.Errorf("could not read directory: %w", err)
}
size := len(entries)
versions := make([]string, size)
for i, e := range entries {
if !e.IsDir() {
versions[(size-1)-i] = e.Name()
}
}
return versions, nil
}
func (l *localStorage) ListArticles() ([]string, error) {
entries, err := os.ReadDir(l.baseDirectory)
if err != nil {
return nil, fmt.Errorf("could not read directory: %w", err)
}
titles := []string{}
for _, e := range entries {
if e.IsDir() {
titles = append(titles, e.Name())
}
}
return titles, nil
}
func ts() string {
return strconv.FormatInt(time.Now().UnixNano(), 10)
}
func (l *localStorage) relpath(elem ...string) string {
return filepath.Join(append([]string{l.baseDirectory}, elem...)...)
}
func (l *localStorage) exists(title string) bool {
if _, err := os.Stat(l.relpath(title, "current")); err != nil {
return false
} else {
return true
}
}