fuse integreated

This commit is contained in:
Ben Sarmiento
2023-11-11 11:52:34 +01:00
29 changed files with 1074 additions and 1001 deletions

View File

@@ -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)

View File

@@ -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]
}

View File

@@ -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

View File

@@ -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("<?xml version=\"1.0\" encoding=\"utf-8\"?>\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)
}

View File

@@ -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
}

View File

@@ -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 := "<ol>"
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("<li><a href=\"%s/\">%s</a></li>", 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 := "<ol>"
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("<li><a href=\"%s\">%s</a></li>", filePath, filename)
}
return &resp, nil
return &htmlDoc, nil
}

View File

@@ -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 := "<ul>"
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, url.PathEscape(item.Name))
htmlDoc += fmt.Sprintf("<li><a href=\"%s/\">%s</a></li>", path, item.Name)
}
return htmlDoc, nil
}
func createSingleTorrentResponse(basePath string, torrents []torrent.Torrent) (string, error) {
htmlDoc := "<ul>"
finalName := make(map[string]bool)
currentPath := filepath.Join(basePath)
for _, torrent := range torrents {
for _, file := range torrent.SelectedFiles {
if file.Link == "" {
// TODO: fix the file?
// log.Println("File has no link, skipping", file.Path)
continue
}
filename := filepath.Base(file.Path)
if finalName[filename] {
continue
}
finalName[filename] = true
filePath := filepath.Join(currentPath, url.PathEscape(filename))
htmlDoc += fmt.Sprintf("<li><a href=\"%s\">%s</a></li>", filePath, filename)
}
}
return htmlDoc, nil
}

View File

@@ -16,8 +16,7 @@ import (
// Router creates a WebDAV router
func Router(mux *http.ServeMux, c config.ConfigInterface, t *torrent.TorrentManager, cache *expirable.LRU[string, string]) {
rlog := logutil.NewLogger()
log := rlog.Named("net")
log := logutil.NewLogger().Named("net")
mux.HandleFunc("/http/", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
@@ -26,7 +25,7 @@ func Router(mux *http.ServeMux, c config.ConfigInterface, t *torrent.TorrentMana
if countNonEmptySegments(strings.Split(requestPath, "/")) > 3 {
universal.HandleGetRequest(w, r, t, c, cache)
} else {
intHttp.HandleDirectoryListing(w, r, t, c, cache)
intHttp.HandleDirectoryListing(w, r, t, c)
}
case http.MethodHead:
@@ -41,7 +40,7 @@ func Router(mux *http.ServeMux, c config.ConfigInterface, t *torrent.TorrentMana
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "PROPFIND":
dav.HandlePropfindRequest(w, r, t, c, cache)
dav.HandlePropfindRequest(w, r, t, c)
case http.MethodGet:
universal.HandleGetRequest(w, r, t, c, cache)

View File

@@ -30,8 +30,7 @@ func (se *ScriptExecutor) Execute() (string, error) {
}
func OnLibraryUpdateHook(config config.ConfigInterface) {
rlog := logutil.NewLogger()
log := rlog.Named("hooks")
log := logutil.NewLogger().Named("hooks")
executor := &ScriptExecutor{
Script: config.GetOnLibraryUpdate(),

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,22 @@
package torrent
import (
"sync"
"github.com/debridmediamanager.com/zurg/pkg/realdebrid"
"github.com/elliotchance/orderedmap/v2"
)
type Torrent struct {
Version string
realdebrid.Torrent
SelectedFiles []File
AccessKey string
SelectedFiles *orderedmap.OrderedMap[string, *File]
Directories []string
LatestAdded string
InProgress bool
ForRepair bool
lock *sync.Mutex
Instances []realdebrid.TorrentInfo
}
type File struct {
realdebrid.File
Link string
Unavailable bool
Link string
}

View File

@@ -11,16 +11,15 @@ import (
"github.com/debridmediamanager.com/zurg/internal/dav"
intHttp "github.com/debridmediamanager.com/zurg/internal/http"
"github.com/debridmediamanager.com/zurg/internal/torrent"
zurghttp "github.com/debridmediamanager.com/zurg/pkg/http"
"github.com/debridmediamanager.com/zurg/pkg/logutil"
"github.com/debridmediamanager.com/zurg/pkg/realdebrid"
"github.com/hashicorp/golang-lru/v2/expirable"
"go.uber.org/zap"
)
// 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]) {
rlog := logutil.NewLogger()
log := rlog.Named("uniget")
log := logutil.NewLogger().Named("uniget")
requestPath := path.Clean(r.URL.Path)
isDav := true
@@ -35,93 +34,99 @@ func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *torrent.Torrent
// If there are less than 3 segments, return an error or adjust as needed
if len(segments) <= 3 {
if isDav {
dav.HandlePropfindRequest(w, r, t, c, cache)
dav.HandlePropfindRequest(w, r, t, c)
} else {
intHttp.HandleDirectoryListing(w, r, t, c, cache)
intHttp.HandleDirectoryListing(w, r, t, c)
}
return
}
if data, exists := cache.Get(requestPath); exists {
streamFileToResponse(data, w, r, c, log)
return
}
baseDirectory := segments[len(segments)-3]
torrentName := segments[len(segments)-2]
accessKey := segments[len(segments)-2]
filename := segments[len(segments)-1]
torrents := t.FindAllTorrentsWithName(baseDirectory, torrentName)
if torrents == nil {
log.Errorf("Cannot find torrent %s in the directory %s", requestPath, baseDirectory)
torrent, _ := t.TorrentMap.Get(accessKey)
if torrent == nil {
log.Warnf("Cannot find torrent %s in the directory %s", accessKey, baseDirectory)
http.Error(w, "File not found", http.StatusNotFound)
return
}
torrent, file := getFile(torrents, filename)
file, _ := torrent.SelectedFiles.Get(filename)
if file == nil {
log.Errorf("Cannot find file from path %s", requestPath)
log.Warnf("Cannot find file from path %s", requestPath)
http.Error(w, "File not found", http.StatusNotFound)
return
}
if data, exists := cache.Get(requestPath); exists {
streamFileToResponse(torrent, data, w, r, t, c, log)
return
}
if file.Link == "" {
// This is a dead file, serve an alternate file
log.Errorf("File %s is no longer available", filename)
streamErrorVideo("https://www.youtube.com/watch?v=bGTqwt6vdcY", w, r, c, log)
log.Warnf("File %s is not yet available, zurg is repairing the torrent", filename)
streamErrorVideo("https://www.youtube.com/watch?v=bGTqwt6vdcY", w, r, t, c, log)
return
}
link := file.Link
unrestrictFn := func() (*realdebrid.UnrestrictResponse, error) {
return realdebrid.UnrestrictLink(c.GetToken(), link)
}
resp := realdebrid.RetryUntilOk(unrestrictFn)
resp := t.UnrestrictUntilOk(link)
if resp == nil {
if !file.Unavailable {
log.Errorf("Cannot unrestrict file %s %s", filename, link)
t.HideTheFile(torrent, file)
}
streamErrorVideo("https://www.youtube.com/watch?v=gea_FJrtFVA", w, r, c, log)
go t.Repair(torrent.AccessKey)
log.Warnf("File %s is no longer available, torrent is marked for repair", file.Path)
streamErrorVideo("https://www.youtube.com/watch?v=gea_FJrtFVA", w, r, t, c, log)
return
} else if resp.Filename != filename {
actualExt := filepath.Ext(resp.Filename)
expectedExt := filepath.Ext(filename)
if actualExt != expectedExt && resp.Streamable != 1 {
log.Errorf("File extension mismatch: %s and %s", filename, resp.Filename)
streamErrorVideo("https://www.youtube.com/watch?v=t9VgOriBHwE", w, r, c, log)
log.Warnf("File was changed and is not streamable: %s and %s", filename, resp.Filename)
streamErrorVideo("https://www.youtube.com/watch?v=t9VgOriBHwE", w, r, t, c, log)
return
} else {
log.Errorf("Filename mismatch: %s and %s", filename, resp.Filename)
log.Warnf("Filename mismatch: %s and %s", filename, resp.Filename)
}
}
cache.Add(requestPath, resp.Download)
streamFileToResponse(resp.Download, w, r, c, log)
streamFileToResponse(torrent, resp.Download, w, r, t, c, log)
}
func streamFileToResponse(url string, w http.ResponseWriter, r *http.Request, c config.ConfigInterface, log *zap.SugaredLogger) {
func streamFileToResponse(torrent *torrent.Torrent, url string, w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, log *zap.SugaredLogger) {
// Create a new request for the file download.
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Errorf("Error creating new request %v", err)
streamErrorVideo("https://www.youtube.com/watch?v=H3NSrObyAxM", w, r, c, log)
log.Errorf("Error creating new request: %v", err)
streamErrorVideo("https://www.youtube.com/watch?v=H3NSrObyAxM", w, r, t, c, log)
return
}
for k, values := range r.Header {
for _, v := range values {
req.Header.Add(k, v)
}
// copy range header if it exists
if r.Header.Get("Range") != "" {
req.Header.Add("Range", r.Header.Get("Range"))
}
resp, err := http.DefaultClient.Do(req)
// Create a custom HTTP client
client := zurghttp.NewHTTPClient(c.GetToken(), 10, c)
resp, err := client.Do(req)
if err != nil {
log.Errorf("Error downloading file %v", err)
streamErrorVideo("https://www.youtube.com/watch?v=FSSd8cponAA", w, r, c, log)
log.Warnf("Cannot download file %v ; torrent is marked for repair", err)
if torrent != nil {
go t.Repair(torrent.AccessKey)
}
streamErrorVideo("https://www.youtube.com/watch?v=FSSd8cponAA", w, r, t, c, log)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
log.Errorf("Received a nonOK status code %d", resp.StatusCode)
streamErrorVideo("https://www.youtube.com/watch?v=BcseUxviVqE", w, r, c, log)
log.Warnf("Received a %s status code ; torrent is marked for repair", resp.Status)
if torrent != nil {
go t.Repair(torrent.AccessKey)
}
streamErrorVideo("https://www.youtube.com/watch?v=BcseUxviVqE", w, r, t, c, log)
return
}
@@ -135,14 +140,11 @@ func streamFileToResponse(url string, w http.ResponseWriter, r *http.Request, c
io.CopyBuffer(w, resp.Body, buf)
}
func streamErrorVideo(link string, w http.ResponseWriter, r *http.Request, c config.ConfigInterface, log *zap.SugaredLogger) {
unrestrictFn := func() (*realdebrid.UnrestrictResponse, error) {
return realdebrid.UnrestrictLink(c.GetToken(), link)
}
resp := realdebrid.RetryUntilOk(unrestrictFn)
func streamErrorVideo(link string, w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, log *zap.SugaredLogger) {
resp := t.UnrestrictUntilOk(link)
if resp == nil {
http.Error(w, "REAL-DEBRID IS DOWN", http.StatusInternalServerError)
return
}
streamFileToResponse(resp.Download, w, r, c, log)
streamFileToResponse(nil, resp.Download, w, r, t, c, log)
}

View File

@@ -14,8 +14,7 @@ import (
)
func HandleHeadRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) {
rlog := logutil.NewLogger()
log := rlog.Named("unihead")
log := logutil.NewLogger().Named("unihead")
requestPath := path.Clean(r.URL.Path)
requestPath = strings.Replace(requestPath, "/http", "", 1)
@@ -42,25 +41,25 @@ func HandleHeadRequest(w http.ResponseWriter, r *http.Request, t *torrent.Torren
}
baseDirectory := segments[len(segments)-3]
torrentName := segments[len(segments)-2]
accessKey := segments[len(segments)-2]
filename := segments[len(segments)-1]
torrents := t.FindAllTorrentsWithName(baseDirectory, torrentName)
if torrents == nil {
log.Errorf("Cannot find torrent %s in the directory %s", requestPath, baseDirectory)
http.Error(w, "Cannot find file", http.StatusNotFound)
torrent, _ := t.TorrentMap.Get(accessKey)
if torrent == nil {
log.Warnf("Cannot find torrent %s in the directory %s", accessKey, baseDirectory)
http.Error(w, "File not found", http.StatusNotFound)
return
}
_, file := getFile(torrents, filename)
file, _ := torrent.SelectedFiles.Get(filename)
if file == nil {
log.Errorf("Cannot find file from path %s", requestPath)
log.Warnf("Cannot find file from path %s", requestPath)
http.Error(w, "Cannot find file", http.StatusNotFound)
return
}
if file.Link == "" {
// This is a dead file, serve an alternate file
log.Errorf("File %s is no longer available", filename)
log.Warnf("File %s is no longer available", filename)
http.Error(w, "Cannot find file", http.StatusNotFound)
return
}

View File

@@ -1,20 +0,0 @@
package universal
import (
"path/filepath"
"github.com/debridmediamanager.com/zurg/internal/torrent"
)
// getFile finds a link by a fragment, it might be wrong
func getFile(torrents []torrent.Torrent, filename string) (*torrent.Torrent, *torrent.File) {
for t := range torrents {
for f, file := range torrents[t].SelectedFiles {
fname := filepath.Base(file.Path)
if filename == fname {
return &torrents[t], &torrents[t].SelectedFiles[f]
}
}
}
return nil, nil
}

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"time"
@@ -64,40 +63,32 @@ func (o Object) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
})
}
case DIRECTORY:
seen := make(map[string]bool)
for _, item := range o.fs.t.GetByDirectory(o.name) {
if item.Progress != 100 {
for el := o.fs.t.TorrentMap.Front(); el != nil; el = el.Next() {
item := el.Value
if item.InProgress {
continue
}
if _, exists := seen[item.Name]; exists {
continue
}
seen[item.Name] = true
dirs = append(dirs, fuse.Dirent{
Name: item.Name,
Name: item.AccessKey,
Type: fuse.DT_Dir,
})
}
case TORRENT:
finalName := make(map[string]bool)
for _, item := range o.fs.t.FindAllTorrentsWithName(o.parentName, o.name) {
for _, file := range item.SelectedFiles {
if file.Link == "" {
// log.Println("File has no link, skipping", file.Path)
continue
}
filename := filepath.Base(file.Path)
if finalName[filename] {
// fragment := davextra.GetLinkFragment(file.Link)
// filename = davextra.InsertLinkFragment(filename, fragment)
continue
}
finalName[filename] = true
dirs = append(dirs, fuse.Dirent{
Name: filename,
Type: fuse.DT_File,
})
torrent, _ := o.fs.t.TorrentMap.Get(o.name)
if torrent == nil {
return nil, syscall.ENOENT
}
for el := torrent.SelectedFiles.Front(); el != nil; el = el.Next() {
file := el.Value
if file.Link == "" {
// log.Println("File has no link, skipping", file.Path)
continue
}
filename := filepath.Base(file.Path)
dirs = append(dirs, fuse.Dirent{
Name: filename,
Type: fuse.DT_File,
})
}
}
return dirs, nil
@@ -119,33 +110,36 @@ func (o Object) Lookup(ctx context.Context, name string) (fs.Node, error) {
}
}
case DIRECTORY:
for _, item := range o.fs.t.GetByDirectory(o.name) {
if item.Name == name && item.Progress == 100 {
return Object{
fs: o.fs,
objType: TORRENT,
parentName: o.name,
name: name,
mtime: convertRFC3339toTime(item.Added),
}, nil
}
torrent, _ := o.fs.t.TorrentMap.Get(name)
if torrent == nil {
return nil, syscall.ENOENT
}
return Object{
fs: o.fs,
objType: TORRENT,
parentName: o.name,
name: name,
mtime: convertRFC3339toTime(torrent.LatestAdded),
}, nil
case TORRENT:
for _, item := range o.fs.t.FindAllTorrentsWithName(o.parentName, o.name) {
for _, file := range item.SelectedFiles {
if strings.HasSuffix(file.Path, name) && file.Link != "" {
return Object{
fs: o.fs,
objType: FILE,
parentName: o.name,
name: name,
file: &file,
size: uint64(file.Bytes),
mtime: convertRFC3339toTime(item.Added),
}, nil
}
}
torrent, _ := o.fs.t.TorrentMap.Get(name)
if torrent == nil {
return nil, syscall.ENOENT
}
file, _ := torrent.SelectedFiles.Get(name)
if file == nil {
return nil, syscall.ENOENT
}
return Object{
fs: o.fs,
objType: FILE,
parentName: o.name,
name: name,
file: file,
size: uint64(file.Bytes),
mtime: convertRFC3339toTime(torrent.LatestAdded),
}, nil
}
return nil, syscall.ENOENT
}