package main import ( "context" "errors" "io" "log" "os" "path" "strings" "time" "github.com/minio/minio-go/v7" "golang.org/x/net/webdav" ) /* * S3FS lifetime is limited to a single request * Conversely, Golang's abstraction has been thought to be shared between users * Sharing an instance between users would be very dangerous (as we would need many checks between shared values) */ type S3FS struct { cache map[string]*S3Stat mc *minio.Client ctx context.Context } func NewS3FS(mc *minio.Client) S3FS { return S3FS{ cache: make(map[string]*S3Stat), mc: mc, } } func (s S3FS) Mkdir(ctx context.Context, name string, perm os.FileMode) error { s.ctx = ctx p := NewS3Path(name) if p.class == ROOT { return errors.New("Unable to create another root folder") } else if p.class == BUCKET { log.Println("Creating bucket is not implemented yet") return nil } f, err := NewS3File(&s, path.Join(name, ".bagage")) if err != nil { return err } defer f.Close() _, err = io.Copy(f, strings.NewReader("This is a placeholder")) return nil } func (s S3FS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { s.ctx = ctx // If the file does not exist when opening it, we create a stub if _, ok := s.cache[name]; !ok { st := new(S3Stat) st.fs = &s st.path = NewS3Path(name) st.path.class = OBJECT st.obj.Key = st.path.key st.obj.LastModified = time.Now() s.cache[name] = st } return NewS3File(&s, name) } func (s S3FS) RemoveAll(ctx context.Context, name string) error { //@FIXME nautilus deletes files one by one, at the end, it does not find its folder as it is "already deleted" s.ctx = ctx p := NewS3Path(name) if p.class == ROOT { return errors.New("Unable to create another root folder") } else if p.class == BUCKET { log.Println("Deleting bucket is not implemented yet") return nil } objCh := s.mc.ListObjects(s.ctx, p.bucket, minio.ListObjectsOptions{Prefix: p.key, Recursive: true}) rmCh := s.mc.RemoveObjects(s.ctx, p.bucket, objCh, minio.RemoveObjectsOptions{}) for rErr := range rmCh { return rErr.Err } return nil } func (s S3FS) Rename(ctx context.Context, oldName, newName string) error { s.ctx = ctx po := NewS3Path(oldName) pn := NewS3Path(newName) if po.class == ROOT || pn.class == ROOT { return errors.New("Unable to rename root folder") } else if po.class == BUCKET || pn.class == BUCKET { log.Println("Moving a bucket is not implemented yet") return nil } //Check that newName is not inside oldName if len(newName) > len(oldName) && newName[:len(oldName)] == oldName { return errors.New("Cannot move an entity inside itself (eg. moving /data in /data/test is impossible)") } //Gather all keys, copy the object, delete the original objCh := s.mc.ListObjects(s.ctx, po.bucket, minio.ListObjectsOptions{Prefix: po.key, Recursive: true}) for obj := range objCh { src := minio.CopySrcOptions{ Bucket: po.bucket, Object: obj.Key, } dst := minio.CopyDestOptions{ Bucket: pn.bucket, Object: path.Join(pn.key, obj.Key[len(po.key):]), } _, err := s.mc.CopyObject(s.ctx, dst, src) if err != nil { return err } err = s.mc.RemoveObject(s.ctx, po.bucket, obj.Key, minio.RemoveObjectOptions{}) var e minio.ErrorResponse log.Println(errors.As(err, &e)) log.Println(e) if errors.As(err, &e) && e.StatusCode == 200 { /* @FIXME workaround for garage's bug #98 */ } else if err != nil { return err } } return nil } func (s S3FS) Stat(ctx context.Context, name string) (os.FileInfo, error) { s.ctx = ctx return NewS3Stat(&s, name) }