github.com/falafeljan/pkger@v0.18.0/pkging/mem/file.go (about)

     1  package mem
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"time"
    12  
    13  	"github.com/markbates/pkger/here"
    14  	"github.com/markbates/pkger/pkging"
    15  )
    16  
    17  const timeFmt = time.RFC3339Nano
    18  
    19  var _ pkging.File = &File{}
    20  
    21  type File struct {
    22  	Here   here.Info
    23  	info   *pkging.FileInfo
    24  	path   here.Path
    25  	data   []byte
    26  	parent here.Path
    27  	writer *bytes.Buffer
    28  	reader io.Reader
    29  	pkging pkging.Pkger
    30  }
    31  
    32  // Seek sets the offset for the next Read or Write on file to offset, interpreted according to whence: 0 means relative to the origin of the file, 1 means relative to the current offset, and 2 means relative to the end. It returns the new offset and an error, if any.
    33  func (f *File) Seek(ofpkginget int64, whence int) (int64, error) {
    34  	if len(f.data) > 0 && f.reader == nil {
    35  		f.reader = bytes.NewReader(f.data)
    36  	}
    37  
    38  	if sk, ok := f.reader.(io.Seeker); ok {
    39  		return sk.Seek(ofpkginget, whence)
    40  	}
    41  	return 0, nil
    42  }
    43  
    44  // Close closes the File, rendering it unusable for I/O.
    45  func (f *File) Close() error {
    46  	defer func() {
    47  		f.reader = nil
    48  		f.writer = nil
    49  	}()
    50  	if f.reader != nil {
    51  		if c, ok := f.reader.(io.Closer); ok {
    52  			if err := c.Close(); err != nil {
    53  				return err
    54  			}
    55  		}
    56  	}
    57  
    58  	if f.writer == nil {
    59  		return nil
    60  	}
    61  
    62  	f.data = f.writer.Bytes()
    63  
    64  	fi := f.info
    65  	fi.Details.Size = int64(len(f.data))
    66  	fi.Details.ModTime = pkging.ModTime(time.Now())
    67  	f.info = fi
    68  	return nil
    69  }
    70  
    71  // Read reads up to len(b) bytes from the File. It returns the number of bytes read and any error encountered. At end of file, Read returns 0, io.EOF.
    72  func (f *File) Read(p []byte) (int, error) {
    73  	if len(f.data) > 0 && f.reader == nil {
    74  		f.reader = bytes.NewReader(f.data)
    75  	}
    76  
    77  	if f.reader != nil {
    78  		return f.reader.Read(p)
    79  	}
    80  
    81  	return 0, fmt.Errorf("unable to read %s", f.Name())
    82  }
    83  
    84  // Write writes len(b) bytes to the File. It returns the number of bytes written and an error, if any. Write returns a non-nil error when n != len(b).
    85  func (f *File) Write(b []byte) (int, error) {
    86  	if f.writer == nil {
    87  		f.writer = &bytes.Buffer{}
    88  	}
    89  	i, err := f.writer.Write(b)
    90  	return i, err
    91  }
    92  
    93  // Info returns the here.Info of the file
    94  func (f File) Info() here.Info {
    95  	return f.Here
    96  }
    97  
    98  // Stat returns the FileInfo structure describing file. If there is an error, it will be of type *PathError.
    99  func (f File) Stat() (os.FileInfo, error) {
   100  	if f.info == nil {
   101  		return nil, os.ErrNotExist
   102  	}
   103  	return f.info, nil
   104  }
   105  
   106  // Name retuns the name of the file in pkger format
   107  func (f File) Name() string {
   108  	return f.path.String()
   109  }
   110  
   111  // Path returns the here.Path of the file
   112  func (f File) Path() here.Path {
   113  	return f.path
   114  }
   115  
   116  func (f File) String() string {
   117  	return f.Path().String()
   118  }
   119  
   120  // Readdir reads the contents of the directory associated with file and returns a slice of up to n FileInfo values, as would be returned by Lstat, in directory order. Subsequent calls on the same file will yield further FileInfos.
   121  //
   122  // If n > 0, Readdir returns at most n FileInfo structures. In this case, if Readdir returns an empty slice, it will return a non-nil error explaining why. At the end of a directory, the error is io.EOF.
   123  //
   124  // If n <= 0, Readdir returns all the FileInfo from the directory in a single slice. In this case, if Readdir succeeds (reads all the way to the end of the directory), it returns the slice and a nil error. If it encounters an error before the end of the directory, Readdir returns the FileInfo read until that point and a non-nil error.
   125  func (f *File) Readdir(count int) ([]os.FileInfo, error) {
   126  	var infos []os.FileInfo
   127  	root := f.Path().String()
   128  	err := f.pkging.Walk(root, func(path string, info os.FileInfo, err error) error {
   129  		if err != nil {
   130  			return err
   131  		}
   132  
   133  		if count > 0 && len(infos) == count {
   134  			return io.EOF
   135  		}
   136  
   137  		if root == path {
   138  			return nil
   139  		}
   140  
   141  		pt, err := f.pkging.Parse(path)
   142  		if err != nil {
   143  			return err
   144  		}
   145  		if pt.Name == f.parent.Name {
   146  			return nil
   147  		}
   148  
   149  		infos = append(infos, info)
   150  		if info.IsDir() && path != root {
   151  			return filepath.SkipDir
   152  		}
   153  
   154  		return nil
   155  	})
   156  
   157  	if err != nil {
   158  		if _, ok := err.(*os.PathError); ok {
   159  			return infos, nil
   160  		}
   161  		if err != io.EOF {
   162  			return nil, err
   163  		}
   164  	}
   165  	return infos, nil
   166  
   167  }
   168  
   169  // Open implements the http.FileSystem interface. A FileSystem implements access to a collection of named files. The elements in a file path are separated by slash ('/', U+002F) characters, regardless of host operating system convention.
   170  func (f *File) Open(name string) (http.File, error) {
   171  	pt, err := f.Here.Parse(name)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	if pt == f.path {
   177  		return f, nil
   178  	}
   179  
   180  	pt.Name = path.Join(f.Path().Name, pt.Name)
   181  
   182  	di, err := f.pkging.Open(pt.String())
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	fi, err := di.Stat()
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	if fi.IsDir() {
   193  		d2 := &File{
   194  			info:   pkging.NewFileInfo(fi),
   195  			Here:   di.Info(),
   196  			path:   pt,
   197  			parent: f.path,
   198  			pkging: f.pkging,
   199  		}
   200  		di = d2
   201  	}
   202  	return di, nil
   203  }