github.com/nektos/act@v0.2.63/pkg/artifactcache/storage.go (about)

     1  package artifactcache
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"os"
     8  	"path/filepath"
     9  )
    10  
    11  type Storage struct {
    12  	rootDir string
    13  }
    14  
    15  func NewStorage(rootDir string) (*Storage, error) {
    16  	if err := os.MkdirAll(rootDir, 0o755); err != nil {
    17  		return nil, err
    18  	}
    19  	return &Storage{
    20  		rootDir: rootDir,
    21  	}, nil
    22  }
    23  
    24  func (s *Storage) Exist(id uint64) (bool, error) {
    25  	name := s.filename(id)
    26  	if _, err := os.Stat(name); os.IsNotExist(err) {
    27  		return false, nil
    28  	} else if err != nil {
    29  		return false, err
    30  	}
    31  	return true, nil
    32  }
    33  
    34  func (s *Storage) Write(id uint64, offset int64, reader io.Reader) error {
    35  	name := s.tempName(id, offset)
    36  	if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil {
    37  		return err
    38  	}
    39  	file, err := os.Create(name)
    40  	if err != nil {
    41  		return err
    42  	}
    43  	defer file.Close()
    44  
    45  	_, err = io.Copy(file, reader)
    46  	return err
    47  }
    48  
    49  func (s *Storage) Commit(id uint64, size int64) (int64, error) {
    50  	defer func() {
    51  		_ = os.RemoveAll(s.tempDir(id))
    52  	}()
    53  
    54  	name := s.filename(id)
    55  	tempNames, err := s.tempNames(id)
    56  	if err != nil {
    57  		return 0, err
    58  	}
    59  
    60  	if err := os.MkdirAll(filepath.Dir(name), 0o755); err != nil {
    61  		return 0, err
    62  	}
    63  	file, err := os.Create(name)
    64  	if err != nil {
    65  		return 0, err
    66  	}
    67  	defer file.Close()
    68  
    69  	var written int64
    70  	for _, v := range tempNames {
    71  		f, err := os.Open(v)
    72  		if err != nil {
    73  			return 0, err
    74  		}
    75  		n, err := io.Copy(file, f)
    76  		_ = f.Close()
    77  		if err != nil {
    78  			return 0, err
    79  		}
    80  		written += n
    81  	}
    82  
    83  	// If size is less than 0, it means the size is unknown.
    84  	// We can't check the size of the file, just skip the check.
    85  	// It happens when the request comes from old versions of actions, like `actions/cache@v2`.
    86  	if size >= 0 && written != size {
    87  		_ = file.Close()
    88  		_ = os.Remove(name)
    89  		return 0, fmt.Errorf("broken file: %v != %v", written, size)
    90  	}
    91  
    92  	return written, nil
    93  }
    94  
    95  func (s *Storage) Serve(w http.ResponseWriter, r *http.Request, id uint64) {
    96  	name := s.filename(id)
    97  	http.ServeFile(w, r, name)
    98  }
    99  
   100  func (s *Storage) Remove(id uint64) {
   101  	_ = os.Remove(s.filename(id))
   102  	_ = os.RemoveAll(s.tempDir(id))
   103  }
   104  
   105  func (s *Storage) filename(id uint64) string {
   106  	return filepath.Join(s.rootDir, fmt.Sprintf("%02x", id%0xff), fmt.Sprint(id))
   107  }
   108  
   109  func (s *Storage) tempDir(id uint64) string {
   110  	return filepath.Join(s.rootDir, "tmp", fmt.Sprint(id))
   111  }
   112  
   113  func (s *Storage) tempName(id uint64, offset int64) string {
   114  	return filepath.Join(s.tempDir(id), fmt.Sprintf("%016x", offset))
   115  }
   116  
   117  func (s *Storage) tempNames(id uint64) ([]string, error) {
   118  	dir := s.tempDir(id)
   119  	files, err := os.ReadDir(dir)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	var names []string
   124  	for _, v := range files {
   125  		if !v.IsDir() {
   126  			names = append(names, filepath.Join(dir, v.Name()))
   127  		}
   128  	}
   129  	return names, nil
   130  }