Files
MeBox/internal/handler/media_scan.go
T
truewhile b0fe40142a Rebrand MMTL to MeBox (name, logo, Docker image) (#17)
* Rebrand MMTL to MeBox across codebase and assets

Rename the project display name, Go module path, environment variable
prefix (MEBOX_*), Docker image references, and UI branding from MMTL/mmtl
to MeBox/mebox. Replace logo assets with the new MeBox icon and keep
legacy SQLite migration support for existing mmtl.db deployments.

Co-authored-by: truewhile <truewhile@users.noreply.github.com>

* Fix logo icons: use cube-only crop without truncated text

Previous icon generation cropped too much of the source image, including
partial MeBox wordmark text that was cut off in square icon containers.
Regenerate logo-64/192/512, favicon, and SVG from cube-only region.

Co-authored-by: truewhile <truewhile@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: truewhile <truewhile@users.noreply.github.com>
2026-09-02 16:26:28 +08:00

168 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"context"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/truewhile/MeBox/internal/service"
)
func scanLibraryHandler(svc *service.Container) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
lib, err := svc.Repo.Library.FindByID(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if lib == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "library not found"})
return
}
if _, ok := service.ParseCloudLibraryMount(lib.Path); ok {
// 云盘扫描已随网盘后端移除;此处保留空分支以兼容既有客户端。
c.JSON(http.StatusBadRequest, gin.H{"error": "cloud 网盘后端已移除,无法扫描云盘媒体库"})
return
}
finishScan, ok := svc.Scan.TryBeginLocalScan(id)
if !ok {
c.JSON(http.StatusAccepted, gin.H{
"library_id": id,
"queued": true,
"already_running": true,
"message": "该媒体库正在后台扫描,请在任务面板查看进度",
"estimate_message": "页面关闭不会中断扫描",
})
return
}
task := startScanHTTPTask(svc, "手动扫描入库", lib.Name, lib.Path)
go func(libraryID string, task *service.TaskHandle, finish func()) {
defer finish()
res, err := svc.Scan.ScanLibrary(context.Background(), libraryID)
if err != nil {
finishHTTPTask(task, err, "scan", "手动扫描入库失败", scanTaskMetrics(res), scanTaskDetails(res, 20))
return
}
finishHTTPTask(task, nil, "completed", "手动扫描入库结束", scanTaskMetrics(res), scanTaskDetails(res, 20))
}(id, task, finishScan)
c.JSON(http.StatusAccepted, gin.H{
"library_id": id,
"queued": true,
"message": "本地媒体库扫描已在后台运行,页面关闭不会中断",
"estimate_message": "可在右上角任务面板查看扫描进度",
})
}
}
func scanLibraryRootHandler(svc *service.Container) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
rootID := c.Param("root_id")
finishScan, ok := svc.Scan.TryBeginLocalScan(id + ":" + rootID)
if !ok {
c.JSON(http.StatusAccepted, gin.H{
"library_id": id,
"queued": true,
"already_running": true,
"message": "该路径正在后台扫描,请在任务面板查看进度",
"estimate_message": "页面关闭不会中断扫描",
})
return
}
task := startScanHTTPTask(svc, "手动扫描媒体库路径", id, rootID)
go func(libraryID, libraryRootID string, task *service.TaskHandle, finish func()) {
defer finish()
res, err := svc.Scan.ScanLibraryRoot(context.Background(), libraryID, libraryRootID)
if err != nil {
finishHTTPTask(task, err, "scan", "手动扫描路径失败", scanTaskMetrics(res), scanTaskDetails(res, 20))
return
}
finishHTTPTask(task, nil, "completed", "手动扫描路径结束", scanTaskMetrics(res), scanTaskDetails(res, 20))
}(id, rootID, task, finishScan)
c.JSON(http.StatusAccepted, gin.H{
"library_id": id,
"queued": true,
"message": "媒体库路径扫描已在后台运行,页面关闭不会中断",
"estimate_message": "可在右上角任务面板查看扫描进度",
})
}
}
// queueLibraryRootScan starts the same background scan used by the manual
// scan endpoints. Library creation/root addition used to refresh only the
// watcher, which left a newly-created library empty until the user clicked
// "扫描" manually. Keeping this helper in the scan handler makes all mutation
// paths use one scan lifecycle and preserves the local-scan de-duplication.
func queueLibraryRootScan(svc *service.Container, libraryID, rootID string) {
if svc == nil || svc.Scan == nil || strings.TrimSpace(libraryID) == "" {
return
}
key := libraryID
if strings.TrimSpace(rootID) != "" {
key += ":" + rootID
}
finish, ok := svc.Scan.TryBeginLocalScan(key)
if !ok {
return
}
go func() {
defer finish()
if strings.TrimSpace(rootID) == "" {
_, _ = svc.Scan.ScanLibrary(context.Background(), libraryID)
return
}
_, _ = svc.Scan.ScanLibraryRoot(context.Background(), libraryID, rootID)
}()
}
func startScanHTTPTask(svc *service.Container, name, libraryName, path string) *service.TaskHandle {
if svc == nil || svc.Tasks == nil {
return nil
}
if libraryName != "" {
name += ":" + libraryName
}
return svc.Tasks.Start(service.TaskKindScan, name, service.TaskUpdate{
Stage: "scan",
SourcePath: path,
Message: "正在扫描并入库",
})
}
func scanTaskMetrics(res *service.ScanResult) map[string]int64 {
if res == nil {
return nil
}
return map[string]int64{
"visited": int64(res.Visited),
"added": int64(res.Added),
"updated": int64(res.Updated),
"skipped": int64(res.Skipped),
"probed": int64(res.Probed),
"local_metadata": int64(res.LocalMetadata),
"removed": res.Removed,
"errors": int64(res.ErrorCount),
}
}
func scanTaskDetails(res *service.ScanResult, limit int) []string {
if res == nil || limit <= 0 {
return nil
}
out := make([]string, 0, limit)
for _, line := range res.Errors {
line = strings.TrimSpace(line)
if line == "" {
continue
}
out = append(out, "错误: "+line)
if len(out) >= limit {
return out
}
}
return out
}