81 lines
1.8 KiB
Go
81 lines
1.8 KiB
Go
package contract
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
// File is a request-scoped upload, staged on disk using a bounded copy buffer.
|
|
// Name and ContentType are untrusted client metadata. Open streams content;
|
|
// readers and the temporary file are closed when the generated handler returns.
|
|
// Persist wanted content before returning. Retaining File does not extend its lifetime.
|
|
type File struct {
|
|
Name string
|
|
ContentType string
|
|
Size int64
|
|
state *uploadState
|
|
}
|
|
type uploadState struct {
|
|
mu sync.Mutex
|
|
path string
|
|
closed bool
|
|
readers map[*uploadReader]bool
|
|
}
|
|
type uploadReader struct {
|
|
file *os.File
|
|
once sync.Once
|
|
err error
|
|
state *uploadState
|
|
}
|
|
|
|
// Present reports whether the optional upload was supplied.
|
|
func (f File) Present() bool { return f.state != nil }
|
|
|
|
func (f File) Open() (io.ReadCloser, error) {
|
|
if f.state == nil {
|
|
return nil, errors.New("upload file is absent")
|
|
}
|
|
s := f.state
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if s.closed {
|
|
return nil, errors.New("upload file is closed")
|
|
}
|
|
file, err := os.Open(s.path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r := &uploadReader{file: file, state: s}
|
|
s.readers[r] = true
|
|
return r, nil
|
|
}
|
|
func (r *uploadReader) Read(p []byte) (int, error) { return r.file.Read(p) }
|
|
func (r *uploadReader) Close() error {
|
|
r.once.Do(func() { r.err = r.file.Close(); r.state.mu.Lock(); delete(r.state.readers, r); r.state.mu.Unlock() })
|
|
return r.err
|
|
}
|
|
func (f File) cleanup() error {
|
|
if f.state == nil {
|
|
return nil
|
|
}
|
|
s := f.state
|
|
s.mu.Lock()
|
|
if s.closed {
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
s.closed = true
|
|
readers := make([]*uploadReader, 0, len(s.readers))
|
|
for r := range s.readers {
|
|
readers = append(readers, r)
|
|
}
|
|
s.mu.Unlock()
|
|
var err error
|
|
for _, r := range readers {
|
|
err = errors.Join(err, r.Close())
|
|
}
|
|
return errors.Join(err, os.Remove(s.path))
|
|
}
|