Refactor with ordered maps

This commit is contained in:
Ben Sarmiento
2023-11-10 19:03:07 +01:00
parent 15a0ba95d8
commit b97f859a32
12 changed files with 180 additions and 256 deletions

View File

@@ -37,7 +37,7 @@ func main() {
cache := expirable.NewLRU[string, string](1e4, nil, time.Hour) cache := expirable.NewLRU[string, string](1e4, nil, time.Hour)
torrentMgr := torrent.NewTorrentManager(config, cache, db) torrentMgr := torrent.NewTorrentManager(config, db)
mux := http.NewServeMux() mux := http.NewServeMux()
net.Router(mux, config, torrentMgr, cache) net.Router(mux, config, torrentMgr, cache)

1
go.mod
View File

@@ -14,6 +14,7 @@ require (
github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/gofrs/flock v0.8.1 // indirect github.com/gofrs/flock v0.8.1 // indirect
github.com/nutsdb/nutsdb v0.14.1 // indirect github.com/nutsdb/nutsdb v0.14.1 // indirect

2
go.sum
View File

@@ -11,6 +11,8 @@ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"path" "path"
"path/filepath"
"strings" "strings"
"github.com/debridmediamanager.com/zurg/internal/config" "github.com/debridmediamanager.com/zurg/internal/config"
@@ -69,10 +70,28 @@ func handleListOfTorrents(requestPath string, w http.ResponseWriter, r *http.Req
for _, directory := range c.GetDirectories() { for _, directory := range c.GetDirectories() {
if basePath == directory { if basePath == directory {
torrents := t.GetByDirectory(basePath) var responses []dav.Response
resp, err := createMultiTorrentResponse("/"+basePath, torrents)
if err != nil { responses = append(responses, dav.Directory(basePath))
return nil, fmt.Errorf("cannot read directory (%s): %w", basePath, err)
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) return xml.Marshal(resp)
} }
@@ -82,17 +101,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) { func handleSingleTorrent(requestPath string, w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager) ([]byte, error) {
directory := path.Dir(requestPath) accessKey := path.Base(requestPath)
torrentName := path.Base(requestPath) torrent, exists := t.TorrentMap.Get(accessKey)
if !exists {
sameNameTorrents := t.FindAllTorrentsWithName(directory, torrentName) return nil, fmt.Errorf("cannot find torrent %s", requestPath)
if len(sameNameTorrents) == 0 {
return nil, fmt.Errorf("cannot find directory when generating single torrent: %s", requestPath)
} }
resp, err := createSingleTorrentResponse("/"+directory, sameNameTorrents) var responses []dav.Response
if err != nil {
return nil, fmt.Errorf("cannot read directory (%s): %w", requestPath, err) // 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) 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.InProgress {
continue
}
if _, exists := seen[item.AccessKey]; exists {
continue
}
seen[item.AccessKey] = true
path := filepath.Join(basePath, item.AccessKey)
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].AccessKey)
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.LatestAdded),
file.Link,
))
}
}
responses = append(responses, torrentResponses...)
return &dav.MultiStatus{
XMLNS: "DAV:",
Response: responses,
}, nil
}

View File

@@ -65,13 +65,15 @@ func handleListOfTorrents(requestPath string, w http.ResponseWriter, r *http.Req
for _, directory := range c.GetDirectories() { for _, directory := range c.GetDirectories() {
if basePath == directory { if basePath == directory {
htmlDoc := "<ol>" htmlDoc := "<ol>"
for name, torrent := range t.TorrentMap { for el := t.TorrentMap.Front(); el != nil; el = el.Next() {
if len(torrent.SelectedFiles) == 0 { accessKey := el.Key
torrent := el.Value
if torrent.InProgress {
continue continue
} }
for _, dir := range torrent.Directories { for _, dir := range torrent.Directories {
if dir == basePath { if dir == basePath {
htmlDoc += fmt.Sprintf("<li><a href=\"%s/\">%s</a></li>", filepath.Join(requestPath, url.PathEscape(name)), name) htmlDoc += fmt.Sprintf("<li><a href=\"%s/\">%s</a></li>", filepath.Join(requestPath, url.PathEscape(accessKey)), accessKey)
break break
} }
} }
@@ -84,12 +86,18 @@ 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) { func handleSingleTorrent(requestPath string, w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager) (*string, error) {
torrentName := path.Base(requestPath) accessKey := path.Base(requestPath)
torrent, _ := t.TorrentMap.Get(accessKey)
if torrent == nil {
return nil, fmt.Errorf("cannot find torrent %s", requestPath)
}
htmlDoc := "<ol>" htmlDoc := "<ol>"
for _, file := range t.TorrentMap[torrentName].SelectedFiles { for el := torrent.SelectedFiles.Front(); el != nil; el = el.Next() {
file := el.Value
if file.Link == "" { if file.Link == "" {
// TODO: fix the file? // will be caught by torrent manager's repairAll
fmt.Printf("File %s has no link, skipping\n", file.Path) // just skip it for now
continue continue
} }
filename := filepath.Base(file.Path) filename := filepath.Base(file.Path)

View File

@@ -3,7 +3,9 @@ package torrent
import ( import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"math"
"os" "os"
"path/filepath"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -11,18 +13,17 @@ import (
"github.com/debridmediamanager.com/zurg/internal/config" "github.com/debridmediamanager.com/zurg/internal/config"
"github.com/debridmediamanager.com/zurg/pkg/logutil" "github.com/debridmediamanager.com/zurg/pkg/logutil"
"github.com/debridmediamanager.com/zurg/pkg/realdebrid" "github.com/debridmediamanager.com/zurg/pkg/realdebrid"
"github.com/hashicorp/golang-lru/v2/expirable" "github.com/elliotchance/orderedmap/v2"
"github.com/nutsdb/nutsdb" "github.com/nutsdb/nutsdb"
"go.uber.org/zap" "go.uber.org/zap"
) )
type TorrentManager struct { type TorrentManager struct {
TorrentMap map[string]*Torrent TorrentMap *orderedmap.OrderedMap[string, *Torrent] // accessKey -> Torrent
requiredVersion string requiredVersion string
rd *realdebrid.RealDebrid rd *realdebrid.RealDebrid
checksum string checksum string
config config.ConfigInterface config config.ConfigInterface
cache *expirable.LRU[string, string]
db *nutsdb.DB db *nutsdb.DB
workerPool chan bool workerPool chan bool
directoryMap map[string][]string directoryMap map[string][]string
@@ -34,13 +35,12 @@ type TorrentManager struct {
// NewTorrentManager creates a new torrent manager // NewTorrentManager creates a new torrent manager
// it will fetch all torrents and their info in the background // it will fetch all torrents and their info in the background
// and store them in-memory and cached in files // and store them in-memory and cached in files
func NewTorrentManager(config config.ConfigInterface, cache *expirable.LRU[string, string], db *nutsdb.DB) *TorrentManager { func NewTorrentManager(config config.ConfigInterface, db *nutsdb.DB) *TorrentManager {
t := &TorrentManager{ t := &TorrentManager{
TorrentMap: make(map[string]*Torrent), TorrentMap: orderedmap.NewOrderedMap[string, *Torrent](),
requiredVersion: fmt.Sprintf("8.11.2023 - retain:%v", config.EnableRetainFolderNameExtension()), requiredVersion: fmt.Sprintf("10.11.2023/retain=%t", config.EnableRetainFolderNameExtension()),
rd: realdebrid.NewRealDebrid(config.GetToken(), logutil.NewLogger().Named("realdebrid")), rd: realdebrid.NewRealDebrid(config.GetToken(), logutil.NewLogger().Named("realdebrid")),
config: config, config: config,
cache: cache,
db: db, db: db,
workerPool: make(chan bool, config.GetNumOfWorkers()), workerPool: make(chan bool, config.GetNumOfWorkers()),
directoryMap: make(map[string][]string), directoryMap: make(map[string][]string),
@@ -74,10 +74,11 @@ func NewTorrentManager(config config.ConfigInterface, cache *expirable.LRU[strin
if newTorrent == nil { if newTorrent == nil {
continue continue
} }
if _, exists := t.TorrentMap[newTorrent.AccessKey]; exists { torrent, _ := t.TorrentMap.Get(newTorrent.AccessKey)
t.TorrentMap[newTorrent.AccessKey] = t.mergeToMain(t.TorrentMap[newTorrent.AccessKey], newTorrent) if torrent != nil {
t.TorrentMap.Set(newTorrent.AccessKey, t.mergeToMain(torrent, newTorrent))
} else { } else {
t.TorrentMap[newTorrent.AccessKey] = newTorrent t.TorrentMap.Set(newTorrent.AccessKey, newTorrent)
} }
} }
t.checksum = t.getChecksum() t.checksum = t.getChecksum()
@@ -95,15 +96,11 @@ func (t *TorrentManager) mergeToMain(t1, t2 *Torrent) *Torrent {
merged := t1 merged := t1
// Merge SelectedFiles // Merge SelectedFiles
fileMap := make(map[int]File) for el := t2.SelectedFiles.Front(); el != nil; el = el.Next() {
for _, f := range append(t1.SelectedFiles, t2.SelectedFiles...) { if _, ok := merged.SelectedFiles.Get(el.Key); !ok {
if _, exists := fileMap[f.ID]; !exists { merged.SelectedFiles.Set(el.Key, el.Value)
fileMap[f.ID] = f
} }
} }
for _, f := range fileMap {
merged.SelectedFiles = append(merged.SelectedFiles, f)
}
// Merge Instances // Merge Instances
merged.Instances = append(t1.Instances, t2.Instances...) merged.Instances = append(t1.Instances, t2.Instances...)
@@ -113,7 +110,7 @@ func (t *TorrentManager) mergeToMain(t1, t2 *Torrent) *Torrent {
merged.LatestAdded = t2.LatestAdded merged.LatestAdded = t2.LatestAdded
} }
// InProgress // InProgress - if one of the instances is in progress, then the whole torrent is in progress
for _, instance := range merged.Instances { for _, instance := range merged.Instances {
if instance.Progress != 100 { if instance.Progress != 100 {
merged.InProgress = true merged.InProgress = true
@@ -124,36 +121,6 @@ func (t *TorrentManager) mergeToMain(t1, t2 *Torrent) *Torrent {
return merged return merged
} }
// GetByDirectory returns all torrents that have a file in the specified directory
func (t *TorrentManager) GetByDirectory(directory string) []Torrent {
var torrents []Torrent
for k, v := range t.TorrentMap {
found := false
for _, dir := range v.Directories {
if dir == directory {
found = true
break
}
}
if found {
torrents = append(torrents, *t.TorrentMap[k])
}
}
return torrents
}
// FindAllTorrentsWithName finds all torrents in a given directory with a given name
func (t *TorrentManager) FindAllTorrentsWithName(directory, torrentName string) []Torrent {
var matchingTorrents []Torrent
torrents := t.GetByDirectory(directory)
for i := range torrents {
if torrents[i].AccessKey == torrentName || strings.Contains(torrents[i].AccessKey, torrentName) {
matchingTorrents = append(matchingTorrents, torrents[i])
}
}
return matchingTorrents
}
// proxy // proxy
func (t *TorrentManager) UnrestrictUntilOk(link string) *realdebrid.UnrestrictResponse { func (t *TorrentManager) UnrestrictUntilOk(link string) *realdebrid.UnrestrictResponse {
return t.rd.UnrestrictUntilOk(link) return t.rd.UnrestrictUntilOk(link)
@@ -244,16 +211,33 @@ func (t *TorrentManager) startRefreshJob() {
<-t.workerPool <-t.workerPool
}(i) }(i)
} }
// deletes
// for el := t.TorrentMap.Front(); el != nil; el = el.Next() {
// found := false
// for _, newTorrent := range newTorrents {
// if newTorrent.ID == el.Value.AccessKey {
// found = true
// break
// }
// }
// if !found {
// t.log.Infof("Torrent id=%s is no longer found", accessKey)
// t.TorrentMap.Delete(accessKey)
// }
// }
wg.Wait() wg.Wait()
close(torrentsChan) close(torrentsChan)
for newTorrent := range torrentsChan { for newTorrent := range torrentsChan {
if newTorrent == nil { if newTorrent == nil {
continue continue
} }
if _, exists := t.TorrentMap[newTorrent.AccessKey]; exists { torrent, _ := t.TorrentMap.Get(newTorrent.AccessKey)
t.TorrentMap[newTorrent.AccessKey] = t.mergeToMain(t.TorrentMap[newTorrent.AccessKey], newTorrent) if torrent != nil {
t.TorrentMap.Set(newTorrent.AccessKey, t.mergeToMain(torrent, newTorrent))
} else { } else {
t.TorrentMap[newTorrent.AccessKey] = newTorrent t.TorrentMap.Set(newTorrent.AccessKey, newTorrent)
} }
} }
t.checksum = t.getChecksum() t.checksum = t.getChecksum()
@@ -288,7 +272,7 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
// it also has a Link field, which can be empty // it also has a Link field, which can be empty
// 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 selectedFiles := orderedmap.NewOrderedMap[string, *File]()
streamableCount := 0 streamableCount := 0
// if some Links are empty, we need to repair it // if some Links are empty, we need to repair it
forRepair := false forRepair := false
@@ -299,22 +283,26 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
if file.Selected == 0 { if file.Selected == 0 {
continue continue
} }
selectedFiles = append(selectedFiles, File{ selectedFiles.Set(filepath.Base(file.Path), &File{
File: file, File: file,
Link: "", // no link yet Link: "", // no link yet
}) })
} }
if len(selectedFiles) > len(info.Links) && info.Progress == 100 { if selectedFiles.Len() > len(info.Links) && info.Progress == 100 {
t.log.Debugf("Some links has expired for %s %s: %d selected but only %d link(s)", info.ID, info.Name, len(selectedFiles), len(info.Links)) t.log.Debugf("Some links has expired for %s %s: %d selected but only %d link(s)", info.ID, info.Name, selectedFiles.Len(), len(info.Links))
// chaotic file means RD will not output the desired file selection // chaotic file means RD will not output the desired file selection
// e.g. even if we select just a single mkv, it will output a rar // e.g. even if we select just a single mkv, it will output a rar
var isChaotic bool var isChaotic bool
selectedFiles, isChaotic = t.organizeChaos(&rdTorrent, selectedFiles) selectedFiles, isChaotic = t.organizeChaos(&rdTorrent, selectedFiles)
if isChaotic { if isChaotic && selectedFiles.Len() == 1 {
t.log.Infof("Torrent %s %s is unfixable, it's always returning an unstreamable link, ignoring", info.ID, info.Name) t.log.Infof("Torrent %s %s is unfixable, it's always returning an unstreamable link, ignoring", info.ID, info.Name)
t.log.Debugf("You can try fixing it yourself magnet:?xt=urn:btih:%s", info.Hash) t.log.Debugf("You can try fixing it yourself magnet:?xt=urn:btih:%s", info.Hash)
} else { } else {
if streamableCount > 1 { if streamableCount > 1 {
// case for repair 1: it's missing some links (or all links)
// if we download it as is, we might get the same file over and over again
// so we need to redownload it with other files selected
// that is why we check if there are other streamable files
t.log.Infof("Torrent %s %s marked for repair", info.ID, info.Name) t.log.Infof("Torrent %s %s marked for repair", info.ID, info.Name)
forRepair = true forRepair = true
} else { } else {
@@ -322,10 +310,16 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
t.log.Debugf("You can try fixing it yourself magnet:?xt=urn:btih:%s", info.Hash) t.log.Debugf("You can try fixing it yourself magnet:?xt=urn:btih:%s", info.Hash)
} }
} }
} else if len(selectedFiles) > 0 { } else if selectedFiles.Len() == len(info.Links) {
// all links are still intact! good! // all links are still intact! good!
for i, link := range info.Links { i := 0
selectedFiles[i].Link = link for el := selectedFiles.Front(); el != nil; el = el.Next() {
if i < len(info.Links) {
file := el.Value
file.Link = info.Links[i]
selectedFiles.Set(el.Key, file)
i++
}
} }
} }
@@ -338,7 +332,7 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
InProgress: info.Progress != 100, InProgress: info.Progress != 100,
Instances: []realdebrid.TorrentInfo{*info}, Instances: []realdebrid.TorrentInfo{*info},
} }
if len(selectedFiles) > 0 && torrentFromFile == nil { if selectedFiles.Len() > 0 && torrentFromFile == nil {
t.writeToFile(info) // only when there are selected files, else it's useless t.writeToFile(info) // only when there are selected files, else it's useless
} }
return &torrent return &torrent
@@ -429,7 +423,7 @@ func (t *TorrentManager) readFromFile(torrentID string) *realdebrid.TorrentInfo
return &torrent return &torrent
} }
func (t *TorrentManager) organizeChaos(info *realdebrid.Torrent, selectedFiles []File) ([]File, bool) { func (t *TorrentManager) organizeChaos(info *realdebrid.Torrent, selectedFiles *orderedmap.OrderedMap[string, *File]) (*orderedmap.OrderedMap[string, *File], bool) {
type Result struct { type Result struct {
Response *realdebrid.UnrestrictResponse Response *realdebrid.UnrestrictResponse
} }
@@ -465,18 +459,19 @@ func (t *TorrentManager) organizeChaos(info *realdebrid.Torrent, selectedFiles [
continue continue
} }
found := false found := false
for i := range selectedFiles { for el := selectedFiles.Front(); el != nil; el = el.Next() {
if strings.Contains(selectedFiles[i].Path, result.Response.Filename) { if file, _ := selectedFiles.Get(el.Key); strings.Contains(file.Path, result.Response.Filename) {
t.log.Debugf("Found a file that is in the selection for torrent id=%s: %s", info.ID, result.Response.Filename) t.log.Debugf("Found a file that is in the selection for torrent id=%s: %s", info.ID, result.Response.Filename)
selectedFiles[i].Link = result.Response.Link file.Link = result.Response.Link
found = true found = true
} }
} }
if !found { if !found {
isChaotic = result.Response.Streamable == 0 t.log.Debugf("Found a file that is NOT in the selection for torrent id=%s: %s %v", info.ID, result.Response.Filename, result.Response.Streamable)
t.log.Debugf("Found a file that is not in the selection for torrent id=%s: %s %v", info.ID, result.Response.Filename, result.Response.Streamable) if result.Response.Streamable == 1 {
selectedFiles = append(selectedFiles, File{ selectedFiles.Set(filepath.Base(result.Response.Filename), &File{
File: realdebrid.File{ File: realdebrid.File{
ID: math.MaxInt32,
Path: result.Response.Filename, Path: result.Response.Filename,
Bytes: result.Response.Filesize, Bytes: result.Response.Filesize,
Selected: 1, Selected: 1,
@@ -485,64 +480,40 @@ func (t *TorrentManager) organizeChaos(info *realdebrid.Torrent, selectedFiles [
}) })
} }
} }
}
return selectedFiles, isChaotic return selectedFiles, isChaotic
} }
// HideTheFile marks a file as deleted
// func (t *TorrentManager) HideTheFile(torrent *Torrent, file *File) {
// file.Unavailable = true
// t.repair(torrent, false)
// }
// func (t *TorrentManager) repairAll() { // func (t *TorrentManager) repairAll() {
// for _, torrent := range t.torrentMap { // for el := t.TorrentMap.Front(); el != nil; el = el.Next() {
// // do not repair if: // torrent := el.Value
// // in progress // // do not repair if: in progress
// hasInProgress := false // if torrent.InProgress {
// for _, info := range torrent.Instances {
// if info.Progress != 100 {
// hasInProgress = true
// break
// }
// }
// if hasInProgress {
// continue // continue
// } // }
// // already repaired based on other instances
// var missingFiles []File // var missingFiles []File
// for _, file := range torrent.SelectedFiles { // for el2 := torrent.SelectedFiles.Front(); el2 != nil; el2 = el2.Next() {
// if file.Link == "" || file.Unavailable { // file, ok := torrent.SelectedFiles.Get(el2.Key)
// missingFiles = append(missingFiles, file) // if !ok {
// } // continue
// }
// 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)
// } // }
// // check for case of repairs like
// // case 1: missing links
// // case 2: unrestrictable links TODO
// if file.Link == "" {
// missingFiles = append(missingFiles, *file)
// } // }
// } // }
// if len(missingFiles) == 0 { // if len(missingFiles) == 0 {
// t.log.Infof("Torrent id=%s is already repaired", info.ID) // continue
// return
// } // }
// for _, info := range torrent.Instances { // for _, info := range torrent.Instances {
// if info.Progress != 100 {
// continue
// }
// if info.ForRepair { // if info.ForRepair {
// t.log.Infof("There were less links than was expected on %s %s; fixing...", info.ID, info.Name) // t.log.Infof("There were less links than was expected on %s %s; fixing...", info.ID, info.Name)
// t.repair(&info, true) // // t.repair(&info, true)
// break // only repair the first one for repair and then move on // break // only repair the first one for repair and then move on
// } // }
// if len(info.Links) == 0 && info.Progress == 100 { // if len(info.Links) == 0 && info.Progress == 100 {
@@ -557,6 +528,7 @@ func (t *TorrentManager) organizeChaos(info *realdebrid.Torrent, selectedFiles [
// } // }
// func (t *TorrentManager) repair(info *realdebrid.TorrentInfo, tryReinsertionFirst bool) { // func (t *TorrentManager) repair(info *realdebrid.TorrentInfo, tryReinsertionFirst bool) {
// // file.Link == "" should be repaired
// // then we repair it! // // then we repair it!
// t.log.Infof("Repairing torrent id=%s", info.ID) // t.log.Infof("Repairing torrent id=%s", info.ID)
// // check if we can still add more downloads // // check if we can still add more downloads
@@ -576,6 +548,9 @@ func (t *TorrentManager) organizeChaos(info *realdebrid.Torrent, selectedFiles [
// var otherStreamableFileIDs []int // var otherStreamableFileIDs []int
// for _, file := range info.Files { // for _, file := range info.Files {
// found := false // found := false
// for el := selectedFiles.Front(); el != nil; el = el.Next() {
// }
// for _, selectedFile := range selectedFiles { // for _, selectedFile := range selectedFiles {
// if selectedFile.ID == file.ID { // if selectedFile.ID == file.ID {
// found = true // found = true

View File

@@ -2,11 +2,12 @@ package torrent
import ( import (
"github.com/debridmediamanager.com/zurg/pkg/realdebrid" "github.com/debridmediamanager.com/zurg/pkg/realdebrid"
"github.com/elliotchance/orderedmap/v2"
) )
type Torrent struct { type Torrent struct {
AccessKey string AccessKey string
SelectedFiles []File SelectedFiles *orderedmap.OrderedMap[string, *File]
Directories []string Directories []string
LatestAdded string LatestAdded string
InProgress bool InProgress bool
@@ -17,5 +18,4 @@ type Torrent struct {
type File struct { type File struct {
realdebrid.File realdebrid.File
Link string Link string
Unavailable bool
} }

View File

@@ -47,17 +47,17 @@ func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *torrent.Torrent
} }
baseDirectory := segments[len(segments)-3] baseDirectory := segments[len(segments)-3]
torrentName := segments[len(segments)-2] accessKey := segments[len(segments)-2]
filename := segments[len(segments)-1] filename := segments[len(segments)-1]
torrents := t.FindAllTorrentsWithName(baseDirectory, torrentName) torrent, _ := t.TorrentMap.Get(accessKey)
if torrents == nil { if torrent == nil {
log.Errorf("Cannot find torrent %s in the directory %s", requestPath, baseDirectory) log.Errorf("Cannot find torrent %s in the directory %s", accessKey, baseDirectory)
http.Error(w, "File not found", http.StatusNotFound) http.Error(w, "File not found", http.StatusNotFound)
return return
} }
_, file := getFile(torrents, filename) file, _ := torrent.SelectedFiles.Get(filename)
if file == nil { if file == nil {
log.Errorf("Cannot find file from path %s", requestPath) log.Errorf("Cannot find file from path %s", requestPath)
http.Error(w, "File not found", http.StatusNotFound) http.Error(w, "File not found", http.StatusNotFound)
@@ -73,10 +73,7 @@ func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *torrent.Torrent
resp := t.UnrestrictUntilOk(link) resp := t.UnrestrictUntilOk(link)
if resp == nil { if resp == nil {
if !file.Unavailable { // TODO: maybe repair the torrent?
log.Errorf("Cannot unrestrict file %s %s", filename, link)
// t.HideTheFile(torrent, file)
}
streamErrorVideo("https://www.youtube.com/watch?v=gea_FJrtFVA", w, r, t, c, log) streamErrorVideo("https://www.youtube.com/watch?v=gea_FJrtFVA", w, r, t, c, log)
return return
} else if resp.Filename != filename { } else if resp.Filename != filename {

View File

@@ -41,17 +41,17 @@ func HandleHeadRequest(w http.ResponseWriter, r *http.Request, t *torrent.Torren
} }
baseDirectory := segments[len(segments)-3] baseDirectory := segments[len(segments)-3]
torrentName := segments[len(segments)-2] accessKey := segments[len(segments)-2]
filename := segments[len(segments)-1] filename := segments[len(segments)-1]
torrents := t.FindAllTorrentsWithName(baseDirectory, torrentName) torrent, _ := t.TorrentMap.Get(accessKey)
if torrents == nil { if torrent == nil {
log.Errorf("Cannot find torrent %s in the directory %s", requestPath, baseDirectory) log.Errorf("Cannot find torrent %s in the directory %s", accessKey, baseDirectory)
http.Error(w, "Cannot find file", http.StatusNotFound) http.Error(w, "File not found", http.StatusNotFound)
return return
} }
_, file := getFile(torrents, filename) file, _ := torrent.SelectedFiles.Get(filename)
if file == nil { if file == nil {
log.Errorf("Cannot find file from path %s", requestPath) log.Errorf("Cannot find file from path %s", requestPath)
http.Error(w, "Cannot find file", http.StatusNotFound) http.Error(w, "Cannot find file", http.StatusNotFound)

View File

@@ -1,20 +1 @@
package universal 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

@@ -44,20 +44,20 @@ type TorrentInfo struct {
Version string `json:"-"` Version string `json:"-"`
} }
func (t *Torrent) UnmarshalJSON(data []byte) error { func (i *TorrentInfo) UnmarshalJSON(data []byte) error {
type Alias Torrent type Alias TorrentInfo
aux := &struct { aux := &struct {
Progress float64 `json:"progress"` Progress float64 `json:"progress"`
*Alias *Alias
}{ }{
Alias: (*Alias)(t), Alias: (*Alias)(i),
} }
if err := json.Unmarshal(data, &aux); err != nil { if err := json.Unmarshal(data, &aux); err != nil {
return err return err
} }
t.Progress = int(math.Round(aux.Progress)) i.Progress = int(math.Round(aux.Progress))
return nil return nil
} }