mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-04 05:18:25 +00:00 
			
		
		
		
	details: https://pkg.go.dev/encoding/json/v2 --------- Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
			
				
	
	
		
			376 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			376 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2025 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package assetfs
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"compress/gzip"
 | 
						|
	"io"
 | 
						|
	"io/fs"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/modules/json"
 | 
						|
	"code.gitea.io/gitea/modules/util"
 | 
						|
)
 | 
						|
 | 
						|
type EmbeddedFile interface {
 | 
						|
	io.ReadSeeker
 | 
						|
	fs.ReadDirFile
 | 
						|
	ReadDir(n int) ([]fs.DirEntry, error)
 | 
						|
}
 | 
						|
 | 
						|
type EmbeddedFileInfo interface {
 | 
						|
	fs.FileInfo
 | 
						|
	fs.DirEntry
 | 
						|
	GetGzipContent() ([]byte, bool)
 | 
						|
}
 | 
						|
 | 
						|
type decompressor interface {
 | 
						|
	io.Reader
 | 
						|
	Close() error
 | 
						|
	Reset(io.Reader) error
 | 
						|
}
 | 
						|
 | 
						|
type embeddedFileInfo struct {
 | 
						|
	fs       *embeddedFS
 | 
						|
	fullName string
 | 
						|
	data     []byte
 | 
						|
 | 
						|
	BaseName   string              `json:"n"`
 | 
						|
	OriginSize int64               `json:"s,omitempty"`
 | 
						|
	DataBegin  int64               `json:"b,omitempty"`
 | 
						|
	DataLen    int64               `json:"l,omitempty"`
 | 
						|
	Children   []*embeddedFileInfo `json:"c,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
func (fi *embeddedFileInfo) GetGzipContent() ([]byte, bool) {
 | 
						|
	// when generating the bindata, if the compressed data equals or is larger than the original data, we store the original data
 | 
						|
	if fi.DataLen == fi.OriginSize {
 | 
						|
		return nil, false
 | 
						|
	}
 | 
						|
	return fi.data, true
 | 
						|
}
 | 
						|
 | 
						|
type EmbeddedFileBase struct {
 | 
						|
	info       *embeddedFileInfo
 | 
						|
	dataReader io.ReadSeeker
 | 
						|
	seekPos    int64
 | 
						|
}
 | 
						|
 | 
						|
func (f *EmbeddedFileBase) ReadDir(n int) ([]fs.DirEntry, error) {
 | 
						|
	// this method is used to satisfy the "func (f ioFile) ReadDir(...)" in httpfs
 | 
						|
	l, err := f.info.fs.ReadDir(f.info.fullName)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if n < 0 || n > len(l) {
 | 
						|
		return l, nil
 | 
						|
	}
 | 
						|
	return l[:n], nil
 | 
						|
}
 | 
						|
 | 
						|
type EmbeddedOriginFile struct {
 | 
						|
	EmbeddedFileBase
 | 
						|
}
 | 
						|
 | 
						|
type EmbeddedCompressedFile struct {
 | 
						|
	EmbeddedFileBase
 | 
						|
	decompressor    decompressor
 | 
						|
	decompressorPos int64
 | 
						|
}
 | 
						|
 | 
						|
type embeddedFS struct {
 | 
						|
	meta func() *EmbeddedMeta
 | 
						|
 | 
						|
	files   map[string]*embeddedFileInfo
 | 
						|
	filesMu sync.RWMutex
 | 
						|
 | 
						|
	data []byte
 | 
						|
}
 | 
						|
 | 
						|
type EmbeddedMeta struct {
 | 
						|
	Root *embeddedFileInfo
 | 
						|
}
 | 
						|
 | 
						|
func NewEmbeddedFS(data []byte) fs.ReadDirFS {
 | 
						|
	efs := &embeddedFS{data: data, files: make(map[string]*embeddedFileInfo)}
 | 
						|
	efs.meta = sync.OnceValue(func() *EmbeddedMeta {
 | 
						|
		var meta EmbeddedMeta
 | 
						|
		p := bytes.LastIndexByte(data, '\n')
 | 
						|
		if p < 0 {
 | 
						|
			return &meta
 | 
						|
		}
 | 
						|
		if err := json.Unmarshal(data[p+1:], &meta); err != nil {
 | 
						|
			panic("embedded file is not valid")
 | 
						|
		}
 | 
						|
		return &meta
 | 
						|
	})
 | 
						|
	return efs
 | 
						|
}
 | 
						|
 | 
						|
var _ fs.ReadDirFS = (*embeddedFS)(nil)
 | 
						|
 | 
						|
func (e *embeddedFS) ReadDir(name string) (l []fs.DirEntry, err error) {
 | 
						|
	fi, err := e.getFileInfo(name)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if !fi.IsDir() {
 | 
						|
		return nil, fs.ErrNotExist
 | 
						|
	}
 | 
						|
	l = make([]fs.DirEntry, len(fi.Children))
 | 
						|
	for i, child := range fi.Children {
 | 
						|
		l[i], err = e.getFileInfo(name + "/" + child.BaseName)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return l, nil
 | 
						|
}
 | 
						|
 | 
						|
func (e *embeddedFS) getFileInfo(fullName string) (*embeddedFileInfo, error) {
 | 
						|
	// no need to do heavy "path.Clean()" because we don't want to support "foo/../bar" or absolute paths
 | 
						|
	fullName = strings.TrimPrefix(fullName, "./")
 | 
						|
	if fullName == "" {
 | 
						|
		fullName = "."
 | 
						|
	}
 | 
						|
 | 
						|
	e.filesMu.RLock()
 | 
						|
	fi := e.files[fullName]
 | 
						|
	e.filesMu.RUnlock()
 | 
						|
	if fi != nil {
 | 
						|
		return fi, nil
 | 
						|
	}
 | 
						|
 | 
						|
	fields := strings.Split(fullName, "/")
 | 
						|
	fi = e.meta().Root
 | 
						|
	if fullName != "." {
 | 
						|
		found := true
 | 
						|
		for _, field := range fields {
 | 
						|
			for _, child := range fi.Children {
 | 
						|
				if found = child.BaseName == field; found {
 | 
						|
					fi = child
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if !found {
 | 
						|
				return nil, fs.ErrNotExist
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	e.filesMu.Lock()
 | 
						|
	defer e.filesMu.Unlock()
 | 
						|
	if fi != nil {
 | 
						|
		fi.fs = e
 | 
						|
		fi.fullName = fullName
 | 
						|
		fi.data = e.data[fi.DataBegin : fi.DataBegin+fi.DataLen]
 | 
						|
		e.files[fullName] = fi // do not cache nil, otherwise keeping accessing random non-existing file will cause OOM
 | 
						|
		return fi, nil
 | 
						|
	}
 | 
						|
	return nil, fs.ErrNotExist
 | 
						|
}
 | 
						|
 | 
						|
func (e *embeddedFS) Open(name string) (fs.File, error) {
 | 
						|
	info, err := e.getFileInfo(name)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	base := EmbeddedFileBase{info: info}
 | 
						|
	base.dataReader = bytes.NewReader(base.info.data)
 | 
						|
	if info.DataLen != info.OriginSize {
 | 
						|
		decomp, err := gzip.NewReader(base.dataReader)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		return &EmbeddedCompressedFile{EmbeddedFileBase: base, decompressor: decomp}, nil
 | 
						|
	}
 | 
						|
	return &EmbeddedOriginFile{base}, nil
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	_ EmbeddedFileInfo = (*embeddedFileInfo)(nil)
 | 
						|
	_ EmbeddedFile     = (*EmbeddedOriginFile)(nil)
 | 
						|
	_ EmbeddedFile     = (*EmbeddedCompressedFile)(nil)
 | 
						|
)
 | 
						|
 | 
						|
func (f *EmbeddedOriginFile) Read(p []byte) (n int, err error) {
 | 
						|
	return f.dataReader.Read(p)
 | 
						|
}
 | 
						|
 | 
						|
func (f *EmbeddedCompressedFile) Read(p []byte) (n int, err error) {
 | 
						|
	if f.decompressorPos > f.seekPos {
 | 
						|
		if err = f.decompressor.Reset(bytes.NewReader(f.info.data)); err != nil {
 | 
						|
			return 0, err
 | 
						|
		}
 | 
						|
		f.decompressorPos = 0
 | 
						|
	}
 | 
						|
	if f.decompressorPos < f.seekPos {
 | 
						|
		if _, err = io.CopyN(io.Discard, f.decompressor, f.seekPos-f.decompressorPos); err != nil {
 | 
						|
			return 0, err
 | 
						|
		}
 | 
						|
		f.decompressorPos = f.seekPos
 | 
						|
	}
 | 
						|
	n, err = f.decompressor.Read(p)
 | 
						|
	f.decompressorPos += int64(n)
 | 
						|
	f.seekPos = f.decompressorPos
 | 
						|
	return n, err
 | 
						|
}
 | 
						|
 | 
						|
func (f *EmbeddedFileBase) Seek(offset int64, whence int) (int64, error) {
 | 
						|
	switch whence {
 | 
						|
	case io.SeekStart:
 | 
						|
		f.seekPos = offset
 | 
						|
	case io.SeekCurrent:
 | 
						|
		f.seekPos += offset
 | 
						|
	case io.SeekEnd:
 | 
						|
		f.seekPos = f.info.OriginSize + offset
 | 
						|
	}
 | 
						|
	return f.seekPos, nil
 | 
						|
}
 | 
						|
 | 
						|
func (f *EmbeddedFileBase) Stat() (fs.FileInfo, error) {
 | 
						|
	return f.info, nil
 | 
						|
}
 | 
						|
 | 
						|
func (f *EmbeddedOriginFile) Close() error {
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (f *EmbeddedCompressedFile) Close() error {
 | 
						|
	return f.decompressor.Close()
 | 
						|
}
 | 
						|
 | 
						|
func (fi *embeddedFileInfo) Name() string {
 | 
						|
	return fi.BaseName
 | 
						|
}
 | 
						|
 | 
						|
func (fi *embeddedFileInfo) Size() int64 {
 | 
						|
	return fi.OriginSize
 | 
						|
}
 | 
						|
 | 
						|
func (fi *embeddedFileInfo) Mode() fs.FileMode {
 | 
						|
	return util.Iif(fi.IsDir(), fs.ModeDir|0o555, 0o444)
 | 
						|
}
 | 
						|
 | 
						|
func (fi *embeddedFileInfo) ModTime() time.Time {
 | 
						|
	return getExecutableModTime()
 | 
						|
}
 | 
						|
 | 
						|
func (fi *embeddedFileInfo) IsDir() bool {
 | 
						|
	return fi.Children != nil
 | 
						|
}
 | 
						|
 | 
						|
func (fi *embeddedFileInfo) Sys() any {
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (fi *embeddedFileInfo) Type() fs.FileMode {
 | 
						|
	return util.Iif(fi.IsDir(), fs.ModeDir, 0)
 | 
						|
}
 | 
						|
 | 
						|
func (fi *embeddedFileInfo) Info() (fs.FileInfo, error) {
 | 
						|
	return fi, nil
 | 
						|
}
 | 
						|
 | 
						|
// getExecutableModTime returns the modification time of the executable file.
 | 
						|
// In bindata, we can't use the ModTime of the files because we need to make the build reproducible
 | 
						|
var getExecutableModTime = sync.OnceValue(func() (modTime time.Time) {
 | 
						|
	exePath, err := os.Executable()
 | 
						|
	if err != nil {
 | 
						|
		return modTime
 | 
						|
	}
 | 
						|
	exePath, err = filepath.Abs(exePath)
 | 
						|
	if err != nil {
 | 
						|
		return modTime
 | 
						|
	}
 | 
						|
	exePath, err = filepath.EvalSymlinks(exePath)
 | 
						|
	if err != nil {
 | 
						|
		return modTime
 | 
						|
	}
 | 
						|
	st, err := os.Stat(exePath)
 | 
						|
	if err != nil {
 | 
						|
		return modTime
 | 
						|
	}
 | 
						|
	return st.ModTime()
 | 
						|
})
 | 
						|
 | 
						|
func GenerateEmbedBindata(fsRootPath, outputFile string) error {
 | 
						|
	output, err := os.OpenFile(outputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer output.Close()
 | 
						|
 | 
						|
	meta := &EmbeddedMeta{}
 | 
						|
	meta.Root = &embeddedFileInfo{}
 | 
						|
	var outputOffset int64
 | 
						|
	var embedFiles func(parent *embeddedFileInfo, fsPath, embedPath string) error
 | 
						|
	embedFiles = func(parent *embeddedFileInfo, fsPath, embedPath string) error {
 | 
						|
		dirEntries, err := os.ReadDir(fsPath)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		for _, dirEntry := range dirEntries {
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if dirEntry.IsDir() {
 | 
						|
				child := &embeddedFileInfo{
 | 
						|
					BaseName: dirEntry.Name(),
 | 
						|
					Children: []*embeddedFileInfo{}, // non-nil means it's a directory
 | 
						|
				}
 | 
						|
				parent.Children = append(parent.Children, child)
 | 
						|
				if err = embedFiles(child, filepath.Join(fsPath, dirEntry.Name()), path.Join(embedPath, dirEntry.Name())); err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				data, err := os.ReadFile(filepath.Join(fsPath, dirEntry.Name()))
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
				var compressed bytes.Buffer
 | 
						|
				gz, _ := gzip.NewWriterLevel(&compressed, gzip.BestCompression)
 | 
						|
				if _, err = gz.Write(data); err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
				if err = gz.Close(); err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
 | 
						|
				// only use the compressed data if it is smaller than the original data
 | 
						|
				outputBytes := util.Iif(len(compressed.Bytes()) < len(data), compressed.Bytes(), data)
 | 
						|
				child := &embeddedFileInfo{
 | 
						|
					BaseName:   dirEntry.Name(),
 | 
						|
					OriginSize: int64(len(data)),
 | 
						|
					DataBegin:  outputOffset,
 | 
						|
					DataLen:    int64(len(outputBytes)),
 | 
						|
				}
 | 
						|
				if _, err = output.Write(outputBytes); err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
				outputOffset += child.DataLen
 | 
						|
				parent.Children = append(parent.Children, child)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if err = embedFiles(meta.Root, fsRootPath, ""); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	jsonBuf, err := json.Marshal(meta)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	_, _ = output.Write([]byte{'\n'})
 | 
						|
	_, err = output.Write(bytes.TrimSpace(jsonBuf))
 | 
						|
	return err
 | 
						|
}
 |