1// Copyright 2020 The Gitea Authors. All rights reserved. 2// Use of this source code is governed by a MIT-style 3// license that can be found in the LICENSE file. 4 5package storage 6 7import ( 8 "context" 9 "errors" 10 "fmt" 11 "io" 12 "net/url" 13 "os" 14 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/setting" 17) 18 19var ( 20 // ErrURLNotSupported represents url is not supported 21 ErrURLNotSupported = errors.New("url method not supported") 22) 23 24// ErrInvalidConfiguration is called when there is invalid configuration for a storage 25type ErrInvalidConfiguration struct { 26 cfg interface{} 27 err error 28} 29 30func (err ErrInvalidConfiguration) Error() string { 31 if err.err != nil { 32 return fmt.Sprintf("Invalid Configuration Argument: %v: Error: %v", err.cfg, err.err) 33 } 34 return fmt.Sprintf("Invalid Configuration Argument: %v", err.cfg) 35} 36 37// IsErrInvalidConfiguration checks if an error is an ErrInvalidConfiguration 38func IsErrInvalidConfiguration(err error) bool { 39 _, ok := err.(ErrInvalidConfiguration) 40 return ok 41} 42 43// Type is a type of Storage 44type Type string 45 46// NewStorageFunc is a function that creates a storage 47type NewStorageFunc func(ctx context.Context, cfg interface{}) (ObjectStorage, error) 48 49var storageMap = map[Type]NewStorageFunc{} 50 51// RegisterStorageType registers a provided storage type with a function to create it 52func RegisterStorageType(typ Type, fn func(ctx context.Context, cfg interface{}) (ObjectStorage, error)) { 53 storageMap[typ] = fn 54} 55 56// Object represents the object on the storage 57type Object interface { 58 io.ReadCloser 59 io.Seeker 60 Stat() (os.FileInfo, error) 61} 62 63// ObjectStorage represents an object storage to handle a bucket and files 64type ObjectStorage interface { 65 Open(path string) (Object, error) 66 // Save store a object, if size is unknown set -1 67 Save(path string, r io.Reader, size int64) (int64, error) 68 Stat(path string) (os.FileInfo, error) 69 Delete(path string) error 70 URL(path, name string) (*url.URL, error) 71 IterateObjects(func(path string, obj Object) error) error 72} 73 74// Copy copies a file from source ObjectStorage to dest ObjectStorage 75func Copy(dstStorage ObjectStorage, dstPath string, srcStorage ObjectStorage, srcPath string) (int64, error) { 76 f, err := srcStorage.Open(srcPath) 77 if err != nil { 78 return 0, err 79 } 80 defer f.Close() 81 82 size := int64(-1) 83 fsinfo, err := f.Stat() 84 if err == nil { 85 size = fsinfo.Size() 86 } 87 88 return dstStorage.Save(dstPath, f, size) 89} 90 91// Clean delete all the objects in this storage 92func Clean(storage ObjectStorage) error { 93 return storage.IterateObjects(func(path string, obj Object) error { 94 _ = obj.Close() 95 return storage.Delete(path) 96 }) 97} 98 99// SaveFrom saves data to the ObjectStorage with path p from the callback 100func SaveFrom(objStorage ObjectStorage, p string, callback func(w io.Writer) error) error { 101 pr, pw := io.Pipe() 102 defer pr.Close() 103 go func() { 104 defer pw.Close() 105 if err := callback(pw); err != nil { 106 _ = pw.CloseWithError(err) 107 } 108 }() 109 110 _, err := objStorage.Save(p, pr, -1) 111 return err 112} 113 114var ( 115 // Attachments represents attachments storage 116 Attachments ObjectStorage 117 118 // LFS represents lfs storage 119 LFS ObjectStorage 120 121 // Avatars represents user avatars storage 122 Avatars ObjectStorage 123 // RepoAvatars represents repository avatars storage 124 RepoAvatars ObjectStorage 125 126 // RepoArchives represents repository archives storage 127 RepoArchives ObjectStorage 128) 129 130// Init init the stoarge 131func Init() error { 132 if err := initAttachments(); err != nil { 133 return err 134 } 135 136 if err := initAvatars(); err != nil { 137 return err 138 } 139 140 if err := initRepoAvatars(); err != nil { 141 return err 142 } 143 144 if err := initLFS(); err != nil { 145 return err 146 } 147 148 return initRepoArchives() 149} 150 151// NewStorage takes a storage type and some config and returns an ObjectStorage or an error 152func NewStorage(typStr string, cfg interface{}) (ObjectStorage, error) { 153 if len(typStr) == 0 { 154 typStr = string(LocalStorageType) 155 } 156 fn, ok := storageMap[Type(typStr)] 157 if !ok { 158 return nil, fmt.Errorf("Unsupported storage type: %s", typStr) 159 } 160 161 return fn(context.Background(), cfg) 162} 163 164func initAvatars() (err error) { 165 log.Info("Initialising Avatar storage with type: %s", setting.Avatar.Storage.Type) 166 Avatars, err = NewStorage(setting.Avatar.Storage.Type, &setting.Avatar.Storage) 167 return 168} 169 170func initAttachments() (err error) { 171 log.Info("Initialising Attachment storage with type: %s", setting.Attachment.Storage.Type) 172 Attachments, err = NewStorage(setting.Attachment.Storage.Type, &setting.Attachment.Storage) 173 return 174} 175 176func initLFS() (err error) { 177 log.Info("Initialising LFS storage with type: %s", setting.LFS.Storage.Type) 178 LFS, err = NewStorage(setting.LFS.Storage.Type, &setting.LFS.Storage) 179 return 180} 181 182func initRepoAvatars() (err error) { 183 log.Info("Initialising Repository Avatar storage with type: %s", setting.RepoAvatar.Storage.Type) 184 RepoAvatars, err = NewStorage(setting.RepoAvatar.Storage.Type, &setting.RepoAvatar.Storage) 185 return 186} 187 188func initRepoArchives() (err error) { 189 log.Info("Initialising Repository Archive storage with type: %s", setting.RepoArchive.Storage.Type) 190 RepoArchives, err = NewStorage(setting.RepoArchive.Storage.Type, &setting.RepoArchive.Storage) 191 return 192} 193