brunel/packages/packages.go

482 lines
12 KiB
Go
Raw Normal View History

2024-07-28 20:59:50 +02:00
package packages
import (
"brunel/config"
"brunel/deb"
"brunel/domain"
"brunel/helpers"
"compress/bzip2"
"fmt"
"io"
"log/slog"
"net/http"
"slices"
"strings"
2024-08-04 19:01:27 +02:00
"sync"
2024-07-28 20:59:50 +02:00
"time"
2024-08-04 19:01:27 +02:00
"golang.org/x/sync/errgroup"
2024-07-28 20:59:50 +02:00
"pault.ag/go/debian/version"
2024-07-30 02:24:18 +02:00
"github.com/alphadose/haxmap"
2024-07-28 20:59:50 +02:00
"github.com/klauspost/compress/gzip"
"github.com/ulikunitz/xz"
)
2024-08-04 19:01:27 +02:00
// Constants for frequently used strings
const (
DMOSuffix = "-dmo"
DebianInstallerSection = "debian-installer"
PackageKey = "Package"
SourceKey = "Source"
VersionKey = "Version"
ArchitectureKey = "Architecture"
DescriptionKey = "Description"
SectionKey = "Section"
)
2024-07-28 20:59:50 +02:00
var LastUpdateTime time.Time
2024-08-02 22:07:39 +02:00
var currentPackages = haxmap.New[string, domain.SourcePackage]()
2024-07-28 20:59:50 +02:00
func ProcessPackages() error {
2024-08-04 19:01:27 +02:00
start := time.Now()
internalPackages := haxmap.New[string, domain.SourcePackage]()
externalPackages := haxmap.New[string, domain.SourcePackage]()
var eg errgroup.Group
eg.Go(func() error {
return LoadPackages(internalPackages, config.Configs.LocalPackageFiles, true)
})
eg.Go(func() error {
return LoadPackages(externalPackages, config.Configs.ExternalPackageFiles, false)
})
if err := eg.Wait(); err != nil {
2024-07-28 20:59:50 +02:00
return err
}
2024-08-04 19:01:27 +02:00
slog.Info("packages processed in " + time.Since(start).String())
2024-07-30 02:24:18 +02:00
combinePackages(internalPackages)
combinePackages(externalPackages)
2024-07-28 20:59:50 +02:00
ProcessMissingPackages(internalPackages, externalPackages)
2024-08-04 12:20:13 +02:00
ProcessStalePackages(internalPackages, externalPackages)
2024-07-28 20:59:50 +02:00
2024-07-30 09:20:32 +02:00
updatedPackages := haxmap.New[string, domain.SourcePackage]()
2024-07-30 02:24:18 +02:00
internalPackages.ForEach(func(k string, v domain.SourcePackage) bool {
2024-08-02 22:07:39 +02:00
curr, exists := currentPackages.Get(k)
2024-07-30 09:20:32 +02:00
if !exists {
updatedPackages.Set(k, v)
2024-07-30 09:08:00 +02:00
return true
}
2024-07-30 09:20:32 +02:00
mergedPackage := domain.SourcePackage{
2024-08-04 13:25:24 +02:00
Name: curr.Name,
Has32bit: curr.Has32bit,
Version: v.Version,
NewVersion: v.NewVersion,
Status: v.Status,
BuildAttempts: curr.BuildAttempts,
LastBuildStatus: curr.LastBuildStatus,
Packages: haxmap.New[string, domain.PackageInfo](),
2024-07-30 09:20:32 +02:00
}
updatedPackages.Set(k, mergedPackage)
2024-07-28 20:59:50 +02:00
return true
})
2024-08-02 22:07:39 +02:00
updatedPackages.ForEach(func(k string, v domain.SourcePackage) bool {
for _, pkg := range config.Configs.I386List {
2024-08-04 19:01:27 +02:00
if v.Name == pkg || v.Name == pkg+DMOSuffix {
2024-08-02 22:07:39 +02:00
v.Has32bit = true
2024-08-02 22:35:32 +02:00
updatedPackages.Set(k, v)
2024-08-02 22:07:39 +02:00
return true
}
}
v.Has32bit = false
2024-08-02 22:35:32 +02:00
updatedPackages.Set(k, v)
2024-08-02 22:07:39 +02:00
return true
})
currentPackages = updatedPackages
2024-07-30 09:20:32 +02:00
2024-07-28 20:59:50 +02:00
LastUpdateTime = time.Now()
2024-08-04 19:01:27 +02:00
if err := helpers.DBInst.DropPackages(); err != nil {
return fmt.Errorf("dropping packages: %w", err)
}
2024-08-04 19:01:27 +02:00
if err := SaveToDb(); err != nil {
return fmt.Errorf("saving to database: %w", err)
2024-07-28 20:59:50 +02:00
}
return nil
}
2024-07-30 02:24:18 +02:00
func GetPackages() *haxmap.Map[string, domain.SourcePackage] {
2024-08-02 22:07:39 +02:00
return currentPackages
2024-07-28 20:59:50 +02:00
}
func UpdatePackage(pkg domain.PackageInfo) error {
2024-08-02 22:07:39 +02:00
curr, ok := currentPackages.Get(pkg.Source)
2024-07-28 20:59:50 +02:00
if !ok {
return fmt.Errorf("package %s not found", pkg.Source)
}
curr.Packages.Set(pkg.PackageName, pkg)
2024-08-02 22:07:39 +02:00
currentPackages.Set(pkg.Source, curr)
2024-07-28 20:59:50 +02:00
return saveSingleToDb(curr)
}
2024-07-29 23:40:26 +02:00
func UpdateSourcePackage(pkg domain.SourcePackage) error {
2024-08-02 22:07:39 +02:00
currentPackages.Set(pkg.Name, pkg)
2024-07-29 23:40:26 +02:00
return saveSingleToDb(pkg)
}
2024-07-28 20:59:50 +02:00
func saveSingleToDb(pkg domain.SourcePackage) error {
2024-08-04 19:01:27 +02:00
if err := helpers.DBInst.UpdatePackage(pkg); err != nil {
return fmt.Errorf("updating package: %w", err)
2024-07-28 20:59:50 +02:00
}
LastUpdateTime = time.Now()
2024-08-04 19:01:27 +02:00
if err := helpers.DBInst.UpdateLastUpdateTime(LastUpdateTime); err != nil {
return fmt.Errorf("updating last update time: %w", err)
2024-07-28 20:59:50 +02:00
}
return nil
}
func SaveToDb() error {
2024-08-04 19:01:27 +02:00
if err := helpers.DBInst.SavePackages(currentPackages); err != nil {
slog.Error("Error saving packages to database", "error", err)
return fmt.Errorf("saving packages to database: %w", err)
2024-07-28 20:59:50 +02:00
}
LastUpdateTime = time.Now()
return helpers.DBInst.UpdateLastUpdateTime(LastUpdateTime)
}
func LoadFromDb() error {
packages, err := helpers.DBInst.GetPackages()
if err != nil {
2024-08-04 19:01:27 +02:00
slog.Error("Error getting packages from database", "error", err)
2024-07-28 20:59:50 +02:00
return nil
}
slices.SortStableFunc(packages, func(a, b domain.SourcePackage) int {
if a.Name == b.Name {
return 0
}
if a.Name > b.Name {
return 1
}
return -1
})
2024-08-02 22:07:39 +02:00
currentPackages.Clear()
2024-07-28 20:59:50 +02:00
for _, pkg := range packages {
2024-08-02 22:07:39 +02:00
currentPackages.Set(pkg.Name, pkg)
2024-07-28 20:59:50 +02:00
}
return nil
}
2024-08-04 19:01:27 +02:00
func LoadPackages(packages *haxmap.Map[string, domain.SourcePackage], packageFiles []config.PackageFile, isInternal bool) error {
slices.SortStableFunc(packageFiles, func(a, b config.PackageFile) int {
2024-07-28 20:59:50 +02:00
if a.Priority == b.Priority {
return 0
}
2024-08-04 19:01:27 +02:00
if (isInternal && a.Priority < b.Priority) || (!isInternal && a.Priority > b.Priority) {
2024-07-28 20:59:50 +02:00
return 1
}
return -1
})
2024-08-04 19:01:27 +02:00
var eg errgroup.Group
var mu sync.Mutex
packageResults := make([][]fetchResult, len(packageFiles))
for i, pkg := range packageFiles {
eg.Go(func() error {
results, err := fetchPackagesForFile(pkg)
2024-07-28 20:59:50 +02:00
if err != nil {
return err
}
2024-08-04 19:01:27 +02:00
mu.Lock()
packageResults[i] = results
mu.Unlock()
return nil
})
}
if err := eg.Wait(); err != nil {
return fmt.Errorf("fetching package files: %w", err)
}
for _, results := range packageResults {
for _, result := range results {
processPackageResult(packages, result)
2024-07-28 20:59:50 +02:00
}
}
return nil
}
2024-08-04 19:01:27 +02:00
type fetchResult struct {
repo string
packages *haxmap.Map[string, domain.PackageInfo]
}
2024-07-28 20:59:50 +02:00
2024-08-04 19:01:27 +02:00
func fetchPackagesForFile(pkg config.PackageFile) ([]fetchResult, error) {
var results []fetchResult
for _, repo := range pkg.Subrepos {
packages, err := fetchPackageFile(pkg, repo)
if err != nil {
return nil, fmt.Errorf("fetching package file for repo %s: %w", repo, err)
2024-07-28 20:59:50 +02:00
}
2024-08-04 19:01:27 +02:00
results = append(results, fetchResult{repo: repo, packages: packages})
2024-07-28 20:59:50 +02:00
}
2024-08-04 19:01:27 +02:00
return results, nil
}
2024-07-28 20:59:50 +02:00
2024-08-04 19:01:27 +02:00
func processPackageResult(packages *haxmap.Map[string, domain.SourcePackage], result fetchResult) {
result.packages.ForEach(func(newKey string, newPkg domain.PackageInfo) bool {
pk, ok := packages.Get(newPkg.Source)
if !ok {
newMap := haxmap.New[string, domain.PackageInfo]()
newMap.Set(newKey, newPkg)
packages.Set(newPkg.Source, domain.SourcePackage{
Name: newPkg.Source,
Packages: newMap,
Status: domain.Current,
Version: newPkg.Version,
})
return true
}
pk.Version = getHighestVer(pk.Version, newPkg.Version)
2024-08-04 19:01:27 +02:00
pkg, ok := pk.Packages.Get(newKey)
if !ok {
pk.Packages.Set(newKey, newPkg)
} else {
mVer, _ := version.Parse(pkg.Version)
extVer, _ := version.Parse(strings.Split(newPkg.Version, "+b")[0])
cmpVal := version.Compare(extVer, mVer)
if cmpVal >= 0 {
pk.Packages.Set(newKey, newPkg)
}
2024-08-04 19:01:27 +02:00
}
packages.Set(newPkg.Source, pk)
2024-08-04 19:01:27 +02:00
return true
})
2024-07-28 20:59:50 +02:00
}
2024-07-30 02:24:18 +02:00
func ProcessMissingPackages(internalPackages *haxmap.Map[string, domain.SourcePackage], externalPackages *haxmap.Map[string, domain.SourcePackage]) {
externalPackages.ForEach(func(k string, src domain.SourcePackage) bool {
2024-07-28 20:59:50 +02:00
_, ok := internalPackages.Get(k)
if !ok && src.Packages.Len() > 0 {
2024-08-04 13:25:24 +02:00
src.Status = domain.Missing
2024-07-28 20:59:50 +02:00
internalPackages.Set(k, src)
}
return true
})
}
2024-07-30 02:24:18 +02:00
func ProcessStalePackages(internalPackages *haxmap.Map[string, domain.SourcePackage], externalPackages *haxmap.Map[string, domain.SourcePackage]) {
externalPackages.ForEach(func(newPackage string, newSource domain.SourcePackage) bool {
2024-07-28 20:59:50 +02:00
matchedPackage, ok := internalPackages.Get(newPackage)
if !ok || matchedPackage.Packages.Len() == 0 {
return true
}
2024-08-04 19:01:27 +02:00
ver := getHighestVer(newSource.Version, matchedPackage.Version)
if ver != matchedPackage.Version {
matchedPackage.NewVersion = ver
2024-08-04 13:25:24 +02:00
matchedPackage.Status = domain.Stale
internalPackages.Set(newPackage, matchedPackage)
}
2024-08-04 13:25:24 +02:00
2024-07-28 20:59:50 +02:00
return true
})
}
2024-07-30 02:24:18 +02:00
func fetchPackageFile(pkg config.PackageFile, selectedRepo string) (*haxmap.Map[string, domain.PackageInfo], error) {
2024-07-28 20:59:50 +02:00
resp, err := http.Get(pkg.Url + selectedRepo + "/" + pkg.Packagepath + "." + pkg.Compression)
if err != nil {
2024-08-04 19:01:27 +02:00
return nil, fmt.Errorf("fetching package file: %w", err)
2024-07-28 20:59:50 +02:00
}
defer resp.Body.Close()
2024-08-04 19:01:27 +02:00
rdr, err := getCompressedReader(resp.Body, pkg.Compression)
if err != nil {
return nil, fmt.Errorf("creating compressed reader: %w", err)
2024-07-28 20:59:50 +02:00
}
2024-07-30 02:24:18 +02:00
packages := haxmap.New[string, domain.PackageInfo]()
2024-07-28 20:59:50 +02:00
sreader := deb.NewControlFileReader(rdr, false, false)
2024-08-04 19:01:27 +02:00
2024-07-28 20:59:50 +02:00
for {
stanza, err := sreader.ReadStanza()
if err != nil || stanza == nil {
break
}
2024-08-04 19:01:27 +02:00
if stanza[SectionKey] == DebianInstallerSection {
2024-07-28 20:59:50 +02:00
continue
}
2024-08-04 19:01:27 +02:00
name := stanza[PackageKey]
2024-07-28 20:59:50 +02:00
2024-08-04 19:01:27 +02:00
if shouldSkipPackage(name, pkg) {
2024-07-28 20:59:50 +02:00
continue
}
2024-08-04 19:01:27 +02:00
source, sourceVersion := parseSource(stanza[SourceKey])
if source == "" {
source = name
}
2024-08-04 19:01:27 +02:00
versionStr := chooseVersion(sourceVersion, stanza[VersionKey])
ver, err := version.Parse(versionStr)
2024-07-28 20:59:50 +02:00
if err != nil {
2024-08-04 19:01:27 +02:00
return nil, fmt.Errorf("parsing version %s: %w", versionStr, err)
2024-07-28 20:59:50 +02:00
}
2024-08-04 19:01:27 +02:00
if shouldUpdatePackage(packages, name, ver) {
packages.Set(name, domain.PackageInfo{
PackageName: name,
Version: ver.String(),
Source: source,
Architecture: stanza[ArchitectureKey],
Description: stanza[DescriptionKey],
Status: domain.Current,
})
2024-07-28 20:59:50 +02:00
}
}
return packages, nil
}
2024-08-04 19:01:27 +02:00
func getCompressedReader(body io.Reader, compression string) (io.Reader, error) {
switch compression {
case "bz2":
return bzip2.NewReader(body), nil
case "xz":
return xz.NewReader(body)
case "gz":
return gzip.NewReader(body)
default:
return body, nil
}
}
func shouldSkipPackage(name string, pkg config.PackageFile) bool {
if pkg.UseWhitelist && len(pkg.Whitelist) > 0 && !nameContains(name, pkg.Whitelist) {
return true
}
return nameContains(name, pkg.Blacklist)
}
func parseSource(source string) (string, string) {
sourceSplit := strings.Split(source, " ")
if len(sourceSplit) > 1 {
return sourceSplit[0], strings.Trim(sourceSplit[1], "()")
}
return source, ""
}
func chooseVersion(sourceVersion, stanzaVersion string) string {
if sourceVersion != "" {
return sourceVersion
}
return stanzaVersion
}
func shouldUpdatePackage(packages *haxmap.Map[string, domain.PackageInfo], name string, ver version.Version) bool {
existingPkg, exists := packages.Get(name)
if !exists {
return true
}
existingVer, _ := version.Parse(existingPkg.Version)
return version.Compare(ver, existingVer) >= 0
}
2024-07-28 20:59:50 +02:00
func nameContains(name string, match []string) bool {
for _, m := range match {
if strings.Contains(name, m) {
return true
}
}
return false
}
func GetPackagesCount() domain.PackagesCount {
count := domain.PackagesCount{
Stale: 0,
Missing: 0,
Current: 0,
Error: 0,
Queued: 0,
Building: 0,
}
2024-08-04 13:02:55 +02:00
currentPackages.ForEach(func(k string, pkg domain.SourcePackage) bool {
switch pkg.Status {
case domain.Stale:
count.Stale++
case domain.Missing:
count.Missing++
case domain.Built:
count.Current++
case domain.Current:
count.Current++
case domain.Error:
count.Error++
}
2024-07-28 20:59:50 +02:00
return true
})
2024-07-29 23:49:20 +02:00
2024-07-28 20:59:50 +02:00
return count
}
2024-07-30 02:24:18 +02:00
func combinePackages(packages *haxmap.Map[string, domain.SourcePackage]) {
dmoPackages := haxmap.New[string, domain.SourcePackage]()
2024-08-04 19:01:27 +02:00
// Identify and collect -dmo packages
2024-07-30 02:24:18 +02:00
packages.ForEach(func(k string, v domain.SourcePackage) bool {
2024-08-04 19:01:27 +02:00
if strings.HasSuffix(k, DMOSuffix) {
2024-07-30 02:24:18 +02:00
dmoPackages.Set(k, v)
packages.Del(k)
}
return true
})
2024-08-04 19:01:27 +02:00
// Combine -dmo packages with base packages or add them back
2024-07-30 02:24:18 +02:00
dmoPackages.ForEach(func(dmoName string, dmoPkg domain.SourcePackage) bool {
2024-08-04 19:01:27 +02:00
baseName := strings.TrimSuffix(dmoName, DMOSuffix)
if basePkg, hasBase := packages.Get(baseName); hasBase {
combinedPkg := combineDMOPackages(dmoPkg, basePkg)
packages.Set(dmoName, combinedPkg)
packages.Del(baseName)
2024-07-30 02:24:18 +02:00
} else {
packages.Set(dmoName, dmoPkg)
}
return true
})
}
2024-08-04 13:25:24 +02:00
2024-08-04 19:01:27 +02:00
func combineDMOPackages(dmoPkg, basePkg domain.SourcePackage) domain.SourcePackage {
combinedPkg := dmoPkg
basePkg.Packages.ForEach(func(pkgName string, pkgInfo domain.PackageInfo) bool {
if _, exists := combinedPkg.Packages.Get(pkgName); !exists {
combinedPkg.Packages.Set(pkgName, pkgInfo)
}
return true
})
return combinedPkg
}
2024-08-04 13:25:24 +02:00
func getHighestVer(ver string, newVer string) string {
mVer, _ := version.Parse(ver)
extVer, _ := version.Parse(newVer)
cmpVal := version.Compare(mVer, extVer)
if cmpVal < 0 {
return newVer
}
return ver
}