diff --git a/internal/repomaint/build_map.go b/internal/repomaint/build_map.go new file mode 100644 index 0000000..049a734 --- /dev/null +++ b/internal/repomaint/build_map.go @@ -0,0 +1,119 @@ +package repomaint + +import ( + "archive/tar" + "bufio" + "compress/gzip" + "fmt" + "io" + "log/slog" + "os" + "path/filepath" + "slices" + "strings" +) + +func (r *RepoSync) buildMap(repo string) (map[string]string, error) { + // create slice all filenames in repo + pkgs, err := r.cachedPkgs(repo) + if err != nil { + return nil, err + } + + // open db file + db, fr, gzr, err := r.openDb(repo) + if err != nil { + return nil, err + } + defer fr.Close() + defer gzr.Close() + + // for entry in db + pkgMap := make(map[string]string) + for { + hdr, err := db.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + if filepath.Base(hdr.Name) != "desc" { + continue + } + if !slices.Contains(pkgs, filepath.Dir(hdr.Name)) { + continue + } + pkgName, pkgFile, err := parseDesc(db) + if err != nil { + slog.Warn("failed to parse desc file", "pkg", hdr.Name, "err", err) + continue + } + + pkgMap[pkgName] = pkgFile + } + + return pkgMap, nil +} + +func (r *RepoSync) cachedPkgs(repo string) ([]string, error) { + repoPath := filepath.Join(repo, repoArch) + f, err := r.root.Open(repoPath) + if err != nil { + return nil, err + } + files, err := f.ReadDir(-1) + if err != nil { + return nil, err + } + var pkgs []string + for _, f := range files { + pkg := strings.TrimSuffix(f.Name(), pkgSuffix) + pkgs = append(pkgs, pkg) + } + + return pkgs, nil +} + +func (r *RepoSync) openDb(repo string) (*tar.Reader, *os.File, *gzip.Reader, error) { + + repoPath := filepath.Join(repo, repoArch) + path := filepath.Join(repoPath, repo+dbSuffix) + f, err := r.root.Open(path) + if err != nil { + return nil, nil, nil, err + } + gr, err := gzip.NewReader(f) + if err != nil { + f.Close() + return nil, nil, nil, err + } + tr := tar.NewReader(gr) + + return tr, f, gr, nil +} + +func parseDesc(f io.Reader) (pkgname, filename string, err error) { + scanner := bufio.NewScanner(f) + for scanner.Scan() { + switch scanner.Text() { + case "%NAME%": + scanner.Scan() + pkgname = scanner.Text() + case "%FILENAME%": + scanner.Scan() + filename = scanner.Text() + } + if pkgname != "" && filename != "" { + break + } + } + if err := scanner.Err(); err != nil { + return "", "", err + } + if pkgname == "" || filename == "" { + return "", "", fmt.Errorf("incomplete or corrupt desc") + } + + return pkgname, filename, nil +} diff --git a/internal/repomaint/create_map.go b/internal/repomaint/create_map.go deleted file mode 100644 index 32d69ee..0000000 --- a/internal/repomaint/create_map.go +++ /dev/null @@ -1,11 +0,0 @@ -package repomaint - -func (r *RepoSync) createMap(repo string) (map[string]string, error) { - // create slice all filenames in repo - // open db file - // for file in slice - // find in db - // insert into map as map[packagename] = filename - // return map - return nil, nil -} diff --git a/internal/repomaint/repomaint.go b/internal/repomaint/repomaint.go index b5a2a7a..90f3106 100644 --- a/internal/repomaint/repomaint.go +++ b/internal/repomaint/repomaint.go @@ -6,6 +6,12 @@ import ( "github.com/ewpt3ch/pkgstash/internal/cache" ) +const ( + repoArch = "os/x86_64" + dbSuffix = ".db.tar.gz" + pkgSuffix = "-x86_64.pkg.tar.zst" +) + type CacheClient interface { FetchDB() error Fetch(relpath string) (*cache.CacheFile, error) diff --git a/internal/repomaint/repomaint_test.go b/internal/repomaint/repomaint_test.go index 49a1c02..584b9d4 100644 --- a/internal/repomaint/repomaint_test.go +++ b/internal/repomaint/repomaint_test.go @@ -4,10 +4,12 @@ import ( "io" "os" "path/filepath" + "strings" "testing" "github.com/ewpt3ch/pkgstash/internal/cache" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // create a CacheClient to mock cache.Fetch and cache.FetchDB to fulfill @@ -22,6 +24,48 @@ func (m *mockCache) Fetch(relPath string) (*cache.CacheFile, error) { return nil, nil } +func TestParseDesc(t *testing.T) { + t.Run("parse good desc file", func(t *testing.T) { + r := strings.NewReader("%NAME%\nlinux\n\n\n%FAKEKEY%\nFAKEINFO\n\n%FILENAME%\nlinux-7.0.9\n") + pkgName, fileName, err := parseDesc(r) + require.NoError(t, err) + assert.Equal(t, "linux", pkgName) + assert.Equal(t, "linux-7.0.9", fileName) + }) + + t.Run("parse bad desc file", func(t *testing.T) { + r := strings.NewReader("%NAME\nlinux\n\n%FAKEKEY%\nwhat\n") + pkgName, fileName, err := parseDesc(r) + assert.Error(t, err) + assert.Equal(t, "", pkgName) + assert.Equal(t, "", fileName) + }) +} + +func TestCachedFiles(t *testing.T) { + testFiles := []string{ + "linux-x86_64.pkg.tar.zst", + "gcc-rust-x86_64.pkg.tar.zst", + "linux-api-headers-x86_64.pkg.tar.zst", + } + c := &mockCache{ + fetched: make([]string, 5), + } + rs, err := NewRepoSync(c, t.TempDir(), []string{"core"}) + if err != nil { + t.Fatalf("failed to create RepoMaint: %v", err) + } + rs.root.MkdirAll("core/os/x86_64", 0750) + for _, f := range testFiles { + rs.root.WriteFile(filepath.Join("core/os/x86_64", f), []byte{}, 0644) + } + + cachedFiles, err := rs.cachedPkgs("core") + assert.Contains(t, cachedFiles, "linux") + assert.Contains(t, cachedFiles, "gcc-rust") + assert.Contains(t, cachedFiles, "linux-api-headers") +} + func TestSyncMapBuild(t *testing.T) { testFiles := make(map[string]string) testFiles["linux"] = "linux-7.0.9.arch1-1-x86_64.pkg.tar.zst"