Files
pkgstash/internal/cache/cache.go
T

125 lines
2.5 KiB
Go

package cache
import (
"io"
"log/slog"
"net"
"net/http"
"os"
"path/filepath"
"sync"
"sync/atomic"
"time"
)
const userAgent = "pacman/7.1.0 (Linux x86_64) libalpm/16.0.1"
type Cache struct {
cfg CacheConfig
cr *os.Root
mirrorIdx atomic.Uint64
refreshMu sync.Mutex
client http.Client
inFlight map[string]*inFlight
inFlightMu sync.Mutex
}
type CacheConfig struct {
mirrorURLs []string
mirroredRepos []string
DialTimeout time.Duration
ResponseHeaderTimeout time.Duration
ClientTimeout time.Duration
}
type inFlight struct {
tmpPath string
done chan struct{}
err error
}
type CacheFile struct {
Reader io.ReadCloser
Size int64
Filename string
}
func NewCache(cacheRoot string, mirrorURLs []string, mirroredRepos []string) (*Cache, error) {
cfg := CacheConfig{
mirrorURLs: mirrorURLs,
mirroredRepos: mirroredRepos,
DialTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ClientTimeout: 0 * time.Second,
}
transport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: cfg.DialTimeout,
}).DialContext,
ResponseHeaderTimeout: cfg.ResponseHeaderTimeout,
}
cr, err := os.OpenRoot(cacheRoot)
if err != nil {
return nil, err
}
if err := checkSymLinks(cr, mirroredRepos); err != nil {
return nil, err
}
return &Cache{
cfg: cfg,
cr: cr,
client: http.Client{
Timeout: cfg.ClientTimeout,
Transport: transport,
},
inFlight: make(map[string]*inFlight),
}, nil
}
func (c *Cache) Fetch(relPath string) (*CacheFile, error) {
// relPath is relative to the localRoot
// ie relPath includes /{repo}/os/{arch}/ and the actual name linux-x.x.x.pkg.tar.zst
// return file directly if exists in cache
cf, err := c.getCachedFile(relPath)
if err == nil {
return cf, nil
}
// fetch file from upstream
slog.Debug("calling fetch", "file", relPath)
flight, file, err := c.getStream(relPath)
if err != nil {
return nil, err
}
return &CacheFile{
Reader: &tailer{f: file, flight: flight},
Size: -1,
Filename: filepath.Base(relPath),
}, nil
}
func checkSymLinks(cr *os.Root, repos []string) error {
for _, repo := range repos {
dirPath := filepath.Join(repo, "os/x86_64")
if err := cr.MkdirAll(dirPath, 0750); err != nil {
return err
}
lnPath := filepath.Join(dirPath, repo+".db")
srcPath := repo + ".db.tar.gz"
if _, err := cr.Lstat(lnPath); err == nil {
continue // link exists
}
if err := cr.Symlink(srcPath, lnPath); err != nil {
return err
}
}
return nil
}