init adding rclone
This commit is contained in:
263
pkg/webdav/handler.go
Normal file
263
pkg/webdav/handler.go
Normal file
@@ -0,0 +1,263 @@
|
||||
package webdav
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/sirrobot01/debrid-blackhole/pkg/debrid/cache"
|
||||
"github.com/sirrobot01/debrid-blackhole/pkg/debrid/torrent"
|
||||
"golang.org/x/net/webdav"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
Name string
|
||||
logger zerolog.Logger
|
||||
cache *cache.Cache
|
||||
rootListing atomic.Value
|
||||
lastRefresh time.Time
|
||||
refreshMutex sync.Mutex
|
||||
RootPath string
|
||||
}
|
||||
|
||||
func NewHandler(name string, cache *cache.Cache, logger zerolog.Logger) *Handler {
|
||||
h := &Handler{
|
||||
Name: name,
|
||||
cache: cache,
|
||||
logger: logger,
|
||||
RootPath: fmt.Sprintf("/%s", name),
|
||||
}
|
||||
|
||||
h.refreshRootListing()
|
||||
|
||||
// Start background refresh
|
||||
go h.backgroundRefresh()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *Handler) backgroundRefresh() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
h.refreshRootListing()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) refreshRootListing() {
|
||||
h.refreshMutex.Lock()
|
||||
defer h.refreshMutex.Unlock()
|
||||
|
||||
if time.Since(h.lastRefresh) < time.Minute {
|
||||
return
|
||||
}
|
||||
|
||||
var files []os.FileInfo
|
||||
h.cache.GetTorrents().Range(func(key, value interface{}) bool {
|
||||
cachedTorrent := value.(*cache.CachedTorrent)
|
||||
if cachedTorrent != nil && cachedTorrent.Torrent != nil {
|
||||
files = append(files, &FileInfo{
|
||||
name: cachedTorrent.Torrent.Name,
|
||||
size: 0,
|
||||
mode: 0755 | os.ModeDir,
|
||||
modTime: time.Now(),
|
||||
isDir: true,
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
h.rootListing.Store(files)
|
||||
h.lastRefresh = time.Now()
|
||||
}
|
||||
|
||||
func (h *Handler) getParentRootPath() string {
|
||||
return fmt.Sprintf("/webdav/%s", h.Name)
|
||||
}
|
||||
|
||||
func (h *Handler) getRootFileInfos() []os.FileInfo {
|
||||
if listing := h.rootListing.Load(); listing != nil {
|
||||
return listing.([]os.FileInfo)
|
||||
}
|
||||
return []os.FileInfo{}
|
||||
}
|
||||
|
||||
// Mkdir implements webdav.FileSystem
|
||||
func (h *Handler) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
||||
return os.ErrPermission // Read-only filesystem
|
||||
}
|
||||
|
||||
// RemoveAll implements webdav.FileSystem
|
||||
func (h *Handler) RemoveAll(ctx context.Context, name string) error {
|
||||
return os.ErrPermission // Read-only filesystem
|
||||
}
|
||||
|
||||
// Rename implements webdav.FileSystem
|
||||
func (h *Handler) Rename(ctx context.Context, oldName, newName string) error {
|
||||
return os.ErrPermission // Read-only filesystem
|
||||
}
|
||||
|
||||
func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
|
||||
name = path.Clean("/" + name)
|
||||
|
||||
// Fast path for root directory
|
||||
if name == h.getParentRootPath() {
|
||||
return &File{
|
||||
cache: h.cache,
|
||||
isDir: true,
|
||||
children: h.getRootFileInfos(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Remove root directory from path
|
||||
name = strings.TrimPrefix(name, h.getParentRootPath())
|
||||
name = strings.TrimPrefix(name, "/")
|
||||
parts := strings.SplitN(name, "/", 2)
|
||||
|
||||
// Get torrent from cache using sync.Map
|
||||
cachedTorrent := h.cache.GetTorrentByName(parts[0])
|
||||
if cachedTorrent == nil {
|
||||
h.logger.Debug().Msgf("Torrent not found: %s", parts[0])
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
return &File{
|
||||
cache: h.cache,
|
||||
cachedTorrent: cachedTorrent,
|
||||
isDir: true,
|
||||
children: h.getTorrentFileInfos(cachedTorrent.Torrent),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Use a map for faster file lookup
|
||||
fileMap := make(map[string]*torrent.File, len(cachedTorrent.Torrent.Files))
|
||||
for i := range cachedTorrent.Torrent.Files {
|
||||
fileMap[cachedTorrent.Torrent.Files[i].Name] = &cachedTorrent.Torrent.Files[i]
|
||||
}
|
||||
|
||||
if file, ok := fileMap[parts[1]]; ok {
|
||||
return &File{
|
||||
cache: h.cache,
|
||||
cachedTorrent: cachedTorrent,
|
||||
file: file,
|
||||
isDir: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
h.logger.Debug().Msgf("File not found: %s", name)
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
// Stat implements webdav.FileSystem
|
||||
func (h *Handler) Stat(ctx context.Context, name string) (os.FileInfo, error) {
|
||||
f, err := h.OpenFile(ctx, name, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.Stat()
|
||||
}
|
||||
|
||||
func (h *Handler) getTorrentFileInfos(torrent *torrent.Torrent) []os.FileInfo {
|
||||
files := make([]os.FileInfo, 0, len(torrent.Files))
|
||||
for _, file := range torrent.Files {
|
||||
files = append(files, &FileInfo{
|
||||
name: file.Name,
|
||||
size: file.Size,
|
||||
mode: 0644,
|
||||
modTime: time.Now(),
|
||||
isDir: false,
|
||||
})
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Handle OPTIONS
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
// Create WebDAV handler
|
||||
handler := &webdav.Handler{
|
||||
FileSystem: h,
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
Logger: func(r *http.Request, err error) {
|
||||
if err != nil {
|
||||
h.logger.Error().
|
||||
Err(err).
|
||||
Str("method", r.Method).
|
||||
Str("path", r.URL.Path).
|
||||
Msg("WebDAV error")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Special handling for GET requests on directories
|
||||
if r.Method == "GET" {
|
||||
if f, err := h.OpenFile(r.Context(), r.URL.Path, os.O_RDONLY, 0); err == nil {
|
||||
if fi, err := f.Stat(); err == nil && fi.IsDir() {
|
||||
h.serveDirectory(w, r, f)
|
||||
return
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) serveDirectory(w http.ResponseWriter, r *http.Request, file webdav.File) {
|
||||
var children []os.FileInfo
|
||||
if f, ok := file.(*File); ok {
|
||||
children = f.children
|
||||
} else {
|
||||
var err error
|
||||
children, err = file.Readdir(-1)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to list directory", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Clean and prepare the path
|
||||
cleanPath := path.Clean(r.URL.Path)
|
||||
parentPath := path.Dir(cleanPath)
|
||||
showParent := cleanPath != "/" && parentPath != "." && parentPath != cleanPath
|
||||
|
||||
// Prepare template data
|
||||
data := struct {
|
||||
Path string
|
||||
ParentPath string
|
||||
ShowParent bool
|
||||
Children []os.FileInfo
|
||||
}{
|
||||
Path: cleanPath,
|
||||
ParentPath: parentPath,
|
||||
ShowParent: showParent,
|
||||
Children: children,
|
||||
}
|
||||
|
||||
// Parse and execute template
|
||||
tmpl, err := template.New("directory").Parse(directoryTemplate)
|
||||
if err != nil {
|
||||
h.logger.Error().Err(err).Msg("Failed to parse directory template")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := tmpl.Execute(w, data); err != nil {
|
||||
h.logger.Error().Err(err).Msg("Failed to execute directory template")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user