forked from bitfield/morningpost
/
store.go
127 lines (112 loc) · 2.07 KB
/
store.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
package morningpost
import (
"encoding/gob"
"errors"
"hash/fnv"
"os"
"path"
"runtime"
"syscall"
"golang.org/x/exp/maps"
)
type Store interface {
Add(...Feed)
Delete(uint64)
GetAll() []Feed
Save() error
}
type FileStore struct {
data map[uint64]Feed
path string
}
func (f *FileStore) Add(feeds ...Feed) {
h := fnv.New64a()
for _, feed := range feeds {
h.Write([]byte(feed.Endpoint))
feed.ID = h.Sum64()
f.data[h.Sum64()] = feed
h.Reset()
}
}
func (f *FileStore) GetAll() []Feed {
return maps.Values(f.data)
}
func (f *FileStore) Load() error {
file, err := os.Open(f.path)
if errors.Is(err, os.ErrNotExist) {
return nil
}
if err != nil {
return err
}
dec := gob.NewDecoder(file)
return dec.Decode(&f.data)
}
func (f *FileStore) Save() error {
if _, err := os.Stat(path.Dir(f.path)); os.IsNotExist(err) {
err := os.MkdirAll(path.Dir(f.path), 0755)
if err != nil {
return err
}
}
file, err := os.Create(f.path)
if err != nil {
return err
}
defer file.Close()
enc := gob.NewEncoder(file)
return enc.Encode(f.data)
}
func (f *FileStore) Delete(id uint64) {
delete(f.data, id)
}
func (f *FileStore) Path() string {
return f.path
}
func NewFileStore(opts ...FileStoreOption) (*FileStore, error) {
fileStore := &FileStore{
data: map[uint64]Feed{},
path: userStateDir() + "/MorningPost/morningpost.db",
}
for _, o := range opts {
o(fileStore)
}
err := fileStore.Load()
if err != nil {
return nil, err
}
return fileStore, nil
}
func getenv(key string) string {
v, _ := syscall.Getenv(key)
return v
}
func userStateDir() string {
switch runtime.GOOS {
case "windows":
dir := getenv("AppData")
if dir == "" {
return "./"
}
return dir
case "darwin", "ios":
dir := getenv("HOME")
if dir == "" {
return "./"
}
dir += "/Library/Application Support"
return dir
default: // Unix
dir := getenv("XDG_STATE_HOME")
if dir == "" {
return "/var/lib"
}
return dir
}
}
type FileStoreOption func(*FileStore)
func WithFileStorePath(path string) FileStoreOption {
return func(f *FileStore) {
f.path = path
}
}