Cell canvas renderer with frame cache and a perceptual 256-colour quantizer; responsive geometry (large/medium/small/too-small); squarified treemap with nested previews, LOD grouping, category colouring and adaptive labels; spatial arrow navigation with stable NodeID selection; zoom and breadcrumbs; modal system (info, help, confirm, save); list, file-type, top-N, scan-info, duplicate and comparison views; live search/filter; mouse support; move-to-trash.
73 lines
1.6 KiB
Go
73 lines
1.6 KiB
Go
package platform
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
)
|
|
|
|
// TrashName is the user-facing name of the trash.
|
|
const TrashName = "Trash"
|
|
|
|
// Reveal selects the item in Finder.
|
|
func Reveal(path string) error {
|
|
if err := checkAbs(path); err != nil {
|
|
return err
|
|
}
|
|
return startDetached("open", "-R", path)
|
|
}
|
|
|
|
// Trash moves path into ~/.Trash (same volume) or the volume's
|
|
// .Trashes/<uid> directory. It never copies and never deletes.
|
|
func Trash(path string) error {
|
|
if err := checkAbs(path); err != nil {
|
|
return err
|
|
}
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dirs := []string{filepath.Join(home, ".Trash")}
|
|
if vol := volumeOf(path); vol != "" {
|
|
dirs = append(dirs, filepath.Join(vol, ".Trashes", fmt.Sprint(os.Getuid())))
|
|
}
|
|
base := filepath.Base(path)
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
|
continue
|
|
}
|
|
for i := 1; i < 10000; i++ {
|
|
name := base
|
|
if i > 1 {
|
|
name = fmt.Sprintf("%s %d", base, i)
|
|
}
|
|
dst := filepath.Join(dir, name)
|
|
if _, err := os.Lstat(dst); err == nil {
|
|
continue
|
|
}
|
|
err := os.Rename(path, dst)
|
|
if errors.Is(err, syscall.EXDEV) {
|
|
break // try the next trash location
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
return errors.New("trash: item is on a volume without a usable trash")
|
|
}
|
|
|
|
func volumeOf(path string) string {
|
|
const prefix = "/Volumes/"
|
|
if len(path) > len(prefix) && path[:len(prefix)] == prefix {
|
|
rest := path[len(prefix):]
|
|
for i := 0; i < len(rest); i++ {
|
|
if rest[i] == '/' {
|
|
return prefix + rest[:i]
|
|
}
|
|
}
|
|
return path
|
|
}
|
|
return ""
|
|
}
|