-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathstate.go
166 lines (146 loc) · 4.49 KB
/
state.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package housekeeper
import (
"fmt"
"os"
"strconv"
"sync"
"time"
)
// CachedBeatmap represents a beatmap that is held in the cache of CheeseGull.
type CachedBeatmap struct {
ID int
NoVideo bool
LastUpdate time.Time
lastRequested time.Time
fileSize uint64
isDownloaded bool
mtx sync.RWMutex
waitGroup sync.WaitGroup
}
// File opens the File of the beatmap from the filesystem.
func (c *CachedBeatmap) File() (*os.File, error) {
return os.Open(c.fileName())
}
// CreateFile creates the File of the beatmap in the filesystem, and returns it
// in write mode.
func (c *CachedBeatmap) CreateFile() (*os.File, error) {
return os.Create(c.fileName())
}
func (c *CachedBeatmap) fileName() string {
n := ""
if c.NoVideo {
n = "n"
}
return "data/" + strconv.Itoa(c.ID) + n + ".osz"
}
// IsDownloaded checks whether the beatmap has been downloaded.
func (c *CachedBeatmap) IsDownloaded() bool {
c.mtx.RLock()
i := c.isDownloaded
c.mtx.RUnlock()
return i
}
// FileSize returns the FileSize of c.
func (c *CachedBeatmap) FileSize() uint64 {
c.mtx.RLock()
i := c.fileSize
c.mtx.RUnlock()
return i
}
// MustBeDownloaded will check whether the beatmap is downloaded.
// If it is not, it will wait for it to become downloaded.
func (c *CachedBeatmap) MustBeDownloaded() {
if c.IsDownloaded() {
return
}
c.waitGroup.Wait()
}
// DownloadCompleted must be called once the beatmap has finished downloading.
func (c *CachedBeatmap) DownloadCompleted(fileSize uint64, parentHouse *House) {
c.mtx.Lock()
c.fileSize = fileSize
c.isDownloaded = true
c.mtx.Unlock()
c.waitGroup.Done()
parentHouse.scheduleCleanup()
}
// SetLastRequested changes the last requested time.
func (c *CachedBeatmap) SetLastRequested(t time.Time) {
c.mtx.Lock()
c.lastRequested = t
c.mtx.Unlock()
}
func (c *CachedBeatmap) String() string {
return fmt.Sprintf("{ID: %d NoVideo: %t LastUpdate: %v}", c.ID, c.NoVideo, c.LastUpdate)
}
// AcquireBeatmap attempts to add a new CachedBeatmap to the state.
// In order to add a new CachedBeatmap to the state, one must not already exist
// in the state with the same ID, NoVideo and LastUpdate. In case one is already
// found, this is returned, alongside with false. If LastUpdate is newer than
// that of the beatmap stored in the state, then the beatmap in the state's
// downloaded status is switched back to false and the LastUpdate is changed.
// true is also returned, indicating that the caller now has the burden of
// downloading the beatmap.
//
// In the case the cachedbeatmap has not been stored in the state, then
// it is added to the state and, like the case where LastUpdated has been
// changed, true is returned, indicating that the caller must now download the
// beatmap.
//
// If you're confused attempting to read this, let me give you an example:
//
// A: Yo, is this beatmap cached?
// B: Yes, yes it is! Here you go with the information about it. No need to do
// anything else.
// ----
// A: Yo, got this beatmap updated 2 hours ago. Have you got it cached?
// B: Ah, I'm afraid that I only have the version updated 10 hours ago.
// Mind downloading the updated version for me?
// ----
// A: Yo, is this beatmap cached?
// B: Nope, I didn't know it existed before you told me. I've recorded its
// info now, but jokes on you, you now have to actually download it.
// Chop chop!
func (h *House) AcquireBeatmap(c *CachedBeatmap) (*CachedBeatmap, bool) {
if c == nil {
return nil, false
}
h.stateMutex.Lock()
for _, b := range h.state {
// if the id or novideo is different, then all is good and we
// can proceed with the next element.
if b.ID != c.ID || b.NoVideo != c.NoVideo {
continue
}
// unlocking because in either branch, we will return.
h.stateMutex.Unlock()
b.mtx.Lock()
// if c is not newer than b, then just return.
if !b.LastUpdate.Before(c.LastUpdate) {
b.mtx.Unlock()
if b.FileSize() == 0 {
// Try re-downloading beatmaps if their size is 0
b.waitGroup.Add(1)
return b, true
}
// If size is ok, do not redownload
return b, false
}
b.LastUpdate = c.LastUpdate
b.mtx.Unlock()
b.waitGroup.Add(1)
return b, true
}
// c was not present in our state: we need to add it.
// we need to recreate the CachedBeatmap: this way we can be sure the zero
// is set for the unexported fields.
n := &CachedBeatmap{
ID: c.ID,
NoVideo: c.NoVideo,
LastUpdate: c.LastUpdate,
}
h.state = append(h.state, n)
h.stateMutex.Unlock()
n.waitGroup.Add(1)
return n, true
}