| // Package cache provides an interface for interfacing with the Glide local cache | 
 | // | 
 | // Glide has a local cache of metadata and repositories similar to the GOPATH. | 
 | // To store the cache Glide creates a .glide directory with a cache subdirectory. | 
 | // This is usually in the users home directory unless there is no accessible | 
 | // home directory in which case the .glide directory is in the root of the | 
 | // repository. | 
 | // | 
 | // To get the cache location use the `cache.Location()` function. This will | 
 | // return the proper base location in your environment. | 
 | // | 
 | // Within the cache directory there are two subdirectories. They are the src | 
 | // and info directories. The src directory contains version control checkouts | 
 | // of the packages. The info direcory contains metadata. The metadata maps to | 
 | // the RepoInfo struct. Both stores are happed to keys. | 
 | // | 
 | // Using the `cache.Key()` function you can get a key for a repo. Pass in a | 
 | // location such as `https://github.com/foo/bar` or `git@example.com:foo.git` | 
 | // and a key will be returned that can be used for caching operations. | 
 | // | 
 | // Note, the caching is based on repo rather than package. This is important | 
 | // for a couple reasons. | 
 | // | 
 | // 1. Forks or package replacements are supported in Glide. Where a different | 
 | //    repo maps to a package. | 
 | // 2. Permissions enable different access. For example `https://example.com/foo.git` | 
 | //    and `git@example.com:foo.git` may have access to different branches or tags. | 
 | package cache | 
 |  | 
 | import ( | 
 | 	"encoding/json" | 
 | 	"errors" | 
 | 	"io/ioutil" | 
 | 	"net/url" | 
 | 	"os" | 
 | 	"path/filepath" | 
 | 	"regexp" | 
 | 	"strings" | 
 | 	"sync" | 
 | 	"time" | 
 |  | 
 | 	"github.com/Masterminds/glide/msg" | 
 | 	gpath "github.com/Masterminds/glide/path" | 
 | ) | 
 |  | 
 | // Enabled sets if the cache is globally enabled. Defaults to true. | 
 | var Enabled = true | 
 |  | 
 | // ErrCacheDisabled is returned with the cache is disabled. | 
 | var ErrCacheDisabled = errors.New("Cache disabled") | 
 |  | 
 | var isSetup bool | 
 |  | 
 | var setupMutex sync.Mutex | 
 |  | 
 | // Setup creates the cache location. | 
 | func Setup() { | 
 | 	setupMutex.Lock() | 
 | 	defer setupMutex.Unlock() | 
 |  | 
 | 	if isSetup { | 
 | 		return | 
 | 	} | 
 | 	msg.Debug("Setting up the cache directory") | 
 | 	pths := []string{ | 
 | 		"cache", | 
 | 		filepath.Join("cache", "src"), | 
 | 		filepath.Join("cache", "info"), | 
 | 	} | 
 |  | 
 | 	for _, l := range pths { | 
 | 		err := os.MkdirAll(filepath.Join(gpath.Home(), l), 0755) | 
 | 		if err != nil { | 
 | 			msg.Die("Cache directory unavailable: %s", err) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	isSetup = true | 
 | } | 
 |  | 
 | // SetupReset resets if setup has been completed. The next time setup is run | 
 | // it will attempt a full setup. | 
 | func SetupReset() { | 
 | 	isSetup = false | 
 | } | 
 |  | 
 | // Location returns the location of the cache. | 
 | func Location() string { | 
 | 	p := filepath.Join(gpath.Home(), "cache") | 
 | 	Setup() | 
 |  | 
 | 	return p | 
 | } | 
 |  | 
 | // scpSyntaxRe matches the SCP-like addresses used to access repos over SSH. | 
 | var scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`) | 
 |  | 
 | // Key generates a cache key based on a url or scp string. The key is file | 
 | // system safe. | 
 | func Key(repo string) (string, error) { | 
 |  | 
 | 	var u *url.URL | 
 | 	var err error | 
 | 	var strip bool | 
 | 	if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil { | 
 | 		// Match SCP-like syntax and convert it to a URL. | 
 | 		// Eg, "git@github.com:user/repo" becomes | 
 | 		// "ssh://git@github.com/user/repo". | 
 | 		u = &url.URL{ | 
 | 			Scheme: "ssh", | 
 | 			User:   url.User(m[1]), | 
 | 			Host:   m[2], | 
 | 			Path:   "/" + m[3], | 
 | 		} | 
 | 		strip = true | 
 | 	} else { | 
 | 		u, err = url.Parse(repo) | 
 | 		if err != nil { | 
 | 			return "", err | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if strip { | 
 | 		u.Scheme = "" | 
 | 	} | 
 |  | 
 | 	var key string | 
 | 	if u.Scheme != "" { | 
 | 		key = u.Scheme + "-" | 
 | 	} | 
 | 	if u.User != nil && u.User.Username() != "" { | 
 | 		key = key + u.User.Username() + "-" | 
 | 	} | 
 | 	key = key + u.Host | 
 | 	if u.Path != "" { | 
 | 		key = key + strings.Replace(u.Path, "/", "-", -1) | 
 | 	} | 
 |  | 
 | 	key = strings.Replace(key, ":", "-", -1) | 
 |  | 
 | 	return key, nil | 
 | } | 
 |  | 
 | // RepoInfo holds information about a repo. | 
 | type RepoInfo struct { | 
 | 	DefaultBranch string `json:"default-branch"` | 
 | 	LastUpdate    string `json:"last-update"` | 
 | } | 
 |  | 
 | // SaveRepoData stores data about a repo in the Glide cache | 
 | func SaveRepoData(key string, data RepoInfo) error { | 
 | 	if !Enabled { | 
 | 		return ErrCacheDisabled | 
 | 	} | 
 | 	location := Location() | 
 | 	data.LastUpdate = time.Now().String() | 
 | 	d, err := json.Marshal(data) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 |  | 
 | 	pp := filepath.Join(location, "info") | 
 | 	err = os.MkdirAll(pp, 0755) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 |  | 
 | 	p := filepath.Join(pp, key+".json") | 
 | 	f, err := os.Create(p) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	defer f.Close() | 
 |  | 
 | 	_, err = f.Write(d) | 
 | 	return err | 
 | } | 
 |  | 
 | // RepoData retrieves cached information about a repo. | 
 | func RepoData(key string) (*RepoInfo, error) { | 
 | 	if !Enabled { | 
 | 		return &RepoInfo{}, ErrCacheDisabled | 
 | 	} | 
 | 	location := Location() | 
 | 	c := &RepoInfo{} | 
 | 	p := filepath.Join(location, "info", key+".json") | 
 | 	f, err := ioutil.ReadFile(p) | 
 | 	if err != nil { | 
 | 		return &RepoInfo{}, err | 
 | 	} | 
 | 	err = json.Unmarshal(f, c) | 
 | 	if err != nil { | 
 | 		return &RepoInfo{}, err | 
 | 	} | 
 | 	return c, nil | 
 | } | 
 |  | 
 | var lockSync sync.Mutex | 
 |  | 
 | var lockData = make(map[string]*sync.Mutex) | 
 |  | 
 | // Lock locks a particular key name | 
 | func Lock(name string) { | 
 | 	lockSync.Lock() | 
 | 	m, ok := lockData[name] | 
 | 	if !ok { | 
 | 		m = &sync.Mutex{} | 
 | 		lockData[name] = m | 
 | 	} | 
 | 	lockSync.Unlock() | 
 | 	msg.Debug("Locking %s", name) | 
 | 	m.Lock() | 
 | } | 
 |  | 
 | // Unlock unlocks a particular key name | 
 | func Unlock(name string) { | 
 | 	msg.Debug("Unlocking %s", name) | 
 | 	lockSync.Lock() | 
 | 	if m, ok := lockData[name]; ok { | 
 | 		m.Unlock() | 
 | 	} | 
 |  | 
 | 	lockSync.Unlock() | 
 | } |