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, _ 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 }