Add HW 3
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
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:]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user