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:
Deivid Soto 2026-03-28 21:36:12 +01:00
parent e332c0a6e4
commit 197e33956a
24 changed files with 2310 additions and 84 deletions

View file

@ -146,13 +146,28 @@ func (d *TorrentDownloader) Download(ctx context.Context, task *Task, outputDir
}
// 4. Determine file path
// For multi-file torrents, fileName includes the torrent dir prefix (e.g. "TorrentName/file.mkv").
// Try the full path first, then just the file inside the torrent dir.
filePath := filepath.Join(d.cfg.DataDir, fileName)
if _, statErr := os.Stat(filePath); statErr != nil {
filePath = filepath.Join(d.cfg.DataDir, t.Name())
// File might have been moved — try torrent directory
dirPath := filepath.Join(d.cfg.DataDir, t.Name())
if fi, statErr2 := os.Stat(dirPath); statErr2 == nil && fi.IsDir() {
// Look for the actual file inside the directory
base := filepath.Base(fileName)
candidate := filepath.Join(dirPath, base)
if _, statErr3 := os.Stat(candidate); statErr3 == nil {
filePath = candidate
} else {
filePath = dirPath
}
} else {
filePath = dirPath
}
}
result.FilePath = filePath
result.FileName = fileName
result.FileName = filepath.Base(fileName)
result.Method = MethodTorrent
result.Size = totalBytes
@ -211,6 +226,13 @@ func (d *TorrentDownloader) pollDownload(ctx context.Context, t *torrent.Torrent
// Peer stats
stats := t.Stats()
// Terminal progress
pct := int(float64(downloaded) / float64(totalBytes) * 100)
fmt.Fprintf(os.Stderr, "\r[%s] %d%% — %s/%s @ %s/s peers:%d seeds:%d",
task.ID[:8], pct,
formatBytes(downloaded), formatBytes(totalBytes), formatBytes(speed),
stats.ActivePeers, stats.ConnectedSeeders)
// Report progress
p := Progress{
DownloadedBytes: downloaded,
@ -230,6 +252,7 @@ func (d *TorrentDownloader) pollDownload(ctx context.Context, t *torrent.Torrent
// Check completion
if downloaded >= totalBytes {
fmt.Fprint(os.Stderr, "\r\033[2K") // clear progress line
log.Printf("[%s] download complete: %s", task.ID[:8], fileName)
return &Result{}, nil
}