Demuxes the OneDrive and Google Drive files.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
drive-demuxer/drive-demuxer.go

318 lines
7.2 KiB

package main
import (
"flag"
"github.com/cheggaaa/pb/v3"
"github.com/udhos/equalfile"
"log"
"os"
"path"
)
type TopLevel struct {
LeftInput string
RightInput string
LeftOutput string
CombinedOutput string
RightOutput string
DryRun bool
Bar *pb.ProgressBar
Comparer *equalfile.Cmp
}
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)
if s.DryRun {
return
}
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)
if s.DryRun {
return
}
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)
if s.DryRun {
return
}
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)
if s.DryRun {
return
}
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)
if s.DryRun {
return
}
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)
equal, err := s.Comparer.CompareFile(leftInPath, rightInPath)
if err != nil {
log.Printf("Error comparing path %s and %s: %s\n", leftInPath, rightInPath, err)
return false
}
return equal
}
func (s *Step) Walk() {
children := s.ListChildren()
s.Bar.AddTotal(int64(len(children)))
for _, child := range children {
rightPath := s.InputPath(child, RIGHT)
rightState, err := s.CheckState(rightPath)
if err != nil {
log.Printf("Error statting path %s: %s\n", rightPath, err)
s.Bar.Increment()
continue
} else if rightState == UNKNOWN {
log.Printf("Unknown stat value for path %s\n", rightPath)
s.Bar.Increment()
continue
} else if rightState == MISSING {
s.SeparateLeft(child)
s.Bar.Increment()
continue
}
leftPath := s.InputPath(child, LEFT)
leftState, err := s.CheckState(leftPath)
if err != nil {
log.Printf("Error statting path %s: %s\n", leftPath, err)
s.Bar.Increment()
continue
} else if leftState == UNKNOWN {
log.Printf("Unknown stat value for path %s\n", leftPath)
s.Bar.Increment()
continue
} else if leftState == MISSING {
s.SeparateRight(child)
s.Bar.Increment()
continue
}
switch rightState {
case FILE:
if leftState == FILE {
if s.AreFilesIdentical(child) {
s.Combine(child)
s.Bar.Increment()
} else {
s.Separate(child)
s.Bar.Increment()
}
} else {
s.Separate(child)
s.Bar.Increment()
}
case DIRECTORY:
if leftState == DIRECTORY {
substep := Step{
TopLevel: s.TopLevel,
Subpath: path.Join(s.Subpath, child),
}
s.MakeCombinedDir(child)
substep.Walk()
s.RemoveInputDirs(child)
s.Bar.Increment()
} else {
s.Separate(child)
s.Bar.Increment()
}
default:
panic("Unexpected state")
}
}
}
func main() {
settings := TopLevel{
Bar: pb.StartNew(1),
Comparer: equalfile.New(nil, equalfile.Options{}),
}
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.BoolVar(&settings.DryRun,
"dry-run", true, "True if no actual operation should be performed.")
flag.Parse()
(&Step{
TopLevel: &settings,
Subpath: "",
}).Walk()
settings.Bar.Increment()
}