Files
larksuite-cli/internal/vfs/localfileio/atomicwrite.go
tuxedomm 900c12ce8d feat: add FileIO extension for file transfer abstraction (#314)
* feat: add FileIO extension for file transfer abstraction

Introduce extension/fileio package with Provider/FileIO/File interfaces
and a global registry, following the same pattern as extension/credential.

- Add LocalFileIO default implementation with path validation and atomic writes
- Wire FileIOProvider into Factory and resolve at runtime via RuntimeContext.FileIO()
- Factory holds Provider (not resolved instance), deferring resolution to execution time
2026-04-08 14:13:59 +08:00

75 lines
1.5 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package localfileio
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/larksuite/cli/internal/vfs"
)
// AtomicWrite writes data to path atomically via temp file + rename.
func AtomicWrite(path string, data []byte, perm os.FileMode) error {
return atomicWrite(path, perm, func(tmp *os.File) error {
_, err := tmp.Write(data)
return err
})
}
// AtomicWriteFromReader atomically copies reader contents into path.
func AtomicWriteFromReader(path string, reader io.Reader, perm os.FileMode) (int64, error) {
var copied int64
err := atomicWrite(path, perm, func(tmp *os.File) error {
n, err := io.Copy(tmp, reader)
copied = n
return err
})
if err != nil {
return 0, err
}
return copied, nil
}
func atomicWrite(path string, perm os.FileMode, writeFn func(tmp *os.File) error) error {
dir := filepath.Dir(path)
tmp, err := vfs.CreateTemp(dir, "."+filepath.Base(path)+".*.tmp")
if err != nil {
return fmt.Errorf("create temp file: %w", err)
}
tmpName := tmp.Name()
closed := false
success := false
defer func() {
if !success {
if !closed {
tmp.Close()
}
vfs.Remove(tmpName)
}
}()
if err := tmp.Chmod(perm); err != nil {
return err
}
if err := writeFn(tmp); err != nil {
return err
}
if err := tmp.Sync(); err != nil {
return err
}
if err := tmp.Close(); err != nil {
return err
}
closed = true
if err := vfs.Rename(tmpName, path); err != nil {
return err
}
success = true
return nil
}