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