feat: initialize project structure with frontend dependencies and backend protocols

This commit is contained in:
2026-06-09 11:14:49 +01:00
commit 13848fb227
43 changed files with 4836 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
package local
import (
"io"
"os"
"path/filepath"
"runtime"
"time"
"goexplore/internal/explorer"
)
type LocalExplorer struct {
basePath string
}
func New() *LocalExplorer {
basePath := "/"
if runtime.GOOS == "windows" {
basePath = "C:\\"
}
return &LocalExplorer{basePath: basePath}
}
func (e *LocalExplorer) Connect() error { return nil }
func (e *LocalExplorer) Disconnect() error { return nil }
func (e *LocalExplorer) ListDir(path string) ([]explorer.FileEntry, error) {
if path == "" {
path = e.basePath
}
entries, err := os.ReadDir(path)
if err != nil {
return nil, err
}
var result []explorer.FileEntry
for _, entry := range entries {
info, err := entry.Info()
if err != nil {
continue
}
result = append(result, explorer.FileEntry{
Name: entry.Name(),
Path: filepath.ToSlash(filepath.Join(path, entry.Name())),
Size: info.Size(),
Modified: info.ModTime().Format(time.RFC3339),
IsDir: entry.IsDir(),
Permissions: info.Mode().String(),
})
}
return result, nil
}
func (e *LocalExplorer) Stat(path string) (explorer.FileEntry, error) {
if path == "" {
path = e.basePath
}
info, err := os.Stat(path)
if err != nil {
return explorer.FileEntry{}, err
}
return explorer.FileEntry{
Name: info.Name(),
Path: filepath.ToSlash(path),
Size: info.Size(),
Modified: info.ModTime().Format(time.RFC3339),
IsDir: info.IsDir(),
Permissions: info.Mode().String(),
}, nil
}
func (e *LocalExplorer) MkDir(path string) error {
return os.Mkdir(path, 0755)
}
func (e *LocalExplorer) Delete(path string) error {
return os.RemoveAll(path)
}
func (e *LocalExplorer) Rename(src, dst string) error {
return os.Rename(src, dst)
}
func (e *LocalExplorer) ReadFile(path string) (io.ReadCloser, error) {
return os.Open(path)
}
func (e *LocalExplorer) WriteFile(path string, r io.Reader, size int64) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}
+125
View File
@@ -0,0 +1,125 @@
package nfs
import (
"fmt"
"io"
"path/filepath"
"runtime"
"time"
"github.com/vmware/go-nfs-client/nfs"
"github.com/vmware/go-nfs-client/nfs/rpc"
"goexplore/internal/config"
"goexplore/internal/explorer"
)
type NFSExplorer struct {
cfg *config.ConnectionConfig
mount *nfs.Mount
target *nfs.Target
}
func New(c *config.ConnectionConfig, secret string) (*NFSExplorer, error) {
if runtime.GOOS == "windows" {
return nil, fmt.Errorf("NFS not supported on this platform")
}
return &NFSExplorer{cfg: c}, nil
}
func (e *NFSExplorer) Connect() error {
mount, err := nfs.DialMount(e.cfg.Host)
if err != nil {
return err
}
e.mount = mount
auth := rpc.AuthNull
target, err := mount.Mount(e.cfg.Bucket, auth)
if err != nil {
mount.Close()
return err
}
e.target = target
return nil
}
func (e *NFSExplorer) Disconnect() error {
if e.target != nil {
e.target.Close()
}
if e.mount != nil {
e.mount.Close()
}
return nil
}
func (e *NFSExplorer) ListDir(path string) ([]explorer.FileEntry, error) {
if path == "" {
path = "."
}
files, err := e.target.ReadDirPlus(path)
if err != nil {
return nil, err
}
var entries []explorer.FileEntry
for _, f := range files {
if f.Name() == "." || f.Name() == ".." {
continue
}
entries = append(entries, explorer.FileEntry{
Name: f.Name(),
Path: filepath.ToSlash(filepath.Join(path, f.Name())),
Size: f.Size(),
Modified: f.ModTime().Format(time.RFC3339),
IsDir: f.IsDir(),
Permissions: f.Mode().String(),
})
}
return entries, nil
}
func (e *NFSExplorer) Stat(path string) (explorer.FileEntry, error) {
if path == "" {
path = "."
}
f, _, err := e.target.Lookup(path)
if err != nil {
return explorer.FileEntry{}, err
}
return explorer.FileEntry{
Name: f.Name(),
Path: filepath.ToSlash(path),
Size: f.Size(),
Modified: f.ModTime().Format(time.RFC3339),
IsDir: f.IsDir(),
Permissions: f.Mode().String(),
}, nil
}
func (e *NFSExplorer) MkDir(path string) error {
_, err := e.target.Mkdir(path, 0755)
return err
}
func (e *NFSExplorer) Delete(path string) error {
return e.target.RemoveAll(path)
}
func (e *NFSExplorer) Rename(src, dst string) error {
return fmt.Errorf("rename not fully supported by go-nfs-client wrapper")
}
func (e *NFSExplorer) ReadFile(path string) (io.ReadCloser, error) {
return e.target.Open(path)
}
func (e *NFSExplorer) WriteFile(path string, r io.Reader, size int64) error {
f, err := e.target.OpenFile(path, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}
+309
View File
@@ -0,0 +1,309 @@
package s3
import (
"context"
"fmt"
"io"
"path/filepath"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
appcfg "goexplore/internal/config"
"goexplore/internal/explorer"
)
type S3Explorer struct {
cfg *appcfg.ConnectionConfig
secret string
client *s3.Client
}
func New(c *appcfg.ConnectionConfig, secret string) *S3Explorer {
return &S3Explorer{cfg: c, secret: secret}
}
func (e *S3Explorer) Connect() error {
region := e.cfg.Region
if region == "" {
region = "us-east-1"
}
creds := credentials.NewStaticCredentialsProvider(e.cfg.Username, e.secret, "")
optFns := []func(*config.LoadOptions) error{
config.WithCredentialsProvider(creds),
config.WithRegion(region),
}
awsCfg, err := config.LoadDefaultConfig(context.TODO(), optFns...)
if err != nil {
return err
}
clientOpts := func(o *s3.Options) {
if e.cfg.Host != "" {
host := e.cfg.Host
if !strings.HasPrefix(host, "http") {
host = "https://" + host
}
if e.cfg.Port > 0 {
host = fmt.Sprintf("%s:%d", host, e.cfg.Port)
}
o.BaseEndpoint = aws.String(host)
}
o.UsePathStyle = e.cfg.PathStyle
}
e.client = s3.NewFromConfig(awsCfg, clientOpts)
return nil
}
func (e *S3Explorer) Disconnect() error {
return nil
}
func (e *S3Explorer) getBucketAndPath(path string) (string, string, string, error) {
if e.cfg.Bucket != "" {
return e.cfg.Bucket, strings.TrimPrefix(path, "/"), "", nil
}
path = strings.TrimPrefix(path, "/")
if path == "" {
return "", "", "", fmt.Errorf("no bucket specified")
}
parts := strings.SplitN(path, "/", 2)
bucket := parts[0]
subpath := ""
if len(parts) > 1 {
subpath = parts[1]
}
return bucket, subpath, bucket, nil
}
func (e *S3Explorer) ListDir(path string) ([]explorer.FileEntry, error) {
if e.cfg.Bucket == "" && (path == "" || path == "/") {
result, err := e.client.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
if err != nil {
return nil, err
}
var entries []explorer.FileEntry
for _, b := range result.Buckets {
entries = append(entries, explorer.FileEntry{
Name: *b.Name,
Path: *b.Name,
Modified: b.CreationDate.Format(time.RFC3339),
IsDir: true,
Permissions: "bucket",
})
}
return entries, nil
}
bucket, subpath, bucketName, err := e.getBucketAndPath(path)
if err != nil {
return nil, err
}
prefix := subpath
if prefix != "" && !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
input := &s3.ListObjectsV2Input{
Bucket: aws.String(bucket),
Prefix: aws.String(prefix),
Delimiter: aws.String("/"),
}
result, err := e.client.ListObjectsV2(context.TODO(), input)
if err != nil {
return nil, err
}
var entries []explorer.FileEntry
for _, cp := range result.CommonPrefixes {
name := strings.TrimSuffix(*cp.Prefix, "/")
name = filepath.Base(name)
fullPath := *cp.Prefix
if e.cfg.Bucket == "" {
fullPath = bucketName + "/" + *cp.Prefix
}
entries = append(entries, explorer.FileEntry{
Name: name,
Path: fullPath,
IsDir: true,
Permissions: "dir",
})
}
for _, obj := range result.Contents {
if *obj.Key == prefix {
continue
}
name := filepath.Base(*obj.Key)
fullPath := *obj.Key
if e.cfg.Bucket == "" {
fullPath = bucketName + "/" + *obj.Key
}
entries = append(entries, explorer.FileEntry{
Name: name,
Path: fullPath,
Size: *obj.Size,
Modified: obj.LastModified.Format(time.RFC3339),
IsDir: false,
Permissions: "file",
})
}
return entries, nil
}
func (e *S3Explorer) Stat(path string) (explorer.FileEntry, error) {
if e.cfg.Bucket == "" && (path == "" || path == "/") {
return explorer.FileEntry{
Name: "/", Path: "/", IsDir: true,
}, nil
}
bucket, subpath, bucketName, err := e.getBucketAndPath(path)
if err != nil {
return explorer.FileEntry{}, err
}
if subpath == "" {
return explorer.FileEntry{
Name: bucket, Path: bucketName, IsDir: true,
}, nil
}
input := &s3.HeadObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(subpath),
}
head, err := e.client.HeadObject(context.TODO(), input)
if err != nil {
return explorer.FileEntry{
Name: filepath.Base(subpath),
Path: path,
IsDir: true,
}, nil
}
return explorer.FileEntry{
Name: filepath.Base(subpath),
Path: path,
Size: *head.ContentLength,
Modified: head.LastModified.Format(time.RFC3339),
IsDir: false,
}, nil
}
func (e *S3Explorer) MkDir(path string) error {
bucket, subpath, _, err := e.getBucketAndPath(path)
if err != nil {
return err
}
if subpath == "" {
_, err := e.client.CreateBucket(context.TODO(), &s3.CreateBucketInput{
Bucket: aws.String(bucket),
})
return err
}
if !strings.HasSuffix(subpath, "/") {
subpath += "/"
}
_, err = e.client.PutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(subpath),
Body: strings.NewReader(""),
})
return err
}
func (e *S3Explorer) Delete(path string) error {
bucket, subpath, _, err := e.getBucketAndPath(path)
if err != nil {
return err
}
if subpath == "" {
_, err := e.client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{
Bucket: aws.String(bucket),
})
return err
}
_, err = e.client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(subpath),
})
return err
}
func (e *S3Explorer) Rename(src, dst string) error {
bucket1, subpath1, _, err := e.getBucketAndPath(src)
if err != nil {
return err
}
bucket2, subpath2, _, err := e.getBucketAndPath(dst)
if err != nil {
return err
}
if bucket1 != bucket2 {
return fmt.Errorf("cross-bucket rename not supported")
}
_, err = e.client.CopyObject(context.TODO(), &s3.CopyObjectInput{
Bucket: aws.String(bucket2),
CopySource: aws.String(fmt.Sprintf("%s/%s", bucket1, subpath1)),
Key: aws.String(subpath2),
})
if err != nil {
return err
}
_, err = e.client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{
Bucket: aws.String(bucket1),
Key: aws.String(subpath1),
})
return err
}
func (e *S3Explorer) ReadFile(path string) (io.ReadCloser, error) {
bucket, subpath, _, err := e.getBucketAndPath(path)
if err != nil {
return nil, err
}
out, err := e.client.GetObject(context.TODO(), &s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(subpath),
})
if err != nil {
return nil, err
}
return out.Body, nil
}
func (e *S3Explorer) WriteFile(path string, r io.Reader, size int64) error {
bucket, subpath, _, err := e.getBucketAndPath(path)
if err != nil {
return err
}
_, err = e.client.PutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(subpath),
Body: r,
})
return err
}
+164
View File
@@ -0,0 +1,164 @@
package sftp
import (
"fmt"
"io"
"path/filepath"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"goexplore/internal/config"
"goexplore/internal/explorer"
)
type SFTPExplorer struct {
cfg *config.ConnectionConfig
secret string
client *sftp.Client
conn *ssh.Client
}
func New(c *config.ConnectionConfig, secret string) *SFTPExplorer {
return &SFTPExplorer{cfg: c, secret: secret}
}
func (e *SFTPExplorer) Connect() error {
port := e.cfg.Port
if port == 0 {
port = 22
}
var authMethod ssh.AuthMethod
signer, err := ssh.ParsePrivateKey([]byte(e.secret))
if err == nil {
authMethod = ssh.PublicKeys(signer)
} else {
authMethod = ssh.Password(e.secret)
}
config := &ssh.ClientConfig{
User: e.cfg.Username,
Auth: []ssh.AuthMethod{
authMethod,
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", e.cfg.Host, port), config)
if err != nil {
return err
}
e.conn = conn
client, err := sftp.NewClient(conn)
if err != nil {
conn.Close()
return err
}
e.client = client
return nil
}
func (e *SFTPExplorer) Disconnect() error {
if e.client != nil {
e.client.Close()
}
if e.conn != nil {
e.conn.Close()
}
return nil
}
func (e *SFTPExplorer) ListDir(path string) ([]explorer.FileEntry, error) {
if path == "" {
path = "."
}
files, err := e.client.ReadDir(path)
if err != nil {
return nil, err
}
var entries []explorer.FileEntry
for _, f := range files {
entries = append(entries, explorer.FileEntry{
Name: f.Name(),
Path: filepath.ToSlash(filepath.Join(path, f.Name())),
Size: f.Size(),
Modified: f.ModTime().Format(time.RFC3339),
IsDir: f.IsDir(),
Permissions: f.Mode().String(),
})
}
return entries, nil
}
func (e *SFTPExplorer) Stat(path string) (explorer.FileEntry, error) {
if path == "" {
path = "."
}
f, err := e.client.Stat(path)
if err != nil {
return explorer.FileEntry{}, err
}
return explorer.FileEntry{
Name: f.Name(),
Path: filepath.ToSlash(path),
Size: f.Size(),
Modified: f.ModTime().Format(time.RFC3339),
IsDir: f.IsDir(),
Permissions: f.Mode().String(),
}, nil
}
func (e *SFTPExplorer) MkDir(path string) error {
return e.client.MkdirAll(path)
}
func (e *SFTPExplorer) removeAll(path string) error {
fi, err := e.client.Stat(path)
if err != nil {
return err
}
if !fi.IsDir() {
return e.client.Remove(path)
}
files, err := e.client.ReadDir(path)
if err != nil {
return err
}
for _, f := range files {
if f.Name() == "." || f.Name() == ".." {
continue
}
if err := e.removeAll(filepath.Join(path, f.Name())); err != nil {
return err
}
}
return e.client.RemoveDirectory(path)
}
func (e *SFTPExplorer) Delete(path string) error {
return e.removeAll(path)
}
func (e *SFTPExplorer) Rename(src, dst string) error {
return e.client.Rename(src, dst)
}
func (e *SFTPExplorer) ReadFile(path string) (io.ReadCloser, error) {
return e.client.Open(path)
}
func (e *SFTPExplorer) WriteFile(path string, r io.Reader, size int64) error {
f, err := e.client.Create(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}
+245
View File
@@ -0,0 +1,245 @@
package smb
import (
"fmt"
"io"
"net"
"path/filepath"
"strings"
"time"
"github.com/hirochachacha/go-smb2"
"goexplore/internal/config"
"goexplore/internal/explorer"
)
type SMBExplorer struct {
cfg *config.ConnectionConfig
secret string
conn net.Conn
sess *smb2.Session
defaultShare *smb2.Share
shares map[string]*smb2.Share
}
func New(c *config.ConnectionConfig, secret string) *SMBExplorer {
return &SMBExplorer{cfg: c, secret: secret, shares: make(map[string]*smb2.Share)}
}
func (e *SMBExplorer) Connect() error {
port := e.cfg.Port
if port == 0 {
port = 445
}
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", e.cfg.Host, port))
if err != nil {
return err
}
e.conn = conn
d := &smb2.Dialer{
Initiator: &smb2.NTLMInitiator{
User: e.cfg.Username,
Password: e.secret,
},
}
s, err := d.Dial(conn)
if err != nil {
conn.Close()
return err
}
e.sess = s
if e.cfg.Bucket != "" {
share, err := s.Mount(e.cfg.Bucket)
if err != nil {
s.Logoff()
conn.Close()
return err
}
e.defaultShare = share
}
return nil
}
func (e *SMBExplorer) Disconnect() error {
if e.defaultShare != nil {
e.defaultShare.Umount()
}
for _, s := range e.shares {
s.Umount()
}
if e.sess != nil {
e.sess.Logoff()
}
if e.conn != nil {
e.conn.Close()
}
return nil
}
func (e *SMBExplorer) getShareAndPath(path string) (*smb2.Share, string, string, error) {
if e.defaultShare != nil {
if path == "" || path == "/" {
path = "."
}
return e.defaultShare, strings.ReplaceAll(path, "/", "\\"), "", nil
}
path = strings.TrimPrefix(filepath.ToSlash(path), "/")
if path == "" || path == "." {
return nil, "", "", fmt.Errorf("no share specified")
}
parts := strings.SplitN(path, "/", 2)
sharename := parts[0]
subpath := "."
if len(parts) > 1 {
subpath = parts[1]
}
if s, ok := e.shares[sharename]; ok {
return s, strings.ReplaceAll(subpath, "/", "\\"), sharename, nil
}
s, err := e.sess.Mount(sharename)
if err != nil {
return nil, "", "", err
}
e.shares[sharename] = s
return s, strings.ReplaceAll(subpath, "/", "\\"), sharename, nil
}
func (e *SMBExplorer) ListDir(path string) ([]explorer.FileEntry, error) {
if e.defaultShare == nil && (path == "" || path == "/" || path == ".") {
shares, err := e.sess.ListSharenames()
if err != nil {
return nil, err
}
var entries []explorer.FileEntry
for _, s := range shares {
entries = append(entries, explorer.FileEntry{
Name: s,
Path: s,
IsDir: true,
Permissions: "share",
})
}
return entries, nil
}
share, subpath, shareName, err := e.getShareAndPath(path)
if err != nil {
return nil, err
}
files, err := share.ReadDir(subpath)
if err != nil {
return nil, err
}
var entries []explorer.FileEntry
for _, f := range files {
fullPath := ""
if e.defaultShare != nil {
fullPath = strings.ReplaceAll(filepath.Join(subpath, f.Name()), "\\", "/")
} else {
fullPath = filepath.ToSlash(filepath.Join(shareName, subpath, f.Name()))
}
entries = append(entries, explorer.FileEntry{
Name: f.Name(),
Path: fullPath,
Size: f.Size(),
Modified: f.ModTime().Format(time.RFC3339),
IsDir: f.IsDir(),
Permissions: f.Mode().String(),
})
}
return entries, nil
}
func (e *SMBExplorer) Stat(path string) (explorer.FileEntry, error) {
if e.defaultShare == nil && (path == "" || path == "/" || path == ".") {
return explorer.FileEntry{
Name: "/", Path: "/", IsDir: true, Permissions: "root",
}, nil
}
share, subpath, shareName, err := e.getShareAndPath(path)
if err != nil {
return explorer.FileEntry{}, err
}
f, err := share.Stat(subpath)
if err != nil {
return explorer.FileEntry{}, err
}
fullPath := strings.ReplaceAll(subpath, "\\", "/")
if e.defaultShare == nil {
fullPath = filepath.ToSlash(filepath.Join(shareName, subpath))
}
return explorer.FileEntry{
Name: f.Name(),
Path: fullPath,
Size: f.Size(),
Modified: f.ModTime().Format(time.RFC3339),
IsDir: f.IsDir(),
Permissions: f.Mode().String(),
}, nil
}
func (e *SMBExplorer) MkDir(path string) error {
share, subpath, _, err := e.getShareAndPath(path)
if err != nil {
return err
}
return share.Mkdir(subpath, 0755)
}
func (e *SMBExplorer) Delete(path string) error {
share, subpath, _, err := e.getShareAndPath(path)
if err != nil {
return err
}
return share.RemoveAll(subpath)
}
func (e *SMBExplorer) Rename(src, dst string) error {
share1, subpath1, shareName1, err := e.getShareAndPath(src)
if err != nil {
return err
}
_, subpath2, shareName2, err := e.getShareAndPath(dst)
if err != nil {
return err
}
if shareName1 != shareName2 {
return fmt.Errorf("cross-share rename not supported")
}
return share1.Rename(subpath1, subpath2)
}
func (e *SMBExplorer) ReadFile(path string) (io.ReadCloser, error) {
share, subpath, _, err := e.getShareAndPath(path)
if err != nil {
return nil, err
}
return share.Open(subpath)
}
func (e *SMBExplorer) WriteFile(path string, r io.Reader, size int64) error {
share, subpath, _, err := e.getShareAndPath(path)
if err != nil {
return err
}
f, err := share.Create(subpath)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}
+104
View File
@@ -0,0 +1,104 @@
package webdav
import (
"fmt"
"io"
"strings"
"time"
"github.com/studio-b12/gowebdav"
"goexplore/internal/config"
"goexplore/internal/explorer"
)
type WebDAVExplorer struct {
cfg *config.ConnectionConfig
secret string
client *gowebdav.Client
}
func New(c *config.ConnectionConfig, secret string) *WebDAVExplorer {
return &WebDAVExplorer{cfg: c, secret: secret}
}
func (e *WebDAVExplorer) Connect() error {
host := e.cfg.Host
if !strings.HasPrefix(host, "http") {
host = "http://" + host
}
if e.cfg.Port > 0 {
host = fmt.Sprintf("%s:%d", host, e.cfg.Port)
}
if e.cfg.Bucket != "" {
host = host + "/" + e.cfg.Bucket
}
e.client = gowebdav.NewClient(host, e.cfg.Username, e.secret)
err := e.client.Connect()
if err != nil {
return err
}
return nil
}
func (e *WebDAVExplorer) Disconnect() error {
return nil
}
func (e *WebDAVExplorer) ListDir(path string) ([]explorer.FileEntry, error) {
if path == "" {
path = "/"
}
files, err := e.client.ReadDir(path)
if err != nil {
return nil, err
}
var entries []explorer.FileEntry
for _, f := range files {
entries = append(entries, explorer.FileEntry{
Name: f.Name(),
Path: strings.TrimSuffix(path, "/") + "/" + f.Name(),
Size: f.Size(),
Modified: f.ModTime().Format(time.RFC3339),
IsDir: f.IsDir(),
Permissions: f.Mode().String(),
})
}
return entries, nil
}
func (e *WebDAVExplorer) Stat(path string) (explorer.FileEntry, error) {
f, err := e.client.Stat(path)
if err != nil {
return explorer.FileEntry{}, err
}
return explorer.FileEntry{
Name: f.Name(),
Path: path,
Size: f.Size(),
Modified: f.ModTime().Format(time.RFC3339),
IsDir: f.IsDir(),
Permissions: f.Mode().String(),
}, nil
}
func (e *WebDAVExplorer) MkDir(path string) error {
return e.client.MkdirAll(path, 0755)
}
func (e *WebDAVExplorer) Delete(path string) error {
return e.client.RemoveAll(path)
}
func (e *WebDAVExplorer) Rename(src, dst string) error {
return e.client.Rename(src, dst, true)
}
func (e *WebDAVExplorer) ReadFile(path string) (io.ReadCloser, error) {
return e.client.ReadStream(path)
}
func (e *WebDAVExplorer) WriteFile(path string, r io.Reader, size int64) error {
return e.client.WriteStream(path, r, 0644)
}