Evstigneev Denis
38c5fe8b5a
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
|
|
}
|