aboutsummaryrefslogtreecommitdiff
path: root/s3_fs.go
blob: f8983ef3479016933f12cba41bf050c3baffda69 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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 {
		log.Println("found object", obj)
		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)
		log.Println("copy", obj)
		if err != nil {
			log.Println("copy err", err)
			return err
		}

		log.Println("delete", obj)
		err = s.mc.RemoveObject(s.ctx, po.bucket, obj.Key, minio.RemoveObjectOptions{})
		if err != nil /*&& err.Code != 200*/ /* @FIXME workaround for garage's bug #98 */ {
			log.Println("delete err", err)
			return err
		}
	}

	return nil
}

func (s S3FS) Stat(ctx context.Context, name string) (os.FileInfo, error) {
	s.ctx = ctx
	return NewS3Stat(&s, name)
}