mirror of
https://github.com/rclone/rclone.git
synced 2025-12-06 00:03:32 +00:00
Added rclone archive command to create and read archive files
Co-Authored-By: Nick Craig-Wood <nick@craig-wood.com>
This commit is contained in:
committed by
Nick Craig-Wood
parent
409dc75328
commit
cc09978b79
@@ -5,6 +5,10 @@ import (
|
|||||||
// Active commands
|
// Active commands
|
||||||
_ "github.com/rclone/rclone/cmd"
|
_ "github.com/rclone/rclone/cmd"
|
||||||
_ "github.com/rclone/rclone/cmd/about"
|
_ "github.com/rclone/rclone/cmd/about"
|
||||||
|
_ "github.com/rclone/rclone/cmd/archive"
|
||||||
|
_ "github.com/rclone/rclone/cmd/archive/create"
|
||||||
|
_ "github.com/rclone/rclone/cmd/archive/extract"
|
||||||
|
_ "github.com/rclone/rclone/cmd/archive/list"
|
||||||
_ "github.com/rclone/rclone/cmd/authorize"
|
_ "github.com/rclone/rclone/cmd/authorize"
|
||||||
_ "github.com/rclone/rclone/cmd/backend"
|
_ "github.com/rclone/rclone/cmd/backend"
|
||||||
_ "github.com/rclone/rclone/cmd/bisync"
|
_ "github.com/rclone/rclone/cmd/bisync"
|
||||||
|
|||||||
40
cmd/archive/archive.go
Normal file
40
cmd/archive/archive.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//go:build !plan9
|
||||||
|
|
||||||
|
// Package archive implements 'rclone archive'.
|
||||||
|
package archive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/rclone/rclone/cmd"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmd.Root.AddCommand(Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command - archive command
|
||||||
|
var Command = &cobra.Command{
|
||||||
|
Use: "archive <action> [opts] <source> [<destination>]",
|
||||||
|
Short: `Perform an action on an archive.`,
|
||||||
|
Long: `Perform an action on an archive. Requires the use of a
|
||||||
|
subcommand to specify the protocol, e.g.
|
||||||
|
|
||||||
|
rclone archive list remote:file.zip
|
||||||
|
|
||||||
|
Each subcommand has its own options which you can see in their help.
|
||||||
|
|
||||||
|
See [rclone archive create](/commands/rclone_archive_create/) for the
|
||||||
|
archive formats supported.
|
||||||
|
`,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"versionIntroduced": "v1.72",
|
||||||
|
},
|
||||||
|
RunE: func(command *cobra.Command, args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return errors.New("archive requires an action, e.g. 'rclone archive list remote:'")
|
||||||
|
}
|
||||||
|
return errors.New("unknown action")
|
||||||
|
},
|
||||||
|
}
|
||||||
188
cmd/archive/archive_test.go
Normal file
188
cmd/archive/archive_test.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package archive_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/archives"
|
||||||
|
_ "github.com/rclone/rclone/backend/local"
|
||||||
|
_ "github.com/rclone/rclone/backend/memory"
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/rclone/rclone/fs/operations"
|
||||||
|
"github.com/rclone/rclone/fstest"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/rclone/rclone/cmd/archive/create"
|
||||||
|
"github.com/rclone/rclone/cmd/archive/extract"
|
||||||
|
"github.com/rclone/rclone/cmd/archive/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
t1 = fstest.Time("2017-02-03T04:05:06.499999999Z")
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMain drives the tests
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
fstest.TestMain(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckValidDestination(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
r := fstest.NewRun(t)
|
||||||
|
|
||||||
|
// create file
|
||||||
|
r.WriteObject(ctx, "file1.txt", "111", t1)
|
||||||
|
|
||||||
|
// test checkValidDestination when file exists
|
||||||
|
err = create.CheckValidDestination(ctx, r.Fremote, "file1.txt")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// test checkValidDestination when file does not exist
|
||||||
|
err = create.CheckValidDestination(ctx, r.Fremote, "file2.txt")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// test checkValidDestination when dest is a directory
|
||||||
|
if r.Fremote.Features().CanHaveEmptyDirectories {
|
||||||
|
err = create.CheckValidDestination(ctx, r.Fremote, "")
|
||||||
|
require.ErrorIs(t, err, fs.ErrorIsDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test checkValidDestination when dest does not exists
|
||||||
|
err = create.CheckValidDestination(ctx, r.Fremote, "dir/file.txt")
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test archiving to the remote
|
||||||
|
func testArchiveRemote(t *testing.T, fromLocal bool, subDir string, extension string) {
|
||||||
|
var err error
|
||||||
|
ctx := context.Background()
|
||||||
|
r := fstest.NewRun(t)
|
||||||
|
var src, dst fs.Fs
|
||||||
|
var f1, f2, f3 fstest.Item
|
||||||
|
|
||||||
|
// create files to archive on src
|
||||||
|
if fromLocal {
|
||||||
|
// create files to archive on local
|
||||||
|
src = r.Flocal
|
||||||
|
dst = r.Fremote
|
||||||
|
f1 = r.WriteFile("file1.txt", "content 1", t1)
|
||||||
|
f2 = r.WriteFile("dir1/sub1.txt", "sub content 1", t1)
|
||||||
|
f3 = r.WriteFile("dir2/sub2a.txt", "sub content 2a", t1)
|
||||||
|
} else {
|
||||||
|
// create files to archive on remote
|
||||||
|
src = r.Fremote
|
||||||
|
dst = r.Flocal
|
||||||
|
f1 = r.WriteObject(ctx, "file1.txt", "content 1", t1)
|
||||||
|
f2 = r.WriteObject(ctx, "dir1/sub1.txt", "sub content 1", t1)
|
||||||
|
f3 = r.WriteObject(ctx, "dir2/sub2a.txt", "sub content 2a", t1)
|
||||||
|
}
|
||||||
|
fstest.CheckItems(t, src, f1, f2, f3)
|
||||||
|
|
||||||
|
// create archive on dst
|
||||||
|
archiveName := "test." + extension
|
||||||
|
err = create.ArchiveCreate(ctx, dst, archiveName, src, "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// list archive on dst
|
||||||
|
expected := map[string]int64{
|
||||||
|
"file1.txt": 9,
|
||||||
|
"dir1/": 0,
|
||||||
|
"dir1/sub1.txt": 13,
|
||||||
|
"dir2/": 0,
|
||||||
|
"dir2/sub2a.txt": 14,
|
||||||
|
}
|
||||||
|
listFile := func(ctx context.Context, f archives.FileInfo) error {
|
||||||
|
name := f.NameInArchive
|
||||||
|
gotSize := f.Size()
|
||||||
|
if f.IsDir() && !strings.HasSuffix(name, "/") {
|
||||||
|
name += "/"
|
||||||
|
gotSize = 0
|
||||||
|
}
|
||||||
|
wantSize, found := expected[name]
|
||||||
|
assert.True(t, found, name)
|
||||||
|
assert.Equal(t, wantSize, gotSize)
|
||||||
|
delete(expected, name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = list.ArchiveList(ctx, dst, archiveName, listFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(expected), expected)
|
||||||
|
|
||||||
|
// clear the src
|
||||||
|
require.NoError(t, operations.Purge(ctx, src, ""))
|
||||||
|
require.NoError(t, src.Mkdir(ctx, ""))
|
||||||
|
fstest.CheckItems(t, src)
|
||||||
|
|
||||||
|
// extract dst archive back to src
|
||||||
|
err = extract.ArchiveExtract(ctx, src, subDir, dst, archiveName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// check files on src are restored from the archive on dst
|
||||||
|
items := []fstest.Item{f1, f2, f3}
|
||||||
|
if subDir != "" {
|
||||||
|
for i := range items {
|
||||||
|
item := &items[i]
|
||||||
|
item.Path = subDir + "/" + item.Path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fstest.CheckListingWithPrecision(t, src, items, nil, fs.ModTimeNotSupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testArchive(t *testing.T) {
|
||||||
|
var extensions = []string{
|
||||||
|
"zip",
|
||||||
|
"tar",
|
||||||
|
"tar.gz",
|
||||||
|
"tar.bz2",
|
||||||
|
"tar.lz",
|
||||||
|
"tar.lz4",
|
||||||
|
"tar.xz",
|
||||||
|
"tar.zst",
|
||||||
|
"tar.br",
|
||||||
|
"tar.sz",
|
||||||
|
"tar.mz",
|
||||||
|
}
|
||||||
|
for _, extension := range extensions {
|
||||||
|
t.Run(extension, func(t *testing.T) {
|
||||||
|
for _, subDir := range []string{"", "subdir"} {
|
||||||
|
name := subDir
|
||||||
|
if name == "" {
|
||||||
|
name = "root"
|
||||||
|
}
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Run("local", func(t *testing.T) {
|
||||||
|
testArchiveRemote(t, true, name, extension)
|
||||||
|
})
|
||||||
|
t.Run("remote", func(t *testing.T) {
|
||||||
|
testArchiveRemote(t, false, name, extension)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntegration(t *testing.T) {
|
||||||
|
testArchive(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemory(t *testing.T) {
|
||||||
|
if *fstest.RemoteName != "" {
|
||||||
|
t.Skip("skipping as -remote is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset -remote to point to :memory:
|
||||||
|
oldFstestRemoteName := fstest.RemoteName
|
||||||
|
remoteName := ":memory:"
|
||||||
|
fstest.RemoteName = &remoteName
|
||||||
|
defer func() {
|
||||||
|
fstest.RemoteName = oldFstestRemoteName
|
||||||
|
}()
|
||||||
|
fstest.ResetRun()
|
||||||
|
|
||||||
|
testArchive(t)
|
||||||
|
}
|
||||||
7
cmd/archive/archive_unsupported.go
Normal file
7
cmd/archive/archive_unsupported.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Build for unsupported platforms to stop go complaining
|
||||||
|
// about "no buildable Go source files "
|
||||||
|
|
||||||
|
//go:build plan9
|
||||||
|
|
||||||
|
// Package archive implements 'rclone archive'.
|
||||||
|
package archive
|
||||||
388
cmd/archive/create/create.go
Normal file
388
cmd/archive/create/create.go
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
//go:build !plan9
|
||||||
|
|
||||||
|
// Package create implements 'rclone archive create'.
|
||||||
|
package create
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mholt/archives"
|
||||||
|
"github.com/rclone/rclone/cmd"
|
||||||
|
"github.com/rclone/rclone/cmd/archive"
|
||||||
|
"github.com/rclone/rclone/cmd/archive/files"
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/rclone/rclone/fs/config/flags"
|
||||||
|
"github.com/rclone/rclone/fs/filter"
|
||||||
|
"github.com/rclone/rclone/fs/operations"
|
||||||
|
"github.com/rclone/rclone/fs/walk"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fullPath = false
|
||||||
|
prefix = ""
|
||||||
|
format = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flagSet := Command.Flags()
|
||||||
|
flags.BoolVarP(flagSet, &fullPath, "full-path", "", fullPath, "Set prefix for files in archive to source path", "")
|
||||||
|
flags.StringVarP(flagSet, &prefix, "prefix", "", prefix, "Set prefix for files in archive to entered value or source path", "")
|
||||||
|
flags.StringVarP(flagSet, &format, "format", "", format, "Create the archive with format or guess from extension.", "")
|
||||||
|
archive.Command.AddCommand(Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command - create
|
||||||
|
var Command = &cobra.Command{
|
||||||
|
Use: "create [flags] <source> [<destination>]",
|
||||||
|
Short: `Archive source file(s) to destination.`,
|
||||||
|
// Warning! "!" will be replaced by backticks below
|
||||||
|
Long: strings.ReplaceAll(`
|
||||||
|
Creates an archive from the files in source:path and saves the archive to
|
||||||
|
dest:path. If dest:path is missing, it will write to the console.
|
||||||
|
|
||||||
|
The valid formats for the !--format! flag are listed below. If
|
||||||
|
!--format! is not set rclone will guess it from the extension of dest:path.
|
||||||
|
|
||||||
|
| Format | Extensions |
|
||||||
|
|:-------|:-----------|
|
||||||
|
| zip | .zip |
|
||||||
|
| tar | .tar |
|
||||||
|
| tar.gz | .tar.gz, .tgz, .taz |
|
||||||
|
| tar.bz2| .tar.bz2, .tb2, .tbz, .tbz2, .tz2 |
|
||||||
|
| tar.lz | .tar.lz |
|
||||||
|
| tar.lz4| .tar.lz4 |
|
||||||
|
| tar.xz | .tar.xz, .txz |
|
||||||
|
| tar.zst| .tar.zst, .tzst |
|
||||||
|
| tar.br | .tar.br |
|
||||||
|
| tar.sz | .tar.sz |
|
||||||
|
| tar.mz | .tar.mz |
|
||||||
|
|
||||||
|
The !--prefix! and !--full-path! flags control the prefix for the files
|
||||||
|
in the archive.
|
||||||
|
|
||||||
|
If the flag !--full-path! is set then the files will have the full source
|
||||||
|
path as the prefix.
|
||||||
|
|
||||||
|
If the flag !--prefix=<value>! is set then the files will have
|
||||||
|
!<value>! as prefix. It's possible to create invalid file names with
|
||||||
|
!--prefix=<value>! so use with caution. Flag !--prefix! has
|
||||||
|
priority over !--full-path!.
|
||||||
|
|
||||||
|
Given a directory !/sourcedir! with the following:
|
||||||
|
|
||||||
|
file1.txt
|
||||||
|
dir1/file2.txt
|
||||||
|
|
||||||
|
Running the command !rclone archive create /sourcedir /dest.tar.gz!
|
||||||
|
will make an archive with the contents:
|
||||||
|
|
||||||
|
file1.txt
|
||||||
|
dir1/
|
||||||
|
dir1/file2.txt
|
||||||
|
|
||||||
|
Running the command !rclone archive create --full-path /sourcedir /dest.tar.gz!
|
||||||
|
will make an archive with the contents:
|
||||||
|
|
||||||
|
sourcedir/file1.txt
|
||||||
|
sourcedir/dir1/
|
||||||
|
sourcedir/dir1/file2.txt
|
||||||
|
|
||||||
|
Running the command !rclone archive create --prefix=my_new_path /sourcedir /dest.tar.gz!
|
||||||
|
will make an archive with the contents:
|
||||||
|
|
||||||
|
my_new_path/file1.txt
|
||||||
|
my_new_path/dir1/
|
||||||
|
my_new_path/dir1/file2.txt
|
||||||
|
`, "!", "`"),
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"versionIntroduced": "v1.72",
|
||||||
|
},
|
||||||
|
RunE: func(command *cobra.Command, args []string) error {
|
||||||
|
var src, dst fs.Fs
|
||||||
|
var dstFile string
|
||||||
|
if len(args) == 1 { // source only, archive to stdout
|
||||||
|
src = cmd.NewFsSrc(args)
|
||||||
|
} else if len(args) == 2 {
|
||||||
|
src = cmd.NewFsSrc(args)
|
||||||
|
dst, dstFile = cmd.NewFsDstFile(args[1:2])
|
||||||
|
} else {
|
||||||
|
cmd.CheckArgs(1, 2, command, args)
|
||||||
|
}
|
||||||
|
cmd.Run(false, false, command, func() error {
|
||||||
|
fmt.Printf("dst=%v, dstFile=%q, src=%v, format=%q, prefix=%q\n", dst, dstFile, src, format, prefix)
|
||||||
|
if prefix != "" {
|
||||||
|
return ArchiveCreate(context.Background(), dst, dstFile, src, format, prefix)
|
||||||
|
} else if fullPath {
|
||||||
|
return ArchiveCreate(context.Background(), dst, dstFile, src, format, src.Root())
|
||||||
|
}
|
||||||
|
return ArchiveCreate(context.Background(), dst, dstFile, src, format, "")
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Globals
|
||||||
|
var (
|
||||||
|
archiveFormats = map[string]archives.CompressedArchive{
|
||||||
|
"zip": archives.CompressedArchive{
|
||||||
|
Archival: archives.Zip{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
"tar": archives.CompressedArchive{
|
||||||
|
Archival: archives.Tar{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
"tar.gz": archives.CompressedArchive{
|
||||||
|
Compression: archives.Gz{},
|
||||||
|
Archival: archives.Tar{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
"tar.bz2": archives.CompressedArchive{
|
||||||
|
Compression: archives.Bz2{},
|
||||||
|
Archival: archives.Tar{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
"tar.lz": archives.CompressedArchive{
|
||||||
|
Compression: archives.Lzip{},
|
||||||
|
Archival: archives.Tar{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
"tar.lz4": archives.CompressedArchive{
|
||||||
|
Compression: archives.Lz4{},
|
||||||
|
Archival: archives.Tar{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
"tar.xz": archives.CompressedArchive{
|
||||||
|
Compression: archives.Xz{},
|
||||||
|
Archival: archives.Tar{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
"tar.zst": archives.CompressedArchive{
|
||||||
|
Compression: archives.Zstd{},
|
||||||
|
Archival: archives.Tar{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
"tar.br": archives.CompressedArchive{
|
||||||
|
Compression: archives.Brotli{},
|
||||||
|
Archival: archives.Tar{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
"tar.sz": archives.CompressedArchive{
|
||||||
|
Compression: archives.Sz{},
|
||||||
|
Archival: archives.Tar{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
"tar.mz": archives.CompressedArchive{
|
||||||
|
Compression: archives.MinLZ{},
|
||||||
|
Archival: archives.Tar{ContinueOnError: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
archiveExtensions = map[string]string{
|
||||||
|
// zip
|
||||||
|
"*.zip": "zip",
|
||||||
|
// tar
|
||||||
|
"*.tar": "tar",
|
||||||
|
// tar.gz
|
||||||
|
"*.tar.gz": "tar.gz",
|
||||||
|
"*.tgz": "tar.gz",
|
||||||
|
"*.taz": "tar.gz",
|
||||||
|
// tar.bz2
|
||||||
|
"*.tar.bz2": "tar.bz2",
|
||||||
|
"*.tb2": "tar.bz2",
|
||||||
|
"*.tbz": "tar.bz2",
|
||||||
|
"*.tbz2": "tar.bz2",
|
||||||
|
"*.tz2": "tar.bz2",
|
||||||
|
// tar.lz
|
||||||
|
"*.tar.lz": "tar.lz",
|
||||||
|
// tar.lz4
|
||||||
|
"*.tar.lz4": "tar.lz4",
|
||||||
|
// tar.xz
|
||||||
|
"*.tar.xz": "tar.xz",
|
||||||
|
"*.txz": "tar.xz",
|
||||||
|
// tar.zst
|
||||||
|
"*.tar.zst": "tar.zst",
|
||||||
|
"*.tzst": "tar.zst",
|
||||||
|
// tar.br
|
||||||
|
"*.tar.br": "tar.br",
|
||||||
|
// tar.sz
|
||||||
|
"*.tar.sz": "tar.sz",
|
||||||
|
// tar.mz
|
||||||
|
"*.tar.mz": "tar.mz",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// sorted FileInfo list
|
||||||
|
|
||||||
|
type archivesFileInfoList []archives.FileInfo
|
||||||
|
|
||||||
|
func (a archivesFileInfoList) Len() int {
|
||||||
|
return len(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a archivesFileInfoList) Less(i, j int) bool {
|
||||||
|
if a[i].FileInfo.IsDir() == a[j].FileInfo.IsDir() {
|
||||||
|
// both are same type, order by name
|
||||||
|
return strings.Compare(a[i].NameInArchive, a[j].NameInArchive) < 0
|
||||||
|
} else if a[i].FileInfo.IsDir() {
|
||||||
|
return strings.Compare(strings.TrimSuffix(a[i].NameInArchive, "/"), path.Dir(a[j].NameInArchive)) < 0
|
||||||
|
}
|
||||||
|
return strings.Compare(path.Dir(a[i].NameInArchive), strings.TrimSuffix(a[j].NameInArchive, "/")) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a archivesFileInfoList) Swap(i, j int) {
|
||||||
|
a[i], a[j] = a[j], a[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCompressor(format string, filename string) (archives.CompressedArchive, error) {
|
||||||
|
var compressor archives.CompressedArchive
|
||||||
|
var found bool
|
||||||
|
// make filename lowercase for checks
|
||||||
|
filename = strings.ToLower(filename)
|
||||||
|
|
||||||
|
if format == "" {
|
||||||
|
// format flag not set, get format from the file extension
|
||||||
|
for pattern, formatName := range archiveExtensions {
|
||||||
|
ok, err := path.Match(pattern, filename)
|
||||||
|
if err != nil {
|
||||||
|
// error in pattern
|
||||||
|
return archives.CompressedArchive{}, fmt.Errorf("invalid extension pattern '%s'", pattern)
|
||||||
|
} else if ok {
|
||||||
|
// pattern matches filename, get compressor
|
||||||
|
compressor, found = archiveFormats[formatName]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// format flag set, look for it
|
||||||
|
compressor, found = archiveFormats[format]
|
||||||
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
|
return compressor, nil
|
||||||
|
} else if format == "" {
|
||||||
|
return archives.CompressedArchive{}, fmt.Errorf("format not set and can't be guessed from extension")
|
||||||
|
}
|
||||||
|
return archives.CompressedArchive{}, fmt.Errorf("invalid format '%s'", format)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckValidDestination - takes (dst, dstFile) and checks it is valid
|
||||||
|
func CheckValidDestination(ctx context.Context, dst fs.Fs, dstFile string) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// check if dst + dstFile is a file
|
||||||
|
_, err = dst.NewObject(ctx, dstFile)
|
||||||
|
if err == nil {
|
||||||
|
// (dst, dstFile) is a valid file we can overwrite
|
||||||
|
return nil
|
||||||
|
} else if errors.Is(err, fs.ErrorIsDir) {
|
||||||
|
// dst is a directory
|
||||||
|
return fmt.Errorf("destination must not be a directory: %w", err)
|
||||||
|
} else if !errors.Is(err, fs.ErrorObjectNotFound) {
|
||||||
|
// dst is a directory (we need a filename) or some other error happened
|
||||||
|
// not good, leave
|
||||||
|
return fmt.Errorf("error reading destination: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are here dst points to a non existent path
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMetadata(ctx context.Context, o fs.DirEntry) fs.Metadata {
|
||||||
|
meta, err := fs.GetMetadata(ctx, o)
|
||||||
|
if err != nil {
|
||||||
|
meta = make(fs.Metadata, 0)
|
||||||
|
}
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveCreate - compresses/archive source to destination
|
||||||
|
func ArchiveCreate(ctx context.Context, dst fs.Fs, dstFile string, src fs.Fs, format string, prefix string) error {
|
||||||
|
var err error
|
||||||
|
var list archivesFileInfoList
|
||||||
|
var compArchive archives.CompressedArchive
|
||||||
|
var totalLength int64
|
||||||
|
|
||||||
|
// check id dst is valid
|
||||||
|
err = CheckValidDestination(ctx, dst, dstFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ci := fs.GetConfig(ctx)
|
||||||
|
fi := filter.GetConfig(ctx)
|
||||||
|
// get archive format
|
||||||
|
compArchive, err = getCompressor(format, dstFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// get source files
|
||||||
|
err = walk.ListR(ctx, src, "", false, ci.MaxDepth, walk.ListAll, func(entries fs.DirEntries) error {
|
||||||
|
// get directories
|
||||||
|
entries.ForDir(func(o fs.Directory) {
|
||||||
|
var metadata fs.Metadata
|
||||||
|
if ci.Metadata {
|
||||||
|
metadata = loadMetadata(ctx, o)
|
||||||
|
}
|
||||||
|
if fi.Include(o.Remote(), o.Size(), o.ModTime(ctx), metadata) {
|
||||||
|
info := files.NewArchiveFileInfo(ctx, o, prefix, metadata)
|
||||||
|
list = append(list, info)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// get files
|
||||||
|
entries.ForObject(func(o fs.Object) {
|
||||||
|
var metadata fs.Metadata
|
||||||
|
if ci.Metadata {
|
||||||
|
metadata = loadMetadata(ctx, o)
|
||||||
|
}
|
||||||
|
if fi.Include(o.Remote(), o.Size(), o.ModTime(ctx), metadata) {
|
||||||
|
info := files.NewArchiveFileInfo(ctx, o, prefix, metadata)
|
||||||
|
list = append(list, info)
|
||||||
|
totalLength += o.Size()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if list.Len() == 0 {
|
||||||
|
return fmt.Errorf("no files found in source")
|
||||||
|
}
|
||||||
|
sort.Stable(list)
|
||||||
|
// create archive
|
||||||
|
if ci.DryRun {
|
||||||
|
// write nowhere
|
||||||
|
counter := files.NewCountWriter(nil)
|
||||||
|
err = compArchive.Archive(ctx, counter, list)
|
||||||
|
// log totals
|
||||||
|
fs.Infof(nil, "Total files added %d", list.Len())
|
||||||
|
fs.Infof(nil, "Total bytes read %d", totalLength)
|
||||||
|
fs.Infof(nil, "Compressed file size %d", counter.Count())
|
||||||
|
|
||||||
|
return err
|
||||||
|
} else if dst == nil {
|
||||||
|
// write to stdout
|
||||||
|
counter := files.NewCountWriter(os.Stdout)
|
||||||
|
err = compArchive.Archive(ctx, counter, list)
|
||||||
|
// log totals
|
||||||
|
fs.Infof(nil, "Total files added %d", list.Len())
|
||||||
|
fs.Infof(nil, "Total bytes read %d", totalLength)
|
||||||
|
fs.Infof(nil, "Compressed file size %d", counter.Count())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// write to remote
|
||||||
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
|
// write to pipewriter in background
|
||||||
|
counter := files.NewCountWriter(pipeWriter)
|
||||||
|
go func() {
|
||||||
|
err := compArchive.Archive(ctx, counter, list)
|
||||||
|
pipeWriter.CloseWithError(err)
|
||||||
|
}()
|
||||||
|
// rcat to remote from pipereader
|
||||||
|
_, err = operations.Rcat(ctx, dst, dstFile, pipeReader, time.Now(), nil)
|
||||||
|
// log totals
|
||||||
|
fs.Infof(nil, "Total files added %d", list.Len())
|
||||||
|
fs.Infof(nil, "Total bytes read %d", totalLength)
|
||||||
|
fs.Infof(nil, "Compressed file size %d", counter.Count())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
7
cmd/archive/create/create_unsupported.go
Normal file
7
cmd/archive/create/create_unsupported.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Build for unsupported platforms to stop go complaining
|
||||||
|
// about "no buildable Go source files "
|
||||||
|
|
||||||
|
//go:build plan9
|
||||||
|
|
||||||
|
// Package archive implements 'rclone archive create'.
|
||||||
|
package create
|
||||||
191
cmd/archive/extract/extract.go
Normal file
191
cmd/archive/extract/extract.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
//go:build !plan9
|
||||||
|
|
||||||
|
// Package extract implements 'rclone archive extract'
|
||||||
|
package extract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"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/filter"
|
||||||
|
"github.com/rclone/rclone/fs/operations"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
archive.Command.AddCommand(Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command - extract
|
||||||
|
var Command = &cobra.Command{
|
||||||
|
Use: "extract [flags] <source> <destination>",
|
||||||
|
Short: `Extract archives from source to destination.`,
|
||||||
|
Long: strings.ReplaceAll(`
|
||||||
|
|
||||||
|
Extract the archive contents to a destination directory auto detecting
|
||||||
|
the format. See [rclone archive create](/commands/rclone_archive_create/)
|
||||||
|
for the archive formats supported.
|
||||||
|
|
||||||
|
For example on this archive:
|
||||||
|
|
||||||
|
|||
|
||||||
|
$ 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
|
||||||
|
|||
|
||||||
|
|
||||||
|
You can run extract like this
|
||||||
|
|
||||||
|
|||
|
||||||
|
$ rclone archive extract remote:archive.zip remote:extracted
|
||||||
|
|||
|
||||||
|
|
||||||
|
Which gives this result
|
||||||
|
|
||||||
|
|||
|
||||||
|
$ rclone tree remote:extracted
|
||||||
|
/
|
||||||
|
├── dir
|
||||||
|
│ └── bye.txt
|
||||||
|
└── file.txt
|
||||||
|
|||
|
||||||
|
|
||||||
|
The source or destination or both can be local or remote.
|
||||||
|
|
||||||
|
Filters can be used to only extract certain files:
|
||||||
|
|
||||||
|
|||
|
||||||
|
$ rclone archive extract archive.zip partial --include "bye.*"
|
||||||
|
$ rclone tree partial
|
||||||
|
/
|
||||||
|
└── dir
|
||||||
|
└── bye.txt
|
||||||
|
|||
|
||||||
|
|
||||||
|
The [archive backend](/archive/) can also be used to extract 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(2, 2, command, args)
|
||||||
|
|
||||||
|
src, srcFile := cmd.NewFsFile(args[0])
|
||||||
|
dst, dstFile := cmd.NewFsFile(args[1])
|
||||||
|
|
||||||
|
cmd.Run(false, false, command, func() error {
|
||||||
|
return ArchiveExtract(context.Background(), dst, dstFile, src, srcFile)
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveExtract extracts files from (src, srcFile) to (dst, dstDir)
|
||||||
|
func ArchiveExtract(ctx context.Context, dst fs.Fs, dstDir string, src fs.Fs, srcFile string) error {
|
||||||
|
var srcObj fs.Object
|
||||||
|
var filesExtracted = 0
|
||||||
|
var err error
|
||||||
|
|
||||||
|
fi := filter.GetConfig(ctx)
|
||||||
|
ci := fs.GetConfig(ctx)
|
||||||
|
// get source object
|
||||||
|
srcObj, err = src.NewObject(ctx, srcFile)
|
||||||
|
fs.Debugf(nil, "srcFile: %q, src : %v", srcFile, src)
|
||||||
|
if errors.Is(err, fs.ErrorIsDir) {
|
||||||
|
return fmt.Errorf("source can't be a directory: %w", err)
|
||||||
|
} else if errors.Is(err, fs.ErrorObjectNotFound) {
|
||||||
|
return fmt.Errorf("source not found: %w", err)
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("unable to access source: %w", err)
|
||||||
|
}
|
||||||
|
fs.Debugf(nil, "Source archive file: %s/%s", src.Root(), srcFile)
|
||||||
|
// Create destination directory
|
||||||
|
err = dst.Mkdir(ctx, dstDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to access destination: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.Debugf(dst, "Destination for extracted files: %q", dstDir)
|
||||||
|
// start accounting
|
||||||
|
tr := accounting.Stats(ctx).NewTransfer(srcObj, nil)
|
||||||
|
defer 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()
|
||||||
|
in := tr.Account(ctx, in0)
|
||||||
|
// 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, "Extract %s/%s, format %s to %s", src.Root(), srcFile, strings.TrimPrefix(format.Extension(), "."), dst.Root())
|
||||||
|
|
||||||
|
// 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(), "."))
|
||||||
|
}
|
||||||
|
// extract files
|
||||||
|
err = ex.Extract(ctx, in, func(ctx context.Context, f archives.FileInfo) error {
|
||||||
|
remote := f.NameInArchive
|
||||||
|
if dstDir != "" {
|
||||||
|
remote = path.Join(dstDir, remote)
|
||||||
|
}
|
||||||
|
// check if file should be extracted
|
||||||
|
if !fi.Include(remote, f.Size(), f.ModTime(), fs.Metadata{}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// process directory
|
||||||
|
if f.IsDir() {
|
||||||
|
// directory
|
||||||
|
fs.Debugf(nil, "mkdir %s", remote)
|
||||||
|
// leave if --dry-run set
|
||||||
|
if ci.DryRun {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// create the directory
|
||||||
|
return operations.Mkdir(ctx, dst, remote)
|
||||||
|
}
|
||||||
|
// process file
|
||||||
|
fs.Debugf(nil, "Extract %s", remote)
|
||||||
|
// leave if --dry-run set
|
||||||
|
if ci.DryRun {
|
||||||
|
filesExtracted++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// open file
|
||||||
|
fin, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// extract the file to destination
|
||||||
|
_, err = operations.Rcat(ctx, dst, remote, fin, f.ModTime(), nil)
|
||||||
|
if err == nil {
|
||||||
|
filesExtracted++
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
fs.Infof(nil, "Total files extracted %d", filesExtracted)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
7
cmd/archive/extract/extract_unsupported.go
Normal file
7
cmd/archive/extract/extract_unsupported.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Build for unsupported platforms to stop go complaining
|
||||||
|
// about "no buildable Go source files "
|
||||||
|
|
||||||
|
//go:build plan9
|
||||||
|
|
||||||
|
// Package archive implements 'rclone archive extract'.
|
||||||
|
package extract
|
||||||
34
cmd/archive/files/countwriter.go
Normal file
34
cmd/archive/files/countwriter.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CountWriter counts bytes written through it.
|
||||||
|
// It is safe for concurrent Count/Reset; Write is as safe as the wrapped Writer.
|
||||||
|
type CountWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
count atomic.Uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCountWriter wraps w (use nil if you want to drop data).
|
||||||
|
func NewCountWriter(w io.Writer) *CountWriter {
|
||||||
|
if w == nil {
|
||||||
|
w = io.Discard
|
||||||
|
}
|
||||||
|
return &CountWriter{w: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *CountWriter) Write(p []byte) (int, error) {
|
||||||
|
n, err := cw.w.Write(p)
|
||||||
|
if n > 0 {
|
||||||
|
cw.count.Add(uint64(n))
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the total bytes written.
|
||||||
|
func (cw *CountWriter) Count() uint64 {
|
||||||
|
return cw.count.Load()
|
||||||
|
}
|
||||||
109
cmd/archive/files/countwriter_test.go
Normal file
109
cmd/archive/files/countwriter_test.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stubWriter struct {
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s stubWriter) Write(p []byte) (int, error) {
|
||||||
|
if s.n > len(p) {
|
||||||
|
return len(p), s.err
|
||||||
|
}
|
||||||
|
return s.n, s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCountWriter(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("initial count is zero", func(t *testing.T) {
|
||||||
|
cw := NewCountWriter(io.Discard)
|
||||||
|
require.Equal(t, uint64(0), cw.Count())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("counts bytes with real writes", func(t *testing.T) {
|
||||||
|
cw := NewCountWriter(io.Discard)
|
||||||
|
n, err := cw.Write([]byte("abcd"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 4, n)
|
||||||
|
assert.Equal(t, uint64(4), cw.Count())
|
||||||
|
|
||||||
|
n, err = cw.Write([]byte("xyz"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 3, n)
|
||||||
|
assert.Equal(t, uint64(7), cw.Count())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil writer uses io.Discard", func(t *testing.T) {
|
||||||
|
cw := NewCountWriter(nil)
|
||||||
|
n, err := cw.Write([]byte("ok"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, n)
|
||||||
|
assert.Equal(t, uint64(2), cw.Count())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("zero-length write does not change count", func(t *testing.T) {
|
||||||
|
cw := NewCountWriter(io.Discard)
|
||||||
|
n, err := cw.Write(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, n)
|
||||||
|
assert.Equal(t, uint64(0), cw.Count())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("partial write with error counts n and returns error", func(t *testing.T) {
|
||||||
|
s := stubWriter{n: 3, err: errors.New("boom")}
|
||||||
|
cw := NewCountWriter(s)
|
||||||
|
n, err := cw.Write([]byte("abcdef"))
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, 3, n)
|
||||||
|
assert.Equal(t, uint64(3), cw.Count())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("short successful write counts returned n", func(t *testing.T) {
|
||||||
|
s := stubWriter{n: 1}
|
||||||
|
cw := NewCountWriter(s)
|
||||||
|
n, err := cw.Write([]byte("hi"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, n)
|
||||||
|
assert.Equal(t, uint64(1), cw.Count())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCountWriterConcurrent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const (
|
||||||
|
goroutines = 32
|
||||||
|
loops = 200
|
||||||
|
chunkSize = 64
|
||||||
|
)
|
||||||
|
data := make([]byte, chunkSize)
|
||||||
|
|
||||||
|
cw := NewCountWriter(io.Discard)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(goroutines)
|
||||||
|
for g := 0; g < goroutines; g++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < loops; i++ {
|
||||||
|
n, err := cw.Write(data)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, chunkSize, n)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
want := uint64(goroutines * loops * chunkSize)
|
||||||
|
assert.Equal(t, want, cw.Count())
|
||||||
|
}
|
||||||
235
cmd/archive/files/files.go
Normal file
235
cmd/archive/files/files.go
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
// 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())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
185
cmd/archive/list/list.go
Normal file
185
cmd/archive/list/list.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
//go:build !plan9
|
||||||
|
|
||||||
|
// Package list inplements '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()
|
||||||
|
in := tr.Account(ctx, in0)
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
7
cmd/archive/list/list_unsupported.go
Normal file
7
cmd/archive/list/list_unsupported.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Build for unsupported platforms to stop go complaining
|
||||||
|
// about "no buildable Go source files "
|
||||||
|
|
||||||
|
//go:build plan9
|
||||||
|
|
||||||
|
// Package archive implements 'rclone archive list'.
|
||||||
|
package list
|
||||||
@@ -7,7 +7,9 @@ versionIntroduced: "v1.72"
|
|||||||
# {{< icon "fas fa-archive" >}} Archive
|
# {{< icon "fas fa-archive" >}} Archive
|
||||||
|
|
||||||
The Archive backend allows read only access to the content of archive
|
The Archive backend allows read only access to the content of archive
|
||||||
files on cloud storage without downloading them completely.
|
files on cloud storage without downloading the complete archive. This
|
||||||
|
means you could mount a large archive file and use only the parts of
|
||||||
|
it your application requires, rather than having to extract it.
|
||||||
|
|
||||||
The archive files are recognised by their extension.
|
The archive files are recognised by their extension.
|
||||||
|
|
||||||
@@ -19,6 +21,18 @@ The archive files are recognised by their extension.
|
|||||||
The supported archive file types are cloud friendly - a single file
|
The supported archive file types are cloud friendly - a single file
|
||||||
can be found and downloaded without downloading the whole archive.
|
can be found and downloaded without downloading the whole archive.
|
||||||
|
|
||||||
|
If you just want to create, list or extract archives and don't want to
|
||||||
|
mount them then you may find the `rclone archive` commands more
|
||||||
|
convenient.
|
||||||
|
|
||||||
|
- [rclone archive create](/commands/rclone_archive_create/)
|
||||||
|
- [rclone archive list](/commands/rclone_archive_list/)
|
||||||
|
- [rclone archive extract](/commands/rclone_archive_extract/)
|
||||||
|
|
||||||
|
These commands supports a wider range of non cloud friendly archives
|
||||||
|
(but not squashfs) but can't be used for `rclone mount` or any other
|
||||||
|
rclone commands (eg `rclone check`).
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
This backend is best used without configuration.
|
This backend is best used without configuration.
|
||||||
@@ -184,7 +198,9 @@ mksquashfs 100files 100files.sqfs -comp zstd -b 1M
|
|||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
Files in archive are read only. It isn't possible to create archives yet.
|
Files in the archive backend are read only. It isn't possible to
|
||||||
|
create archives with the archive backend yet. However you **can** create
|
||||||
|
archives with [rclone archive create](/commands/rclone_archive_create/).
|
||||||
|
|
||||||
Only `.zip` and `.sqfs` archives are supported as these are the only
|
Only `.zip` and `.sqfs` archives are supported as these are the only
|
||||||
common archiving formats which make it easy to read directory listings
|
common archiving formats which make it easy to read directory listings
|
||||||
|
|||||||
15
go.mod
15
go.mod
@@ -51,6 +51,7 @@ require (
|
|||||||
github.com/lanrat/extsort v1.4.2
|
github.com/lanrat/extsort v1.4.2
|
||||||
github.com/mattn/go-colorable v0.1.14
|
github.com/mattn/go-colorable v0.1.14
|
||||||
github.com/mattn/go-runewidth v0.0.17
|
github.com/mattn/go-runewidth v0.0.17
|
||||||
|
github.com/mholt/archives v0.1.5
|
||||||
github.com/minio/minio-go/v7 v7.0.95
|
github.com/minio/minio-go/v7 v7.0.95
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/moby/sys/mountinfo v0.7.2
|
github.com/moby/sys/mountinfo v0.7.2
|
||||||
@@ -110,9 +111,11 @@ require (
|
|||||||
github.com/ProtonMail/go-srp v0.0.7 // indirect
|
github.com/ProtonMail/go-srp v0.0.7 // indirect
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.9.0 // indirect
|
github.com/ProtonMail/gopenpgp/v2 v2.9.0 // indirect
|
||||||
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
||||||
|
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||||
github.com/akavel/rsrc v0.10.2 // indirect
|
github.com/akavel/rsrc v0.10.2 // indirect
|
||||||
github.com/anacrolix/generics v0.1.0 // indirect
|
github.com/anacrolix/generics v0.1.0 // indirect
|
||||||
github.com/anchore/go-lzo v0.1.0 // indirect
|
github.com/anchore/go-lzo v0.1.0 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc // indirect
|
github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc // indirect
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||||
@@ -131,6 +134,9 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect
|
||||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||||
|
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||||
|
github.com/bodgit/windows v1.0.1 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/bradenaw/juniper v0.15.3 // indirect
|
github.com/bradenaw/juniper v0.15.3 // indirect
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
||||||
@@ -144,6 +150,7 @@ require (
|
|||||||
github.com/creasty/defaults v1.8.0 // indirect
|
github.com/creasty/defaults v1.8.0 // indirect
|
||||||
github.com/cronokirby/saferith v0.33.0 // indirect
|
github.com/cronokirby/saferith v0.33.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/ebitengine/purego v0.9.0 // indirect
|
github.com/ebitengine/purego v0.9.0 // indirect
|
||||||
github.com/emersion/go-message v0.18.2 // indirect
|
github.com/emersion/go-message v0.18.2 // indirect
|
||||||
@@ -184,6 +191,7 @@ require (
|
|||||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||||
github.com/jtolio/noiseconn v0.0.0-20231127013910-f6d9ecbf1de7 // indirect
|
github.com/jtolio/noiseconn v0.0.0-20231127013910-f6d9ecbf1de7 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
@@ -192,11 +200,14 @@ require (
|
|||||||
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
|
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
|
github.com/minio/minlz v1.0.1 // indirect
|
||||||
github.com/minio/xxml v0.0.3 // indirect
|
github.com/minio/xxml v0.0.3 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
|
||||||
github.com/oklog/ulid v1.3.1 // indirect
|
github.com/oklog/ulid v1.3.1 // indirect
|
||||||
github.com/panjf2000/ants/v2 v2.11.3 // indirect
|
github.com/panjf2000/ants/v2 v2.11.3 // indirect
|
||||||
github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14 // indirect
|
github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14 // indirect
|
||||||
@@ -219,7 +230,10 @@ require (
|
|||||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
|
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect
|
||||||
github.com/smartystreets/goconvey v1.8.1 // indirect
|
github.com/smartystreets/goconvey v1.8.1 // indirect
|
||||||
github.com/sony/gobreaker v1.0.0 // indirect
|
github.com/sony/gobreaker v1.0.0 // indirect
|
||||||
|
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||||
github.com/spacemonkeygo/monkit/v3 v3.0.24 // indirect
|
github.com/spacemonkeygo/monkit/v3 v3.0.24 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/therootcompany/xz v1.0.1 // indirect
|
||||||
github.com/tinylib/msgp v1.4.0 // indirect
|
github.com/tinylib/msgp v1.4.0 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||||
@@ -234,6 +248,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
|
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
||||||
golang.org/x/tools v0.37.0 // indirect
|
golang.org/x/tools v0.37.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect
|
||||||
|
|||||||
50
go.sum
50
go.sum
@@ -88,6 +88,10 @@ github.com/ProtonMail/gopenpgp/v2 v2.9.0 h1:ruLzBmwe4dR1hdnrsEJ/S7psSBmV15gFttFU
|
|||||||
github.com/ProtonMail/gopenpgp/v2 v2.9.0/go.mod h1:IldDyh9Hv1ZCCYatTuuEt1XZJ0OPjxLpTarDfglih7s=
|
github.com/ProtonMail/gopenpgp/v2 v2.9.0/go.mod h1:IldDyh9Hv1ZCCYatTuuEt1XZJ0OPjxLpTarDfglih7s=
|
||||||
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||||
|
github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
|
||||||
|
github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
|
||||||
|
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
||||||
|
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||||
github.com/a8m/tree v0.0.0-20240104212747-2c8764a5f17e h1:KMVieI1/Ub++GYfnhyFPoGE3g5TUiG4srE3TMGr5nM4=
|
github.com/a8m/tree v0.0.0-20240104212747-2c8764a5f17e h1:KMVieI1/Ub++GYfnhyFPoGE3g5TUiG4srE3TMGr5nM4=
|
||||||
github.com/a8m/tree v0.0.0-20240104212747-2c8764a5f17e/go.mod h1:j5astEcUkZQX8lK+KKlQ3NRQ50f4EE8ZjyZpCz3mrH4=
|
github.com/a8m/tree v0.0.0-20240104212747-2c8764a5f17e/go.mod h1:j5astEcUkZQX8lK+KKlQ3NRQ50f4EE8ZjyZpCz3mrH4=
|
||||||
github.com/aalpar/deheap v0.0.0-20210914013432-0cc84d79dec3 h1:hhdWprfSpFbN7lz3W1gM40vOgvSh1WCSMxYD6gGB4Hs=
|
github.com/aalpar/deheap v0.0.0-20210914013432-0cc84d79dec3 h1:hhdWprfSpFbN7lz3W1gM40vOgvSh1WCSMxYD6gGB4Hs=
|
||||||
@@ -104,6 +108,10 @@ github.com/anacrolix/log v0.17.0 h1:cZvEGRPCbIg+WK+qAxWj/ap2Gj8cx1haOCSVxNZQpK4=
|
|||||||
github.com/anacrolix/log v0.17.0/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA=
|
github.com/anacrolix/log v0.17.0/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA=
|
||||||
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
|
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
|
||||||
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
|
github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk=
|
||||||
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||||
github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc h1:LoL75er+LKDHDUfU5tRvFwxH0LjPpZN8OoG8Ll+liGU=
|
github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc h1:LoL75er+LKDHDUfU5tRvFwxH0LjPpZN8OoG8Ll+liGU=
|
||||||
@@ -154,6 +162,14 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn
|
|||||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||||
|
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||||
|
github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
|
||||||
|
github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=
|
||||||
|
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
|
||||||
|
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
|
||||||
|
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
||||||
|
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/bradenaw/juniper v0.15.3 h1:RHIAMEDTpvmzV1wg1jMAHGOoI2oJUSPx3lxRldXnFGo=
|
github.com/bradenaw/juniper v0.15.3 h1:RHIAMEDTpvmzV1wg1jMAHGOoI2oJUSPx3lxRldXnFGo=
|
||||||
@@ -219,6 +235,9 @@ github.com/dop251/scsu v0.0.0-20220106150536-84ac88021d00 h1:xJBhC00smQpSZw3Kr0E
|
|||||||
github.com/dop251/scsu v0.0.0-20220106150536-84ac88021d00/go.mod h1:nNICngOdmNImBb/vuL+dSc0aIg3ryNATpjxThNoPw4g=
|
github.com/dop251/scsu v0.0.0-20220106150536-84ac88021d00/go.mod h1:nNICngOdmNImBb/vuL+dSc0aIg3ryNATpjxThNoPw4g=
|
||||||
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5 h1:FT+t0UEDykcor4y3dMVKXIiWJETBpRgERYTGlmMd7HU=
|
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5 h1:FT+t0UEDykcor4y3dMVKXIiWJETBpRgERYTGlmMd7HU=
|
||||||
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5/go.mod h1:rSS3kM9XMzSQ6pw91Qgd6yB5jdt70N4OdtrAf74As5M=
|
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5/go.mod h1:rSS3kM9XMzSQ6pw91Qgd6yB5jdt70N4OdtrAf74As5M=
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||||
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI=
|
github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI=
|
||||||
github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40=
|
github.com/dsnet/try v0.0.3/go.mod h1:WBM8tRpUmnXXhY1U6/S8dt6UWdHTQ7y8A5YSkRCkq40=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
@@ -338,6 +357,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
@@ -429,11 +449,15 @@ github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRt
|
|||||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/koofr/go-httpclient v0.0.0-20240520111329-e20f8f203988 h1:CjEMN21Xkr9+zwPmZPaJJw+apzVbjGL5uK/6g9Q2jGU=
|
github.com/koofr/go-httpclient v0.0.0-20240520111329-e20f8f203988 h1:CjEMN21Xkr9+zwPmZPaJJw+apzVbjGL5uK/6g9Q2jGU=
|
||||||
github.com/koofr/go-httpclient v0.0.0-20240520111329-e20f8f203988/go.mod h1:/agobYum3uo/8V6yPVnq+R82pyVGCeuWW5arT4Txn8A=
|
github.com/koofr/go-httpclient v0.0.0-20240520111329-e20f8f203988/go.mod h1:/agobYum3uo/8V6yPVnq+R82pyVGCeuWW5arT4Txn8A=
|
||||||
github.com/koofr/go-koofrclient v0.0.0-20221207135200-cbd7fc9ad6a6 h1:FHVoZMOVRA+6/y4yRlbiR3WvsrOcKBd/f64H7YiWR2U=
|
github.com/koofr/go-koofrclient v0.0.0-20221207135200-cbd7fc9ad6a6 h1:FHVoZMOVRA+6/y4yRlbiR3WvsrOcKBd/f64H7YiWR2U=
|
||||||
@@ -469,12 +493,20 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
|
github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
|
||||||
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mholt/archives v0.0.0-20241226194006-fc8400ac3529 h1:XsFbmbdHgEXRCASoX0wlUi1Es+yTDhsmiUo2UVukmLs=
|
||||||
|
github.com/mholt/archives v0.0.0-20241226194006-fc8400ac3529/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I=
|
||||||
|
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||||
|
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||||
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
||||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU=
|
github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU=
|
||||||
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
|
github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo=
|
||||||
|
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
|
||||||
|
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||||
github.com/minio/xxml v0.0.3 h1:ZIpPQpfyG5uZQnqqC0LZuWtPk/WT8G/qkxvO6jb7zMU=
|
github.com/minio/xxml v0.0.3 h1:ZIpPQpfyG5uZQnqqC0LZuWtPk/WT8G/qkxvO6jb7zMU=
|
||||||
github.com/minio/xxml v0.0.3/go.mod h1:wcXErosl6IezQIMEWSK/LYC2VS7LJ1dAkgvuyIN3aH4=
|
github.com/minio/xxml v0.0.3/go.mod h1:wcXErosl6IezQIMEWSK/LYC2VS7LJ1dAkgvuyIN3aH4=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
@@ -491,6 +523,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/ncw/swift/v2 v2.0.4 h1:hHWVFxn5/YaTWAASmn4qyq2p6OyP/Hm3vMLzkjEqR7w=
|
github.com/ncw/swift/v2 v2.0.4 h1:hHWVFxn5/YaTWAASmn4qyq2p6OyP/Hm3vMLzkjEqR7w=
|
||||||
github.com/ncw/swift/v2 v2.0.4/go.mod h1:cbAO76/ZwcFrFlHdXPjaqWZ9R7Hdar7HpjRXBfbjigk=
|
github.com/ncw/swift/v2 v2.0.4/go.mod h1:cbAO76/ZwcFrFlHdXPjaqWZ9R7Hdar7HpjRXBfbjigk=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||||
@@ -566,6 +602,7 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
|||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
||||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
|
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
|
||||||
@@ -591,8 +628,14 @@ github.com/snabb/httpreaderat v1.0.1/go.mod h1:lpbGrKDWF37yvRbtRvQsbesS6Ty5c83t8
|
|||||||
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||||
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
|
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
|
||||||
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||||
|
github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
|
||||||
|
github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=
|
||||||
|
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||||
|
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
||||||
github.com/spacemonkeygo/monkit/v3 v3.0.24 h1:cKixJ+evHnfJhWNyIZjBy5hoW8LTWmrJXPo18tzLNrk=
|
github.com/spacemonkeygo/monkit/v3 v3.0.24 h1:cKixJ+evHnfJhWNyIZjBy5hoW8LTWmrJXPo18tzLNrk=
|
||||||
github.com/spacemonkeygo/monkit/v3 v3.0.24/go.mod h1:XkZYGzknZwkD0AKUnZaSXhRiVTLCkq7CWVa3IsE72gA=
|
github.com/spacemonkeygo/monkit/v3 v3.0.24/go.mod h1:XkZYGzknZwkD0AKUnZaSXhRiVTLCkq7CWVa3IsE72gA=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
@@ -618,6 +661,8 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD
|
|||||||
github.com/t3rm1n4l/go-mega v0.0.0-20250926104142-ccb8d3498e6c h1:BLopNCyqewbE8+BtlIp/Juzu8AJGxz0gHdGADnsblVc=
|
github.com/t3rm1n4l/go-mega v0.0.0-20250926104142-ccb8d3498e6c h1:BLopNCyqewbE8+BtlIp/Juzu8AJGxz0gHdGADnsblVc=
|
||||||
github.com/t3rm1n4l/go-mega v0.0.0-20250926104142-ccb8d3498e6c/go.mod h1:ykucQyiE9Q2qx1wLlEtZkkNn1IURib/2O+Mvd25i1Fo=
|
github.com/t3rm1n4l/go-mega v0.0.0-20250926104142-ccb8d3498e6c/go.mod h1:ykucQyiE9Q2qx1wLlEtZkkNn1IURib/2O+Mvd25i1Fo=
|
||||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
||||||
|
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||||
|
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||||
github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8=
|
github.com/tinylib/msgp v1.4.0 h1:SYOeDRiydzOw9kSiwdYp9UcBgPFtLU2WDHaJXyHruf8=
|
||||||
github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o=
|
github.com/tinylib/msgp v1.4.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o=
|
||||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||||
@@ -630,6 +675,7 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/unknwon/goconfig v1.0.0 h1:rS7O+CmUdli1T+oDm7fYj1MwqNWtEJfNj+FqcUHML8U=
|
github.com/unknwon/goconfig v1.0.0 h1:rS7O+CmUdli1T+oDm7fYj1MwqNWtEJfNj+FqcUHML8U=
|
||||||
@@ -644,6 +690,8 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
|
|||||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@@ -694,6 +742,8 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
|||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||||
|
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
||||||
|
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
|
||||||
goftp.io/server/v2 v2.0.2 h1:tkZpqyXys+vC15W5yGMi8Kzmbv1QSgeKr8qJXBnJbm8=
|
goftp.io/server/v2 v2.0.2 h1:tkZpqyXys+vC15W5yGMi8Kzmbv1QSgeKr8qJXBnJbm8=
|
||||||
goftp.io/server/v2 v2.0.2/go.mod h1:Fl1WdcV7fx1pjOWx7jEHb7tsJ8VwE7+xHu6bVJ6r2qg=
|
goftp.io/server/v2 v2.0.2/go.mod h1:Fl1WdcV7fx1pjOWx7jEHb7tsJ8VwE7+xHu6bVJ6r2qg=
|
||||||
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
||||||
|
|||||||
Reference in New Issue
Block a user