159 lines
4.3 KiB
Go
159 lines
4.3 KiB
Go
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.RLock()
|
|
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.RUnlock()
|
|
|
|
// if c is not newer than b, then just return.
|
|
if !b.LastUpdate.Before(c.LastUpdate) {
|
|
return b, false
|
|
}
|
|
|
|
b.LastUpdate = c.LastUpdate
|
|
return b, true
|
|
}
|
|
h.stateMutex.RUnlock()
|
|
|
|
// 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,
|
|
}
|
|
n.waitGroup.Add(1)
|
|
h.stateMutex.Lock()
|
|
h.state = append(h.state, n)
|
|
h.stateMutex.Unlock()
|
|
|
|
return n, true
|
|
}
|