Use new router

This commit is contained in:
Ben Sarmiento
2023-11-30 22:46:29 +01:00
parent 5472f7d08d
commit 0ad879066e
13 changed files with 311 additions and 384 deletions

View File

@@ -11,7 +11,7 @@ FROM golang:1-alpine AS builder
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN apk add --no-cache bash git go gcc musl-dev curl fuse RUN apk add --no-cache bash git go gcc musl-dev curl fuse
RUN go build -ldflags="-s -w -X 'github.com/debridmediamanager/zurg/internal.BuiltAt=${BuiltAt}' -X 'github.com/debridmediamanager/zurg/internal.GoVersion=${GoVersion}' -X 'github.com/debridmediamanager/zurg/internal.GitCommit=${GitCommit}' -X 'github.com/debridmediamanager/zurg/internal.Version=${Version}'" -o zurg cmd/zurg RUN go build -ldflags="-s -w -X 'github.com/debridmediamanager/zurg/internal.BuiltAt=${BuiltAt}' -X 'github.com/debridmediamanager/zurg/internal.GoVersion=${GoVersion}' -X 'github.com/debridmediamanager/zurg/internal.GitCommit=${GitCommit}' -X 'github.com/debridmediamanager/zurg/internal.Version=${Version}'" -o zurg ./cmd/zurg
# Obfuscation stage # Obfuscation stage
FROM alpine:3 AS obfuscator FROM alpine:3 AS obfuscator

View File

@@ -14,14 +14,14 @@ async def extract_links(url):
async def benchmark(url): async def benchmark(url):
# This will still block, because subprocess.run is not async # This will still block, because subprocess.run is not async
subprocess.run(['hey', '-n', '10000', '-c', '50', url]) subprocess.run(['hey', '-n', '1000', '-c', '10', url])
url = 'http://localhost:9999/http/' url = 'http://localhost:9999/http/'
async def main(): async def main():
async for link in extract_links(url): async for link in extract_links(url):
print("BENCHMARKING " + link.replace('/http/', '/')) print("BENCHMARKING " + link.replace('/http/', '/dav/'))
await benchmark(link.replace('/http/', '/')) await benchmark(link.replace('/http/', '/dav/'))
print("BENCHMARKING " + link) print("BENCHMARKING " + link)
await benchmark(link) await benchmark(link)

2
go.mod
View File

@@ -11,6 +11,8 @@ require github.com/orcaman/concurrent-map/v2 v2.0.1
require github.com/panjf2000/ants/v2 v2.8.2 require github.com/panjf2000/ants/v2 v2.8.2
require github.com/julienschmidt/httprouter v1.3.0 // indirect
require ( require (
github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/dgraph-io/ristretto v0.1.1 github.com/dgraph-io/ristretto v0.1.1

2
go.sum
View File

@@ -14,6 +14,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c= github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM= github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
github.com/panjf2000/ants/v2 v2.8.2 h1:D1wfANttg8uXhC9149gRt1PDQ+dLVFjNXkCEycMcvQQ= github.com/panjf2000/ants/v2 v2.8.2 h1:D1wfANttg8uXhC9149gRt1PDQ+dLVFjNXkCEycMcvQQ=

View File

@@ -2,18 +2,19 @@ package internal
import ( import (
"fmt" "fmt"
"net/http" netHttp "net/http"
"os" "os"
"github.com/debridmediamanager/zurg/internal/config" "github.com/debridmediamanager/zurg/internal/config"
"github.com/debridmediamanager/zurg/internal/net" "github.com/debridmediamanager/zurg/internal/router"
"github.com/debridmediamanager/zurg/internal/torrent" "github.com/debridmediamanager/zurg/internal/torrent"
"github.com/debridmediamanager/zurg/internal/universal" "github.com/debridmediamanager/zurg/internal/universal"
zurghttp "github.com/debridmediamanager/zurg/pkg/http" "github.com/debridmediamanager/zurg/pkg/http"
"github.com/debridmediamanager/zurg/pkg/logutil" "github.com/debridmediamanager/zurg/pkg/logutil"
"github.com/debridmediamanager/zurg/pkg/realdebrid" "github.com/debridmediamanager/zurg/pkg/realdebrid"
"github.com/debridmediamanager/zurg/pkg/utils" "github.com/debridmediamanager/zurg/pkg/utils"
"github.com/dgraph-io/ristretto" "github.com/dgraph-io/ristretto"
"github.com/julienschmidt/httprouter"
"github.com/panjf2000/ants/v2" "github.com/panjf2000/ants/v2"
) )
@@ -27,7 +28,7 @@ func MainApp(configPath string) {
os.Exit(1) os.Exit(1)
} }
apiClient := zurghttp.NewHTTPClient(config.GetToken(), config.GetRetriesUntilFailed(), config.GetRealDebridTimeout(), config, log.Named("httpclient")) apiClient := http.NewHTTPClient(config.GetToken(), config.GetRetriesUntilFailed(), config.GetRealDebridTimeout(), config, log.Named("httpclient"))
rd := realdebrid.NewRealDebrid(apiClient, log.Named("realdebrid")) rd := realdebrid.NewRealDebrid(apiClient, log.Named("realdebrid"))
@@ -52,17 +53,17 @@ func MainApp(configPath string) {
torrentMgr := torrent.NewTorrentManager(config, rd, p, cache, log.Named("manager")) torrentMgr := torrent.NewTorrentManager(config, rd, p, cache, log.Named("manager"))
downloadClient := zurghttp.NewHTTPClient(config.GetToken(), config.GetRetriesUntilFailed(), 0, config, log.Named("dlclient")) downloadClient := http.NewHTTPClient(config.GetToken(), config.GetRetriesUntilFailed(), 0, config, log.Named("dlclient"))
getfile := universal.NewGetFile(downloadClient) getfile := universal.NewGetFile(downloadClient)
mux := http.NewServeMux() handler := httprouter.New()
net.Router(mux, getfile, config, torrentMgr, log.Named("net")) handler.RedirectTrailingSlash = false
handler.RedirectFixedPath = true
router.ApplyRouteTable(handler, getfile, torrentMgr, config, log.Named("router"))
addr := fmt.Sprintf("%s:%s", config.GetHost(), config.GetPort()) addr := fmt.Sprintf("%s:%s", config.GetHost(), config.GetPort())
server := &http.Server{Addr: addr, Handler: mux}
zurglog.Infof("Starting server on %s", addr) zurglog.Infof("Starting server on %s", addr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := netHttp.ListenAndServe(addr, handler); err != nil && err != netHttp.ErrServerClosed {
zurglog.Errorf("Failed to start server: %v", err) zurglog.Errorf("Failed to start server: %v", err)
os.Exit(1) os.Exit(1)
} }

View File

@@ -2,88 +2,36 @@ package dav
import ( import (
"fmt" "fmt"
"net/http"
"path"
"strings"
"github.com/debridmediamanager/zurg/internal/torrent" "github.com/debridmediamanager/zurg/internal/torrent"
"go.uber.org/zap"
) )
func HandleDeleteRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, log *zap.SugaredLogger) { func HandleDeleteTorrent(directory, torrentName string, t *torrent.TorrentManager) error {
requestPath := path.Clean(r.URL.Path)
filteredSegments := splitIntoSegments(requestPath)
var err error
switch {
case len(filteredSegments) == 0:
err = fmt.Errorf("cannot delete root")
case len(filteredSegments) == 1:
err = fmt.Errorf("cannot delete configured directory")
case len(filteredSegments) == 2:
err = handleDeleteTorrent(w, filteredSegments, t)
case len(filteredSegments) >= 3:
err = handleDeleteFile(w, filteredSegments, t)
default:
log.Warnf("Request %s %s not found", r.Method, requestPath)
http.Error(w, "Not Found", http.StatusNotFound)
return
}
if err != nil {
if strings.Contains(err.Error(), "cannot find") {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
log.Errorf("Error processing request: %v", err)
http.Error(w, "Server error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"")
}
func handleDeleteTorrent(w http.ResponseWriter, segments []string, t *torrent.TorrentManager) error {
directory := segments[0]
torrents, ok := t.DirectoryMap.Get(directory) torrents, ok := t.DirectoryMap.Get(directory)
if !ok { if !ok {
return fmt.Errorf("cannot find directory %s", directory) return fmt.Errorf("cannot find directory %s", directory)
} }
if !torrents.Has(torrentName) {
accessKey := segments[1] return fmt.Errorf("cannot find torrent %s", torrentName)
tor, ok := torrents.Get(accessKey)
if !ok {
return fmt.Errorf("cannot find torrent %s", accessKey)
} }
t.Delete(torrentName)
t.Delete(tor.AccessKey)
w.WriteHeader(http.StatusNoContent)
return nil return nil
} }
func handleDeleteFile(w http.ResponseWriter, segments []string, t *torrent.TorrentManager) error { func HandleDeleteFile(directory, torrentName, fileName string, t *torrent.TorrentManager) error {
directory := segments[0]
torrents, ok := t.DirectoryMap.Get(directory) torrents, ok := t.DirectoryMap.Get(directory)
if !ok { if !ok {
return fmt.Errorf("cannot find directory %s", directory) return fmt.Errorf("cannot find directory %s", directory)
} }
tor, ok := torrents.Get(torrentName)
accessKey := segments[1]
tor, ok := torrents.Get(accessKey)
if !ok { if !ok {
return fmt.Errorf("cannot find torrent %s", accessKey) return fmt.Errorf("cannot find torrent %s", torrentName)
} }
file, ok := tor.SelectedFiles.Get(fileName)
// set filepath to last segment
filepath := segments[len(segments)-1]
file, ok := tor.SelectedFiles.Get(filepath)
if !ok { if !ok {
return fmt.Errorf("cannot find file %s", filepath) return fmt.Errorf("cannot find file %s", fileName)
} }
file.Link = "unselect" file.Link = "unselect"
t.ScheduleForRefresh() t.ScheduleForRefresh()
w.WriteHeader(http.StatusNoContent)
return nil return nil
} }

View File

@@ -2,8 +2,6 @@ package dav
import ( import (
"fmt" "fmt"
"net/http"
"path"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
@@ -13,44 +11,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, log *zap.SugaredLogger) { func HandleListDirectories(t *torrent.TorrentManager) (*string, error) {
requestPath := path.Clean(r.URL.Path)
var output *string
var err error
filteredSegments := splitIntoSegments(requestPath)
switch {
case len(filteredSegments) == 0:
output, err = handleListDirectories(w, t)
case len(filteredSegments) == 1:
output, err = handleListTorrents(w, requestPath, t, log)
case len(filteredSegments) == 2:
output, err = handleListFiles(w, requestPath, t, log)
default:
log.Warnf("Request %s %s not found", r.Method, requestPath)
http.Error(w, "Not Found", http.StatusNotFound)
return
}
if err != nil {
if strings.Contains(err.Error(), "cannot find") {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
log.Errorf("Error processing request: %v", err)
http.Error(w, "Server error", http.StatusInternalServerError)
return
}
if output != nil {
w.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, *output)
}
}
func handleListDirectories(w http.ResponseWriter, t *torrent.TorrentManager) (*string, error) {
davDoc := "<?xml version=\"1.0\" encoding=\"utf-8\"?><d:multistatus xmlns:d=\"DAV:\">" davDoc := "<?xml version=\"1.0\" encoding=\"utf-8\"?><d:multistatus xmlns:d=\"DAV:\">"
// initial response is the directory itself // initial response is the directory itself
davDoc += dav.BaseDirectory("", "") davDoc += dav.BaseDirectory("", "")
@@ -68,8 +29,7 @@ func handleListDirectories(w http.ResponseWriter, t *torrent.TorrentManager) (*s
return &davDoc, nil return &davDoc, nil
} }
func handleListTorrents(w http.ResponseWriter, requestPath string, t *torrent.TorrentManager, log *zap.SugaredLogger) (*string, error) { func HandleListTorrents(directory string, t *torrent.TorrentManager, log *zap.SugaredLogger) (*string, error) {
directory := path.Base(requestPath)
_, ok := t.DirectoryMap.Get(directory) _, ok := t.DirectoryMap.Get(directory)
if !ok { if !ok {
return nil, fmt.Errorf("cannot find directory %s", directory) return nil, fmt.Errorf("cannot find directory %s", directory)
@@ -92,20 +52,18 @@ func handleListTorrents(w http.ResponseWriter, requestPath string, t *torrent.To
} }
} }
func handleListFiles(w http.ResponseWriter, requestPath string, t *torrent.TorrentManager, log *zap.SugaredLogger) (*string, error) { func HandleListFiles(directory, torrentName string, t *torrent.TorrentManager, log *zap.SugaredLogger) (*string, error) {
directory := path.Base(path.Dir(requestPath))
torrents, ok := t.DirectoryMap.Get(directory) torrents, ok := t.DirectoryMap.Get(directory)
if !ok { if !ok {
return nil, fmt.Errorf("cannot find directory %s", directory) return nil, fmt.Errorf("cannot find directory %s", directory)
} }
accessKey := path.Base(requestPath) tor, ok := torrents.Get(torrentName)
tor, ok := torrents.Get(accessKey)
if !ok { if !ok {
return nil, fmt.Errorf("cannot find torrent %s", accessKey) return nil, fmt.Errorf("cannot find torrent %s", torrentName)
} }
if resp, ok := t.ResponseCache.Get(directory + "/" + accessKey + ".dav"); !ok { if resp, ok := t.ResponseCache.Get(directory + "/" + torrentName + ".dav"); !ok {
log.Debugf("Generating xml for torrent %s", accessKey) log.Debugf("Generating xml for torrent %s", torrentName)
davDoc := "<?xml version=\"1.0\" encoding=\"utf-8\"?><d:multistatus xmlns:d=\"DAV:\">" + dav.BaseDirectory(filepath.Join(directory, tor.AccessKey), tor.LatestAdded) davDoc := "<?xml version=\"1.0\" encoding=\"utf-8\"?><d:multistatus xmlns:d=\"DAV:\">" + dav.BaseDirectory(filepath.Join(directory, tor.AccessKey), tor.LatestAdded)
filenames := tor.SelectedFiles.Keys() filenames := tor.SelectedFiles.Keys()
sort.Strings(filenames) sort.Strings(filenames)

View File

@@ -2,9 +2,7 @@ package http
import ( import (
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"path"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
@@ -13,45 +11,9 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
func HandleDirectoryListing(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, log *zap.SugaredLogger) { func HandleListDirectories(torMgr *torrent.TorrentManager) (*string, error) {
requestPath := path.Clean(r.URL.Path)
var output *string
var err error
filteredSegments := removeEmptySegments(strings.Split(requestPath, "/"))
switch {
case len(filteredSegments) == 1:
output, err = handleRoot(t)
case len(filteredSegments) == 2:
output, err = handleListOfTorrents(requestPath, t, log)
case len(filteredSegments) == 3:
output, err = handleSingleTorrent(requestPath, t, log)
default:
log.Warnf("Request %s %s not found", r.Method, requestPath)
http.Error(w, "Not Found", http.StatusNotFound)
return
}
if err != nil {
if strings.Contains(err.Error(), "cannot find") {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
log.Errorf("Error processing request: %v", err)
http.Error(w, "Server error", http.StatusInternalServerError)
return
}
if output != nil {
w.Header().Set("Content-Type", "text/html; charset=\"utf-8\"")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, *output)
}
}
func handleRoot(t *torrent.TorrentManager) (*string, error) {
htmlDoc := "<ol>" htmlDoc := "<ol>"
directories := t.DirectoryMap.Keys() directories := torMgr.DirectoryMap.Keys()
sort.Strings(directories) sort.Strings(directories)
for _, directory := range directories { for _, directory := range directories {
if strings.HasPrefix(directory, "int__") { if strings.HasPrefix(directory, "int__") {
@@ -64,8 +26,7 @@ func handleRoot(t *torrent.TorrentManager) (*string, error) {
return &htmlDoc, nil return &htmlDoc, nil
} }
func handleListOfTorrents(requestPath string, t *torrent.TorrentManager, log *zap.SugaredLogger) (*string, error) { func HandleListTorrents(directory string, t *torrent.TorrentManager, log *zap.SugaredLogger) (*string, error) {
directory := path.Base(requestPath)
torrents, ok := t.DirectoryMap.Get(directory) torrents, ok := t.DirectoryMap.Get(directory)
if !ok { if !ok {
return nil, fmt.Errorf("cannot find directory %s", directory) return nil, fmt.Errorf("cannot find directory %s", directory)
@@ -85,7 +46,7 @@ func handleListOfTorrents(requestPath string, t *torrent.TorrentManager, log *za
return allTorrents[i].AccessKey < allTorrents[j].AccessKey return allTorrents[i].AccessKey < allTorrents[j].AccessKey
}) })
for _, tor := range allTorrents { for _, tor := range allTorrents {
htmlDoc = htmlDoc + fmt.Sprintf("<li><a href=\"%s/\">%s</a></li>", filepath.Join(requestPath, url.PathEscape(tor.AccessKey)), tor.AccessKey) htmlDoc = htmlDoc + fmt.Sprintf("<li><a href=\"/http/%s/\">%s</a></li>", filepath.Join(directory, url.PathEscape(tor.AccessKey)), tor.AccessKey)
} }
return &htmlDoc, nil return &htmlDoc, nil
} else { } else {
@@ -94,20 +55,18 @@ func handleListOfTorrents(requestPath string, t *torrent.TorrentManager, log *za
} }
} }
func handleSingleTorrent(requestPath string, t *torrent.TorrentManager, log *zap.SugaredLogger) (*string, error) { func HandleListFiles(directory, torrentName string, t *torrent.TorrentManager, log *zap.SugaredLogger) (*string, error) {
directory := path.Base(path.Dir(requestPath))
torrents, ok := t.DirectoryMap.Get(directory) torrents, ok := t.DirectoryMap.Get(directory)
if !ok { if !ok {
return nil, fmt.Errorf("cannot find directory %s", directory) return nil, fmt.Errorf("cannot find directory %s", directory)
} }
accessKey := path.Base(requestPath) tor, ok := torrents.Get(torrentName)
tor, ok := torrents.Get(accessKey)
if !ok { if !ok {
return nil, fmt.Errorf("cannot find torrent %s", accessKey) return nil, fmt.Errorf("cannot find torrent %s", torrentName)
} }
if resp, ok := t.ResponseCache.Get(directory + "/" + accessKey + ".html"); !ok { if resp, ok := t.ResponseCache.Get(directory + "/" + torrentName + ".html"); !ok {
log.Debugf("Generating html for torrent %s", accessKey) log.Debugf("Generating html for torrent %s", torrentName)
htmlDoc := "<ol>" htmlDoc := "<ol>"
filenames := tor.SelectedFiles.Keys() filenames := tor.SelectedFiles.Keys()
sort.Strings(filenames) sort.Strings(filenames)
@@ -116,8 +75,8 @@ func handleSingleTorrent(requestPath string, t *torrent.TorrentManager, log *zap
if file == nil || !strings.HasPrefix(file.Link, "http") { if file == nil || !strings.HasPrefix(file.Link, "http") {
continue continue
} }
filePath := filepath.Join(requestPath, url.PathEscape(filename)) filePath := filepath.Join(directory, torrentName, url.PathEscape(filename))
htmlDoc += fmt.Sprintf("<li><a href=\"%s\">%s</a></li>", filePath, filename) htmlDoc += fmt.Sprintf("<li><a href=\"/http/%s\">%s</a></li>", filePath, filename)
} }
return &htmlDoc, nil return &htmlDoc, nil
} else { } else {

View File

@@ -1,66 +0,0 @@
package net
import (
"net/http"
"path"
"strings"
"github.com/debridmediamanager/zurg/internal/config"
"github.com/debridmediamanager/zurg/internal/dav"
intHttp "github.com/debridmediamanager/zurg/internal/http"
"github.com/debridmediamanager/zurg/internal/torrent"
"github.com/debridmediamanager/zurg/internal/universal"
"go.uber.org/zap"
)
// Router creates a WebDAV router
func Router(mux *http.ServeMux, getfile *universal.GetFile, c config.ConfigInterface, t *torrent.TorrentManager, log *zap.SugaredLogger) {
mux.HandleFunc("/http/", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
requestPath := path.Clean(r.URL.Path)
if countNonEmptySegments(strings.Split(requestPath, "/")) > 3 {
getfile.HandleGetRequest(w, r, t, c, log)
} else {
intHttp.HandleDirectoryListing(w, r, t, log)
}
case http.MethodHead:
universal.HandleHeadRequest(w, r, t, log)
default:
log.Errorf("Request %s %s not supported yet", r.Method, r.URL.Path)
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
}
})
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "PROPFIND":
dav.HandlePropfindRequest(w, r, t, log)
case http.MethodDelete:
dav.HandleDeleteRequest(w, r, t, log)
case http.MethodGet:
getfile.HandleGetRequest(w, r, t, c, log)
case http.MethodOptions:
w.WriteHeader(http.StatusOK)
default:
log.Errorf("Request %s %s not supported yet", r.Method, r.URL.Path)
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
}
})
}
func countNonEmptySegments(urlSegments []string) int {
count := 0
for _, s := range urlSegments {
if s != "" {
count++
}
}
return count
}

172
internal/router/router.go Normal file
View File

@@ -0,0 +1,172 @@
package router
import (
"fmt"
"net/http"
"github.com/debridmediamanager/zurg/internal/config"
"github.com/debridmediamanager/zurg/internal/dav"
intHttp "github.com/debridmediamanager/zurg/internal/http"
"github.com/debridmediamanager/zurg/internal/torrent"
"github.com/debridmediamanager/zurg/internal/universal"
"github.com/julienschmidt/httprouter"
"go.uber.org/zap"
)
type ZurgRouter struct {
getfile *universal.GetFile
torMgr *torrent.TorrentManager
cfg config.ConfigInterface
log *zap.SugaredLogger
}
func ApplyRouteTable(router *httprouter.Router, getfile *universal.GetFile, torMgr *torrent.TorrentManager, cfg config.ConfigInterface, log *zap.SugaredLogger) {
zr := &ZurgRouter{
getfile: getfile,
torMgr: torMgr,
cfg: cfg,
log: log,
}
// http router
router.GET("/http/:directory/:torrent/:file", zr.universalDownloadFileHandler)
router.GET("/http/:directory/:torrent/", zr.httpTorrentDirectoryHandler)
router.GET("/http/:directory/", zr.httpDirectoryHandler)
router.GET("/http/", zr.httpRootHandler)
// HEAD route
router.HEAD("/http/:directory/:torrent/:file", zr.headFileHandler)
// dav router
router.GET("/dav/:directory/:torrent/:file", zr.universalDownloadFileHandler)
router.GET("/dav/:directory/:torrent/", zr.propfindTorrentHandler)
router.GET("/dav/:directory/", zr.propfindDirectoryHandler)
router.GET("/dav/", zr.propfindRootHandler)
// PROPFIND routes
router.Handle("PROPFIND", "/dav/:directory/:torrent/", zr.propfindTorrentHandler)
router.Handle("PROPFIND", "/dav/:directory/", zr.propfindDirectoryHandler)
router.Handle("PROPFIND", "/dav/", zr.propfindRootHandler)
// DELETE routes
router.DELETE("/dav/:directory/:torrent/:file", zr.deleteFileHandler)
router.DELETE("/dav/:directory/:torrent/", zr.deleteTorrentHandler)
// Global OPTIONS route
router.GlobalOPTIONS = http.HandlerFunc(zr.globalOptionsHandler)
// root route
router.GET("/", zr.rootHandler)
}
func (zr *ZurgRouter) httpTorrentDirectoryHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
directory := params.ByName("directory")
torrentName := params.ByName("torrent")
out, err := intHttp.HandleListFiles(directory, torrentName, zr.torMgr, zr.log)
if err != nil {
http.Error(resp, "Not Found", http.StatusNotFound)
return
}
resp.Header().Set("Content-Type", "text/html; charset=\"utf-8\"")
resp.WriteHeader(http.StatusOK)
fmt.Fprint(resp, *out)
}
func (zr *ZurgRouter) httpDirectoryHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
directory := params.ByName("directory")
out, err := intHttp.HandleListTorrents(directory, zr.torMgr, zr.log)
if err != nil {
http.Error(resp, "Not Found", http.StatusNotFound)
return
}
resp.Header().Set("Content-Type", "text/html; charset=\"utf-8\"")
resp.WriteHeader(http.StatusOK)
fmt.Fprint(resp, *out)
}
func (zr *ZurgRouter) httpRootHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
out, err := intHttp.HandleListDirectories(zr.torMgr)
if err != nil {
http.Error(resp, "Not Found", http.StatusNotFound)
return
}
resp.Header().Set("Content-Type", "text/html; charset=\"utf-8\"")
resp.WriteHeader(http.StatusOK)
fmt.Fprint(resp, *out)
}
func (zr *ZurgRouter) rootHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
fmt.Fprint(resp, "<h1>zurg</h1><a href=\"/http/\">HTTP</a><br><a href=\"/dav/\">DAV</a>")
}
func (zr *ZurgRouter) propfindTorrentHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
directory := params.ByName("directory")
torrentName := params.ByName("torrent")
out, err := dav.HandleListFiles(directory, torrentName, zr.torMgr, zr.log)
if err != nil {
http.Error(resp, "Not Found", http.StatusNotFound)
return
}
resp.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"")
resp.WriteHeader(http.StatusOK)
fmt.Fprint(resp, *out)
}
func (zr *ZurgRouter) propfindDirectoryHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
directory := params.ByName("directory")
out, err := dav.HandleListTorrents(directory, zr.torMgr, zr.log)
if err != nil {
http.Error(resp, "Not Found", http.StatusNotFound)
return
}
resp.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"")
resp.WriteHeader(http.StatusOK)
fmt.Fprint(resp, *out)
}
func (zr *ZurgRouter) propfindRootHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
out, err := dav.HandleListDirectories(zr.torMgr)
if err != nil {
http.Error(resp, "Not Found", http.StatusNotFound)
return
}
resp.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"")
resp.WriteHeader(http.StatusOK)
fmt.Fprint(resp, *out)
}
func (zr *ZurgRouter) deleteFileHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
directory := params.ByName("directory")
torrentName := params.ByName("torrent")
fileName := params.ByName("file")
if dav.HandleDeleteFile(directory, torrentName, fileName, zr.torMgr) != nil {
http.Error(resp, "Not Found", http.StatusNotFound)
return
}
resp.WriteHeader(http.StatusNoContent)
}
func (zr *ZurgRouter) deleteTorrentHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
directory := params.ByName("directory")
torrentName := params.ByName("torrent")
if dav.HandleDeleteTorrent(directory, torrentName, zr.torMgr) != nil {
http.Error(resp, "Not Found", http.StatusNotFound)
return
}
resp.WriteHeader(http.StatusNoContent)
}
func (zr *ZurgRouter) globalOptionsHandler(resp http.ResponseWriter, req *http.Request) {
resp.WriteHeader(http.StatusOK)
}
func (zr *ZurgRouter) universalDownloadFileHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
directory := params.ByName("directory")
torrentName := params.ByName("torrent")
fileName := params.ByName("file")
zr.getfile.HandleGetRequest(directory, torrentName, fileName, resp, req, zr.torMgr, zr.cfg, zr.log)
}
func (zr *ZurgRouter) headFileHandler(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
directory := params.ByName("directory")
torrentName := params.ByName("torrent")
fileName := params.ByName("file")
universal.HandleHeadRequest(directory, torrentName, fileName, resp, req, zr.torMgr, zr.log)
}

View File

@@ -67,26 +67,26 @@ func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, p
// Fetch downloads // Fetch downloads
t.DownloadCache = cmap.New[*realdebrid.Download]() t.DownloadCache = cmap.New[*realdebrid.Download]()
_ = t.workerPool.Submit(func() { // _ = t.workerPool.Submit(func() {
page := 1 // page := 1
offset := 0 // offset := 0
for { // for {
downloads, totalDownloads, err := t.Api.GetDownloads(page, offset) // downloads, totalDownloads, err := t.Api.GetDownloads(page, offset)
if err != nil { // if err != nil {
t.log.Fatalf("Cannot get downloads: %v\n", err) // t.log.Fatalf("Cannot get downloads: %v\n", err)
} // }
for i := range downloads { // for i := range downloads {
if !t.DownloadCache.Has(downloads[i].Link) { // if !t.DownloadCache.Has(downloads[i].Link) {
t.DownloadCache.Set(downloads[i].Link, &downloads[i]) // t.DownloadCache.Set(downloads[i].Link, &downloads[i])
} // }
} // }
offset += len(downloads) // offset += len(downloads)
page++ // page++
if offset >= totalDownloads { // if offset >= totalDownloads {
break // break
} // }
} // }
}) // })
var newTorrents []realdebrid.Torrent var newTorrents []realdebrid.Torrent
var err error var err error
@@ -869,7 +869,7 @@ func (t *TorrentManager) updateDirectoryResponsesCache() {
continue continue
} }
davRet += dav.Directory(tor.AccessKey, tor.LatestAdded) davRet += dav.Directory(tor.AccessKey, tor.LatestAdded)
htmlRet += fmt.Sprintf("<li><a href=\"/http/%s/%s\">%s</a></li>", directory, tor.AccessKey, tor.AccessKey) htmlRet += fmt.Sprintf("<li><a href=\"/http/%s/%s/\">%s</a></li>", directory, tor.AccessKey, tor.AccessKey)
} }
} }
@@ -897,7 +897,7 @@ func (t *TorrentManager) buildTorrentResponses(tor *Torrent) (string, string) {
davRet += dav.File(filename, file.Bytes, file.Ended) davRet += dav.File(filename, file.Bytes, file.Ended)
filePath := filepath.Join("$dir", tor.AccessKey, url.PathEscape(filename)) filePath := filepath.Join("$dir", tor.AccessKey, url.PathEscape(filename))
htmlRet += fmt.Sprintf("<li><a href=\"%s\">%s</a></li>", filePath, filename) htmlRet += fmt.Sprintf("<li><a href=\"/http/%s\">%s</a></li>", filePath, filename)
} }
davRet += "</d:multistatus>" davRet += "</d:multistatus>"

View File

@@ -5,13 +5,10 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/debridmediamanager/zurg/internal/config" "github.com/debridmediamanager/zurg/internal/config"
"github.com/debridmediamanager/zurg/internal/dav"
intHttp "github.com/debridmediamanager/zurg/internal/http"
intTor "github.com/debridmediamanager/zurg/internal/torrent" intTor "github.com/debridmediamanager/zurg/internal/torrent"
zurghttp "github.com/debridmediamanager/zurg/pkg/http" zurghttp "github.com/debridmediamanager/zurg/pkg/http"
"go.uber.org/zap" "go.uber.org/zap"
@@ -26,155 +23,131 @@ func NewGetFile(client *zurghttp.HTTPClient) *GetFile {
} }
// HandleGetRequest handles a GET request universally for both WebDAV and HTTP // HandleGetRequest handles a GET request universally for both WebDAV and HTTP
func (gf *GetFile) HandleGetRequest(w http.ResponseWriter, r *http.Request, t *intTor.TorrentManager, c config.ConfigInterface, log *zap.SugaredLogger) { func (gf *GetFile) HandleGetRequest(directory, torrentName, fileName string, resp http.ResponseWriter, req *http.Request, torMgr *intTor.TorrentManager, cfg config.ConfigInterface, log *zap.SugaredLogger) {
requestPath := path.Clean(r.URL.Path) torrents, ok := torMgr.DirectoryMap.Get(directory)
isDav := true if !ok {
if strings.Contains(requestPath, "/http") { log.Warnf("Cannot find directory %s", directory)
requestPath = strings.Replace(requestPath, "/http", "/", 1) http.Error(resp, "File not found", http.StatusNotFound)
isDav = false
}
if requestPath == "/favicon.ico" {
return return
} }
segments := strings.Split(requestPath, "/") torrent, ok := torrents.Get(torrentName)
// If there are less than 3 segments, return an error or adjust as needed if !ok {
if len(segments) <= 3 { log.Warnf("Cannot find torrent %sfrom path %s", torrentName, req.URL.Path)
if isDav { http.Error(resp, "File not found", http.StatusNotFound)
dav.HandlePropfindRequest(w, r, t, log)
} else {
intHttp.HandleDirectoryListing(w, r, t, log)
}
return return
} }
baseDirectory := segments[len(segments)-3] file, ok := torrent.SelectedFiles.Get(fileName)
accessKey := segments[len(segments)-2]
filename := segments[len(segments)-1]
torrents, ok := t.DirectoryMap.Get(baseDirectory)
if !ok { if !ok {
log.Warnf("Cannot find directory %s", baseDirectory) log.Warnf("Cannot find file %s from path %s", fileName, req.URL.Path)
http.Error(w, "File not found", http.StatusNotFound) http.Error(resp, "File not found", http.StatusNotFound)
return
}
torrent, ok := torrents.Get(accessKey)
if !ok {
log.Warnf("Cannot find torrent %s in the directory %s", accessKey, baseDirectory)
http.Error(w, "File not found", http.StatusNotFound)
return
}
file, ok := torrent.SelectedFiles.Get(filename)
if !ok {
log.Warnf("Cannot find file from path %s", requestPath)
http.Error(w, "File not found", http.StatusNotFound)
return return
} }
if !strings.HasPrefix(file.Link, "http") { if !strings.HasPrefix(file.Link, "http") {
// This is a dead file, serve an alternate file // This is a dead file, serve an alternate file
log.Warnf("File %s is not available", filename) log.Warnf("File %s is not available", fileName)
http.Error(w, "File is not available", http.StatusNotFound) http.Error(resp, "File is not available", http.StatusNotFound)
return return
} }
link := file.Link link := file.Link
if download, exists := t.DownloadCache.Get(link); exists { if download, exists := torMgr.DownloadCache.Get(link); exists {
if c.ShouldServeFromRclone() && t.Api.CanFetchFirstByte(download.Download) { if cfg.ShouldServeFromRclone() && torMgr.Api.CanFetchFirstByte(download.Download) {
redirect(w, r, download.Download, c) redirect(resp, req, download.Download, cfg)
return return
} else { } else {
err := gf.streamCachedLinkToResponse(download.Download, w, r, t, c, log) err := gf.streamCachedLinkToResponse(download.Download, resp, req, torMgr, cfg, log)
if err == nil { if err == nil {
return return
} }
} }
} }
resp := t.UnrestrictUntilOk(link) unrestrict := torMgr.UnrestrictUntilOk(link)
if resp == nil { if unrestrict == nil {
// log.Warnf("File %s is no longer available, link %s", filepath.Base(file.Path), link) // log.Warnf("File %s is no longer available, link %s", filepath.Base(file.Path), link)
file.Link = "repair" file.Link = "repair"
if c.EnableRepair() { if cfg.EnableRepair() {
// log.Debugf("File %s is marked for repair", filepath.Base(file.Path)) // log.Debugf("File %s is marked for repair", filepath.Base(file.Path))
t.ScheduleForRefresh() // force a recheck torMgr.ScheduleForRefresh() // force a recheck
} }
http.Error(w, "File is not available", http.StatusNotFound) http.Error(resp, "File is not available", http.StatusNotFound)
return return
} else { } else {
if resp.Filename != filename { if unrestrict.Filename != fileName {
// this is possible if there's only 1 streamable file in the torrent // this is possible if there's only 1 streamable file in the torrent
// and then suddenly it's a rar file // and then suddenly it's a rar file
actualExt := filepath.Ext(resp.Filename) actualExt := filepath.Ext(unrestrict.Filename)
expectedExt := filepath.Ext(filename) expectedExt := filepath.Ext(fileName)
if actualExt != expectedExt && resp.Streamable != 1 { if actualExt != expectedExt && unrestrict.Streamable != 1 {
log.Warnf("File was changed and is not streamable: %s and %s", filename, resp.Filename) log.Warnf("File was changed and is not streamable: %s and %s", fileName, unrestrict.Filename)
http.Error(w, "File is not available", http.StatusNotFound) http.Error(resp, "File is not available", http.StatusNotFound)
return return
} else { } else {
log.Warnf("Filename mismatch: %s and %s", filename, resp.Filename) log.Warnf("Filename mismatch: %s and %s", fileName, unrestrict.Filename)
} }
} }
t.DownloadCache.Set(link, resp) torMgr.DownloadCache.Set(link, unrestrict)
if c.ShouldServeFromRclone() { if cfg.ShouldServeFromRclone() {
redirect(w, r, resp.Download, c) redirect(resp, req, unrestrict.Download, cfg)
} else { } else {
gf.streamFileToResponse(file, resp.Download, w, r, t, c, log) gf.streamFileToResponse(file, unrestrict.Download, resp, req, torMgr, cfg, log)
} }
return return
} }
} }
func (gf *GetFile) streamCachedLinkToResponse(url string, w http.ResponseWriter, r *http.Request, torMgr *intTor.TorrentManager, cfg config.ConfigInterface, log *zap.SugaredLogger) error { func (gf *GetFile) streamCachedLinkToResponse(url string, resp http.ResponseWriter, req *http.Request, torMgr *intTor.TorrentManager, cfg config.ConfigInterface, log *zap.SugaredLogger) error {
// Create a new request for the file download. // Create a new dlReq for the file download.
req, err := http.NewRequest(http.MethodGet, url, nil) dlReq, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil { if err != nil {
return fmt.Errorf("file is not available") return fmt.Errorf("file is not available")
} }
// copy range header if it exists // copy range header if it exists
if r.Header.Get("Range") != "" { if req.Header.Get("Range") != "" {
req.Header.Add("Range", r.Header.Get("Range")) dlReq.Header.Add("Range", req.Header.Get("Range"))
} }
resp, err := gf.client.Do(req) download, err := gf.client.Do(dlReq)
if err != nil { if err != nil {
return fmt.Errorf("file is not available") return fmt.Errorf("file is not available")
} }
defer resp.Body.Close() defer download.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent { if download.StatusCode != http.StatusOK && download.StatusCode != http.StatusPartialContent {
return fmt.Errorf("file is not available") return fmt.Errorf("file is not available")
} }
for k, vv := range resp.Header { for k, vv := range download.Header {
for _, v := range vv { for _, v := range vv {
w.Header().Add(k, v) resp.Header().Add(k, v)
} }
} }
buf := make([]byte, cfg.GetNetworkBufferSize()) buf := make([]byte, cfg.GetNetworkBufferSize())
io.CopyBuffer(w, resp.Body, buf) io.CopyBuffer(resp, download.Body, buf)
return nil return nil
} }
func (gf *GetFile) streamFileToResponse(file *intTor.File, url string, w http.ResponseWriter, r *http.Request, torMgr *intTor.TorrentManager, cfg config.ConfigInterface, log *zap.SugaredLogger) { func (gf *GetFile) streamFileToResponse(file *intTor.File, url string, resp http.ResponseWriter, req *http.Request, torMgr *intTor.TorrentManager, cfg config.ConfigInterface, log *zap.SugaredLogger) {
// Create a new request for the file download. // Create a new request for the file download.
req, err := http.NewRequest(http.MethodGet, url, nil) dlReq, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil { if err != nil {
if file != nil { if file != nil {
log.Errorf("Error creating new request for file %s: %v", file.Path, err) log.Errorf("Error creating new request for file %s: %v", file.Path, err)
} }
http.Error(w, "File is not available", http.StatusNotFound) http.Error(resp, "File is not available", http.StatusNotFound)
return return
} }
// copy range header if it exists // copy range header if it exists
if r.Header.Get("Range") != "" { if req.Header.Get("Range") != "" {
req.Header.Add("Range", r.Header.Get("Range")) dlReq.Header.Add("Range", req.Header.Get("Range"))
} }
resp, err := gf.client.Do(req) download, err := gf.client.Do(dlReq)
if err != nil { if err != nil {
if file != nil { if file != nil {
log.Warnf("Cannot download file %s: %v", file.Path, err) log.Warnf("Cannot download file %s: %v", file.Path, err)
@@ -184,40 +157,40 @@ func (gf *GetFile) streamFileToResponse(file *intTor.File, url string, w http.Re
torMgr.ScheduleForRefresh() // force a recheck torMgr.ScheduleForRefresh() // force a recheck
} }
} }
http.Error(w, "File is not available", http.StatusNotFound) http.Error(resp, "File is not available", http.StatusNotFound)
return return
} }
defer resp.Body.Close() defer download.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent { if download.StatusCode != http.StatusOK && download.StatusCode != http.StatusPartialContent {
if file != nil { if file != nil {
log.Warnf("Received a %s status code for file %s", resp.Status, file.Path) log.Warnf("Received a %s status code for file %s", download.Status, file.Path)
file.Link = "repair" file.Link = "repair"
if cfg.EnableRepair() { if cfg.EnableRepair() {
// log.Debugf("File %s is marked for repair", filepath.Base(file.Path)) // log.Debugf("File %s is marked for repair", filepath.Base(file.Path))
torMgr.ScheduleForRefresh() // force a recheck torMgr.ScheduleForRefresh() // force a recheck
} }
} }
http.Error(w, "File is not available", http.StatusNotFound) http.Error(resp, "File is not available", http.StatusNotFound)
return return
} }
for k, vv := range resp.Header { for k, vv := range download.Header {
for _, v := range vv { for _, v := range vv {
w.Header().Add(k, v) resp.Header().Add(k, v)
} }
} }
buf := make([]byte, cfg.GetNetworkBufferSize()) buf := make([]byte, cfg.GetNetworkBufferSize())
io.CopyBuffer(w, resp.Body, buf) io.CopyBuffer(resp, download.Body, buf)
} }
func redirect(w http.ResponseWriter, r *http.Request, url string, c config.ConfigInterface) { func redirect(resp http.ResponseWriter, req *http.Request, url string, cfg config.ConfigInterface) {
prefHost := c.GetRandomPreferredHost() prefHost := cfg.GetRandomPreferredHost()
if prefHost != "" { if prefHost != "" {
url = replaceHostInURL(url, prefHost) url = replaceHostInURL(url, prefHost)
} }
http.Redirect(w, r, url, http.StatusFound) http.Redirect(resp, req, url, http.StatusFound)
} }
func replaceHostInURL(inputURL string, newHost string) string { func replaceHostInURL(inputURL string, newHost string) string {

View File

@@ -3,7 +3,6 @@ package universal
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -11,55 +10,34 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
const ( func HandleHeadRequest(directory, torrentName, fileName string, w http.ResponseWriter, req *http.Request, t *torrent.TorrentManager, log *zap.SugaredLogger) {
SPLIT_TOKEN = "$"
)
func HandleHeadRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, log *zap.SugaredLogger) { torrents, ok := t.DirectoryMap.Get(directory)
requestPath := path.Clean(r.URL.Path)
requestPath = strings.Replace(requestPath, "/http", "", 1)
if requestPath == "/favicon.ico" {
return
}
segments := strings.Split(requestPath, "/")
// If there are less than 3 segments, return an error or adjust as needed
if len(segments) < 4 {
log.Errorf("Request %s %s not supported yet", r.Method, r.URL.Path)
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
return
}
baseDirectory := segments[len(segments)-3]
accessKey := segments[len(segments)-2]
filename := segments[len(segments)-1]
torrents, ok := t.DirectoryMap.Get(baseDirectory)
if !ok { if !ok {
log.Warnf("Cannot find directory %s", baseDirectory) log.Warnf("Cannot find directory %s", directory)
http.Error(w, "File not found", http.StatusNotFound) http.Error(w, "File not found", http.StatusNotFound)
return return
} }
torrent, ok := torrents.Get(accessKey) torrent, ok := torrents.Get(torrentName)
if !ok { if !ok {
log.Warnf("Cannot find torrent %s in the directory %s", accessKey, baseDirectory) log.Warnf("Cannot find torrent %s from path %s", torrentName, req.URL.Path)
http.Error(w, "File not found", http.StatusNotFound) http.Error(w, "File not found", http.StatusNotFound)
return return
} }
file, _ := torrent.SelectedFiles.Get(filename) file, _ := torrent.SelectedFiles.Get(fileName)
if file == nil { if file == nil {
log.Warnf("Cannot find file from path %s", requestPath) log.Warnf("Cannot find file %s from path %s", fileName, req.URL.Path)
http.Error(w, "Cannot find file", http.StatusNotFound) http.Error(w, "Cannot find file", http.StatusNotFound)
return return
} }
if !strings.HasPrefix(file.Link, "http") { if !strings.HasPrefix(file.Link, "http") {
// This is a dead file, serve an alternate file // This is a dead file, serve an alternate file
log.Warnf("File %s is no longer available", filename) log.Warnf("File %s is no longer available", fileName)
http.Error(w, "Cannot find file", http.StatusNotFound) http.Error(w, "Cannot find file", http.StatusNotFound)
return return
} }
contentType := getContentMimeType(filename) contentType := getContentMimeType(fileName)
contentLength := fmt.Sprintf("%d", file.Bytes) contentLength := fmt.Sprintf("%d", file.Bytes)
lastModified := file.Ended lastModified := file.Ended
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", contentType)