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  }