Files
jukebox_maker/internal/watcher/watcher.go

144 lines
2.6 KiB
Go

package watcher
import (
"context"
"os"
"path/filepath"
"sort"
"sync"
"time"
"jukebox_maker/internal/disk"
)
type DiskEvent struct {
Info disk.DiskInfo
Prev disk.DiskInfo
}
type Handler func(event DiskEvent)
type Watcher struct {
mountPath string
interval time.Duration
handler Handler
mu sync.RWMutex
disks map[string]disk.DiskInfo
}
func New(mountPath string, interval time.Duration, handler Handler) *Watcher {
return &Watcher{
mountPath: mountPath,
interval: interval,
handler: handler,
disks: make(map[string]disk.DiskInfo),
}
}
func (w *Watcher) ListDisks() []disk.DiskInfo {
w.mu.RLock()
defer w.mu.RUnlock()
items := make([]disk.DiskInfo, 0, len(w.disks))
for _, info := range w.disks {
items = append(items, info)
}
sort.Slice(items, func(i, j int) bool { return items[i].MountPath < items[j].MountPath })
return items
}
func (w *Watcher) DiskByMountPath(mountPath string) (disk.DiskInfo, bool) {
w.mu.RLock()
defer w.mu.RUnlock()
info, ok := w.disks[mountPath]
return info, ok
}
func (w *Watcher) DiskByID(diskID string) (disk.DiskInfo, bool) {
w.mu.RLock()
defer w.mu.RUnlock()
for _, info := range w.disks {
if info.DiskID == diskID {
return info, true
}
}
return disk.DiskInfo{}, false
}
func (w *Watcher) ProbeNow() {
w.probe()
}
func (w *Watcher) Run(ctx context.Context) {
ticker := time.NewTicker(w.interval)
defer ticker.Stop()
// probe immediately on start
w.probe()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
w.probe()
}
}
}
func (w *Watcher) probe() {
next := discoverDisks(w.mountPath)
w.mu.Lock()
prev := w.disks
w.disks = next
w.mu.Unlock()
if w.handler == nil {
return
}
seen := make(map[string]struct{}, len(prev)+len(next))
for mountPath, info := range next {
seen[mountPath] = struct{}{}
prevInfo := prev[mountPath]
if prevInfo.State != info.State || prevInfo.DiskID != info.DiskID {
w.handler(DiskEvent{Info: info, Prev: prevInfo})
}
}
for mountPath, prevInfo := range prev {
if _, ok := seen[mountPath]; ok {
continue
}
w.handler(DiskEvent{
Info: disk.DiskInfo{
State: disk.DiskAbsent,
MountPath: mountPath,
},
Prev: prevInfo,
})
}
}
func discoverDisks(root string) map[string]disk.DiskInfo {
entries, err := os.ReadDir(root)
if err != nil {
return map[string]disk.DiskInfo{}
}
disks := make(map[string]disk.DiskInfo)
for _, entry := range entries {
if !entry.IsDir() {
continue
}
mountPath := filepath.Join(root, entry.Name())
info, _ := disk.Probe(mountPath)
if info.State == disk.DiskAbsent {
continue
}
disks[mountPath] = info
}
return disks
}