2019-01-05 22:44:33 +00:00
|
|
|
package files
|
|
|
|
|
|
|
|
import (
|
2024-09-16 21:01:16 +00:00
|
|
|
"crypto/md5"
|
|
|
|
"crypto/sha1"
|
2019-01-05 22:44:33 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/hex"
|
2024-08-24 22:02:33 +00:00
|
|
|
"fmt"
|
2019-01-05 22:44:33 +00:00
|
|
|
"hash"
|
|
|
|
"io"
|
|
|
|
"mime"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2024-08-24 22:02:33 +00:00
|
|
|
"path/filepath"
|
2019-01-05 22:44:33 +00:00
|
|
|
"strings"
|
2023-12-01 23:47:00 +00:00
|
|
|
"sync"
|
2019-01-05 22:44:33 +00:00
|
|
|
"time"
|
2024-02-10 00:13:02 +00:00
|
|
|
"unicode/utf8"
|
2019-01-05 22:44:33 +00:00
|
|
|
|
2023-06-15 01:08:09 +00:00
|
|
|
"github.com/gtsteffaniak/filebrowser/errors"
|
2024-11-21 00:15:30 +00:00
|
|
|
"github.com/gtsteffaniak/filebrowser/fileutils"
|
2024-08-24 22:02:33 +00:00
|
|
|
"github.com/gtsteffaniak/filebrowser/settings"
|
2024-11-21 00:15:30 +00:00
|
|
|
"github.com/gtsteffaniak/filebrowser/users"
|
2023-12-01 23:47:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2024-10-07 22:44:53 +00:00
|
|
|
pathMutexes = make(map[string]*sync.Mutex)
|
|
|
|
pathMutexesMu sync.Mutex // Mutex to protect the pathMutexes map
|
2019-01-05 22:44:33 +00:00
|
|
|
)
|
|
|
|
|
2024-10-07 22:44:53 +00:00
|
|
|
type ReducedItem struct {
|
2024-11-21 00:15:30 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Size int64 `json:"size"`
|
|
|
|
ModTime time.Time `json:"modified"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Mode os.FileMode `json:"-"`
|
|
|
|
Content string `json:"content,omitempty"`
|
2024-10-07 22:44:53 +00:00
|
|
|
}
|
|
|
|
|
2019-01-05 22:44:33 +00:00
|
|
|
// FileInfo describes a file.
|
2024-10-07 22:44:53 +00:00
|
|
|
// reduced item is non-recursive reduced "Items", used to pass flat items array
|
2019-01-05 22:44:33 +00:00
|
|
|
type FileInfo struct {
|
2024-11-21 00:15:30 +00:00
|
|
|
Files []ReducedItem `json:"-"`
|
|
|
|
Dirs map[string]*FileInfo `json:"-"`
|
|
|
|
Path string `json:"path"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Items []ReducedItem `json:"items"`
|
|
|
|
Size int64 `json:"size"`
|
|
|
|
Extension string `json:"-"`
|
|
|
|
ModTime time.Time `json:"modified"`
|
|
|
|
CacheTime time.Time `json:"-"`
|
|
|
|
Mode os.FileMode `json:"-"`
|
|
|
|
IsSymlink bool `json:"isSymlink,omitempty"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Subtitles []string `json:"subtitles,omitempty"`
|
|
|
|
Content string `json:"content,omitempty"`
|
|
|
|
Checksums map[string]string `json:"checksums,omitempty"`
|
|
|
|
Token string `json:"token,omitempty"`
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// FileOptions are the options when getting a file info.
|
|
|
|
type FileOptions struct {
|
2024-08-24 22:02:33 +00:00
|
|
|
Path string // realpath
|
2024-09-16 21:01:16 +00:00
|
|
|
IsDir bool
|
2021-01-07 10:30:17 +00:00
|
|
|
Modify bool
|
|
|
|
Expand bool
|
|
|
|
ReadHeader bool
|
2021-03-02 11:00:18 +00:00
|
|
|
Token string
|
2024-11-21 00:15:30 +00:00
|
|
|
Checker users.Checker
|
2021-04-23 12:04:02 +00:00
|
|
|
Content bool
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
func (f FileOptions) Components() (string, string) {
|
|
|
|
return filepath.Dir(f.Path), filepath.Base(f.Path)
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
2024-10-07 22:44:53 +00:00
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
|
2024-11-21 00:15:30 +00:00
|
|
|
index := GetIndex(rootPath)
|
|
|
|
opts.Path = index.makeIndexPath(opts.Path)
|
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
// Lock access for the specific path
|
|
|
|
pathMutex := getMutex(opts.Path)
|
|
|
|
pathMutex.Lock()
|
|
|
|
defer pathMutex.Unlock()
|
|
|
|
if !opts.Checker.Check(opts.Path) {
|
|
|
|
return nil, os.ErrPermission
|
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
_, isDir, err := GetRealPath(opts.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
opts.IsDir = isDir
|
|
|
|
// check if the file exists in the index
|
|
|
|
info, exists := index.GetReducedMetadata(opts.Path, opts.IsDir)
|
|
|
|
if exists {
|
|
|
|
// Let's not refresh if less than a second has passed
|
|
|
|
if time.Since(info.CacheTime) > time.Second {
|
|
|
|
RefreshFileInfo(opts) //nolint:errcheck
|
|
|
|
}
|
|
|
|
if opts.Content {
|
|
|
|
content := ""
|
|
|
|
content, err = getContent(opts.Path)
|
|
|
|
if err != nil {
|
|
|
|
return info, err
|
2024-09-16 21:01:16 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
info.Content = content
|
2024-09-16 21:01:16 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
return info, nil
|
2024-09-16 21:01:16 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
err = RefreshFileInfo(opts)
|
2024-09-16 21:01:16 +00:00
|
|
|
if err != nil {
|
2024-11-21 00:15:30 +00:00
|
|
|
return nil, err
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
info, exists = index.GetReducedMetadata(opts.Path, opts.IsDir)
|
|
|
|
if !exists {
|
|
|
|
return nil, err
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
if opts.Content {
|
|
|
|
content, err := getContent(opts.Path)
|
|
|
|
if err != nil {
|
|
|
|
return info, err
|
|
|
|
}
|
|
|
|
info.Content = content
|
|
|
|
}
|
|
|
|
return info, nil
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
|
|
|
|
2024-09-16 21:01:16 +00:00
|
|
|
func RefreshFileInfo(opts FileOptions) error {
|
2024-11-21 00:15:30 +00:00
|
|
|
refreshOptions := FileOptions{
|
|
|
|
Path: opts.Path,
|
|
|
|
IsDir: opts.IsDir,
|
|
|
|
Token: opts.Token,
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
|
|
|
index := GetIndex(rootPath)
|
2024-11-21 00:15:30 +00:00
|
|
|
|
|
|
|
if !refreshOptions.IsDir {
|
|
|
|
refreshOptions.Path = index.makeIndexPath(filepath.Dir(refreshOptions.Path))
|
|
|
|
refreshOptions.IsDir = true
|
|
|
|
} else {
|
|
|
|
refreshOptions.Path = index.makeIndexPath(refreshOptions.Path)
|
2024-02-10 00:13:02 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
|
|
|
|
current, exists := index.GetMetadataInfo(refreshOptions.Path, true)
|
|
|
|
|
|
|
|
file, err := stat(refreshOptions)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("file/folder does not exist to refresh data: %s", refreshOptions.Path)
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
|
|
|
|
//utils.PrintStructFields(*file)
|
|
|
|
result := index.UpdateMetadata(file)
|
2024-09-16 21:01:16 +00:00
|
|
|
if !result {
|
2024-11-21 00:15:30 +00:00
|
|
|
return fmt.Errorf("file/folder does not exist in metadata: %s", refreshOptions.Path)
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if current.Size != file.Size {
|
|
|
|
index.recursiveUpdateDirSizes(filepath.Dir(refreshOptions.Path), file, current.Size)
|
2024-09-16 21:01:16 +00:00
|
|
|
}
|
|
|
|
return nil
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
|
|
|
|
2024-09-16 21:01:16 +00:00
|
|
|
func stat(opts FileOptions) (*FileInfo, error) {
|
2024-11-21 00:15:30 +00:00
|
|
|
realPath, _, err := GetRealPath(rootPath, opts.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
info, err := os.Lstat(realPath)
|
2024-08-24 22:02:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
2024-08-24 22:02:33 +00:00
|
|
|
file := &FileInfo{
|
|
|
|
Path: opts.Path,
|
2024-11-21 00:15:30 +00:00
|
|
|
Name: filepath.Base(opts.Path),
|
2024-08-24 22:02:33 +00:00
|
|
|
ModTime: info.ModTime(),
|
|
|
|
Mode: info.Mode(),
|
|
|
|
Size: info.Size(),
|
|
|
|
Extension: filepath.Ext(info.Name()),
|
|
|
|
Token: opts.Token,
|
|
|
|
}
|
|
|
|
if info.IsDir() {
|
2024-11-21 00:15:30 +00:00
|
|
|
// Open and read directory contents
|
|
|
|
dir, err := os.Open(realPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-07-26 10:59:09 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
defer dir.Close()
|
|
|
|
|
|
|
|
dirInfo, err := dir.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
index := GetIndex(rootPath)
|
|
|
|
// Check cached metadata to decide if refresh is needed
|
|
|
|
cachedParentDir, exists := index.GetMetadataInfo(opts.Path, true)
|
|
|
|
if exists && dirInfo.ModTime().Before(cachedParentDir.CacheTime) {
|
|
|
|
return cachedParentDir, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read directory contents and process
|
|
|
|
files, err := dir.Readdir(-1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
file.Files = []ReducedItem{}
|
|
|
|
file.Dirs = map[string]*FileInfo{}
|
|
|
|
|
|
|
|
var totalSize int64
|
|
|
|
for _, item := range files {
|
|
|
|
itemPath := filepath.Join(realPath, item.Name())
|
2021-07-26 10:59:09 +00:00
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
if item.IsDir() {
|
|
|
|
itemInfo := &FileInfo{
|
|
|
|
Name: item.Name(),
|
|
|
|
ModTime: item.ModTime(),
|
|
|
|
Mode: item.Mode(),
|
|
|
|
}
|
|
|
|
|
|
|
|
if exists {
|
|
|
|
// if directory size was already cached use that.
|
|
|
|
cachedDir, ok := cachedParentDir.Dirs[item.Name()]
|
|
|
|
if ok {
|
|
|
|
itemInfo.Size = cachedDir.Size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
file.Dirs[item.Name()] = itemInfo
|
|
|
|
totalSize += itemInfo.Size
|
|
|
|
} else {
|
|
|
|
itemInfo := ReducedItem{
|
|
|
|
Name: item.Name(),
|
|
|
|
Size: item.Size(),
|
|
|
|
ModTime: item.ModTime(),
|
|
|
|
Mode: item.Mode(),
|
|
|
|
}
|
|
|
|
if IsSymlink(item.Mode()) {
|
|
|
|
itemInfo.Type = "symlink"
|
|
|
|
info, err := os.Stat(itemPath)
|
|
|
|
if err == nil {
|
|
|
|
itemInfo.Name = info.Name()
|
|
|
|
itemInfo.ModTime = info.ModTime()
|
|
|
|
itemInfo.Size = info.Size()
|
|
|
|
itemInfo.Mode = info.Mode()
|
|
|
|
} else {
|
|
|
|
file.Type = "invalid_link"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if file.Type != "invalid_link" {
|
|
|
|
err := itemInfo.detectType(itemPath, true, opts.Content, opts.ReadHeader)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("failed to detect type for %v: %v \n", itemPath, err)
|
|
|
|
}
|
|
|
|
file.Files = append(file.Files, itemInfo)
|
|
|
|
}
|
|
|
|
totalSize += itemInfo.Size
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
file.Size = totalSize
|
|
|
|
}
|
2021-07-26 10:59:09 +00:00
|
|
|
return file, nil
|
|
|
|
}
|
|
|
|
|
2019-01-05 22:44:33 +00:00
|
|
|
// Checksum checksums a given File for a given User, using a specific
|
|
|
|
// algorithm. The checksums data is saved on File object.
|
|
|
|
func (i *FileInfo) Checksum(algo string) error {
|
|
|
|
|
|
|
|
if i.Checksums == nil {
|
|
|
|
i.Checksums = map[string]string{}
|
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
fullpath := filepath.Join(i.Path, i.Name)
|
|
|
|
reader, err := os.Open(fullpath)
|
2019-01-05 22:44:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer reader.Close()
|
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
hashFuncs := map[string]hash.Hash{
|
|
|
|
"md5": md5.New(),
|
|
|
|
"sha1": sha1.New(),
|
|
|
|
"sha256": sha256.New(),
|
|
|
|
"sha512": sha512.New(),
|
|
|
|
}
|
|
|
|
|
|
|
|
h, ok := hashFuncs[algo]
|
|
|
|
if !ok {
|
2019-01-05 22:44:33 +00:00
|
|
|
return errors.ErrInvalidOption
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = io.Copy(h, reader)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
i.Checksums[algo] = hex.EncodeToString(h.Sum(nil))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
// RealPath gets the real path for the file, resolving symlinks if supported.
|
2022-02-21 18:59:22 +00:00
|
|
|
func (i *FileInfo) RealPath() string {
|
2024-08-24 22:02:33 +00:00
|
|
|
realPath, err := filepath.EvalSymlinks(i.Path)
|
|
|
|
if err == nil {
|
|
|
|
return realPath
|
2022-02-21 18:59:22 +00:00
|
|
|
}
|
|
|
|
return i.Path
|
|
|
|
}
|
|
|
|
|
2024-09-16 21:01:16 +00:00
|
|
|
func GetRealPath(relativePath ...string) (string, bool, error) {
|
2024-08-24 22:02:33 +00:00
|
|
|
combined := []string{settings.Config.Server.Root}
|
|
|
|
for _, path := range relativePath {
|
|
|
|
combined = append(combined, strings.TrimPrefix(path, settings.Config.Server.Root))
|
|
|
|
}
|
|
|
|
joinedPath := filepath.Join(combined...)
|
|
|
|
// Convert relative path to absolute path
|
|
|
|
absolutePath, err := filepath.Abs(joinedPath)
|
|
|
|
if err != nil {
|
2024-11-21 00:15:30 +00:00
|
|
|
return absolutePath, false, fmt.Errorf("could not get real path: %v, %s", combined, err)
|
2024-08-24 22:02:33 +00:00
|
|
|
}
|
|
|
|
// Resolve symlinks and get the real path
|
|
|
|
return resolveSymlinks(absolutePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func DeleteFiles(absPath string, opts FileOptions) error {
|
|
|
|
err := os.RemoveAll(absPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-09-16 21:01:16 +00:00
|
|
|
err = RefreshFileInfo(opts)
|
2024-11-21 00:15:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func MoveResource(realsrc, realdst string, isSrcDir bool) error {
|
|
|
|
err := fileutils.MoveFile(realsrc, realdst)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// refresh info for source and dest
|
|
|
|
err = RefreshFileInfo(FileOptions{
|
|
|
|
Path: realsrc,
|
|
|
|
IsDir: isSrcDir,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return errors.ErrEmptyKey
|
|
|
|
}
|
|
|
|
refreshConfig := FileOptions{Path: realdst, IsDir: true}
|
|
|
|
if !isSrcDir {
|
|
|
|
refreshConfig.Path = filepath.Dir(realdst)
|
|
|
|
}
|
|
|
|
err = RefreshFileInfo(refreshConfig)
|
|
|
|
if err != nil {
|
|
|
|
return errors.ErrEmptyKey
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func CopyResource(realsrc, realdst string, isSrcDir bool) error {
|
|
|
|
err := fileutils.CopyFile(realsrc, realdst)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshConfig := FileOptions{Path: realdst, IsDir: true}
|
|
|
|
if !isSrcDir {
|
|
|
|
refreshConfig.Path = filepath.Dir(realdst)
|
|
|
|
}
|
|
|
|
err = RefreshFileInfo(refreshConfig)
|
2024-09-16 21:01:16 +00:00
|
|
|
if err != nil {
|
2024-08-24 22:02:33 +00:00
|
|
|
return errors.ErrEmptyKey
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func WriteDirectory(opts FileOptions) error {
|
2024-11-21 00:15:30 +00:00
|
|
|
realPath, _, _ := GetRealPath(rootPath, opts.Path)
|
2024-08-24 22:02:33 +00:00
|
|
|
// Ensure the parent directories exist
|
2024-11-21 00:15:30 +00:00
|
|
|
err := os.MkdirAll(realPath, 0775)
|
2024-08-24 22:02:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-09-16 21:01:16 +00:00
|
|
|
err = RefreshFileInfo(opts)
|
|
|
|
if err != nil {
|
2024-08-24 22:02:33 +00:00
|
|
|
return errors.ErrEmptyKey
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func WriteFile(opts FileOptions, in io.Reader) error {
|
|
|
|
dst := opts.Path
|
|
|
|
parentDir := filepath.Dir(dst)
|
|
|
|
// Split the directory from the destination path
|
|
|
|
dir := filepath.Dir(dst)
|
|
|
|
|
|
|
|
// Create the directory and all necessary parents
|
|
|
|
err := os.MkdirAll(dir, 0775)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the file for writing (create if it doesn't exist, truncate if it does)
|
|
|
|
file, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
// Copy the contents from the reader to the file
|
|
|
|
_, err = io.Copy(file, in)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
opts.Path = parentDir
|
2024-09-16 21:01:16 +00:00
|
|
|
err = RefreshFileInfo(opts)
|
|
|
|
if err != nil {
|
2024-08-24 22:02:33 +00:00
|
|
|
return errors.ErrEmptyKey
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// resolveSymlinks resolves symlinks in the given path
|
2024-09-16 21:01:16 +00:00
|
|
|
func resolveSymlinks(path string) (string, bool, error) {
|
2024-08-24 22:02:33 +00:00
|
|
|
for {
|
|
|
|
// Get the file info
|
|
|
|
info, err := os.Lstat(path)
|
|
|
|
if err != nil {
|
2024-11-21 00:15:30 +00:00
|
|
|
return path, false, fmt.Errorf("could not stat path: %v, %s", path, err)
|
2024-08-24 22:02:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if it's a symlink
|
|
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
|
|
// Read the symlink target
|
|
|
|
target, err := os.Readlink(path)
|
|
|
|
if err != nil {
|
2024-11-21 00:15:30 +00:00
|
|
|
return path, false, err
|
2024-08-24 22:02:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve the target relative to the symlink's directory
|
|
|
|
path = filepath.Join(filepath.Dir(path), target)
|
|
|
|
} else {
|
2024-09-16 21:01:16 +00:00
|
|
|
// Not a symlink, so return the resolved path and check if it's a directory
|
|
|
|
return path, info.IsDir(), nil
|
2024-08-24 22:02:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-10 00:13:02 +00:00
|
|
|
// addContent reads and sets content based on the file type.
|
2024-11-21 00:15:30 +00:00
|
|
|
func getContent(path string) (string, error) {
|
|
|
|
realPath, _, err := GetRealPath(rootPath, path)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2024-02-10 00:13:02 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
|
|
|
|
content, err := os.ReadFile(realPath)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
stringContent := string(content)
|
|
|
|
if !utf8.ValidString(stringContent) {
|
|
|
|
return "", fmt.Errorf("file is not utf8 encoded")
|
|
|
|
}
|
|
|
|
if stringContent == "" {
|
|
|
|
return "empty-file-x6OlSil", nil
|
|
|
|
}
|
|
|
|
return stringContent, nil
|
2024-02-10 00:13:02 +00:00
|
|
|
}
|
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
// detectType detects the file type.
|
2024-11-21 00:15:30 +00:00
|
|
|
func (i *ReducedItem) detectType(path string, modify, saveContent, readHeader bool) error {
|
|
|
|
name := i.Name
|
|
|
|
var contentErr error
|
|
|
|
var contentString string
|
|
|
|
if saveContent {
|
|
|
|
contentString, contentErr = getContent(path)
|
|
|
|
if contentErr == nil {
|
|
|
|
i.Content = contentString
|
|
|
|
}
|
2024-08-24 22:02:33 +00:00
|
|
|
}
|
2024-11-21 00:15:30 +00:00
|
|
|
|
2020-11-24 10:32:23 +00:00
|
|
|
if IsNamedPipe(i.Mode) {
|
|
|
|
i.Type = "blob"
|
2024-11-21 00:15:30 +00:00
|
|
|
return contentErr
|
2020-11-24 10:32:23 +00:00
|
|
|
}
|
2024-02-10 00:13:02 +00:00
|
|
|
|
2024-11-21 00:15:30 +00:00
|
|
|
ext := filepath.Ext(name)
|
2021-03-17 18:06:56 +00:00
|
|
|
var buffer []byte
|
|
|
|
if readHeader {
|
2024-11-21 00:15:30 +00:00
|
|
|
buffer = i.readFirstBytes(path)
|
|
|
|
mimetype := mime.TypeByExtension(ext)
|
2021-03-17 18:06:56 +00:00
|
|
|
if mimetype == "" {
|
2023-12-01 23:47:00 +00:00
|
|
|
http.DetectContentType(buffer)
|
2021-03-17 18:06:56 +00:00
|
|
|
}
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
2024-02-10 00:13:02 +00:00
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
for _, fileType := range AllFiletypeOptions {
|
|
|
|
if IsMatchingType(ext, fileType) {
|
|
|
|
i.Type = fileType
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
2023-12-01 23:47:00 +00:00
|
|
|
switch i.Type {
|
|
|
|
case "text":
|
|
|
|
if !modify {
|
|
|
|
i.Type = "textImmutable"
|
|
|
|
}
|
|
|
|
if saveContent {
|
2024-11-21 00:15:30 +00:00
|
|
|
return contentErr
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
|
|
|
case "video":
|
2024-11-21 00:15:30 +00:00
|
|
|
// TODO add back somewhere else, not during metadata fetch
|
|
|
|
//parentDir := strings.TrimRight(path, name)
|
|
|
|
//i.detectSubtitles(parentDir)
|
2023-12-01 23:47:00 +00:00
|
|
|
case "doc":
|
|
|
|
if ext == ".pdf" {
|
|
|
|
i.Type = "pdf"
|
2024-02-10 00:13:02 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if saveContent {
|
2024-11-21 00:15:30 +00:00
|
|
|
return nil
|
2019-01-06 09:24:09 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
|
|
|
if i.Type == "" {
|
2021-01-07 10:30:17 +00:00
|
|
|
i.Type = "blob"
|
2024-02-10 00:13:02 +00:00
|
|
|
if saveContent {
|
2024-11-21 00:15:30 +00:00
|
|
|
return contentErr
|
2024-02-10 00:13:02 +00:00
|
|
|
}
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
2024-02-10 00:13:02 +00:00
|
|
|
|
2019-01-05 22:44:33 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
// readFirstBytes reads the first bytes of the file.
|
2024-11-21 00:15:30 +00:00
|
|
|
func (i *ReducedItem) readFirstBytes(path string) []byte {
|
|
|
|
file, err := os.Open(path)
|
2021-01-07 10:30:17 +00:00
|
|
|
if err != nil {
|
|
|
|
i.Type = "blob"
|
|
|
|
return nil
|
|
|
|
}
|
2024-08-24 22:02:33 +00:00
|
|
|
defer file.Close()
|
2021-01-07 10:30:17 +00:00
|
|
|
|
2021-07-26 10:00:05 +00:00
|
|
|
buffer := make([]byte, 512) //nolint:gomnd
|
2024-08-24 22:02:33 +00:00
|
|
|
n, err := file.Read(buffer)
|
2021-01-07 10:30:17 +00:00
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
i.Type = "blob"
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return buffer[:n]
|
|
|
|
}
|
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
// detectSubtitles detects subtitles for video files.
|
2024-11-21 00:15:30 +00:00
|
|
|
//func (i *FileInfo) detectSubtitles(path string) {
|
|
|
|
// if i.Type != "video" {
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// parentDir := filepath.Dir(path)
|
|
|
|
// fileName := filepath.Base(path)
|
|
|
|
// i.Subtitles = []string{}
|
|
|
|
// ext := filepath.Ext(fileName)
|
|
|
|
// dir, err := os.Open(parentDir)
|
|
|
|
// if err != nil {
|
|
|
|
// // Directory must have been deleted, remove it from the index
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// defer dir.Close() // Ensure directory handle is closed
|
|
|
|
//
|
|
|
|
// files, err := dir.Readdir(-1)
|
|
|
|
// if err != nil {
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// base := strings.TrimSuffix(fileName, ext)
|
|
|
|
// subtitleExts := []string{".vtt", ".txt", ".srt", ".lrc"}
|
|
|
|
//
|
|
|
|
// for _, f := range files {
|
|
|
|
// if f.IsDir() || !strings.HasPrefix(f.Name(), base) {
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// for _, subtitleExt := range subtitleExts {
|
|
|
|
// if strings.HasSuffix(f.Name(), subtitleExt) {
|
|
|
|
// i.Subtitles = append(i.Subtitles, filepath.Join(parentDir, f.Name()))
|
|
|
|
// break
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
2024-08-24 22:02:33 +00:00
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
func IsNamedPipe(mode os.FileMode) bool {
|
|
|
|
return mode&os.ModeNamedPipe != 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func IsSymlink(mode os.FileMode) bool {
|
|
|
|
return mode&os.ModeSymlink != 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func getMutex(path string) *sync.Mutex {
|
|
|
|
// Lock access to pathMutexes map
|
|
|
|
pathMutexesMu.Lock()
|
|
|
|
defer pathMutexesMu.Unlock()
|
|
|
|
|
|
|
|
// Create a mutex for the path if it doesn't exist
|
|
|
|
if pathMutexes[path] == nil {
|
|
|
|
pathMutexes[path] = &sync.Mutex{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return pathMutexes[path]
|
|
|
|
}
|
2024-08-24 22:02:33 +00:00
|
|
|
|
|
|
|
func Exists(path string) bool {
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if err == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|