144 lines
2.6 KiB
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
|
|
}
|