diff --git a/file/dirops.go b/file/dirops.go index 62b0341..455bef2 100644 --- a/file/dirops.go +++ b/file/dirops.go @@ -6,11 +6,22 @@ import ( "os" "path" "path/filepath" + "time" ) +// Set of allowed file extensions for the safety check before clearing the objects folder +var allowedExtensions = map[string]struct{}{ + ".json": {}, + ".gmnotes": {}, + ".luascriptstate": {}, + ".ttslua": {}, + ".xml": {}, +} + // DirCreator abstracts folder creation type DirCreator interface { CreateDir(relpath string, suggestion string) (string, error) + Clear() error } // DirExplorer allows files and folders to be enumerated @@ -51,6 +62,59 @@ func (d *DirOps) CreateDir(relpath, suggestion string) (string, error) { return dirname, nil } +// preClearCheck walks the directory and ensures all files have an allowed extension. +// It returns an error if a file with a disallowed extension is found. +func (d *DirOps) preClearCheck() error { + walkErr := filepath.Walk(d.base, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip directories + if info.IsDir() { + return nil + } + + ext := filepath.Ext(info.Name()) + if _, isAllowed := allowedExtensions[ext]; !isAllowed { + return fmt.Errorf("unsafe file type found: %s", path) + } + return nil + }) + + // If the directory doesn't exist, Walk returns an error. We treat this as "safe". + if os.IsNotExist(walkErr) { + return nil + } + return walkErr +} + +// Clear removes all contents from the base directory and recreates it. +func (d *DirOps) Clear() error { + log.Println("Performing safety check...") + startTime := time.Now() + + if err := d.preClearCheck(); err != nil { + return fmt.Errorf("pre-clear safety check failed, operation aborted: %w", err) + } + + duration := time.Since(startTime) + log.Printf("Safety check passed in %v. Proceeding with clear.", duration) + + // Remove the directory and all its contents + if err := os.RemoveAll(d.base); err != nil { + return fmt.Errorf("error clearing directory %s: %w", d.base, err) + } + + // Recreate the empty directory + if err := os.MkdirAll(d.base, 0755); err != nil { + return fmt.Errorf("error recreating directory %s: %w", d.base, err) + } + + log.Printf("Cleared and recreated directory: %s", d.base) + return nil +} + // ListFilesAndFolders allows for file exploration. returns relateive file or folder names func (d *DirOps) ListFilesAndFolders(relpath string) ([]string, []string, error) { p := filepath.Join(d.base, relpath) diff --git a/main.go b/main.go index 3a33547..bab6dcd 100644 --- a/main.go +++ b/main.go @@ -92,7 +92,13 @@ func main() { if *objin != "" { *modfile = *objin objs = file.NewJSONOps(filepath.Dir(*objout)) + } else { + // clear the objects directory to avoid orphaned files (by removing and recreating) + if err := objdir.Clear(); err != nil { + log.Fatalf("Failed to clear objects directory before writing: %v", err) + } } + raw, err := prepForReverse(*moddir, *modfile) if err != nil { log.Fatalf("prepForReverse (%s) failed : %v", *modfile, err) diff --git a/tests/fakefiles.go b/tests/fakefiles.go index 6ebe915..304f23e 100644 --- a/tests/fakefiles.go +++ b/tests/fakefiles.go @@ -116,6 +116,11 @@ func (f *FakeFiles) CreateDir(a, b string) (string, error) { return b, nil } +// Clear satisfies DirCreator +func (f *FakeFiles) Clear() error { + return nil +} + // ListFilesAndFolders satisfies DirExplorer func (f *FakeFiles) ListFilesAndFolders(relpath string) ([]string, []string, error) { // ignore non json files. i don't think they Matter