1// Copyright © 2015 Jerry Jacobs <jerry.jacobs@xor-gate.org>.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package sftpfs
15
16import (
17	"os"
18	"time"
19
20	"github.com/pkg/sftp"
21	"github.com/spf13/afero"
22)
23
24// Fs is a afero.Fs implementation that uses functions provided by the sftp package.
25//
26// For details in any method, check the documentation of the sftp package
27// (github.com/pkg/sftp).
28type Fs struct {
29	client *sftp.Client
30}
31
32func New(client *sftp.Client) afero.Fs {
33	return &Fs{client: client}
34}
35
36func (s Fs) Name() string { return "sftpfs" }
37
38func (s Fs) Create(name string) (afero.File, error) {
39	return FileCreate(s.client, name)
40}
41
42func (s Fs) Mkdir(name string, perm os.FileMode) error {
43	err := s.client.Mkdir(name)
44	if err != nil {
45		return err
46	}
47	return s.client.Chmod(name, perm)
48}
49
50func (s Fs) MkdirAll(path string, perm os.FileMode) error {
51	// Fast path: if we can tell whether path is a directory or file, stop with success or error.
52	dir, err := s.Stat(path)
53	if err == nil {
54		if dir.IsDir() {
55			return nil
56		}
57		return err
58	}
59
60	// Slow path: make sure parent exists and then call Mkdir for path.
61	i := len(path)
62	for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
63		i--
64	}
65
66	j := i
67	for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
68		j--
69	}
70
71	if j > 1 {
72		// Create parent
73		err = s.MkdirAll(path[0:j-1], perm)
74		if err != nil {
75			return err
76		}
77	}
78
79	// Parent now exists; invoke Mkdir and use its result.
80	err = s.Mkdir(path, perm)
81	if err != nil {
82		// Handle arguments like "foo/." by
83		// double-checking that directory doesn't exist.
84		dir, err1 := s.Lstat(path)
85		if err1 == nil && dir.IsDir() {
86			return nil
87		}
88		return err
89	}
90	return nil
91}
92
93func (s Fs) Open(name string) (afero.File, error) {
94	return FileOpen(s.client, name)
95}
96
97func (s Fs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
98	return nil, nil
99}
100
101func (s Fs) Remove(name string) error {
102	return s.client.Remove(name)
103}
104
105func (s Fs) RemoveAll(path string) error {
106	// TODO have a look at os.RemoveAll
107	// https://github.com/golang/go/blob/master/src/os/path.go#L66
108	return nil
109}
110
111func (s Fs) Rename(oldname, newname string) error {
112	return s.client.Rename(oldname, newname)
113}
114
115func (s Fs) Stat(name string) (os.FileInfo, error) {
116	return s.client.Stat(name)
117}
118
119func (s Fs) Lstat(p string) (os.FileInfo, error) {
120	return s.client.Lstat(p)
121}
122
123func (s Fs) Chmod(name string, mode os.FileMode) error {
124	return s.client.Chmod(name, mode)
125}
126
127func (s Fs) Chtimes(name string, atime time.Time, mtime time.Time) error {
128	return s.client.Chtimes(name, atime, mtime)
129}
130