323 lines
9.3 KiB
Go
323 lines
9.3 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"litepan/internal/cacheretention"
|
|
"litepan/internal/domain"
|
|
)
|
|
|
|
type retentionTaskDTO struct {
|
|
ID int64 `json:"id,omitempty"`
|
|
AccountID int64 `json:"account_id"`
|
|
AccountName string `json:"account_name,omitempty"`
|
|
ParentID string `json:"parent_id"`
|
|
Path string `json:"path"`
|
|
ScanDepth int `json:"scan_depth"`
|
|
ApiInterval int `json:"api_interval"`
|
|
RefreshInterval int `json:"refresh_interval"`
|
|
Status string `json:"status"`
|
|
PausedReason string `json:"paused_reason,omitempty"`
|
|
FileCount int `json:"file_count"`
|
|
LastRefresh string `json:"last_refresh,omitempty"`
|
|
LastRefreshStatus string `json:"last_refresh_status,omitempty"`
|
|
LastDurationMs int `json:"last_duration_ms"`
|
|
LastAPICalls int `json:"last_api_calls"`
|
|
LastSkipCalls int `json:"last_skip_calls"`
|
|
LastScannedDirs int `json:"last_scanned_dirs"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
TimeWindowEnabled bool `json:"time_window_enabled"`
|
|
TimeStart string `json:"time_start"`
|
|
TimeEnd string `json:"time_end"`
|
|
ScannedDirs int `json:"scanned_dirs,omitempty"`
|
|
ScannedFiles int `json:"scanned_files,omitempty"`
|
|
CurrentDir string `json:"current_dir,omitempty"`
|
|
StartedAt string `json:"started_at,omitempty"`
|
|
CurrentDurationMs int `json:"current_duration_ms,omitempty"`
|
|
IsRunning bool `json:"is_running"`
|
|
IsPending bool `json:"is_pending"`
|
|
CreatedAt string `json:"created_at,omitempty"`
|
|
UpdatedAt string `json:"updated_at,omitempty"`
|
|
}
|
|
|
|
func toRetentionDTO(t *domain.CacheRetentionTask, svc *cacheretention.Service) retentionTaskDTO {
|
|
if t == nil {
|
|
return retentionTaskDTO{}
|
|
}
|
|
dto := retentionTaskDTO{
|
|
ID: t.ID,
|
|
AccountID: t.AccountID,
|
|
AccountName: t.AccountName,
|
|
ParentID: t.ParentID,
|
|
Path: t.Path,
|
|
ScanDepth: t.ScanDepth,
|
|
ApiInterval: t.ApiInterval,
|
|
RefreshInterval: t.RefreshInterval,
|
|
Status: t.Status,
|
|
PausedReason: t.PausedReason,
|
|
FileCount: t.FileCount,
|
|
LastRefreshStatus: t.LastRefreshStatus,
|
|
LastDurationMs: t.LastDurationMS,
|
|
LastAPICalls: t.LastAPICalls,
|
|
LastSkipCalls: t.LastSkipCalls,
|
|
LastScannedDirs: t.LastScannedDirs,
|
|
ErrorMessage: t.ErrorMessage,
|
|
TimeWindowEnabled: t.TimeWindowEnabled,
|
|
TimeStart: t.TimeStart,
|
|
TimeEnd: t.TimeEnd,
|
|
IsRunning: svc != nil && svc.IsExecuting(t.ID),
|
|
IsPending: svc != nil && svc.IsPending(t.ID),
|
|
CreatedAt: FormatAPITime(t.CreatedAt),
|
|
UpdatedAt: FormatAPITime(t.UpdatedAt),
|
|
}
|
|
if t.LastRefresh != nil {
|
|
dto.LastRefresh = FormatAPITime(*t.LastRefresh)
|
|
}
|
|
if svc != nil {
|
|
if st, ok := svc.TaskLiveStats(t.ID); ok {
|
|
dto.ScannedDirs = st.ScannedDirs
|
|
dto.ScannedFiles = st.ScannedFiles
|
|
dto.CurrentDir = st.CurrentDir
|
|
if !st.StartedAt.IsZero() {
|
|
dto.StartedAt = FormatAPITime(st.StartedAt)
|
|
dto.CurrentDurationMs = int(time.Since(st.StartedAt).Milliseconds())
|
|
}
|
|
}
|
|
}
|
|
return dto
|
|
}
|
|
|
|
func fromRetentionDTO(d retentionTaskDTO) *domain.CacheRetentionTask {
|
|
return &domain.CacheRetentionTask{
|
|
ID: d.ID,
|
|
AccountID: d.AccountID,
|
|
ParentID: d.ParentID,
|
|
Path: d.Path,
|
|
ScanDepth: d.ScanDepth,
|
|
ApiInterval: d.ApiInterval,
|
|
RefreshInterval: d.RefreshInterval,
|
|
TimeWindowEnabled: d.TimeWindowEnabled,
|
|
TimeStart: d.TimeStart,
|
|
TimeEnd: d.TimeEnd,
|
|
}
|
|
}
|
|
|
|
func retentionRunPayload(result cacheretention.RunNowResult) map[string]any {
|
|
return map[string]any{
|
|
"state": result.State,
|
|
"startup_remaining": result.StartupRemaining,
|
|
"retry_after_seconds": result.RetryAfterSeconds,
|
|
"cache_ttl_minutes": result.CacheTTLMinutes,
|
|
}
|
|
}
|
|
|
|
func retentionRunMessage(result cacheretention.RunNowResult, create bool) string {
|
|
if create {
|
|
switch result.State {
|
|
case "queued_startup":
|
|
return "配置创建成功,启动退避结束后(约 " + strconv.Itoa(result.StartupRemaining) + " 秒)自动执行"
|
|
case "blocked_by_strm":
|
|
return "配置创建成功,同账号任务占用结束后自动执行"
|
|
case "running":
|
|
return "配置创建成功,已触发执行"
|
|
case "cache_disabled":
|
|
return "配置创建成功,但该账号目录缓存已关闭,任务无法生效"
|
|
default:
|
|
return "配置创建成功"
|
|
}
|
|
}
|
|
return result.Message()
|
|
}
|
|
|
|
func (h *Handler) listRetentionTasks(w http.ResponseWriter, r *http.Request) {
|
|
if !ensureServiceReady(w, h.cacheRetention != nil) {
|
|
return
|
|
}
|
|
tasks, err := h.cacheRetention.ListTasks(r.Context())
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
out := make([]retentionTaskDTO, 0, len(tasks))
|
|
for _, t := range tasks {
|
|
out = append(out, toRetentionDTO(t, h.cacheRetention))
|
|
}
|
|
writeJSON(w, http.StatusOK, Resp{
|
|
Success: true,
|
|
Data: map[string]any{
|
|
"items": out,
|
|
"startup_remaining": h.cacheRetention.StartupRemaining(),
|
|
},
|
|
})
|
|
}
|
|
|
|
func (h *Handler) getRetentionStats(w http.ResponseWriter, r *http.Request) {
|
|
if !ensureServiceReady(w, h.cacheRetention != nil) {
|
|
return
|
|
}
|
|
stats, err := h.cacheRetention.Stats(r.Context())
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
writeOK(w, stats)
|
|
}
|
|
|
|
func (h *Handler) createRetentionTask(w http.ResponseWriter, r *http.Request) {
|
|
if !ensureServiceReady(w, h.cacheRetention != nil) {
|
|
return
|
|
}
|
|
var in retentionTaskDTO
|
|
if err := decodeJSON(r, &in); err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
task, err := h.cacheRetention.CreateTask(r.Context(), fromRetentionDTO(in))
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
run := h.cacheRetention.RunNow(r.Context(), task.ID)
|
|
writeJSON(w, http.StatusOK, Resp{
|
|
Success: true,
|
|
Data: map[string]any{"id": task.ID, "run": retentionRunPayload(run)},
|
|
Message: retentionRunMessage(run, true),
|
|
})
|
|
}
|
|
|
|
func (h *Handler) updateRetentionTask(w http.ResponseWriter, r *http.Request) {
|
|
if !ensureServiceReady(w, h.cacheRetention != nil) {
|
|
return
|
|
}
|
|
id, err := pathID(r)
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
var in retentionTaskDTO
|
|
if err := decodeJSON(r, &in); err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
in.ID = id
|
|
task, err := h.cacheRetention.UpdateTask(r.Context(), fromRetentionDTO(in))
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
writeOK(w, toRetentionDTO(task, h.cacheRetention))
|
|
}
|
|
|
|
func (h *Handler) deleteRetentionTask(w http.ResponseWriter, r *http.Request) {
|
|
if !ensureServiceReady(w, h.cacheRetention != nil) {
|
|
return
|
|
}
|
|
id, err := pathID(r)
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
if err := h.cacheRetention.DeleteTask(r.Context(), id); err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, Resp{Success: true, Message: "配置已删除"})
|
|
}
|
|
|
|
func (h *Handler) toggleRetentionTask(w http.ResponseWriter, r *http.Request) {
|
|
if !ensureServiceReady(w, h.cacheRetention != nil) {
|
|
return
|
|
}
|
|
id, err := pathID(r)
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
task, err := h.cacheRetention.ToggleTask(r.Context(), id)
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, Resp{Success: true, Data: toRetentionDTO(task, h.cacheRetention), Message: "状态已切换"})
|
|
}
|
|
|
|
func (h *Handler) refreshRetentionTask(w http.ResponseWriter, r *http.Request) {
|
|
if !ensureServiceReady(w, h.cacheRetention != nil) {
|
|
return
|
|
}
|
|
id, err := pathID(r)
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
run := h.cacheRetention.RunNow(r.Context(), id)
|
|
if run.State == "missing" {
|
|
writeJSON(w, http.StatusNotFound, Resp{Success: false, Message: "配置不存在"})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, Resp{
|
|
Success: true,
|
|
Data: retentionRunPayload(run),
|
|
Message: retentionRunMessage(run, false),
|
|
})
|
|
}
|
|
|
|
func (h *Handler) forceStopRetentionTask(w http.ResponseWriter, r *http.Request) {
|
|
if !ensureServiceReady(w, h.cacheRetention != nil) {
|
|
return
|
|
}
|
|
id, err := pathID(r)
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
if err := h.cacheRetention.ForceStop(r.Context(), id); err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, Resp{Success: true, Message: "任务已强制停止,下次调度不受影响"})
|
|
}
|
|
|
|
func (h *Handler) ackRetentionScopeWarn(w http.ResponseWriter, r *http.Request) {
|
|
if !ensureServiceReady(w, h.cacheRetention != nil) {
|
|
return
|
|
}
|
|
id, err := pathID(r)
|
|
if err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
if err := h.cacheRetention.AckLargeScopeWarn(r.Context(), id); err != nil {
|
|
writeErr(w, err)
|
|
return
|
|
}
|
|
if h.notifications != nil {
|
|
_, _ = h.notifications.DeleteByRef(r.Context(), domain.NotificationCategoryCacheScopeWarn, id)
|
|
}
|
|
writeJSON(w, http.StatusOK, Resp{Success: true, Message: "已关闭该任务的范围过大提醒"})
|
|
}
|
|
|
|
func (h *Handler) retentionDefaults(w http.ResponseWriter, _ *http.Request) {
|
|
remaining := 0
|
|
if h.cacheRetention != nil {
|
|
remaining = h.cacheRetention.StartupRemaining()
|
|
}
|
|
writeOK(w, map[string]any{
|
|
"api_interval": 200,
|
|
"refresh_interval": 60,
|
|
"scan_depth": 4,
|
|
"max_configs": 6,
|
|
"startup_remaining": remaining,
|
|
})
|
|
}
|
|
|
|
func (h *Handler) retentionStartupRemaining(w http.ResponseWriter, _ *http.Request) {
|
|
remaining := 0
|
|
if h.cacheRetention != nil {
|
|
remaining = h.cacheRetention.StartupRemaining()
|
|
}
|
|
writeOK(w, map[string]any{"startup_remaining": remaining})
|
|
}
|