1
0
mirror of https://github.com/rclone/rclone.git synced 2025-12-06 00:03:32 +00:00
Files
rclone/cmd/archive/files/files.go
2025-10-30 16:20:48 +00:00

236 lines
4.9 KiB
Go

// Package files implements io/fs objects
package files
import (
"archive/tar"
"context"
"fmt"
"io"
stdfs "io/fs"
"path"
"strconv"
"strings"
"time"
"github.com/mholt/archives"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/accounting"
"github.com/rclone/rclone/fs/operations"
)
// fill tar.Header with metadata if available (too bad username/groupname is not available)
func metadataToHeader(metadata fs.Metadata, header *tar.Header) {
var val string
var ok bool
var err error
var mode, uid, gid int64
var atime, ctime time.Time
var uname, gname string
// check if metadata is valid
if metadata != nil {
// mode
val, ok = metadata["mode"]
if !ok {
mode = 0644
} else {
mode, err = strconv.ParseInt(val, 8, 64)
if err != nil {
mode = 0664
}
}
// uid
val, ok = metadata["uid"]
if !ok {
uid = 0
} else {
uid, err = strconv.ParseInt(val, 10, 32)
if err != nil {
uid = 0
}
}
// gid
val, ok = metadata["gid"]
if !ok {
gid = 0
} else {
gid, err = strconv.ParseInt(val, 10, 32)
if err != nil {
gid = 0
}
}
// access time
val, ok := metadata["atime"]
if !ok {
atime = time.Unix(0, 0)
} else {
atime, err = time.Parse(time.RFC3339Nano, val)
if err != nil {
atime = time.Unix(0, 0)
}
}
// set uname/gname
if uid == 0 {
uname = "root"
} else {
uname = strconv.FormatInt(uid, 10)
}
if gid == 0 {
gname = "root"
} else {
gname = strconv.FormatInt(gid, 10)
}
} else {
mode = 0644
uid = 0
gid = 0
uname = "root"
gname = "root"
atime = header.ModTime
ctime = header.ModTime
}
// set values
header.Mode = mode
header.Uid = int(uid)
header.Gid = int(gid)
header.Uname = uname
header.Gname = gname
header.AccessTime = atime
header.ChangeTime = ctime
}
// structs for fs.FileInfo,fs.File,SeekableFile
type fileInfoImpl struct {
header *tar.Header
}
type fileImpl struct {
entry stdfs.FileInfo
ctx context.Context
reader io.ReadSeekCloser
transfer *accounting.Transfer
err error
}
func newFileInfo(ctx context.Context, entry fs.DirEntry, prefix string, metadata fs.Metadata) stdfs.FileInfo {
var fi = new(fileInfoImpl)
fi.header = new(tar.Header)
if prefix != "" {
fi.header.Name = path.Join(strings.TrimPrefix(prefix, "/"), entry.Remote())
} else {
fi.header.Name = entry.Remote()
}
fi.header.Size = entry.Size()
fi.header.ModTime = entry.ModTime(ctx)
// set metadata
metadataToHeader(metadata, fi.header)
// flag if directory
_, isDir := entry.(fs.Directory)
if isDir {
fi.header.Mode = int64(stdfs.ModeDir) | fi.header.Mode
}
return fi
}
func (a *fileInfoImpl) Name() string {
return a.header.Name
}
func (a *fileInfoImpl) Size() int64 {
return a.header.Size
}
func (a *fileInfoImpl) Mode() stdfs.FileMode {
return stdfs.FileMode(a.header.Mode)
}
func (a *fileInfoImpl) ModTime() time.Time {
return a.header.ModTime
}
func (a *fileInfoImpl) IsDir() bool {
return (a.header.Mode & int64(stdfs.ModeDir)) != 0
}
func (a *fileInfoImpl) Sys() any {
return a.header
}
func (a *fileInfoImpl) String() string {
return fmt.Sprintf("Name=%v Size=%v IsDir=%v UID=%v GID=%v", a.Name(), a.Size(), a.IsDir(), a.header.Uid, a.header.Gid)
}
// create a fs.File compatible struct
func newFile(ctx context.Context, obj fs.Object, fi stdfs.FileInfo) (stdfs.File, error) {
var f = new(fileImpl)
// create stdfs.File
f.entry = fi
f.ctx = ctx
f.err = nil
// create transfer
f.transfer = accounting.Stats(ctx).NewTransfer(obj, nil)
// get open options
var options []fs.OpenOption
for _, option := range fs.GetConfig(ctx).DownloadHeaders {
options = append(options, option)
}
// open file
f.reader, f.err = operations.Open(ctx, obj, options...)
if f.err != nil {
defer f.transfer.Done(ctx, f.err)
return nil, f.err
}
// Account the transfer
f.reader = f.transfer.Account(ctx, f.reader)
return f, f.err
}
func (a *fileImpl) Stat() (stdfs.FileInfo, error) {
return a.entry, nil
}
func (a *fileImpl) Read(data []byte) (int, error) {
if a.reader == nil {
a.err = fmt.Errorf("file %s not open", a.entry.Name())
return 0, a.err
}
i, err := a.reader.Read(data)
a.err = err
return i, a.err
}
func (a *fileImpl) Close() error {
// close file
if a.reader == nil {
a.err = fmt.Errorf("file %s not open", a.entry.Name())
} else {
a.err = a.reader.Close()
}
// close transfer
a.transfer.Done(a.ctx, a.err)
return a.err
}
// NewArchiveFileInfo will take a fs.DirEntry and return a archives.Fileinfo
func NewArchiveFileInfo(ctx context.Context, entry fs.DirEntry, prefix string, metadata fs.Metadata) archives.FileInfo {
fi := newFileInfo(ctx, entry, prefix, metadata)
return archives.FileInfo{
FileInfo: fi,
NameInArchive: fi.Name(),
LinkTarget: "",
Open: func() (stdfs.File, error) {
obj, isObject := entry.(fs.Object)
if isObject {
return newFile(ctx, obj, fi)
}
return nil, fmt.Errorf("%s is not a file", fi.Name())
},
}
}