Files
synclib/filesystem.go

193 lines
4.4 KiB
Go

package main
import (
"fmt"
"os"
"sync"
)
var filesystem FileSystem = NewRealFileSystem()
// FileSystem abstracts filesystem operations so we can swap implementations
type FileSystem interface {
Remove(path string) error
RemoveAll(path string) error
MkdirAll(path string, perm os.FileMode) error
Symlink(source, target string) error
Link(source, target string) error
RecordLinkAttempt(kind string, source, target string, err error, dryRun bool)
SummaryLines() []string
IsDryRun() bool
}
const (
opRemove = "remove"
opRemoveAll = "remove_all"
opMkdirAll = "mkdir_all"
opSymlink = "symlink"
opHardlink = "hardlink"
)
type operationRecord struct {
kind string
source string
target string
err error
dryRun bool
}
func (r operationRecord) summaryLine() (string, bool) {
if r.kind != opSymlink && r.kind != opHardlink {
return "", false
}
kindLabel := "Symlink"
if r.kind == opHardlink {
kindLabel = "Hardlink"
}
status := "OK"
if r.err != nil {
status = fmt.Sprintf("%sFAIL%s: %v", BRed, Reset, r.err)
} else if r.dryRun {
status = "DRY-RUN"
}
source := FormatSourcePath(r.source)
target := FormatTargetPath(r.target)
return fmt.Sprintf("%s %s → %s (%s)", kindLabel, source, target, status), true
}
type baseFileSystem struct {
mu sync.Mutex
operations []operationRecord
}
func (b *baseFileSystem) addOperation(kind, source, target string, err error, dryRun bool) {
b.mu.Lock()
defer b.mu.Unlock()
b.operations = append(b.operations, operationRecord{
kind: kind,
source: source,
target: target,
err: err,
dryRun: dryRun,
})
}
func (b *baseFileSystem) snapshot() []operationRecord {
b.mu.Lock()
defer b.mu.Unlock()
ops := make([]operationRecord, len(b.operations))
copy(ops, b.operations)
return ops
}
func summarizeOperations(records []operationRecord) []string {
var lines []string
for _, record := range records {
if line, ok := record.summaryLine(); ok {
lines = append(lines, line)
}
}
return lines
}
type realFileSystem struct {
baseFileSystem
}
// NewRealFileSystem returns a filesystem implementation that writes to disk
func NewRealFileSystem() FileSystem {
return &realFileSystem{}
}
func (fs *realFileSystem) Remove(path string) error {
err := os.Remove(path)
fs.addOperation(opRemove, "", path, err, false)
return err
}
func (fs *realFileSystem) RemoveAll(path string) error {
err := os.RemoveAll(path)
fs.addOperation(opRemoveAll, "", path, err, false)
return err
}
func (fs *realFileSystem) MkdirAll(path string, perm os.FileMode) error {
err := os.MkdirAll(path, perm)
fs.addOperation(opMkdirAll, "", path, err, false)
return err
}
func (fs *realFileSystem) Symlink(source, target string) error {
err := os.Symlink(source, target)
fs.RecordLinkAttempt(opSymlink, source, target, err, false)
return err
}
func (fs *realFileSystem) Link(source, target string) error {
err := os.Link(source, target)
fs.RecordLinkAttempt(opHardlink, source, target, err, false)
return err
}
func (fs *realFileSystem) RecordLinkAttempt(kind string, source, target string, err error, dryRun bool) {
fs.addOperation(kind, source, target, err, dryRun)
}
func (fs *realFileSystem) SummaryLines() []string {
return summarizeOperations(fs.snapshot())
}
func (fs *realFileSystem) IsDryRun() bool {
return false
}
type dryRunFileSystem struct {
baseFileSystem
}
// NewDryRunFileSystem returns a filesystem implementation that only records operations
func NewDryRunFileSystem() FileSystem {
return &dryRunFileSystem{}
}
func (fs *dryRunFileSystem) Remove(path string) error {
fs.addOperation(opRemove, "", path, nil, true)
return nil
}
func (fs *dryRunFileSystem) RemoveAll(path string) error {
fs.addOperation(opRemoveAll, "", path, nil, true)
return nil
}
func (fs *dryRunFileSystem) MkdirAll(path string, perm os.FileMode) error {
fs.addOperation(opMkdirAll, "", path, nil, true)
return nil
}
func (fs *dryRunFileSystem) Symlink(source, target string) error {
fs.RecordLinkAttempt(opSymlink, source, target, nil, true)
return nil
}
func (fs *dryRunFileSystem) Link(source, target string) error {
fs.RecordLinkAttempt(opHardlink, source, target, nil, true)
return nil
}
func (fs *dryRunFileSystem) RecordLinkAttempt(kind string, source, target string, err error, dryRun bool) {
fs.addOperation(kind, source, target, err, dryRun)
}
func (fs *dryRunFileSystem) SummaryLines() []string {
return summarizeOperations(fs.snapshot())
}
func (fs *dryRunFileSystem) IsDryRun() bool {
return true
}