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,147 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func computeFileChecksum(path string) (string, error) {
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer fd.Close()
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, fd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func filePacketModTime(packet FilePacket) time.Time {
|
||||
if packet.ModTime <= 0 {
|
||||
return time.Time{}
|
||||
}
|
||||
return time.Unix(0, packet.ModTime)
|
||||
}
|
||||
|
||||
func applyReceivedFileMeta(path string, mode os.FileMode, modTime time.Time) {
|
||||
if mode != 0 {
|
||||
_ = os.Chmod(path, mode.Perm())
|
||||
}
|
||||
if !modTime.IsZero() {
|
||||
_ = os.Chtimes(path, modTime, modTime)
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeFileName(name string) string {
|
||||
trimmed := strings.TrimSpace(name)
|
||||
if trimmed == "" {
|
||||
return "unnamed"
|
||||
}
|
||||
trimmed = strings.ReplaceAll(trimmed, "/", "_")
|
||||
trimmed = strings.ReplaceAll(trimmed, "\\", "_")
|
||||
trimmed = strings.ReplaceAll(trimmed, ":", "_")
|
||||
return trimmed
|
||||
}
|
||||
|
||||
func shortFileIDSuffix(fileID string) string {
|
||||
cleaned := sanitizeFileName(fileID)
|
||||
if len(cleaned) > 12 {
|
||||
return cleaned[:12]
|
||||
}
|
||||
if cleaned == "" {
|
||||
return "copy"
|
||||
}
|
||||
return cleaned
|
||||
}
|
||||
|
||||
func pathExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (p *fileReceivePool) receiveDirLocked() string {
|
||||
if p.dir != "" {
|
||||
return p.dir
|
||||
}
|
||||
return os.TempDir()
|
||||
}
|
||||
|
||||
func (p *fileReceivePool) uniqueFinalPathLocked(baseDir string, name string, fileID string) string {
|
||||
cleanName := sanitizeFileName(filepath.Base(name))
|
||||
if cleanName == "" {
|
||||
cleanName = "unnamed.bin"
|
||||
}
|
||||
ext := filepath.Ext(cleanName)
|
||||
base := strings.TrimSuffix(cleanName, ext)
|
||||
candidate := filepath.Join(baseDir, cleanName)
|
||||
if !p.pathReservedLocked(candidate) && !pathExists(candidate) {
|
||||
return candidate
|
||||
}
|
||||
suffix := shortFileIDSuffix(fileID)
|
||||
candidate = filepath.Join(baseDir, fmt.Sprintf("%s.%s%s", base, suffix, ext))
|
||||
if !p.pathReservedLocked(candidate) && !pathExists(candidate) {
|
||||
return candidate
|
||||
}
|
||||
for i := 1; ; i++ {
|
||||
candidate = filepath.Join(baseDir, fmt.Sprintf("%s.%s.%d%s", base, suffix, i, ext))
|
||||
if !p.pathReservedLocked(candidate) && !pathExists(candidate) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *fileReceivePool) pathReservedLocked(path string) bool {
|
||||
for _, session := range p.sessions {
|
||||
if session.finalPath == path || session.tmpPath == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *fileReceivePool) trimCompletedLocked() {
|
||||
if p.completedLimit <= 0 || len(p.completed) <= p.completedLimit {
|
||||
return
|
||||
}
|
||||
for len(p.completed) > p.completedLimit {
|
||||
oldestKey := ""
|
||||
oldestTime := time.Time{}
|
||||
for key, session := range p.completed {
|
||||
candidateTime := completedFileReceiveTime(session)
|
||||
if oldestKey == "" || candidateTime.Before(oldestTime) || (candidateTime.Equal(oldestTime) && key < oldestKey) {
|
||||
oldestKey = key
|
||||
oldestTime = candidateTime
|
||||
}
|
||||
}
|
||||
if oldestKey == "" {
|
||||
return
|
||||
}
|
||||
delete(p.completed, oldestKey)
|
||||
}
|
||||
}
|
||||
|
||||
func completedFileReceiveTime(session *fileReceiveSession) time.Time {
|
||||
if session == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
if !session.updatedAt.IsZero() {
|
||||
return session.updatedAt
|
||||
}
|
||||
return session.startedAt
|
||||
}
|
||||
|
||||
func (s *fileReceiveSession) copy() *fileReceiveSession {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
dup := *s
|
||||
return &dup
|
||||
}
|
||||
Reference in New Issue
Block a user