2019-01-05 22:44:33 +00:00
|
|
|
package files
|
|
|
|
|
|
|
|
import (
|
2020-05-31 23:12:36 +00:00
|
|
|
"crypto/md5" //nolint:gosec
|
|
|
|
"crypto/sha1" //nolint:gosec
|
2019-01-05 22:44:33 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/hex"
|
|
|
|
"hash"
|
|
|
|
"io"
|
|
|
|
"mime"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2023-12-01 23:47:00 +00:00
|
|
|
filepath "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
|
|
|
|
2020-05-31 23:12:36 +00:00
|
|
|
"github.com/spf13/afero"
|
|
|
|
|
2023-06-15 01:08:09 +00:00
|
|
|
"github.com/gtsteffaniak/filebrowser/errors"
|
|
|
|
"github.com/gtsteffaniak/filebrowser/rules"
|
2023-12-01 23:47:00 +00:00
|
|
|
"github.com/gtsteffaniak/filebrowser/users"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
bytesInMegabyte int64 = 1000000
|
|
|
|
pathMutexes = make(map[string]*sync.Mutex)
|
|
|
|
pathMutexesMu sync.Mutex // Mutex to protect the pathMutexes map
|
2019-01-05 22:44:33 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// FileInfo describes a file.
|
|
|
|
type FileInfo struct {
|
|
|
|
*Listing
|
|
|
|
Fs afero.Fs `json:"-"`
|
2023-12-01 23:47:00 +00:00
|
|
|
Path string `json:"path,omitempty"`
|
2019-01-05 22:44:33 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Size int64 `json:"size"`
|
2023-12-01 23:47:00 +00:00
|
|
|
Extension string `json:"-"`
|
2019-01-05 22:44:33 +00:00
|
|
|
ModTime time.Time `json:"modified"`
|
2023-12-01 23:47:00 +00:00
|
|
|
CacheTime time.Time `json:"-"`
|
|
|
|
Mode os.FileMode `json:"-"`
|
|
|
|
IsDir bool `json:"isDir,omitempty"`
|
|
|
|
IsSymlink bool `json:"isSymlink,omitempty"`
|
2019-01-05 22:44:33 +00:00
|
|
|
Type string `json:"type"`
|
|
|
|
Subtitles []string `json:"subtitles,omitempty"`
|
|
|
|
Content string `json:"content,omitempty"`
|
|
|
|
Checksums map[string]string `json:"checksums,omitempty"`
|
2021-03-02 11:00:18 +00:00
|
|
|
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 {
|
2021-01-07 10:30:17 +00:00
|
|
|
Fs afero.Fs
|
|
|
|
Path string
|
|
|
|
Modify bool
|
|
|
|
Expand bool
|
|
|
|
ReadHeader bool
|
2021-03-02 11:00:18 +00:00
|
|
|
Token string
|
2021-01-07 10:30:17 +00:00
|
|
|
Checker rules.Checker
|
2021-04-23 12:04:02 +00:00
|
|
|
Content bool
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
// Sorting constants
|
|
|
|
const (
|
|
|
|
SortingByName = "name"
|
|
|
|
SortingBySize = "size"
|
|
|
|
SortingByModified = "modified"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Listing is a collection of files.
|
|
|
|
type Listing struct {
|
|
|
|
Items []*FileInfo `json:"items"`
|
|
|
|
Path string `json:"path"`
|
|
|
|
NumDirs int `json:"numDirs"`
|
|
|
|
NumFiles int `json:"numFiles"`
|
|
|
|
Sorting users.Sorting `json:"sorting"`
|
|
|
|
}
|
|
|
|
|
2019-01-05 22:44:33 +00:00
|
|
|
// NewFileInfo creates a File object from a path and a given user. This File
|
|
|
|
// object will be automatically filled depending on if it is a directory
|
|
|
|
// or a file. If it's a video file, it will also detect any subtitles.
|
|
|
|
func NewFileInfo(opts FileOptions) (*FileInfo, error) {
|
|
|
|
if !opts.Checker.Check(opts.Path) {
|
|
|
|
return nil, os.ErrPermission
|
|
|
|
}
|
2023-12-01 23:47:00 +00:00
|
|
|
file, err := stat(opts.Path, opts) // Pass opts.Path here
|
2019-01-05 22:44:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if opts.Expand {
|
|
|
|
if file.IsDir {
|
2023-12-01 23:47:00 +00:00
|
|
|
if err := file.readListing(opts.Path, opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
|
2020-05-31 23:12:36 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return file, nil
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
2023-12-01 23:47:00 +00:00
|
|
|
err = file.detectType(opts.Path, opts.Modify, opts.Content, true)
|
2019-01-05 22:44:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return file, err
|
|
|
|
}
|
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
func FileInfoFaster(opts FileOptions) (*FileInfo, error) {
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
index := GetIndex(rootPath)
|
|
|
|
trimmed := strings.TrimPrefix(opts.Path, "/")
|
|
|
|
if trimmed == "" {
|
|
|
|
trimmed = "/"
|
|
|
|
}
|
|
|
|
adjustedPath := makeIndexPath(trimmed, index.Root)
|
|
|
|
var info FileInfo
|
|
|
|
info, exists := index.GetMetadataInfo(adjustedPath)
|
2024-02-10 00:13:02 +00:00
|
|
|
if exists && !opts.Content {
|
2023-12-01 23:47:00 +00:00
|
|
|
// Check if the cache time is less than 1 second
|
|
|
|
if time.Since(info.CacheTime) > time.Second {
|
2024-07-30 17:45:27 +00:00
|
|
|
go RefreshFileInfo(opts)
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
|
|
|
// refresh cache after
|
|
|
|
return &info, nil
|
|
|
|
} else {
|
2024-02-10 00:13:02 +00:00
|
|
|
// don't bother caching content
|
|
|
|
if opts.Content {
|
|
|
|
file, err := NewFileInfo(opts)
|
|
|
|
return file, err
|
|
|
|
}
|
2024-07-30 17:45:27 +00:00
|
|
|
updated := RefreshFileInfo(opts)
|
2023-12-01 23:47:00 +00:00
|
|
|
if !updated {
|
|
|
|
file, err := NewFileInfo(opts)
|
|
|
|
return file, err
|
|
|
|
}
|
|
|
|
info, exists = index.GetMetadataInfo(adjustedPath)
|
|
|
|
if !exists || info.Name == "" {
|
|
|
|
return &FileInfo{}, errors.ErrEmptyKey
|
|
|
|
}
|
|
|
|
return &info, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-30 17:45:27 +00:00
|
|
|
func RefreshFileInfo(opts FileOptions) bool {
|
2023-12-01 23:47:00 +00:00
|
|
|
if !opts.Checker.Check(opts.Path) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
index := GetIndex(rootPath)
|
|
|
|
trimmed := strings.TrimPrefix(opts.Path, "/")
|
|
|
|
if trimmed == "" {
|
|
|
|
trimmed = "/"
|
|
|
|
}
|
|
|
|
adjustedPath := makeIndexPath(trimmed, index.Root)
|
2024-02-10 00:13:02 +00:00
|
|
|
file, err := stat(opts.Path, opts) // Pass opts.Path here
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
_ = file.detectType(adjustedPath, true, opts.Content, opts.ReadHeader)
|
2023-12-01 23:47:00 +00:00
|
|
|
if file.IsDir {
|
|
|
|
err := file.readListing(opts.Path, opts.Checker, opts.ReadHeader)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
//_, exists := index.GetFileMetadata(adjustedPath)
|
2024-02-10 00:13:02 +00:00
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
return index.UpdateFileMetadata(adjustedPath, *file)
|
|
|
|
} else {
|
|
|
|
//_, exists := index.GetFileMetadata(adjustedPath)
|
|
|
|
return index.UpdateFileMetadata(adjustedPath, *file)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func stat(path string, opts FileOptions) (*FileInfo, error) {
|
|
|
|
var file *FileInfo
|
2021-07-26 10:59:09 +00:00
|
|
|
if lstaterFs, ok := opts.Fs.(afero.Lstater); ok {
|
2023-12-01 23:47:00 +00:00
|
|
|
info, _, err := lstaterFs.LstatIfPossible(path)
|
|
|
|
if err == nil {
|
|
|
|
file = &FileInfo{
|
|
|
|
Fs: opts.Fs,
|
|
|
|
Path: opts.Path,
|
|
|
|
Name: info.Name(),
|
|
|
|
ModTime: info.ModTime(),
|
|
|
|
Mode: info.Mode(),
|
|
|
|
Size: info.Size(),
|
|
|
|
Extension: filepath.Ext(info.Name()),
|
|
|
|
Token: opts.Token,
|
|
|
|
}
|
|
|
|
if info.IsDir() {
|
|
|
|
file.IsDir = true
|
|
|
|
}
|
|
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
|
|
file.IsSymlink = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if file == nil || file.IsSymlink {
|
|
|
|
info, err := opts.Fs.Stat(opts.Path)
|
2021-07-26 10:59:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-12-01 23:47:00 +00:00
|
|
|
|
|
|
|
if file != nil && file.IsSymlink {
|
|
|
|
file.Size = info.Size()
|
|
|
|
file.IsDir = info.IsDir()
|
|
|
|
return file, nil
|
|
|
|
}
|
|
|
|
|
2021-07-26 10:59:09 +00:00
|
|
|
file = &FileInfo{
|
|
|
|
Fs: opts.Fs,
|
|
|
|
Path: opts.Path,
|
|
|
|
Name: info.Name(),
|
|
|
|
ModTime: info.ModTime(),
|
|
|
|
Mode: info.Mode(),
|
|
|
|
IsDir: info.IsDir(),
|
|
|
|
Size: info.Size(),
|
|
|
|
Extension: filepath.Ext(info.Name()),
|
|
|
|
Token: opts.Token,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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.IsDir {
|
|
|
|
return errors.ErrIsDirectory
|
|
|
|
}
|
|
|
|
|
|
|
|
if i.Checksums == nil {
|
|
|
|
i.Checksums = map[string]string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
reader, err := i.Fs.Open(i.Path)
|
|
|
|
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 {
|
|
|
|
if realPathFs, ok := i.Fs.(interface {
|
|
|
|
RealPath(name string) (fPath string, err error)
|
|
|
|
}); ok {
|
|
|
|
realPath, err := realPathFs.RealPath(i.Path)
|
|
|
|
if err == nil {
|
|
|
|
return realPath
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return i.Path
|
|
|
|
}
|
|
|
|
|
2024-02-10 00:13:02 +00:00
|
|
|
// addContent reads and sets content based on the file type.
|
|
|
|
func (i *FileInfo) addContent(path string) error {
|
|
|
|
if !i.IsDir {
|
|
|
|
afs := &afero.Afero{Fs: i.Fs}
|
|
|
|
content, err := afs.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-07-30 17:45:27 +00:00
|
|
|
stringContent := string(content)
|
|
|
|
if !utf8.ValidString(stringContent) {
|
2024-02-10 00:13:02 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-07-30 17:45:27 +00:00
|
|
|
if stringContent == "" {
|
|
|
|
i.Content = "empty-file-x6OlSil"
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
i.Content = stringContent
|
2024-02-10 00:13:02 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
// detectType detects the file type.
|
|
|
|
func (i *FileInfo) detectType(path string, modify, saveContent, readHeader bool) error {
|
2020-11-24 10:32:23 +00:00
|
|
|
if IsNamedPipe(i.Mode) {
|
|
|
|
i.Type = "blob"
|
2024-02-10 00:13:02 +00:00
|
|
|
if saveContent {
|
|
|
|
return i.addContent(path)
|
|
|
|
}
|
2020-11-24 10:32:23 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-02-10 00:13:02 +00:00
|
|
|
|
2021-03-17 18:06:56 +00:00
|
|
|
var buffer []byte
|
|
|
|
if readHeader {
|
2021-01-07 10:30:17 +00:00
|
|
|
buffer = i.readFirstBytes()
|
2023-12-01 23:47:00 +00:00
|
|
|
mimetype := mime.TypeByExtension(i.Extension)
|
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
|
|
|
ext := filepath.Ext(i.Name)
|
|
|
|
for _, fileType := range AllFiletypeOptions {
|
|
|
|
if IsMatchingType(ext, fileType) {
|
|
|
|
i.Type = fileType
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
2024-02-10 00:13:02 +00:00
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
switch i.Type {
|
|
|
|
case "text":
|
|
|
|
if !modify {
|
|
|
|
i.Type = "textImmutable"
|
|
|
|
}
|
|
|
|
if saveContent {
|
2024-02-10 00:13:02 +00:00
|
|
|
return i.addContent(path)
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
|
|
|
case "video":
|
|
|
|
parentDir := strings.TrimRight(path, i.Name)
|
|
|
|
i.detectSubtitles(parentDir)
|
|
|
|
case "doc":
|
|
|
|
if ext == ".pdf" {
|
|
|
|
i.Type = "pdf"
|
2024-02-10 00:13:02 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if saveContent {
|
|
|
|
return i.addContent(path)
|
2019-01-06 09:24:09 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-01 23:47:00 +00:00
|
|
|
}
|
2024-02-10 00:13:02 +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 {
|
|
|
|
return i.addContent(path)
|
|
|
|
}
|
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.
|
2021-01-07 10:30:17 +00:00
|
|
|
func (i *FileInfo) readFirstBytes() []byte {
|
|
|
|
reader, err := i.Fs.Open(i.Path)
|
|
|
|
if err != nil {
|
|
|
|
i.Type = "blob"
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
defer reader.Close()
|
|
|
|
|
2021-07-26 10:00:05 +00:00
|
|
|
buffer := make([]byte, 512) //nolint:gomnd
|
2021-01-07 10:30:17 +00:00
|
|
|
n, err := reader.Read(buffer)
|
|
|
|
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.
|
|
|
|
func (i *FileInfo) detectSubtitles(parentDir string) {
|
2019-01-05 22:44:33 +00:00
|
|
|
if i.Type != "video" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
i.Subtitles = []string{}
|
2023-12-01 23:47:00 +00:00
|
|
|
ext := filepath.Ext(i.Name)
|
|
|
|
dir, err := os.Open(parentDir)
|
|
|
|
if err != nil {
|
|
|
|
// Directory must have been deleted, remove it from the index
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Read the directory contents
|
|
|
|
files, err := dir.Readdir(-1)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
base := strings.TrimSuffix(i.Name, 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
|
2021-12-20 23:07:43 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-01 23:47:00 +00:00
|
|
|
// readListing reads the contents of a directory and fills the listing.
|
|
|
|
func (i *FileInfo) readListing(path string, checker rules.Checker, readHeader bool) error {
|
2019-01-05 22:44:33 +00:00
|
|
|
afs := &afero.Afero{Fs: i.Fs}
|
|
|
|
dir, err := afs.ReadDir(i.Path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
listing := &Listing{
|
|
|
|
Items: []*FileInfo{},
|
2023-12-01 23:47:00 +00:00
|
|
|
Path: i.Path,
|
2019-01-05 22:44:33 +00:00
|
|
|
NumDirs: 0,
|
|
|
|
NumFiles: 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, f := range dir {
|
|
|
|
name := f.Name()
|
2023-12-01 23:47:00 +00:00
|
|
|
fPath := filepath.Join(i.Path, name)
|
2019-01-05 22:44:33 +00:00
|
|
|
|
2020-05-31 23:12:36 +00:00
|
|
|
if !checker.Check(fPath) {
|
2019-01-05 22:44:33 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-05-05 15:14:40 +00:00
|
|
|
isSymlink, isInvalidLink := false, false
|
2020-11-24 10:32:23 +00:00
|
|
|
if IsSymlink(f.Mode()) {
|
2021-07-26 10:59:09 +00:00
|
|
|
isSymlink = true
|
2020-05-31 23:12:36 +00:00
|
|
|
info, err := i.Fs.Stat(fPath)
|
2019-01-05 22:44:33 +00:00
|
|
|
if err == nil {
|
|
|
|
f = info
|
2022-05-05 15:14:40 +00:00
|
|
|
} else {
|
|
|
|
isInvalidLink = true
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
file := &FileInfo{
|
2023-12-01 23:47:00 +00:00
|
|
|
Name: name,
|
|
|
|
Size: f.Size(),
|
|
|
|
ModTime: f.ModTime(),
|
|
|
|
Mode: f.Mode(),
|
|
|
|
}
|
|
|
|
if f.IsDir() {
|
|
|
|
file.IsDir = true
|
|
|
|
}
|
|
|
|
if isSymlink {
|
|
|
|
file.IsSymlink = true
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if file.IsDir {
|
|
|
|
listing.NumDirs++
|
|
|
|
} else {
|
|
|
|
listing.NumFiles++
|
|
|
|
|
2022-05-05 15:14:40 +00:00
|
|
|
if isInvalidLink {
|
|
|
|
file.Type = "invalid_link"
|
|
|
|
} else {
|
2023-12-01 23:47:00 +00:00
|
|
|
err := file.detectType(path, true, false, readHeader)
|
2022-05-05 15:14:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-01-05 22:44:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
listing.Items = append(listing.Items, file)
|
|
|
|
}
|
|
|
|
|
|
|
|
i.Listing = listing
|
|
|
|
return nil
|
|
|
|
}
|
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]
|
|
|
|
}
|