270 lines
5.6 KiB
Go
270 lines
5.6 KiB
Go
package mounts
|
|
|
|
import (
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// ScanFunc is a function type for scanning a mount path
|
|
type ScanFunc func(path string) (int, error)
|
|
|
|
// mountPoller manages polling for a single mount
|
|
type mountPoller struct {
|
|
mountID string
|
|
stopChan chan struct{}
|
|
stopped bool
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// PollingStatus holds the polling state for a mount
|
|
type PollingStatus struct {
|
|
Active bool `json:"active"`
|
|
LastScan int64 `json:"lastScan,omitempty"`
|
|
}
|
|
|
|
// Poller manages per-mount polling goroutines
|
|
type Poller struct {
|
|
manager *Manager
|
|
scanFunc ScanFunc
|
|
|
|
pollers map[string]*mountPoller // key: mount ID
|
|
status map[string]*PollingStatus
|
|
|
|
running bool
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewPoller creates a new mount poller
|
|
func NewPoller(manager *Manager, scanFunc ScanFunc) *Poller {
|
|
return &Poller{
|
|
manager: manager,
|
|
scanFunc: scanFunc,
|
|
pollers: make(map[string]*mountPoller),
|
|
status: make(map[string]*PollingStatus),
|
|
}
|
|
}
|
|
|
|
// Start begins the polling service
|
|
func (p *Poller) Start() error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.running {
|
|
return nil
|
|
}
|
|
p.running = true
|
|
|
|
log.Println("[Poller] Starting mount polling service")
|
|
|
|
// Initial sync with mount configurations
|
|
go p.syncWithMounts()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop stops all polling goroutines
|
|
func (p *Poller) Stop() error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if !p.running {
|
|
return nil
|
|
}
|
|
p.running = false
|
|
|
|
log.Println("[Poller] Stopping mount polling service")
|
|
|
|
// Stop all pollers
|
|
for id, poller := range p.pollers {
|
|
p.stopPoller(poller)
|
|
delete(p.pollers, id)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SyncWithMounts synchronizes pollers with current mount configurations
|
|
func (p *Poller) SyncWithMounts() {
|
|
p.syncWithMounts()
|
|
}
|
|
|
|
// syncWithMounts is the internal implementation
|
|
func (p *Poller) syncWithMounts() {
|
|
p.mu.RLock()
|
|
running := p.running
|
|
p.mu.RUnlock()
|
|
|
|
if !running {
|
|
return
|
|
}
|
|
|
|
// Get current mounts BEFORE acquiring the write lock to avoid deadlock
|
|
// (ListMounts calls GetPollingStatus which needs RLock)
|
|
mounts, err := p.manager.listMountsWithoutPollingStatus()
|
|
if err != nil {
|
|
log.Printf("[Poller] Failed to list mounts: %v", err)
|
|
return
|
|
}
|
|
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
// Re-check running state after acquiring lock
|
|
if !p.running {
|
|
return
|
|
}
|
|
|
|
// Track which mounts we've seen
|
|
seenMounts := make(map[string]bool)
|
|
|
|
for _, mount := range mounts {
|
|
seenMounts[mount.ID] = true
|
|
|
|
// Check if mount should be polled
|
|
shouldPoll := mount.PollingEnabled && mount.Mounted
|
|
|
|
existingPoller, exists := p.pollers[mount.ID]
|
|
|
|
if shouldPoll {
|
|
// Calculate interval
|
|
interval := mount.PollingIntervalMs
|
|
if interval < MinPollingIntervalMs {
|
|
interval = DefaultPollingIntervalMs
|
|
}
|
|
|
|
if !exists {
|
|
// Start new poller
|
|
log.Printf("[Poller] Starting poller for mount %s (interval: %dms)", mount.Name, interval)
|
|
p.startPollerForMount(mount.ID, mount.MountPath, interval)
|
|
}
|
|
} else if exists {
|
|
// Stop existing poller
|
|
log.Printf("[Poller] Stopping poller for mount %s", mount.Name)
|
|
p.stopPoller(existingPoller)
|
|
delete(p.pollers, mount.ID)
|
|
}
|
|
}
|
|
|
|
// Stop pollers for mounts that no longer exist
|
|
for id, poller := range p.pollers {
|
|
if !seenMounts[id] {
|
|
log.Printf("[Poller] Stopping poller for removed mount %s", id)
|
|
p.stopPoller(poller)
|
|
delete(p.pollers, id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// startPollerForMount starts a polling goroutine for a mount
|
|
func (p *Poller) startPollerForMount(mountID, mountPath string, intervalMs int) {
|
|
poller := &mountPoller{
|
|
mountID: mountID,
|
|
stopChan: make(chan struct{}),
|
|
}
|
|
p.pollers[mountID] = poller
|
|
|
|
// Initialize status
|
|
if _, exists := p.status[mountID]; !exists {
|
|
p.status[mountID] = &PollingStatus{}
|
|
}
|
|
p.status[mountID].Active = true
|
|
|
|
go func() {
|
|
ticker := time.NewTicker(time.Duration(intervalMs) * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-poller.stopChan:
|
|
return
|
|
case <-ticker.C:
|
|
p.runPollScan(mountID, mountPath)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// stopPoller stops a mount poller
|
|
func (p *Poller) stopPoller(poller *mountPoller) {
|
|
poller.mu.Lock()
|
|
defer poller.mu.Unlock()
|
|
|
|
if poller.stopped {
|
|
return
|
|
}
|
|
poller.stopped = true
|
|
close(poller.stopChan)
|
|
|
|
// Update status
|
|
if status, exists := p.status[poller.mountID]; exists {
|
|
status.Active = false
|
|
}
|
|
}
|
|
|
|
// runPollScan executes a scan for a mount
|
|
func (p *Poller) runPollScan(mountID, mountPath string) {
|
|
if p.scanFunc == nil {
|
|
return
|
|
}
|
|
|
|
fileCount, err := p.scanFunc(mountPath)
|
|
if err != nil {
|
|
log.Printf("[Poller] Scan failed for mount %s: %v", mountID, err)
|
|
return
|
|
}
|
|
|
|
// Update status
|
|
p.mu.Lock()
|
|
if status, exists := p.status[mountID]; exists {
|
|
status.LastScan = NowMS()
|
|
}
|
|
p.mu.Unlock()
|
|
|
|
log.Printf("[Poller] Poll scan complete for mount %s: %d files", mountID, fileCount)
|
|
}
|
|
|
|
// TriggerScan triggers an immediate scan for a mount
|
|
func (p *Poller) TriggerScan(mountID, mountPath string) (int, error) {
|
|
if p.scanFunc == nil {
|
|
return 0, nil
|
|
}
|
|
|
|
fileCount, err := p.scanFunc(mountPath)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Update status
|
|
p.mu.Lock()
|
|
if status, exists := p.status[mountID]; exists {
|
|
status.LastScan = NowMS()
|
|
} else {
|
|
p.status[mountID] = &PollingStatus{
|
|
LastScan: NowMS(),
|
|
}
|
|
}
|
|
p.mu.Unlock()
|
|
|
|
return fileCount, nil
|
|
}
|
|
|
|
// GetPollingStatus returns the polling status for a mount
|
|
func (p *Poller) GetPollingStatus(mountID string) *PollingStatus {
|
|
p.mu.RLock()
|
|
defer p.mu.RUnlock()
|
|
|
|
if status, exists := p.status[mountID]; exists {
|
|
return &PollingStatus{
|
|
Active: status.Active,
|
|
LastScan: status.LastScan,
|
|
}
|
|
}
|
|
return &PollingStatus{}
|
|
}
|
|
|
|
// NotifyMountChanged notifies the poller that a mount's state has changed
|
|
func (p *Poller) NotifyMountChanged() {
|
|
go p.syncWithMounts()
|
|
}
|