github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/packer/cache.go (about)

     1  package packer
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"log"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"sync"
    11  )
    12  
    13  // Cache implements a caching interface where files can be stored for
    14  // re-use between multiple runs.
    15  type Cache interface {
    16  	// Lock takes a key and returns the path where the file can be written to.
    17  	// Packer guarantees that no other process will write to this file while
    18  	// the lock is held.
    19  	//
    20  	// If the key has an extension (e.g., file.ext), the resulting path
    21  	// will have that extension as well.
    22  	//
    23  	// The cache will block and wait for the lock.
    24  	Lock(string) string
    25  
    26  	// Unlock will unlock a certain cache key. Be very careful that this
    27  	// is only called once per lock obtained.
    28  	Unlock(string)
    29  
    30  	// RLock returns the path to a key in the cache and locks it for reading.
    31  	// The second return parameter is whether the key existed or not.
    32  	// This will block if any locks are held for writing. No lock will be
    33  	// held if the key doesn't exist.
    34  	RLock(string) (string, bool)
    35  
    36  	// RUnlock will unlock a key for reading.
    37  	RUnlock(string)
    38  }
    39  
    40  // FileCache implements a Cache by caching the data directly to a cache
    41  // directory.
    42  type FileCache struct {
    43  	CacheDir string
    44  	l        sync.Mutex
    45  	rw       map[string]*sync.RWMutex
    46  }
    47  
    48  func (f *FileCache) Lock(key string) string {
    49  	hashKey := f.hashKey(key)
    50  	rw := f.rwLock(hashKey)
    51  	rw.Lock()
    52  
    53  	return f.cachePath(key, hashKey)
    54  }
    55  
    56  func (f *FileCache) Unlock(key string) {
    57  	hashKey := f.hashKey(key)
    58  	rw := f.rwLock(hashKey)
    59  	rw.Unlock()
    60  }
    61  
    62  func (f *FileCache) RLock(key string) (string, bool) {
    63  	hashKey := f.hashKey(key)
    64  	rw := f.rwLock(hashKey)
    65  	rw.RLock()
    66  
    67  	return f.cachePath(key, hashKey), true
    68  }
    69  
    70  func (f *FileCache) RUnlock(key string) {
    71  	hashKey := f.hashKey(key)
    72  	rw := f.rwLock(hashKey)
    73  	rw.RUnlock()
    74  }
    75  
    76  func (f *FileCache) cachePath(key string, hashKey string) string {
    77  	if endIndex := strings.Index(key, "?"); endIndex > -1 {
    78  		key = key[:endIndex]
    79  	}
    80  
    81  	suffix := ""
    82  	dotIndex := strings.LastIndex(key, ".")
    83  	if dotIndex > -1 {
    84  		if slashIndex := strings.LastIndex(key, "/"); slashIndex <= dotIndex {
    85  			suffix = key[dotIndex:]
    86  		}
    87  	}
    88  
    89  	// Make the cache directory. We ignore errors here, but
    90  	// log them in case something happens.
    91  	if err := os.MkdirAll(f.CacheDir, 0755); err != nil {
    92  		log.Printf(
    93  			"[ERR] Error making cacheDir: %s %s", f.CacheDir, err)
    94  	}
    95  
    96  	return filepath.Join(f.CacheDir, hashKey+suffix)
    97  }
    98  
    99  func (f *FileCache) hashKey(key string) string {
   100  	sha := sha256.New()
   101  	sha.Write([]byte(key))
   102  	return hex.EncodeToString(sha.Sum(nil))
   103  }
   104  
   105  func (f *FileCache) rwLock(hashKey string) *sync.RWMutex {
   106  	f.l.Lock()
   107  	defer f.l.Unlock()
   108  
   109  	if f.rw == nil {
   110  		f.rw = make(map[string]*sync.RWMutex)
   111  	}
   112  
   113  	if result, ok := f.rw[hashKey]; ok {
   114  		return result
   115  	}
   116  
   117  	var result sync.RWMutex
   118  	f.rw[hashKey] = &result
   119  	return &result
   120  }