99 lines
2.7 KiB
Go
99 lines
2.7 KiB
Go
package cacher
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"source.hodakov.me/hdkv/faketunes/internal/domains/cacher/dto"
|
|
"source.hodakov.me/hdkv/faketunes/internal/domains/cacher/models"
|
|
)
|
|
|
|
// GetFileDTO gets the ALAC file from cache or transcodes one with transcoder if needed.
|
|
func (c *Cacher) GetFileDTO(sourcePath string) (*dto.CacheItem, error) {
|
|
item, err := c.getFile(sourcePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: %w (%w)", ErrCacher, ErrFailedToGetSourceFile, err)
|
|
}
|
|
|
|
return models.CacheItemModelToDTO(item), nil
|
|
}
|
|
|
|
func (c *Cacher) getFile(sourcePath string) (*models.CacheItem, error) {
|
|
sourceFileInfo, err := os.Stat(sourcePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: %w (%w)", ErrCacher, ErrFailedToGetSourceFile, err)
|
|
}
|
|
|
|
keyData := fmt.Sprintf("%s:%d", sourcePath, sourceFileInfo.ModTime().UnixNano())
|
|
hash := md5.Sum([]byte(keyData))
|
|
cacheKey := fmt.Sprintf("%x", hash)
|
|
cacheFilePath := filepath.Join(c.cacheDir, cacheKey+".m4a")
|
|
|
|
c.cacheMutex.Lock()
|
|
defer c.cacheMutex.Unlock()
|
|
|
|
// Check if file information exists in cache
|
|
if item, ok := c.items[cacheKey]; ok {
|
|
if _, err := os.Stat(item.Path); err != nil {
|
|
// File exists in cache and on disk
|
|
item.Updated = time.Now().UTC()
|
|
|
|
c.updateCachedStat(sourcePath, item.Size)
|
|
|
|
return item, nil
|
|
}
|
|
}
|
|
|
|
// Check if file exists on disk but information about it doesn't exist in
|
|
// the memory (for example, after application restart).
|
|
if cachedFileInfo, err := os.Stat(cacheFilePath); err == nil {
|
|
// Verify that the file on disk is newer than the source file and has content.
|
|
// If that's the case, return the item information and store it in memory.
|
|
if cachedFileInfo.ModTime().After(sourceFileInfo.ModTime()) &&
|
|
cachedFileInfo.Size() > 1024 {
|
|
item := &models.CacheItem{
|
|
Path: cacheFilePath,
|
|
Size: cachedFileInfo.Size(),
|
|
Updated: time.Now().UTC(),
|
|
}
|
|
c.items[cacheKey] = item
|
|
c.currentSize += cachedFileInfo.Size()
|
|
|
|
c.updateCachedStat(sourcePath, item.Size)
|
|
|
|
return item, nil
|
|
}
|
|
}
|
|
|
|
// File does not exist on disk, need to transcode.
|
|
// Register in the queue
|
|
c.transcoder.QueueChannel() <- struct{}{}
|
|
defer func() {
|
|
<-c.transcoder.QueueChannel()
|
|
}()
|
|
|
|
// Convert file
|
|
size, err := c.transcoder.Convert(sourcePath, cacheFilePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%w: %w (%w)", ErrCacher, ErrFailedToTranscodeFile, err)
|
|
}
|
|
|
|
// Add converted file information to cache
|
|
item := &models.CacheItem{
|
|
Path: cacheFilePath,
|
|
Size: size,
|
|
Updated: time.Now(),
|
|
}
|
|
c.items[cacheKey] = item
|
|
c.currentSize += size
|
|
|
|
c.updateCachedStat(sourcePath, size)
|
|
// TODO: run cleanup on inotify events.
|
|
c.cleanup()
|
|
|
|
return item, nil
|
|
}
|