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 }