feat: improve daemon resilience, streaming, and usenet downloads
- Add daemon state persistence and stale resume file cleanup - Add TriggerPoll for WebSocket resume actions - Improve stream server with graceful shutdown and connection tracking - Add desktop notifications for download completion - Add media file organization with Movies/TV Shows detection - Improve usenet downloader with progress tracking and resume support - Add self-update package with GitHub release verification - Downgrade tablewriter to v0.0.5 (v1.x API breaking change)
This commit is contained in:
parent
e332c0a6e4
commit
197e33956a
24 changed files with 2310 additions and 84 deletions
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
|
||||
"github.com/torrentclaw/torrentclaw-cli/internal/config"
|
||||
"github.com/torrentclaw/torrentclaw-cli/internal/engine"
|
||||
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/download"
|
||||
"github.com/torrentclaw/torrentclaw-cli/internal/upgrade"
|
||||
)
|
||||
|
||||
|
|
@ -117,6 +118,12 @@ func runDaemonStart() error {
|
|||
return fmt.Errorf("create download dir: %w", err)
|
||||
}
|
||||
|
||||
// Clean up stale resume files (>7 days old)
|
||||
resumeDir := filepath.Join(config.DataDir(), "resume")
|
||||
if removed := download.CleanStaleFiles(resumeDir, 7*24*time.Hour); removed > 0 {
|
||||
log.Printf("Cleaned %d stale resume file(s)", removed)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
bold.Println(" unarr Daemon")
|
||||
fmt.Println()
|
||||
|
|
@ -314,7 +321,8 @@ func runDaemonStart() error {
|
|||
manager.PauseTask(taskID)
|
||||
cancelStreamTask(taskID)
|
||||
case "resume":
|
||||
log.Printf("[%s] resume requested via WebSocket", taskID[:8])
|
||||
log.Printf("[%s] resume requested via WebSocket, triggering poll", taskID[:8])
|
||||
d.TriggerPoll()
|
||||
case "stream":
|
||||
// Use registry mutex to prevent TOCTOU race with HTTP-polled stream requests
|
||||
streamRegistry.mu.Lock()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package cmd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -125,13 +127,29 @@ func handleStreamTask(parentCtx context.Context, at agent.Task, reporter *engine
|
|||
Seeds: p.Seeds,
|
||||
FileName: p.FileName,
|
||||
})
|
||||
|
||||
// Terminal progress
|
||||
if p.TotalBytes > 0 {
|
||||
pct := int(float64(p.DownloadedBytes) / float64(p.TotalBytes) * 100)
|
||||
fmt.Fprintf(os.Stderr, "\r[%s] %d%% — %s/%s @ %s/s peers:%d seeds:%d",
|
||||
at.ID[:8], pct,
|
||||
ui.FormatBytes(p.DownloadedBytes), ui.FormatBytes(p.TotalBytes), ui.FormatBytes(p.SpeedBps),
|
||||
p.Peers, p.Seeds)
|
||||
}
|
||||
|
||||
if p.DownloadedBytes >= p.TotalBytes && p.TotalBytes > 0 {
|
||||
fmt.Fprint(os.Stderr, "\r\033[2K") // clear progress line
|
||||
task.Transition(engine.StatusCompleted)
|
||||
log.Printf("[%s] stream download complete, server stays up until cancelled", at.ID[:8])
|
||||
// Don't return — keep HTTP server running so the player
|
||||
// can finish reading. The stream stops when the user
|
||||
// cancels from the web or the daemon shuts down.
|
||||
<-ctx.Done()
|
||||
log.Printf("[%s] stream download complete, server stays up for 30m or until cancelled", at.ID[:8])
|
||||
// Keep HTTP server running so the player can finish reading.
|
||||
// Auto-shutdown after 30 minutes of idle to prevent resource leaks.
|
||||
idleTimer := time.NewTimer(30 * time.Minute)
|
||||
defer idleTimer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-idleTimer.C:
|
||||
log.Printf("[%s] stream idle timeout (30m), shutting down", at.ID[:8])
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue