Refactor with ordered maps
This commit is contained in:
@@ -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
1
go.mod
@@ -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
2
go.sum
@@ -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=
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user