loveuer 7ec8cd5c0e init: 0.0.8
feat: 实现了分片上传
todo:
  1. 上传完成后 code 回显
  2. 清理不用的临时 code file
  3. 断点续传
  4. 多 chunk 并发上传
2025-05-12 15:54:43 +08:00

123 lines
2.3 KiB
Go

package controller
import (
"context"
"fmt"
"github.com/loveuer/nf/nft/log"
"github.com/loveuer/ushare/internal/opt"
gonanoid "github.com/matoous/go-nanoid/v2"
"io"
"os"
"sync"
"time"
)
type metaInfo struct {
f *os.File
name string
create time.Time
last time.Time
size int64
cursor int64
ip string
}
func (m *metaInfo) generateMeta(code string) error {
content := fmt.Sprintf("filename=%s\ncreated_at=%d\nsize=%d\nuploader_ip=%s",
m.name, m.create.UnixMilli(), m.size, m.ip,
)
return os.WriteFile(opt.MetaPath(code), []byte(content), 0644)
}
type meta struct {
sync.Mutex
ctx context.Context
m map[string]*metaInfo
}
var (
MetaManager = &meta{m: make(map[string]*metaInfo)}
)
const letters = "1234567890abcdefghijklmnopqrstuvwxyz"
func (m *meta) New(size int64, filename, ip string) (string, error) {
now := time.Now()
code, err := gonanoid.Generate(letters, 16)
if err != nil {
return "", err
}
f, err := os.Create(opt.FilePath(code))
if err != nil {
return "", err
}
if err = f.Truncate(size); err != nil {
f.Close()
return "", err
}
m.Lock()
defer m.Unlock()
m.m[code] = &metaInfo{f: f, name: filename, last: now, size: size, cursor: 0, create: now, ip: ip}
return code, nil
}
func (m *meta) Write(code string, start, end int64, reader io.Reader) (total, cursor int64, err error) {
m.Lock()
defer m.Unlock()
if _, ok := m.m[code]; !ok {
return 0, 0, fmt.Errorf("code not exist")
}
w, err := io.CopyN(m.m[code].f, reader, end-start+1)
if err != nil {
return 0, 0, err
}
m.m[code].cursor += w
m.m[code].last = time.Now()
total = m.m[code].size
cursor = m.m[code].cursor
if m.m[code].cursor == m.m[code].size {
defer delete(m.m, code)
if err = m.m[code].generateMeta(code); err != nil {
return 0, 0, err
}
}
return total, cursor, nil
}
func (m *meta) Start(ctx context.Context) {
ticker := time.NewTicker(time.Minute)
m.ctx = ctx
go func() {
for {
select {
case <-ctx.Done():
return
case now := <-ticker.C:
for code, info := range m.m {
if now.Sub(info.last) > 1*time.Minute {
m.Lock()
info.f.Close()
os.RemoveAll(opt.FilePath(code))
delete(m.m, code)
m.Unlock()
log.Warn("MetaController: code timeout removed, code = %s", code)
}
}
}
}
}()
}