328 lines
8.0 KiB
Go
328 lines
8.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"seanime/internal/database/db_bridge"
|
|
"seanime/internal/library/anime"
|
|
"seanime/internal/library/filesystem"
|
|
"time"
|
|
|
|
"github.com/goccy/go-json"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/samber/lo"
|
|
"github.com/sourcegraph/conc/pool"
|
|
)
|
|
|
|
// HandleGetLocalFiles
|
|
//
|
|
// @summary returns all local files.
|
|
// @desc Reminder that local files are scanned from the library path.
|
|
// @route /api/v1/library/local-files [GET]
|
|
// @returns []anime.LocalFile
|
|
func (h *Handler) HandleGetLocalFiles(c echo.Context) error {
|
|
|
|
lfs, _, err := db_bridge.GetLocalFiles(h.App.Database)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
return h.RespondWithData(c, lfs)
|
|
}
|
|
|
|
func (h *Handler) HandleDumpLocalFilesToFile(c echo.Context) error {
|
|
|
|
lfs, _, err := db_bridge.GetLocalFiles(h.App.Database)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
filename := fmt.Sprintf("seanime-localfiles-%s.json", time.Now().Format("2006-01-02_15-04-05"))
|
|
|
|
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
|
|
c.Response().Header().Set("Content-Type", "application/json")
|
|
|
|
jsonData, err := json.MarshalIndent(lfs, "", " ")
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
return c.Blob(200, "application/json", jsonData)
|
|
}
|
|
|
|
// HandleImportLocalFiles
|
|
//
|
|
// @summary imports local files from the given path.
|
|
// @desc This will import local files from the given path.
|
|
// @desc The response is ignored, the client should refetch the entire library collection and media entry.
|
|
// @route /api/v1/library/local-files/import [POST]
|
|
func (h *Handler) HandleImportLocalFiles(c echo.Context) error {
|
|
type body struct {
|
|
DataFilePath string `json:"dataFilePath"`
|
|
}
|
|
|
|
b := new(body)
|
|
if err := c.Bind(b); err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
contentB, err := os.ReadFile(b.DataFilePath)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
var lfs []*anime.LocalFile
|
|
if err := json.Unmarshal(contentB, &lfs); err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
if len(lfs) == 0 {
|
|
return h.RespondWithError(c, errors.New("no local files found"))
|
|
}
|
|
|
|
_, err = db_bridge.InsertLocalFiles(h.App.Database, lfs)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
h.App.Database.TrimLocalFileEntries()
|
|
|
|
return h.RespondWithData(c, true)
|
|
}
|
|
|
|
// HandleLocalFileBulkAction
|
|
//
|
|
// @summary performs an action on all local files.
|
|
// @desc This will perform the given action on all local files.
|
|
// @desc The response is ignored, the client should refetch the entire library collection and media entry.
|
|
// @route /api/v1/library/local-files [POST]
|
|
// @returns []anime.LocalFile
|
|
func (h *Handler) HandleLocalFileBulkAction(c echo.Context) error {
|
|
|
|
type body struct {
|
|
Action string `json:"action"`
|
|
}
|
|
|
|
b := new(body)
|
|
if err := c.Bind(b); err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
// Get all the local files
|
|
lfs, lfsId, err := db_bridge.GetLocalFiles(h.App.Database)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
switch b.Action {
|
|
case "lock":
|
|
for _, lf := range lfs {
|
|
// Note: Don't lock local files that are not associated with a media.
|
|
// Else refreshing the library will ignore them.
|
|
if lf.MediaId != 0 {
|
|
lf.Locked = true
|
|
}
|
|
}
|
|
case "unlock":
|
|
for _, lf := range lfs {
|
|
lf.Locked = false
|
|
}
|
|
}
|
|
|
|
// Save the local files
|
|
retLfs, err := db_bridge.SaveLocalFiles(h.App.Database, lfsId, lfs)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
return h.RespondWithData(c, retLfs)
|
|
}
|
|
|
|
// HandleUpdateLocalFileData
|
|
//
|
|
// @summary updates the local file with the given path.
|
|
// @desc This will update the local file with the given path.
|
|
// @desc The response is ignored, the client should refetch the entire library collection and media entry.
|
|
// @route /api/v1/library/local-file [PATCH]
|
|
// @returns []anime.LocalFile
|
|
func (h *Handler) HandleUpdateLocalFileData(c echo.Context) error {
|
|
|
|
type body struct {
|
|
Path string `json:"path"`
|
|
Metadata *anime.LocalFileMetadata `json:"metadata"`
|
|
Locked bool `json:"locked"`
|
|
Ignored bool `json:"ignored"`
|
|
MediaId int `json:"mediaId"`
|
|
}
|
|
|
|
b := new(body)
|
|
if err := c.Bind(b); err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
// Get all the local files
|
|
lfs, lfsId, err := db_bridge.GetLocalFiles(h.App.Database)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
lf, found := lo.Find(lfs, func(i *anime.LocalFile) bool {
|
|
return i.HasSamePath(b.Path)
|
|
})
|
|
if !found {
|
|
return h.RespondWithError(c, errors.New("local file not found"))
|
|
}
|
|
lf.Metadata = b.Metadata
|
|
lf.Locked = b.Locked
|
|
lf.Ignored = b.Ignored
|
|
lf.MediaId = b.MediaId
|
|
|
|
// Save the local files
|
|
retLfs, err := db_bridge.SaveLocalFiles(h.App.Database, lfsId, lfs)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
return h.RespondWithData(c, retLfs)
|
|
}
|
|
|
|
// HandleUpdateLocalFiles
|
|
//
|
|
// @summary updates local files with the given paths.
|
|
// @desc The client should refetch the entire library collection and media entry.
|
|
// @route /api/v1/library/local-files [PATCH]
|
|
// @returns bool
|
|
func (h *Handler) HandleUpdateLocalFiles(c echo.Context) error {
|
|
|
|
type body struct {
|
|
Paths []string `json:"paths"`
|
|
Action string `json:"action"`
|
|
MediaId int `json:"mediaId,omitempty"`
|
|
}
|
|
|
|
b := new(body)
|
|
if err := c.Bind(b); err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
// Get all the local files
|
|
lfs, lfsId, err := db_bridge.GetLocalFiles(h.App.Database)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
// Update the files
|
|
for _, path := range b.Paths {
|
|
lf, found := lo.Find(lfs, func(i *anime.LocalFile) bool {
|
|
return i.HasSamePath(path)
|
|
})
|
|
if !found {
|
|
continue
|
|
}
|
|
switch b.Action {
|
|
case "lock":
|
|
lf.Locked = true
|
|
case "unlock":
|
|
lf.Locked = false
|
|
case "ignore":
|
|
lf.MediaId = 0
|
|
lf.Ignored = true
|
|
lf.Locked = false
|
|
case "unignore":
|
|
lf.Ignored = false
|
|
lf.Locked = false
|
|
case "unmatch":
|
|
lf.MediaId = 0
|
|
lf.Locked = false
|
|
lf.Ignored = false
|
|
case "match":
|
|
lf.MediaId = b.MediaId
|
|
lf.Locked = true
|
|
lf.Ignored = false
|
|
}
|
|
}
|
|
|
|
// Save the local files
|
|
_, err = db_bridge.SaveLocalFiles(h.App.Database, lfsId, lfs)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
return h.RespondWithData(c, true)
|
|
}
|
|
|
|
// HandleDeleteLocalFiles
|
|
//
|
|
// @summary deletes local files with the given paths.
|
|
// @desc This will delete the local files with the given paths.
|
|
// @desc The client should refetch the entire library collection and media entry.
|
|
// @route /api/v1/library/local-files [DELETE]
|
|
// @returns bool
|
|
func (h *Handler) HandleDeleteLocalFiles(c echo.Context) error {
|
|
|
|
type body struct {
|
|
Paths []string `json:"paths"`
|
|
}
|
|
|
|
b := new(body)
|
|
if err := c.Bind(b); err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
// Get all the local files
|
|
lfs, lfsId, err := db_bridge.GetLocalFiles(h.App.Database)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
// Delete the files
|
|
p := pool.New().WithErrors()
|
|
for _, path := range b.Paths {
|
|
path := path
|
|
p.Go(func() error {
|
|
err := os.Remove(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
if err := p.Wait(); err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
// Remove the files from the list
|
|
lfs = lo.Filter(lfs, func(i *anime.LocalFile, _ int) bool {
|
|
return !lo.Contains(b.Paths, i.Path)
|
|
})
|
|
|
|
// Save the local files
|
|
_, err = db_bridge.SaveLocalFiles(h.App.Database, lfsId, lfs)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
return h.RespondWithData(c, true)
|
|
}
|
|
|
|
// HandleRemoveEmptyDirectories
|
|
//
|
|
// @summary removes empty directories.
|
|
// @desc This will remove empty directories in the library path.
|
|
// @route /api/v1/library/empty-directories [DELETE]
|
|
// @returns bool
|
|
func (h *Handler) HandleRemoveEmptyDirectories(c echo.Context) error {
|
|
|
|
libraryPaths, err := h.App.Database.GetAllLibraryPathsFromSettings()
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
for _, path := range libraryPaths {
|
|
filesystem.RemoveEmptyDirectories(path, h.App.Logger)
|
|
}
|
|
|
|
return h.RespondWithData(c, true)
|
|
}
|