Files
BackupX/server/internal/backup/log_hub.go
Awuqing 1003302bdd 功能: 集成 rclone 高级传输特性 + 全 70+ 后端支持
1. 失败自动重试:rclone Pacer 指数退避,默认 10 次底层 HTTP 重试
2. 带宽限制:配置 bandwidth_limit + Settings 运行时可调
3. 上传实时进度:progressReader + LogHub SSE 推送字节级进度/速率
4. 存储空间查询:StorageAbout 可选接口,GetUsage 返回远端真实空间
5. 全 rclone 后端:backend/all 引入 70+ 后端,新增 rclone 存储类型,
   API 驱动的可搜索后端选择器 + 动态配置表单
2026-03-31 23:37:59 +08:00

147 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package backup
import (
"fmt"
"sync"
"time"
)
type LogHub struct {
mu sync.RWMutex
streams map[uint]*logStreamState
}
type logStreamState struct {
nextSequence int64
events []LogEvent
subscribers map[int]chan LogEvent
nextSubID int
completed bool
status string
}
func NewLogHub() *LogHub {
return &LogHub{streams: make(map[uint]*logStreamState)}
}
func (h *LogHub) Append(recordID uint, level, message string) LogEvent {
h.mu.Lock()
defer h.mu.Unlock()
state := h.ensureState(recordID)
state.nextSequence++
event := LogEvent{RecordID: recordID, Sequence: state.nextSequence, Level: level, Message: message, Timestamp: time.Now().UTC(), Status: state.status}
state.events = append(state.events, event)
for _, subscriber := range state.subscribers {
select {
case subscriber <- event:
default:
}
}
return event
}
func (h *LogHub) Snapshot(recordID uint) []LogEvent {
h.mu.RLock()
defer h.mu.RUnlock()
state, ok := h.streams[recordID]
if !ok {
return nil
}
result := make([]LogEvent, len(state.events))
copy(result, state.events)
return result
}
func (h *LogHub) Subscribe(recordID uint, buffer int) (<-chan LogEvent, func()) {
if buffer <= 0 {
buffer = 32
}
h.mu.Lock()
defer h.mu.Unlock()
state := h.ensureState(recordID)
state.nextSubID++
id := state.nextSubID
channel := make(chan LogEvent, buffer)
state.subscribers[id] = channel
for _, event := range state.events {
channel <- event
}
cancel := func() {
h.mu.Lock()
defer h.mu.Unlock()
stream, ok := h.streams[recordID]
if !ok {
return
}
subscriber, ok := stream.subscribers[id]
if !ok {
return
}
delete(stream.subscribers, id)
close(subscriber)
}
return channel, cancel
}
func (h *LogHub) Complete(recordID uint, status string) {
h.mu.Lock()
defer h.mu.Unlock()
state := h.ensureState(recordID)
state.completed = true
state.status = status
state.nextSequence++
event := LogEvent{RecordID: recordID, Sequence: state.nextSequence, Level: "info", Message: "stream completed", Timestamp: time.Now().UTC(), Completed: true, Status: status}
state.events = append(state.events, event)
for _, subscriber := range state.subscribers {
select {
case subscriber <- event:
default:
}
}
}
// AppendProgress 推送上传进度事件(节流:每个 recordID 每 500ms 最多一次,最终值始终推送)。
func (h *LogHub) AppendProgress(recordID uint, progress ProgressInfo) {
h.mu.Lock()
defer h.mu.Unlock()
state := h.ensureState(recordID)
// 节流:距上次 progress 事件不足 500ms 且未完成则跳过100% 始终推送)
now := time.Now().UTC()
isFinal := progress.Percent >= 100
if !isFinal && len(state.events) > 0 {
last := state.events[len(state.events)-1]
if last.Progress != nil && now.Sub(last.Timestamp) < 500*time.Millisecond {
return
}
}
state.nextSequence++
event := LogEvent{
RecordID: recordID,
Sequence: state.nextSequence,
Level: "progress",
Message: fmt.Sprintf("上传进度: %.1f%%", progress.Percent),
Timestamp: now,
Status: state.status,
Progress: &progress,
}
state.events = append(state.events, event)
for _, subscriber := range state.subscribers {
select {
case subscriber <- event:
default:
}
}
}
func (h *LogHub) ensureState(recordID uint) *logStreamState {
state, ok := h.streams[recordID]
if ok {
return state
}
state = &logStreamState{subscribers: make(map[int]chan LogEvent), status: "running"}
h.streams[recordID] = state
return state
}