package detect import ( "path/filepath" "strings" "github.com/shirou/gopsutil/v3/disk" ) // SDCard represents a detected removable storage device type SDCard struct { Mountpoint string `json:"mountpoint"` Label string `json:"label"` Device string `json:"device"` Fstype string `json:"fstype"` TotalBytes uint64 `json:"totalBytes"` FreeBytes uint64 `json:"freeBytes"` MediaFiles []string `json:"mediaFiles"` } // mediaExtensions is the set of file extensions we care about var mediaExtensions = map[string]bool{ ".mp4": true, ".mov": true, ".jpg": true, ".jpeg": true, ".png": true, ".raw": true, ".gpr": true, ".360": true, ".lrv": true, ".thm": true, } // Detect returns all removable partitions that look like SD cards / media func Detect() []SDCard { partitions, err := disk.Partitions(true) if err != nil { return nil } var cards []SDCard for _, p := range partitions { if !isRemovable(p) { continue } usage, err := disk.Usage(p.Mountpoint) if err != nil { continue } card := SDCard{ Mountpoint: p.Mountpoint, Label: filepath.Base(p.Mountpoint), Device: p.Device, Fstype: p.Fstype, TotalBytes: usage.Total, FreeBytes: usage.Free, } cards = append(cards, card) } return cards } // isRemovable heuristic: opts contain "removable" or path looks like /media/ func isRemovable(p disk.PartitionStat) bool { opts := strings.Join(p.Opts, ",") if strings.Contains(opts, "removable") { return true } mp := p.Mountpoint if strings.HasPrefix(mp, "/media/") || strings.HasPrefix(mp, "/run/media/") || strings.HasPrefix(mp, "/Volumes/") || strings.HasPrefix(mp, "/mnt/sd") { return true } return false } // HasMediaExtension returns true if path has a known media extension func HasMediaExtension(path string) bool { ext := strings.ToLower(filepath.Ext(path)) return mediaExtensions[ext] }