Everything is in memory now without libs

This commit is contained in:
Ben Sarmiento
2023-10-18 02:06:01 +02:00
parent 0886c93250
commit bd72dc4540
6 changed files with 128 additions and 101 deletions

View File

@@ -79,7 +79,7 @@ func extractIDFromFilename(filename string) (string, string) {
} }
// findLinkByFragment finds a link by a fragment, it might be wrong // findLinkByFragment finds a link by a fragment, it might be wrong
func findLinkByFragment(torrents []realdebrid.Torrent, fragment string) string { func findLinkByFragment(torrents []torrent.Torrent, fragment string) string {
for _, torrent := range torrents { for _, torrent := range torrents {
for _, link := range torrent.Links { for _, link := range torrent.Links {
if strings.HasPrefix(link, fmt.Sprintf("https://real-debrid.com/d/%s", fragment)) { if strings.HasPrefix(link, fmt.Sprintf("https://real-debrid.com/d/%s", fragment)) {

View File

@@ -1,16 +1,14 @@
package dav package dav
import ( import (
"fmt"
"path/filepath" "path/filepath"
"github.com/debridmediamanager.com/zurg/internal/torrent" "github.com/debridmediamanager.com/zurg/internal/torrent"
"github.com/debridmediamanager.com/zurg/pkg/dav" "github.com/debridmediamanager.com/zurg/pkg/dav"
"github.com/debridmediamanager.com/zurg/pkg/realdebrid"
) )
// createMultiTorrentResponse creates a WebDAV response for a list of torrents // createMultiTorrentResponse creates a WebDAV response for a list of torrents
func createMultiTorrentResponse(torrents []realdebrid.Torrent) (*dav.MultiStatus, error) { func createMultiTorrentResponse(torrents []torrent.Torrent) (*dav.MultiStatus, error) {
var responses []dav.Response var responses []dav.Response
responses = append(responses, dav.Directory("/torrents")) responses = append(responses, dav.Directory("/torrents"))
@@ -36,7 +34,7 @@ func createMultiTorrentResponse(torrents []realdebrid.Torrent) (*dav.MultiStatus
} }
// createTorrentResponse creates a WebDAV response for torrents with the same name // createTorrentResponse creates a WebDAV response for torrents with the same name
func createCombinedTorrentResponse(torrents []realdebrid.Torrent, t *torrent.TorrentManager) (*dav.MultiStatus, error) { func createCombinedTorrentResponse(torrents []torrent.Torrent, t *torrent.TorrentManager) (*dav.MultiStatus, error) {
var responses []dav.Response var responses []dav.Response
// initial response is the directory itself // initial response is the directory itself
currentPath := filepath.Join("/torrents", torrents[0].Filename) currentPath := filepath.Join("/torrents", torrents[0].Filename)
@@ -51,43 +49,7 @@ func createCombinedTorrentResponse(torrents []realdebrid.Torrent, t *torrent.Tor
if info == nil { if info == nil {
continue continue
} }
for _, file := range info.SelectedFiles {
var selectedFiles []realdebrid.File
for _, file := range info.Files {
if file.Selected == 0 {
continue
}
selectedFiles = append(selectedFiles, file)
}
if len(selectedFiles) != len(info.Links) {
fmt.Println("Links and files do not match", info.Filename)
// TODO: Add auto-healing for this
// for _, link := range info.Links {
// unrestrictFn := func() (*realdebrid.UnrestrictResponse, error) {
// return realdebrid.UnrestrictCheck(os.Getenv("RD_TOKEN"), link)
// }
// resp := realdebrid.RetryUntilOk(unrestrictFn)
// if resp == nil {
// continue
// } else {
// if _, exists := seen[resp.Filename]; exists {
// continue
// }
// seen[resp.Filename] = true
// filePath := filepath.Join(currentPath, resp.Filename)
// torrentResponses = append(torrentResponses,
// dav.File(
// filePath,
// resp.Filesize,
// info.Added,
// resp.Link,
// ),
// )
// }
// }
} else {
for _, file := range selectedFiles {
filename := filepath.Base(file.Path) filename := filepath.Base(file.Path)
if _, exists := seen[filename]; exists { if _, exists := seen[filename]; exists {
continue continue
@@ -103,7 +65,6 @@ func createCombinedTorrentResponse(torrents []realdebrid.Torrent, t *torrent.Tor
idx++ idx++
} }
} }
}
responses = append(responses, torrentResponses...) responses = append(responses, torrentResponses...)
return &dav.MultiStatus{ return &dav.MultiStatus{

View File

@@ -5,7 +5,6 @@ import (
"time" "time"
"github.com/debridmediamanager.com/zurg/internal/torrent" "github.com/debridmediamanager.com/zurg/internal/torrent"
"github.com/debridmediamanager.com/zurg/pkg/realdebrid"
) )
// convertDate converts a date from RFC3339 to RFC1123 // convertDate converts a date from RFC3339 to RFC1123
@@ -19,8 +18,8 @@ func convertDate(input string) string {
} }
// findAllTorrentsWithName finds all torrents with a given name // findAllTorrentsWithName finds all torrents with a given name
func findAllTorrentsWithName(t *torrent.TorrentManager, filename string) []realdebrid.Torrent { func findAllTorrentsWithName(t *torrent.TorrentManager, filename string) []torrent.Torrent {
var matchingTorrents []realdebrid.Torrent var matchingTorrents []torrent.Torrent
torrents := t.GetAll() torrents := t.GetAll()
for _, torrent := range torrents { for _, torrent := range torrents {

View File

@@ -1 +0,0 @@
package torrent

View File

@@ -2,39 +2,30 @@ package torrent
import ( import (
"log" "log"
"strings"
"sync"
"github.com/debridmediamanager.com/zurg/pkg/realdebrid" "github.com/debridmediamanager.com/zurg/pkg/realdebrid"
"github.com/dgraph-io/ristretto"
) )
type TorrentManager struct { type TorrentManager struct {
token string token string
cache *ristretto.Cache torrents []Torrent
workerPool chan bool workerPool chan bool
} }
// 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 cache them // and store them in-memory
func NewTorrentManager(token string) *TorrentManager { func NewTorrentManager(token string) *TorrentManager {
cache, err := ristretto.NewCache(&ristretto.Config{
NumCounters: 1e7, // number of keys to track frequency of (10M).
MaxCost: 1 << 30, // maximum cost of cache (1GB).
BufferItems: 64, // number of keys per Get buffer.
})
if err != nil {
panic(err)
}
handler := &TorrentManager{ handler := &TorrentManager{
token: token, token: token,
cache: cache,
workerPool: make(chan bool, 10), workerPool: make(chan bool, 10),
} }
torrents := handler.getAll() handler.torrents = handler.getAll()
for _, torrent := range torrents { for _, torrent := range handler.torrents {
go func(id string) { go func(id string) {
handler.workerPool <- true handler.workerPool <- true
handler.getInfo(id) handler.getInfo(id)
@@ -46,48 +37,112 @@ func NewTorrentManager(token string) *TorrentManager {
return handler return handler
} }
func (t *TorrentManager) getAll() []realdebrid.Torrent { func (t *TorrentManager) getAll() []Torrent {
cacheKey := "t:all"
torrents, err := realdebrid.GetTorrents(t.token) torrents, err := realdebrid.GetTorrents(t.token)
if err != nil { if err != nil {
log.Printf("Cannot get torrents: %v\n", err.Error()) log.Printf("Cannot get torrents: %v\n", err.Error())
return nil return nil
} }
t.cache.Set(cacheKey, torrents, 0) var torrentsV2 []Torrent
return torrents for _, torrent := range torrents {
torrentsV2 = append(torrentsV2, Torrent{
Torrent: torrent,
SelectedFiles: nil,
})
}
return torrentsV2
} }
func (t *TorrentManager) GetAll() []realdebrid.Torrent { func (t *TorrentManager) GetAll() []Torrent {
cacheKey := "t:all" return t.torrents
if data, found := t.cache.Get(cacheKey); found {
if cachedTorrents, ok := data.([]realdebrid.Torrent); ok {
return cachedTorrents
} else {
t.cache.Del(cacheKey)
}
}
return t.getAll()
} }
func (t *TorrentManager) getInfo(torrentID string) *realdebrid.Torrent { func (t *TorrentManager) getInfo(torrentID string) *Torrent {
cacheKey := "t:" + torrentID
info, err := realdebrid.GetTorrentInfo(t.token, torrentID) info, err := realdebrid.GetTorrentInfo(t.token, torrentID)
if err != nil { if err != nil {
log.Printf("Cannot get info: %v\n", err.Error()) log.Printf("Cannot get info: %v\n", err.Error())
return nil return nil
} }
t.cache.Set(cacheKey, info, 0) var selectedFiles []File
return info for _, file := range info.Files {
if file.Selected == 0 {
continue
}
selectedFiles = append(selectedFiles, File{
File: file,
Link: "",
})
}
if len(selectedFiles) != len(info.Links) {
type Result struct {
Filename string
Link string
} }
func (t *TorrentManager) GetInfo(torrentID string) *realdebrid.Torrent { resultsChan := make(chan Result, len(info.Links))
cacheKey := "t:" + torrentID var wg sync.WaitGroup
if data, found := t.cache.Get(cacheKey); found {
if torrent, ok := data.(*realdebrid.Torrent); ok { // Limit concurrency
return torrent sem := make(chan struct{}, 10) // e.g., 10 concurrent requests
for _, link := range info.Links {
wg.Add(1)
sem <- struct{}{} // Acquire semaphore
go func(lnk string) {
defer wg.Done()
defer func() { <-sem }() // Release semaphore
unrestrictFn := func() (*realdebrid.UnrestrictResponse, error) {
return realdebrid.UnrestrictLink(t.token, lnk)
}
resp := realdebrid.RetryUntilOk(unrestrictFn)
if resp != nil {
resultsChan <- Result{Filename: resp.Filename, Link: resp.Link}
}
}(link)
}
go func() {
wg.Wait()
close(resultsChan)
}()
for result := range resultsChan {
for i := range selectedFiles {
if strings.HasSuffix(selectedFiles[i].Path, result.Filename) {
selectedFiles[i].Link = result.Link
}
}
}
} else { } else {
t.cache.Del(cacheKey) for i, link := range info.Links {
selectedFiles[i].Link = link
} }
} }
torrent := t.getByID(torrentID)
if torrent != nil {
torrent.SelectedFiles = selectedFiles
}
log.Println("Fetched info for", info.Filename)
return torrent
}
func (t *TorrentManager) GetInfo(torrentID string) *Torrent {
for _, torrent := range t.torrents {
if torrent.ID == torrentID {
if torrent.SelectedFiles != nil {
return t.getInfo(torrentID) return t.getInfo(torrentID)
} }
}
}
return nil
}
func (t *TorrentManager) getByID(torrentID string) *Torrent {
for _, torrent := range t.torrents {
if torrent.ID == torrentID {
return &torrent
}
}
return nil
}

13
internal/torrent/types.go Normal file
View File

@@ -0,0 +1,13 @@
package torrent
import "github.com/debridmediamanager.com/zurg/pkg/realdebrid"
type Torrent struct {
realdebrid.Torrent
SelectedFiles []File
}
type File struct {
realdebrid.File
Link string
}