mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2025-09-23 11:13:35 +08:00
webdav: can read now
This commit is contained in:
@@ -2,6 +2,7 @@ package weed_server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/util"
|
||||
"golang.org/x/net/webdav"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -25,6 +28,9 @@ type WebDavOption struct {
|
||||
DomainName string
|
||||
BucketsPath string
|
||||
GrpcDialOption grpc.DialOption
|
||||
Collection string
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
}
|
||||
|
||||
type WebDavServer struct {
|
||||
@@ -37,7 +43,7 @@ type WebDavServer struct {
|
||||
|
||||
func NewWebDavServer(option *WebDavOption) (ws *WebDavServer, err error) {
|
||||
|
||||
fs, _ := NewWebDavFileSystem()
|
||||
fs, _ := NewWebDavFileSystem(option)
|
||||
|
||||
ws = &WebDavServer{
|
||||
option: option,
|
||||
@@ -78,31 +84,49 @@ func NewWebDavServer(option *WebDavOption) (ws *WebDavServer, err error) {
|
||||
// adapted from https://github.com/mattn/davfs/blob/master/plugin/mysql/mysql.go
|
||||
|
||||
type WebDavFileSystem struct {
|
||||
option *WebDavOption
|
||||
secret security.SigningKey
|
||||
filer *filer2.Filer
|
||||
grpcDialOption grpc.DialOption
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
mod_time time.Time
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
modifiledTime time.Time
|
||||
isDirectory bool
|
||||
}
|
||||
|
||||
func (fi *FileInfo) Name() string { return fi.name }
|
||||
func (fi *FileInfo) Size() int64 { return fi.size }
|
||||
func (fi *FileInfo) Mode() os.FileMode { return fi.mode }
|
||||
func (fi *FileInfo) ModTime() time.Time { return fi.mod_time }
|
||||
func (fi *FileInfo) IsDir() bool { return fi.mode.IsDir() }
|
||||
func (fi *FileInfo) ModTime() time.Time { return fi.modifiledTime }
|
||||
func (fi *FileInfo) IsDir() bool { return fi.isDirectory }
|
||||
func (fi *FileInfo) Sys() interface{} { return nil }
|
||||
|
||||
type WebDavFile struct {
|
||||
fs *WebDavFileSystem
|
||||
name string
|
||||
isDirectory bool
|
||||
off int64
|
||||
fs *WebDavFileSystem
|
||||
name string
|
||||
isDirectory bool
|
||||
off int64
|
||||
entry *filer_pb.Entry
|
||||
entryViewCache []filer2.VisibleInterval
|
||||
}
|
||||
|
||||
func NewWebDavFileSystem() (webdav.FileSystem, error) {
|
||||
return &WebDavFileSystem{}, nil
|
||||
func NewWebDavFileSystem(option *WebDavOption) (webdav.FileSystem, error) {
|
||||
return &WebDavFileSystem{
|
||||
option: option,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fs *WebDavFileSystem) WithFilerClient(ctx context.Context, fn func(filer_pb.SeaweedFilerClient) error) error {
|
||||
|
||||
return util.WithCachedGrpcClient(ctx, func(grpcConnection *grpc.ClientConn) error {
|
||||
client := filer_pb.NewSeaweedFilerClient(grpcConnection)
|
||||
return fn(client)
|
||||
}, fs.option.FilerGrpcAddress, fs.option.GrpcDialOption)
|
||||
|
||||
}
|
||||
|
||||
func clearName(name string) (string, error) {
|
||||
@@ -117,32 +141,55 @@ func clearName(name string) (string, error) {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (fs *WebDavFileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
||||
func (fs *WebDavFileSystem) Mkdir(ctx context.Context, fullDirPath string, perm os.FileMode) error {
|
||||
|
||||
glog.V(2).Infof("WebDavFileSystem.Mkdir %v", name)
|
||||
glog.V(2).Infof("WebDavFileSystem.Mkdir %v", fullDirPath)
|
||||
|
||||
if !strings.HasSuffix(name, "/") {
|
||||
name += "/"
|
||||
if !strings.HasSuffix(fullDirPath, "/") {
|
||||
fullDirPath += "/"
|
||||
}
|
||||
|
||||
var err error
|
||||
if name, err = clearName(name); err != nil {
|
||||
if fullDirPath, err = clearName(fullDirPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fs.stat(name)
|
||||
_, err = fs.stat(ctx, fullDirPath)
|
||||
if err == nil {
|
||||
return os.ErrExist
|
||||
}
|
||||
|
||||
base := "/"
|
||||
for _, elem := range strings.Split(strings.Trim(name, "/"), "/") {
|
||||
for _, elem := range strings.Split(strings.Trim(fullDirPath, "/"), "/") {
|
||||
base += elem + "/"
|
||||
_, err = fs.stat(base)
|
||||
_, err = fs.stat(ctx, base)
|
||||
if err != os.ErrNotExist {
|
||||
return err
|
||||
}
|
||||
// _, err = fs.db.Exec(`insert into filesystem(name, content, mode, mod_time) values(?, '', ?, now())`, base, perm.Perm()|os.ModeDir)
|
||||
err = fs.WithFilerClient(ctx, func(client filer_pb.SeaweedFilerClient) error {
|
||||
dir, name := filer2.FullPath(base).DirAndName()
|
||||
request := &filer_pb.CreateEntryRequest{
|
||||
Directory: dir,
|
||||
Entry: &filer_pb.Entry{
|
||||
Name: name,
|
||||
IsDirectory: true,
|
||||
Attributes: &filer_pb.FuseAttributes{
|
||||
Mtime: time.Now().Unix(),
|
||||
Crtime: time.Now().Unix(),
|
||||
FileMode: uint32(perm),
|
||||
Uid: fs.option.Uid,
|
||||
Gid: fs.option.Gid,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
glog.V(1).Infof("mkdir: %v", request)
|
||||
if _, err := client.CreateEntry(ctx, request); err != nil {
|
||||
return fmt.Errorf("mkdir %s/%s: %v", dir, name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,68 +197,116 @@ func (fs *WebDavFileSystem) Mkdir(ctx context.Context, name string, perm os.File
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *WebDavFileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
|
||||
func (fs *WebDavFileSystem) OpenFile(ctx context.Context, fullFilePath string, flag int, perm os.FileMode) (webdav.File, error) {
|
||||
|
||||
glog.V(2).Infof("WebDavFileSystem.OpenFile %v", name)
|
||||
glog.V(2).Infof("WebDavFileSystem.OpenFile %v", fullFilePath)
|
||||
|
||||
var err error
|
||||
if name, err = clearName(name); err != nil {
|
||||
if fullFilePath, err = clearName(fullFilePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if flag&os.O_CREATE != 0 {
|
||||
// file should not have / suffix.
|
||||
if strings.HasSuffix(name, "/") {
|
||||
if strings.HasSuffix(fullFilePath, "/") {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
// based directory should be exists.
|
||||
dir, _ := path.Split(name)
|
||||
_, err := fs.stat(dir)
|
||||
dir, _ := path.Split(fullFilePath)
|
||||
_, err := fs.stat(ctx, dir)
|
||||
if err != nil {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
_, err = fs.stat(name)
|
||||
_, err = fs.stat(ctx, fullFilePath)
|
||||
if err == nil {
|
||||
if flag&os.O_EXCL != 0 {
|
||||
return nil, os.ErrExist
|
||||
}
|
||||
fs.removeAll(name)
|
||||
fs.removeAll(ctx, fullFilePath)
|
||||
}
|
||||
// _, err = fs.db.Exec(`insert into filesystem(name, content, mode, mod_time) values(?, '', ?, now())`, name, perm.Perm())
|
||||
|
||||
dir, name := filer2.FullPath(fullFilePath).DirAndName()
|
||||
err = fs.WithFilerClient(ctx, func(client filer_pb.SeaweedFilerClient) error {
|
||||
if _, err := client.CreateEntry(ctx, &filer_pb.CreateEntryRequest{
|
||||
Directory: dir,
|
||||
Entry: &filer_pb.Entry{
|
||||
Name: name,
|
||||
IsDirectory: perm&os.ModeDir > 0,
|
||||
Attributes: &filer_pb.FuseAttributes{
|
||||
Mtime: time.Now().Unix(),
|
||||
Crtime: time.Now().Unix(),
|
||||
FileMode: uint32(perm),
|
||||
Uid: fs.option.Uid,
|
||||
Gid: fs.option.Gid,
|
||||
Collection: fs.option.Collection,
|
||||
Replication: "000",
|
||||
TtlSec: 0,
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("create %s: %v", fullFilePath, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &WebDavFile{fs, name, false, 0}, nil
|
||||
return &WebDavFile{
|
||||
fs: fs,
|
||||
name: fullFilePath,
|
||||
isDirectory: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
fi, err := fs.stat(name)
|
||||
fi, err := fs.stat(ctx, fullFilePath)
|
||||
if err != nil {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
if !strings.HasSuffix(name, "/") && fi.IsDir() {
|
||||
name += "/"
|
||||
if !strings.HasSuffix(fullFilePath, "/") && fi.IsDir() {
|
||||
fullFilePath += "/"
|
||||
}
|
||||
|
||||
return &WebDavFile{fs, name, true, 0}, nil
|
||||
return &WebDavFile{
|
||||
fs: fs,
|
||||
name: fullFilePath,
|
||||
isDirectory: false,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (fs *WebDavFileSystem) removeAll(name string) error {
|
||||
func (fs *WebDavFileSystem) removeAll(ctx context.Context, fullFilePath string) error {
|
||||
var err error
|
||||
if name, err = clearName(name); err != nil {
|
||||
if fullFilePath, err = clearName(fullFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fi, err := fs.stat(name)
|
||||
fi, err := fs.stat(ctx, fullFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
//_, err = fs.db.Exec(`delete from filesystem where name like $1 escape '\'`, strings.Replace(name, `%`, `\%`, -1)+`%`)
|
||||
//_, err = fs.db.Exec(`delete from filesystem where fullFilePath like $1 escape '\'`, strings.Replace(fullFilePath, `%`, `\%`, -1)+`%`)
|
||||
} else {
|
||||
//_, err = fs.db.Exec(`delete from filesystem where name = ?`, name)
|
||||
//_, err = fs.db.Exec(`delete from filesystem where fullFilePath = ?`, fullFilePath)
|
||||
}
|
||||
dir, name := filer2.FullPath(fullFilePath).DirAndName()
|
||||
err = fs.WithFilerClient(ctx, func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
request := &filer_pb.DeleteEntryRequest{
|
||||
Directory: dir,
|
||||
Name: name,
|
||||
IsDeleteData: true,
|
||||
}
|
||||
|
||||
glog.V(3).Infof("removing entry: %v", request)
|
||||
_, err := client.DeleteEntry(ctx, request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("remove %s: %v", fullFilePath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -219,7 +314,7 @@ func (fs *WebDavFileSystem) RemoveAll(ctx context.Context, name string) error {
|
||||
|
||||
glog.V(2).Infof("WebDavFileSystem.RemoveAll %v", name)
|
||||
|
||||
return fs.removeAll(name)
|
||||
return fs.removeAll(ctx, name)
|
||||
}
|
||||
|
||||
func (fs *WebDavFileSystem) Rename(ctx context.Context, oldName, newName string) error {
|
||||
@@ -234,7 +329,7 @@ func (fs *WebDavFileSystem) Rename(ctx context.Context, oldName, newName string)
|
||||
return err
|
||||
}
|
||||
|
||||
of, err := fs.stat(oldName)
|
||||
of, err := fs.stat(ctx, oldName)
|
||||
if err != nil {
|
||||
return os.ErrExist
|
||||
}
|
||||
@@ -243,34 +338,55 @@ func (fs *WebDavFileSystem) Rename(ctx context.Context, oldName, newName string)
|
||||
newName += "/"
|
||||
}
|
||||
|
||||
_, err = fs.stat(newName)
|
||||
_, err = fs.stat(ctx, newName)
|
||||
if err == nil {
|
||||
return os.ErrExist
|
||||
}
|
||||
|
||||
//_, err = fs.db.Exec(`update filesystem set name = ? where name = ?`, newName, oldName)
|
||||
return err
|
||||
oldDir, oldBaseName := filer2.FullPath(oldName).DirAndName()
|
||||
newDir, newBaseName := filer2.FullPath(newName).DirAndName()
|
||||
|
||||
return fs.WithFilerClient(ctx, func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
request := &filer_pb.AtomicRenameEntryRequest{
|
||||
OldDirectory: oldDir,
|
||||
OldName: oldBaseName,
|
||||
NewDirectory: newDir,
|
||||
NewName: newBaseName,
|
||||
}
|
||||
|
||||
_, err := client.AtomicRenameEntry(ctx, request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("renaming %s/%s => %s/%s: %v", oldDir, oldBaseName, newDir, newBaseName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func (fs *WebDavFileSystem) stat(name string) (os.FileInfo, error) {
|
||||
func (fs *WebDavFileSystem) stat(ctx context.Context, fullFilePath string) (os.FileInfo, error) {
|
||||
var err error
|
||||
if name, err = clearName(name); err != nil {
|
||||
if fullFilePath, err = clearName(fullFilePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//rows, err := fs.db.Query(`select name, format(length(content)/2, 0), mode, mod_time from filesystem where name = ?`, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var fi FileInfo
|
||||
// err = rows.Scan(&fi.name, &fi.size, &fi.mode, &fi.mod_time)
|
||||
entry, err := filer2.GetEntry(ctx, fs, fullFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi.size = int64(filer2.TotalSize(entry.GetChunks()))
|
||||
fi.name = fullFilePath
|
||||
fi.mode = os.FileMode(entry.Attributes.FileMode)
|
||||
fi.modifiledTime = time.Unix(entry.Attributes.Mtime, 0)
|
||||
fi.isDirectory = entry.IsDirectory
|
||||
|
||||
_, fi.name = path.Split(path.Clean(fi.name))
|
||||
if fi.name == "" {
|
||||
fi.name = "/"
|
||||
fi.mod_time = time.Now()
|
||||
fi.modifiledTime = time.Now()
|
||||
fi.isDirectory = true
|
||||
}
|
||||
return &fi, nil
|
||||
}
|
||||
@@ -279,7 +395,7 @@ func (fs *WebDavFileSystem) Stat(ctx context.Context, name string) (os.FileInfo,
|
||||
|
||||
glog.V(2).Infof("WebDavFileSystem.Stat %v", name)
|
||||
|
||||
return fs.stat(name)
|
||||
return fs.stat(ctx, name)
|
||||
}
|
||||
|
||||
func (f *WebDavFile) Write(p []byte) (int, error) {
|
||||
@@ -299,41 +415,105 @@ func (f *WebDavFile) Close() error {
|
||||
|
||||
glog.V(2).Infof("WebDavFileSystem.Close %v", f.name)
|
||||
|
||||
if f.entry != nil {
|
||||
f.entry = nil
|
||||
f.entryViewCache = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *WebDavFile) Read(p []byte) (int, error) {
|
||||
func (f *WebDavFile) Read(p []byte) (readSize int, err error) {
|
||||
|
||||
glog.V(2).Infof("WebDavFileSystem.Read %v", f.name)
|
||||
ctx := context.Background()
|
||||
|
||||
var err error
|
||||
//rows, err := f.fs.db.Query(`select mode, substr(content, ?, ?) from filesystem where name = ?`, 1+f.off*2, len(p)*2, f.name)
|
||||
if f.entry == nil {
|
||||
f.entry, err = filer2.GetEntry(ctx, f.fs, f.name)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
//defer rows.Close()
|
||||
if len(f.entry.Chunks) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if f.entryViewCache == nil {
|
||||
f.entryViewCache = filer2.NonOverlappingVisibleIntervals(f.entry.Chunks)
|
||||
}
|
||||
chunkViews := filer2.ViewFromVisibleIntervals(f.entryViewCache, f.off, len(p))
|
||||
|
||||
return 0, io.EOF
|
||||
totalRead, err := filer2.ReadIntoBuffer(ctx, f.fs, f.name, p, chunkViews, f.off)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
readSize = int(totalRead)
|
||||
|
||||
f.off += totalRead
|
||||
if readSize == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *WebDavFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||
func (f *WebDavFile) Readdir(count int) (ret []os.FileInfo, err error) {
|
||||
|
||||
glog.V(2).Infof("WebDavFileSystem.Readdir %v", f.name)
|
||||
glog.V(2).Infof("WebDavFileSystem.Readdir %v count %d", f.name, count)
|
||||
ctx := context.Background()
|
||||
|
||||
// return f.children[old:f.off], nil
|
||||
return nil, nil
|
||||
dir := f.name
|
||||
if dir != "/" && strings.HasSuffix(dir, "/") {
|
||||
dir = dir[:len(dir)-1]
|
||||
}
|
||||
|
||||
err = filer2.ReadDirAllEntries(ctx, f.fs, dir, func(entry *filer_pb.Entry) {
|
||||
fi := FileInfo{
|
||||
size: int64(filer2.TotalSize(entry.GetChunks())),
|
||||
name: entry.Name,
|
||||
mode: os.FileMode(entry.Attributes.FileMode),
|
||||
modifiledTime: time.Unix(entry.Attributes.Mtime, 0),
|
||||
isDirectory: entry.IsDirectory,
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(fi.name, "/") && fi.IsDir() {
|
||||
fi.name += "/"
|
||||
}
|
||||
glog.V(4).Infof("entry: %v", fi.name)
|
||||
ret = append(ret, &fi)
|
||||
})
|
||||
|
||||
|
||||
old := f.off
|
||||
if old >= int64(len(ret)) {
|
||||
if count > 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
if count > 0 {
|
||||
f.off += int64(count)
|
||||
if f.off > int64(len(ret)) {
|
||||
f.off = int64(len(ret))
|
||||
}
|
||||
} else {
|
||||
f.off = int64(len(ret))
|
||||
old = 0
|
||||
}
|
||||
|
||||
return ret[old:f.off], nil
|
||||
}
|
||||
|
||||
func (f *WebDavFile) Seek(offset int64, whence int) (int64, error) {
|
||||
|
||||
glog.V(2).Infof("WebDavFile.Seek %v %v %v", f.name, offset, whence)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
var err error
|
||||
switch whence {
|
||||
case 0:
|
||||
f.off = 0
|
||||
case 2:
|
||||
if fi, err := f.fs.stat(f.name); err != nil {
|
||||
if fi, err := f.fs.stat(ctx, f.name); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
f.off = fi.Size()
|
||||
@@ -347,5 +527,7 @@ func (f *WebDavFile) Stat() (os.FileInfo, error) {
|
||||
|
||||
glog.V(2).Infof("WebDavFile.Stat %v", f.name)
|
||||
|
||||
return f.fs.stat(f.name)
|
||||
ctx := context.Background()
|
||||
|
||||
return f.fs.stat(ctx, f.name)
|
||||
}
|
||||
|
Reference in New Issue
Block a user