1package deb
2
3import (
4	"bytes"
5	"fmt"
6	"path/filepath"
7
8	"github.com/aptly-dev/aptly/aptly"
9	"github.com/aptly-dev/aptly/database"
10	"github.com/ugorji/go/codec"
11)
12
13// PackageCollection does management of packages in DB
14type PackageCollection struct {
15	db          database.Storage
16	codecHandle *codec.MsgpackHandle
17}
18
19// Verify interface
20var (
21	_ PackageCatalog = &PackageCollection{}
22)
23
24// NewPackageCollection creates new PackageCollection and binds it to database
25func NewPackageCollection(db database.Storage) *PackageCollection {
26	return &PackageCollection{
27		db:          db,
28		codecHandle: &codec.MsgpackHandle{},
29	}
30}
31
32// oldPackage is Package struct for aptly < 0.4 with all fields in one struct
33// It is used to decode old aptly DBs
34type oldPackage struct {
35	IsSource           bool
36	Name               string
37	Version            string
38	Architecture       string
39	SourceArchitecture string
40	Source             string
41	Provides           []string
42	Depends            []string
43	BuildDepends       []string
44	BuildDependsInDep  []string
45	PreDepends         []string
46	Suggests           []string
47	Recommends         []string
48	Files              []PackageFile
49	Extra              Stanza
50}
51
52// ByKey find package in DB by its key
53func (collection *PackageCollection) ByKey(key []byte) (*Package, error) {
54	encoded, err := collection.db.Get(key)
55	if err != nil {
56		return nil, err
57	}
58
59	p := &Package{}
60
61	if len(encoded) > 2 && (encoded[0] != 0xc1 || encoded[1] != 0x1) {
62		oldp := &oldPackage{}
63
64		decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
65		err = decoder.Decode(oldp)
66		if err != nil {
67			return nil, err
68		}
69
70		p.Name = oldp.Name
71		p.Version = oldp.Version
72		p.Architecture = oldp.Architecture
73		p.IsSource = oldp.IsSource
74		p.SourceArchitecture = oldp.SourceArchitecture
75		p.Source = oldp.Source
76		p.Provides = oldp.Provides
77
78		p.deps = &PackageDependencies{
79			Depends:           oldp.Depends,
80			BuildDepends:      oldp.BuildDepends,
81			BuildDependsInDep: oldp.BuildDependsInDep,
82			PreDepends:        oldp.PreDepends,
83			Suggests:          oldp.Suggests,
84			Recommends:        oldp.Recommends,
85		}
86
87		p.extra = &oldp.Extra
88		for i := range oldp.Files {
89			oldp.Files[i].Filename = filepath.Base(oldp.Files[i].Filename)
90		}
91		p.UpdateFiles(PackageFiles(oldp.Files))
92
93		// Save in new format
94		err = collection.Update(p)
95		if err != nil {
96			return nil, err
97		}
98	} else {
99		decoder := codec.NewDecoderBytes(encoded[2:], collection.codecHandle)
100		err = decoder.Decode(p)
101		if err != nil {
102			return nil, err
103		}
104	}
105
106	p.collection = collection
107
108	return p, nil
109}
110
111// loadExtra loads Stanza with all the xtra information about the package
112func (collection *PackageCollection) loadExtra(p *Package) *Stanza {
113	encoded, err := collection.db.Get(p.Key("xE"))
114	if err != nil {
115		panic("unable to load extra")
116	}
117
118	stanza := &Stanza{}
119
120	decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
121	err = decoder.Decode(stanza)
122	if err != nil {
123		panic("unable to decode extra")
124	}
125
126	return stanza
127}
128
129// loadDependencies loads dependencies for the package
130func (collection *PackageCollection) loadDependencies(p *Package) *PackageDependencies {
131	encoded, err := collection.db.Get(p.Key("xD"))
132	if err != nil {
133		panic(fmt.Sprintf("unable to load deps: %s, %s", p, err))
134	}
135
136	deps := &PackageDependencies{}
137
138	decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
139	err = decoder.Decode(deps)
140	if err != nil {
141		panic("unable to decode deps")
142	}
143
144	return deps
145}
146
147// loadFiles loads additional PackageFiles record
148func (collection *PackageCollection) loadFiles(p *Package) *PackageFiles {
149	encoded, err := collection.db.Get(p.Key("xF"))
150	if err != nil {
151		panic("unable to load files")
152	}
153
154	files := &PackageFiles{}
155
156	decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
157	err = decoder.Decode(files)
158	if err != nil {
159		panic("unable to decode files")
160	}
161
162	return files
163}
164
165// loadContents loads or calculates and saves package contents
166func (collection *PackageCollection) loadContents(p *Package, packagePool aptly.PackagePool, progress aptly.Progress) []string {
167	encoded, err := collection.db.Get(p.Key("xC"))
168	if err == nil {
169		contents := []string{}
170
171		decoder := codec.NewDecoderBytes(encoded, collection.codecHandle)
172		err = decoder.Decode(&contents)
173		if err != nil {
174			panic("unable to decode contents")
175		}
176
177		return contents
178	}
179
180	if err != database.ErrNotFound {
181		panic("unable to load contents")
182	}
183
184	contents, err := p.CalculateContents(packagePool, progress)
185	if err != nil {
186		// failed to acquire contents, don't persist it
187		return contents
188	}
189
190	var buf bytes.Buffer
191	err = codec.NewEncoder(&buf, collection.codecHandle).Encode(contents)
192	if err != nil {
193		panic("unable to encode contents")
194	}
195
196	err = collection.db.Put(p.Key("xC"), buf.Bytes())
197	if err != nil {
198		panic("unable to save contents")
199	}
200
201	return contents
202}
203
204// Update adds or updates information about package in DB checking for conficts first
205func (collection *PackageCollection) Update(p *Package) error {
206	var encodeBuffer bytes.Buffer
207
208	encoder := codec.NewEncoder(&encodeBuffer, collection.codecHandle)
209
210	encodeBuffer.Reset()
211	encodeBuffer.WriteByte(0xc1)
212	encodeBuffer.WriteByte(0x1)
213	err := encoder.Encode(p)
214	if err != nil {
215		return err
216	}
217
218	err = collection.db.Put(p.Key(""), encodeBuffer.Bytes())
219	if err != nil {
220		return err
221	}
222
223	// Encode offloaded fields one by one
224	if p.files != nil {
225		encodeBuffer.Reset()
226		err = encoder.Encode(*p.files)
227		if err != nil {
228			return err
229		}
230
231		err = collection.db.Put(p.Key("xF"), encodeBuffer.Bytes())
232		if err != nil {
233			return err
234		}
235	}
236
237	if p.deps != nil {
238		encodeBuffer.Reset()
239		err = encoder.Encode(*p.deps)
240		if err != nil {
241			return err
242		}
243
244		err = collection.db.Put(p.Key("xD"), encodeBuffer.Bytes())
245		if err != nil {
246			return err
247		}
248
249		p.deps = nil
250	}
251
252	if p.extra != nil {
253		encodeBuffer.Reset()
254		err = encoder.Encode(*p.extra)
255		if err != nil {
256			return err
257		}
258
259		err = collection.db.Put(p.Key("xE"), encodeBuffer.Bytes())
260		if err != nil {
261			return err
262		}
263
264		p.extra = nil
265	}
266
267	p.collection = collection
268
269	return nil
270}
271
272// AllPackageRefs returns list of all packages as PackageRefList
273func (collection *PackageCollection) AllPackageRefs() *PackageRefList {
274	return &PackageRefList{Refs: collection.db.KeysByPrefix([]byte("P"))}
275}
276
277// DeleteByKey deletes package in DB by key
278func (collection *PackageCollection) DeleteByKey(key []byte) error {
279	for _, key := range [][]byte{key, append([]byte("xF"), key...), append([]byte("xD"), key...), append([]byte("xE"), key...)} {
280		err := collection.db.Delete(key)
281		if err != nil {
282			return err
283		}
284	}
285	return nil
286}
287
288// Scan does full scan on all the packages
289func (collection *PackageCollection) Scan(q PackageQuery) (result *PackageList) {
290	result = NewPackageListWithDuplicates(true, 0)
291
292	for _, key := range collection.db.KeysByPrefix([]byte("P")) {
293		pkg, err := collection.ByKey(key)
294		if err != nil {
295			panic(fmt.Sprintf("unable to load package: %s", err))
296		}
297
298		if q.Matches(pkg) {
299			result.Add(pkg)
300		}
301	}
302
303	return
304}
305
306// Search is not implemented
307func (collection *PackageCollection) Search(dep Dependency, allMatches bool) (searchResults []*Package) {
308	panic("Not implemented")
309}
310
311// SearchSupported returns false
312func (collection *PackageCollection) SearchSupported() bool {
313	return false
314}
315
316// SearchByKey finds package by exact key
317func (collection *PackageCollection) SearchByKey(arch, name, version string) (result *PackageList) {
318	result = NewPackageListWithDuplicates(true, 0)
319
320	for _, key := range collection.db.KeysByPrefix([]byte(fmt.Sprintf("P%s %s %s", arch, name, version))) {
321		pkg, err := collection.ByKey(key)
322		if err != nil {
323			panic(fmt.Sprintf("unable to load package: %s", err))
324		}
325
326		if pkg.Architecture == arch && pkg.Name == name && pkg.Version == version {
327			result.Add(pkg)
328		}
329	}
330
331	return
332}
333