Merge pull request #3 from debridmediamanager/finalize-repair
Finalize repair
This commit is contained in:
@@ -6,7 +6,7 @@ ARG GOARCH=amd64
|
|||||||
FROM golang:1-alpine AS builder
|
FROM golang:1-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags="-s -w" -o zurg cmd/zurg/main.go
|
RUN CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags="-s -w" -o zurg cmd/zurg/zurg.go
|
||||||
|
|
||||||
# Obfuscation stage
|
# Obfuscation stage
|
||||||
FROM alpine:3 AS obfuscator
|
FROM alpine:3 AS obfuscator
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/debridmediamanager.com/zurg/internal/config"
|
|
||||||
"github.com/debridmediamanager.com/zurg/internal/net"
|
|
||||||
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
|
||||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
config, configErr := config.LoadZurgConfig("./config.yml")
|
|
||||||
if configErr != nil {
|
|
||||||
log.Panicf("Config failed to load: %v", configErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
cache := expirable.NewLRU[string, string](1e4, nil, time.Hour)
|
|
||||||
|
|
||||||
t := torrent.NewTorrentManager(config, cache)
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
net.Router(mux, config, t, cache)
|
|
||||||
|
|
||||||
addr := fmt.Sprintf(":%s", config.GetPort())
|
|
||||||
log.Printf("Starting server on %s\n", addr)
|
|
||||||
err := http.ListenAndServe(addr, mux)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Failed to start server: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,6 +38,7 @@ func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.To
|
|||||||
case len(filteredSegments) == 2:
|
case len(filteredSegments) == 2:
|
||||||
output, err = handleSingleTorrent(requestPath, w, r, t)
|
output, err = handleSingleTorrent(requestPath, w, r, t)
|
||||||
default:
|
default:
|
||||||
|
log.Println("Not Found", r.Method, requestPath)
|
||||||
writeHTTPError(w, "Not Found", http.StatusNotFound)
|
writeHTTPError(w, "Not Found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -105,6 +106,5 @@ func handleSingleTorrent(requestPath string, w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeHTTPError(w http.ResponseWriter, errorMessage string, statusCode int) {
|
func writeHTTPError(w http.ResponseWriter, errorMessage string, statusCode int) {
|
||||||
log.Println(errorMessage)
|
|
||||||
http.Error(w, errorMessage, statusCode)
|
http.Error(w, errorMessage, statusCode)
|
||||||
}
|
}
|
||||||
@@ -1,278 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"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/davextra"
|
|
||||||
"github.com/debridmediamanager.com/zurg/pkg/realdebrid"
|
|
||||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HandleHeadRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) {
|
|
||||||
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.Println("Method not implemented", r.Method, r.URL.Path)
|
|
||||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if data, exists := cache.Get("head:" + requestPath); exists {
|
|
||||||
splits := strings.Split(data, " ")
|
|
||||||
contentType := splits[0]
|
|
||||||
contentLength := splits[1]
|
|
||||||
w.Header().Set("Content-Type", contentType)
|
|
||||||
w.Header().Set("Content-Length", contentLength)
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
baseDirectory := segments[len(segments)-3]
|
|
||||||
torrentName := segments[len(segments)-2]
|
|
||||||
filename := segments[len(segments)-1]
|
|
||||||
|
|
||||||
torrents := t.FindAllTorrentsWithName(baseDirectory, torrentName)
|
|
||||||
if torrents == nil {
|
|
||||||
log.Println("Cannot find torrent", requestPath)
|
|
||||||
http.Error(w, "Cannot find file", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filenameV2, linkFragment := davextra.ExtractLinkFragment(filename)
|
|
||||||
_, file := getFile(torrents, filenameV2, linkFragment)
|
|
||||||
if file == nil {
|
|
||||||
log.Println("Cannot find file (head)", requestPath)
|
|
||||||
http.Error(w, "Cannot find file", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if file.Link == "" {
|
|
||||||
log.Println("Link not found (head)", filename)
|
|
||||||
http.Error(w, "Cannot find file", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
contentType := getContentMimeType(filename)
|
|
||||||
contentLength := fmt.Sprintf("%d", file.Bytes)
|
|
||||||
w.Header().Set("Content-Type", contentType)
|
|
||||||
w.Header().Set("Content-Length", contentLength)
|
|
||||||
cache.Add("head:"+requestPath, contentType+" "+contentLength)
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getContentMimeType(filePath string) string {
|
|
||||||
switch filepath.Ext(filePath) {
|
|
||||||
case ".mkv":
|
|
||||||
return "video/x-matroska"
|
|
||||||
case ".mp4":
|
|
||||||
return "video/mp4"
|
|
||||||
case ".avi":
|
|
||||||
return "video/x-msvideo"
|
|
||||||
case ".mp3":
|
|
||||||
return "audio/mpeg"
|
|
||||||
case ".rar":
|
|
||||||
return "application/x-rar-compressed"
|
|
||||||
case ".zip":
|
|
||||||
return "application/zip"
|
|
||||||
case ".7z":
|
|
||||||
return "application/x-7z-compressed"
|
|
||||||
case ".srt":
|
|
||||||
return "text/plain"
|
|
||||||
default:
|
|
||||||
return "application/octet-stream"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) {
|
|
||||||
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 {
|
|
||||||
HandleDirectoryListing(w, r, t, c, cache)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if data, exists := cache.Get(requestPath); exists {
|
|
||||||
http.Redirect(w, r, data, http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
baseDirectory := segments[len(segments)-3]
|
|
||||||
torrentName := segments[len(segments)-2]
|
|
||||||
filename := segments[len(segments)-1]
|
|
||||||
|
|
||||||
torrents := t.FindAllTorrentsWithName(baseDirectory, torrentName)
|
|
||||||
if torrents == nil {
|
|
||||||
log.Println("Cannot find torrent", requestPath)
|
|
||||||
http.Error(w, "Cannot find file", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filenameV2, linkFragment := davextra.ExtractLinkFragment(filename)
|
|
||||||
_, file := getFile(torrents, filenameV2, linkFragment)
|
|
||||||
if file == nil {
|
|
||||||
log.Println("Cannot find file (get)", requestPath)
|
|
||||||
http.Error(w, "Cannot find file", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if file.Link == "" {
|
|
||||||
log.Println("Link not found (get)", filename)
|
|
||||||
http.Error(w, "Cannot find file", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
link := file.Link
|
|
||||||
|
|
||||||
unrestrictFn := func() (*realdebrid.UnrestrictResponse, error) {
|
|
||||||
return realdebrid.UnrestrictLink(c.GetToken(), link)
|
|
||||||
}
|
|
||||||
resp := realdebrid.RetryUntilOk(unrestrictFn)
|
|
||||||
if resp == nil {
|
|
||||||
log.Println("Cannot unrestrict link", link, filenameV2)
|
|
||||||
// t.HideTheFile(torrent, file)
|
|
||||||
// http.Error(w, "Cannot find file", http.StatusNotFound)
|
|
||||||
http.Redirect(w, r, "https://send.nukes.wtf/tDeTd0", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if resp.Filename != filenameV2 {
|
|
||||||
actualExt := filepath.Ext(resp.Filename)
|
|
||||||
expectedExt := filepath.Ext(filenameV2)
|
|
||||||
if actualExt != expectedExt {
|
|
||||||
log.Println("File extension mismatch", resp.Filename, filenameV2)
|
|
||||||
} else {
|
|
||||||
log.Println("Filename mismatch", resp.Filename, filenameV2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cache.Add(requestPath, resp.Download)
|
|
||||||
http.Redirect(w, r, resp.Download, http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFile finds a link by a fragment, it might be wrong
|
|
||||||
func getFile(torrents []torrent.Torrent, filename, fragment string) (*torrent.Torrent, *torrent.File) {
|
|
||||||
for t := range torrents {
|
|
||||||
for f, file := range torrents[t].SelectedFiles {
|
|
||||||
fname := filepath.Base(file.Path)
|
|
||||||
if filename == fname && strings.Contains(file.Link, fragment) {
|
|
||||||
return &torrents[t], &torrents[t].SelectedFiles[f]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleDirectoryListing(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) {
|
|
||||||
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
|
|
||||||
|
|
||||||
filteredSegments := removeEmptyStrings(strings.Split(requestPath, "/"))
|
|
||||||
switch {
|
|
||||||
case len(filteredSegments) == 1:
|
|
||||||
output, err = handleRoot(w, r, c)
|
|
||||||
case len(filteredSegments) == 2:
|
|
||||||
output, err = handleListOfTorrents(requestPath, w, r, t, c)
|
|
||||||
case len(filteredSegments) == 3:
|
|
||||||
output, err = handleSingleTorrent(requestPath, w, r, t)
|
|
||||||
case len(filteredSegments) > 3:
|
|
||||||
HandleGetRequest(w, r, t, c, cache)
|
|
||||||
default:
|
|
||||||
writeHTTPError(w, "Not Found", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error processing request: %v\n", err)
|
|
||||||
writeHTTPError(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleRoot(w http.ResponseWriter, r *http.Request, c config.ConfigInterface) (*string, error) {
|
|
||||||
htmlDoc := "<ul>"
|
|
||||||
|
|
||||||
for _, directory := range c.GetDirectories() {
|
|
||||||
directoryPath := url.PathEscape(directory)
|
|
||||||
htmlDoc += fmt.Sprintf("<li><a href=\"/http/%s/\">%s</a></li>", directoryPath, directory)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &htmlDoc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleListOfTorrents(requestPath string, w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface) (*string, error) {
|
|
||||||
basePath := path.Base(requestPath)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return &resp, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("cannot find directory when generating list: %s", requestPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := createSingleTorrentResponse(requestPath, sameNameTorrents)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot read directory (%s): %w", requestPath, err)
|
|
||||||
}
|
|
||||||
return &resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeHTTPError(w http.ResponseWriter, errorMessage string, statusCode int) {
|
|
||||||
log.Println(errorMessage)
|
|
||||||
http.Error(w, errorMessage, statusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeEmptyStrings(slice []string) []string {
|
|
||||||
var result []string
|
|
||||||
for _, s := range slice {
|
|
||||||
if s != "" {
|
|
||||||
result = append(result, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
101
internal/http/listing.go
Normal file
101
internal/http/listing.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/debridmediamanager.com/zurg/internal/config"
|
||||||
|
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
||||||
|
"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]) {
|
||||||
|
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
|
||||||
|
|
||||||
|
filteredSegments := removeEmptySegments(strings.Split(requestPath, "/"))
|
||||||
|
switch {
|
||||||
|
case len(filteredSegments) == 1:
|
||||||
|
output, err = handleRoot(w, r, c)
|
||||||
|
case len(filteredSegments) == 2:
|
||||||
|
output, err = handleListOfTorrents(requestPath, w, r, t, c)
|
||||||
|
case len(filteredSegments) == 3:
|
||||||
|
output, err = handleSingleTorrent(requestPath, w, r, t)
|
||||||
|
default:
|
||||||
|
log.Println("Not Found (http)", r.Method, requestPath, len(filteredSegments))
|
||||||
|
writeHTTPError(w, "Not Found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error processing request: %v\n", err)
|
||||||
|
writeHTTPError(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRoot(w http.ResponseWriter, r *http.Request, c config.ConfigInterface) (*string, error) {
|
||||||
|
htmlDoc := "<ul>"
|
||||||
|
|
||||||
|
for _, directory := range c.GetDirectories() {
|
||||||
|
directoryPath := url.PathEscape(directory)
|
||||||
|
htmlDoc += fmt.Sprintf("<li><a href=\"/http/%s/\">%s</a></li>", directoryPath, directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &htmlDoc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleListOfTorrents(requestPath string, w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface) (*string, error) {
|
||||||
|
basePath := path.Base(requestPath)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("cannot find directory when generating list: %s", requestPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := createSingleTorrentResponse(requestPath, sameNameTorrents)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot read directory (%s): %w", requestPath, err)
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
19
internal/http/util.go
Normal file
19
internal/http/util.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeHTTPError(w http.ResponseWriter, errorMessage string, statusCode int) {
|
||||||
|
http.Error(w, errorMessage, statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeEmptySegments(urlSegments []string) []string {
|
||||||
|
var result []string
|
||||||
|
for _, s := range urlSegments {
|
||||||
|
if s != "" {
|
||||||
|
result = append(result, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -3,11 +3,14 @@ package net
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/debridmediamanager.com/zurg/internal/config"
|
"github.com/debridmediamanager.com/zurg/internal/config"
|
||||||
"github.com/debridmediamanager.com/zurg/internal/dav"
|
"github.com/debridmediamanager.com/zurg/internal/dav"
|
||||||
intHttp "github.com/debridmediamanager.com/zurg/internal/http"
|
intHttp "github.com/debridmediamanager.com/zurg/internal/http"
|
||||||
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
||||||
|
"github.com/debridmediamanager.com/zurg/internal/universal"
|
||||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,10 +19,15 @@ func Router(mux *http.ServeMux, c config.ConfigInterface, t *torrent.TorrentMana
|
|||||||
mux.HandleFunc("/http/", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/http/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
intHttp.HandleDirectoryListing(w, r, t, c, cache)
|
requestPath := path.Clean(r.URL.Path)
|
||||||
|
if countNonEmptySegments(strings.Split(requestPath, "/")) > 3 {
|
||||||
|
universal.HandleGetRequest(w, r, t, c, cache)
|
||||||
|
} else {
|
||||||
|
intHttp.HandleDirectoryListing(w, r, t, c, cache)
|
||||||
|
}
|
||||||
|
|
||||||
case http.MethodHead:
|
case http.MethodHead:
|
||||||
intHttp.HandleHeadRequest(w, r, t, c, cache)
|
universal.HandleHeadRequest(w, r, t, c, cache)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Println("Method not implemented", r.Method)
|
log.Println("Method not implemented", r.Method)
|
||||||
@@ -33,7 +41,7 @@ func Router(mux *http.ServeMux, c config.ConfigInterface, t *torrent.TorrentMana
|
|||||||
dav.HandlePropfindRequest(w, r, t, c, cache)
|
dav.HandlePropfindRequest(w, r, t, c, cache)
|
||||||
|
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
dav.HandleGetRequest(w, r, t, c, cache)
|
universal.HandleGetRequest(w, r, t, c, cache)
|
||||||
|
|
||||||
case http.MethodOptions:
|
case http.MethodOptions:
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
@@ -44,3 +52,13 @@ func Router(mux *http.ServeMux, c config.ConfigInterface, t *torrent.TorrentMana
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func countNonEmptySegments(urlSegments []string) int {
|
||||||
|
count := 0
|
||||||
|
for _, s := range urlSegments {
|
||||||
|
if s != "" {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ type TorrentManager struct {
|
|||||||
// and store them in-memory
|
// and store them in-memory
|
||||||
func NewTorrentManager(config config.ConfigInterface, cache *expirable.LRU[string, string]) *TorrentManager {
|
func NewTorrentManager(config config.ConfigInterface, cache *expirable.LRU[string, string]) *TorrentManager {
|
||||||
t := &TorrentManager{
|
t := &TorrentManager{
|
||||||
requiredVersion: "24.10.2023",
|
requiredVersion: "26.10.2023",
|
||||||
config: config,
|
config: config,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
workerPool: make(chan bool, config.GetNumOfWorkers()),
|
workerPool: make(chan bool, config.GetNumOfWorkers()),
|
||||||
@@ -68,23 +68,6 @@ func NewTorrentManager(config config.ConfigInterface, cache *expirable.LRU[strin
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TorrentManager) repairAll(wg *sync.WaitGroup) {
|
|
||||||
wg.Wait()
|
|
||||||
for _, torrent := range t.torrents {
|
|
||||||
if torrent.ForRepair {
|
|
||||||
log.Println("Issues detected on", torrent.Name, "; fixing...")
|
|
||||||
t.repair(torrent.ID, torrent.SelectedFiles, true)
|
|
||||||
}
|
|
||||||
if len(torrent.Links) == 0 {
|
|
||||||
// If the torrent has no links
|
|
||||||
// and already processing repair
|
|
||||||
// delete it!
|
|
||||||
log.Println("Deleting", torrent.Name, "as it has no links")
|
|
||||||
realdebrid.DeleteTorrent(t.config.GetToken(), torrent.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetByDirectory returns all torrents that have a file in the specified directory
|
// GetByDirectory returns all torrents that have a file in the specified directory
|
||||||
func (t *TorrentManager) GetByDirectory(directory string) []Torrent {
|
func (t *TorrentManager) GetByDirectory(directory string) []Torrent {
|
||||||
var torrents []Torrent
|
var torrents []Torrent
|
||||||
@@ -99,11 +82,10 @@ func (t *TorrentManager) GetByDirectory(directory string) []Torrent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HideTheFile marks a file as deleted
|
// HideTheFile marks a file as deleted
|
||||||
// func (t *TorrentManager) HideTheFile(torrent *Torrent, file *File) {
|
func (t *TorrentManager) HideTheFile(torrent *Torrent, file *File) {
|
||||||
// file.Link = ""
|
file.Unavailable = true
|
||||||
// t.writeToFile(torrent)
|
t.repair(torrent.ID, torrent.SelectedFiles, false)
|
||||||
// t.repair(torrent.ID, []File{*file}, false)
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// FindAllTorrentsWithName finds all torrents in a given directory with a given name
|
// FindAllTorrentsWithName finds all torrents in a given directory with a given name
|
||||||
func (t *TorrentManager) FindAllTorrentsWithName(directory, torrentName string) []Torrent {
|
func (t *TorrentManager) FindAllTorrentsWithName(directory, torrentName string) []Torrent {
|
||||||
@@ -270,7 +252,7 @@ func (t *TorrentManager) addMoreInfo(torrent *Torrent) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Getting info for", torrent.ID)
|
// log.Println("Getting info for", torrent.ID)
|
||||||
info, err := realdebrid.GetTorrentInfo(t.config.GetToken(), torrent.ID)
|
info, err := realdebrid.GetTorrentInfo(t.config.GetToken(), torrent.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot get info: %v\n", err)
|
log.Printf("Cannot get info: %v\n", err)
|
||||||
@@ -282,9 +264,16 @@ func (t *TorrentManager) addMoreInfo(torrent *Torrent) {
|
|||||||
// if it is empty, it means the file is no longer available
|
// if it is empty, it means the file is no longer available
|
||||||
// Files+Links together are the same as SelectedFiles
|
// Files+Links together are the same as SelectedFiles
|
||||||
var selectedFiles []File
|
var selectedFiles []File
|
||||||
|
var streamableFiles []File
|
||||||
// if some Links are empty, we need to repair it
|
// if some Links are empty, we need to repair it
|
||||||
forRepair := false
|
forRepair := false
|
||||||
for _, file := range info.Files {
|
for _, file := range info.Files {
|
||||||
|
if isStreamable(file.Path) {
|
||||||
|
streamableFiles = append(streamableFiles, File{
|
||||||
|
File: file,
|
||||||
|
Link: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
if file.Selected == 0 {
|
if file.Selected == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -300,10 +289,14 @@ func (t *TorrentManager) addMoreInfo(torrent *Torrent) {
|
|||||||
var isChaotic bool
|
var isChaotic bool
|
||||||
selectedFiles, isChaotic = t.organizeChaos(info, selectedFiles)
|
selectedFiles, isChaotic = t.organizeChaos(info, selectedFiles)
|
||||||
if isChaotic {
|
if isChaotic {
|
||||||
log.Println("This torrent is unfixable, ignoring", info.Name, info.ID)
|
log.Println("This torrent is unfixable, it's always returning an unstreamable link, ignoring", info.Name, info.ID)
|
||||||
} else {
|
} else {
|
||||||
log.Println("Marking for repair", info.Name)
|
if len(streamableFiles) > 1 {
|
||||||
forRepair = true
|
log.Println("Marking for repair", info.Name)
|
||||||
|
forRepair = true
|
||||||
|
} else {
|
||||||
|
log.Println("This torrent is unfixable, the lone streamable link has expired, ignoring", info.Name, info.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// all links are still intact! good!
|
// all links are still intact! good!
|
||||||
@@ -320,6 +313,64 @@ func (t *TorrentManager) addMoreInfo(torrent *Torrent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mapToDirectories maps torrents to directories
|
||||||
|
func (t *TorrentManager) mapToDirectories() {
|
||||||
|
// Map torrents to directories
|
||||||
|
switch t.config.GetVersion() {
|
||||||
|
case "v1":
|
||||||
|
configV1 := t.config.(*config.ZurgConfigV1)
|
||||||
|
groupMap := configV1.GetGroupMap()
|
||||||
|
// for every group, iterate over every torrent
|
||||||
|
// and then sprinkle/distribute the torrents to the directories of the group
|
||||||
|
for group, directories := range groupMap {
|
||||||
|
log.Printf("Processing directory group '%s', sequence: %s\n", group, strings.Join(directories, " > "))
|
||||||
|
counter := make(map[string]int)
|
||||||
|
for i := range t.torrents {
|
||||||
|
// don't process torrents that are already mapped if it is not the first run
|
||||||
|
if _, exists := t.processedTorrents[t.torrents[i].ID]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, directory := range directories {
|
||||||
|
var filenames []string
|
||||||
|
for _, file := range t.torrents[i].Files {
|
||||||
|
filenames = append(filenames, file.Path)
|
||||||
|
}
|
||||||
|
if configV1.MeetsConditions(directory, t.torrents[i].ID, t.torrents[i].Name, filenames) {
|
||||||
|
found := false
|
||||||
|
for _, dir := range t.TorrentDirectoriesMap[t.torrents[i].Name] {
|
||||||
|
if dir == directory {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
counter[directory]++
|
||||||
|
t.TorrentDirectoriesMap[t.torrents[i].Name] = append(t.TorrentDirectoriesMap[t.torrents[i].Name], directory)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sum := 0
|
||||||
|
for _, count := range counter {
|
||||||
|
sum += count
|
||||||
|
}
|
||||||
|
if sum > 0 {
|
||||||
|
log.Printf("Directory group processed: %s %v %d\n", group, counter, sum)
|
||||||
|
} else {
|
||||||
|
log.Println("No new additions to directory group", group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Println("Unknown config version")
|
||||||
|
}
|
||||||
|
for _, torrent := range t.torrents {
|
||||||
|
t.processedTorrents[torrent.ID] = true
|
||||||
|
}
|
||||||
|
log.Println("Finished mapping to directories")
|
||||||
|
}
|
||||||
|
|
||||||
// getByID returns a torrent by its ID
|
// getByID returns a torrent by its ID
|
||||||
func (t *TorrentManager) getByID(torrentID string) *Torrent {
|
func (t *TorrentManager) getByID(torrentID string) *Torrent {
|
||||||
for i := range t.torrents {
|
for i := range t.torrents {
|
||||||
@@ -376,6 +427,116 @@ func (t *TorrentManager) readFromFile(torrentID string) *Torrent {
|
|||||||
return &torrent
|
return &torrent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TorrentManager) repairAll(wg *sync.WaitGroup) {
|
||||||
|
wg.Wait()
|
||||||
|
for _, torrent := range t.torrents {
|
||||||
|
if torrent.ForRepair {
|
||||||
|
log.Println("Issues were detected on", torrent.Name, "; fixing...")
|
||||||
|
t.repair(torrent.ID, torrent.SelectedFiles, true)
|
||||||
|
}
|
||||||
|
if len(torrent.Links) == 0 && torrent.Progress == 100 {
|
||||||
|
// If the torrent has no links
|
||||||
|
// and already processing repair
|
||||||
|
// delete it!
|
||||||
|
log.Println("Deleting", torrent.Name, "as it has no links")
|
||||||
|
realdebrid.DeleteTorrent(t.config.GetToken(), torrent.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TorrentManager) repair(torrentID string, selectedFiles []File, tryReinsertionFirst bool) {
|
||||||
|
torrent := t.getByID(torrentID)
|
||||||
|
if torrent == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if it is already "being" repaired
|
||||||
|
found := false
|
||||||
|
for _, hash := range t.inProgress {
|
||||||
|
if hash == torrent.Hash {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
log.Println("Repair in progress, skipping", torrentID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if it is already repaired
|
||||||
|
foundFiles := t.findAllDownloadedFilesFromHash(torrent.Hash)
|
||||||
|
var missingFiles []File
|
||||||
|
for _, sFile := range selectedFiles {
|
||||||
|
if sFile.Link == "" || sFile.Unavailable {
|
||||||
|
found := false
|
||||||
|
for _, fFile := range foundFiles {
|
||||||
|
// same file but different link, then yes it has been repaired
|
||||||
|
if sFile.Path == fFile.Path && sFile.Link != fFile.Link {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
missingFiles = append(missingFiles, sFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(missingFiles) == 0 {
|
||||||
|
log.Println(torrent.Name, "is already repaired")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// then we repair it!
|
||||||
|
log.Println("Repairing torrent", torrentID)
|
||||||
|
// check if we can still add more downloads
|
||||||
|
proceed := t.canCapacityHandle()
|
||||||
|
if !proceed {
|
||||||
|
log.Println("Cannot add more torrents, exiting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// first solution: add the same selection, maybe it can be fixed by reinsertion?
|
||||||
|
success := false
|
||||||
|
if tryReinsertionFirst {
|
||||||
|
success = t.reinsertTorrent(torrent, "", true)
|
||||||
|
}
|
||||||
|
if !success {
|
||||||
|
// if all the selected files are missing but there are other streamable files
|
||||||
|
var otherStreamableFileIDs []int
|
||||||
|
for _, file := range torrent.Files {
|
||||||
|
found := false
|
||||||
|
for _, selectedFile := range selectedFiles {
|
||||||
|
if selectedFile.ID == file.ID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found && isStreamable(file.Path) {
|
||||||
|
otherStreamableFileIDs = append(otherStreamableFileIDs, file.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (len(missingFiles) == len(selectedFiles) || len(missingFiles) == 1) && len(otherStreamableFileIDs) > 0 {
|
||||||
|
// we will download 1 extra streamable file to force a redownload of the missing files
|
||||||
|
// or if there's only 1 missing file, we will download 1 more to prevent a rename
|
||||||
|
missingFilesPlus1 := strings.Join(getFileIDs(missingFiles), ",")
|
||||||
|
missingFilesPlus1 += fmt.Sprintf(",%d", otherStreamableFileIDs[0])
|
||||||
|
t.reinsertTorrent(torrent, missingFilesPlus1, false)
|
||||||
|
} else {
|
||||||
|
// if not, last resort: add only the missing files but do it in 2 batches
|
||||||
|
half := len(missingFiles) / 2
|
||||||
|
missingFiles1 := strings.Join(getFileIDs(missingFiles[:half]), ",")
|
||||||
|
missingFiles2 := strings.Join(getFileIDs(missingFiles[half:]), ",")
|
||||||
|
if missingFiles1 != "" {
|
||||||
|
t.reinsertTorrent(torrent, missingFiles1, false)
|
||||||
|
}
|
||||||
|
if missingFiles2 != "" {
|
||||||
|
t.reinsertTorrent(torrent, missingFiles2, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Println("Waiting for downloads to finish")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TorrentManager) reinsertTorrent(torrent *Torrent, missingFiles string, deleteIfFailed bool) bool {
|
func (t *TorrentManager) reinsertTorrent(torrent *Torrent, missingFiles string, deleteIfFailed bool) bool {
|
||||||
// if missingFiles is not provided, look for missing files
|
// if missingFiles is not provided, look for missing files
|
||||||
if missingFiles == "" {
|
if missingFiles == "" {
|
||||||
@@ -485,7 +646,7 @@ func (t *TorrentManager) organizeChaos(info *realdebrid.Torrent, selectedFiles [
|
|||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
// "chaos" file, we don't know where it belongs
|
// "chaos" file, we don't know where it belongs
|
||||||
isChaotic = true
|
isChaotic = !isStreamable(result.Response.Filename)
|
||||||
selectedFiles = append(selectedFiles, File{
|
selectedFiles = append(selectedFiles, File{
|
||||||
File: realdebrid.File{
|
File: realdebrid.File{
|
||||||
Path: result.Response.Filename,
|
Path: result.Response.Filename,
|
||||||
@@ -500,133 +661,6 @@ func (t *TorrentManager) organizeChaos(info *realdebrid.Torrent, selectedFiles [
|
|||||||
return selectedFiles, isChaotic
|
return selectedFiles, isChaotic
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TorrentManager) repair(torrentID string, selectedFiles []File, tryReinsertionFirst bool) {
|
|
||||||
torrent := t.getByID(torrentID)
|
|
||||||
if torrent == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if it is already "being" repaired
|
|
||||||
found := false
|
|
||||||
for _, hash := range t.inProgress {
|
|
||||||
if hash == torrent.Hash {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if found {
|
|
||||||
log.Println("Repair in progress, skipping", torrentID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if it is already repaired
|
|
||||||
foundFiles := t.findAllDownloadedFilesFromHash(torrent.Hash)
|
|
||||||
var missingFiles []File
|
|
||||||
for _, sFile := range selectedFiles {
|
|
||||||
if sFile.Link == "" {
|
|
||||||
found := false
|
|
||||||
for _, fFile := range foundFiles {
|
|
||||||
if sFile.Path == fFile.Path {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
missingFiles = append(missingFiles, sFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(missingFiles) == 0 {
|
|
||||||
log.Println(torrent.Name, "is already repaired")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// then we repair it!
|
|
||||||
log.Println("Repairing torrent", torrentID)
|
|
||||||
// check if we can still add more downloads
|
|
||||||
proceed := t.canCapacityHandle()
|
|
||||||
if !proceed {
|
|
||||||
log.Println("Cannot add more torrents, exiting")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// first solution: add the same selection, maybe it can be fixed by reinsertion?
|
|
||||||
success := false
|
|
||||||
if tryReinsertionFirst {
|
|
||||||
success = t.reinsertTorrent(torrent, "", true)
|
|
||||||
}
|
|
||||||
if !success {
|
|
||||||
// if not, last resort: add only the missing files and do it in 2 batches
|
|
||||||
half := len(missingFiles) / 2
|
|
||||||
missingFiles1 := getFileIDs(missingFiles[:half])
|
|
||||||
missingFiles2 := getFileIDs(missingFiles[half:])
|
|
||||||
if missingFiles1 != "" {
|
|
||||||
t.reinsertTorrent(torrent, missingFiles1, false)
|
|
||||||
}
|
|
||||||
if missingFiles2 != "" {
|
|
||||||
t.reinsertTorrent(torrent, missingFiles2, false)
|
|
||||||
}
|
|
||||||
log.Println("Waiting for downloads to finish")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TorrentManager) mapToDirectories() {
|
|
||||||
// Map torrents to directories
|
|
||||||
switch t.config.GetVersion() {
|
|
||||||
case "v1":
|
|
||||||
configV1 := t.config.(*config.ZurgConfigV1)
|
|
||||||
groupMap := configV1.GetGroupMap()
|
|
||||||
// for every group, iterate over every torrent
|
|
||||||
// and then sprinkle/distribute the torrents to the directories of the group
|
|
||||||
for group, directories := range groupMap {
|
|
||||||
log.Printf("Processing directory group '%s', sequence: %s\n", group, strings.Join(directories, " > "))
|
|
||||||
counter := make(map[string]int)
|
|
||||||
for i := range t.torrents {
|
|
||||||
// don't process torrents that are already mapped if it is not the first run
|
|
||||||
if _, exists := t.processedTorrents[t.torrents[i].ID]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, directory := range directories {
|
|
||||||
var filenames []string
|
|
||||||
for _, file := range t.torrents[i].Files {
|
|
||||||
filenames = append(filenames, file.Path)
|
|
||||||
}
|
|
||||||
if configV1.MeetsConditions(directory, t.torrents[i].ID, t.torrents[i].Name, filenames) {
|
|
||||||
found := false
|
|
||||||
for _, dir := range t.TorrentDirectoriesMap[t.torrents[i].Name] {
|
|
||||||
if dir == directory {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
counter[directory]++
|
|
||||||
t.TorrentDirectoriesMap[t.torrents[i].Name] = append(t.TorrentDirectoriesMap[t.torrents[i].Name], directory)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sum := 0
|
|
||||||
for _, count := range counter {
|
|
||||||
sum += count
|
|
||||||
}
|
|
||||||
if sum > 0 {
|
|
||||||
log.Printf("Directory group processed: %s %v %d\n", group, counter, sum)
|
|
||||||
} else {
|
|
||||||
log.Println("No new additions to directory group", group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Println("Unknown config version")
|
|
||||||
}
|
|
||||||
for _, torrent := range t.torrents {
|
|
||||||
t.processedTorrents[torrent.ID] = true
|
|
||||||
}
|
|
||||||
log.Println("Finished mapping to directories")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TorrentManager) canCapacityHandle() bool {
|
func (t *TorrentManager) canCapacityHandle() bool {
|
||||||
// max waiting time is 45 minutes
|
// max waiting time is 45 minutes
|
||||||
const maxRetries = 50
|
const maxRetries = 50
|
||||||
@@ -667,17 +701,3 @@ func (t *TorrentManager) canCapacityHandle() bool {
|
|||||||
retryCount++
|
retryCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileIDs(files []File) string {
|
|
||||||
var fileIDs string
|
|
||||||
for _, file := range files {
|
|
||||||
// this won't include the id=0 files that were "chaos"
|
|
||||||
if file.File.Selected == 1 && file.ID != 0 && file.Link == "" {
|
|
||||||
fileIDs += fmt.Sprintf("%d,", file.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(fileIDs) > 0 {
|
|
||||||
fileIDs = fileIDs[:len(fileIDs)-1]
|
|
||||||
}
|
|
||||||
return fileIDs
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ type Torrent struct {
|
|||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
realdebrid.File
|
realdebrid.File
|
||||||
Link string
|
Link string
|
||||||
|
Unavailable bool
|
||||||
}
|
}
|
||||||
|
|||||||
24
internal/torrent/util.go
Normal file
24
internal/torrent/util.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package torrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getFileIDs(files []File) []string {
|
||||||
|
var fileIDs []string
|
||||||
|
for _, file := range files {
|
||||||
|
if file.ID != 0 {
|
||||||
|
fileIDs = append(fileIDs, fmt.Sprintf("%d", file.ID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStreamable(filePath string) bool {
|
||||||
|
return strings.HasSuffix(filePath, ".mkv") ||
|
||||||
|
strings.HasSuffix(filePath, ".mp4") ||
|
||||||
|
strings.HasSuffix(filePath, ".avi") ||
|
||||||
|
strings.HasSuffix(filePath, ".wmv") ||
|
||||||
|
strings.HasSuffix(filePath, ".m4v")
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package dav
|
package universal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
@@ -8,31 +8,40 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/debridmediamanager.com/zurg/internal/config"
|
"github.com/debridmediamanager.com/zurg/internal/config"
|
||||||
|
"github.com/debridmediamanager.com/zurg/internal/dav"
|
||||||
|
intHttp "github.com/debridmediamanager.com/zurg/internal/http"
|
||||||
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
||||||
"github.com/debridmediamanager.com/zurg/pkg/davextra"
|
"github.com/debridmediamanager.com/zurg/pkg/davextra"
|
||||||
"github.com/debridmediamanager.com/zurg/pkg/realdebrid"
|
"github.com/debridmediamanager.com/zurg/pkg/realdebrid"
|
||||||
"github.com/hashicorp/golang-lru/v2/expirable"
|
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleGetRequest handles a GET request to a file
|
// HandleGetRequest handles a GET request universally for both WebDAV and HTTP
|
||||||
func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) {
|
func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) {
|
||||||
requestPath := path.Clean(r.URL.Path)
|
requestPath := path.Clean(r.URL.Path)
|
||||||
|
isDav := true
|
||||||
|
if strings.Contains(requestPath, "/http") {
|
||||||
|
requestPath = strings.Replace(requestPath, "/http", "/", 1)
|
||||||
|
isDav = false
|
||||||
|
}
|
||||||
if requestPath == "/favicon.ico" {
|
if requestPath == "/favicon.ico" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
segments := strings.Split(requestPath, "/")
|
segments := strings.Split(requestPath, "/")
|
||||||
// If there are less than 3 segments, return an error or adjust as needed
|
// If there are less than 3 segments, return an error or adjust as needed
|
||||||
if len(segments) < 4 {
|
if len(segments) <= 3 {
|
||||||
// log.Println("Invalid url", requestPath)
|
if isDav {
|
||||||
// http.Error(w, "Cannot find file", http.StatusNotFound)
|
dav.HandlePropfindRequest(w, r, t, c, cache)
|
||||||
HandlePropfindRequest(w, r, t, c, cache)
|
} else {
|
||||||
|
intHttp.HandleDirectoryListing(w, r, t, c, cache)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if data, exists := cache.Get(requestPath); exists {
|
if data, exists := cache.Get(requestPath); exists {
|
||||||
http.Redirect(w, r, data, http.StatusFound)
|
http.Redirect(w, r, data, http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get the last two segments
|
|
||||||
baseDirectory := segments[len(segments)-3]
|
baseDirectory := segments[len(segments)-3]
|
||||||
torrentName := segments[len(segments)-2]
|
torrentName := segments[len(segments)-2]
|
||||||
filename := segments[len(segments)-1]
|
filename := segments[len(segments)-1]
|
||||||
@@ -45,14 +54,14 @@ func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *torrent.Torrent
|
|||||||
}
|
}
|
||||||
|
|
||||||
filenameV2, linkFragment := davextra.ExtractLinkFragment(filename)
|
filenameV2, linkFragment := davextra.ExtractLinkFragment(filename)
|
||||||
_, file := getFile(torrents, filenameV2, linkFragment)
|
torrent, file := getFile(torrents, filenameV2, linkFragment)
|
||||||
if file == nil {
|
if file == nil {
|
||||||
log.Println("Cannot find file", requestPath)
|
log.Println("Cannot find file (get)", requestPath)
|
||||||
http.Error(w, "Cannot find file", http.StatusNotFound)
|
http.Error(w, "Cannot find file", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if file.Link == "" {
|
if file.Link == "" {
|
||||||
log.Println("Link not found", filename)
|
log.Println("Link not found (get)", filename)
|
||||||
http.Error(w, "Cannot find file", http.StatusNotFound)
|
http.Error(w, "Cannot find file", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -63,12 +72,12 @@ func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *torrent.Torrent
|
|||||||
}
|
}
|
||||||
resp := realdebrid.RetryUntilOk(unrestrictFn)
|
resp := realdebrid.RetryUntilOk(unrestrictFn)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
log.Println("Cannot unrestrict link", link, filenameV2)
|
if !file.Unavailable {
|
||||||
// t.HideTheFile(torrent, file)
|
log.Println("Cannot unrestrict link", link, filenameV2)
|
||||||
|
t.HideTheFile(torrent, file)
|
||||||
|
}
|
||||||
http.Redirect(w, r, "https://send.nukes.wtf/tDeTd0", http.StatusFound)
|
http.Redirect(w, r, "https://send.nukes.wtf/tDeTd0", http.StatusFound)
|
||||||
return
|
} else if resp.Filename != filenameV2 {
|
||||||
}
|
|
||||||
if resp.Filename != filenameV2 {
|
|
||||||
actualExt := filepath.Ext(resp.Filename)
|
actualExt := filepath.Ext(resp.Filename)
|
||||||
expectedExt := filepath.Ext(filenameV2)
|
expectedExt := filepath.Ext(filenameV2)
|
||||||
if actualExt != expectedExt {
|
if actualExt != expectedExt {
|
||||||
@@ -76,20 +85,8 @@ func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *torrent.Torrent
|
|||||||
} else {
|
} else {
|
||||||
log.Println("Filename mismatch", resp.Filename, filenameV2)
|
log.Println("Filename mismatch", resp.Filename, filenameV2)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
cache.Add(requestPath, resp.Download)
|
||||||
|
http.Redirect(w, r, resp.Download, http.StatusFound)
|
||||||
}
|
}
|
||||||
cache.Add(requestPath, resp.Download)
|
|
||||||
http.Redirect(w, r, resp.Download, http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFile finds a link by a fragment, it might be wrong
|
|
||||||
func getFile(torrents []torrent.Torrent, filename, fragment string) (*torrent.Torrent, *torrent.File) {
|
|
||||||
for t := range torrents {
|
|
||||||
for f, file := range torrents[t].SelectedFiles {
|
|
||||||
fname := filepath.Base(file.Path)
|
|
||||||
if filename == fname && strings.Contains(file.Link, fragment) {
|
|
||||||
return &torrents[t], &torrents[t].SelectedFiles[f]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
94
internal/universal/head.go
Normal file
94
internal/universal/head.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package universal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"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/davextra"
|
||||||
|
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleHeadRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) {
|
||||||
|
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.Println("Method not implemented", r.Method, r.URL.Path)
|
||||||
|
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if data, exists := cache.Get("head:" + requestPath); exists {
|
||||||
|
splits := strings.Split(data, " ")
|
||||||
|
contentType := splits[0]
|
||||||
|
contentLength := splits[1]
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
w.Header().Set("Content-Length", contentLength)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDirectory := segments[len(segments)-3]
|
||||||
|
torrentName := segments[len(segments)-2]
|
||||||
|
filename := segments[len(segments)-1]
|
||||||
|
|
||||||
|
torrents := t.FindAllTorrentsWithName(baseDirectory, torrentName)
|
||||||
|
if torrents == nil {
|
||||||
|
log.Println("Cannot find torrent", requestPath)
|
||||||
|
http.Error(w, "Cannot find file", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filenameV2, linkFragment := davextra.ExtractLinkFragment(filename)
|
||||||
|
_, file := getFile(torrents, filenameV2, linkFragment)
|
||||||
|
if file == nil {
|
||||||
|
log.Println("Cannot find file (head)", requestPath)
|
||||||
|
http.Error(w, "Cannot find file", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if file.Link == "" {
|
||||||
|
log.Println("Link not found (head)", filename)
|
||||||
|
http.Error(w, "Cannot find file", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentType := getContentMimeType(filename)
|
||||||
|
contentLength := fmt.Sprintf("%d", file.Bytes)
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
w.Header().Set("Content-Length", contentLength)
|
||||||
|
cache.Add("head:"+requestPath, contentType+" "+contentLength)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getContentMimeType(filePath string) string {
|
||||||
|
switch filepath.Ext(filePath) {
|
||||||
|
case ".mkv":
|
||||||
|
return "video/x-matroska"
|
||||||
|
case ".mp4":
|
||||||
|
return "video/mp4"
|
||||||
|
case ".avi":
|
||||||
|
return "video/x-msvideo"
|
||||||
|
case ".mp3":
|
||||||
|
return "audio/mpeg"
|
||||||
|
case ".rar":
|
||||||
|
return "application/x-rar-compressed"
|
||||||
|
case ".zip":
|
||||||
|
return "application/zip"
|
||||||
|
case ".7z":
|
||||||
|
return "application/x-7z-compressed"
|
||||||
|
case ".srt":
|
||||||
|
return "text/plain"
|
||||||
|
default:
|
||||||
|
return "application/octet-stream"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
internal/universal/util.go
Normal file
21
internal/universal/util.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package universal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/debridmediamanager.com/zurg/internal/torrent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getFile finds a link by a fragment, it might be wrong
|
||||||
|
func getFile(torrents []torrent.Torrent, filename, fragment string) (*torrent.Torrent, *torrent.File) {
|
||||||
|
for t := range torrents {
|
||||||
|
for f, file := range torrents[t].SelectedFiles {
|
||||||
|
fname := filepath.Base(file.Path)
|
||||||
|
if filename == fname && strings.Contains(file.Link, fragment) {
|
||||||
|
return &torrents[t], &torrents[t].SelectedFiles[f]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user