-
Notifications
You must be signed in to change notification settings - Fork 1.6k
feat(storage): add WebDAV provider support #649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d5f31c5
f71eca6
c2caa17
3743668
54e93f1
d26cf88
0e1bd6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package storage | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"log" | ||
"os" | ||
"path" | ||
"time" | ||
|
||
"github.com/studio-b12/gowebdav" | ||
) | ||
|
||
// WebDAVStorage is a storage backed by a WebDAV server | ||
// basePath is the root directory within the WebDAV server | ||
// where files will be stored. | ||
type WebDAVStorage struct { | ||
client *gowebdav.Client | ||
basePath string | ||
logger *log.Logger | ||
} | ||
|
||
// NewWebDAVStorage creates a new WebDAVStorage | ||
func NewWebDAVStorage(url, basePath, username, password string, logger *log.Logger) (*WebDAVStorage, error) { | ||
c := gowebdav.NewClient(url, username, password) | ||
if err := c.Connect(); err != nil { | ||
if logger != nil { | ||
logger.Printf("webdav connect error: %v", err) | ||
} | ||
return nil, err | ||
} | ||
if logger != nil { | ||
logger.Printf("webdav connected to %s", url) | ||
} | ||
return &WebDAVStorage{client: c, basePath: basePath, logger: logger}, nil | ||
} | ||
|
||
// Type returns the storage type | ||
func (s *WebDAVStorage) Type() string { return "webdav" } | ||
|
||
func (s *WebDAVStorage) fullPath(token, filename string) string { | ||
return path.Join(s.basePath, token, filename) | ||
} | ||
|
||
// Head retrieves content length of a file from storage | ||
func (s *WebDAVStorage) Head(_ context.Context, token, filename string) (uint64, error) { | ||
fi, err := s.client.Stat(s.fullPath(token, filename)) | ||
if err != nil { | ||
if s.logger != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we don't do this kind of logging (unfortunately, I can admit) throughout all the project. if you want extra information for the user could you please wrap with en error with your specific message instead? |
||
s.logger.Printf("webdav head %s/%s error: %v", token, filename, err) | ||
} | ||
return 0, err | ||
} | ||
if s.logger != nil { | ||
s.logger.Printf("webdav head %s/%s ok", token, filename) | ||
} | ||
return uint64(fi.Size()), nil | ||
} | ||
|
||
// Get retrieves a file from storage | ||
func (s *WebDAVStorage) Get(_ context.Context, token, filename string, rng *Range) (io.ReadCloser, uint64, error) { | ||
p := s.fullPath(token, filename) | ||
var rc io.ReadCloser | ||
var err error | ||
if rng != nil { | ||
rc, err = s.client.ReadStreamRange(p, int64(rng.Start), int64(rng.Limit)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. have you tested this on playing a video or audio media with scrobbling? |
||
} else { | ||
rc, err = s.client.ReadStream(p) | ||
} | ||
if err != nil { | ||
if s.logger != nil { | ||
s.logger.Printf("webdav get %s/%s error: %v", token, filename, err) | ||
} | ||
return nil, 0, err | ||
} | ||
fi, err := s.client.Stat(p) | ||
if err != nil { | ||
if cerr := rc.Close(); cerr != nil && s.logger != nil { | ||
s.logger.Printf("webdav close %s/%s error: %v", token, filename, cerr) | ||
} | ||
if s.logger != nil { | ||
s.logger.Printf("webdav stat %s/%s error: %v", token, filename, err) | ||
} | ||
return nil, 0, err | ||
} | ||
size := uint64(fi.Size()) | ||
if rng != nil { | ||
size = rng.AcceptLength(size) | ||
} | ||
if s.logger != nil { | ||
s.logger.Printf("webdav get %s/%s ok", token, filename) | ||
} | ||
return rc, size, nil | ||
} | ||
|
||
// Delete removes a file from storage | ||
func (s *WebDAVStorage) Delete(_ context.Context, token, filename string) error { | ||
if err := s.client.Remove(s.fullPath(token, filename)); err != nil { | ||
if s.logger != nil { | ||
s.logger.Printf("webdav delete %s/%s error: %v", token, filename, err) | ||
} | ||
return err | ||
} | ||
if s.logger != nil { | ||
s.logger.Printf("webdav delete %s/%s ok", token, filename) | ||
} | ||
return nil | ||
} | ||
|
||
// Purge cleans up the storage (noop for webdav) | ||
func (s *WebDAVStorage) Purge(context.Context, time.Duration) error { return nil } | ||
|
||
// Put saves a file on storage | ||
func (s *WebDAVStorage) Put(_ context.Context, token, filename string, reader io.Reader, _ string, _ uint64) error { | ||
dir := path.Join(s.basePath, token) | ||
if err := s.client.MkdirAll(dir, 0755); err != nil { | ||
if s.logger != nil { | ||
s.logger.Printf("webdav mkdir %s error: %v", dir, err) | ||
} | ||
return err | ||
} | ||
if s.logger != nil { | ||
s.logger.Printf("webdav mkdir %s ok", dir) | ||
} | ||
if err := s.client.WriteStream(s.fullPath(token, filename), reader, 0644); err != nil { | ||
if s.logger != nil { | ||
s.logger.Printf("webdav put %s/%s error: %v", token, filename, err) | ||
} | ||
return err | ||
} | ||
if s.logger != nil { | ||
s.logger.Printf("webdav put %s/%s ok", token, filename) | ||
} | ||
return nil | ||
} | ||
|
||
// IsNotExist indicates if a file doesn't exist on storage | ||
func (s *WebDAVStorage) IsNotExist(err error) bool { | ||
if err == nil { | ||
return false | ||
} | ||
if _, ok := err.(*os.PathError); ok { | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
func (s *WebDAVStorage) IsRangeSupported() bool { return true } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks an helper that could be moved outside of the specific storage implementation, if we don't have already one somewhere.
could you please do?