Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions internal/op/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,14 @@ func ArchiveDecompress(ctx context.Context, storage driver.Driver, srcPath, dstD
if err != nil {
return errors.WithMessage(err, "failed to get src object")
}
// Like Copy and Move, make sure dstDir exists: decompressing to another
// storage goes through Put, which creates it, but decompressing within
// the same storage fetched it directly and failed for a new nested path.
// MakeDir is a no-op when the dir already exists.
err = MakeDir(ctx, storage, dstDirPath)
if err != nil {
return errors.WithMessagef(err, "failed to make dir [%s]", dstDirPath)
}
dstDir, err := GetUnwrap(ctx, storage, dstDirPath)
if err != nil {
return errors.WithMessage(err, "failed to get dst dir")
Expand Down
76 changes: 76 additions & 0 deletions internal/op/archive_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package op_test

import (
"context"
"fmt"
"os"
"path/filepath"
"testing"

"github.com/alist-org/alist/v3/drivers/local"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
)

// decompressLocal is the Local driver with server-side archive decompression,
// which writes a fixed file into the destination directory.
type decompressLocal struct {
local.Local
}

func (d *decompressLocal) Config() driver.Config {
config := d.Local.Config()
config.Name = "LocalDecompressTest"
return config
}

func (d *decompressLocal) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) error {
return os.WriteFile(filepath.Join(dstDir.GetPath(), "extracted.txt"), []byte("extracted"), 0o644)
}

func init() {
op.RegisterDriver(func() driver.Driver {
return &decompressLocal{}
})
}

// TestArchiveDecompressSameStorageCreatesMissingNestedDstDir covers the gap
// fixed for Copy and Move: decompressing within the same storage fetched
// dstDir without ensuring it exists, so it failed with "failed to get dst
// dir", while the task based path used for other storages goes through Put,
// which does create it.
func TestArchiveDecompressSameStorageCreatesMissingNestedDstDir(t *testing.T) {
root := t.TempDir()
mountPath := "/decompresstest-" + t.Name()
_, err := op.CreateStorage(context.Background(), model.Storage{
Driver: "LocalDecompressTest",
MountPath: mountPath,
Addition: fmt.Sprintf(`{"root_folder_path":%q}`, root),
})
if err != nil {
t.Fatalf("failed to create storage: %+v", err)
}
storage, err := op.GetStorageByMountPath(mountPath)
if err != nil {
t.Fatalf("failed to get storage: %+v", err)
}

if err := os.WriteFile(filepath.Join(root, "src.zip"), []byte("archive"), 0o644); err != nil {
t.Fatalf("failed to write source file: %+v", err)
}

// dstDirPath's intermediate directories ("a", "a/b") do not exist yet.
err = op.ArchiveDecompress(context.Background(), storage, "/src.zip", "/a/b", model.ArchiveDecompressArgs{})
if err != nil {
t.Fatalf("ArchiveDecompress into a not-yet-existing nested destination failed: %+v", err)
}

got, err := os.ReadFile(filepath.Join(root, "a", "b", "extracted.txt"))
if err != nil {
t.Fatalf("extracted file not found at destination: %+v", err)
}
if string(got) != "extracted" {
t.Errorf("extracted file content = %q, want %q", got, "extracted")
}
}