234 lines
4.8 KiB
Go
234 lines
4.8 KiB
Go
package hw3test
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Env contains information about all files in the test environment.
|
|
// Can be written to disk.
|
|
type Env struct {
|
|
RootDir *EnvDir
|
|
}
|
|
|
|
// Lookup returns the node for the given path.
|
|
func (e *Env) Lookup(p string) (*EnvDir, EnvNode) {
|
|
p = path.Clean(strings.TrimPrefix(p, "/"))
|
|
if p == "." {
|
|
return nil, e.RootDir
|
|
}
|
|
dir, file := path.Split(p)
|
|
|
|
dirs := strings.Split(dir, "/")
|
|
dirs = dirs[:len(dirs)-1]
|
|
|
|
parent := e.RootDir
|
|
for _, d := range dirs {
|
|
nxt, ok := parent.Listing[d]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
parent, ok = nxt.(*EnvDir)
|
|
if !ok {
|
|
// not a directory
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
nxt := parent.Listing[file]
|
|
return parent, nxt
|
|
}
|
|
|
|
// Clone returns deep clone of an Env.
|
|
func (e *Env) Clone() *Env {
|
|
return &Env{
|
|
RootDir: e.RootDir.Clone().(*EnvDir),
|
|
}
|
|
}
|
|
|
|
// EnvNode is a file/dir.
|
|
type EnvNode interface {
|
|
// WriteToDisk persists node and its children to disk.
|
|
WriteToDisk(path string) error
|
|
|
|
// Stats aggregates stats of the node and its children.
|
|
Stats(path string, stats *Stats)
|
|
|
|
// Clone creates a deep copy of the node.
|
|
Clone() EnvNode
|
|
}
|
|
|
|
// EnvDir is a virtual directory, that can be written to disk.
|
|
type EnvDir struct {
|
|
Listing map[string]EnvNode
|
|
Depth int
|
|
}
|
|
|
|
func (d *EnvDir) WriteToDisk(p string) error {
|
|
err := os.Mkdir(p, 0777)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for name, writter := range d.Listing {
|
|
nxt := path.Join(p, name)
|
|
err := writter.WriteToDisk(nxt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WriteToDiskSelected keeps the size and directory structure of every initial
|
|
// file, but only materializes contents for paths that a query may read.
|
|
func (d *EnvDir) WriteToDiskSelected(p string, needed map[string]bool) error {
|
|
return d.writeToDiskSelected(p, "", needed)
|
|
}
|
|
|
|
func (d *EnvDir) writeToDiskSelected(p, relative string, needed map[string]bool) error {
|
|
if err := os.Mkdir(p, 0777); err != nil {
|
|
return err
|
|
}
|
|
for name, node := range d.Listing {
|
|
childPath := filepath.Join(p, name)
|
|
childRelative := path.Join(relative, name)
|
|
switch child := node.(type) {
|
|
case *EnvDir:
|
|
if err := child.writeToDiskSelected(childPath, childRelative, needed); err != nil {
|
|
return err
|
|
}
|
|
case *EnvFile:
|
|
if needed[childRelative] {
|
|
if err := child.WriteToDisk(childPath); err != nil {
|
|
return err
|
|
}
|
|
} else if err := child.WriteSparseToDisk(childPath); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("unsupported environment node %T", child)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *EnvDir) Stats(p string, stats *Stats) {
|
|
stats.Dirs++
|
|
stats.DirPaths = append(stats.DirPaths, p)
|
|
for name, node := range d.Listing {
|
|
node.Stats(path.Join(p, name), stats)
|
|
}
|
|
}
|
|
|
|
func (d *EnvDir) Clone() EnvNode {
|
|
newDir := &EnvDir{
|
|
Listing: map[string]EnvNode{},
|
|
Depth: d.Depth,
|
|
}
|
|
|
|
for name, node := range d.Listing {
|
|
newDir.Listing[name] = node.Clone()
|
|
}
|
|
return newDir
|
|
}
|
|
|
|
func (d *EnvDir) CreateDir(name string) (dir *EnvDir, exist bool) {
|
|
_, exist = d.Listing[name]
|
|
if exist {
|
|
return nil, exist
|
|
}
|
|
|
|
newDir := &EnvDir{
|
|
Listing: map[string]EnvNode{},
|
|
Depth: d.Depth + 1,
|
|
}
|
|
d.Listing[name] = newDir
|
|
return newDir, false
|
|
}
|
|
|
|
// EnvFile is a virtual file, that can be written to disk.
|
|
type EnvFile struct {
|
|
GenSeed int64
|
|
Size int64
|
|
TextOnly bool
|
|
}
|
|
|
|
func (f *EnvFile) Open() io.Reader {
|
|
var gen io.Reader = rand.New(rand.NewSource(f.GenSeed))
|
|
if f.TextOnly {
|
|
gen = &TextReader{gen}
|
|
}
|
|
gen = io.LimitReader(gen, f.Size)
|
|
return gen
|
|
}
|
|
|
|
func (f *EnvFile) WriteToDisk(p string) error {
|
|
file, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
reader := f.Open()
|
|
_, err = io.CopyBuffer(file, reader, make([]byte, 256*1024))
|
|
return err
|
|
}
|
|
|
|
func (f *EnvFile) WriteSparseToDisk(p string) error {
|
|
file, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
return file.Truncate(f.Size)
|
|
}
|
|
|
|
func (f *EnvFile) Stats(p string, stats *Stats) {
|
|
stats.Files++
|
|
stats.Size += f.Size
|
|
stats.FilePaths = append(stats.FilePaths, p)
|
|
}
|
|
|
|
func (f *EnvFile) Clone() EnvNode {
|
|
return &EnvFile{
|
|
GenSeed: f.GenSeed,
|
|
Size: f.Size,
|
|
TextOnly: f.TextOnly,
|
|
}
|
|
}
|
|
|
|
// Stats is a helper for listing all files/dirs in environment.
|
|
type Stats struct {
|
|
// Number of directories in the environment.
|
|
Files int
|
|
|
|
// Count of directories in the environment.
|
|
Dirs int
|
|
|
|
// Size of all files in the environment.
|
|
Size int64
|
|
|
|
// FilePaths to all files in the environment.
|
|
FilePaths []string
|
|
|
|
// DirPaths to all directories in the environment.
|
|
DirPaths []string
|
|
}
|
|
|
|
// Normalize will fix stats to be deterministic and don't contain root dir.
|
|
func (s *Stats) Normalize() {
|
|
sort.Strings(s.FilePaths)
|
|
sort.Strings(s.DirPaths)
|
|
|
|
if len(s.DirPaths) > 0 && s.DirPaths[0] == "" {
|
|
s.DirPaths = s.DirPaths[1:]
|
|
}
|
|
}
|