All checks were successful
		
		
	
	test / test (push) Successful in 42s
				
			## Pull Request template Please, go through these steps before clicking submit on this PR. 1. Give a descriptive title to your PR. 2. Provide a description of your changes. 3. Make sure you have some relevant tests. 4. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if applicable). **PLEASE REMOVE THIS TEMPLATE BEFORE SUBMITTING** Reviewed-on: #369 Co-authored-by: Evstigneev Denis <danteevstigneev@yandex.ru> Co-committed-by: Evstigneev Denis <danteevstigneev@yandex.ru>
		
			
				
	
	
		
			247 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package structfs
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"net/http"
 | 
						|
	"os"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// FileServer creates new file server from the struct iface with specific tag and specific modtime
 | 
						|
func FileServer(iface interface{}, tag string, modtime time.Time) http.Handler {
 | 
						|
	if modtime.IsZero() {
 | 
						|
		modtime = time.Now()
 | 
						|
	}
 | 
						|
	return &fs{iface: iface, tag: tag, modtime: modtime}
 | 
						|
}
 | 
						|
 | 
						|
func (fs *fs) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
						|
	upath := r.URL.Path
 | 
						|
	if !strings.HasPrefix(upath, "/") {
 | 
						|
		upath = "/" + upath
 | 
						|
		r.URL.Path = upath
 | 
						|
	}
 | 
						|
	f, err := fs.Open(r.URL.Path)
 | 
						|
	if err != nil {
 | 
						|
		w.WriteHeader(http.StatusInternalServerError)
 | 
						|
		_, _ = w.Write([]byte(err.Error()))
 | 
						|
		return
 | 
						|
	}
 | 
						|
	w.Header().Set("Content-Type", "application/octet-stream")
 | 
						|
	http.ServeContent(w, r, r.URL.Path, fs.modtime, f)
 | 
						|
}
 | 
						|
 | 
						|
type fs struct {
 | 
						|
	modtime time.Time
 | 
						|
	iface   interface{}
 | 
						|
	tag     string
 | 
						|
}
 | 
						|
 | 
						|
type file struct {
 | 
						|
	modtime time.Time
 | 
						|
	name    string
 | 
						|
	data    []byte
 | 
						|
	offset  int64
 | 
						|
}
 | 
						|
 | 
						|
type fileInfo struct {
 | 
						|
	modtime time.Time
 | 
						|
	name    string
 | 
						|
	size    int64
 | 
						|
}
 | 
						|
 | 
						|
func (fi *fileInfo) Sys() interface{} {
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (fi *fileInfo) Size() int64 {
 | 
						|
	return fi.size
 | 
						|
}
 | 
						|
 | 
						|
func (fi *fileInfo) Name() string {
 | 
						|
	return fi.name
 | 
						|
}
 | 
						|
 | 
						|
func (fi *fileInfo) Mode() os.FileMode {
 | 
						|
	if strings.HasSuffix(fi.name, "/") {
 | 
						|
		return os.FileMode(0o755) | os.ModeDir
 | 
						|
	}
 | 
						|
	return os.FileMode(0o644)
 | 
						|
}
 | 
						|
 | 
						|
func (fi *fileInfo) IsDir() bool {
 | 
						|
	// disables additional open /index.html
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func (fi *fileInfo) ModTime() time.Time {
 | 
						|
	return fi.modtime
 | 
						|
}
 | 
						|
 | 
						|
func (f *file) Close() error {
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (f *file) Read(b []byte) (int, error) {
 | 
						|
	var err error
 | 
						|
	var n int
 | 
						|
 | 
						|
	if f.offset >= int64(len(f.data)) {
 | 
						|
		return 0, io.EOF
 | 
						|
	}
 | 
						|
 | 
						|
	if len(f.data) > 0 {
 | 
						|
		n = copy(b, f.data[f.offset:])
 | 
						|
	}
 | 
						|
 | 
						|
	if n < len(b) {
 | 
						|
		err = io.EOF
 | 
						|
	}
 | 
						|
 | 
						|
	f.offset += int64(n)
 | 
						|
	return n, err
 | 
						|
}
 | 
						|
 | 
						|
func (f *file) Readdir(_ int) ([]os.FileInfo, error) {
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func (f *file) Seek(offset int64, whence int) (int64, error) {
 | 
						|
	//	log.Printf("seek %d %d %s\n", offset, whence, f.name)
 | 
						|
	switch whence {
 | 
						|
	case io.SeekStart:
 | 
						|
		f.offset = offset
 | 
						|
	case io.SeekCurrent:
 | 
						|
		f.offset += offset
 | 
						|
	case io.SeekEnd:
 | 
						|
		f.offset = int64(len(f.data)) + offset
 | 
						|
	}
 | 
						|
	return f.offset, nil
 | 
						|
}
 | 
						|
 | 
						|
func (f *file) Stat() (os.FileInfo, error) {
 | 
						|
	return &fileInfo{name: f.name, size: int64(len(f.data)), modtime: f.modtime}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (fs *fs) Open(path string) (http.File, error) {
 | 
						|
	return newFile(path, fs.iface, fs.tag, fs.modtime)
 | 
						|
}
 | 
						|
 | 
						|
func newFile(name string, iface interface{}, tag string, modtime time.Time) (*file, error) {
 | 
						|
	var err error
 | 
						|
 | 
						|
	f := &file{name: name, modtime: modtime}
 | 
						|
	f.data, err = structItem(name, iface, tag)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return f, nil
 | 
						|
}
 | 
						|
 | 
						|
func structItem(path string, iface interface{}, tag string) ([]byte, error) {
 | 
						|
	var buf []byte
 | 
						|
	var err error
 | 
						|
	var curiface interface{}
 | 
						|
 | 
						|
	if path == "/" {
 | 
						|
		return getNames(iface, tag)
 | 
						|
	}
 | 
						|
 | 
						|
	idx := strings.Index(path[1:], "/")
 | 
						|
	switch {
 | 
						|
	case idx > 0:
 | 
						|
		curiface, err = getStruct(path[1:idx+1], iface, tag)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		buf, err = structItem(path[idx+1:], curiface, tag)
 | 
						|
	case idx == 0:
 | 
						|
		return getNames(iface, tag)
 | 
						|
	case idx < 0:
 | 
						|
		return getValue(path[1:], iface, tag)
 | 
						|
	}
 | 
						|
 | 
						|
	return buf, err
 | 
						|
}
 | 
						|
 | 
						|
func getNames(iface interface{}, tag string) ([]byte, error) {
 | 
						|
	var lines []string
 | 
						|
	s := reflectValue(iface)
 | 
						|
	typeOf := s.Type()
 | 
						|
	for i := 0; i < s.NumField(); i++ {
 | 
						|
		value := typeOf.Field(i).Tag.Get(tag)
 | 
						|
		if value != "" {
 | 
						|
			lines = append(lines, value)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if len(lines) > 0 {
 | 
						|
		return []byte(strings.Join(lines, "\n")), nil
 | 
						|
	}
 | 
						|
	return nil, fmt.Errorf("failed to find names")
 | 
						|
}
 | 
						|
 | 
						|
func getStruct(name string, iface interface{}, tag string) (interface{}, error) {
 | 
						|
	s := reflectValue(iface)
 | 
						|
	typeOf := s.Type()
 | 
						|
	for i := 0; i < s.NumField(); i++ {
 | 
						|
		if typeOf.Field(i).Tag.Get(tag) == name {
 | 
						|
			return s.Field(i).Interface(), nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, fmt.Errorf("failed to find iface %T with name %s", iface, name)
 | 
						|
}
 | 
						|
 | 
						|
func getValue(name string, iface interface{}, tag string) ([]byte, error) {
 | 
						|
	s := reflectValue(iface)
 | 
						|
	typeOf := s.Type()
 | 
						|
	switch typeOf.Kind() {
 | 
						|
	case reflect.Map:
 | 
						|
		return []byte(fmt.Sprintf("%v", s.MapIndex(reflect.ValueOf(name)).Interface())), nil
 | 
						|
	default:
 | 
						|
		for i := 0; i < s.NumField(); i++ {
 | 
						|
			if typeOf.Field(i).Tag.Get(tag) != name {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			ifs := s.Field(i).Interface()
 | 
						|
			switch s.Field(i).Kind() {
 | 
						|
			case reflect.Slice:
 | 
						|
				var lines []string
 | 
						|
				for k := 0; k < s.Field(i).Len(); k++ {
 | 
						|
					lines = append(lines, fmt.Sprintf("%v", s.Field(i).Index(k)))
 | 
						|
				}
 | 
						|
				return []byte(strings.Join(lines, "\n")), nil
 | 
						|
			default:
 | 
						|
				return []byte(fmt.Sprintf("%v", ifs)), nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, fmt.Errorf("failed to find %s in interface %T", name, iface)
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
func hasValidType(obj interface{}, types []reflect.Kind) bool {
 | 
						|
	for _, t := range types {
 | 
						|
		if reflect.TypeOf(obj).Kind() == t {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false
 | 
						|
}
 | 
						|
*/
 | 
						|
 | 
						|
func reflectValue(obj interface{}) reflect.Value {
 | 
						|
	var val reflect.Value
 | 
						|
 | 
						|
	if reflect.TypeOf(obj).Kind() == reflect.Ptr {
 | 
						|
		val = reflect.ValueOf(obj).Elem()
 | 
						|
	} else {
 | 
						|
		val = reflect.ValueOf(obj)
 | 
						|
	}
 | 
						|
 | 
						|
	return val
 | 
						|
}
 |