feat(notify): 重构通信内核并补齐 stream/bulk/record/transfer 能力
- 引入 LogicalConn/TransportConn 分层,ClientConn 保留兼容适配层 - 新增 Stream、Bulk、RecordStream 三条数据面能力及对应控制路径 - 完成 transfer/file 传输内核与状态快照、诊断能力 - 补齐 reconnect、inbound dispatcher、modern psk 等基础模块 - 增加大规模回归、并发与基准测试覆盖 - 更新依赖库
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type fileReceiveCheckpoint struct {
|
||||
FileID string `json:"file_id"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Mode uint32 `json:"mode"`
|
||||
ModTime int64 `json:"mod_time"`
|
||||
Checksum string `json:"checksum"`
|
||||
Received int64 `json:"received"`
|
||||
TmpPath string `json:"tmp_path"`
|
||||
FinalPath string `json:"final_path"`
|
||||
StartedAt int64 `json:"started_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
PreviousUpdatedAt int64 `json:"previous_updated_at"`
|
||||
PreviousReceived int64 `json:"previous_received"`
|
||||
}
|
||||
|
||||
func (p *fileReceivePool) restoreCheckpointLocked(scope string, packet FilePacket, now time.Time) (*fileReceiveSession, bool, error) {
|
||||
checkpoint, ok, err := p.loadCheckpointLocked(scope, packet.FileID)
|
||||
if err != nil || !ok {
|
||||
return nil, ok, err
|
||||
}
|
||||
name := filepath.Base(packet.Name)
|
||||
if name == "." || name == "/" || name == "" {
|
||||
name = "unnamed.bin"
|
||||
}
|
||||
if checkpoint.FileID != packet.FileID || checkpoint.Name != name || checkpoint.Size != packet.Size || !strings.EqualFold(checkpoint.Checksum, packet.Checksum) {
|
||||
p.removeCheckpointLocked(scope, packet.FileID)
|
||||
if checkpoint.TmpPath != "" {
|
||||
_ = os.Remove(checkpoint.TmpPath)
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
if checkpoint.TmpPath == "" {
|
||||
p.removeCheckpointLocked(scope, packet.FileID)
|
||||
return nil, false, nil
|
||||
}
|
||||
info, statErr := os.Stat(checkpoint.TmpPath)
|
||||
if statErr != nil {
|
||||
if checkpoint.FinalPath != "" && pathExists(checkpoint.FinalPath) {
|
||||
session := checkpoint.toSession(now)
|
||||
session.tmpPath = checkpoint.FinalPath
|
||||
session.finalPath = checkpoint.FinalPath
|
||||
session.received = session.size
|
||||
p.completed[fileReceiveKey(scope, packet.FileID)] = session.copy()
|
||||
p.removeCheckpointLocked(scope, packet.FileID)
|
||||
return session.copy(), true, nil
|
||||
}
|
||||
p.removeCheckpointLocked(scope, packet.FileID)
|
||||
return nil, false, nil
|
||||
}
|
||||
received := info.Size()
|
||||
if received < 0 {
|
||||
received = 0
|
||||
}
|
||||
if packet.Size > 0 && received > packet.Size {
|
||||
received = packet.Size
|
||||
}
|
||||
session := checkpoint.toSession(now)
|
||||
session.name = name
|
||||
session.mode = os.FileMode(packet.Mode)
|
||||
session.modTime = filePacketModTime(packet)
|
||||
session.checksum = packet.Checksum
|
||||
session.received = received
|
||||
if session.finalPath == "" || (session.finalPath != session.tmpPath && pathExists(session.finalPath)) {
|
||||
session.finalPath = p.uniqueFinalPathLocked(p.receiveDirLocked(), name, packet.FileID)
|
||||
}
|
||||
p.sessions[fileReceiveKey(scope, packet.FileID)] = session
|
||||
if session.received != checkpoint.Received || session.finalPath != checkpoint.FinalPath {
|
||||
if err := p.saveCheckpointLocked(scope, session); err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
}
|
||||
return session.copy(), true, nil
|
||||
}
|
||||
|
||||
func (p *fileReceivePool) loadCheckpointLocked(scope string, fileID string) (fileReceiveCheckpoint, bool, error) {
|
||||
path := p.checkpointPathLocked(scope, fileID)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fileReceiveCheckpoint{}, false, nil
|
||||
}
|
||||
return fileReceiveCheckpoint{}, false, err
|
||||
}
|
||||
var checkpoint fileReceiveCheckpoint
|
||||
if err := json.Unmarshal(data, &checkpoint); err != nil {
|
||||
_ = os.Remove(path)
|
||||
return fileReceiveCheckpoint{}, false, nil
|
||||
}
|
||||
return checkpoint, true, nil
|
||||
}
|
||||
|
||||
func (p *fileReceivePool) saveCheckpointLocked(scope string, session *fileReceiveSession) error {
|
||||
if p == nil || session == nil || session.fileID == "" {
|
||||
return nil
|
||||
}
|
||||
path := p.checkpointPathLocked(scope, session.fileID)
|
||||
checkpoint := fileReceiveCheckpoint{
|
||||
FileID: session.fileID,
|
||||
Name: session.name,
|
||||
Size: session.size,
|
||||
Mode: uint32(session.mode.Perm()),
|
||||
ModTime: session.modTime.UnixNano(),
|
||||
Checksum: session.checksum,
|
||||
Received: session.received,
|
||||
TmpPath: session.tmpPath,
|
||||
FinalPath: session.finalPath,
|
||||
StartedAt: session.startedAt.UnixNano(),
|
||||
UpdatedAt: session.updatedAt.UnixNano(),
|
||||
PreviousUpdatedAt: session.previousUpdatedAt.UnixNano(),
|
||||
PreviousReceived: session.previousReceived,
|
||||
}
|
||||
data, err := json.Marshal(checkpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpPath := path + ".tmp"
|
||||
if err := os.WriteFile(tmpPath, data, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmpPath, path)
|
||||
}
|
||||
|
||||
func (p *fileReceivePool) removeCheckpointLocked(scope string, fileID string) {
|
||||
if p == nil || fileID == "" {
|
||||
return
|
||||
}
|
||||
_ = os.Remove(p.checkpointPathLocked(scope, fileID))
|
||||
}
|
||||
|
||||
func (p *fileReceivePool) checkpointPathLocked(scope string, fileID string) string {
|
||||
baseDir := p.receiveDirLocked()
|
||||
sum := sha256.Sum256([]byte(fileReceiveKey(scope, fileID)))
|
||||
return filepath.Join(baseDir, ".notify_recv_"+hex.EncodeToString(sum[:8])+".json")
|
||||
}
|
||||
|
||||
func (checkpoint fileReceiveCheckpoint) toSession(now time.Time) *fileReceiveSession {
|
||||
now = normalizeFileEventTime(now)
|
||||
session := &fileReceiveSession{
|
||||
fileID: checkpoint.FileID,
|
||||
name: checkpoint.Name,
|
||||
size: checkpoint.Size,
|
||||
mode: os.FileMode(checkpoint.Mode),
|
||||
modTime: time.Unix(0, checkpoint.ModTime),
|
||||
checksum: checkpoint.Checksum,
|
||||
received: checkpoint.Received,
|
||||
tmpPath: checkpoint.TmpPath,
|
||||
finalPath: checkpoint.FinalPath,
|
||||
previousReceived: checkpoint.PreviousReceived,
|
||||
}
|
||||
session.startedAt = unixNanoTime(checkpoint.StartedAt)
|
||||
session.updatedAt = unixNanoTime(checkpoint.UpdatedAt)
|
||||
session.previousUpdatedAt = unixNanoTime(checkpoint.PreviousUpdatedAt)
|
||||
if session.startedAt.IsZero() {
|
||||
session.startedAt = now
|
||||
}
|
||||
if session.updatedAt.IsZero() {
|
||||
session.updatedAt = now
|
||||
}
|
||||
return session
|
||||
}
|
||||
Reference in New Issue
Block a user