package disk import ( "errors" "os" "path/filepath" "strings" "syscall" "github.com/google/uuid" ) type DiskState string const ( DiskAbsent DiskState = "absent" DiskForeign DiskState = "foreign" DiskKnown DiskState = "known" ) type DiskInfo struct { State DiskState `json:"state"` DiskID string `json:"disk_id"` TotalBytes int64 `json:"total_bytes"` FreeBytes int64 `json:"free_bytes"` MountPath string `json:"mount_path"` } const MarkerDir = ".jukebox" const idFile = "disk.id" func Probe(mountPath string) (DiskInfo, error) { info := DiskInfo{MountPath: mountPath, State: DiskAbsent} if _, err := os.ReadDir(mountPath); err != nil { return info, nil } total, free, err := DiskUsage(mountPath) if err != nil { return info, nil } info.TotalBytes = total info.FreeBytes = free idPath := filepath.Join(mountPath, MarkerDir, idFile) data, err := os.ReadFile(idPath) if errors.Is(err, os.ErrNotExist) { info.State = DiskForeign return info, nil } if err != nil { info.State = DiskForeign return info, nil } info.DiskID = strings.TrimSpace(string(data)) info.State = DiskKnown return info, nil } func IsMountPoint(path string) bool { pathInfo, err := os.Stat(path) if err != nil { return false } parent := filepath.Dir(filepath.Clean(path)) parentInfo, err := os.Stat(parent) if err != nil { return false } pathStat, ok := pathInfo.Sys().(*syscall.Stat_t) if !ok { return false } parentStat, ok := parentInfo.Sys().(*syscall.Stat_t) if !ok { return false } return pathStat.Dev != parentStat.Dev } func CheckWritable(path string) error { f, err := os.CreateTemp(path, ".jukebox-writecheck-*") if err != nil { return err } name := f.Name() if err := f.Close(); err != nil { _ = os.Remove(name) return err } return os.Remove(name) } func InitDisk(mountPath string) (string, error) { dir := filepath.Join(mountPath, MarkerDir) if err := os.MkdirAll(dir, 0o755); err != nil { return "", err } id := uuid.New().String() idPath := filepath.Join(dir, idFile) if err := os.WriteFile(idPath, []byte(id), 0o644); err != nil { return "", err } return id, nil } func DBPath(mountPath string) string { return filepath.Join(mountPath, MarkerDir, "history.db") } func DiskUsage(mountPath string) (total, free int64, err error) { var stat syscall.Statfs_t if err = syscall.Statfs(mountPath, &stat); err != nil { return 0, 0, err } total = int64(stat.Blocks) * int64(stat.Bsize) free = int64(stat.Bavail) * int64(stat.Bsize) return total, free, nil }