Skip to content

Commit 50a72e7

Browse files
wxiaoguangwolfogrelunny
authored
Use a general approach to access custom/static/builtin assets (#24022)
The idea is to use a Layered Asset File-system (modules/assetfs/layered.go) For example: when there are 2 layers: "custom", "builtin", when access to asset "my/page.tmpl", the Layered Asset File-system will first try to use "custom" assets, if not found, then use "builtin" assets. This approach will hugely simplify a lot of code, make them testable. Other changes: * Simplify the AssetsHandlerFunc code * Simplify the `gitea embedded` sub-command code --------- Co-authored-by: Jason Song <i@wolfogre.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
1 parent 42919cc commit 50a72e7

36 files changed

+688
-1054
lines changed

cmd/embedded.go

Lines changed: 52 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
// Copyright 2020 The Gitea Authors. All rights reserved.
22
// SPDX-License-Identifier: MIT
33

4-
//go:build bindata
5-
64
package cmd
75

86
import (
97
"errors"
108
"fmt"
119
"os"
1210
"path/filepath"
13-
"sort"
1411
"strings"
1512

13+
"code.gitea.io/gitea/modules/assetfs"
1614
"code.gitea.io/gitea/modules/log"
1715
"code.gitea.io/gitea/modules/options"
1816
"code.gitea.io/gitea/modules/public"
@@ -89,24 +87,20 @@ var (
8987
},
9088
}
9189

92-
sections map[string]*section
93-
assets []asset
90+
matchedAssetFiles []assetFile
9491
)
9592

96-
type section struct {
97-
Path string
98-
Names func() []string
99-
IsDir func(string) (bool, error)
100-
Asset func(string) ([]byte, error)
101-
}
102-
103-
type asset struct {
104-
Section *section
105-
Name string
106-
Path string
93+
type assetFile struct {
94+
fs *assetfs.LayeredFS
95+
name string
96+
path string
10797
}
10898

10999
func initEmbeddedExtractor(c *cli.Context) error {
100+
// FIXME: there is a bug, if the user runs `gitea embedded` with a different user or root,
101+
// The setting.Init (loadRunModeFrom) will fail and do log.Fatal
102+
// But the console logger has been deleted, so nothing is printed, the user sees nothing and Gitea just exits.
103+
110104
// Silence the console logger
111105
log.DelNamedLogger("console")
112106
log.DelNamedLogger(log.DEFAULT)
@@ -115,24 +109,14 @@ func initEmbeddedExtractor(c *cli.Context) error {
115109
setting.InitProviderAllowEmpty()
116110
setting.LoadCommonSettings()
117111

118-
pats, err := getPatterns(c.Args())
112+
patterns, err := compileCollectPatterns(c.Args())
119113
if err != nil {
120114
return err
121115
}
122-
sections := make(map[string]*section, 3)
123-
124-
sections["public"] = &section{Path: "public", Names: public.AssetNames, IsDir: public.AssetIsDir, Asset: public.Asset}
125-
sections["options"] = &section{Path: "options", Names: options.AssetNames, IsDir: options.AssetIsDir, Asset: options.Asset}
126-
sections["templates"] = &section{Path: "templates", Names: templates.BuiltinAssetNames, IsDir: templates.BuiltinAssetIsDir, Asset: templates.BuiltinAsset}
127116

128-
for _, sec := range sections {
129-
assets = append(assets, buildAssetList(sec, pats, c)...)
130-
}
131-
132-
// Sort assets
133-
sort.SliceStable(assets, func(i, j int) bool {
134-
return assets[i].Path < assets[j].Path
135-
})
117+
collectAssetFilesByPattern(c, patterns, "options", options.BuiltinAssets())
118+
collectAssetFilesByPattern(c, patterns, "public", public.BuiltinAssets())
119+
collectAssetFilesByPattern(c, patterns, "templates", templates.BuiltinAssets())
136120

137121
return nil
138122
}
@@ -166,8 +150,8 @@ func runListDo(c *cli.Context) error {
166150
return err
167151
}
168152

169-
for _, a := range assets {
170-
fmt.Println(a.Path)
153+
for _, a := range matchedAssetFiles {
154+
fmt.Println(a.path)
171155
}
172156

173157
return nil
@@ -178,19 +162,19 @@ func runViewDo(c *cli.Context) error {
178162
return err
179163
}
180164

181-
if len(assets) == 0 {
182-
return fmt.Errorf("No files matched the given pattern")
183-
} else if len(assets) > 1 {
184-
return fmt.Errorf("Too many files matched the given pattern; try to be more specific")
165+
if len(matchedAssetFiles) == 0 {
166+
return fmt.Errorf("no files matched the given pattern")
167+
} else if len(matchedAssetFiles) > 1 {
168+
return fmt.Errorf("too many files matched the given pattern, try to be more specific")
185169
}
186170

187-
data, err := assets[0].Section.Asset(assets[0].Name)
171+
data, err := matchedAssetFiles[0].fs.ReadFile(matchedAssetFiles[0].name)
188172
if err != nil {
189-
return fmt.Errorf("%s: %w", assets[0].Path, err)
173+
return fmt.Errorf("%s: %w", matchedAssetFiles[0].path, err)
190174
}
191175

192176
if _, err = os.Stdout.Write(data); err != nil {
193-
return fmt.Errorf("%s: %w", assets[0].Path, err)
177+
return fmt.Errorf("%s: %w", matchedAssetFiles[0].path, err)
194178
}
195179

196180
return nil
@@ -202,7 +186,7 @@ func runExtractDo(c *cli.Context) error {
202186
}
203187

204188
if len(c.Args()) == 0 {
205-
return fmt.Errorf("A list of pattern of files to extract is mandatory (e.g. '**' for all)")
189+
return fmt.Errorf("a list of pattern of files to extract is mandatory (e.g. '**' for all)")
206190
}
207191

208192
destdir := "."
@@ -227,31 +211,31 @@ func runExtractDo(c *cli.Context) error {
227211
if err != nil {
228212
return fmt.Errorf("%s: %s", destdir, err)
229213
} else if !fi.IsDir() {
230-
return fmt.Errorf("%s is not a directory.", destdir)
214+
return fmt.Errorf("destination %q is not a directory", destdir)
231215
}
232216

233217
fmt.Printf("Extracting to %s:\n", destdir)
234218

235219
overwrite := c.Bool("overwrite")
236220
rename := c.Bool("rename")
237221

238-
for _, a := range assets {
222+
for _, a := range matchedAssetFiles {
239223
if err := extractAsset(destdir, a, overwrite, rename); err != nil {
240224
// Non-fatal error
241-
fmt.Fprintf(os.Stderr, "%s: %v", a.Path, err)
225+
fmt.Fprintf(os.Stderr, "%s: %v", a.path, err)
242226
}
243227
}
244228

245229
return nil
246230
}
247231

248-
func extractAsset(d string, a asset, overwrite, rename bool) error {
249-
dest := filepath.Join(d, filepath.FromSlash(a.Path))
232+
func extractAsset(d string, a assetFile, overwrite, rename bool) error {
233+
dest := filepath.Join(d, filepath.FromSlash(a.path))
250234
dir := filepath.Dir(dest)
251235

252-
data, err := a.Section.Asset(a.Name)
236+
data, err := a.fs.ReadFile(a.name)
253237
if err != nil {
254-
return fmt.Errorf("%s: %w", a.Path, err)
238+
return fmt.Errorf("%s: %w", a.path, err)
255239
}
256240

257241
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
@@ -272,7 +256,7 @@ func extractAsset(d string, a asset, overwrite, rename bool) error {
272256
return fmt.Errorf("%s already exists, but it's not a regular file", dest)
273257
} else if rename {
274258
if err := util.Rename(dest, dest+".bak"); err != nil {
275-
return fmt.Errorf("Error creating backup for %s: %w", dest, err)
259+
return fmt.Errorf("error creating backup for %s: %w", dest, err)
276260
}
277261
// Attempt to respect file permissions mask (even if user:group will be set anew)
278262
perms = fi.Mode()
@@ -293,40 +277,38 @@ func extractAsset(d string, a asset, overwrite, rename bool) error {
293277
return nil
294278
}
295279

296-
func buildAssetList(sec *section, globs []glob.Glob, c *cli.Context) []asset {
297-
results := make([]asset, 0, 64)
298-
for _, name := range sec.Names() {
299-
if isdir, err := sec.IsDir(name); !isdir && err == nil {
300-
if sec.Path == "public" &&
301-
strings.HasPrefix(name, "vendor/") &&
302-
!c.Bool("include-vendored") {
303-
continue
304-
}
305-
matchName := sec.Path + "/" + name
306-
for _, g := range globs {
307-
if g.Match(matchName) {
308-
results = append(results, asset{
309-
Section: sec,
310-
Name: name,
311-
Path: sec.Path + "/" + name,
312-
})
313-
break
314-
}
280+
func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) {
281+
fs := assetfs.Layered(layer)
282+
files, err := fs.ListAllFiles(".", true)
283+
if err != nil {
284+
log.Error("Error listing files in %q: %v", path, err)
285+
return
286+
}
287+
for _, name := range files {
288+
if path == "public" &&
289+
strings.HasPrefix(name, "vendor/") &&
290+
!c.Bool("include-vendored") {
291+
continue
292+
}
293+
matchName := path + "/" + name
294+
for _, g := range globs {
295+
if g.Match(matchName) {
296+
matchedAssetFiles = append(matchedAssetFiles, assetFile{fs: fs, name: name, path: path + "/" + name})
297+
break
315298
}
316299
}
317300
}
318-
return results
319301
}
320302

321-
func getPatterns(args []string) ([]glob.Glob, error) {
303+
func compileCollectPatterns(args []string) ([]glob.Glob, error) {
322304
if len(args) == 0 {
323305
args = []string{"**"}
324306
}
325307
pat := make([]glob.Glob, len(args))
326308
for i := range args {
327309
if g, err := glob.Compile(args[i], '/'); err != nil {
328310
return nil, fmt.Errorf("'%s': Invalid glob pattern: %w", args[i], err)
329-
} else {
311+
} else { //nolint:revive
330312
pat[i] = g
331313
}
332314
}

cmd/embedded_stub.go

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)