mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-11-02 11:57:04 +08:00
feature (webdav): shared links as webdav server
This commit is contained in:
@ -85,11 +85,11 @@
|
||||
|
||||
/* Font stuff */
|
||||
.CodeMirror {
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
}
|
||||
.cm-s-default .cm-header { font-size: 17px; }
|
||||
.cm-s-default .cm-header.cm-level1{ font-size: 18px;}
|
||||
.cm-s-default .cm-header { font-size: 18px; }
|
||||
.cm-s-default .cm-header.cm-level1{ font-size: 19px;}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.CodeMirror { font-size: 14px; }
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package common
|
||||
|
||||
const (
|
||||
APP_VERSION = "v0.3"
|
||||
APP_VERSION = "v0.4rc"
|
||||
CONFIG_PATH = "data/config/"
|
||||
PLUGIN_PATH = "data/plugin/"
|
||||
LOG_PATH = "data/log/"
|
||||
|
||||
@ -48,17 +48,32 @@ func Hash(str string, n int) string {
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(str))
|
||||
d := hasher.Sum(nil)
|
||||
size := len(Letters)
|
||||
h := ""
|
||||
for i:=0; i<len(d); i++ {
|
||||
if n > 0 && i >= n {
|
||||
if n > 0 && len(h) >= n {
|
||||
break
|
||||
}
|
||||
h += string(Letters[int(d[i]) % size])
|
||||
h += ReversedBaseChange(Letters, int(d[i]))
|
||||
}
|
||||
|
||||
if len(h) > n {
|
||||
return h[0:len(h) - 1]
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func ReversedBaseChange(alphabet []rune, i int) string {
|
||||
str := ""
|
||||
for {
|
||||
str += string(alphabet[i % len(alphabet)])
|
||||
i = i / len(alphabet)
|
||||
if i == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func RandomString(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
|
||||
@ -87,10 +87,10 @@ func(this SafeMapStringString) Set(key string, value string) {
|
||||
|
||||
func(this SafeMapStringString) Gets(keys ...string) []string{
|
||||
this.RLock()
|
||||
defer this.RUnlock()
|
||||
res := make([]string, len(keys))
|
||||
for i, key := range keys {
|
||||
res[i] = this.internal[key]
|
||||
}
|
||||
this.RUnlock()
|
||||
return res
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"github.com/mickael-kerjean/filestash/server/model"
|
||||
"github.com/mickael-kerjean/net/webdav"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func WebdavHandler(ctx App, res http.ResponseWriter, req *http.Request) {
|
||||
@ -13,11 +15,115 @@ func WebdavHandler(ctx App, res http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// https://github.com/golang/net/blob/master/webdav/webdav.go#L49-L68
|
||||
canRead := model.CanRead(&ctx)
|
||||
canWrite := model.CanRead(&ctx)
|
||||
canUpload := model.CanUpload(&ctx)
|
||||
switch req.Method {
|
||||
case "OPTIONS", "GET", "HEAD", "POST", "PROPFIND":
|
||||
if canRead == false {
|
||||
SendErrorResult(res, ErrPermissionDenied)
|
||||
return
|
||||
}
|
||||
case "MKCOL", "DELETE", "COPY", "MOVE", "PROPPATCH":
|
||||
if canWrite == false {
|
||||
SendErrorResult(res, ErrPermissionDenied)
|
||||
return
|
||||
}
|
||||
case "PUT", "LOCK", "UNLOCK":
|
||||
if canWrite == false && canUpload == false {
|
||||
SendErrorResult(res, ErrPermissionDenied)
|
||||
return
|
||||
}
|
||||
default:
|
||||
SendErrorResult(res, ErrNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
h := &webdav.Handler{
|
||||
Prefix: "/s/" + ctx.Share.Id,
|
||||
FileSystem: model.NewWebdavFs(ctx.Backend, ctx.Share.Path),
|
||||
FileSystem: model.NewWebdavFs(ctx.Backend, ctx.Share.Backend, ctx.Share.Path),
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
}
|
||||
h.ServeHTTP(res, req)
|
||||
}
|
||||
|
||||
/*
|
||||
* OSX ask for a lot of crap while mounting as a network drive. To avoid wasting resources with such
|
||||
* an imbecile and considering we can't even see the source code they are running, the best approach we
|
||||
* could go on is: "crap in, crap out" where useless request coming in are identified and answer appropriatly
|
||||
*/
|
||||
func WebdavBlacklist (fn func(App, http.ResponseWriter, *http.Request)) func(ctx App, res http.ResponseWriter, req *http.Request) {
|
||||
return func(ctx App, res http.ResponseWriter, req *http.Request) {
|
||||
base := filepath.Base(req.URL.String())
|
||||
|
||||
if req.Method == "PUT" || req.Method == "MKCOL" {
|
||||
if strings.HasPrefix(base, "._") {
|
||||
res.WriteHeader(http.StatusMethodNotAllowed)
|
||||
res.Write([]byte(""))
|
||||
return
|
||||
} else if base == ".DS_Store" {
|
||||
res.WriteHeader(http.StatusMethodNotAllowed)
|
||||
res.Write([]byte(""))
|
||||
return
|
||||
} else if base == ".localized" {
|
||||
res.WriteHeader(http.StatusMethodNotAllowed)
|
||||
res.Write([]byte(""))
|
||||
return
|
||||
}
|
||||
} else if req.Method == "PROPFIND" {
|
||||
if strings.HasPrefix(base, "._") {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
} else if base == ".DS_Store" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
res.Write([]byte(""))
|
||||
return
|
||||
} else if base == ".localized" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
} else if base == ".ql_disablethumbnails" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
res.Write([]byte(""))
|
||||
return
|
||||
} else if base == ".ql_disablecache" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
} else if base == ".hidden" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
} else if base == ".Spotlight-V100" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
} else if base == ".metadata_never_index" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
} else if base == "Contents" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
} else if base == ".metadata_never_index_unless_rootfs" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
} else if req.Method == "GET" {
|
||||
if base == ".DS_Store" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
res.Write([]byte(""))
|
||||
return
|
||||
}
|
||||
} else if req.Method == "DELETE" {
|
||||
if base == ".DS_Store" {
|
||||
res.WriteHeader(http.StatusForbidden)
|
||||
res.Write([]byte(""))
|
||||
return
|
||||
}
|
||||
} else if req.Method == "LOCK" || req.Method == "UNLOCK" {
|
||||
if base == ".DS_Store" {
|
||||
res.WriteHeader(http.StatusMethodNotAllowed)
|
||||
res.Write([]byte(""))
|
||||
return
|
||||
}
|
||||
}
|
||||
fn(ctx, res, req)
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ func Init(a *App) {
|
||||
// Webdav server / Shared Link
|
||||
middlewares = []Middleware{ IndexHeaders, SecureHeaders }
|
||||
r.HandleFunc("/s/{share}", NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a)).Methods("GET")
|
||||
middlewares = []Middleware{ SessionStart }
|
||||
middlewares = []Middleware{ WebdavBlacklist, SessionStart }
|
||||
r.PathPrefix("/s/{share}").Handler(NewMiddlewareChain(WebdavHandler, middlewares, *a))
|
||||
|
||||
// Application Resources
|
||||
|
||||
@ -246,6 +246,9 @@ func (b Sftp) Close() error {
|
||||
func (b Sftp) err(e error) error {
|
||||
f, ok := e.(*sftp.StatusError)
|
||||
if ok == false {
|
||||
if e == os.ErrNotExist {
|
||||
return ErrNotFound
|
||||
}
|
||||
return e
|
||||
}
|
||||
switch f.Code {
|
||||
@ -258,7 +261,7 @@ func (b Sftp) err(e error) error {
|
||||
case 3:
|
||||
return NewError("Permission denied", 403)
|
||||
case 4:
|
||||
return NewError("Failure", 400)
|
||||
return NewError("Failure", 409)
|
||||
case 5:
|
||||
return NewError("Not Compatible", 400)
|
||||
case 6:
|
||||
|
||||
@ -89,7 +89,9 @@ func (w WebDav) Ls(path string) ([]os.FileInfo, error) {
|
||||
<d:getlastmodified/>
|
||||
</d:prop>
|
||||
</d:propfind>`
|
||||
res, err := w.request("PROPFIND", w.params.url+encodeURL(path), strings.NewReader(query), nil)
|
||||
res, err := w.request("PROPFIND", w.params.url+encodeURL(path), strings.NewReader(query), func(req *http.Request) {
|
||||
req.Header.Add("Depth", "1")
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -111,6 +113,7 @@ func (w WebDav) Ls(path string) ([]os.FileInfo, error) {
|
||||
if tag.Href == ShortURLDav || tag.Href == LongURLDav {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, prop := range tag.Props {
|
||||
if i > 0 {
|
||||
break
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"os"
|
||||
"context"
|
||||
"fmt"
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"github.com/mickael-kerjean/net/webdav"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DAVCachePath = "data/cache/webdav/"
|
||||
@ -19,195 +21,245 @@ func init() {
|
||||
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, path string) WebdavFs {
|
||||
func NewWebdavFs(b IBackend, primaryKey string, chroot string) WebdavFs {
|
||||
return WebdavFs{
|
||||
backend: b,
|
||||
path: path,
|
||||
id: primaryKey,
|
||||
chroot: chroot,
|
||||
}
|
||||
}
|
||||
|
||||
func (fs WebdavFs) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
||||
Log.Info("MKDIR ('%s')", name)
|
||||
if name = fs.resolve(name); name == "" {
|
||||
return os.ErrInvalid
|
||||
func (this WebdavFs) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
||||
if name = this.fullpath(name); name == "" {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
return fs.backend.Mkdir(name)
|
||||
return this.backend.Mkdir(name)
|
||||
}
|
||||
|
||||
func (fs WebdavFs) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
|
||||
Log.Info("OPEN_FILE ('%s')", name)
|
||||
return NewWebdavNode(name, fs), nil
|
||||
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 (fs WebdavFs) RemoveAll(ctx context.Context, name string) error {
|
||||
Log.Info("RM ('%s')", name)
|
||||
if name = fs.resolve(name); name == "" {
|
||||
return os.ErrInvalid
|
||||
func (this WebdavFs) RemoveAll(ctx context.Context, name string) error {
|
||||
if name = this.fullpath(name); name == "" {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
return fs.backend.Rm(name)
|
||||
return this.backend.Rm(name)
|
||||
}
|
||||
|
||||
func (fs WebdavFs) Rename(ctx context.Context, oldName, newName string) error {
|
||||
Log.Info("MV ('%s' => '%s')", oldName, newName)
|
||||
if oldName = fs.resolve(oldName); oldName == "" {
|
||||
return os.ErrInvalid
|
||||
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
|
||||
}
|
||||
if newName = fs.resolve(newName); newName == "" {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return fs.backend.Mv(oldName, newName)
|
||||
return this.backend.Mv(oldName, newName)
|
||||
}
|
||||
|
||||
func (fs WebdavFs) Stat(ctx context.Context, name string) (os.FileInfo, error) {
|
||||
Log.Info("STAT ('%s')", name)
|
||||
if name = fs.resolve(name); name == "" {
|
||||
return nil, os.ErrInvalid
|
||||
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()
|
||||
}
|
||||
|
||||
if obj, ok := fs.backend.(interface{ Stat(path string) (os.FileInfo, error) }); ok {
|
||||
return obj.Stat(name)
|
||||
}
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
func (fs WebdavFs) resolve(path string) string {
|
||||
p := filepath.Join(fs.path, path)
|
||||
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, fs.path) == true {
|
||||
return p
|
||||
}
|
||||
if strings.HasPrefix(p, this.chroot) == false {
|
||||
return ""
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
|
||||
type WebdavNode struct {
|
||||
fs WebdavFs
|
||||
/*
|
||||
* Implement a webdav.File and os.Stat : https://godoc.org/golang.org/x/net/webdav#File
|
||||
*/
|
||||
type WebdavFile struct {
|
||||
path string
|
||||
fileread *os.File
|
||||
filewrite *os.File
|
||||
backend IBackend
|
||||
cache string
|
||||
fread *os.File
|
||||
fwrite *os.File
|
||||
}
|
||||
|
||||
func NewWebdavNode(name string, fs WebdavFs) *WebdavNode {
|
||||
return &WebdavNode{
|
||||
fs: fs,
|
||||
path: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WebdavNode) Readdir(count int) ([]os.FileInfo, error) {
|
||||
Log.Info(" => READ_DIR ('%s')", w.path)
|
||||
var path string
|
||||
if path = w.fs.resolve(w.path); path == "" {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
return w.fs.backend.Ls(path)
|
||||
}
|
||||
|
||||
func (w *WebdavNode) Stat() (os.FileInfo, error) {
|
||||
Log.Info(" => STAT ('%s')", w.path)
|
||||
// if w.filewrite != nil {
|
||||
// var path stringc
|
||||
// var err error
|
||||
|
||||
// if path = w.fs.resolve(w.path); path == "" {
|
||||
// return nil, os.ErrInvalid
|
||||
// }
|
||||
// name := w.filewrite.Name()
|
||||
// w.filewrite.Close()
|
||||
// if w.filewrite, err = os.OpenFile(name, os.O_RDONLY, os.ModePerm); err != nil {
|
||||
// return nil, os.ErrInvalid
|
||||
// }
|
||||
|
||||
// if err = w.fs.backend.Save(path, w.filewrite); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
return w.fs.Stat(context.Background(), w.path)
|
||||
}
|
||||
|
||||
func (w *WebdavNode) Close() error {
|
||||
Log.Info(" => CLOSE ('%s')", w.path)
|
||||
if w.fileread != nil {
|
||||
if err := w.cleanup(w.fileread); err != nil {
|
||||
return err
|
||||
}
|
||||
w.fileread = nil
|
||||
}
|
||||
if w.filewrite != nil {
|
||||
defer w.cleanup(w.filewrite)
|
||||
name := w.filewrite.Name()
|
||||
w.filewrite.Close()
|
||||
reader, err := os.OpenFile(name, os.O_RDONLY, os.ModePerm);
|
||||
if err != nil {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
path := w.fs.resolve(w.path)
|
||||
if path == "" {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if err := w.fs.backend.Save(path, reader); err != nil {
|
||||
return err
|
||||
}
|
||||
reader.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WebdavNode) Read(p []byte) (int, error) {
|
||||
Log.Info(" => READ ('%s')", w.path)
|
||||
if w.fileread != nil {
|
||||
return w.fileread.Read(p)
|
||||
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
|
||||
}
|
||||
|
||||
func (w *WebdavNode) Seek(offset int64, whence int) (int64, error) {
|
||||
Log.Info(" => SEEK ('%s')", w.path)
|
||||
var path string
|
||||
var err error
|
||||
if path = w.fs.resolve(w.path); path == "" {
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
return this.fread.Read(p)
|
||||
}
|
||||
|
||||
if w.fileread == nil {
|
||||
var reader io.Reader
|
||||
if w.fileread, err = os.OpenFile(cachePath + "tmp_" + QuickString(10), os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm); err != nil {
|
||||
return 0, os.ErrInvalid
|
||||
func (this *WebdavFile) Close() error {
|
||||
if this.fread != nil {
|
||||
name := this.fread.Name()
|
||||
if this.fread.Close() == nil {
|
||||
this.fread = nil
|
||||
}
|
||||
if reader, err = w.fs.backend.Cat(path); err != nil {
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
io.Copy(w.fileread, reader)
|
||||
|
||||
name := w.fileread.Name()
|
||||
w.fileread.Close()
|
||||
w.fileread, err = os.OpenFile(name, os.O_RDONLY, os.ModePerm)
|
||||
}
|
||||
return w.fileread.Seek(offset, whence)
|
||||
}
|
||||
|
||||
func (w *WebdavNode) Write(p []byte) (int, error) {
|
||||
Log.Info(" => WRITE ('%s')", w.path)
|
||||
var err error
|
||||
|
||||
if w.filewrite == nil {
|
||||
if w.filewrite, err = os.OpenFile(cachePath + "tmp_" + QuickString(10), os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm); err != nil {
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
}
|
||||
return w.filewrite.Write(p)
|
||||
}
|
||||
|
||||
func (w *WebdavNode) cleanup(file *os.File) error {
|
||||
name := file.Name()
|
||||
file.Close();
|
||||
os.Remove(name);
|
||||
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)
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user