commit
0dd446c293
@ -0,0 +1,333 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"flag" |
||||
"io" |
||||
"log" |
||||
"os" |
||||
"path" |
||||
"sync" |
||||
) |
||||
|
||||
type TopLevel struct { |
||||
LeftInput string |
||||
RightInput string |
||||
LeftOutput string |
||||
CombinedOutput string |
||||
RightOutput string |
||||
} |
||||
|
||||
type Step struct { |
||||
*TopLevel |
||||
Subpath string |
||||
} |
||||
|
||||
type Direction byte |
||||
|
||||
const ( |
||||
LEFT Direction = iota |
||||
RIGHT |
||||
COMBINED |
||||
) |
||||
|
||||
type FileState byte |
||||
|
||||
const ( |
||||
MISSING FileState = iota |
||||
FILE |
||||
DIRECTORY |
||||
UNKNOWN |
||||
) |
||||
|
||||
func (s *Step) CheckState(filePath string) (out FileState, err error) { |
||||
info, err := os.Stat(filePath) |
||||
if err != nil { |
||||
if os.IsNotExist(err) { |
||||
return MISSING, nil |
||||
} else { |
||||
return UNKNOWN, err |
||||
} |
||||
} |
||||
if info.IsDir() { |
||||
return DIRECTORY, nil |
||||
} else if info.Mode().IsRegular() { |
||||
return FILE, nil |
||||
} else { |
||||
return UNKNOWN, nil |
||||
} |
||||
} |
||||
|
||||
func (s *Step) InputPath(child string, side Direction) string { |
||||
var basePath string |
||||
switch side { |
||||
case LEFT: |
||||
basePath = s.LeftInput |
||||
case RIGHT: |
||||
basePath = s.RightInput |
||||
default: |
||||
panic("Unexpected side for input path") |
||||
} |
||||
return path.Join(basePath, s.Subpath, child) |
||||
} |
||||
|
||||
func (s *Step) OutputPath(child string, side Direction) string { |
||||
var basePath string |
||||
switch side { |
||||
case LEFT: |
||||
basePath = s.LeftOutput |
||||
case RIGHT: |
||||
basePath = s.RightOutput |
||||
case COMBINED: |
||||
basePath = s.CombinedOutput |
||||
default: |
||||
panic("Unexpected side for output path") |
||||
} |
||||
return path.Join(basePath, s.Subpath, child) |
||||
} |
||||
|
||||
func (s *Step) Separate(child string) { |
||||
s.SeparateLeft(child) |
||||
s.SeparateRight(child) |
||||
} |
||||
|
||||
func (s *Step) SeparateLeft(child string) { |
||||
leftInPath := s.InputPath(child, LEFT) |
||||
leftOutPath := s.OutputPath(child, LEFT) |
||||
|
||||
leftBase := path.Dir(leftOutPath) |
||||
err := os.MkdirAll(leftBase, 0755) |
||||
if err != nil { |
||||
log.Printf("Failed creating %s: %s\n", leftBase, err) |
||||
} |
||||
err = os.Rename(leftInPath, leftOutPath) |
||||
if err != nil && !os.IsNotExist(err) { |
||||
log.Printf("Failed moving %s to %s: %s\n", leftInPath, leftOutPath, err) |
||||
} |
||||
} |
||||
|
||||
func (s *Step) SeparateRight(child string) { |
||||
rightInPath := s.InputPath(child, RIGHT) |
||||
rightOutPath := s.OutputPath(child, RIGHT) |
||||
|
||||
rightBase := path.Dir(rightOutPath) |
||||
err := os.MkdirAll(rightBase, 0755) |
||||
if err != nil { |
||||
log.Printf("Failed creating %s: %s\n", rightBase, err) |
||||
} |
||||
err = os.Rename(rightInPath, rightOutPath) |
||||
if err != nil && !os.IsNotExist(err) { |
||||
log.Printf("Failed moving %s to %s: %s\n", rightInPath, rightOutPath, err) |
||||
} |
||||
} |
||||
|
||||
func (s *Step) Combine(child string) { |
||||
leftInPath := s.InputPath(child, LEFT) |
||||
rightInPath := s.InputPath(child, RIGHT) |
||||
combinedOutPath := s.OutputPath(child, COMBINED) |
||||
|
||||
combinedBase := path.Dir(combinedOutPath) |
||||
err := os.MkdirAll(combinedBase, 0755) |
||||
if err != nil { |
||||
log.Printf("Failed creating %s: %s\n", combinedBase, err) |
||||
} |
||||
err = os.Rename(leftInPath, combinedOutPath) |
||||
if err != nil && !os.IsNotExist(err) { |
||||
log.Printf("Failed moving %s to %s: %s\n", leftInPath, combinedOutPath, err) |
||||
} |
||||
err = os.Remove(rightInPath) |
||||
if err != nil && !os.IsNotExist(err) { |
||||
log.Printf("Failed removing %s: %s\n", rightInPath, err) |
||||
} |
||||
} |
||||
|
||||
func (s *Step) MakeCombinedDir(child string) { |
||||
combinedOutPath := s.OutputPath(child, COMBINED) |
||||
err := os.MkdirAll(combinedOutPath, 0755) |
||||
if err != nil { |
||||
log.Printf("Failed creating %s: %s\n", combinedOutPath, err) |
||||
} |
||||
} |
||||
|
||||
func (s *Step) RemoveInputDirs(child string) { |
||||
leftInPath := s.InputPath(child, LEFT) |
||||
rightInPath := s.InputPath(child, RIGHT) |
||||
err := os.Remove(leftInPath) |
||||
if err != nil && !os.IsNotExist(err) { |
||||
log.Printf("Failed removing %s: %s\n", leftInPath, err) |
||||
} |
||||
err = os.Remove(rightInPath) |
||||
if err != nil && !os.IsNotExist(err) { |
||||
log.Printf("Failed removing %s: %s\n", rightInPath, err) |
||||
} |
||||
} |
||||
|
||||
func (s *Step) ListChildren() []string { |
||||
leftInPath := s.InputPath("", LEFT) |
||||
rightInPath := s.InputPath("", RIGHT) |
||||
results := make(map[string]bool) |
||||
leftFiles, err := os.ReadDir(leftInPath) |
||||
if err != nil { |
||||
log.Printf("Failed listing %s: %s\n", leftInPath, err) |
||||
} |
||||
rightFiles, err := os.ReadDir(rightInPath) |
||||
if err != nil { |
||||
log.Printf("Failed listing %s: %s\n", rightInPath, err) |
||||
} |
||||
for _, file := range leftFiles { |
||||
results[file.Name()] = true |
||||
} |
||||
for _, file := range rightFiles { |
||||
results[file.Name()] = true |
||||
} |
||||
out := make([]string, 0, len(results)) |
||||
for file, _ := range results { |
||||
out = append(out, file) |
||||
} |
||||
return out |
||||
} |
||||
|
||||
func (s *Step) AreFilesIdentical(child string) bool { |
||||
leftInPath := s.InputPath(child, LEFT) |
||||
rightInPath := s.InputPath(child, RIGHT) |
||||
leftInfo, err := os.Stat(leftInPath) |
||||
if err != nil { |
||||
log.Printf("Error statting path %s: %s\n", leftInPath, err) |
||||
return false |
||||
} |
||||
rightInfo, err := os.Stat(rightInPath) |
||||
if err != nil { |
||||
log.Printf("Error statting path %s: %s\n", rightInPath, err) |
||||
return false |
||||
} |
||||
if leftInfo.Size() != rightInfo.Size() { |
||||
return false |
||||
} |
||||
|
||||
leftFile, err := os.OpenFile(leftInPath, os.O_RDONLY, 0) |
||||
if err != nil { |
||||
log.Printf("Error opening path %s: %s\n", leftInPath, err) |
||||
return false |
||||
} |
||||
defer func() { |
||||
err := leftFile.Close() |
||||
if err != nil { |
||||
log.Printf("Error closing %s: %s\n", leftInPath, err) |
||||
} |
||||
}() |
||||
rightFile, err := os.OpenFile(rightInPath, os.O_RDONLY, 0) |
||||
if err != nil { |
||||
log.Printf("Error opening path %s: %s\n", rightInPath, err) |
||||
return false |
||||
} |
||||
defer func() { |
||||
err := rightFile.Close() |
||||
if err != nil { |
||||
log.Printf("Error closing %s: %s\n", rightInPath, err) |
||||
} |
||||
}() |
||||
|
||||
var leftData [4096]byte |
||||
var rightData [4096]byte |
||||
for { |
||||
leftRead, err := leftFile.Read(leftData[:]) |
||||
if err != nil && err != io.EOF { |
||||
log.Printf("Error reading %s: %s\n", leftInPath, err) |
||||
return false |
||||
} |
||||
rightRead, err := rightFile.Read(rightData[:]) |
||||
if err != nil && err != io.EOF { |
||||
log.Printf("Error reading %s: %s\n", rightInPath, err) |
||||
return false |
||||
} |
||||
if leftRead != rightRead { |
||||
return false |
||||
} |
||||
if !bytes.Equal(leftData[:], rightData[:]) { |
||||
return false |
||||
} |
||||
if err == io.EOF { |
||||
return true |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (s *Step) Walk() { |
||||
wg := sync.WaitGroup{} |
||||
|
||||
for _, child := range s.ListChildren() { |
||||
rightPath := s.InputPath(child, RIGHT) |
||||
rightState, err := s.CheckState(rightPath) |
||||
if err != nil { |
||||
log.Printf("Error statting path %s: %s\n", rightPath, err) |
||||
continue |
||||
} else if rightState == UNKNOWN { |
||||
log.Printf("Unknown stat value for path %s\n", rightPath) |
||||
continue |
||||
} else if rightState == MISSING { |
||||
s.SeparateLeft(child) |
||||
continue |
||||
} |
||||
|
||||
leftPath := s.InputPath(child, LEFT) |
||||
leftState, err := s.CheckState(leftPath) |
||||
if err != nil { |
||||
log.Printf("Error statting path %s: %s\n", leftPath, err) |
||||
continue |
||||
} else if leftState == UNKNOWN { |
||||
log.Printf("Unknown stat value for path %s\n", leftPath) |
||||
continue |
||||
} else if leftState == MISSING { |
||||
s.SeparateRight(child) |
||||
continue |
||||
} |
||||
|
||||
switch rightState { |
||||
case FILE: |
||||
if leftState == FILE && s.AreFilesIdentical(child) { |
||||
s.Combine(child) |
||||
} else { |
||||
s.Separate(child) |
||||
} |
||||
case DIRECTORY: |
||||
if leftState == DIRECTORY { |
||||
s.MakeCombinedDir(child) |
||||
substep := Step{ |
||||
TopLevel: s.TopLevel, |
||||
Subpath: path.Join(s.Subpath, child), |
||||
} |
||||
wg.Add(1) |
||||
go func() { |
||||
defer func() { wg.Done() }() |
||||
substep.Walk() |
||||
s.RemoveInputDirs(child) |
||||
}() |
||||
} else { |
||||
s.Separate(child) |
||||
} |
||||
default: |
||||
panic("Unexpected state") |
||||
} |
||||
} |
||||
|
||||
wg.Wait() |
||||
} |
||||
|
||||
func main() { |
||||
settings := TopLevel{} |
||||
flag.StringVar(&settings.LeftInput, |
||||
"left-input", "./input/left", "The name of the left side of the input.") |
||||
flag.StringVar(&settings.RightInput, |
||||
"right-input", "./input/right", "The name of the right side of the input.") |
||||
flag.StringVar(&settings.LeftOutput, |
||||
"left-output", "./output/left", "The name of the left side of the output.") |
||||
flag.StringVar(&settings.CombinedOutput, |
||||
"combined-output", "./output/combined", "The name of the combined side of the output.") |
||||
flag.StringVar(&settings.RightOutput, |
||||
"right-output", "./output/right", "The name of the right side of the output.") |
||||
flag.Parse() |
||||
(&Step{ |
||||
TopLevel: &settings, |
||||
Subpath: "", |
||||
}).Walk() |
||||
} |
Loading…
Reference in new issue