Fix issues with repair, move to a different streaming option
This commit is contained in:
@@ -2,6 +2,7 @@ package webdav
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||
"golang.org/x/net/webdav"
|
||||
@@ -415,104 +416,90 @@ func (h *Handler) serveDirectory(w http.ResponseWriter, r *http.Request, file we
|
||||
func (h *Handler) handleGet(w http.ResponseWriter, r *http.Request) {
|
||||
fRaw, err := h.OpenFile(r.Context(), r.URL.Path, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
h.logger.Error().Err(err).
|
||||
Str("path", r.URL.Path).
|
||||
Msg("Failed to open file")
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
defer func(fRaw webdav.File) {
|
||||
err := fRaw.Close()
|
||||
if err != nil {
|
||||
h.logger.Error().Err(err).Msg("Failed to close file")
|
||||
return
|
||||
}
|
||||
}(fRaw)
|
||||
defer fRaw.Close()
|
||||
|
||||
fi, err := fRaw.Stat()
|
||||
if err != nil {
|
||||
h.logger.Error().Err(err).Msg("Failed to stat file")
|
||||
http.Error(w, "Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// If the target is a directory, use your directory listing logic.
|
||||
if fi.IsDir() {
|
||||
h.serveDirectory(w, r, fRaw)
|
||||
return
|
||||
}
|
||||
|
||||
// Checks if the file is a torrent file
|
||||
// .content is nil if the file is a torrent file
|
||||
// .content means file is preloaded, e.g version.txt
|
||||
if file, ok := fRaw.(*File); ok && file.content == nil {
|
||||
link, err := file.getDownloadLink()
|
||||
if err != nil {
|
||||
h.logger.Debug().
|
||||
Err(err).
|
||||
Str("link", file.link).
|
||||
Str("path", r.URL.Path).
|
||||
Msg("Could not fetch download link")
|
||||
http.Error(w, "Could not fetch download link", http.StatusPreconditionFailed)
|
||||
return
|
||||
}
|
||||
if link == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
file.downloadLink = link
|
||||
// If the torrent file is not a RAR file and users enabled proxy streaming
|
||||
if !file.isRar && h.cache.StreamWithRclone() {
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Expires", "0")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fi.Name()))
|
||||
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")
|
||||
w.Header().Set("X-Accel-Redirect", file.downloadLink)
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
http.Redirect(w, r, file.downloadLink, http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ETags
|
||||
// Set common headers
|
||||
etag := fmt.Sprintf("\"%x-%x\"", fi.ModTime().Unix(), fi.Size())
|
||||
w.Header().Set("ETag", etag)
|
||||
w.Header().Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat))
|
||||
|
||||
// 7. Content-Type by extension
|
||||
ext := filepath.Ext(fi.Name())
|
||||
contentType := mime.TypeByExtension(ext)
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
if contentType := mime.TypeByExtension(ext); contentType != "" {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
}
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
rs, ok := fRaw.(io.ReadSeeker)
|
||||
if !ok {
|
||||
if r.Header.Get("Range") != "" {
|
||||
http.Error(w, "Range not supported", http.StatusRequestedRangeNotSatisfiable)
|
||||
// Handle File struct with direct streaming
|
||||
if file, ok := fRaw.(*File); ok {
|
||||
// Handle nginx proxy (X-Accel-Redirect)
|
||||
if file.content == nil && !file.isRar && h.cache.StreamWithRclone() {
|
||||
link, err := file.getDownloadLink()
|
||||
if err != nil || link == "" {
|
||||
http.Error(w, "Could not fetch download link", http.StatusPreconditionFailed)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fi.Name()))
|
||||
w.Header().Set("X-Accel-Redirect", link)
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
http.Redirect(w, r, link, http.StatusFound)
|
||||
return
|
||||
}
|
||||
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:
|
||||
|
||||
if err := file.StreamResponse(w, r); err != nil {
|
||||
var streamErr *streamError
|
||||
if errors.As(err, &streamErr) {
|
||||
// Handle client disconnections silently (just debug log)
|
||||
if errors.Is(streamErr.Err, context.Canceled) || errors.Is(streamErr.Err, context.DeadlineExceeded) || streamErr.IsClientDisconnection {
|
||||
return // Don't log as error or try to write response
|
||||
}
|
||||
|
||||
if streamErr.StatusCode > 0 && !hasHeadersWritten(w) {
|
||||
http.Error(w, streamErr.Error(), streamErr.StatusCode)
|
||||
} else {
|
||||
h.logger.Error().
|
||||
Err(streamErr.Err).
|
||||
Str("path", r.URL.Path).
|
||||
Msg("Stream error")
|
||||
}
|
||||
} else {
|
||||
// Generic error
|
||||
if !hasHeadersWritten(w) {
|
||||
http.Error(w, "Stream error", http.StatusInternalServerError)
|
||||
} else {
|
||||
h.logger.Error().
|
||||
Err(err).
|
||||
Str("path", r.URL.Path).
|
||||
Msg("Stream error after headers written")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
http.ServeContent(w, r, fi.Name(), fi.ModTime(), rs)
|
||||
|
||||
// Fallback to ServeContent for other webdav.File implementations
|
||||
if rs, ok := fRaw.(io.ReadSeeker); ok {
|
||||
http.ServeContent(w, r, fi.Name(), fi.ModTime(), rs)
|
||||
} else {
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = io.Copy(w, fRaw)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleHead(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user