/
fileSystem.go
150 lines (127 loc) · 4.13 KB
/
fileSystem.go
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
149
150
package ftp
import (
"context"
"errors"
"fmt"
"path"
"github.com/c2fo/vfs/v6"
"github.com/c2fo/vfs/v6/backend"
"github.com/c2fo/vfs/v6/backend/ftp/types"
"github.com/c2fo/vfs/v6/utils"
)
// Scheme defines the filesystem type.
const Scheme = "ftp"
const name = "File Transfer Protocol"
var dataConnGetterFunc func(context.Context, utils.Authority, *FileSystem, *File, types.OpenType) (types.DataConn, error)
var defaultClientGetter func(context.Context, utils.Authority, Options) (client types.Client, err error)
// FileSystem implements vfs.Filesystem for the FTP filesystem.
type FileSystem struct {
options vfs.Options
ftpclient types.Client
dataconn types.DataConn
}
// Retry will return the default no-op retrier. The FTP client provides its own retryer interface, and is available
// to override via the ftp.FileSystem Options type.
func (fs *FileSystem) Retry() vfs.Retry {
return vfs.DefaultRetryer()
}
// NewFile function returns the FTP implementation of vfs.File.
func (fs *FileSystem) NewFile(authority, filePath string) (vfs.File, error) {
if fs == nil {
return nil, errors.New("non-nil ftp.FileSystem pointer is required")
}
if filePath == "" {
return nil, errors.New("non-empty string for path is required")
}
if err := utils.ValidateAbsoluteFilePath(filePath); err != nil {
return nil, err
}
auth, err := utils.NewAuthority(authority)
if err != nil {
return nil, err
}
return &File{
fileSystem: fs,
authority: auth,
path: path.Clean(filePath),
}, nil
}
// NewLocation function returns the FTP implementation of vfs.Location.
func (fs *FileSystem) NewLocation(authority, locPath string) (vfs.Location, error) {
if fs == nil {
return nil, errors.New("non-nil ftp.FileSystem pointer is required")
}
if err := utils.ValidateAbsoluteLocationPath(locPath); err != nil {
return nil, err
}
auth, err := utils.NewAuthority(authority)
if err != nil {
return nil, err
}
return &Location{
fileSystem: fs,
path: utils.EnsureTrailingSlash(path.Clean(locPath)),
Authority: auth,
}, nil
}
// Name returns "Secure File Transfer Protocol"
func (fs *FileSystem) Name() string {
return name
}
// Scheme return "ftp" as the initial part of a file URI ie: ftp://
func (fs *FileSystem) Scheme() string {
return Scheme
}
// Client returns the underlying ftp data connection, creating it, if necessary
// See Overview for authentication resolution
func (fs *FileSystem) DataConn(ctx context.Context, authority utils.Authority, t types.OpenType, f *File) (types.DataConn, error) {
if t != types.SingleOp && f == nil {
return nil, errors.New("can not create DataConn for read or write for a nil file")
}
return dataConnGetterFunc(ctx, authority, fs, f, t)
}
// Client returns the underlying ftp client, creating it, if necessary
// See Overview for authentication resolution
func (fs *FileSystem) Client(ctx context.Context, authority utils.Authority) (types.Client, error) {
if fs.ftpclient == nil {
if fs.options == nil {
fs.options = Options{}
}
if opts, ok := fs.options.(Options); ok {
var err error
fs.ftpclient, err = defaultClientGetter(ctx, authority, opts)
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("unable to create client, vfs.Options must be an ftp.Options")
}
}
return fs.ftpclient, nil
}
// WithOptions sets options for client and returns the filesystem (chainable)
func (fs *FileSystem) WithOptions(opts vfs.Options) *FileSystem {
// only set options if vfs.Options is ftp.Options
if opts, ok := opts.(Options); ok {
fs.options = opts
// we set client to nil to ensure that a new client is created using the new context when Client() is called
fs.ftpclient = nil
}
return fs
}
// WithClient passes in an ftp client and returns the filesystem (chainable)
func (fs *FileSystem) WithClient(client types.Client) *FileSystem {
fs.ftpclient = client
fs.options = nil
return fs
}
// NewFileSystem initializer for fileSystem struct.
func NewFileSystem() *FileSystem {
return &FileSystem{}
}
func init() {
defaultClientGetter = getClient
dataConnGetterFunc = getDataConn
// registers a default Filesystem
backend.Register(Scheme, NewFileSystem())
}