mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 17:55:08 +00:00
All files in cmd/analyze/ are macOS-only but several were missing the //go:build darwin constraint. On Linux (e.g. CodeQL CI), Go compiled these files without the types defined in main.go (which had the tag), causing undefined symbol errors for dirEntry, fileEntry, scanResult, cacheEntry, historyEntry and model. - Add //go:build darwin to heap.go, cache.go, scanner.go, cleanable.go, delete.go, format.go, constants.go and all *_test.go files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
149 lines
3.4 KiB
Go
149 lines
3.4 KiB
Go
//go:build darwin
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
const trashTimeout = 30 * time.Second
|
|
|
|
func deletePathCmd(path string, counter *int64) tea.Cmd {
|
|
return func() tea.Msg {
|
|
count, err := trashPathWithProgress(path, counter)
|
|
return deleteProgressMsg{
|
|
done: true,
|
|
err: err,
|
|
count: count,
|
|
path: path,
|
|
}
|
|
}
|
|
}
|
|
|
|
// deleteMultiplePathsCmd moves paths to Trash and aggregates results.
|
|
func deleteMultiplePathsCmd(paths []string, counter *int64) tea.Cmd {
|
|
return func() tea.Msg {
|
|
var totalCount int64
|
|
var errors []string
|
|
|
|
// Process deeper paths first to avoid parent/child conflicts.
|
|
pathsToDelete := append([]string(nil), paths...)
|
|
sort.Slice(pathsToDelete, func(i, j int) bool {
|
|
return strings.Count(pathsToDelete[i], string(filepath.Separator)) > strings.Count(pathsToDelete[j], string(filepath.Separator))
|
|
})
|
|
|
|
for _, path := range pathsToDelete {
|
|
count, err := trashPathWithProgress(path, counter)
|
|
totalCount += count
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
errors = append(errors, err.Error())
|
|
}
|
|
}
|
|
|
|
var resultErr error
|
|
if len(errors) > 0 {
|
|
resultErr = &multiDeleteError{errors: errors}
|
|
}
|
|
|
|
return deleteProgressMsg{
|
|
done: true,
|
|
err: resultErr,
|
|
count: totalCount,
|
|
path: "",
|
|
}
|
|
}
|
|
}
|
|
|
|
// multiDeleteError holds multiple deletion errors.
|
|
type multiDeleteError struct {
|
|
errors []string
|
|
}
|
|
|
|
func (e *multiDeleteError) Error() string {
|
|
if len(e.errors) == 1 {
|
|
return e.errors[0]
|
|
}
|
|
return strings.Join(e.errors[:min(3, len(e.errors))], "; ")
|
|
}
|
|
|
|
// trashPathWithProgress moves a path to Trash using Finder.
|
|
// This allows users to recover accidentally deleted files.
|
|
func trashPathWithProgress(root string, counter *int64) (int64, error) {
|
|
// Verify path exists (use Lstat to handle broken symlinks).
|
|
info, err := os.Lstat(root)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Count items for progress reporting.
|
|
var count int64
|
|
if info.IsDir() {
|
|
_ = filepath.WalkDir(root, func(_ string, d os.DirEntry, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if !d.IsDir() {
|
|
count++
|
|
if counter != nil {
|
|
atomic.StoreInt64(counter, count)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
} else {
|
|
count = 1
|
|
if counter != nil {
|
|
atomic.StoreInt64(counter, 1)
|
|
}
|
|
}
|
|
|
|
// Move to Trash using Finder AppleScript.
|
|
if err := moveToTrash(root); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// moveToTrash uses macOS Finder to move a file/directory to Trash.
|
|
// This is the safest method as it uses the system's native trash mechanism.
|
|
func moveToTrash(path string) error {
|
|
absPath, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to resolve path: %w", err)
|
|
}
|
|
|
|
// Escape path for AppleScript (handle quotes and backslashes).
|
|
escapedPath := strings.ReplaceAll(absPath, "\\", "\\\\")
|
|
escapedPath = strings.ReplaceAll(escapedPath, "\"", "\\\"")
|
|
|
|
script := fmt.Sprintf(`tell application "Finder" to delete POSIX file "%s"`, escapedPath)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), trashTimeout)
|
|
defer cancel()
|
|
|
|
cmd := exec.CommandContext(ctx, "osascript", "-e", script)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return fmt.Errorf("timeout moving to Trash")
|
|
}
|
|
return fmt.Errorf("failed to move to Trash: %s", strings.TrimSpace(string(output)))
|
|
}
|
|
|
|
return nil
|
|
}
|