filestash/server/model/webdav.go
2019-01-28 01:09:45 +11:00

265 lines
6.1 KiB
Go

package model
import (
"context"
"fmt"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/mickael-kerjean/net/webdav"
"io"
"os"
"path/filepath"
"strings"
"time"
)
const DAVCachePath = "data/cache/webdav/"
var cachePath string
func init() {
cachePath = filepath.Join(GetCurrentDir(), DAVCachePath) + "/"
os.RemoveAll(cachePath)
os.MkdirAll(cachePath, os.ModePerm)
}
/*
* Implement a webdav.FileSystem: https://godoc.org/golang.org/x/net/webdav#FileSystem
*/
type WebdavFs struct {
backend IBackend
path string
id string
chroot string
}
func NewWebdavFs(b IBackend, primaryKey string, chroot string) WebdavFs {
return WebdavFs{
backend: b,
id: primaryKey,
chroot: chroot,
}
}
func (this WebdavFs) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
if name = this.fullpath(name); name == "" {
return os.ErrNotExist
}
return this.backend.Mkdir(name)
}
func (this WebdavFs) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
if name = this.fullpath(name); name == "" {
return nil, os.ErrNotExist
}
return &WebdavFile{
path: name,
backend: this.backend,
cache: fmt.Sprintf("%stmp_%s", cachePath, Hash(this.id + name, 20)),
}, nil
}
func (this WebdavFs) RemoveAll(ctx context.Context, name string) error {
if name = this.fullpath(name); name == "" {
return os.ErrNotExist
}
return this.backend.Rm(name)
}
func (this WebdavFs) Rename(ctx context.Context, oldName, newName string) error {
if oldName = this.fullpath(oldName); oldName == "" {
return os.ErrNotExist
} else if newName = this.fullpath(newName); newName == "" {
return os.ErrNotExist
}
return this.backend.Mv(oldName, newName)
}
func (this WebdavFs) Stat(ctx context.Context, name string) (os.FileInfo, error) {
if name = this.fullpath(name); name == "" {
return nil, os.ErrNotExist
}
return WebdavFile{
path: name,
backend: this.backend,
cache: fmt.Sprintf("%stmp_%s", cachePath, Hash(this.id + name, 20)),
}.Stat()
}
func (this WebdavFs) fullpath(path string) string {
p := filepath.Join(this.chroot, path)
if strings.HasSuffix(path, "/") == true && strings.HasSuffix(p, "/") == false {
p += "/"
}
if strings.HasPrefix(p, this.chroot) == false {
return ""
}
return p
}
/*
* Implement a webdav.File and os.Stat : https://godoc.org/golang.org/x/net/webdav#File
*/
type WebdavFile struct {
path string
backend IBackend
cache string
fread *os.File
fwrite *os.File
}
func (this *WebdavFile) Read(p []byte) (n int, err error) {
if strings.HasPrefix(filepath.Base(this.path), ".") {
return 0, os.ErrNotExist
}
if this.fread == nil {
if this.fread = this.pull_remote_file(); this.fread == nil {
return -1, os.ErrInvalid
}
}
return this.fread.Read(p)
}
func (this *WebdavFile) Close() error {
if this.fread != nil {
name := this.fread.Name()
if this.fread.Close() == nil {
this.fread = nil
}
if this.fwrite != nil {
// while writing something, we flush any cache to avoid being out of sync
os.Remove(name)
return nil
}
}
if this.fwrite != nil {
// save the cache that's been written to disk in the remote storage
name := this.fwrite.Name()
if this.fwrite.Close() == nil {
this.fwrite = nil
}
if f, err := os.OpenFile(name+"_writer", os.O_RDONLY, os.ModePerm); err == nil {
this.backend.Save(this.path, f)
f.Close()
}
os.Remove(name)
}
return nil
}
func (this *WebdavFile) Seek(offset int64, whence int) (int64, error) {
if this.fread == nil {
this.fread = this.pull_remote_file();
if this.fread == nil {
return offset, ErrNotFound
}
}
a, err := this.fread.Seek(offset, whence)
if err != nil {
return a, ErrNotFound
}
return a, nil
}
func (this WebdavFile) Readdir(count int) ([]os.FileInfo, error) {
if strings.HasPrefix(filepath.Base(this.path), ".") {
return nil, os.ErrNotExist
}
return this.backend.Ls(this.path)
}
func (this WebdavFile) Stat() (os.FileInfo, error) {
return &this, nil
}
func (this *WebdavFile) Write(p []byte) (int, error) {
if strings.HasPrefix(filepath.Base(this.path), ".") {
return 0, os.ErrNotExist
}
if this.fwrite == nil {
f, err := os.OpenFile(this.cache+"_writer", os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm);
if err != nil {
return 0, os.ErrInvalid
}
this.fwrite = f
}
return this.fwrite.Write(p)
}
func (this WebdavFile) pull_remote_file() *os.File {
filename := this.cache+"_reader"
defer removeIn2Minutes(filename)
if f, err := os.OpenFile(filename, os.O_RDONLY, os.ModePerm); err == nil {
return f
}
if f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm); err == nil {
if reader, err := this.backend.Cat(this.path); err == nil {
io.Copy(f, reader)
f.Close()
if obj, ok := reader.(interface{ Close() error }); ok {
obj.Close()
}
if f, err = os.OpenFile(filename, os.O_RDONLY, os.ModePerm); err == nil {
return f
}
return nil
}
f.Close()
}
return nil
}
func (this WebdavFile) Name() string {
return filepath.Base(this.path)
}
func (this *WebdavFile) Size() int64 {
if this.fread == nil {
if this.fread = this.pull_remote_file(); this.fread == nil {
return 0
}
}
if info, err := this.fread.Stat(); err == nil {
return info.Size()
}
return 0
}
func (this WebdavFile) Mode() os.FileMode {
return 0
}
func (this WebdavFile) ModTime() time.Time {
return time.Now()
}
func (this WebdavFile) IsDir() bool {
if strings.HasSuffix(this.path, "/") {
return true
}
return false
}
func (this WebdavFile) Sys() interface{} {
return nil
}
func (this WebdavFile) ETag(ctx context.Context) (string, error) {
// Building an etag can be an expensive call if the data isn't available locally.
// => 2 etags strategies:
// - use a legit etag value when the data is already in our cache
// - use a dummy value that's changing all the time when we don't have much info
etag := Hash(fmt.Sprintf("%d%s", this.ModTime().UnixNano(), this.path), 20)
if this.fread != nil {
if s, err := this.fread.Stat(); err == nil {
etag = Hash(fmt.Sprintf(`"%x%x"`, this.path, s.Size()), 20)
}
}
return etag, nil
}
func removeIn2Minutes(name string) {
go func(){
time.Sleep(120 * time.Second)
os.Remove(name)
}()
}