From f9e829dce62d3f773e118a5ba0df003d1751bccb Mon Sep 17 00:00:00 2001 From: marcobaobao Date: Thu, 19 Dec 2024 13:08:25 +0100 Subject: [PATCH] added status API endpoint --- server/formats/types.go | 2 +- server/internal/common_types.go | 8 +-- server/server.go | 4 ++ server/status/domain/status.go | 28 +++++++++++ server/status/repository/repository.go | 65 ++++++++++++++++++++++++ server/status/rest/handler.go | 38 ++++++++++++++ server/status/service/service.go | 69 ++++++++++++++++++++++++++ server/status/status.go | 21 ++++++++ 8 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 server/status/domain/status.go create mode 100644 server/status/repository/repository.go create mode 100644 server/status/rest/handler.go create mode 100644 server/status/service/service.go create mode 100644 server/status/status.go diff --git a/server/formats/types.go b/server/formats/types.go index 864d86d..4e1d61b 100644 --- a/server/formats/types.go +++ b/server/formats/types.go @@ -23,6 +23,6 @@ type Format struct { Resolution string `json:"resolution"` VCodec string `json:"vcodec"` ACodec string `json:"acodec"` - Size float32 `json:"filesize_approx"` + Size float64 `json:"filesize_approx"` Language string `json:"language"` } diff --git a/server/internal/common_types.go b/server/internal/common_types.go index f6b0b2a..17daacf 100644 --- a/server/internal/common_types.go +++ b/server/internal/common_types.go @@ -5,9 +5,9 @@ import "time" // Used to unmarshall yt-dlp progress type ProgressTemplate struct { Percentage string `json:"percentage"` - Speed float32 `json:"speed"` + Speed float64 `json:"speed"` Size string `json:"size"` - Eta float32 `json:"eta"` + Eta float64 `json:"eta"` } type PostprocessTemplate struct { @@ -25,8 +25,8 @@ type DownloadOutput struct { type DownloadProgress struct { Status int `json:"process_status"` Percentage string `json:"percentage"` - Speed float32 `json:"speed"` - ETA float32 `json:"eta"` + Speed float64 `json:"speed"` + ETA float64 `json:"eta"` } // Used to deser the yt-dlp -J output diff --git a/server/server.go b/server/server.go index d4ff6fe..dd9e711 100644 --- a/server/server.go +++ b/server/server.go @@ -31,6 +31,7 @@ import ( "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/openid" "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/rest" ytdlpRPC "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/rpc" + "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/status" _ "modernc.org/sqlite" ) @@ -221,6 +222,9 @@ func newServer(c serverConfig) *http.Server { // Logging r.Route("/log", logging.ApplyRouter(observableLogger)) + // Status + r.Route("/status", status.ApplyRouter(c.mdb)) + return &http.Server{Handler: r} } diff --git a/server/status/domain/status.go b/server/status/domain/status.go new file mode 100644 index 0000000..1258238 --- /dev/null +++ b/server/status/domain/status.go @@ -0,0 +1,28 @@ +package domain + +import ( + "context" + "net/http" +) + +type Status struct { + Downloading int `json:"downloading"` + Pending int `json:"pending"` + Completed int `json:"completed"` + DownloadSpeed int `json:"download_speed"` +} + +type Repository interface { + Pending(ctx context.Context) int + Completed(ctx context.Context) int + Downloading(ctx context.Context) int + DownloadSpeed(ctx context.Context) int64 +} + +type Service interface { + Status(ctx context.Context) (*Status, error) +} + +type RestHandler interface { + Status() http.HandlerFunc +} diff --git a/server/status/repository/repository.go b/server/status/repository/repository.go new file mode 100644 index 0000000..f53e966 --- /dev/null +++ b/server/status/repository/repository.go @@ -0,0 +1,65 @@ +package repository + +import ( + "context" + "slices" + + "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/internal" + "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/status/domain" +) + +type Repository struct { + mdb *internal.MemoryDB +} + +// DownloadSpeed implements domain.Repository. +func (r *Repository) DownloadSpeed(ctx context.Context) int64 { + processes := r.mdb.All() + + var downloadSpeed float64 + + for _, p := range *processes { + downloadSpeed += p.Progress.Speed + } + + return int64(downloadSpeed) +} + +// Completed implements domain.Repository. +func (r *Repository) Completed(ctx context.Context) int { + processes := r.mdb.All() + + completed := slices.DeleteFunc(*processes, func(p internal.ProcessResponse) bool { + return p.Progress.Status != internal.StatusCompleted + }) + + return len(completed) +} + +// Downloading implements domain.Repository. +func (r *Repository) Downloading(ctx context.Context) int { + processes := r.mdb.All() + + downloading := slices.DeleteFunc(*processes, func(p internal.ProcessResponse) bool { + return p.Progress.Status != internal.StatusDownloading + }) + + return len(downloading) +} + +// Pending implements domain.Repository. +func (r *Repository) Pending(ctx context.Context) int { + processes := r.mdb.All() + + pending := slices.DeleteFunc(*processes, func(p internal.ProcessResponse) bool { + return p.Progress.Status != internal.StatusPending + }) + + return len(pending) +} + +func New(mdb *internal.MemoryDB) domain.Repository { + return &Repository{ + mdb: mdb, + } +} diff --git a/server/status/rest/handler.go b/server/status/rest/handler.go new file mode 100644 index 0000000..c46defb --- /dev/null +++ b/server/status/rest/handler.go @@ -0,0 +1,38 @@ +package rest + +import ( + "encoding/json" + "net/http" + + "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/status/domain" +) + +type RestHandler struct { + service domain.Service +} + +// Status implements domain.RestHandler. +func (h *RestHandler) Status() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + w.Header().Set("Content-Type", "application/json") + + status, err := h.service.Status(r.Context()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err := json.NewEncoder(w).Encode(status); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func New(service domain.Service) domain.RestHandler { + return &RestHandler{ + service: service, + } +} diff --git a/server/status/service/service.go b/server/status/service/service.go new file mode 100644 index 0000000..a4ef745 --- /dev/null +++ b/server/status/service/service.go @@ -0,0 +1,69 @@ +package service + +import ( + "context" + "sync" + + "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/rest" + "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/status/domain" +) + +type Service struct { + repository domain.Repository + utilityService *rest.Service +} + +// Version implements domain.Status. +func (s *Service) Status(ctx context.Context) (*domain.Status, error) { + // rpcVersion, downloaderVersion, err := s.utilityService.GetVersion(ctx) + // if err != nil { + // return nil, err + // } + + var ( + wg sync.WaitGroup + pending int + downloading int + completed int + speed int64 + // version = fmt.Sprintf("RPC: %s yt-dlp: %s", rpcVersion, downloaderVersion) + ) + + wg.Add(4) + + go func() { + pending = s.repository.Pending(ctx) + wg.Done() + }() + + go func() { + downloading = s.repository.Downloading(ctx) + wg.Done() + }() + + go func() { + completed = s.repository.Completed(ctx) + wg.Done() + }() + + go func() { + speed = s.repository.DownloadSpeed(ctx) + wg.Done() + }() + + wg.Wait() + + return &domain.Status{ + Downloading: downloading, + Pending: pending, + Completed: completed, + DownloadSpeed: int(speed), + }, nil +} + +func New(repository domain.Repository, utilityService *rest.Service) domain.Service { + return &Service{ + repository: repository, + utilityService: utilityService, + } +} diff --git a/server/status/status.go b/server/status/status.go new file mode 100644 index 0000000..e13931b --- /dev/null +++ b/server/status/status.go @@ -0,0 +1,21 @@ +package status + +import ( + "github.com/go-chi/chi/v5" + "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/internal" + "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/status/repository" + "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/status/rest" + "github.com/marcopeocchi/yt-dlp-web-ui/v3/server/status/service" +) + +func ApplyRouter(mdb *internal.MemoryDB) func(chi.Router) { + var ( + r = repository.New(mdb) + s = service.New(r, nil) //TODO: nil, wtf? + h = rest.New(s) + ) + + return func(r chi.Router) { + r.Get("/", h.Status()) + } +}