Files
2026-07-13 13:39:47 +08:00

316 lines
11 KiB
Go

package handler
import (
"net/http"
"strconv"
"caorushizi.cn/mediago/internal/api/dto"
"caorushizi.cn/mediago/internal/api/sse"
"caorushizi.cn/mediago/internal/i18n"
"caorushizi.cn/mediago/internal/logger"
"caorushizi.cn/mediago/internal/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// DownloadHandler handles database-persisted download task endpoints.
type DownloadHandler struct {
svc *service.DownloadTaskService
conf ConfigStore
hub *sse.Hub
}
// NewDownloadHandler creates a DownloadHandler.
func NewDownloadHandler(svc *service.DownloadTaskService, conf ConfigStore, hub *sse.Hub) *DownloadHandler {
return &DownloadHandler{svc: svc, conf: conf, hub: hub}
}
// Create adds download tasks (supports batch creation).
func (h *DownloadHandler) Create(c *gin.Context) {
var req dto.AddDownloadBatchReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: err.Error()})
return
}
inputs := make([]*service.AddDownloadTaskInput, 0, len(req.Tasks))
for _, t := range req.Tasks {
inputs = append(inputs, &service.AddDownloadTaskInput{
Name: t.Name,
Type: t.Type,
URL: t.URL,
Headers: t.Headers,
Folder: t.Folder,
})
}
videos, err := h.svc.AddDownloadTasks(inputs)
if err != nil {
logger.Error("Failed to add download tasks", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
// Auto-start downloads if requested
if req.StartDownload {
localPath, _ := h.conf.Get("local").(string)
deleteSegments, _ := h.conf.Get("deleteSegments").(bool)
for _, v := range videos {
if err := h.svc.StartDownload(v.ID, localPath, deleteSegments); err != nil {
logger.Warn("auto-start download failed", zap.Int64("id", v.ID), zap.Error(err))
}
}
}
// Broadcast creation so every connected renderer (main window, overlay
// dialog, external clients) can update its own badge/state without
// relying on cross-WebContents IPC.
if h.hub != nil && len(videos) > 0 {
ids := make([]int64, 0, len(videos))
for _, v := range videos {
ids = append(ids, v.ID)
}
h.hub.Broadcast("download-create", map[string]interface{}{
"ids": ids,
"count": len(ids),
})
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgOK), Data: videos})
}
// List retrieves a paginated list of download tasks.
func (h *DownloadHandler) List(c *gin.Context) {
var req dto.DownloadPaginationReq
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: err.Error()})
return
}
localPath := c.Query("localPath")
result, err := h.svc.GetDownloadTasks(req.Current, req.PageSize, req.Filter, localPath)
if err != nil {
logger.Error("Failed to get download tasks", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgOK), Data: result})
}
// Get retrieves a single download task.
func (h *DownloadHandler) Get(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: i18n.T(c, i18n.MsgInvalidID)})
return
}
video, err := h.svc.FindByIDOrFail(id)
if err != nil {
c.JSON(http.StatusNotFound, dto.ErrorResponse{Success: false, Code: http.StatusNotFound, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgOK), Data: video})
}
// Edit edits a download task.
func (h *DownloadHandler) Edit(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: i18n.T(c, i18n.MsgInvalidID)})
return
}
var req dto.EditDownloadReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: err.Error()})
return
}
data := make(map[string]interface{})
if req.Name != nil {
data["name"] = *req.Name
}
if req.URL != nil {
data["url"] = *req.URL
}
if req.Headers != nil {
data["headers"] = *req.Headers
}
if req.Folder != nil {
data["folder"] = *req.Folder
}
video, err := h.svc.EditDownloadTask(id, data)
if err != nil {
logger.Error("Failed to edit download task", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgOK), Data: video})
}
// Delete removes a download task.
func (h *DownloadHandler) Delete(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: i18n.T(c, i18n.MsgInvalidID)})
return
}
if err := h.svc.DeleteDownloadTask(id); err != nil {
logger.Error("Failed to delete download task", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgDeleted)})
}
// Start begins downloading a task.
func (h *DownloadHandler) Start(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: i18n.T(c, i18n.MsgInvalidID)})
return
}
var req dto.StartDownloadReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: err.Error()})
return
}
// Sync the client-provided localPath into the runtime configuration
if req.LocalPath != "" {
if err := h.conf.Set("local", req.LocalPath); err != nil {
logger.Warn("Failed to sync localPath to config", zap.Error(err))
}
}
if err := h.svc.StartDownload(id, req.LocalPath, req.DeleteSegments); err != nil {
logger.Error("Failed to start download", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgDownloadStarted)})
}
// Stop stops a download.
func (h *DownloadHandler) Stop(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: i18n.T(c, i18n.MsgInvalidID)})
return
}
if err := h.svc.StopDownload(id); err != nil {
logger.Error("Failed to stop download", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgDownloadStopped)})
}
// Logs retrieves the download log.
func (h *DownloadHandler) Logs(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: i18n.T(c, i18n.MsgInvalidID)})
return
}
content, err := h.svc.GetDownloadLog(id)
if err != nil {
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgOK), Data: map[string]interface{}{
"id": id,
"log": content,
}})
}
// Folders retrieves the folder list.
func (h *DownloadHandler) Folders(c *gin.Context) {
folders, err := h.svc.GetTaskFolders()
if err != nil {
logger.Error("Failed to get task folders", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgOK), Data: folders})
}
// Export exports the download list.
func (h *DownloadHandler) Export(c *gin.Context) {
text, err := h.svc.ExportDownloadList()
if err != nil {
logger.Error("Failed to export download list", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgOK), Data: text})
}
// UpdateStatus bulk-updates task statuses.
func (h *DownloadHandler) UpdateStatus(c *gin.Context) {
var req dto.UpdateStatusReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: err.Error()})
return
}
if err := h.svc.SetStatus(req.IDs, req.Status); err != nil {
logger.Error("Failed to update status", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgStatusUpdated)})
}
// UpdateIsLive updates the live-stream flag.
func (h *DownloadHandler) UpdateIsLive(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: i18n.T(c, i18n.MsgInvalidID)})
return
}
var req dto.UpdateIsLiveReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: err.Error()})
return
}
video, err := h.svc.SetIsLive(id, req.IsLive)
if err != nil {
logger.Error("Failed to update isLive", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgOK), Data: video})
}
// Active retrieves active tasks.
func (h *DownloadHandler) Active(c *gin.Context) {
tasks, err := h.svc.FindActiveTasks()
if err != nil {
logger.Error("Failed to find active tasks", zap.Error(err))
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{Success: false, Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, dto.SuccessResponse{Success: true, Code: http.StatusOK, Message: i18n.T(c, i18n.MsgOK), Data: tasks})
}