From d10a6ddedd94db0e7c15f3bf172de243bf0211f9 Mon Sep 17 00:00:00 2001 From: Mukhtar Akere Date: Thu, 22 May 2025 22:23:49 +0100 Subject: [PATCH] - Add etags to stream url - Support for non-File files with range instead of readint to memory - Log more errors for reealdebrid --- pkg/debrid/realdebrid/realdebrid.go | 2 +- pkg/webdav/handler.go | 47 +++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/pkg/debrid/realdebrid/realdebrid.go b/pkg/debrid/realdebrid/realdebrid.go index 5d097b4..c38d1e8 100644 --- a/pkg/debrid/realdebrid/realdebrid.go +++ b/pkg/debrid/realdebrid/realdebrid.go @@ -502,7 +502,7 @@ func (r *RealDebrid) _getDownloadLink(file *types.File) (*types.DownloadLink, er } var data ErrorResponse if err = json.Unmarshal(b, &data); err != nil { - return nil, err + return nil, fmt.Errorf("error unmarshalling %d || %s \n %s", resp.StatusCode, err, string(b)) } switch data.ErrorCode { case 19: diff --git a/pkg/webdav/handler.go b/pkg/webdav/handler.go index f1eb408..df1089a 100644 --- a/pkg/webdav/handler.go +++ b/pkg/webdav/handler.go @@ -1,10 +1,10 @@ package webdav import ( - "bytes" "context" "fmt" "io" + "mime" "net/http" "os" "path" @@ -388,6 +388,7 @@ func (h *Handler) handleGet(w http.ResponseWriter, r *http.Request) { defer func(fRaw webdav.File) { err := fRaw.Close() if err != nil { + h.logger.Error().Err(err).Msg("Failed to close file") return } }(fRaw) @@ -426,27 +427,47 @@ func (h *Handler) handleGet(w http.ResponseWriter, r *http.Request) { file.downloadLink = link if h.cache.StreamWithRclone() { // Redirect to the download link - http.Redirect(w, r, file.downloadLink, http.StatusFound) + http.Redirect(w, r, file.downloadLink, http.StatusTemporaryRedirect) return } } + // ETags + etag := fmt.Sprintf("\"%x-%x\"", fi.ModTime().Unix(), fi.Size()) + w.Header().Set("ETag", etag) + + // 7. Content-Type by extension + ext := filepath.Ext(fi.Name()) + contentType := mime.TypeByExtension(ext) + if contentType == "" { + contentType = "application/octet-stream" + } + w.Header().Set("Content-Type", contentType) + rs, ok := fRaw.(io.ReadSeeker) if !ok { - // If not, read the entire file into memory as a fallback. - buf, err := io.ReadAll(fRaw) - if err != nil { - h.logger.Error().Err(err).Msg("Failed to read file content") - http.Error(w, "Server Error", http.StatusInternalServerError) + if r.Header.Get("Range") != "" { + http.Error(w, "Range not supported", http.StatusRequestedRangeNotSatisfiable) return } - rs = bytes.NewReader(buf) + w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size())) + w.Header().Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat)) + w.Header().Set("Accept-Ranges", "bytes") + ctx := r.Context() + done := make(chan struct{}) + go func() { + defer close(done) + io.Copy(w, fRaw) + }() + select { + case <-ctx.Done(): + h.logger.Debug().Msg("Client cancelled download") + return + case <-done: + } + return } - fileName := fi.Name() - contentType := getContentType(fileName) - w.Header().Set("Content-Type", contentType) - // http.ServeContent automatically handles Range requests. - http.ServeContent(w, r, fileName, fi.ModTime(), rs) + http.ServeContent(w, r, fi.Name(), fi.ModTime(), rs) } func (h *Handler) handleHead(w http.ResponseWriter, r *http.Request) {