242 lines
6.8 KiB
Go
242 lines
6.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"caorushizi.cn/mediago/internal/api/dto"
|
|
"caorushizi.cn/mediago/internal/core"
|
|
"caorushizi.cn/mediago/internal/i18n"
|
|
"caorushizi.cn/mediago/internal/logger"
|
|
"caorushizi.cn/mediago/internal/tasklog"
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// TaskHandler handles task-related endpoints.
|
|
type TaskHandler struct {
|
|
queue *core.TaskQueue
|
|
logs *tasklog.Manager
|
|
mu sync.Mutex
|
|
seq int64
|
|
}
|
|
|
|
// NewTaskHandler creates a TaskHandler.
|
|
func NewTaskHandler(queue *core.TaskQueue, logs *tasklog.Manager) *TaskHandler {
|
|
return &TaskHandler{
|
|
queue: queue,
|
|
logs: logs,
|
|
}
|
|
}
|
|
|
|
// Create creates a download task.
|
|
// @Summary Create a download task
|
|
// @Description Creates a new download task and adds it to the queue; task ID is optional
|
|
// @Description Supports M3U8, Bilibili, and Direct download types
|
|
// @Tags Tasks
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param task body dto.CreateTaskReq true "Download task parameters"
|
|
// @Success 200 {object} dto.SuccessResponse{data=dto.CreateTaskResponse} "Task created successfully, returns task status (pending/success)"
|
|
// @Failure 400 {object} dto.ErrorResponse "Invalid request parameters"
|
|
// @Router /tasks [post]
|
|
func (h *TaskHandler) Create(c *gin.Context) {
|
|
var req dto.CreateTaskReq
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
logger.Warn("Invalid task creation request",
|
|
zap.String("clientIP", c.ClientIP()),
|
|
zap.Error(err))
|
|
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Success: false, Code: http.StatusBadRequest, Message: err.Error()})
|
|
return
|
|
}
|
|
|
|
params := req.ToDownloadParams()
|
|
|
|
logger.Info("Task creation request received",
|
|
zap.String("id", string(params.ID)),
|
|
zap.String("type", string(params.Type)),
|
|
zap.String("url", params.URL),
|
|
zap.String("clientIP", c.ClientIP()))
|
|
|
|
status := h.queue.Enqueue(params)
|
|
|
|
c.JSON(http.StatusOK, dto.SuccessResponse{
|
|
Success: true,
|
|
Code: http.StatusOK,
|
|
Message: i18n.T(c, i18n.MsgTaskCreated),
|
|
Data: dto.CreateTaskResponse{
|
|
ID: string(params.ID),
|
|
Message: i18n.T(c, i18n.MsgTaskEnqueued),
|
|
Status: string(status),
|
|
},
|
|
})
|
|
}
|
|
|
|
// Get retrieves task status.
|
|
// @Summary Get task status
|
|
// @Description Retrieves the status and progress information for the specified task ID
|
|
// @Tags Tasks
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Task ID" example(task-1)
|
|
// @Success 200 {object} dto.SuccessResponse{data=core.TaskInfo} "Task information"
|
|
// @Failure 404 {object} dto.ErrorResponse "Task not found"
|
|
// @Router /tasks/{id} [get]
|
|
func (h *TaskHandler) Get(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
task, ok := h.queue.GetTask(core.TaskID(id))
|
|
if !ok {
|
|
logger.Warn("Task not found",
|
|
zap.String("id", id),
|
|
zap.String("clientIP", c.ClientIP()))
|
|
c.JSON(http.StatusNotFound, dto.ErrorResponse{Success: false, Code: http.StatusNotFound, Message: i18n.T(c, i18n.MsgTaskNotFound)})
|
|
return
|
|
}
|
|
|
|
logger.Debug("Task info retrieved",
|
|
zap.String("id", id),
|
|
zap.String("status", string(task.Status)),
|
|
zap.String("clientIP", c.ClientIP()))
|
|
|
|
c.JSON(http.StatusOK, dto.SuccessResponse{
|
|
Success: true,
|
|
Code: http.StatusOK,
|
|
Message: i18n.T(c, i18n.MsgOK),
|
|
Data: task,
|
|
})
|
|
}
|
|
|
|
// List retrieves all task statuses.
|
|
// @Summary Get all task statuses
|
|
// @Description Retrieves the status and progress information for all tasks
|
|
// @Tags Tasks
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} dto.SuccessResponse{data=dto.TaskListResponse} "Task list"
|
|
// @Router /tasks [get]
|
|
func (h *TaskHandler) List(c *gin.Context) {
|
|
tasks := h.queue.GetAllTasks()
|
|
|
|
logger.Debug("All tasks info retrieved",
|
|
zap.Int("count", len(tasks)),
|
|
zap.String("clientIP", c.ClientIP()))
|
|
|
|
c.JSON(http.StatusOK, dto.SuccessResponse{
|
|
Success: true,
|
|
Code: http.StatusOK,
|
|
Message: i18n.T(c, i18n.MsgOK),
|
|
Data: dto.TaskListResponse{
|
|
Tasks: tasks,
|
|
Total: len(tasks),
|
|
},
|
|
})
|
|
}
|
|
|
|
// Stop stops a download task.
|
|
// @Summary Stop a download task
|
|
// @Description Stops the download task with the specified ID
|
|
// @Tags Tasks
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Task ID" example(task-1)
|
|
// @Success 200 {object} dto.SuccessResponse{data=dto.StopTaskResponse} "Task stopped successfully"
|
|
// @Failure 404 {object} dto.ErrorResponse "Task not found"
|
|
// @Router /tasks/{id}/stop [post]
|
|
func (h *TaskHandler) Stop(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
logger.Info("Stop task request received",
|
|
zap.String("id", id),
|
|
zap.String("clientIP", c.ClientIP()))
|
|
|
|
if err := h.queue.Stop(core.TaskID(id)); err != nil {
|
|
logger.Warn("Failed to stop task",
|
|
zap.String("id", id),
|
|
zap.Error(err))
|
|
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.MsgTaskStopped),
|
|
Data: dto.StopTaskResponse{Message: i18n.T(c, i18n.MsgTaskStopped)},
|
|
})
|
|
}
|
|
|
|
// Logs retrieves task logs.
|
|
// @Summary Get task logs
|
|
// @Description Retrieves the full download log content for the specified task ID
|
|
// @Tags Tasks
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Task ID" example(task-1)
|
|
// @Success 200 {object} dto.SuccessResponse{data=dto.TaskLogResponse} "Task log content"
|
|
// @Failure 404 {object} dto.ErrorResponse "Log not found"
|
|
// @Failure 500 {object} dto.ErrorResponse "Log system not configured or read failed"
|
|
// @Router /tasks/{id}/logs [get]
|
|
func (h *TaskHandler) Logs(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
if h.logs == nil {
|
|
logger.Error("Task log manager not configured",
|
|
zap.String("id", id),
|
|
zap.String("clientIP", c.ClientIP()))
|
|
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{
|
|
Success: false,
|
|
Code: http.StatusInternalServerError,
|
|
Message: i18n.T(c, i18n.MsgTaskLogNotConfigured),
|
|
})
|
|
return
|
|
}
|
|
|
|
content, err := h.logs.Read(id)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
logger.Warn("Task log not found",
|
|
zap.String("id", id),
|
|
zap.String("clientIP", c.ClientIP()))
|
|
c.JSON(http.StatusNotFound, dto.ErrorResponse{
|
|
Success: false,
|
|
Code: http.StatusNotFound,
|
|
Message: i18n.T(c, i18n.MsgTaskLogNotFound),
|
|
})
|
|
return
|
|
}
|
|
|
|
logger.Error("Failed to read task log",
|
|
zap.String("id", id),
|
|
zap.Error(err),
|
|
zap.String("clientIP", c.ClientIP()))
|
|
c.JSON(http.StatusInternalServerError, dto.ErrorResponse{
|
|
Success: false,
|
|
Code: http.StatusInternalServerError,
|
|
Message: i18n.T(c, i18n.MsgTaskLogReadFailed),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, dto.SuccessResponse{
|
|
Success: true,
|
|
Code: http.StatusOK,
|
|
Message: i18n.T(c, i18n.MsgOK),
|
|
Data: dto.TaskLogResponse{
|
|
ID: id,
|
|
Log: content,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (h *TaskHandler) nextTaskID() core.TaskID {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
|
|
h.seq++
|
|
return core.TaskID(strconv.FormatInt(h.seq, 10))
|
|
}
|