mirror of
https://github.com/rclone/rclone.git
synced 2026-05-12 18:38:00 -04:00
The files.com integration tests for rcat/copyurl were failing because
fs/account.Account was declaring a ReadAt method when the underlying
handle did not support it. The files.com SDK decided to use the ReadAt
method to speed transfers up which failed.
ReadAt and Seek methods were added in this commit to support the
archive command:
409dc75328 accounting: add io.Seeker/io.ReaderAt support to accounting.Account
This fixes the problem by adding new methods to the Account object
WithSeeker/WithReaderAt/WithReadAtSeeker which produce an object with
the desired methods or errors if it isn't possible.
This stops Account advertising things it can't do which is bad Go
practice.
190 lines
4.9 KiB
Go
190 lines
4.9 KiB
Go
//go:build !plan9
|
|
|
|
// Package list implements 'rclone archive list'
|
|
package list
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/mholt/archives"
|
|
"github.com/rclone/rclone/cmd"
|
|
"github.com/rclone/rclone/cmd/archive"
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/accounting"
|
|
"github.com/rclone/rclone/fs/config/flags"
|
|
"github.com/rclone/rclone/fs/filter"
|
|
"github.com/rclone/rclone/fs/operations"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
longList = false
|
|
plainList = false
|
|
filesOnly = false
|
|
dirsOnly = false
|
|
)
|
|
|
|
func init() {
|
|
flagSet := Command.Flags()
|
|
flags.BoolVarP(flagSet, &longList, "long", "", longList, "List extra attributtes", "")
|
|
flags.BoolVarP(flagSet, &plainList, "plain", "", plainList, "Only list file names", "")
|
|
flags.BoolVarP(flagSet, &filesOnly, "files-only", "", false, "Only list files", "")
|
|
flags.BoolVarP(flagSet, &dirsOnly, "dirs-only", "", false, "Only list directories", "")
|
|
archive.Command.AddCommand(Command)
|
|
}
|
|
|
|
// Command - list
|
|
var Command = &cobra.Command{
|
|
Use: "list [flags] <source>",
|
|
Short: `List archive contents from source.`,
|
|
Long: strings.ReplaceAll(`
|
|
List the contents of an archive to the console, auto detecting the
|
|
format. See [rclone archive create](/commands/rclone_archive_create/)
|
|
for the archive formats supported.
|
|
|
|
For example:
|
|
|
|
|||
|
|
$ rclone archive list remote:archive.zip
|
|
6 file.txt
|
|
0 dir/
|
|
4 dir/bye.txt
|
|
|||
|
|
|
|
Or with |--long| flag for more info:
|
|
|
|
|||
|
|
$ rclone archive list --long remote:archive.zip
|
|
6 2025-10-30 09:46:23.000000000 file.txt
|
|
0 2025-10-30 09:46:57.000000000 dir/
|
|
4 2025-10-30 09:46:57.000000000 dir/bye.txt
|
|
|||
|
|
|
|
Or with |--plain| flag which is useful for scripting:
|
|
|
|
|||
|
|
$ rclone archive list --plain /path/to/archive.zip
|
|
file.txt
|
|
dir/
|
|
dir/bye.txt
|
|
|||
|
|
|
|
Or with |--dirs-only|:
|
|
|
|
|||
|
|
$ rclone archive list --plain --dirs-only /path/to/archive.zip
|
|
dir/
|
|
|||
|
|
|
|
Or with |--files-only|:
|
|
|
|
|||
|
|
$ rclone archive list --plain --files-only /path/to/archive.zip
|
|
file.txt
|
|
dir/bye.txt
|
|
|||
|
|
|
|
Filters may also be used:
|
|
|
|
|||
|
|
$ rclone archive list --long archive.zip --include "bye.*"
|
|
4 2025-10-30 09:46:57.000000000 dir/bye.txt
|
|
|||
|
|
|
|
The [archive backend](/archive/) can also be used to list files. It
|
|
can be used to read only mount archives also but it supports a
|
|
different set of archive formats to the archive commands.
|
|
`, "|", "`"),
|
|
Annotations: map[string]string{
|
|
"versionIntroduced": "v1.72",
|
|
},
|
|
RunE: func(command *cobra.Command, args []string) error {
|
|
cmd.CheckArgs(1, 1, command, args)
|
|
src, srcFile := cmd.NewFsFile(args[0])
|
|
cmd.Run(false, false, command, func() error {
|
|
return ArchiveList(context.Background(), src, srcFile, listFile)
|
|
})
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func listFile(ctx context.Context, f archives.FileInfo) error {
|
|
ci := fs.GetConfig(ctx)
|
|
fi := filter.GetConfig(ctx)
|
|
|
|
// check if excluded
|
|
if !fi.Include(f.NameInArchive, f.Size(), f.ModTime(), fs.Metadata{}) {
|
|
return nil
|
|
}
|
|
if filesOnly && f.IsDir() {
|
|
return nil
|
|
}
|
|
if dirsOnly && !f.IsDir() {
|
|
return nil
|
|
}
|
|
// get entry name
|
|
name := f.NameInArchive
|
|
if f.IsDir() && !strings.HasSuffix(name, "/") {
|
|
name += "/"
|
|
}
|
|
// print info
|
|
if longList {
|
|
operations.SyncFprintf(os.Stdout, "%s %s %s\n", operations.SizeStringField(f.Size(), ci.HumanReadable, 9), f.ModTime().Format("2006-01-02 15:04:05.000000000"), name)
|
|
} else if plainList {
|
|
operations.SyncFprintf(os.Stdout, "%s\n", name)
|
|
} else {
|
|
operations.SyncFprintf(os.Stdout, "%s %s\n", operations.SizeStringField(f.Size(), ci.HumanReadable, 9), name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ArchiveList -- print a list of the files in the archive
|
|
func ArchiveList(ctx context.Context, src fs.Fs, srcFile string, listFn archives.FileHandler) error {
|
|
var srcObj fs.Object
|
|
var err error
|
|
// get object
|
|
srcObj, err = src.NewObject(ctx, srcFile)
|
|
if err != nil {
|
|
return fmt.Errorf("source is not a file, %w", err)
|
|
}
|
|
fs.Debugf(nil, "Source archive file: %s/%s", src.Root(), srcFile)
|
|
// start accounting
|
|
tr := accounting.Stats(ctx).NewTransfer(srcObj, nil)
|
|
defer func() {
|
|
tr.Done(ctx, err)
|
|
}()
|
|
// open source
|
|
var options []fs.OpenOption
|
|
for _, option := range fs.GetConfig(ctx).DownloadHeaders {
|
|
options = append(options, option)
|
|
}
|
|
in0, err := operations.Open(ctx, srcObj, options...)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open file %s: %w", srcFile, err)
|
|
}
|
|
// account and buffer the transfer
|
|
// in = tr.Account(ctx, in).WithBuffer()
|
|
acc := tr.Account(ctx, in0)
|
|
in, err := acc.WithReadAtSeeker()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// identify format
|
|
format, _, err := archives.Identify(ctx, "", in)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open check file type, %w", err)
|
|
}
|
|
fs.Debugf(nil, "Listing %s/%s, format %s", src.Root(), srcFile, strings.TrimPrefix(format.Extension(), "."))
|
|
// check if extract is supported by format
|
|
ex, isExtract := format.(archives.Extraction)
|
|
if !isExtract {
|
|
return fmt.Errorf("extraction for %s not supported", strings.TrimPrefix(format.Extension(), "."))
|
|
}
|
|
// list files
|
|
return ex.Extract(ctx, in, listFn)
|
|
}
|