aptly/deb/package_collection.go

346 lines
8.1 KiB
Go

package deb
import (
"bytes"
"fmt"
"path/filepath"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/database"
"github.com/ugorji/go/codec"
)
// PackageCollection does management of packages in DB
type PackageCollection struct {
db database.Storage
codecHandle *codec.MsgpackHandle
}
// Verify interface
var (
_ PackageCatalog = &PackageCollection{}
)
// NewPackageCollection creates new PackageCollection and binds it to database
func NewPackageCollection(db database.Storage) *PackageCollection {
return &PackageCollection{
db: db,
codecHandle: &codec.MsgpackHandle{},
}
}
// oldPackage is Package struct for aptly < 0.4 with all fields in one struct
// It is used to decode old aptly DBs
type oldPackage struct {
IsSource bool
Name string
Version string
Architecture string
SourceArchitecture string
Source string
Provides []string
Depends []string
BuildDepends []string
BuildDependsInDep []string
PreDepends []string
Suggests []string
Recommends []string
Files []PackageFile
Extra Stanza
}
// ByKey find package in DB by its key
func (collection *PackageCollection) ByKey(key []byte) (*Package, error) {
encoded, err := collection.db.Get(key)
if err != nil {
return nil, err
}
p := &Package{}
if len(encoded) > 2 && (encoded[0] != 0xc1 || encoded[1] != 0x1) {
oldp := &oldPackage{}
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
err = decoder.Decode(oldp)
if err != nil {
return nil, err
}
p.Name = oldp.Name
p.Version = oldp.Version
p.Architecture = oldp.Architecture
p.IsSource = oldp.IsSource
p.SourceArchitecture = oldp.SourceArchitecture
p.Source = oldp.Source
p.Provides = oldp.Provides
p.deps = &PackageDependencies{
Depends: oldp.Depends,
BuildDepends: oldp.BuildDepends,
BuildDependsInDep: oldp.BuildDependsInDep,
PreDepends: oldp.PreDepends,
Suggests: oldp.Suggests,
Recommends: oldp.Recommends,
}
p.extra = &oldp.Extra
for i := range oldp.Files {
oldp.Files[i].Filename = filepath.Base(oldp.Files[i].Filename)
}
p.UpdateFiles(PackageFiles(oldp.Files))
// Save in new format
err = collection.Update(p)
if err != nil {
return nil, err
}
} else {
decoder := codec.NewDecoderBytes(encoded[2:], collection.codecHandle)
err = decoder.Decode(p)
if err != nil {
return nil, err
}
}
p.collection = collection
return p, nil
}
// loadExtra loads Stanza with all the xtra information about the package
func (collection *PackageCollection) loadExtra(p *Package) *Stanza {
encoded, err := collection.db.Get(p.Key("xE"))
if err != nil {
panic("unable to load extra")
}
stanza := &Stanza{}
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
err = decoder.Decode(stanza)
if err != nil {
panic("unable to decode extra")
}
return stanza
}
// loadDependencies loads dependencies for the package
func (collection *PackageCollection) loadDependencies(p *Package) *PackageDependencies {
encoded, err := collection.db.Get(p.Key("xD"))
if err != nil {
panic(fmt.Sprintf("unable to load deps: %s, %s", p, err))
}
deps := &PackageDependencies{}
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
err = decoder.Decode(deps)
if err != nil {
panic("unable to decode deps")
}
return deps
}
// loadFiles loads additional PackageFiles record
func (collection *PackageCollection) loadFiles(p *Package) *PackageFiles {
encoded, err := collection.db.Get(p.Key("xF"))
if err != nil {
panic("unable to load files")
}
files := &PackageFiles{}
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
err = decoder.Decode(files)
if err != nil {
panic("unable to decode files")
}
return files
}
// loadContents loads or calculates and saves package contents
func (collection *PackageCollection) loadContents(p *Package, packagePool aptly.PackagePool, progress aptly.Progress) []string {
encoded, err := collection.db.Get(p.Key("xC"))
if err == nil {
contents := []string{}
decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
err = decoder.Decode(&contents)
if err != nil {
panic("unable to decode contents")
}
return contents
}
if err != database.ErrNotFound {
panic("unable to load contents")
}
contents, err := p.CalculateContents(packagePool, progress)
if err != nil {
// failed to acquire contents, don't persist it
return contents
}
var buf bytes.Buffer
err = codec.NewEncoder(&buf, collection.codecHandle).Encode(contents)
if err != nil {
panic("unable to encode contents")
}
err = collection.db.Put(p.Key("xC"), buf.Bytes())
if err != nil {
panic("unable to save contents")
}
return contents
}
// Update adds or updates information about package in DB
func (collection *PackageCollection) Update(p *Package) error {
transaction, err := collection.db.OpenTransaction()
if err != nil {
return err
}
defer transaction.Discard()
if err = collection.UpdateInTransaction(p, transaction); err != nil {
return err
}
return transaction.Commit()
}
// UpdateInTransaction updates/creates package info in the context of the outer transaction
func (collection *PackageCollection) UpdateInTransaction(p *Package, transaction database.Transaction) error {
var encodeBuffer bytes.Buffer
encoder := codec.NewEncoder(&encodeBuffer, collection.codecHandle)
encodeBuffer.Reset()
encodeBuffer.WriteByte(0xc1)
encodeBuffer.WriteByte(0x1)
if err := encoder.Encode(p); err != nil {
return err
}
err := transaction.Put(p.Key(""), encodeBuffer.Bytes())
if err != nil {
return err
}
// Encode offloaded fields one by one
if p.files != nil {
encodeBuffer.Reset()
err = encoder.Encode(*p.files)
if err != nil {
return err
}
err = transaction.Put(p.Key("xF"), encodeBuffer.Bytes())
if err != nil {
return err
}
}
if p.deps != nil {
encodeBuffer.Reset()
err = encoder.Encode(*p.deps)
if err != nil {
return err
}
err = transaction.Put(p.Key("xD"), encodeBuffer.Bytes())
if err != nil {
return err
}
p.deps = nil
}
if p.extra != nil {
encodeBuffer.Reset()
err = encoder.Encode(*p.extra)
if err != nil {
return err
}
err = transaction.Put(p.Key("xE"), encodeBuffer.Bytes())
if err != nil {
return err
}
p.extra = nil
}
p.collection = collection
return nil
}
// AllPackageRefs returns list of all packages as PackageRefList
func (collection *PackageCollection) AllPackageRefs() *PackageRefList {
return &PackageRefList{Refs: collection.db.KeysByPrefix([]byte("P"))}
}
// DeleteByKey deletes package in DB by key
func (collection *PackageCollection) DeleteByKey(key []byte, dbw database.Writer) error {
for _, key := range [][]byte{key, append([]byte("xF"), key...), append([]byte("xD"), key...), append([]byte("xE"), key...)} {
err := dbw.Delete(key)
if err != nil {
return err
}
}
return nil
}
// Scan does full scan on all the packages
func (collection *PackageCollection) Scan(q PackageQuery) (result *PackageList) {
result = NewPackageListWithDuplicates(true, 0)
for _, key := range collection.db.KeysByPrefix([]byte("P")) {
pkg, err := collection.ByKey(key)
if err != nil {
panic(fmt.Sprintf("unable to load package: %s", err))
}
if q.Matches(pkg) {
result.Add(pkg)
}
}
return
}
// Search is not implemented
func (collection *PackageCollection) Search(_ Dependency, _ bool) (searchResults []*Package) {
panic("Not implemented")
}
// SearchSupported returns false
func (collection *PackageCollection) SearchSupported() bool {
return false
}
// SearchByKey finds package by exact key
func (collection *PackageCollection) SearchByKey(arch, name, version string) (result *PackageList) {
result = NewPackageListWithDuplicates(true, 0)
for _, key := range collection.db.KeysByPrefix([]byte(fmt.Sprintf("P%s %s %s", arch, name, version))) {
pkg, err := collection.ByKey(key)
if err != nil {
panic(fmt.Sprintf("unable to load package: %s", err))
}
if pkg.Architecture == arch && pkg.Name == name && pkg.Version == version {
result.Add(pkg)
}
}
return
}