334 lines
8.7 KiB
Go
334 lines
8.7 KiB
Go
package file
|
||
|
||
import (
|
||
"os"
|
||
"os/user"
|
||
"path/filepath"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"syscall"
|
||
"time"
|
||
|
||
. "gitea.zaclys.com/bvaudour/gob/option"
|
||
)
|
||
|
||
var (
|
||
owners = make(map[string]string)
|
||
groups = make(map[string]string)
|
||
)
|
||
|
||
// File représente la description d’un fichier sur un disque.
|
||
type File struct {
|
||
info os.FileInfo
|
||
dirname string
|
||
name string
|
||
parent Option[*File]
|
||
children FileList
|
||
}
|
||
|
||
// New récupère les informations d’un fichier à partir de son nom.
|
||
// et de son répertoire d’appartenance.
|
||
func New(dirname, name string) (f Result[*File]) {
|
||
info, err := os.Lstat(filepath.Join(dirname, name))
|
||
|
||
if err == nil {
|
||
return Ok(&File{
|
||
info: info,
|
||
dirname: dirname,
|
||
name: name,
|
||
})
|
||
}
|
||
return Err[*File](err)
|
||
}
|
||
|
||
// NewFromInfo agit comme New mais en ayant déjà récupéré une
|
||
// partie de la description.
|
||
func NewFromInfo(dirname string, info os.FileInfo) *File {
|
||
return &File{
|
||
info: info,
|
||
dirname: dirname,
|
||
name: info.Name(),
|
||
}
|
||
}
|
||
|
||
func (f *File) stat() *syscall.Stat_t { return f.info.Sys().(*syscall.Stat_t) }
|
||
|
||
// Mod retourne les droits UNIX du fichier.
|
||
func (f *File) Mode() os.FileMode { return f.info.Mode() }
|
||
|
||
// HasMode vérifie que le fichier a un des droits spécifiés.
|
||
func (f *File) HasMode(mask os.FileMode) bool { return f.Mode()&mask != 0 }
|
||
|
||
// IsDir retourne vrai si le fichier est un répertoire.
|
||
func (f *File) IsDir() bool { return f.info.IsDir() }
|
||
|
||
// IsSymlink retourne vrai si le fichier est un lien symbolique.
|
||
func (f *File) IsSymlink() bool { return f.HasMode(os.ModeSymlink) }
|
||
|
||
// IsDevice retourne vrai si le fichier est un fichier de périphérique.
|
||
func (f *File) IsDevice() bool { return f.HasMode(os.ModeDevice) }
|
||
|
||
// IsCharDevice retourne vrai si le fichier est un fichier de périphérique de caractère.
|
||
func (f *File) IsCharDevice() bool { return f.HasMode(os.ModeCharDevice) }
|
||
|
||
// IsSocket retourne vrai si le fichier est un socket.
|
||
func (f *File) IsSocket() bool { return f.HasMode(os.ModeSocket) }
|
||
|
||
// IsPipe retourne vrai si le fichier est de type pipe.
|
||
func (f *File) IsPipe() bool { return f.HasMode(os.ModeNamedPipe) }
|
||
|
||
// IsIrregular retourne vrai si fichier est de type inconnu.
|
||
func (f *File) IsIrregular() bool { return f.HasMode(os.ModeIrregular) }
|
||
|
||
// IsRegular retourne vrai si le fichier est un fichier régulier.
|
||
func (f *File) IsRegular() bool { return f.Mode().IsRegular() }
|
||
|
||
// IsUid retourne vrai si le fichier a la permission Suid.
|
||
func (f *File) IsUid() bool { return f.HasMode(os.ModeSetuid) }
|
||
|
||
// IsGid retourne vrai si le fichier a la permission Sgid.
|
||
func (f *File) IsGid() bool { return f.HasMode(os.ModeSetgid) }
|
||
|
||
// IsSticky retourne vrai si le fichier a la permission Sticky Bit.
|
||
func (f *File) IsSticky() bool { return f.HasMode(os.ModeSticky) }
|
||
|
||
// Name retourne le nom du fichier.
|
||
func (f *File) Name() string { return f.name }
|
||
|
||
// DirName retourne le répertoire d’appartenance.
|
||
func (f *File) DirName() string { return f.dirname }
|
||
|
||
// Path retourne le chemin complet du fichier.
|
||
func (f *File) Path() string { return filepath.Join(f.dirname, f.name) }
|
||
|
||
// AbsolutePath agit comme Path mais s’assure que le chemin soit absolu.
|
||
func (f *File) AbsolutePath() string {
|
||
path := f.Path()
|
||
if filepath.IsAbs(path) {
|
||
return path
|
||
}
|
||
|
||
abs, _ := filepath.Abs(path)
|
||
return abs
|
||
}
|
||
|
||
// Split retourne le dossier et le nom du fichier.
|
||
func (f *File) Split() (dirname, name string) {
|
||
path := filepath.Clean(f.Path())
|
||
|
||
return filepath.Split(path)
|
||
}
|
||
|
||
// Split retourne le dossier absolu et le nom du fichier.
|
||
func (f *File) AbsoluteSplit() (dirname, name string) {
|
||
path := f.AbsolutePath()
|
||
|
||
return filepath.Split(path)
|
||
}
|
||
|
||
// Extension retourne l’extension du fichier.
|
||
func (f *File) Extension() string {
|
||
if f.IsDir() {
|
||
return ""
|
||
}
|
||
|
||
return filepath.Ext(f.info.Name())
|
||
}
|
||
|
||
// LinkPath retourne le chemin du fichier pointé
|
||
// par le lien symbolique, ou une chaîne vide si le lien est cassé
|
||
// ou que le fichier n’est pas un lien symbolique.
|
||
func (f *File) LinkPath() string {
|
||
if !f.IsSymlink() {
|
||
return ""
|
||
}
|
||
|
||
lpath, _ := os.Readlink(f.Path())
|
||
return lpath
|
||
}
|
||
|
||
func ts2Date(ts syscall.Timespec) time.Time { return time.Unix(ts.Sec, ts.Nsec) }
|
||
|
||
// CreationTime retourne la date de création du fichier.
|
||
func (f *File) CreationTime() time.Time { return ts2Date(f.stat().Ctim) }
|
||
|
||
// AccessTime retourne la date d’accès du fichier.
|
||
func (f *File) AccessTime() time.Time { return ts2Date(f.stat().Atim) }
|
||
|
||
// ModificationTime retourne la date de modification du fichier.
|
||
func (f *File) ModificationTime() time.Time { return f.info.ModTime() }
|
||
|
||
// Size retourne la taille (en octets) du fichier.
|
||
func (f *File) Size() int64 { return f.info.Size() }
|
||
|
||
// BlockSize retourne la taille de blocs (en octets) du fichier.
|
||
func (f *File) BlockSize() int64 { return f.stat().Blksize }
|
||
|
||
// DirSize retourne la taille totale des fichiers (en octets) contenus dans un répertoire,
|
||
// ou bien la taille du fichier s’il s’agit d’un simple fichier.
|
||
// La taille inclut de façon récursive celle des sous-répertoires.
|
||
func (f *File) DirSize() (size int64) {
|
||
if !f.IsDir() {
|
||
return f.Size()
|
||
}
|
||
|
||
for _, c := range f.children {
|
||
size += c.DirSize()
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
// OwnerID retourne l’ID utilisateur du fichier.
|
||
func (f *File) OwnerID() string { return strconv.Itoa(int(f.stat().Uid)) }
|
||
|
||
// GroupID retourne l’ID du groupe du fichier.
|
||
func (f *File) GroupID() string { return strconv.Itoa(int(f.stat().Gid)) }
|
||
|
||
// Owner retourne le nom du propriétaire du fichier.
|
||
func (f *File) Owner() string {
|
||
id := f.OwnerID()
|
||
if owner, ok := owners[id]; ok {
|
||
return owner
|
||
}
|
||
|
||
var owner string
|
||
if e, err := user.LookupId(id); err == nil {
|
||
owner = e.Name
|
||
}
|
||
owners[id] = owner
|
||
|
||
return owner
|
||
}
|
||
|
||
// Group retourne le nom du groupe du fichier.
|
||
func (f *File) Group() string {
|
||
id := f.GroupID()
|
||
if group, ok := groups[id]; ok {
|
||
return group
|
||
}
|
||
|
||
var group string
|
||
if e, err := user.LookupGroupId(id); err == nil {
|
||
group = e.Name
|
||
}
|
||
groups[id] = group
|
||
|
||
return group
|
||
}
|
||
|
||
// Children retourne la liste des fichiers de premier niveau du répertoire
|
||
// ou rien si le fichier n’est pas un répertoire.
|
||
func (f *File) Children() FileList { return f.children }
|
||
|
||
// SearchChildren recherche les fichiers d’un répertoire.
|
||
// deepness indique la profondeur de recherche et doit valoir au moins 1.
|
||
// searchoptions peut contenir deux options de recherche optionnelles :
|
||
// 1. Fichiers masqués (par défaut non retournés),
|
||
// 2. Fichiers de backup (ie. ceux avec le suffixe ~) (par défaut non retournés).
|
||
func (f *File) SearchChildren(deepness int, searchOptions ...bool) (children FileList) {
|
||
if !f.IsDir() || deepness == 0 {
|
||
return
|
||
}
|
||
|
||
var hidden, backup bool
|
||
if len(searchOptions) > 0 {
|
||
hidden = searchOptions[0]
|
||
if len(searchOptions) > 1 {
|
||
backup = searchOptions[1]
|
||
}
|
||
}
|
||
|
||
dir, err := os.Open(f.Path())
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer dir.Close()
|
||
|
||
infos, _ := dir.Readdir(0)
|
||
dirname := f.Path()
|
||
if deepness > 0 {
|
||
deepness--
|
||
}
|
||
|
||
var wg sync.WaitGroup
|
||
for _, i := range infos {
|
||
name := i.Name()
|
||
if (!hidden && strings.HasPrefix(name, ".")) || (!backup && strings.HasSuffix(name, "~")) {
|
||
continue
|
||
}
|
||
child := NewFromInfo(dirname, i)
|
||
children.Add(child)
|
||
wg.Add(1)
|
||
go (func(c *File) {
|
||
defer wg.Done()
|
||
c.SearchChildren(deepness, searchOptions...)
|
||
})(child)
|
||
}
|
||
wg.Wait()
|
||
|
||
f.children = children
|
||
for _, c := range children {
|
||
c.parent = Some(f)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
// Flatten retourne la liste de tous les fichiers et répertoires de façon récursive.
|
||
// Si only_dir est présent et vaut true, seuls les répertoires sont retournés.
|
||
func (f *File) Flatten(only_dirs ...bool) (fl FileList) {
|
||
d := len(only_dirs) > 0 && only_dirs[0]
|
||
if d && !f.IsDir() {
|
||
return
|
||
}
|
||
|
||
fl.Add(f)
|
||
for _, c := range f.children {
|
||
fl.Add(c.Flatten(only_dirs...)...)
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
// Parent retourne le répertoire d’appartenance.
|
||
func (f *File) Parent() Option[*File] {
|
||
return f.parent
|
||
}
|
||
|
||
// SearchParent agit comme Parent mais force la recherche
|
||
// si celui-ci n’est pas encore défini.
|
||
func (f *File) SearchParent() Option[*File] {
|
||
if f.parent.IsDefined() {
|
||
return f.parent
|
||
}
|
||
|
||
d, _ := f.AbsoluteSplit()
|
||
pdirname, pname := filepath.Split(d)
|
||
p := New(pdirname, pname)
|
||
if parent, ok := p.Ok(); ok {
|
||
f.parent = Some(parent)
|
||
}
|
||
|
||
return f.parent
|
||
}
|
||
|
||
// AddChildren ajoute des fichiers en tant qu’appartenance à un répertoire.
|
||
func (f *File) AddChildren(children ...*File) {
|
||
for _, c := range children {
|
||
c.parent = Some(f)
|
||
}
|
||
f.children.Add(children...)
|
||
}
|
||
|
||
// SetParent définit le répertoire parent du fichier.
|
||
// Si both est donné et est vrai, le fichier est ajouté au répertoire
|
||
// parent en tant qu’enfant.
|
||
func (f *File) SetParent(parent Option[*File], both ...bool) {
|
||
f.parent = parent
|
||
if p, ok := parent.Get(); ok && len(both) > 0 && both[0] {
|
||
p.children.Add(f)
|
||
}
|
||
}
|