Initial commit

Proof-of-concept implementation. Bugs will occur.
This commit is contained in:
2026-02-12 01:18:46 +03:00
commit 13ac06c14b
553 changed files with 253003 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
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
}