1package deb
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"io"
8
9	"github.com/aptly-dev/aptly/database"
10	"github.com/pborman/uuid"
11)
12
13// ContentsIndex calculates mapping from files to packages, with sorting and aggregation
14type ContentsIndex struct {
15	db     database.Storage
16	prefix []byte
17}
18
19// NewContentsIndex creates empty ContentsIndex
20func NewContentsIndex(db database.Storage) *ContentsIndex {
21	return &ContentsIndex{
22		db:     db,
23		prefix: []byte(uuid.New()),
24	}
25}
26
27// Push adds package to contents index, calculating package contents as required
28func (index *ContentsIndex) Push(qualifiedName []byte, contents []string) error {
29	for _, path := range contents {
30		// for performance reasons we only write to leveldb during push.
31		// merging of qualified names per path will be done in WriteTo
32		err := index.db.Put(append(append(append(index.prefix, []byte(path)...), byte(0)), qualifiedName...), nil)
33		if err != nil {
34			return err
35		}
36	}
37
38	return nil
39}
40
41// Empty checks whether index contains no packages
42func (index *ContentsIndex) Empty() bool {
43	return !index.db.HasPrefix(index.prefix)
44}
45
46// WriteTo dumps sorted mapping of files to qualified package names
47func (index *ContentsIndex) WriteTo(w io.Writer) (int64, error) {
48	// For performance reasons push method wrote on key per path and package
49	// in this method we now need to merge all packages which have the same path
50	// and write it to contents index file
51
52	var n int64
53
54	nn, err := fmt.Fprintf(w, "%s %s\n", "FILE", "LOCATION")
55	n += int64(nn)
56	if err != nil {
57		return n, err
58	}
59
60	prefixLen := len(index.prefix)
61
62	var (
63		currentPath []byte
64		currentPkgs [][]byte
65	)
66
67	err = index.db.ProcessByPrefix(index.prefix, func(key []byte, value []byte) error {
68		// cut prefix
69		key = key[prefixLen:]
70
71		i := bytes.Index(key, []byte{0})
72		if i == -1 {
73			return errors.New("corrupted index entry")
74		}
75
76		path := key[:i]
77		pkg := key[i+1:]
78
79		if !bytes.Equal(path, currentPath) {
80			if currentPath != nil {
81				nn, err = w.Write(append(currentPath, ' '))
82				n += int64(nn)
83				if err != nil {
84					return err
85				}
86
87				nn, err = w.Write(bytes.Join(currentPkgs, []byte{','}))
88				n += int64(nn)
89				if err != nil {
90					return err
91				}
92
93				nn, err = w.Write([]byte{'\n'})
94				n += int64(nn)
95				if err != nil {
96					return err
97				}
98			}
99
100			currentPath = append([]byte(nil), path...)
101			currentPkgs = nil
102		}
103
104		currentPkgs = append(currentPkgs, append([]byte(nil), pkg...))
105
106		return nil
107	})
108
109	if err != nil {
110		return n, err
111	}
112
113	if currentPath != nil {
114		nn, err = w.Write(append(currentPath, ' '))
115		n += int64(nn)
116		if err != nil {
117			return n, err
118		}
119
120		nn, err = w.Write(bytes.Join(currentPkgs, []byte{','}))
121		n += int64(nn)
122		if err != nil {
123			return n, err
124		}
125
126		nn, err = w.Write([]byte{'\n'})
127		n += int64(nn)
128	}
129
130	return n, err
131}
132