progress_cache.go
⎇
Raw
1package service
2
3import (
4 "strings"
5 "sync"
6 "time"
7)
8
9type LiveDownload struct {
10 Logs strings.Builder
11 LastUpdate time.Time
12 flushed int // length of Logs already persisted to the DB
13}
14
15type ProgressCache struct {
16 mu sync.RWMutex
17 data map[int64]*LiveDownload
18}
19
20func NewProgressCache() *ProgressCache {
21 return &ProgressCache{
22 data: make(map[int64]*LiveDownload),
23 }
24}
25
26func (c *ProgressCache) Set(id int64, d *LiveDownload) {
27 c.mu.Lock()
28 defer c.mu.Unlock()
29 c.data[id] = d
30}
31
32func (c *ProgressCache) AppendLog(id int64, line string) {
33 c.mu.Lock()
34 defer c.mu.Unlock()
35 if d, ok := c.data[id]; ok {
36 d.Logs.WriteString(line)
37 d.Logs.WriteByte('\n')
38 d.LastUpdate = time.Now()
39 }
40}
41
42// Snapshot returns the full in-memory log buffer for a live download, read under
43// the lock so it never races the writer in AppendLog (strings.Builder is not
44// safe for concurrent read/write). Empty string if the download isn't live.
45func (c *ProgressCache) Snapshot(id int64) string {
46 c.mu.RLock()
47 defer c.mu.RUnlock()
48 if d, ok := c.data[id]; ok {
49 return d.Logs.String()
50 }
51 return ""
52}
53
54func (c *ProgressCache) Delete(id int64) {
55 c.mu.Lock()
56 defer c.mu.Unlock()
57 delete(c.data, id)
58}
59
60// FlushLogs returns only the log content appended since the last flush and
61// advances the flushed mark, so the caller can append (rather than rewrite the
62// whole buffer) to the DB. Empty string when there's nothing new.
63func (c *ProgressCache) FlushLogs(id int64) string {
64 c.mu.Lock()
65 defer c.mu.Unlock()
66 d, ok := c.data[id]
67 if !ok {
68 return ""
69 }
70 full := d.Logs.String()
71 if d.flushed >= len(full) {
72 return ""
73 }
74 tail := full[d.flushed:]
75 d.flushed = len(full)
76 return tail
77}
78