github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/assets/dynamic/impl_afero.go (about) 1 package dynamic 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "github.com/cozy/cozy-stack/pkg/assets/model" 15 "github.com/spf13/afero" 16 ) 17 18 // AferoFS is a wrapper around the [spf13/afero] filesystem. 19 // 20 // It can be setup with two differents drivers: 21 // - [NewInMemory] use the in-memory driver. It should be 22 // used only for the tests as nothing is persisted. 23 // - [NewOsFS] use the OsFs driver. It will save the assets 24 // on the host filesystem. 25 // 26 // [spf13/afero]: https://github.com/spf13/afero 27 type AferoFS struct { 28 fs afero.Fs 29 } 30 31 // NewInMemoryFS instantiate a new [AferoFS] with the in-memeory driver. 32 // 33 // This implementation loose every data after being clean up so it should 34 // be only used for the tests. 35 func NewInMemoryFS() *AferoFS { 36 return &AferoFS{fs: afero.NewMemMapFs()} 37 } 38 39 // NewOsFS instantiate a new [AferoFS] with the OsFS driver. 40 func NewOsFS(rootPath string) (*AferoFS, error) { 41 rootPath, err := filepath.Abs(rootPath) 42 if err != nil { 43 return nil, err 44 } 45 46 err = os.MkdirAll(rootPath, 0755) 47 if err != nil && !errors.Is(err, os.ErrExist) { 48 return nil, fmt.Errorf("invalid path: %w", err) 49 } 50 51 fs := afero.NewBasePathFs(afero.NewOsFs(), rootPath) 52 53 return &AferoFS{fs}, nil 54 } 55 56 func (a *AferoFS) GetAssetFolderName(context, name string) string { 57 return filepath.Join(context, name) 58 } 59 60 func (a *AferoFS) Remove(context, name string) error { 61 filePath := a.GetAssetFolderName(context, name) 62 return a.fs.Remove(filePath) 63 } 64 65 func (a *AferoFS) CheckStatus(_ context.Context) (time.Duration, error) { 66 before := time.Now() 67 _, err := a.fs.Stat("/") 68 return time.Since(before), err 69 } 70 71 func (a *AferoFS) List() (map[string][]*model.Asset, error) { 72 objs := map[string][]*model.Asset{} 73 74 // List contexts 75 entries, err := afero.ReadDir(a.fs, "/") 76 if err != nil { 77 return nil, err 78 } 79 for _, context := range entries { 80 ctxName := context.Name() 81 82 err := afero.Walk(a.fs, ctxName, func(path string, info os.FileInfo, err error) error { 83 if err != nil { 84 return err 85 } 86 if !info.IsDir() { 87 assetName := strings.Replace(path, ctxName, "", 1) 88 asset, err := getAsset(a, ctxName, assetName) 89 if err != nil { 90 return err 91 } 92 objs[ctxName] = append(objs[ctxName], asset) 93 } 94 return nil 95 }) 96 if err != nil { 97 return nil, err 98 } 99 } 100 return objs, nil 101 } 102 103 func (a *AferoFS) Get(context, name string) ([]byte, error) { 104 filePath := a.GetAssetFolderName(context, name) 105 106 f, err := a.fs.Open(filePath) 107 if err != nil { 108 return nil, err 109 } 110 111 buf := new(bytes.Buffer) 112 113 _, err = io.Copy(buf, f) 114 if err != nil { 115 return nil, err 116 } 117 return buf.Bytes(), f.Close() 118 } 119 120 func (a *AferoFS) Add(context, name string, asset *model.Asset) error { 121 filePath := a.GetAssetFolderName(context, name) 122 123 // Creates the asset folder 124 err := a.fs.MkdirAll(filepath.Dir(filePath), 0755) 125 if err != nil { 126 return err 127 } 128 129 // Writing the file 130 f, err := a.fs.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) 131 if err != nil { 132 return err 133 } 134 135 _, err = f.Write(asset.GetData()) 136 if err != nil { 137 return err 138 } 139 140 return f.Close() 141 }