|  | package main | 
|  |  | 
|  | import ( | 
|  | "strings" | 
|  | "sync" | 
|  |  | 
|  | "github.com/FiloSottile/gvt/gbvendor" | 
|  | ) | 
|  |  | 
|  | type cacheKey struct { | 
|  | url, repoType         string | 
|  | branch, tag, revision string | 
|  | } | 
|  |  | 
|  | type cacheEntry struct { | 
|  | wg  sync.WaitGroup | 
|  | v   vendor.WorkingCopy | 
|  | err error | 
|  | } | 
|  |  | 
|  | // Downloader acts as a cache for downloaded repositories | 
|  | type Downloader struct { | 
|  | wcsMu sync.Mutex | 
|  | wcs   map[cacheKey]*cacheEntry | 
|  |  | 
|  | reposMu sync.RWMutex | 
|  | repos   map[string]vendor.RemoteRepo | 
|  | reposI  map[string]vendor.RemoteRepo | 
|  | } | 
|  |  | 
|  | var GlobalDownloader = Downloader{} | 
|  |  | 
|  | func init() { | 
|  | GlobalDownloader.wcs = make(map[cacheKey]*cacheEntry) | 
|  | GlobalDownloader.repos = make(map[string]vendor.RemoteRepo) | 
|  | GlobalDownloader.reposI = make(map[string]vendor.RemoteRepo) | 
|  | } | 
|  |  | 
|  | // Get returns a cached WorkingCopy, or runs RemoteRepo.Checkout | 
|  | func (d *Downloader) Get(repo vendor.RemoteRepo, branch, tag, revision string) (vendor.WorkingCopy, error) { | 
|  | key := cacheKey{ | 
|  | url: repo.URL(), repoType: repo.Type(), | 
|  | branch: branch, tag: tag, revision: revision, | 
|  | } | 
|  | d.wcsMu.Lock() | 
|  | if entry, ok := d.wcs[key]; ok { | 
|  | d.wcsMu.Unlock() | 
|  | entry.wg.Wait() | 
|  | return entry.v, entry.err | 
|  | } | 
|  |  | 
|  | entry := &cacheEntry{} | 
|  | entry.wg.Add(1) | 
|  | d.wcs[key] = entry | 
|  | d.wcsMu.Unlock() | 
|  |  | 
|  | entry.v, entry.err = repo.Checkout(branch, tag, revision) | 
|  | entry.wg.Done() | 
|  | return entry.v, entry.err | 
|  | } | 
|  |  | 
|  | func (d *Downloader) Flush() error { | 
|  | d.wcsMu.Lock() | 
|  | defer d.wcsMu.Unlock() | 
|  |  | 
|  | for _, entry := range d.wcs { | 
|  | entry.wg.Wait() | 
|  | if entry.err != nil { | 
|  | continue | 
|  | } | 
|  | if err := entry.v.Destroy(); err != nil { | 
|  | return err | 
|  | } | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // DeduceRemoteRepo is a cached version of vendor.DeduceRemoteRepo | 
|  | func (d *Downloader) DeduceRemoteRepo(path string, insecure bool) (vendor.RemoteRepo, string, error) { | 
|  | cache := d.repos | 
|  | if insecure { | 
|  | cache = d.reposI | 
|  | } | 
|  |  | 
|  | d.reposMu.RLock() | 
|  | for p, repo := range cache { | 
|  | if path == p || strings.HasPrefix(path, p+"/") { | 
|  | d.reposMu.RUnlock() | 
|  | extra := strings.Trim(strings.TrimPrefix(path, p), "/") | 
|  | return repo, extra, nil | 
|  | } | 
|  | } | 
|  | d.reposMu.RUnlock() | 
|  |  | 
|  | repo, extra, err := vendor.DeduceRemoteRepo(path, insecure) | 
|  | if err != nil { | 
|  | return repo, extra, err | 
|  | } | 
|  |  | 
|  | if !strings.HasSuffix(path, extra) { | 
|  | // Shouldn't happen, but in case just bypass the cache | 
|  | return repo, extra, err | 
|  | } | 
|  | basePath := strings.Trim(strings.TrimSuffix(path, extra), "/") | 
|  | d.reposMu.Lock() | 
|  | cache[basePath] = repo | 
|  | d.reposMu.Unlock() | 
|  |  | 
|  | return repo, extra, err | 
|  | } |