feat: complete contract-driven business development and MySQL integration
This commit is contained in:
80
contract/file.go
Normal file
80
contract/file.go
Normal file
@@ -0,0 +1,80 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user