-
-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Expand file tree
/
Copy pathstorage.go
More file actions
269 lines (227 loc) · 8.39 KB
/
storage.go
File metadata and controls
269 lines (227 loc) · 8.39 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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package storage
import (
"context"
"errors"
"fmt"
"io"
"net/url"
"os"
"path"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// ErrURLNotSupported represents url is not supported
var ErrURLNotSupported = errors.New("url method not supported")
// ErrInvalidConfiguration is called when there is invalid configuration for a storage
type ErrInvalidConfiguration struct {
cfg any
err error
}
func (err ErrInvalidConfiguration) Error() string {
if err.err != nil {
return fmt.Sprintf("Invalid Configuration Argument: %v: Error: %v", err.cfg, err.err)
}
return fmt.Sprintf("Invalid Configuration Argument: %v", err.cfg)
}
// IsErrInvalidConfiguration checks if an error is an ErrInvalidConfiguration
func IsErrInvalidConfiguration(err error) bool {
_, ok := err.(ErrInvalidConfiguration)
return ok
}
type Type = setting.StorageType
// NewStorageFunc is a function that creates a storage
type NewStorageFunc func(ctx context.Context, cfg *setting.Storage) (ObjectStorage, error)
var storageMap = map[Type]NewStorageFunc{}
// RegisterStorageType registers a provided storage type with a function to create it
func RegisterStorageType(typ Type, fn func(ctx context.Context, cfg *setting.Storage) (ObjectStorage, error)) {
storageMap[typ] = fn
}
// Object represents the object on the storage
type Object interface {
io.ReadCloser
io.Seeker
Stat() (os.FileInfo, error)
}
type SignedURLParam struct {
ContentType string
ContentDisposition string
}
func (s *SignedURLParam) withDefaults(name string) *SignedURLParam {
// Here we might not know the real filename, and it's quite inefficient to detect the MIME type by pre-fetching the object head.
// So we just do a quick detection by extension name, at least it works for the "View Raw File" for an LFS file on the Web UI.
// Detect content type by extension name, only support the well-known safe types for inline rendering.
// TODO: OBJECT-STORAGE-CONTENT-TYPE: need a complete solution and refactor for Azure in the future
ext := path.Ext(name)
param := *util.Iif(s == nil, &SignedURLParam{}, s)
isSafe := false
if param.ContentType != "" {
isSafe = public.IsWellKnownSafeInlineMimeType(param.ContentType)
} else if mimeType, safe := public.DetectWellKnownSafeInlineMimeType(ext); safe {
param.ContentType = mimeType
isSafe = true
}
if param.ContentDisposition == "" {
if isSafe {
param.ContentDisposition = "inline"
} else {
param.ContentDisposition = fmt.Sprintf(`attachment; filename="%s"`, quoteEscaper.Replace(name))
}
}
return ¶m
}
// ObjectStorage represents an object storage to handle a bucket and files
type ObjectStorage interface {
Open(path string) (Object, error)
// Save store an object, if size is unknown set -1
// NOTICE: Some storage SDK will close the Reader after saving if it is also a Closer,
// DO NOT use the reader anymore after Save, or wrap it to a non-Closer reader.
Save(path string, r io.Reader, size int64) (int64, error)
Stat(path string) (os.FileInfo, error)
Delete(path string) error
URL(path, name, method string, reqParams *SignedURLParam) (*url.URL, error)
// URL(path, name, method string, reqParams *SignedURLParam) (*url.URL, error)IterateObjects calls the iterator function for each object in the storage with the given path as prefix
// The "fullPath" argument in callback is the full path in this storage.
// * IterateObjects("", ...): iterate all objects in this storage
// * IterateObjects("sub-path", ...): iterate all objects with "sub-path" as prefix in this storage, the "fullPath" will be like "sub-path/xxx"
IterateObjects(basePath string, iterator func(fullPath string, obj Object) error) error
}
// Copy copies a file from source ObjectStorage to dest ObjectStorage
func Copy(dstStorage ObjectStorage, dstPath string, srcStorage ObjectStorage, srcPath string) (int64, error) {
f, err := srcStorage.Open(srcPath)
if err != nil {
return 0, err
}
defer f.Close()
size := int64(-1)
fsinfo, err := f.Stat()
if err == nil {
size = fsinfo.Size()
}
return dstStorage.Save(dstPath, f, size)
}
// Clean delete all the objects in this storage
func Clean(storage ObjectStorage) error {
return storage.IterateObjects("", func(path string, obj Object) error {
_ = obj.Close()
return storage.Delete(path)
})
}
// SaveFrom saves data to the ObjectStorage with path p from the callback
func SaveFrom(objStorage ObjectStorage, path string, callback func(w io.Writer) error) error {
pr, pw := io.Pipe()
defer pr.Close()
go func() {
defer pw.Close()
if err := callback(pw); err != nil {
_ = pw.CloseWithError(err)
}
}()
_, err := objStorage.Save(path, pr, -1)
return err
}
var (
// Attachments represents attachments storage
Attachments ObjectStorage = uninitializedStorage
// LFS represents lfs storage
LFS ObjectStorage = uninitializedStorage
// Avatars represents user avatars storage
Avatars ObjectStorage = uninitializedStorage
// RepoAvatars represents repository avatars storage
RepoAvatars ObjectStorage = uninitializedStorage
// RepoArchives represents repository archives storage
RepoArchives ObjectStorage = uninitializedStorage
// Packages represents packages storage
Packages ObjectStorage = uninitializedStorage
// Actions represents actions storage
Actions ObjectStorage = uninitializedStorage
// Actions Artifacts represents actions artifacts storage
ActionsArtifacts ObjectStorage = uninitializedStorage
)
// Init init the storage
func Init() error {
for _, f := range []func() error{
initAttachments,
initAvatars,
initRepoAvatars,
initLFS,
initRepoArchives,
initPackages,
initActions,
} {
if err := f(); err != nil {
return err
}
}
return nil
}
// NewStorage takes a storage type and some config and returns an ObjectStorage or an error
func NewStorage(typStr Type, cfg *setting.Storage) (ObjectStorage, error) {
if len(typStr) == 0 {
typStr = setting.LocalStorageType
}
fn, ok := storageMap[typStr]
if !ok {
return nil, fmt.Errorf("Unsupported storage type: %s", typStr)
}
return fn(context.Background(), cfg)
}
func initAvatars() (err error) {
log.Info("Initialising Avatar storage with type: %s", setting.Avatar.Storage.Type)
Avatars, err = NewStorage(setting.Avatar.Storage.Type, setting.Avatar.Storage)
return err
}
func initAttachments() (err error) {
if !setting.Attachment.Enabled {
Attachments = discardStorage("Attachment isn't enabled")
return nil
}
log.Info("Initialising Attachment storage with type: %s", setting.Attachment.Storage.Type)
Attachments, err = NewStorage(setting.Attachment.Storage.Type, setting.Attachment.Storage)
return err
}
func initLFS() (err error) {
if !setting.LFS.StartServer {
LFS = discardStorage("LFS isn't enabled")
return nil
}
log.Info("Initialising LFS storage with type: %s", setting.LFS.Storage.Type)
LFS, err = NewStorage(setting.LFS.Storage.Type, setting.LFS.Storage)
return err
}
func initRepoAvatars() (err error) {
log.Info("Initialising Repository Avatar storage with type: %s", setting.RepoAvatar.Storage.Type)
RepoAvatars, err = NewStorage(setting.RepoAvatar.Storage.Type, setting.RepoAvatar.Storage)
return err
}
func initRepoArchives() (err error) {
log.Info("Initialising Repository Archive storage with type: %s", setting.RepoArchive.Storage.Type)
RepoArchives, err = NewStorage(setting.RepoArchive.Storage.Type, setting.RepoArchive.Storage)
return err
}
func initPackages() (err error) {
if !setting.Packages.Enabled {
Packages = discardStorage("Packages isn't enabled")
return nil
}
log.Info("Initialising Packages storage with type: %s", setting.Packages.Storage.Type)
Packages, err = NewStorage(setting.Packages.Storage.Type, setting.Packages.Storage)
return err
}
func initActions() (err error) {
if !setting.Actions.Enabled {
Actions = discardStorage("Actions isn't enabled")
ActionsArtifacts = discardStorage("ActionsArtifacts isn't enabled")
return nil
}
log.Info("Initialising Actions storage with type: %s", setting.Actions.LogStorage.Type)
if Actions, err = NewStorage(setting.Actions.LogStorage.Type, setting.Actions.LogStorage); err != nil {
return err
}
log.Info("Initialising ActionsArtifacts storage with type: %s", setting.Actions.ArtifactStorage.Type)
ActionsArtifacts, err = NewStorage(setting.Actions.ArtifactStorage.Type, setting.Actions.ArtifactStorage)
return err
}