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) } }