mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 11:36:36 +08:00
213 lines
6.6 KiB
Go
213 lines
6.6 KiB
Go
// Package handler — library / media HTTP endpoints.
|
|
package handler
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/ShukeBta/MediaStationGo/internal/middleware"
|
|
"github.com/ShukeBta/MediaStationGo/internal/service"
|
|
)
|
|
|
|
type createLibraryReq struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Path string `json:"path" binding:"required"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
func listLibrariesHandler(svc *service.Container) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
libs, err := svc.Media.ListLibraries(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
role, _ := c.Get(middleware.CtxUserRole)
|
|
includeHidden := role == "admin" && (c.Query("include_hidden") == "1" || c.Query("all") == "1")
|
|
if !includeHidden {
|
|
libs = service.FilterShadowedCloudLibraries(libs)
|
|
visibility := mediaVisibilityForRequest(c, svc)
|
|
filtered := libs[:0]
|
|
for _, lib := range libs {
|
|
if service.LibraryVisibleForUser(c.Request.Context(), svc.Repo, lib, visibility) {
|
|
filtered = append(filtered, lib)
|
|
}
|
|
}
|
|
libs = filtered
|
|
}
|
|
c.JSON(http.StatusOK, libs)
|
|
}
|
|
}
|
|
|
|
func createLibraryHandler(svc *service.Container) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var req createLibraryReq
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
l, err := svc.Media.CreateLibrary(c.Request.Context(), req.Name, req.Path, req.Type)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
uid, _ := c.Get("ctx_user_id")
|
|
svc.Audit.Record(c.Request.Context(), toString(uid), "library.create", l.ID, c.ClientIP(), l.Path)
|
|
// Refresh fsnotify watcher to pick up the new library root.
|
|
go func() { _ = svc.Watcher.Refresh(context.Background()) }()
|
|
c.JSON(http.StatusCreated, l)
|
|
}
|
|
}
|
|
|
|
func deleteLibraryHandler(svc *service.Container) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if err := svc.Media.DeleteLibrary(c.Request.Context(), id); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
uid, _ := c.Get("ctx_user_id")
|
|
svc.Audit.Record(c.Request.Context(), toString(uid), "library.delete", id, c.ClientIP(), "")
|
|
go func() { _ = svc.Watcher.Refresh(context.Background()) }()
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
if svc.WSHub != nil {
|
|
svc.WSHub.Publish("scan", gin.H{
|
|
"library_id": id,
|
|
"cloud": true,
|
|
"queued": true,
|
|
"stage": "queued",
|
|
"message": "云盘扫描已加入后台队列,会递归扫描并自动加入媒体库",
|
|
"estimate_message": "小目录通常几十秒;几万文件的大目录可能需要数分钟到数小时,取决于网盘接口速度",
|
|
})
|
|
}
|
|
go func(libraryID string) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Hour)
|
|
defer cancel()
|
|
if _, err := svc.Scan.ScanLibraryWithoutAutoScrape(ctx, libraryID); err != nil {
|
|
if svc.Log != nil {
|
|
svc.Log.Warn("cloud library async scan failed", zap.String("library_id", libraryID), zap.Error(err))
|
|
}
|
|
if svc.WSHub != nil {
|
|
svc.WSHub.Publish("scan", gin.H{
|
|
"library_id": libraryID,
|
|
"cloud": true,
|
|
"finished": true,
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
}
|
|
}(id)
|
|
c.JSON(http.StatusAccepted, gin.H{
|
|
"library_id": id,
|
|
"visited": 0,
|
|
"added": 0,
|
|
"updated": 0,
|
|
"probed": 0,
|
|
"queued": true,
|
|
"cloud": true,
|
|
"message": "云盘扫描已在后台运行,发现的媒体会自动加入当前媒体库",
|
|
"estimate_message": "小目录通常几十秒;几万文件的大目录可能需要数分钟到数小时,取决于网盘接口速度",
|
|
})
|
|
return
|
|
}
|
|
res, err := svc.Scan.ScanLibrary(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, res)
|
|
}
|
|
}
|
|
|
|
func listMediaHandler(svc *service.Container) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.Param("id")
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
size, _ := strconv.Atoi(c.DefaultQuery("page_size", "50"))
|
|
items, total, err := svc.Media.ListMediaVisible(c.Request.Context(), id, page, size, mediaVisibilityForRequest(c, svc))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"items": items,
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": size,
|
|
})
|
|
}
|
|
}
|
|
|
|
func getMediaHandler(svc *service.Container) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
m, err := svc.Media.GetMedia(c.Request.Context(), c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if m == nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
|
return
|
|
}
|
|
if !mediaVisibleForRequest(c, svc, m) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, m)
|
|
}
|
|
}
|
|
|
|
func searchMediaHandler(svc *service.Container) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
q := c.Query("q")
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
|
|
items, err := svc.Media.SearchMediaVisible(c.Request.Context(), q, limit, mediaVisibilityForRequest(c, svc))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"items": items})
|
|
}
|
|
}
|
|
|
|
func streamHandler(svc *service.Container) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
m, err := svc.Media.GetMedia(c.Request.Context(), c.Param("id"))
|
|
if err != nil || m == nil || !mediaVisibleForRequest(c, svc, m) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
|
return
|
|
}
|
|
err = svc.Stream.ServeFile(c.Writer, c.Request, c.Param("id"))
|
|
if errors.Is(err, service.ErrMediaNotFound) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
|
return
|
|
}
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
}
|
|
}
|