Files
acp/filesystem.go
Samuel CUI 159d8ea4ea Decouple bounded copy pipeline and preserve ordered completion
Overlap source reads, hashing, ordered linear writes, and asynchronous cleanup with bounded buffers. Preserve conflicting output order, API behavior, and no-space diagnostics. Add an LTFS drive-buffer model and reproducible performance checks.
2026-10-09 00:33:14 +08:00

92 lines
2.4 KiB
Go

package acp
import (
"io"
"os"
"github.com/samuelncui/acp/internal/fileio"
)
var closeTarget = (*fileio.Output).Close
// transferFilesystem substitutes file operations without exposing transfer ownership to callers.
type transferFilesystem interface {
Open(path string, mode ReadMode, info os.FileInfo) (itemSource, error)
Create(job *writeJob, target targetSpec) (transferOutput, error)
}
// transferOutput retains the owned path after Close until Commit or Discard settles it.
type transferOutput interface {
io.WriteCloser
Sync() error
Cache(job *baseJob, linear bool)
Restore(*stat) error
Commit(overwrite bool) error
Discard() error
}
type nativeTransferFilesystem struct {
copyer *StreamCopyer
}
func (f nativeTransferFilesystem) Open(path string, mode ReadMode, info os.FileInfo) (itemSource, error) {
return openSourceContent(path, mode, info)
}
func (f nativeTransferFilesystem) Create(job *writeJob, target targetSpec) (transferOutput, error) {
// The existing allocator owns cleanup until it hands an output to this adapter.
output, err := f.copyer.prepareTarget(job, target)
if err != nil {
return nil, err
}
return &nativeTransferOutput{copyer: f.copyer, target: target, output: output}, nil
}
type nativeTransferOutput struct {
copyer *StreamCopyer
target targetSpec
output *fileio.Output
}
func (o *nativeTransferOutput) Write(data []byte) (int, error) {
return o.output.File.Write(data)
}
func (o *nativeTransferOutput) Close() error {
return closeTarget(o.output)
}
func (o *nativeTransferOutput) Sync() error {
return o.output.File.Sync()
}
func (o *nativeTransferOutput) Cache(job *baseJob, linear bool) {
// Linear completion has closed its descriptor but still owns the temporary path.
if linear {
o.copyer.refreshCachePath(o.output.Temporary, job, true)
return
}
// Random completion retains its descriptor until metadata and Sync finish.
o.copyer.refreshCacheEntry(o.output.File, o.target.name, job, true)
}
func (o *nativeTransferOutput) Restore(info *stat) error {
return restoreTarget(o.output.Temporary, info)
}
func (o *nativeTransferOutput) Commit(overwrite bool) error {
return commitTarget(o.output, overwrite)
}
func (o *nativeTransferOutput) Discard() error {
return o.output.Discard()
}
func (c *StreamCopyer) fs() transferFilesystem {
if c.filesystem != nil {
return c.filesystem
}
return nativeTransferFilesystem{copyer: c}
}