- 移动阶段并行处理文件,按原子计数上报累计进度 - 扫描遍历阶段实时上报已发现文件数,界面不再卡在 0/0 - 新增 /api/scan/ws WebSocket 实时推送扫描状态;前端断线指数退避自动重连,并以低频轮询兜底 - 新增统计看板与媒体库筛选/排序,目录选择器路径不存在时回退到最近可用父目录 - 存放目录支持自定义并持久化,默认输出到 KMP已整理
102 lines
2.5 KiB
Go
102 lines
2.5 KiB
Go
// Package api 提供服务器文件系统浏览接口,供前端目录选择器使用(只列出目录,不列文件)。
|
|
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// FsController 服务器文件系统浏览控制器
|
|
type FsController struct{}
|
|
|
|
// NewFsController 创建文件系统浏览控制器
|
|
func NewFsController() *FsController {
|
|
return &FsController{}
|
|
}
|
|
|
|
// FsListResult 目录浏览结果
|
|
type FsListResult struct {
|
|
Current string `json:"current"` // 当前目录绝对路径;盘符层为空
|
|
Parent string `json:"parent"` // 父目录;根/盘符层时等于 Current
|
|
Drives []string `json:"drives"` // Windows 盘符列表(非 Windows 为空)
|
|
Dirs []FsDir `json:"dirs"` // 子目录列表(不含文件)
|
|
}
|
|
|
|
// FsDir 子目录项
|
|
type FsDir struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
}
|
|
|
|
// List GET /api/fs/list?dir= 列出指定目录的子目录(不含文件);
|
|
// dir 为空时返回 Windows 盘符列表或 Unix 根目录。
|
|
func (c *FsController) List(ctx *gin.Context) {
|
|
dir := strings.TrimSpace(ctx.Query("dir"))
|
|
|
|
if dir == "" {
|
|
if runtime.GOOS == "windows" {
|
|
Success(ctx, FsListResult{Drives: listLogicalDrives()})
|
|
return
|
|
}
|
|
dir = string(os.PathSeparator)
|
|
}
|
|
|
|
abs, err := filepath.Abs(dir)
|
|
if err != nil {
|
|
Error(ctx, http.StatusBadRequest, "路径解析失败: "+err.Error())
|
|
return
|
|
}
|
|
// 目录不存在/不可访问时向上回退到最近的已存在父目录,避免阻塞目录选择器
|
|
for {
|
|
st, statErr := os.Stat(abs)
|
|
if statErr == nil && st.IsDir() {
|
|
break
|
|
}
|
|
parent := filepath.Dir(abs)
|
|
if parent == abs {
|
|
// 已到根仍不存在:回退到盘符层
|
|
if runtime.GOOS == "windows" {
|
|
Success(ctx, FsListResult{Drives: listLogicalDrives()})
|
|
return
|
|
}
|
|
abs = string(os.PathSeparator)
|
|
break
|
|
}
|
|
abs = parent
|
|
}
|
|
|
|
entries, err := os.ReadDir(abs)
|
|
if err != nil {
|
|
Error(ctx, http.StatusBadRequest, "读取目录失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
parent := filepath.Dir(abs)
|
|
if parent == abs {
|
|
parent = abs
|
|
}
|
|
dirs := make([]FsDir, 0, len(entries))
|
|
for _, e := range entries {
|
|
if !e.IsDir() {
|
|
continue
|
|
}
|
|
dirs = append(dirs, FsDir{Name: e.Name(), Path: filepath.Join(abs, e.Name())})
|
|
}
|
|
sort.Slice(dirs, func(i, j int) bool {
|
|
return strings.ToLower(dirs[i].Name) < strings.ToLower(dirs[j].Name)
|
|
})
|
|
|
|
Success(ctx, FsListResult{
|
|
Current: abs,
|
|
Parent: parent,
|
|
Drives: nil,
|
|
Dirs: dirs,
|
|
})
|
|
}
|