debanator/backend.go

155 lines
4.0 KiB
Go

package debanator
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"fmt"
"io"
"io/fs"
"os"
"path"
"strconv"
"strings"
log "github.com/sirupsen/logrus"
"pault.ag/go/debian/control"
"pault.ag/go/debian/deb"
"pault.ag/go/debian/dependency"
"pault.ag/go/debian/version"
"golang.org/x/exp/maps"
)
// A backend to search for packages in
type Backend interface {
GetPackages()
}
type FileBackend struct {
path string
}
func NewFileBackend(path string) FileBackend {
return FileBackend{path}
}
func BinaryIndexFromDeb(p string, basePath string) (*control.BinaryIndex, error) {
f, err := os.Open(p)
if err != nil {
return nil, fmt.Errorf("open file: %w", err)
}
defer f.Close()
debFile, err := deb.Load(f, p)
if err != nil {
return nil, fmt.Errorf("read deb: %w", err)
}
md5sum := md5.New()
sha1sum := sha1.New()
sha256sum := sha256.New()
hashWriter := io.MultiWriter(md5sum, sha1sum, sha256sum)
size, err := io.Copy(hashWriter, f)
if err != nil {
return nil, fmt.Errorf("hash file: %w", err)
}
bi := control.BinaryIndex{
Paragraph: control.Paragraph{
Values: make(map[string]string),
},
Package: debFile.Control.Package,
Source: debFile.Control.Source,
Version: debFile.Control.Version,
InstalledSize: fmt.Sprintf("%d", debFile.Control.InstalledSize),
Size: strconv.Itoa(int(size)),
Maintainer: debFile.Control.Maintainer,
Architecture: debFile.Control.Architecture,
MultiArch: debFile.Control.MultiArch,
Description: debFile.Control.Description,
Homepage: debFile.Control.Homepage,
Section: debFile.Control.Section,
// FIXME: gross, make this more centrally managed somehow
Filename: path.Join("pool/main", strings.TrimPrefix(p, basePath)),
Priority: debFile.Control.Priority,
MD5sum: fmt.Sprintf("%x", md5sum.Sum(nil)),
SHA1: fmt.Sprintf("%x", sha1sum.Sum(nil)),
SHA256: fmt.Sprintf("%x", sha256sum.Sum(nil)),
}
if debFile.Control.Depends.String() != "" {
bi.Paragraph.Set("Depends", debFile.Control.Depends.String())
}
if debFile.Control.Recommends.String() != "" {
bi.Paragraph.Set("Recommends", debFile.Control.Recommends.String())
}
if debFile.Control.Suggests.String() != "" {
bi.Paragraph.Set("Suggests", debFile.Control.Suggests.String())
}
if debFile.Control.Breaks.String() != "" {
bi.Paragraph.Set("Breaks", debFile.Control.Breaks.String())
}
if debFile.Control.Replaces.String() != "" {
bi.Paragraph.Set("Replaces", debFile.Control.Replaces.String())
}
if debFile.Control.BuiltUsing.String() != "" {
bi.Paragraph.Set("BuiltUsing", debFile.Control.BuiltUsing.String())
}
return &bi, nil
}
func ScanDebs(debpath string) Repo {
var debs []string
fs.WalkDir(os.DirFS(debpath), ".", func(path string, dir fs.DirEntry, err error) error {
if err != nil {
log.WithFields(log.Fields{
"path": path,
"error": err,
}).Warn("Error scanning for debs")
return nil
}
if !dir.IsDir() && strings.HasSuffix(dir.Name(), ".deb"){
debs = append(debs, path)
}
return nil
})
packs := make(map[string]LogicalPackage)
for _, d := range debs {
p := path.Join(debpath, d)
bi, err := BinaryIndexFromDeb(p, debpath)
if err != nil {
log.WithFields(log.Fields{
"path": p,
"error": err,
}).Error("Error processing deb file")
continue
}
packageName := bi.Package
if _, ok := packs[packageName]; !ok {
packs[packageName] = LogicalPackage{
Name: packageName,
Arches: make(map[dependency.Arch]map[version.Version]control.BinaryIndex),
}
}
pack := packs[packageName]
if _, ok := pack.Arches[bi.Architecture]; !ok {
pack.Arches[bi.Architecture] = make(map[version.Version]control.BinaryIndex)
}
arch := pack.Arches[bi.Architecture]
if _, ok := arch[bi.Version]; !ok {
arch[bi.Version] = *bi
} else {
log.WithFields(log.Fields{
"package": packageName,
"arch": arch,
"version": bi.Version.String(),
}).Warn("Duplicate package/arch/version found, ignoring...")
}
}
return Repo{
packages: maps.Values(packs),
cache: make(map[string]hashedFile),
}
}