1package deb 2 3import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "log" 8 "sort" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/aptly-dev/aptly/database" 14 "github.com/aptly-dev/aptly/utils" 15 "github.com/pborman/uuid" 16 "github.com/ugorji/go/codec" 17) 18 19// Snapshot is immutable state of repository: list of packages 20type Snapshot struct { 21 // Persisten internal ID 22 UUID string `json:"-"` 23 // Human-readable name 24 Name string 25 // Date of creation 26 CreatedAt time.Time 27 28 // Source: kind + ID 29 SourceKind string `json:"-"` 30 SourceIDs []string `json:"-"` 31 // Description of how snapshot was created 32 Description string 33 34 Origin string 35 NotAutomatic string 36 ButAutomaticUpgrades string 37 38 packageRefs *PackageRefList 39} 40 41// NewSnapshotFromRepository creates snapshot from current state of repository 42func NewSnapshotFromRepository(name string, repo *RemoteRepo) (*Snapshot, error) { 43 if repo.packageRefs == nil { 44 return nil, errors.New("mirror not updated") 45 } 46 47 return &Snapshot{ 48 UUID: uuid.New(), 49 Name: name, 50 CreatedAt: time.Now(), 51 SourceKind: SourceRemoteRepo, 52 SourceIDs: []string{repo.UUID}, 53 Description: fmt.Sprintf("Snapshot from mirror %s", repo), 54 Origin: repo.Meta["Origin"], 55 NotAutomatic: repo.Meta["NotAutomatic"], 56 ButAutomaticUpgrades: repo.Meta["ButAutomaticUpgrades"], 57 packageRefs: repo.packageRefs, 58 }, nil 59} 60 61// NewSnapshotFromLocalRepo creates snapshot from current state of local repository 62func NewSnapshotFromLocalRepo(name string, repo *LocalRepo) (*Snapshot, error) { 63 snap := &Snapshot{ 64 UUID: uuid.New(), 65 Name: name, 66 CreatedAt: time.Now(), 67 SourceKind: SourceLocalRepo, 68 SourceIDs: []string{repo.UUID}, 69 Description: fmt.Sprintf("Snapshot from local repo %s", repo), 70 packageRefs: repo.packageRefs, 71 } 72 73 if snap.packageRefs == nil { 74 snap.packageRefs = NewPackageRefList() 75 } 76 77 return snap, nil 78} 79 80// NewSnapshotFromPackageList creates snapshot from PackageList 81func NewSnapshotFromPackageList(name string, sources []*Snapshot, list *PackageList, description string) *Snapshot { 82 return NewSnapshotFromRefList(name, sources, NewPackageRefListFromPackageList(list), description) 83} 84 85// NewSnapshotFromRefList creates snapshot from PackageRefList 86func NewSnapshotFromRefList(name string, sources []*Snapshot, list *PackageRefList, description string) *Snapshot { 87 sourceUUIDs := make([]string, len(sources)) 88 for i := range sources { 89 sourceUUIDs[i] = sources[i].UUID 90 } 91 92 return &Snapshot{ 93 UUID: uuid.New(), 94 Name: name, 95 CreatedAt: time.Now(), 96 SourceKind: "snapshot", 97 SourceIDs: sourceUUIDs, 98 Description: description, 99 packageRefs: list, 100 } 101} 102 103// String returns string representation of snapshot 104func (s *Snapshot) String() string { 105 return fmt.Sprintf("[%s]: %s", s.Name, s.Description) 106} 107 108// NumPackages returns number of packages in snapshot 109func (s *Snapshot) NumPackages() int { 110 return s.packageRefs.Len() 111} 112 113// RefList returns list of package refs in snapshot 114func (s *Snapshot) RefList() *PackageRefList { 115 return s.packageRefs 116} 117 118// Key is a unique id in DB 119func (s *Snapshot) Key() []byte { 120 return []byte("S" + s.UUID) 121} 122 123// RefKey is a unique id for package reference list 124func (s *Snapshot) RefKey() []byte { 125 return []byte("E" + s.UUID) 126} 127 128// Encode does msgpack encoding of Snapshot 129func (s *Snapshot) Encode() []byte { 130 var buf bytes.Buffer 131 132 encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{}) 133 encoder.Encode(s) 134 135 return buf.Bytes() 136} 137 138// Decode decodes msgpack representation into Snapshot 139func (s *Snapshot) Decode(input []byte) error { 140 decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{}) 141 err := decoder.Decode(s) 142 if err != nil { 143 if strings.HasPrefix(err.Error(), "codec.decoder: readContainerLen: Unrecognized descriptor byte: hex: 80") { 144 // probably it is broken DB from go < 1.2, try decoding w/o time.Time 145 var snapshot11 struct { 146 UUID string 147 Name string 148 CreatedAt []byte 149 150 SourceKind string 151 SourceIDs []string 152 Description string 153 } 154 155 decoder = codec.NewDecoderBytes(input, &codec.MsgpackHandle{}) 156 err2 := decoder.Decode(&snapshot11) 157 if err2 != nil { 158 return err 159 } 160 161 s.UUID = snapshot11.UUID 162 s.Name = snapshot11.Name 163 s.SourceKind = snapshot11.SourceKind 164 s.SourceIDs = snapshot11.SourceIDs 165 s.Description = snapshot11.Description 166 } else { 167 return err 168 } 169 } 170 return nil 171} 172 173// SnapshotCollection does listing, updating/adding/deleting of Snapshots 174type SnapshotCollection struct { 175 *sync.RWMutex 176 db database.Storage 177 cache map[string]*Snapshot 178} 179 180// NewSnapshotCollection loads Snapshots from DB and makes up collection 181func NewSnapshotCollection(db database.Storage) *SnapshotCollection { 182 return &SnapshotCollection{ 183 RWMutex: &sync.RWMutex{}, 184 db: db, 185 cache: map[string]*Snapshot{}, 186 } 187} 188 189// Add appends new repo to collection and saves it 190func (collection *SnapshotCollection) Add(snapshot *Snapshot) error { 191 _, err := collection.ByName(snapshot.Name) 192 if err == nil { 193 return fmt.Errorf("snapshot with name %s already exists", snapshot.Name) 194 } 195 196 err = collection.Update(snapshot) 197 if err != nil { 198 return err 199 } 200 201 collection.cache[snapshot.UUID] = snapshot 202 return nil 203} 204 205// Update stores updated information about snapshot in DB 206func (collection *SnapshotCollection) Update(snapshot *Snapshot) error { 207 err := collection.db.Put(snapshot.Key(), snapshot.Encode()) 208 if err != nil { 209 return err 210 } 211 if snapshot.packageRefs != nil { 212 return collection.db.Put(snapshot.RefKey(), snapshot.packageRefs.Encode()) 213 } 214 return nil 215} 216 217// LoadComplete loads additional information about snapshot 218func (collection *SnapshotCollection) LoadComplete(snapshot *Snapshot) error { 219 encoded, err := collection.db.Get(snapshot.RefKey()) 220 if err != nil { 221 return err 222 } 223 224 snapshot.packageRefs = &PackageRefList{} 225 return snapshot.packageRefs.Decode(encoded) 226} 227 228func (collection *SnapshotCollection) search(filter func(*Snapshot) bool, unique bool) []*Snapshot { 229 result := []*Snapshot(nil) 230 for _, s := range collection.cache { 231 if filter(s) { 232 result = append(result, s) 233 } 234 } 235 236 if unique && len(result) > 0 { 237 return result 238 } 239 240 collection.db.ProcessByPrefix([]byte("S"), func(key, blob []byte) error { 241 s := &Snapshot{} 242 if err := s.Decode(blob); err != nil { 243 log.Printf("Error decoding snapshot: %s\n", err) 244 return nil 245 } 246 247 if filter(s) { 248 if _, exists := collection.cache[s.UUID]; !exists { 249 collection.cache[s.UUID] = s 250 result = append(result, s) 251 if unique { 252 return errors.New("abort") 253 } 254 } 255 } 256 257 return nil 258 }) 259 260 return result 261} 262 263// ByName looks up snapshot by name 264func (collection *SnapshotCollection) ByName(name string) (*Snapshot, error) { 265 result := collection.search(func(s *Snapshot) bool { return s.Name == name }, true) 266 if len(result) > 0 { 267 return result[0], nil 268 } 269 270 return nil, fmt.Errorf("snapshot with name %s not found", name) 271} 272 273// ByUUID looks up snapshot by UUID 274func (collection *SnapshotCollection) ByUUID(uuid string) (*Snapshot, error) { 275 if s, ok := collection.cache[uuid]; ok { 276 return s, nil 277 } 278 279 key := (&Snapshot{UUID: uuid}).Key() 280 281 value, err := collection.db.Get(key) 282 if err == database.ErrNotFound { 283 return nil, fmt.Errorf("snapshot with uuid %s not found", uuid) 284 } 285 if err != nil { 286 return nil, err 287 } 288 289 s := &Snapshot{} 290 err = s.Decode(value) 291 292 if err == nil { 293 collection.cache[s.UUID] = s 294 } 295 296 return s, err 297} 298 299// ByRemoteRepoSource looks up snapshots that have specified RemoteRepo as a source 300func (collection *SnapshotCollection) ByRemoteRepoSource(repo *RemoteRepo) []*Snapshot { 301 return collection.search(func(s *Snapshot) bool { 302 return s.SourceKind == SourceRemoteRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID) 303 }, false) 304} 305 306// ByLocalRepoSource looks up snapshots that have specified LocalRepo as a source 307func (collection *SnapshotCollection) ByLocalRepoSource(repo *LocalRepo) []*Snapshot { 308 return collection.search(func(s *Snapshot) bool { 309 return s.SourceKind == SourceLocalRepo && utils.StrSliceHasItem(s.SourceIDs, repo.UUID) 310 }, false) 311} 312 313// BySnapshotSource looks up snapshots that have specified snapshot as a source 314func (collection *SnapshotCollection) BySnapshotSource(snapshot *Snapshot) []*Snapshot { 315 return collection.search(func(s *Snapshot) bool { 316 return s.SourceKind == "snapshot" && utils.StrSliceHasItem(s.SourceIDs, snapshot.UUID) 317 }, false) 318} 319 320// ForEach runs method for each snapshot 321func (collection *SnapshotCollection) ForEach(handler func(*Snapshot) error) error { 322 return collection.db.ProcessByPrefix([]byte("S"), func(key, blob []byte) error { 323 s := &Snapshot{} 324 if err := s.Decode(blob); err != nil { 325 log.Printf("Error decoding snapshot: %s\n", err) 326 return nil 327 } 328 329 return handler(s) 330 }) 331} 332 333// ForEachSorted runs method for each snapshot following some sort order 334func (collection *SnapshotCollection) ForEachSorted(sortMethod string, handler func(*Snapshot) error) error { 335 blobs := collection.db.FetchByPrefix([]byte("S")) 336 list := make([]*Snapshot, 0, len(blobs)) 337 338 for _, blob := range blobs { 339 s := &Snapshot{} 340 if err := s.Decode(blob); err != nil { 341 log.Printf("Error decoding snapshot: %s\n", err) 342 } else { 343 list = append(list, s) 344 } 345 } 346 347 sorter, err := newSnapshotSorter(sortMethod, list) 348 if err != nil { 349 return err 350 } 351 352 for _, s := range sorter.list { 353 err = handler(s) 354 if err != nil { 355 return err 356 } 357 } 358 359 return nil 360} 361 362// Len returns number of snapshots in collection 363// ForEach runs method for each snapshot 364func (collection *SnapshotCollection) Len() int { 365 return len(collection.db.KeysByPrefix([]byte("S"))) 366} 367 368// Drop removes snapshot from collection 369func (collection *SnapshotCollection) Drop(snapshot *Snapshot) error { 370 if _, err := collection.db.Get(snapshot.Key()); err == database.ErrNotFound { 371 panic("snapshot not found!") 372 } 373 374 delete(collection.cache, snapshot.UUID) 375 376 err := collection.db.Delete(snapshot.Key()) 377 if err != nil { 378 return err 379 } 380 381 return collection.db.Delete(snapshot.RefKey()) 382} 383 384// Snapshot sorting methods 385const ( 386 SortName = iota 387 SortTime 388) 389 390type snapshotSorter struct { 391 list []*Snapshot 392 sortMethod int 393} 394 395func newSnapshotSorter(sortMethod string, list []*Snapshot) (*snapshotSorter, error) { 396 s := &snapshotSorter{list: list} 397 398 switch sortMethod { 399 case "time", "Time": 400 s.sortMethod = SortTime 401 case "name", "Name": 402 s.sortMethod = SortName 403 default: 404 return nil, fmt.Errorf("sorting method \"%s\" unknown", sortMethod) 405 } 406 407 sort.Sort(s) 408 409 return s, nil 410} 411 412func (s *snapshotSorter) Swap(i, j int) { 413 s.list[i], s.list[j] = s.list[j], s.list[i] 414} 415 416func (s *snapshotSorter) Less(i, j int) bool { 417 switch s.sortMethod { 418 case SortName: 419 return s.list[i].Name < s.list[j].Name 420 case SortTime: 421 return s.list[i].CreatedAt.Before(s.list[j].CreatedAt) 422 } 423 panic("unknown sort method") 424} 425 426func (s *snapshotSorter) Len() int { 427 return len(s.list) 428} 429