github.com/franciscocpg/glide@v0.0.0-20160823235512-96aa2921b647/cache/cache.go (about)

     1  // Package cache provides an interface for interfacing with the Glide local cache
     2  //
     3  // Glide has a local cache of metadata and repositories similar to the GOPATH.
     4  // To store the cache Glide creates a .glide directory with a cache subdirectory.
     5  // This is usually in the users home directory unless there is no accessible
     6  // home directory in which case the .glide directory is in the root of the
     7  // repository.
     8  //
     9  // To get the cache location use the `cache.Location()` function. This will
    10  // return the proper base location in your environment.
    11  //
    12  // Within the cache directory there are two subdirectories. They are the src
    13  // and info directories. The src directory contains version control checkouts
    14  // of the packages. The info direcory contains metadata. The metadata maps to
    15  // the RepoInfo struct. Both stores are happed to keys.
    16  //
    17  // Using the `cache.Key()` function you can get a key for a repo. Pass in a
    18  // location such as `https://github.com/foo/bar` or `git@example.com:foo.git`
    19  // and a key will be returned that can be used for caching operations.
    20  //
    21  // Note, the caching is based on repo rather than package. This is important
    22  // for a couple reasons.
    23  //
    24  // 1. Forks or package replacements are supported in Glide. Where a different
    25  //    repo maps to a package.
    26  // 2. Permissions enable different access. For example `https://example.com/foo.git`
    27  //    and `git@example.com:foo.git` may have access to different branches or tags.
    28  package cache
    29  
    30  import (
    31  	"encoding/json"
    32  	"errors"
    33  	"io/ioutil"
    34  	"net/url"
    35  	"os"
    36  	"path/filepath"
    37  	"regexp"
    38  	"strings"
    39  	"sync"
    40  	"time"
    41  
    42  	"github.com/Masterminds/glide/msg"
    43  	gpath "github.com/Masterminds/glide/path"
    44  )
    45  
    46  // Enabled sets if the cache is globally enabled. Defaults to true.
    47  var Enabled = true
    48  
    49  // ErrCacheDisabled is returned with the cache is disabled.
    50  var ErrCacheDisabled = errors.New("Cache disabled")
    51  
    52  var isSetup bool
    53  
    54  var setupMutex sync.Mutex
    55  
    56  // Setup creates the cache location.
    57  func Setup() {
    58  	setupMutex.Lock()
    59  	defer setupMutex.Unlock()
    60  
    61  	if isSetup {
    62  		return
    63  	}
    64  	msg.Debug("Setting up the cache directory")
    65  	pths := []string{
    66  		"cache",
    67  		filepath.Join("cache", "src"),
    68  		filepath.Join("cache", "info"),
    69  	}
    70  
    71  	for _, l := range pths {
    72  		err := os.MkdirAll(filepath.Join(gpath.Home(), l), 0755)
    73  		if err != nil {
    74  			msg.Die("Cache directory unavailable: %s", err)
    75  		}
    76  	}
    77  
    78  	isSetup = true
    79  }
    80  
    81  // SetupReset resets if setup has been completed. The next time setup is run
    82  // it will attempt a full setup.
    83  func SetupReset() {
    84  	isSetup = false
    85  }
    86  
    87  // Location returns the location of the cache.
    88  func Location() string {
    89  	p := filepath.Join(gpath.Home(), "cache")
    90  	Setup()
    91  
    92  	return p
    93  }
    94  
    95  // scpSyntaxRe matches the SCP-like addresses used to access repos over SSH.
    96  var scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
    97  
    98  // Key generates a cache key based on a url or scp string. The key is file
    99  // system safe.
   100  func Key(repo string) (string, error) {
   101  
   102  	var u *url.URL
   103  	var err error
   104  	var strip bool
   105  	if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil {
   106  		// Match SCP-like syntax and convert it to a URL.
   107  		// Eg, "git@github.com:user/repo" becomes
   108  		// "ssh://git@github.com/user/repo".
   109  		u = &url.URL{
   110  			Scheme: "ssh",
   111  			User:   url.User(m[1]),
   112  			Host:   m[2],
   113  			Path:   "/" + m[3],
   114  		}
   115  		strip = true
   116  	} else {
   117  		u, err = url.Parse(repo)
   118  		if err != nil {
   119  			return "", err
   120  		}
   121  	}
   122  
   123  	if strip {
   124  		u.Scheme = ""
   125  	}
   126  
   127  	var key string
   128  	if u.Scheme != "" {
   129  		key = u.Scheme + "-"
   130  	}
   131  	if u.User != nil && u.User.Username() != "" {
   132  		key = key + u.User.Username() + "-"
   133  	}
   134  	key = key + u.Host
   135  	if u.Path != "" {
   136  		key = key + strings.Replace(u.Path, "/", "-", -1)
   137  	}
   138  
   139  	key = strings.Replace(key, ":", "-", -1)
   140  
   141  	return key, nil
   142  }
   143  
   144  // RepoInfo holds information about a repo.
   145  type RepoInfo struct {
   146  	DefaultBranch string `json:"default-branch"`
   147  	LastUpdate    string `json:"last-update"`
   148  }
   149  
   150  // SaveRepoData stores data about a repo in the Glide cache
   151  func SaveRepoData(key string, data RepoInfo) error {
   152  	if !Enabled {
   153  		return ErrCacheDisabled
   154  	}
   155  	location := Location()
   156  	data.LastUpdate = time.Now().String()
   157  	d, err := json.Marshal(data)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	pp := filepath.Join(location, "info")
   163  	err = os.MkdirAll(pp, 0755)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	p := filepath.Join(pp, key+".json")
   169  	f, err := os.Create(p)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	defer f.Close()
   174  
   175  	_, err = f.Write(d)
   176  	return err
   177  }
   178  
   179  // RepoData retrieves cached information about a repo.
   180  func RepoData(key string) (*RepoInfo, error) {
   181  	if !Enabled {
   182  		return &RepoInfo{}, ErrCacheDisabled
   183  	}
   184  	location := Location()
   185  	c := &RepoInfo{}
   186  	p := filepath.Join(location, "info", key+".json")
   187  	f, err := ioutil.ReadFile(p)
   188  	if err != nil {
   189  		return &RepoInfo{}, err
   190  	}
   191  	err = json.Unmarshal(f, c)
   192  	if err != nil {
   193  		return &RepoInfo{}, err
   194  	}
   195  	return c, nil
   196  }
   197  
   198  var lockSync sync.Mutex
   199  
   200  var lockData = make(map[string]*sync.Mutex)
   201  
   202  // Lock locks a particular key name
   203  func Lock(name string) {
   204  	lockSync.Lock()
   205  	m, ok := lockData[name]
   206  	if !ok {
   207  		m = &sync.Mutex{}
   208  		lockData[name] = m
   209  	}
   210  	lockSync.Unlock()
   211  	msg.Debug("Locking %s", name)
   212  	m.Lock()
   213  }
   214  
   215  // Unlock unlocks a particular key name
   216  func Unlock(name string) {
   217  	msg.Debug("Unlocking %s", name)
   218  	lockSync.Lock()
   219  	if m, ok := lockData[name]; ok {
   220  		m.Unlock()
   221  	}
   222  
   223  	lockSync.Unlock()
   224  }