node build fixed
This commit is contained in:
67
seanime-2.9.10/internal/library/filesystem/clean.go
Normal file
67
seanime-2.9.10/internal/library/filesystem/clean.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/rs/zerolog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// RemoveEmptyDirectories deletes all empty directories in a given directory.
|
||||
// It ignores errors.
|
||||
func RemoveEmptyDirectories(root string, logger *zerolog.Logger) {
|
||||
|
||||
_ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip the root directory
|
||||
if path == root {
|
||||
return nil
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
// Check if the directory is empty
|
||||
isEmpty, err := isDirectoryEmpty(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete the empty directory
|
||||
if isEmpty {
|
||||
err := os.Remove(path)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Str("path", path).Msg("filesystem: Could not delete empty directory")
|
||||
}
|
||||
logger.Info().Str("path", path).Msg("filesystem: Deleted empty directory")
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func isDirectoryEmpty(path string) (bool, error) {
|
||||
dir, err := os.Open(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
_, err = dir.Readdir(1)
|
||||
if err == nil {
|
||||
// Directory is not empty
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// Directory does not exist
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Directory is empty
|
||||
return true, nil
|
||||
}
|
||||
16
seanime-2.9.10/internal/library/filesystem/clean_test.go
Normal file
16
seanime-2.9.10/internal/library/filesystem/clean_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"seanime/internal/util"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeleteEmptyDirectories(t *testing.T) {
|
||||
|
||||
path := "E:/ANIME_TEST"
|
||||
|
||||
RemoveEmptyDirectories(path, util.NewLogger())
|
||||
|
||||
t.Log("All empty directories removed successfully.")
|
||||
|
||||
}
|
||||
193
seanime-2.9.10/internal/library/filesystem/mediapath.go
Normal file
193
seanime-2.9.10/internal/library/filesystem/mediapath.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"seanime/internal/util"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SeparatedFilePath struct {
|
||||
Filename string
|
||||
Dirnames []string
|
||||
PrefixPath string
|
||||
}
|
||||
|
||||
// SeparateFilePath separates a path into a filename and a slice of dirnames while ignoring the prefix.
|
||||
func SeparateFilePath(path string, prefixPath string) *SeparatedFilePath {
|
||||
path = filepath.ToSlash(path)
|
||||
prefixPath = filepath.ToSlash(prefixPath)
|
||||
cleaned := path
|
||||
if strings.HasPrefix(strings.ToLower(path), strings.ToLower(prefixPath)) {
|
||||
cleaned = path[len(prefixPath):] // Remove prefix
|
||||
}
|
||||
fp := filepath.Base(filepath.ToSlash(path))
|
||||
parentsPath := filepath.Dir(filepath.ToSlash(cleaned))
|
||||
if parentsPath == "." || parentsPath == "/" || parentsPath == ".." {
|
||||
parentsPath = ""
|
||||
}
|
||||
|
||||
return &SeparatedFilePath{
|
||||
Filename: fp,
|
||||
Dirnames: strings.Split(parentsPath, "/"),
|
||||
PrefixPath: prefixPath,
|
||||
}
|
||||
}
|
||||
|
||||
// SeparateFilePathS separates a path into a filename and a slice of dirnames while ignoring the prefix.
|
||||
// Unlike [SeparateFilePath], it will check multiple prefixes.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// path = "/path/to/file.mkv"
|
||||
// potentialPrefixes = []string{"/path/to", "/path"}
|
||||
// fp, dirs := SeparateFilePathS(path, potentialPrefixes)
|
||||
// fmt.Println(fp) // file.mkv
|
||||
// fmt.Println(dirs) // [to]
|
||||
func SeparateFilePathS(path string, potentialPrefixes []string) *SeparatedFilePath {
|
||||
// Sort prefix paths by length in descending order
|
||||
sort.Slice(potentialPrefixes, func(i, j int) bool {
|
||||
return len(potentialPrefixes[i]) > len(potentialPrefixes[j])
|
||||
})
|
||||
|
||||
// Check each prefix path, and remove the first match from the path
|
||||
prefixPath := ""
|
||||
for _, p := range potentialPrefixes {
|
||||
// Normalize the paths for comparison only
|
||||
if strings.HasPrefix(util.NormalizePath(path), util.NormalizePath(p)) {
|
||||
// Remove the prefix from the path
|
||||
path = path[len(p):]
|
||||
prefixPath = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
filename := filepath.ToSlash(filepath.Base(path))
|
||||
parentsPath := filepath.ToSlash(filepath.Dir(filepath.ToSlash(path)))
|
||||
|
||||
dirs := make([]string, 0)
|
||||
for _, dir := range strings.Split(parentsPath, "/") {
|
||||
if dir != "" {
|
||||
dirs = append(dirs, dir)
|
||||
}
|
||||
}
|
||||
|
||||
return &SeparatedFilePath{
|
||||
Filename: filename,
|
||||
Dirnames: dirs,
|
||||
PrefixPath: prefixPath,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMediaFilePathsFromDir returns a slice of strings containing the paths of all the media files in a directory.
|
||||
// DEPRECATED: Use GetMediaFilePathsFromDirS instead.
|
||||
func GetMediaFilePathsFromDir(dirPath string) ([]string, error) {
|
||||
filePaths := make([]string, 0)
|
||||
|
||||
err := filepath.WalkDir(dirPath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
|
||||
if !d.IsDir() && util.IsValidVideoExtension(ext) {
|
||||
filePaths = append(filePaths, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.New("could not traverse the local directory")
|
||||
}
|
||||
|
||||
return filePaths, nil
|
||||
}
|
||||
|
||||
// GetMediaFilePathsFromDirS returns a slice of strings containing the paths of all the video files in a directory.
|
||||
// Unlike GetMediaFilePathsFromDir, it follows symlinks.
|
||||
func GetMediaFilePathsFromDirS(oDirPath string) ([]string, error) {
|
||||
filePaths := make([]string, 0)
|
||||
visited := make(map[string]bool)
|
||||
|
||||
// Normalize the initial directory path
|
||||
dirPath, err := filepath.Abs(oDirPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve path: %w", err)
|
||||
}
|
||||
|
||||
var walkDir func(string) error
|
||||
walkDir = func(oCurrentPath string) error {
|
||||
|
||||
currentPath := oCurrentPath
|
||||
|
||||
// Normalize current path
|
||||
resolvedPath, err := filepath.EvalSymlinks(oCurrentPath)
|
||||
if err == nil {
|
||||
currentPath = resolvedPath
|
||||
}
|
||||
|
||||
if visited[currentPath] {
|
||||
return nil
|
||||
}
|
||||
visited[currentPath] = true
|
||||
|
||||
return filepath.WalkDir(currentPath, func(path string, d fs.DirEntry, err error) error {
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If it's a symlink directory, resolve and walk the symlink
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
linkPath, err := os.Readlink(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolve the symlink to an absolute path
|
||||
if !filepath.IsAbs(linkPath) {
|
||||
linkPath = filepath.Join(filepath.Dir(path), linkPath)
|
||||
}
|
||||
|
||||
// Only follow the symlink if we can access it
|
||||
if _, err := os.Stat(linkPath); err == nil {
|
||||
return walkDir(linkPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
if util.IsValidMediaFile(path) && util.IsValidVideoExtension(ext) {
|
||||
filePaths = append(filePaths, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err = walkDir(dirPath); err != nil {
|
||||
return nil, fmt.Errorf("could not traverse directory %s: %w", dirPath, err)
|
||||
}
|
||||
|
||||
return filePaths, nil
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
func FileExists(filePath string) bool {
|
||||
_, err := os.Stat(filePath)
|
||||
return !errors.Is(err, os.ErrNotExist)
|
||||
}
|
||||
133
seanime-2.9.10/internal/library/filesystem/mediapath_test.go
Normal file
133
seanime-2.9.10/internal/library/filesystem/mediapath_test.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"seanime/internal/util"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSeparateFilePathS(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
potentialPrefixes []string
|
||||
expected *SeparatedFilePath
|
||||
}{
|
||||
{
|
||||
path: "/path/to/file.mkv",
|
||||
potentialPrefixes: []string{"/path/to", "/path"},
|
||||
expected: &SeparatedFilePath{Filename: "file.mkv", Dirnames: []string{}},
|
||||
},
|
||||
{
|
||||
path: "/path/TO/to/file.mkv",
|
||||
potentialPrefixes: []string{"/path"},
|
||||
expected: &SeparatedFilePath{Filename: "file.mkv", Dirnames: []string{"TO", "to"}},
|
||||
},
|
||||
{
|
||||
path: "/path/to/file2.mkv",
|
||||
potentialPrefixes: []string{},
|
||||
expected: &SeparatedFilePath{Filename: "file2.mkv", Dirnames: []string{"path", "to"}},
|
||||
},
|
||||
{
|
||||
path: "/mnt/Anime/Bungou Stray Dogs/Bungou Stray Dogs 5th Season/[SubsPlease] Bungou Stray Dogs - 61 (1080p) [F609B947].mkv",
|
||||
potentialPrefixes: []string{"/mnt/Anime", "/mnt/Anime/", "/mnt", "/var/"},
|
||||
expected: &SeparatedFilePath{Filename: "[SubsPlease] Bungou Stray Dogs - 61 (1080p) [F609B947].mkv", Dirnames: []string{"Bungou Stray Dogs", "Bungou Stray Dogs 5th Season"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.path, func(t *testing.T) {
|
||||
fmt.Println("Here")
|
||||
res := SeparateFilePathS(tt.path, tt.potentialPrefixes)
|
||||
assert.Equal(t, tt.expected.Filename, res.Filename)
|
||||
assert.Equal(t, tt.expected.Dirnames, res.Dirnames)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test with symlinks
|
||||
func TestGetVideoFilePathsFromDir_WithSymlinks(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
libDir := filepath.Join(tmpDir, "library")
|
||||
externalLibDir := t.TempDir()
|
||||
os.Mkdir(libDir, 0755)
|
||||
// Create files in the external directory
|
||||
createFile(t, filepath.Join(externalLibDir, "external_video1.mkv"))
|
||||
createFile(t, filepath.Join(externalLibDir, "external_video2.mp4"))
|
||||
|
||||
// Create directories and files
|
||||
dir1 := filepath.Join(libDir, "Anime1")
|
||||
os.Mkdir(dir1, 0755)
|
||||
createFile(t, filepath.Join(dir1, "Anime1_1.mkv"))
|
||||
createFile(t, filepath.Join(dir1, "Anime1_2.mp4"))
|
||||
|
||||
dir2 := filepath.Join(libDir, "Anime2")
|
||||
os.Mkdir(dir2, 0755)
|
||||
createFile(t, filepath.Join(dir2, "Anime2_1.mkv"))
|
||||
|
||||
// Create a symlink to the external directory
|
||||
symlinkPath := filepath.Join(libDir, "symlink_to_external")
|
||||
if err := os.Symlink(externalLibDir, symlinkPath); err != nil {
|
||||
t.Fatalf("Failed to create symlink: %s", err)
|
||||
}
|
||||
// Create a recursive symlink to the library directory
|
||||
symlinkToLibPath := filepath.Join(externalLibDir, "symlink_to_library")
|
||||
if err := os.Symlink(libDir, symlinkToLibPath); err != nil {
|
||||
t.Fatalf("Failed to create symlink: %s", err)
|
||||
}
|
||||
|
||||
// Expected files
|
||||
expectedPaths := []string{
|
||||
filepath.Join(dir1, "Anime1_1.mkv"),
|
||||
filepath.Join(dir1, "Anime1_2.mp4"),
|
||||
filepath.Join(dir2, "Anime2_1.mkv"),
|
||||
filepath.Join(externalLibDir, "external_video1.mkv"),
|
||||
filepath.Join(externalLibDir, "external_video2.mp4"),
|
||||
}
|
||||
|
||||
filePaths, err := GetMediaFilePathsFromDirS(libDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
util.Spew(filePaths)
|
||||
|
||||
// Check results
|
||||
for _, expected := range expectedPaths {
|
||||
found := false
|
||||
for _, path := range filePaths {
|
||||
// if path == expected {
|
||||
// found = true
|
||||
// break
|
||||
// }
|
||||
// Compare the paths using stdlib
|
||||
info1, err := os.Stat(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get file info for %s: %s", path, err)
|
||||
}
|
||||
info2, err := os.Stat(expected)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get file info for %s: %s", expected, err)
|
||||
}
|
||||
if os.SameFile(info1, info2) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected file path %s not found in result", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createFile(t *testing.T, path string) {
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create file: %s", err)
|
||||
}
|
||||
defer file.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user