diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 946ecf6..e792dfc 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -10,7 +10,7 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} + IMAGE_NAME: ${{ github.repository }}-testing jobs: build-and-push-image: diff --git a/Dockerfile b/Dockerfile index 2c8efde..d8c8cd9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,11 +9,11 @@ COPY . . RUN CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags="-s -w" -o zurg cmd/zurg/main.go # Obfuscation stage -# FROM alpine:3 AS obfuscator -# WORKDIR /app -# COPY --from=builder /app/zurg . -# RUN apk add --no-cache upx -# RUN upx --brute zurg +FROM alpine:3 AS obfuscator +WORKDIR /app +COPY --from=builder /app/zurg . +RUN apk add --no-cache upx +RUN upx --brute zurg # Final stage FROM alpine:3 diff --git a/cmd/zurg/main.go b/cmd/zurg/main.go index de6c243..e904121 100644 --- a/cmd/zurg/main.go +++ b/cmd/zurg/main.go @@ -16,12 +16,17 @@ import ( "github.com/debridmediamanager.com/zurg/internal/zfs" "github.com/debridmediamanager.com/zurg/pkg/chunk" "github.com/debridmediamanager.com/zurg/pkg/logutil" + "github.com/debridmediamanager.com/zurg/pkg/realdebrid" "github.com/hashicorp/golang-lru/v2/expirable" ) func main() { - rlog := logutil.NewLogger() - log := rlog.Named("zurg") + if len(os.Args) > 1 && os.Args[1] == "networktest" { + realdebrid.RunTest() + return + } + + log := logutil.NewLogger().Named("zurg") config, configErr := config.LoadZurgConfig("./config.yml") if configErr != nil { @@ -30,7 +35,9 @@ func main() { cache := expirable.NewLRU[string, string](1e4, nil, time.Hour) - torrentMgr := torrent.NewTorrentManager(config, cache) + rd := realdebrid.NewRealDebrid(config.GetToken(), config, logutil.NewLogger().Named("realdebrid")) + + torrentMgr := torrent.NewTorrentManager(config, rd) mux := http.NewServeMux() net.Router(mux, config, torrentMgr, cache) @@ -53,7 +60,8 @@ func main() { 1, // 1 chunk - load ahead (1MB total) max(runtime.NumCPU()/2, 1), // check threads max(runtime.NumCPU()/2, 1), // load threads - runtime.NumCPU()*2, // max chunks + runtime.NumCPU()*2, + torrentMgr, // max chunks config) if nil != err { log.Panicf("Failed to initialize chunk manager: %v", err) diff --git a/go.mod b/go.mod index eefa01b..60d7ecb 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,10 @@ go 1.21.3 require ( bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5 + github.com/elliotchance/orderedmap/v2 v2.2.0 github.com/hashicorp/golang-lru/v2 v2.0.7 go.uber.org/zap v1.26.0 - golang.org/x/sys v0.4.0 + golang.org/x/sys v0.14.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 252ca36..b762d23 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5 h1:A0NsYy4lDBZAC6QiYeJ4N+XuHIK bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5/go.mod h1:gG3RZAMXCa/OTes6rr9EwusmR1OH1tDDy+cg9c5YliY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk= +github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -16,8 +18,8 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/hosts.txt b/hosts.txt new file mode 100644 index 0000000..cd5a887 --- /dev/null +++ b/hosts.txt @@ -0,0 +1,76 @@ +20.download.real-debrid.cloud +21.download.real-debrid.cloud +22.download.real-debrid.cloud +23.download.real-debrid.cloud +30.download.real-debrid.cloud +31.download.real-debrid.cloud +32.download.real-debrid.cloud +34.download.real-debrid.cloud +40.download.real-debrid.cloud +41.download.real-debrid.cloud +42.download.real-debrid.cloud +43.download.real-debrid.cloud +44.download.real-debrid.cloud +45.download.real-debrid.cloud +50.download.real-debrid.cloud +51.download.real-debrid.cloud +52.download.real-debrid.cloud +53.download.real-debrid.cloud +54.download.real-debrid.cloud +55.download.real-debrid.cloud +56.download.real-debrid.cloud +57.download.real-debrid.cloud +58.download.real-debrid.cloud +59.download.real-debrid.cloud +60.download.real-debrid.cloud +61.download.real-debrid.cloud +62.download.real-debrid.cloud +63.download.real-debrid.cloud +64.download.real-debrid.cloud +65.download.real-debrid.cloud +66.download.real-debrid.cloud +67.download.real-debrid.cloud +68.download.real-debrid.cloud +69.download.real-debrid.cloud +20.download.real-debrid.com +21.download.real-debrid.com +22.download.real-debrid.com +23.download.real-debrid.com +30.download.real-debrid.com +31.download.real-debrid.com +32.download.real-debrid.com +34.download.real-debrid.com +40.download.real-debrid.com +41.download.real-debrid.com +42.download.real-debrid.com +43.download.real-debrid.com +44.download.real-debrid.com +45.download.real-debrid.com +50.download.real-debrid.com +51.download.real-debrid.com +52.download.real-debrid.com +53.download.real-debrid.com +54.download.real-debrid.com +55.download.real-debrid.com +56.download.real-debrid.com +57.download.real-debrid.com +58.download.real-debrid.com +59.download.real-debrid.com +60.download.real-debrid.com +61.download.real-debrid.com +62.download.real-debrid.com +63.download.real-debrid.com +64.download.real-debrid.com +65.download.real-debrid.com +66.download.real-debrid.com +67.download.real-debrid.com +68.download.real-debrid.com +69.download.real-debrid.com +hkg1.download.real-debrid.com +lax1.download.real-debrid.com +lon1.download.real-debrid.com +mum1.download.real-debrid.com +rbx.download.real-debrid.com +sgp1.download.real-debrid.com +tlv1.download.real-debrid.com +tyo1.download.real-debrid.com diff --git a/internal/config/load.go b/internal/config/load.go index 10139bd..b1bb3ba 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -23,11 +23,11 @@ type ConfigInterface interface { GetNetworkBufferSize() int GetMountPoint() string EnableRetainFolderNameExtension() bool + GetRandomPreferredHost() string } func LoadZurgConfig(filename string) (ConfigInterface, error) { - rlog := logutil.NewLogger() - log := rlog.Named("config") + log := logutil.NewLogger().Named("config") log.Debug("Loading config file ", filename) content, err := os.ReadFile(filename) diff --git a/internal/config/types.go b/internal/config/types.go index ddca6a5..109554c 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -1,18 +1,21 @@ package config +import "math/rand" + type ZurgConfig struct { - Version string `yaml:"zurg"` - Token string `yaml:"token"` - Host string `yaml:"host"` - Port string `yaml:"port"` - NumOfWorkers int `yaml:"concurrent_workers"` - RefreshEverySeconds int `yaml:"check_for_changes_every_secs"` - CacheTimeHours int `yaml:"info_cache_time_hours"` - CanRepair bool `yaml:"enable_repair"` - OnLibraryUpdate string `yaml:"on_library_update"` - NetworkBufferSize int `yaml:"network_buffer_size"` - MountPoint string `yaml:"mount_point"` - RetainFolderNameExtension bool `yaml:"retain_folder_name_extension"` + Version string `yaml:"zurg"` + Token string `yaml:"token"` + Host string `yaml:"host"` + Port string `yaml:"port"` + NumOfWorkers int `yaml:"concurrent_workers"` + RefreshEverySeconds int `yaml:"check_for_changes_every_secs"` + CacheTimeHours int `yaml:"info_cache_time_hours"` + CanRepair bool `yaml:"enable_repair"` + OnLibraryUpdate string `yaml:"on_library_update"` + NetworkBufferSize int `yaml:"network_buffer_size"` + MountPoint string `yaml:"mount_point"` + RetainFolderNameExtension bool `yaml:"retain_folder_name_extension"` + PreferredHosts []string `yaml:"preferred_hosts"` } func (z *ZurgConfig) GetToken() string { @@ -73,3 +76,11 @@ func (z *ZurgConfig) GetMountPoint() string { func (z *ZurgConfig) EnableRetainFolderNameExtension() bool { return z.RetainFolderNameExtension } + +func (z *ZurgConfig) GetRandomPreferredHost() string { + if len(z.PreferredHosts) == 0 { + return "" + } + randomIndex := rand.Intn(len(z.PreferredHosts)) + return z.PreferredHosts[randomIndex] +} diff --git a/internal/config/v1.go b/internal/config/v1.go index a199854..dd2bf9b 100644 --- a/internal/config/v1.go +++ b/internal/config/v1.go @@ -5,7 +5,6 @@ import ( "sort" "strings" - "github.com/debridmediamanager.com/zurg/pkg/logutil" "gopkg.in/yaml.v3" ) @@ -33,9 +32,6 @@ func (z *ZurgConfigV1) GetDirectories() []string { } func (z *ZurgConfigV1) GetGroupMap() map[string][]string { - rlog := logutil.NewLogger() - log := rlog.Named("config") - var groupMap = make(map[string][]string) var groupOrderMap = make(map[string]int) // To store GroupOrder for each directory @@ -43,7 +39,6 @@ func (z *ZurgConfigV1) GetGroupMap() map[string][]string { for directory, val := range z.Directories { groupMap[val.Group] = append(groupMap[val.Group], directory) groupOrderMap[directory] = val.GroupOrder - log.Debugf("Added directory to group: %s, group: %s, order: %d", directory, val.Group, val.GroupOrder) } // Sort the slice based on GroupOrder and then directory name for deterministic order @@ -55,7 +50,6 @@ func (z *ZurgConfigV1) GetGroupMap() map[string][]string { return groupOrderMap[dirs[i]] < groupOrderMap[dirs[j]] }) groupMap[group] = dirs - log.Debugf("Sorted directories within a group: %s %v", group, dirs) } // Return a deep copy of the map diff --git a/internal/dav/listing.go b/internal/dav/listing.go index d2653e6..b9bb1ce 100644 --- a/internal/dav/listing.go +++ b/internal/dav/listing.go @@ -5,29 +5,21 @@ import ( "fmt" "net/http" "path" + "path/filepath" "strings" "github.com/debridmediamanager.com/zurg/internal/config" "github.com/debridmediamanager.com/zurg/internal/torrent" "github.com/debridmediamanager.com/zurg/pkg/dav" "github.com/debridmediamanager.com/zurg/pkg/logutil" - "github.com/hashicorp/golang-lru/v2/expirable" ) -func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) { - rlog := logutil.NewLogger() - log := rlog.Named("dav") +func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface) { + log := logutil.NewLogger().Named("dav") requestPath := path.Clean(r.URL.Path) requestPath = strings.Trim(requestPath, "/") - if data, exists := cache.Get(requestPath); exists { - w.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"") - w.WriteHeader(http.StatusMultiStatus) - fmt.Fprint(w, data) - return - } - var output []byte var err error @@ -41,12 +33,16 @@ func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.To case len(filteredSegments) == 2: output, err = handleSingleTorrent(requestPath, w, r, t) default: - log.Errorf("Request %s %s not found", r.Method, requestPath) + 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 @@ -54,8 +50,6 @@ func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.To if output != nil { respBody := fmt.Sprintf("\n%s\n", output) - cache.Add(requestPath, respBody) - w.Header().Set("Content-Type", "text/xml; charset=\"utf-8\"") w.WriteHeader(http.StatusMultiStatus) fmt.Fprint(w, respBody) @@ -64,9 +58,9 @@ func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.To func handleRoot(w http.ResponseWriter, r *http.Request, c config.ConfigInterface) ([]byte, error) { var responses []dav.Response - responses = append(responses, dav.Directory("/")) + responses = append(responses, dav.Directory("")) for _, directory := range c.GetDirectories() { - responses = append(responses, dav.Directory("/"+directory)) + responses = append(responses, dav.Directory(directory)) } rootResponse := dav.MultiStatus{ XMLNS: "DAV:", @@ -80,10 +74,28 @@ func handleListOfTorrents(requestPath string, w http.ResponseWriter, r *http.Req for _, directory := range c.GetDirectories() { if basePath == directory { - torrents := t.GetByDirectory(basePath) - resp, err := createMultiTorrentResponse("/"+basePath, torrents) - if err != nil { - return nil, fmt.Errorf("cannot read directory (%s): %w", basePath, err) + var responses []dav.Response + + responses = append(responses, dav.Directory(basePath)) + + for el := t.TorrentMap.Front(); el != nil; el = el.Next() { + accessKey := el.Key + torrent := el.Value + if torrent.InProgress { + continue + } + for _, dir := range torrent.Directories { + if dir == basePath { + path := filepath.Join(basePath, accessKey) + responses = append(responses, dav.Directory(path)) + break + } + } + } + + resp := &dav.MultiStatus{ + XMLNS: "DAV:", + Response: responses, } return xml.Marshal(resp) } @@ -93,17 +105,37 @@ func handleListOfTorrents(requestPath string, w http.ResponseWriter, r *http.Req } func handleSingleTorrent(requestPath string, w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager) ([]byte, error) { - directory := path.Dir(requestPath) - torrentName := path.Base(requestPath) - - sameNameTorrents := t.FindAllTorrentsWithName(directory, torrentName) - if len(sameNameTorrents) == 0 { - return nil, fmt.Errorf("cannot find directory when generating single torrent: %s", requestPath) + accessKey := path.Base(requestPath) + torrent, exists := t.TorrentMap.Get(accessKey) + if !exists { + return nil, fmt.Errorf("cannot find torrent %s", accessKey) } - resp, err := createSingleTorrentResponse("/"+directory, sameNameTorrents) - if err != nil { - return nil, fmt.Errorf("cannot read directory (%s): %w", requestPath, err) + var responses []dav.Response + + // initial response is the directory itself + responses = append(responses, dav.Directory(requestPath)) + + for el := torrent.SelectedFiles.Front(); el != nil; el = el.Next() { + file := el.Value + if file.Link == "" { + // will be caught by torrent manager's repairAll + // just skip it for now + continue + } + filename := filepath.Base(file.Path) + filePath := filepath.Join(requestPath, filename) + responses = append(responses, dav.File( + filePath, + file.Bytes, + convertRFC3339toRFC1123(torrent.LatestAdded), + file.Link, + )) + } + + resp := &dav.MultiStatus{ + XMLNS: "DAV:", + Response: responses, } return xml.Marshal(resp) } diff --git a/internal/dav/response.go b/internal/dav/response.go deleted file mode 100644 index 6098b6f..0000000 --- a/internal/dav/response.go +++ /dev/null @@ -1,79 +0,0 @@ -package dav - -import ( - "path/filepath" - - "github.com/debridmediamanager.com/zurg/internal/torrent" - "github.com/debridmediamanager.com/zurg/pkg/dav" -) - -// createMultiTorrentResponse creates a WebDAV response for a list of torrents -func createMultiTorrentResponse(basePath string, torrents []torrent.Torrent) (*dav.MultiStatus, error) { - var responses []dav.Response - responses = append(responses, dav.Directory(basePath)) - - seen := make(map[string]bool) - - for _, item := range torrents { - if item.Progress != 100 { - continue - } - if _, exists := seen[item.Name]; exists { - continue - } - seen[item.Name] = true - - path := filepath.Join(basePath, item.Name) - responses = append(responses, dav.Directory(path)) - } - - return &dav.MultiStatus{ - XMLNS: "DAV:", - Response: responses, - }, nil -} - -// createTorrentResponse creates a WebDAV response for a single torrent -// but it also handles the case where there are many torrents with the same name -func createSingleTorrentResponse(basePath string, torrents []torrent.Torrent) (*dav.MultiStatus, error) { - var responses []dav.Response - - // initial response is the directory itself - currentPath := filepath.Join(basePath, torrents[0].Name) - responses = append(responses, dav.Directory(currentPath)) - - finalName := make(map[string]bool) - - var torrentResponses []dav.Response - - for _, torrent := range torrents { - for _, file := range torrent.SelectedFiles { - if file.Link == "" { - // TODO: fix the file? - // log.Println("File has no link, skipping (repairing links take time)", file.Path) - continue - } - - filename := filepath.Base(file.Path) - if finalName[filename] { - continue - } - finalName[filename] = true - - filePath := filepath.Join(currentPath, filename) - torrentResponses = append(torrentResponses, dav.File( - filePath, - file.Bytes, - convertRFC3339toRFC1123(torrent.Added), - file.Link, - )) - } - } - - responses = append(responses, torrentResponses...) - - return &dav.MultiStatus{ - XMLNS: "DAV:", - Response: responses, - }, nil -} diff --git a/internal/http/listing.go b/internal/http/listing.go index f641062..ddf0db7 100644 --- a/internal/http/listing.go +++ b/internal/http/listing.go @@ -5,27 +5,19 @@ import ( "net/http" "net/url" "path" + "path/filepath" "strings" "github.com/debridmediamanager.com/zurg/internal/config" "github.com/debridmediamanager.com/zurg/internal/torrent" "github.com/debridmediamanager.com/zurg/pkg/logutil" - "github.com/hashicorp/golang-lru/v2/expirable" ) -func HandleDirectoryListing(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) { - rlog := logutil.NewLogger() - log := rlog.Named("http") +func HandleDirectoryListing(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface) { + log := logutil.NewLogger().Named("http") requestPath := path.Clean(r.URL.Path) - if data, exists := cache.Get(requestPath); exists { - w.Header().Set("Content-Type", "text/html; charset=\"utf-8\"") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, data) - return - } - var output *string var err error @@ -38,20 +30,21 @@ func HandleDirectoryListing(w http.ResponseWriter, r *http.Request, t *torrent.T case len(filteredSegments) == 3: output, err = handleSingleTorrent(requestPath, w, r, t) default: - log.Errorf("Request %s %s not found", r.Method, requestPath) + 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 { - cache.Add(requestPath, *output) - w.Header().Set("Content-Type", "text/html; charset=\"utf-8\"") w.WriteHeader(http.StatusOK) fmt.Fprint(w, *output) @@ -74,12 +67,21 @@ func handleListOfTorrents(requestPath string, w http.ResponseWriter, r *http.Req for _, directory := range c.GetDirectories() { if basePath == directory { - torrents := t.GetByDirectory(basePath) - resp, err := createMultiTorrentResponse(requestPath, torrents) - if err != nil { - return nil, fmt.Errorf("cannot read directory (%s): %w", basePath, err) + htmlDoc := "
    " + for el := t.TorrentMap.Front(); el != nil; el = el.Next() { + accessKey := el.Key + torrent := el.Value + if torrent.InProgress { + continue + } + for _, dir := range torrent.Directories { + if dir == basePath { + htmlDoc += fmt.Sprintf("
  1. %s
  2. ", filepath.Join(requestPath, url.PathEscape(accessKey)), accessKey) + break + } + } } - return &resp, nil + return &htmlDoc, nil } } @@ -87,18 +89,23 @@ func handleListOfTorrents(requestPath string, w http.ResponseWriter, r *http.Req } func handleSingleTorrent(requestPath string, w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager) (*string, error) { - fullDir := path.Dir(requestPath) - directory := path.Base(fullDir) - torrentName := path.Base(requestPath) - - sameNameTorrents := t.FindAllTorrentsWithName(directory, torrentName) - if len(sameNameTorrents) == 0 { - return nil, fmt.Errorf("cannot find directory when generating single torrent: %s", requestPath) + accessKey := path.Base(requestPath) + torrent, _ := t.TorrentMap.Get(accessKey) + if torrent == nil { + return nil, fmt.Errorf("cannot find torrent %s", accessKey) } - resp, err := createSingleTorrentResponse(requestPath, sameNameTorrents) - if err != nil { - return nil, fmt.Errorf("cannot read directory (%s): %w", requestPath, err) + htmlDoc := "
      " + for el := torrent.SelectedFiles.Front(); el != nil; el = el.Next() { + file := el.Value + if file.Link == "" { + // will be caught by torrent manager's repairAll + // just skip it for now + continue + } + filename := filepath.Base(file.Path) + filePath := filepath.Join(requestPath, url.PathEscape(filename)) + htmlDoc += fmt.Sprintf("
    1. %s
    2. ", filePath, filename) } - return &resp, nil + return &htmlDoc, nil } diff --git a/internal/http/response.go b/internal/http/response.go deleted file mode 100644 index f488257..0000000 --- a/internal/http/response.go +++ /dev/null @@ -1,60 +0,0 @@ -package http - -import ( - "fmt" - "net/url" - "path/filepath" - - "github.com/debridmediamanager.com/zurg/internal/torrent" -) - -// createMultiTorrentResponse creates a WebDAV response for a list of torrents -func createMultiTorrentResponse(basePath string, torrents []torrent.Torrent) (string, error) { - htmlDoc := "