145 lines
4.1 KiB
Go
145 lines
4.1 KiB
Go
|
/*
|
||
|
* MumbleDJ
|
||
|
* By Matthieu Grieger
|
||
|
* bot/cache.go
|
||
|
* Copyright (c) 2016 Matthieu Grieger (MIT License)
|
||
|
*/
|
||
|
|
||
|
package bot
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"sort"
|
||
|
"time"
|
||
|
|
||
|
"github.com/Sirupsen/logrus"
|
||
|
"github.com/spf13/viper"
|
||
|
)
|
||
|
|
||
|
// SortFilesByAge is a type that holds file information for cached items for
|
||
|
// sorting.
|
||
|
type SortFilesByAge []os.FileInfo
|
||
|
|
||
|
// Len returns the length of the file slice.
|
||
|
func (a SortFilesByAge) Len() int {
|
||
|
return len(a)
|
||
|
}
|
||
|
|
||
|
// Swap swaps two elements in the file slice.
|
||
|
func (a SortFilesByAge) Swap(i, j int) {
|
||
|
a[i], a[j] = a[j], a[i]
|
||
|
}
|
||
|
|
||
|
// Less compares two file modification times to determine if one is less than
|
||
|
// the other. Returns true if the item in index i is older than the item in
|
||
|
// index j, false otherwise.
|
||
|
func (a SortFilesByAge) Less(i, j int) bool {
|
||
|
return time.Since(a[i].ModTime()) < time.Since(a[j].ModTime())
|
||
|
}
|
||
|
|
||
|
// Cache keeps track of the filesize of the audio cache and
|
||
|
// provides methods for pruning the cache.
|
||
|
type Cache struct {
|
||
|
NumAudioFiles int
|
||
|
TotalFileSize int64
|
||
|
}
|
||
|
|
||
|
// NewCache creates an empty Cache and returns it.
|
||
|
func NewCache() *Cache {
|
||
|
return &Cache{
|
||
|
NumAudioFiles: 0,
|
||
|
TotalFileSize: 0,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// CheckDirectorySize checks the cache directory to determine if the filesize
|
||
|
// of the files within exceed the user-specified size limit. If so, the oldest
|
||
|
// files are cleared until it is no longer exceeding the limit.
|
||
|
func (c *Cache) CheckDirectorySize() {
|
||
|
const bytesInMiB int = 1048576
|
||
|
|
||
|
c.UpdateStatistics()
|
||
|
for c.TotalFileSize > int64(viper.GetInt("cache.maximum_size")*bytesInMiB) {
|
||
|
if err := c.DeleteOldest(); err != nil {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// UpdateStatistics updates the statistics relevant to the cache (number of
|
||
|
// audio files cached, total current size of the cache).
|
||
|
func (c *Cache) UpdateStatistics() {
|
||
|
c.NumAudioFiles, c.TotalFileSize = c.getCurrentStatistics()
|
||
|
logrus.WithFields(logrus.Fields{
|
||
|
"num_audio_files": c.NumAudioFiles,
|
||
|
"total_file_size": c.TotalFileSize,
|
||
|
}).Infoln("Updated cache statistics.")
|
||
|
}
|
||
|
|
||
|
// CleanPeriodically loops forever, deleting expired cached audio files as necessary.
|
||
|
func (c *Cache) CleanPeriodically() {
|
||
|
for range time.Tick(time.Duration(viper.GetInt("cache.check_interval")) * time.Minute) {
|
||
|
logrus.Infoln("Checking cache for expired files...")
|
||
|
files, _ := ioutil.ReadDir(os.ExpandEnv(viper.GetString("cache.directory")))
|
||
|
for _, file := range files {
|
||
|
// It is safe to check the modification time because when audio files are
|
||
|
// played their modification time is updated. This ensures that audio
|
||
|
// files will not get deleted while they are playing, assuming a reasonable
|
||
|
// expiry time is set in the configuration.
|
||
|
hours := time.Since(file.ModTime()).Hours()
|
||
|
if hours >= viper.GetFloat64("cache.expire_time") {
|
||
|
logrus.WithFields(logrus.Fields{
|
||
|
"expired_file": file.Name(),
|
||
|
}).Infoln("Removing expired cache entry.")
|
||
|
os.Remove(fmt.Sprintf("%s/%s", os.ExpandEnv(viper.GetString("cache.directory")), file.Name()))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// DeleteOldest deletes the oldest file in the cache.
|
||
|
func (c *Cache) DeleteOldest() error {
|
||
|
files, _ := ioutil.ReadDir(os.ExpandEnv(viper.GetString("cache.directory")))
|
||
|
if len(files) > 0 {
|
||
|
sort.Sort(SortFilesByAge(files))
|
||
|
os.Remove(fmt.Sprintf("%s/%s", os.ExpandEnv(viper.GetString("cache.directory")), files[0].Name()))
|
||
|
return nil
|
||
|
}
|
||
|
return errors.New("There are no files currently cached")
|
||
|
}
|
||
|
|
||
|
// DeleteAll deletes all cached audio files.
|
||
|
func (c *Cache) DeleteAll() error {
|
||
|
dir, err := os.Open(os.ExpandEnv(viper.GetString("cache.directory")))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
defer dir.Close()
|
||
|
names, err := dir.Readdirnames(-1)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
logrus.Infoln("Deleting all cached audio files...")
|
||
|
for _, name := range names {
|
||
|
err = os.RemoveAll(filepath.Join(os.ExpandEnv(viper.GetString("cache.directory")), name))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Cache) getCurrentStatistics() (int, int64) {
|
||
|
var totalSize int64
|
||
|
files, _ := ioutil.ReadDir(os.ExpandEnv(viper.GetString("cache.directory")))
|
||
|
for _, file := range files {
|
||
|
totalSize += file.Size()
|
||
|
}
|
||
|
return len(files), totalSize
|
||
|
}
|