194 lines
4.9 KiB
Go
194 lines
4.9 KiB
Go
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)
|
|
}
|