/
storage.go
187 lines (159 loc) · 4.78 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
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
package updater
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/safing/portbase/log"
"github.com/safing/portbase/utils"
)
// ScanStorage scans root within the storage dir and adds found
// resources to the registry. If an error occurred, it is logged
// and the last error is returned. Everything that was found
// despite errors is added to the registry anyway. Leave root
// empty to scan the full storage dir.
func (reg *ResourceRegistry) ScanStorage(root string) error {
var lastError error
// prep root
if root == "" {
root = reg.storageDir.Path
} else {
var err error
root, err = filepath.Abs(root)
if err != nil {
return err
}
if !strings.HasPrefix(root, reg.storageDir.Path) {
return errors.New("supplied scan root path not within storage")
}
}
// walk fs
_ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
// skip tmp dir (including errors trying to read it)
if strings.HasPrefix(path, reg.tmpDir.Path) {
return filepath.SkipDir
}
// handle walker error
if err != nil {
lastError = fmt.Errorf("%s: could not read %s: %w", reg.Name, path, err)
log.Warning(lastError.Error())
return nil
}
// get relative path to storage
relativePath, err := filepath.Rel(reg.storageDir.Path, path)
if err != nil {
lastError = fmt.Errorf("%s: could not get relative path of %s: %w", reg.Name, path, err)
log.Warning(lastError.Error())
return nil
}
// convert to identifier and version
relativePath = filepath.ToSlash(relativePath)
identifier, version, ok := GetIdentifierAndVersion(relativePath)
if !ok {
// file does not conform to format
return nil
}
// fully ignore directories that also have an identifier - these will be unpacked resources
if info.IsDir() {
return filepath.SkipDir
}
// save
err = reg.AddResource(identifier, version, true, false, false)
if err != nil {
lastError = fmt.Errorf("%s: could not get add resource %s v%s: %w", reg.Name, identifier, version, err)
log.Warning(lastError.Error())
}
return nil
})
return lastError
}
// LoadIndexes loads the current release indexes from disk
// or will fetch a new version if not available and the
// registry is marked as online.
func (reg *ResourceRegistry) LoadIndexes(ctx context.Context) error {
var firstErr error
client := &http.Client{}
for _, idx := range reg.getIndexes() {
err := reg.loadIndexFile(idx)
if err == nil {
log.Debugf("%s: loaded index %s", reg.Name, idx.Path)
} else if reg.Online {
// try to download the index file if a local disk version
// does not exist or we don't have permission to read it.
if os.IsNotExist(err) || os.IsPermission(err) {
err = reg.downloadIndex(ctx, client, idx)
}
}
if err != nil && firstErr == nil {
firstErr = err
}
}
return firstErr
}
func (reg *ResourceRegistry) getIndexes() []Index {
reg.RLock()
defer reg.RUnlock()
indexes := make([]Index, len(reg.indexes))
copy(indexes, reg.indexes)
return indexes
}
func (reg *ResourceRegistry) loadIndexFile(idx Index) error {
path := filepath.FromSlash(idx.Path)
data, err := ioutil.ReadFile(filepath.Join(reg.storageDir.Path, path))
if err != nil {
return err
}
releases := make(map[string]string)
err = json.Unmarshal(data, &releases)
if err != nil {
return err
}
if len(releases) == 0 {
log.Warningf("%s: index %s is empty", reg.Name, idx.Path)
return nil
}
err = reg.AddResources(releases, false, true, idx.PreRelease)
if err != nil {
log.Warningf("%s: failed to add resource: %s", reg.Name, err)
}
return nil
}
// CreateSymlinks creates a directory structure with unversioned symlinks to the given updates list.
func (reg *ResourceRegistry) CreateSymlinks(symlinkRoot *utils.DirStructure) error {
err := os.RemoveAll(symlinkRoot.Path)
if err != nil {
return fmt.Errorf("failed to wipe symlink root: %w", err)
}
err = symlinkRoot.Ensure()
if err != nil {
return fmt.Errorf("failed to create symlink root: %w", err)
}
reg.RLock()
defer reg.RUnlock()
for _, res := range reg.resources {
if res.SelectedVersion == nil {
return fmt.Errorf("no selected version available for %s", res.Identifier)
}
targetPath := res.SelectedVersion.storagePath()
linkPath := filepath.Join(symlinkRoot.Path, filepath.FromSlash(res.Identifier))
linkPathDir := filepath.Dir(linkPath)
err = symlinkRoot.EnsureAbsPath(linkPathDir)
if err != nil {
return fmt.Errorf("failed to create dir for link: %w", err)
}
relativeTargetPath, err := filepath.Rel(linkPathDir, targetPath)
if err != nil {
return fmt.Errorf("failed to get relative target path: %w", err)
}
err = os.Symlink(relativeTargetPath, linkPath)
if err != nil {
return fmt.Errorf("failed to link %s: %w", res.Identifier, err)
}
}
return nil
}