better error logging

This commit is contained in:
2024-04-22 10:03:16 +02:00
parent a73b8d362e
commit 3da81affb3
5 changed files with 83 additions and 42 deletions

View File

@@ -201,16 +201,20 @@ func DownloadFile(w http.ResponseWriter, r *http.Request) {
func BulkDownload(mdb *internal.MemoryDB) http.HandlerFunc { func BulkDownload(mdb *internal.MemoryDB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
procs := slices.DeleteFunc(*mdb.All(), func(e internal.ProcessResponse) bool { ps := slices.DeleteFunc(*mdb.All(), func(e internal.ProcessResponse) bool {
return e.Progress.Status != 2 // status completed return e.Progress.Status != internal.StatusCompleted
}) })
if len(ps) == 0 {
return
}
var ( var (
buff bytes.Buffer buff bytes.Buffer
zipWriter = zip.NewWriter(&buff) zipWriter = zip.NewWriter(&buff)
) )
for _, p := range procs { for _, p := range ps {
wr, err := zipWriter.Create(filepath.Base(p.Output.SavedFilePath)) wr, err := zipWriter.Create(filepath.Base(p.Output.SavedFilePath))
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)

View File

@@ -2,6 +2,19 @@ package internal
import "time" import "time"
type ProgressTemplate struct {
Percentage string `json:"percentage"`
Speed float32 `json:"speed"`
Size string `json:"size"`
Eta float32 `json:"eta"`
}
type DownloadOutput struct {
Path string
Filename string
SavedFilePath string `json:"savedFilePath"`
}
// Progress for the Running call // Progress for the Running call
type DownloadProgress struct { type DownloadProgress struct {
Status int `json:"process_status"` Status int `json:"process_status"`

View File

@@ -1,19 +1,22 @@
package internal package internal
import ( import (
"log/slog"
"github.com/marcopeocchi/yt-dlp-web-ui/server/config" "github.com/marcopeocchi/yt-dlp-web-ui/server/config"
) )
type MessageQueue struct { type MessageQueue struct {
producerCh chan *Process producerCh chan *Process
consumerCh chan struct{} consumerCh chan struct{}
logger *slog.Logger
} }
// Creates a new message queue. // Creates a new message queue.
// By default it will be created with a size equals to nthe number of logical // By default it will be created with a size equals to nthe number of logical
// CPU cores. // CPU cores.
// The queue size can be set via the qs flag. // The queue size can be set via the qs flag.
func NewMessageQueue() *MessageQueue { func NewMessageQueue(logger *slog.Logger) *MessageQueue {
size := config.Instance().QueueSize size := config.Instance().QueueSize
if size <= 0 { if size <= 0 {
@@ -23,13 +26,20 @@ func NewMessageQueue() *MessageQueue {
return &MessageQueue{ return &MessageQueue{
producerCh: make(chan *Process, size), producerCh: make(chan *Process, size),
consumerCh: make(chan struct{}, size), consumerCh: make(chan struct{}, size),
logger: logger,
} }
} }
// Publish a message to the queue and set the task to a peding state. // Publish a message to the queue and set the task to a peding state.
func (m *MessageQueue) Publish(p *Process) { func (m *MessageQueue) Publish(p *Process) {
p.SetPending() p.SetPending()
go p.SetMetadata() go func() {
err := p.SetMetadata()
m.logger.Error(
"failed to retrieve metadata",
slog.String("err", err.Error()),
)
}()
m.producerCh <- p m.producerCh <- p
} }

View File

@@ -2,8 +2,11 @@ package internal
import ( import (
"bufio" "bufio"
"bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io"
"log/slog" "log/slog"
"regexp" "regexp"
"slices" "slices"
@@ -35,13 +38,6 @@ const (
StatusErrored StatusErrored
) )
type ProgressTemplate struct {
Percentage string `json:"percentage"`
Speed float32 `json:"speed"`
Size string `json:"size"`
Eta float32 `json:"eta"`
}
// Process descriptor // Process descriptor
type Process struct { type Process struct {
Id string Id string
@@ -54,12 +50,6 @@ type Process struct {
Logger *slog.Logger Logger *slog.Logger
} }
type DownloadOutput struct {
Path string
Filename string
SavedFilePath string `json:"savedFilePath"`
}
// Starts spawns/forks a new yt-dlp process and parse its stdout. // Starts spawns/forks a new yt-dlp process and parse its stdout.
// The process is spawned to outputting a custom progress text that // The process is spawned to outputting a custom progress text that
// Resembles a JSON Object in order to Unmarshal it later. // Resembles a JSON Object in order to Unmarshal it later.
@@ -123,7 +113,6 @@ func (p *Process) Start() {
) )
panic(err) panic(err)
} }
scan := bufio.NewScanner(r)
err = cmd.Start() err = cmd.Start()
if err != nil { if err != nil {
@@ -145,6 +134,8 @@ func (p *Process) Start() {
// spawn a goroutine that does the dirty job of parsing the stdout // spawn a goroutine that does the dirty job of parsing the stdout
// filling the channel with as many stdout line as yt-dlp produces (producer) // filling the channel with as many stdout line as yt-dlp produces (producer)
go func() { go func() {
scan := bufio.NewScanner(r)
defer func() { defer func() {
r.Close() r.Close()
p.Complete() p.Complete()
@@ -161,21 +152,24 @@ func (p *Process) Start() {
// Slows down the unmarshal operation to every 500ms // Slows down the unmarshal operation to every 500ms
go func() { go func() {
rx.Sample(time.Millisecond*500, sourceChan, doneChan, func(event []byte) { rx.Sample(time.Millisecond*500, sourceChan, doneChan, func(event []byte) {
stdout := ProgressTemplate{} var progress ProgressTemplate
err := json.Unmarshal(event, &stdout)
if err == nil { if err := json.Unmarshal(event, &progress); err != nil {
return
}
p.Progress = DownloadProgress{ p.Progress = DownloadProgress{
Status: StatusDownloading, Status: StatusDownloading,
Percentage: stdout.Percentage, Percentage: progress.Percentage,
Speed: stdout.Speed, Speed: progress.Speed,
ETA: stdout.Eta, ETA: progress.Eta,
} }
p.Logger.Info("progress", p.Logger.Info("progress",
slog.String("id", p.getShortId()), slog.String("id", p.getShortId()),
slog.String("url", p.Url), slog.String("url", p.Url),
slog.String("percentege", stdout.Percentage), slog.String("percentege", progress.Percentage),
) )
}
}) })
}() }()
@@ -223,9 +217,13 @@ func (p *Process) Kill() error {
// Returns the available format for this URL // Returns the available format for this URL
func (p *Process) GetFormatsSync() (DownloadFormats, error) { func (p *Process) GetFormatsSync() (DownloadFormats, error) {
cmd := exec.Command(config.Instance().DownloaderPath, p.Url, "-J") cmd := exec.Command(config.Instance().DownloaderPath, p.Url, "-J")
stdout, err := cmd.Output()
stdout, err := cmd.Output()
if err != nil { if err != nil {
p.Logger.Error(
"failed to retrieve metadata",
slog.String("err", err.Error()),
)
return DownloadFormats{}, err return DownloadFormats{}, err
} }
@@ -306,7 +304,17 @@ func (p *Process) SetMetadata() error {
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
p.Logger.Error("failed retrieving info", p.Logger.Error("failed to connect to stdout",
slog.String("id", p.getShortId()),
slog.String("url", p.Url),
slog.String("err", err.Error()),
)
return err
}
stderr, err := cmd.StderrPipe()
if err != nil {
p.Logger.Error("failed to connect to stderr",
slog.String("id", p.getShortId()), slog.String("id", p.getShortId()),
slog.String("url", p.Url), slog.String("url", p.Url),
slog.String("err", err.Error()), slog.String("err", err.Error()),
@@ -319,27 +327,33 @@ func (p *Process) SetMetadata() error {
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
err = cmd.Start() if err := cmd.Start(); err != nil {
if err != nil {
return err return err
} }
var bufferedStderr bytes.Buffer
go func() {
io.Copy(&bufferedStderr, stderr)
}()
p.Logger.Info("retrieving metadata", p.Logger.Info("retrieving metadata",
slog.String("id", p.getShortId()), slog.String("id", p.getShortId()),
slog.String("url", p.Url), slog.String("url", p.Url),
) )
err = json.NewDecoder(stdout).Decode(&info) if err := json.NewDecoder(stdout).Decode(&info); err != nil {
if err != nil {
return err return err
} }
p.Info = info p.Info = info
p.Progress.Status = StatusPending p.Progress.Status = StatusPending
err = cmd.Wait() if err := cmd.Wait(); err != nil {
return errors.New(bufferedStderr.String())
}
return err return nil
} }
func (p *Process) getShortId() string { func (p *Process) getShortId() string {

View File

@@ -89,7 +89,7 @@ func RunBlocking(cfg *RunConfig) {
logger.Error("failed to init database", slog.String("err", err.Error())) logger.Error("failed to init database", slog.String("err", err.Error()))
} }
mq := internal.NewMessageQueue() mq := internal.NewMessageQueue(logger)
go mq.Subscriber() go mq.Subscriber()
srv := newServer(serverConfig{ srv := newServer(serverConfig{