1package deb 2 3import ( 4 "bytes" 5 gocontext "context" 6 "errors" 7 "fmt" 8 "log" 9 "net/url" 10 "os" 11 "path" 12 "sort" 13 "strconv" 14 "strings" 15 "sync" 16 "syscall" 17 "time" 18 19 "github.com/aptly-dev/aptly/aptly" 20 "github.com/aptly-dev/aptly/database" 21 "github.com/aptly-dev/aptly/http" 22 "github.com/aptly-dev/aptly/pgp" 23 "github.com/aptly-dev/aptly/utils" 24 "github.com/pborman/uuid" 25 "github.com/ugorji/go/codec" 26) 27 28// RemoteRepo statuses 29const ( 30 MirrorIdle = iota 31 MirrorUpdating 32) 33 34// RemoteRepo represents remote (fetchable) Debian repository. 35// 36// Repostitory could be filtered when fetching by components, architectures 37type RemoteRepo struct { 38 // Permanent internal ID 39 UUID string 40 // User-assigned name 41 Name string 42 // Root of Debian archive, URL 43 ArchiveRoot string 44 // Distribution name, e.g. squeeze 45 Distribution string 46 // List of components to fetch, if empty, then fetch all components 47 Components []string 48 // List of architectures to fetch, if empty, then fetch all architectures 49 Architectures []string 50 // Meta-information about repository 51 Meta Stanza 52 // Last update date 53 LastDownloadDate time.Time 54 // Checksums for release files 55 ReleaseFiles map[string]utils.ChecksumInfo 56 // Filter for packages 57 Filter string 58 // Status marks state of repository (being updated, no action) 59 Status int 60 // WorkerPID is PID of the process modifying the mirror (if any) 61 WorkerPID int 62 // FilterWithDeps to include dependencies from filter query 63 FilterWithDeps bool 64 // SkipComponentCheck skips component list verification 65 SkipComponentCheck bool 66 // SkipArchitectureCheck skips architecture list verification 67 SkipArchitectureCheck bool 68 // Should we download sources? 69 DownloadSources bool 70 // Should we download .udebs? 71 DownloadUdebs bool 72 // Should we download installer files? 73 DownloadInstaller bool 74 // "Snapshot" of current list of packages 75 packageRefs *PackageRefList 76 // Parsed archived root 77 archiveRootURL *url.URL 78 // Current list of packages (filled while updating mirror) 79 packageList *PackageList 80} 81 82// NewRemoteRepo creates new instance of Debian remote repository with specified params 83func NewRemoteRepo(name string, archiveRoot string, distribution string, components []string, 84 architectures []string, downloadSources bool, downloadUdebs bool, downloadInstaller bool) (*RemoteRepo, error) { 85 result := &RemoteRepo{ 86 UUID: uuid.New(), 87 Name: name, 88 ArchiveRoot: archiveRoot, 89 Distribution: distribution, 90 Components: components, 91 Architectures: architectures, 92 DownloadSources: downloadSources, 93 DownloadUdebs: downloadUdebs, 94 DownloadInstaller: downloadInstaller, 95 } 96 97 err := result.prepare() 98 if err != nil { 99 return nil, err 100 } 101 102 if strings.HasSuffix(result.Distribution, "/") || strings.HasPrefix(result.Distribution, ".") { 103 // flat repo 104 if !strings.HasPrefix(result.Distribution, ".") { 105 result.Distribution = "./" + result.Distribution 106 } 107 result.Architectures = nil 108 if len(result.Components) > 0 { 109 return nil, fmt.Errorf("components aren't supported for flat repos") 110 } 111 if result.DownloadUdebs { 112 return nil, fmt.Errorf("debian-installer udebs aren't supported for flat repos") 113 } 114 result.Components = nil 115 } 116 117 return result, nil 118} 119 120// SetArchiveRoot of remote repo 121func (repo *RemoteRepo) SetArchiveRoot(archiveRoot string) { 122 repo.ArchiveRoot = archiveRoot 123 repo.prepare() 124} 125 126func (repo *RemoteRepo) prepare() error { 127 var err error 128 129 // Add final / to URL 130 if !strings.HasSuffix(repo.ArchiveRoot, "/") { 131 repo.ArchiveRoot = repo.ArchiveRoot + "/" 132 } 133 134 repo.archiveRootURL, err = url.Parse(repo.ArchiveRoot) 135 return err 136} 137 138// String interface 139func (repo *RemoteRepo) String() string { 140 srcFlag := "" 141 if repo.DownloadSources { 142 srcFlag += " [src]" 143 } 144 if repo.DownloadUdebs { 145 srcFlag += " [udeb]" 146 } 147 if repo.DownloadInstaller { 148 srcFlag += " [installer]" 149 } 150 distribution := repo.Distribution 151 if distribution == "" { 152 distribution = "./" 153 } 154 return fmt.Sprintf("[%s]: %s %s%s", repo.Name, repo.ArchiveRoot, distribution, srcFlag) 155} 156 157// IsFlat determines if repository is flat 158func (repo *RemoteRepo) IsFlat() bool { 159 // aptly < 0.5.1 had Distribution = "" for flat repos 160 // aptly >= 0.5.1 had Distribution = "./[path]/" for flat repos 161 return repo.Distribution == "" || (strings.HasPrefix(repo.Distribution, ".") && strings.HasSuffix(repo.Distribution, "/")) 162} 163 164// NumPackages return number of packages retrieved from remote repo 165func (repo *RemoteRepo) NumPackages() int { 166 if repo.packageRefs == nil { 167 return 0 168 } 169 return repo.packageRefs.Len() 170} 171 172// RefList returns package list for repo 173func (repo *RemoteRepo) RefList() *PackageRefList { 174 return repo.packageRefs 175} 176 177// MarkAsUpdating puts current PID and sets status to updating 178func (repo *RemoteRepo) MarkAsUpdating() { 179 repo.Status = MirrorUpdating 180 repo.WorkerPID = os.Getpid() 181} 182 183// MarkAsIdle clears updating flag 184func (repo *RemoteRepo) MarkAsIdle() { 185 repo.Status = MirrorIdle 186 repo.WorkerPID = 0 187} 188 189// CheckLock returns error if mirror is being updated by another process 190func (repo *RemoteRepo) CheckLock() error { 191 if repo.Status == MirrorIdle || repo.WorkerPID == 0 { 192 return nil 193 } 194 195 p, err := os.FindProcess(repo.WorkerPID) 196 if err != nil { 197 return nil 198 } 199 200 err = p.Signal(syscall.Signal(0)) 201 if err == nil { 202 return fmt.Errorf("mirror is locked by update operation, PID %d", repo.WorkerPID) 203 } 204 205 return nil 206} 207 208// IndexesRootURL builds URL for various indexes 209func (repo *RemoteRepo) IndexesRootURL() *url.URL { 210 var path *url.URL 211 212 if !repo.IsFlat() { 213 path = &url.URL{Path: fmt.Sprintf("dists/%s/", repo.Distribution)} 214 } else { 215 path = &url.URL{Path: repo.Distribution} 216 } 217 218 return repo.archiveRootURL.ResolveReference(path) 219} 220 221// ReleaseURL returns URL to Release* files in repo root 222func (repo *RemoteRepo) ReleaseURL(name string) *url.URL { 223 return repo.IndexesRootURL().ResolveReference(&url.URL{Path: name}) 224} 225 226// FlatBinaryPath returns path to Packages files for flat repo 227func (repo *RemoteRepo) FlatBinaryPath() string { 228 return "Packages" 229} 230 231// FlatSourcesPath returns path to Sources files for flat repo 232func (repo *RemoteRepo) FlatSourcesPath() string { 233 return "Sources" 234} 235 236// BinaryPath returns path to Packages files for given component and 237// architecture 238func (repo *RemoteRepo) BinaryPath(component string, architecture string) string { 239 return fmt.Sprintf("%s/binary-%s/Packages", component, architecture) 240} 241 242// SourcesPath returns path to Sources files for given component 243func (repo *RemoteRepo) SourcesPath(component string) string { 244 return fmt.Sprintf("%s/source/Sources", component) 245} 246 247// UdebPath returns path of Packages files for given component and 248// architecture 249func (repo *RemoteRepo) UdebPath(component string, architecture string) string { 250 return fmt.Sprintf("%s/debian-installer/binary-%s/Packages", component, architecture) 251} 252 253// InstallerPath returns path of Packages files for given component and 254// architecture 255func (repo *RemoteRepo) InstallerPath(component string, architecture string) string { 256 return fmt.Sprintf("%s/installer-%s/current/images/SHA256SUMS", component, architecture) 257} 258 259// PackageURL returns URL of package file relative to repository root 260// architecture 261func (repo *RemoteRepo) PackageURL(filename string) *url.URL { 262 path := &url.URL{Path: filename} 263 return repo.archiveRootURL.ResolveReference(path) 264} 265 266// Fetch updates information about repository 267func (repo *RemoteRepo) Fetch(d aptly.Downloader, verifier pgp.Verifier) error { 268 var ( 269 release, inrelease, releasesig *os.File 270 err error 271 ) 272 273 if verifier == nil { 274 // 0. Just download release file to temporary URL 275 release, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("Release").String()) 276 if err != nil { 277 return err 278 } 279 } else { 280 // 1. try InRelease file 281 inrelease, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("InRelease").String()) 282 if err != nil { 283 goto splitsignature 284 } 285 defer inrelease.Close() 286 287 _, err = verifier.VerifyClearsigned(inrelease, true) 288 if err != nil { 289 goto splitsignature 290 } 291 292 inrelease.Seek(0, 0) 293 294 release, err = verifier.ExtractClearsigned(inrelease) 295 if err != nil { 296 goto splitsignature 297 } 298 299 goto ok 300 301 splitsignature: 302 // 2. try Release + Release.gpg 303 release, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("Release").String()) 304 if err != nil { 305 return err 306 } 307 308 releasesig, err = http.DownloadTemp(gocontext.TODO(), d, repo.ReleaseURL("Release.gpg").String()) 309 if err != nil { 310 return err 311 } 312 313 err = verifier.VerifyDetachedSignature(releasesig, release, true) 314 if err != nil { 315 return err 316 } 317 318 _, err = release.Seek(0, 0) 319 if err != nil { 320 return err 321 } 322 } 323ok: 324 325 defer release.Close() 326 327 sreader := NewControlFileReader(release, true, false) 328 stanza, err := sreader.ReadStanza() 329 if err != nil { 330 return err 331 } 332 333 if !repo.IsFlat() { 334 architectures := strings.Split(stanza["Architectures"], " ") 335 sort.Strings(architectures) 336 // "source" architecture is never present, despite Release file claims 337 architectures = utils.StrSlicesSubstract(architectures, []string{ArchitectureSource}) 338 if len(repo.Architectures) == 0 { 339 repo.Architectures = architectures 340 } else if !repo.SkipArchitectureCheck { 341 err = utils.StringsIsSubset(repo.Architectures, architectures, 342 fmt.Sprintf("architecture %%s not available in repo %s, use -force-architectures to override", repo)) 343 if err != nil { 344 return err 345 } 346 } 347 348 components := strings.Split(stanza["Components"], " ") 349 if strings.Contains(repo.Distribution, "/") { 350 distributionLast := path.Base(repo.Distribution) + "/" 351 for i := range components { 352 components[i] = strings.TrimPrefix(components[i], distributionLast) 353 } 354 } 355 if len(repo.Components) == 0 { 356 repo.Components = components 357 } else if !repo.SkipComponentCheck { 358 err = utils.StringsIsSubset(repo.Components, components, 359 fmt.Sprintf("component %%s not available in repo %s, use -force-components to override", repo)) 360 if err != nil { 361 return err 362 } 363 } 364 } 365 366 repo.ReleaseFiles = make(map[string]utils.ChecksumInfo) 367 368 parseSums := func(field string, setter func(sum *utils.ChecksumInfo, data string)) error { 369 for _, line := range strings.Split(stanza[field], "\n") { 370 line = strings.TrimSpace(line) 371 if line == "" { 372 continue 373 } 374 parts := strings.Fields(line) 375 376 if len(parts) != 3 { 377 return fmt.Errorf("unparseable hash sum line: %#v", line) 378 } 379 380 var size int64 381 size, err = strconv.ParseInt(parts[1], 10, 64) 382 if err != nil { 383 return fmt.Errorf("unable to parse size: %s", err) 384 } 385 386 sum := repo.ReleaseFiles[parts[2]] 387 388 sum.Size = size 389 setter(&sum, parts[0]) 390 391 repo.ReleaseFiles[parts[2]] = sum 392 } 393 394 delete(stanza, field) 395 396 return nil 397 } 398 399 err = parseSums("MD5Sum", func(sum *utils.ChecksumInfo, data string) { sum.MD5 = data }) 400 if err != nil { 401 return err 402 } 403 404 err = parseSums("SHA1", func(sum *utils.ChecksumInfo, data string) { sum.SHA1 = data }) 405 if err != nil { 406 return err 407 } 408 409 err = parseSums("SHA256", func(sum *utils.ChecksumInfo, data string) { sum.SHA256 = data }) 410 if err != nil { 411 return err 412 } 413 414 err = parseSums("SHA512", func(sum *utils.ChecksumInfo, data string) { sum.SHA512 = data }) 415 if err != nil { 416 return err 417 } 418 419 repo.Meta = stanza 420 421 return nil 422} 423 424// DownloadPackageIndexes downloads & parses package index files 425func (repo *RemoteRepo) DownloadPackageIndexes(progress aptly.Progress, d aptly.Downloader, verifier pgp.Verifier, collectionFactory *CollectionFactory, 426 ignoreMismatch bool, maxTries int) error { 427 if repo.packageList != nil { 428 panic("packageList != nil") 429 } 430 repo.packageList = NewPackageList() 431 432 // Download and parse all Packages & Source files 433 packagesPaths := [][]string{} 434 435 if repo.IsFlat() { 436 packagesPaths = append(packagesPaths, []string{repo.FlatBinaryPath(), PackageTypeBinary, "", ""}) 437 if repo.DownloadSources { 438 packagesPaths = append(packagesPaths, []string{repo.FlatSourcesPath(), PackageTypeSource, "", ""}) 439 } 440 } else { 441 for _, component := range repo.Components { 442 for _, architecture := range repo.Architectures { 443 packagesPaths = append(packagesPaths, []string{repo.BinaryPath(component, architecture), PackageTypeBinary, component, architecture}) 444 if repo.DownloadUdebs { 445 packagesPaths = append(packagesPaths, []string{repo.UdebPath(component, architecture), PackageTypeUdeb, component, architecture}) 446 } 447 if repo.DownloadInstaller { 448 packagesPaths = append(packagesPaths, []string{repo.InstallerPath(component, architecture), PackageTypeInstaller, component, architecture}) 449 } 450 } 451 if repo.DownloadSources { 452 packagesPaths = append(packagesPaths, []string{repo.SourcesPath(component), PackageTypeSource, component, "source"}) 453 } 454 } 455 } 456 457 for _, info := range packagesPaths { 458 path, kind, component, architecture := info[0], info[1], info[2], info[3] 459 packagesReader, packagesFile, err := http.DownloadTryCompression(gocontext.TODO(), d, repo.IndexesRootURL(), path, repo.ReleaseFiles, ignoreMismatch, maxTries) 460 461 isInstaller := kind == PackageTypeInstaller 462 if err != nil { 463 if _, ok := err.(*http.NoCandidateFoundError); isInstaller && ok { 464 // checking if gpg file is only needed when checksums matches are required. 465 // otherwise there actually has been no candidate found and we can continue 466 if ignoreMismatch { 467 continue 468 } 469 470 // some repos do not have installer hashsum file listed in release file but provide a separate gpg file 471 hashsumPath := repo.IndexesRootURL().ResolveReference(&url.URL{Path: path}).String() 472 packagesFile, err = http.DownloadTemp(gocontext.TODO(), d, hashsumPath) 473 if err != nil { 474 if herr, ok := err.(*http.Error); ok && (herr.Code == 404 || herr.Code == 403) { 475 // installer files are not available in all components and architectures 476 // so ignore it if not found 477 continue 478 } 479 480 return err 481 } 482 483 if verifier != nil { 484 hashsumGpgPath := repo.IndexesRootURL().ResolveReference(&url.URL{Path: path + ".gpg"}).String() 485 var filesig *os.File 486 filesig, err = http.DownloadTemp(gocontext.TODO(), d, hashsumGpgPath) 487 if err != nil { 488 return err 489 } 490 491 err = verifier.VerifyDetachedSignature(filesig, packagesFile, false) 492 if err != nil { 493 return err 494 } 495 496 _, err = packagesFile.Seek(0, 0) 497 } 498 499 packagesReader = packagesFile 500 } 501 502 if err != nil { 503 return err 504 } 505 } 506 defer packagesFile.Close() 507 508 stat, _ := packagesFile.Stat() 509 progress.InitBar(stat.Size(), true) 510 511 sreader := NewControlFileReader(packagesReader, false, isInstaller) 512 513 for { 514 stanza, err := sreader.ReadStanza() 515 if err != nil { 516 return err 517 } 518 if stanza == nil { 519 break 520 } 521 522 off, _ := packagesFile.Seek(0, 1) 523 progress.SetBar(int(off)) 524 525 var p *Package 526 527 if kind == PackageTypeBinary { 528 p = NewPackageFromControlFile(stanza) 529 } else if kind == PackageTypeUdeb { 530 p = NewUdebPackageFromControlFile(stanza) 531 } else if kind == PackageTypeSource { 532 p, err = NewSourcePackageFromControlFile(stanza) 533 if err != nil { 534 return err 535 } 536 } else if kind == PackageTypeInstaller { 537 p, err = NewInstallerPackageFromControlFile(stanza, repo, component, architecture, d) 538 if err != nil { 539 return err 540 } 541 } 542 err = repo.packageList.Add(p) 543 if _, ok := err.(*PackageConflictError); ok { 544 progress.ColoredPrintf("@y[!]@| @!skipping package %s: duplicate in packages index@|", p) 545 } else if err != nil { 546 return err 547 } 548 } 549 550 progress.ShutdownBar() 551 } 552 553 return nil 554} 555 556// ApplyFilter applies filtering to already built PackageList 557func (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQuery, progress aptly.Progress) (oldLen, newLen int, err error) { 558 repo.packageList.PrepareIndex() 559 560 emptyList := NewPackageList() 561 emptyList.PrepareIndex() 562 563 oldLen = repo.packageList.Len() 564 repo.packageList, err = repo.packageList.FilterWithProgress([]PackageQuery{filterQuery}, repo.FilterWithDeps, emptyList, dependencyOptions, repo.Architectures, progress) 565 if repo.packageList != nil { 566 newLen = repo.packageList.Len() 567 } 568 return 569} 570 571// BuildDownloadQueue builds queue, discards current PackageList 572func (repo *RemoteRepo) BuildDownloadQueue(packagePool aptly.PackagePool, packageCollection *PackageCollection, checksumStorage aptly.ChecksumStorage, skipExistingPackages bool) (queue []PackageDownloadTask, downloadSize int64, err error) { 573 queue = make([]PackageDownloadTask, 0, repo.packageList.Len()) 574 seen := make(map[string]int, repo.packageList.Len()) 575 576 err = repo.packageList.ForEach(func(p *Package) error { 577 if repo.packageRefs != nil && skipExistingPackages { 578 if repo.packageRefs.Has(p) { 579 // skip this package, but load checksums/files from package in DB 580 var prevP *Package 581 prevP, err = packageCollection.ByKey(p.Key("")) 582 if err != nil { 583 return err 584 } 585 586 p.UpdateFiles(prevP.Files()) 587 return nil 588 } 589 } 590 591 list, err2 := p.DownloadList(packagePool, checksumStorage) 592 if err2 != nil { 593 return err2 594 } 595 596 for _, task := range list { 597 key := task.File.DownloadURL() 598 idx, found := seen[key] 599 if !found { 600 queue = append(queue, task) 601 downloadSize += task.File.Checksums.Size 602 seen[key] = len(queue) - 1 603 } else { 604 // hook up the task to duplicate entry already on the list 605 queue[idx].Additional = append(queue[idx].Additional, task) 606 } 607 } 608 609 return nil 610 }) 611 if err != nil { 612 return 613 } 614 615 return 616} 617 618// FinalizeDownload swaps for final value of package refs 619func (repo *RemoteRepo) FinalizeDownload(collectionFactory *CollectionFactory, progress aptly.Progress) error { 620 repo.LastDownloadDate = time.Now() 621 622 if progress != nil { 623 progress.InitBar(int64(repo.packageList.Len()), true) 624 } 625 626 var i int 627 628 // update all the packages in collection 629 err := repo.packageList.ForEach(func(p *Package) error { 630 i++ 631 if progress != nil { 632 progress.SetBar(i) 633 } 634 // download process might have updated checksums 635 p.UpdateFiles(p.Files()) 636 return collectionFactory.PackageCollection().Update(p) 637 }) 638 639 repo.packageRefs = NewPackageRefListFromPackageList(repo.packageList) 640 641 if progress != nil { 642 progress.ShutdownBar() 643 } 644 645 repo.packageList = nil 646 647 return err 648} 649 650// Encode does msgpack encoding of RemoteRepo 651func (repo *RemoteRepo) Encode() []byte { 652 var buf bytes.Buffer 653 654 encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{}) 655 encoder.Encode(repo) 656 657 return buf.Bytes() 658} 659 660// Decode decodes msgpack representation into RemoteRepo 661func (repo *RemoteRepo) Decode(input []byte) error { 662 decoder := codec.NewDecoderBytes(input, &codec.MsgpackHandle{}) 663 err := decoder.Decode(repo) 664 if err != nil { 665 if strings.HasPrefix(err.Error(), "codec.decoder: readContainerLen: Unrecognized descriptor byte: hex: 80") { 666 // probably it is broken DB from go < 1.2, try decoding w/o time.Time 667 var repo11 struct { // nolint: maligned 668 UUID string 669 Name string 670 ArchiveRoot string 671 Distribution string 672 Components []string 673 Architectures []string 674 DownloadSources bool 675 Meta Stanza 676 LastDownloadDate []byte 677 ReleaseFiles map[string]utils.ChecksumInfo 678 Filter string 679 FilterWithDeps bool 680 } 681 682 decoder = codec.NewDecoderBytes(input, &codec.MsgpackHandle{}) 683 err2 := decoder.Decode(&repo11) 684 if err2 != nil { 685 return err 686 } 687 688 repo.UUID = repo11.UUID 689 repo.Name = repo11.Name 690 repo.ArchiveRoot = repo11.ArchiveRoot 691 repo.Distribution = repo11.Distribution 692 repo.Components = repo11.Components 693 repo.Architectures = repo11.Architectures 694 repo.DownloadSources = repo11.DownloadSources 695 repo.Meta = repo11.Meta 696 repo.ReleaseFiles = repo11.ReleaseFiles 697 repo.Filter = repo11.Filter 698 repo.FilterWithDeps = repo11.FilterWithDeps 699 } else { 700 return err 701 } 702 } 703 return repo.prepare() 704} 705 706// Key is a unique id in DB 707func (repo *RemoteRepo) Key() []byte { 708 return []byte("R" + repo.UUID) 709} 710 711// RefKey is a unique id for package reference list 712func (repo *RemoteRepo) RefKey() []byte { 713 return []byte("E" + repo.UUID) 714} 715 716// RemoteRepoCollection does listing, updating/adding/deleting of RemoteRepos 717type RemoteRepoCollection struct { 718 *sync.RWMutex 719 db database.Storage 720 cache map[string]*RemoteRepo 721} 722 723// NewRemoteRepoCollection loads RemoteRepos from DB and makes up collection 724func NewRemoteRepoCollection(db database.Storage) *RemoteRepoCollection { 725 return &RemoteRepoCollection{ 726 RWMutex: &sync.RWMutex{}, 727 db: db, 728 cache: make(map[string]*RemoteRepo), 729 } 730} 731 732func (collection *RemoteRepoCollection) search(filter func(*RemoteRepo) bool, unique bool) []*RemoteRepo { 733 result := []*RemoteRepo(nil) 734 for _, r := range collection.cache { 735 if filter(r) { 736 result = append(result, r) 737 } 738 } 739 740 if unique && len(result) > 0 { 741 return result 742 } 743 744 collection.db.ProcessByPrefix([]byte("R"), func(key, blob []byte) error { 745 r := &RemoteRepo{} 746 if err := r.Decode(blob); err != nil { 747 log.Printf("Error decoding remote repo: %s\n", err) 748 return nil 749 } 750 751 if filter(r) { 752 if _, exists := collection.cache[r.UUID]; !exists { 753 collection.cache[r.UUID] = r 754 result = append(result, r) 755 if unique { 756 return errors.New("abort") 757 } 758 } 759 } 760 761 return nil 762 }) 763 764 return result 765} 766 767// Add appends new repo to collection and saves it 768func (collection *RemoteRepoCollection) Add(repo *RemoteRepo) error { 769 _, err := collection.ByName(repo.Name) 770 771 if err == nil { 772 return fmt.Errorf("mirror with name %s already exists", repo.Name) 773 } 774 775 err = collection.Update(repo) 776 if err != nil { 777 return err 778 } 779 780 collection.cache[repo.UUID] = repo 781 return nil 782} 783 784// Update stores updated information about repo in DB 785func (collection *RemoteRepoCollection) Update(repo *RemoteRepo) error { 786 err := collection.db.Put(repo.Key(), repo.Encode()) 787 if err != nil { 788 return err 789 } 790 if repo.packageRefs != nil { 791 err = collection.db.Put(repo.RefKey(), repo.packageRefs.Encode()) 792 if err != nil { 793 return err 794 } 795 } 796 return nil 797} 798 799// LoadComplete loads additional information for remote repo 800func (collection *RemoteRepoCollection) LoadComplete(repo *RemoteRepo) error { 801 encoded, err := collection.db.Get(repo.RefKey()) 802 if err == database.ErrNotFound { 803 return nil 804 } 805 if err != nil { 806 return err 807 } 808 809 repo.packageRefs = &PackageRefList{} 810 return repo.packageRefs.Decode(encoded) 811} 812 813// ByName looks up repository by name 814func (collection *RemoteRepoCollection) ByName(name string) (*RemoteRepo, error) { 815 result := collection.search(func(r *RemoteRepo) bool { return r.Name == name }, true) 816 if len(result) == 0 { 817 return nil, fmt.Errorf("mirror with name %s not found", name) 818 } 819 820 return result[0], nil 821} 822 823// ByUUID looks up repository by uuid 824func (collection *RemoteRepoCollection) ByUUID(uuid string) (*RemoteRepo, error) { 825 if r, ok := collection.cache[uuid]; ok { 826 return r, nil 827 } 828 829 key := (&RemoteRepo{UUID: uuid}).Key() 830 831 value, err := collection.db.Get(key) 832 if err == database.ErrNotFound { 833 return nil, fmt.Errorf("mirror with uuid %s not found", uuid) 834 } 835 if err != nil { 836 return nil, err 837 } 838 839 r := &RemoteRepo{} 840 err = r.Decode(value) 841 842 if err == nil { 843 collection.cache[r.UUID] = r 844 } 845 846 return r, err 847} 848 849// ForEach runs method for each repository 850func (collection *RemoteRepoCollection) ForEach(handler func(*RemoteRepo) error) error { 851 return collection.db.ProcessByPrefix([]byte("R"), func(key, blob []byte) error { 852 r := &RemoteRepo{} 853 if err := r.Decode(blob); err != nil { 854 log.Printf("Error decoding mirror: %s\n", err) 855 return nil 856 } 857 858 return handler(r) 859 }) 860} 861 862// Len returns number of remote repos 863func (collection *RemoteRepoCollection) Len() int { 864 return len(collection.db.KeysByPrefix([]byte("R"))) 865} 866 867// Drop removes remote repo from collection 868func (collection *RemoteRepoCollection) Drop(repo *RemoteRepo) error { 869 if _, err := collection.db.Get(repo.Key()); err == database.ErrNotFound { 870 panic("repo not found!") 871 } 872 873 delete(collection.cache, repo.UUID) 874 875 err := collection.db.Delete(repo.Key()) 876 if err != nil { 877 return err 878 } 879 880 return collection.db.Delete(repo.RefKey()) 881} 882