feat: add user-scoped WebSocket broadcasting for scan progress

- Add UserID field to Job struct for tracking job ownership
- Broadcast scan progress updates to user's WebSocket connections
- Send real-time updates during scanning (progress, files scanned, new items, errors)

This allows the frontend to display live scan progress without HTTP polling.
Scanner now associates scan jobs with requesting user for targeted updates.
This commit is contained in:
2026-03-05 17:13:21 -05:00
parent ff480129a3
commit 40f303b004
2 changed files with 24 additions and 3 deletions
+3 -2
View File
@@ -86,8 +86,9 @@ func (h *Handler) ScanLibrary(c echo.Context) error {
jobID := uuid.New().String()
job := &services.Job{
ID: jobID,
Type: services.JobTypeScan,
ID: jobID,
Type: services.JobTypeScan,
UserID: userID,
Params: map[string]interface{}{
"library_id": req.LibraryID,
"folders": folderPaths,
+21 -1
View File
@@ -2,6 +2,7 @@ package services
import (
"bookhoard/internal/database"
wsync "bookhoard/internal/sync"
"context"
"fmt"
"net/http"
@@ -53,6 +54,7 @@ func (w *Worker) Enqueue(job *Job) {
type Job struct {
ID string
Type JobType
UserID string
Params map[string]interface{}
Status JobStatus
CreatedAt time.Time
@@ -84,6 +86,7 @@ type JobResult struct {
type Worker struct {
jobQueue chan *Job
results map[string]*JobResult
connManager *wsync.ConnectionManager
mu sync.RWMutex
wg sync.WaitGroup
ctx context.Context
@@ -91,7 +94,7 @@ type Worker struct {
shuttingDown atomic.Bool
}
func NewWorker(numWorkers int) *Worker {
func NewWorker(numWorkers int, connManager *wsync.ConnectionManager) *Worker {
ctx, cancel := context.WithCancel(context.Background())
w := &Worker{
@@ -106,6 +109,8 @@ func NewWorker(numWorkers int) *Worker {
go w.worker()
}
w.connManager = connManager
return w
}
@@ -249,6 +254,21 @@ func (w *Worker) processScanJob(job *Job) (interface{}, error) {
result.NewItems = newItems
result.Errors = errors
}
// Broadcast via WebSocket to user
if w.connManager != nil && job.UserID != "" {
msg := wsync.BroadcastMessage{
Type: wsync.MessageTypeScanProgress,
Data: map[string]interface{}{
"job_id": job.ID,
"progress": progress,
"files_scanned": filesScanned,
"new_items": newItems,
"errors": errors,
},
}
w.connManager.BroadcastToUser(job.UserID, msg)
}
}
if err := scanner.SetFolders(folders); err != nil {