Fork channel

Create a new channel as a copy of main.

Rename channel

Rename main to:

Delete channel

Delete main? This cannot be undone.

fs.go
package utils

import (
	"os"
	"path/filepath"
	"sort"
	"strings"
)

// FindFilesOptions configures directory scanning
type FindFilesOptions struct {
	Extension    string   // e.g. ".wav" or ".data"
	Recursive    bool     // whether to walk subdirectories
	SkipPrefixes []string // directory prefixes to skip (e.g. "Clips_")
	SkipHidden   bool     // skip files/folders starting with "."
	MinSize      int64    // minimum file size in bytes
}

// FindFiles scans a directory for files matching the given options
func FindFiles(rootPath string, opts FindFilesOptions) ([]string, error) {
	var results []string

	if opts.Recursive {
		if err := filepath.Walk(rootPath, makeWalkFunc(&results, opts, rootPath)); err != nil {
			return nil, err
		}
	} else {
		if err := scanFlatDir(rootPath, &results, opts); err != nil {
			return nil, err
		}
	}

	sort.Strings(results)
	return results, nil
}

// shouldSkipHidden checks if a path should be skipped due to hidden file rules.
func shouldSkipHidden(name, path, rootPath string, info os.FileInfo, skipHidden bool) (bool, error) {
	if !skipHidden || !strings.HasPrefix(name, ".") || path == rootPath {
		return false, nil
	}
	if info.IsDir() {
		return true, filepath.SkipDir
	}
	return true, nil
}

// shouldSkipDir checks if a directory should be skipped based on prefixes.
func shouldSkipDir(name, path, rootPath string, skipPrefixes []string) bool {
	if path == rootPath {
		return false
	}
	for _, prefix := range skipPrefixes {
		if strings.HasPrefix(name, prefix) {
			return true
		}
	}
	return false
}

// makeWalkFunc returns a filepath.WalkFunc that collects matching files.
func makeWalkFunc(results *[]string, opts FindFilesOptions, rootPath string) filepath.WalkFunc {
	return func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		name := info.Name()

		// Skip hidden files/directories
		if skip, skipErr := shouldSkipHidden(name, path, rootPath, info, opts.SkipHidden); skip {
			return skipErr
		}

		// Check directory skip prefixes
		if info.IsDir() {
			if shouldSkipDir(name, path, rootPath, opts.SkipPrefixes) {
				return filepath.SkipDir
			}
			return nil
		}

		// Check file
		if fileMatches(name, info, opts) {
			*results = append(*results, path)
		}

		return nil
	}
}

// fileMatches checks if a file name meets the extension and size criteria.
func fileMatches(name string, info os.FileInfo, opts FindFilesOptions) bool {
	if !strings.EqualFold(filepath.Ext(name), opts.Extension) {
		return false
	}
	if info.Size() < opts.MinSize {
		return false
	}
	return true
}

// scanFlatDir scans a single directory level for matching files.
func scanFlatDir(rootPath string, results *[]string, opts FindFilesOptions) error {
	entries, err := os.ReadDir(rootPath)
	if err != nil {
		return err
	}

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		name := entry.Name()
		if opts.SkipHidden && strings.HasPrefix(name, ".") {
			continue
		}
		if !strings.EqualFold(filepath.Ext(name), opts.Extension) {
			continue
		}
		path := filepath.Join(rootPath, name)
		if info, err := os.Stat(path); err == nil && info.Size() >= opts.MinSize {
			*results = append(*results, path)
		}
	}
	return nil
}