Introduce components
This commit is contained in:
@@ -6,14 +6,14 @@ A self-hosted Real-Debrid webdav server written from scratch. Together with [rcl
|
|||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
### Latest version: v0.9.3-hotfix.9
|
### Latest version: v0.10.0
|
||||||
|
|
||||||
[Download the binary](https://github.com/debridmediamanager/zurg-testing/releases) or use docker
|
[Download the binary](https://github.com/debridmediamanager/zurg-testing/releases) or use docker
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker pull ghcr.io/debridmediamanager/zurg-testing:latest
|
docker pull ghcr.io/debridmediamanager/zurg-testing:latest
|
||||||
# or
|
# or
|
||||||
docker pull ghcr.io/debridmediamanager/zurg-testing:v0.9.3-hotfix.9
|
docker pull ghcr.io/debridmediamanager/zurg-testing:v0.10.0
|
||||||
```
|
```
|
||||||
|
|
||||||
## How to run zurg in 5 steps for Plex with Docker
|
## How to run zurg in 5 steps for Plex with Docker
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -19,6 +19,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/looplab/fsm v1.0.1 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -11,6 +11,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
|||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/looplab/fsm v1.0.1 h1:OEW0ORrIx095N/6lgoGkFkotqH6s7vaFPsgjLAaF5QU=
|
||||||
|
github.com/looplab/fsm v1.0.1/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
|||||||
@@ -12,16 +12,7 @@ func (t *TorrentManager) CheckDeletedStatus(torrent *Torrent) bool {
|
|||||||
if len(deletedIDs) == torrent.SelectedFiles.Count() && len(deletedIDs) > 0 {
|
if len(deletedIDs) == torrent.SelectedFiles.Count() && len(deletedIDs) > 0 {
|
||||||
return true
|
return true
|
||||||
} else if len(deletedIDs) > 0 {
|
} else if len(deletedIDs) > 0 {
|
||||||
t.saveTorrentChangesToDisk(torrent, func(info *Torrent) {
|
t.writeTorrentToFile(torrent)
|
||||||
info.SelectedFiles.IterCb(func(_ string, file *File) {
|
|
||||||
for _, deletedID := range deletedIDs {
|
|
||||||
if file.ID == deletedID {
|
|
||||||
file.IsDeleted = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -29,15 +20,12 @@ func (t *TorrentManager) CheckDeletedStatus(torrent *Torrent) bool {
|
|||||||
func (t *TorrentManager) Delete(accessKey string, deleteInRD bool) {
|
func (t *TorrentManager) Delete(accessKey string, deleteInRD bool) {
|
||||||
allTorrents, _ := t.DirectoryMap.Get(INT_ALL)
|
allTorrents, _ := t.DirectoryMap.Get(INT_ALL)
|
||||||
if deleteInRD {
|
if deleteInRD {
|
||||||
infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE)
|
|
||||||
if torrent, ok := allTorrents.Get(accessKey); ok {
|
if torrent, ok := allTorrents.Get(accessKey); ok {
|
||||||
torrent.DownloadedIDs.Union(torrent.InProgressIDs).Each(func(id string) bool {
|
for torrentID := range torrent.Components {
|
||||||
t.log.Debugf("Deleting torrent %s (id=%s) in RD", accessKey, id)
|
t.log.Debugf("Deleting torrent %s (id=%s) in RD", accessKey, torrentID)
|
||||||
t.api.DeleteTorrent(id)
|
t.api.DeleteTorrent(torrentID)
|
||||||
infoCache.Remove(id)
|
t.deleteInfoFile(torrentID)
|
||||||
t.deleteTorrentFile(id)
|
}
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.log.Infof("Removing torrent %s from zurg database (not real-debrid)", accessKey)
|
t.log.Infof("Removing torrent %s from zurg database (not real-debrid)", accessKey)
|
||||||
|
|||||||
18
internal/torrent/file_types.go
Normal file
18
internal/torrent/file_types.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package torrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/debridmediamanager/zurg/pkg/realdebrid"
|
||||||
|
"github.com/looplab/fsm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
realdebrid.File
|
||||||
|
Link string `json:"Link"`
|
||||||
|
Ended string `json:"Ended"`
|
||||||
|
|
||||||
|
IsBroken bool `json:"IsBroken"`
|
||||||
|
IsDeleted bool `json:"IsDeleted"`
|
||||||
|
State *fsm.FSM `json:"-"`
|
||||||
|
|
||||||
|
Rename string `json:"Rename"`
|
||||||
|
}
|
||||||
@@ -60,11 +60,9 @@ func (t *TorrentManager) processFixers(instances []realdebrid.Torrent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE)
|
|
||||||
for _, id := range toDelete {
|
for _, id := range toDelete {
|
||||||
t.api.DeleteTorrent(id)
|
t.api.DeleteTorrent(id)
|
||||||
infoCache.Remove(id)
|
t.deleteInfoFile(id)
|
||||||
t.deleteTorrentFile(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, torrent := range toRedownload {
|
for _, torrent := range toRedownload {
|
||||||
|
|||||||
31
internal/torrent/fsm.go
Normal file
31
internal/torrent/fsm.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package torrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/looplab/fsm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFileState() *fsm.FSM {
|
||||||
|
return fsm.NewFSM(
|
||||||
|
"ok",
|
||||||
|
fsm.Events{
|
||||||
|
{Name: "break", Src: []string{"ok"}, Dst: "broken"},
|
||||||
|
{Name: "repair", Src: []string{"broken"}, Dst: "under_repair"},
|
||||||
|
{Name: "repair_done", Src: []string{"under_repair"}, Dst: "ok"},
|
||||||
|
{Name: "delete", Src: []string{"ok", "broken", "under_repair"}, Dst: "deleted"},
|
||||||
|
},
|
||||||
|
fsm.Callbacks{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTorrentState() *fsm.FSM {
|
||||||
|
return fsm.NewFSM(
|
||||||
|
"ok",
|
||||||
|
fsm.Events{
|
||||||
|
{Name: "break", Src: []string{"ok"}, Dst: "broken"},
|
||||||
|
{Name: "repair", Src: []string{"broken"}, Dst: "under_repair"},
|
||||||
|
{Name: "repair_done", Src: []string{"under_repair"}, Dst: "ok"},
|
||||||
|
{Name: "delete", Src: []string{"ok", "broken", "under_repair"}, Dst: "deleted"},
|
||||||
|
},
|
||||||
|
fsm.Callbacks{},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -18,8 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
INT_ALL = "int__all__"
|
INT_ALL = "int__all__"
|
||||||
INT_INFO_CACHE = "int__info__"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TorrentManager struct {
|
type TorrentManager struct {
|
||||||
@@ -41,7 +40,6 @@ type TorrentManager struct {
|
|||||||
|
|
||||||
latestState *LibraryState
|
latestState *LibraryState
|
||||||
allAccessKeys mapset.Set[string]
|
allAccessKeys mapset.Set[string]
|
||||||
allIDs mapset.Set[string]
|
|
||||||
|
|
||||||
fixers cmap.ConcurrentMap[string, string] // trigger -> [command, id]
|
fixers cmap.ConcurrentMap[string, string] // trigger -> [command, id]
|
||||||
repairTrigger chan *Torrent
|
repairTrigger chan *Torrent
|
||||||
@@ -55,7 +53,7 @@ type TorrentManager struct {
|
|||||||
// and store them in-memory and cached in files
|
// and store them in-memory and cached in files
|
||||||
func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, workerPool *ants.Pool, log *logutil.Logger) *TorrentManager {
|
func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, workerPool *ants.Pool, log *logutil.Logger) *TorrentManager {
|
||||||
t := &TorrentManager{
|
t := &TorrentManager{
|
||||||
requiredVersion: "0.9.3-hotfix.10",
|
requiredVersion: "0.10.0",
|
||||||
|
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
api: api,
|
api: api,
|
||||||
@@ -73,7 +71,6 @@ func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, w
|
|||||||
|
|
||||||
latestState: &LibraryState{},
|
latestState: &LibraryState{},
|
||||||
allAccessKeys: mapset.NewSet[string](),
|
allAccessKeys: mapset.NewSet[string](),
|
||||||
allIDs: mapset.NewSet[string](),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t.fixers = t.readFixersFromFile()
|
t.fixers = t.readFixersFromFile()
|
||||||
@@ -140,8 +137,10 @@ func (t *TorrentManager) GetPath(file *File) string {
|
|||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TorrentManager) writeTorrentToFile(instanceID string, torrent *Torrent) {
|
/// torrent functions
|
||||||
filePath := "data/" + instanceID + ".json"
|
|
||||||
|
func (t *TorrentManager) writeTorrentToFile(torrent *Torrent) {
|
||||||
|
filePath := "data/" + torrent.Hash + ".json"
|
||||||
file, err := os.Create(filePath)
|
file, err := os.Create(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.log.Warnf("Cannot create file %s: %v", filePath, err)
|
t.log.Warnf("Cannot create file %s: %v", filePath, err)
|
||||||
@@ -162,11 +161,11 @@ func (t *TorrentManager) writeTorrentToFile(instanceID string, torrent *Torrent)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t.log.Debugf("Saved torrent %s to file", instanceID)
|
t.log.Debugf("Saved torrent %s to file", torrent.Hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TorrentManager) readTorrentFromFile(torrentID string) *Torrent {
|
func (t *TorrentManager) readTorrentFromFile(hash string) *Torrent {
|
||||||
filePath := "data/" + torrentID + ".json"
|
filePath := "data/" + hash + ".json"
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -183,7 +182,7 @@ func (t *TorrentManager) readTorrentFromFile(torrentID string) *Torrent {
|
|||||||
if err := json.Unmarshal(jsonData, &torrent); err != nil {
|
if err := json.Unmarshal(jsonData, &torrent); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if torrent.DownloadedIDs.Union(torrent.InProgressIDs).IsEmpty() {
|
if len(torrent.Components) == 0 {
|
||||||
t.log.Fatal("Torrent has no downloaded or in progress ids")
|
t.log.Fatal("Torrent has no downloaded or in progress ids")
|
||||||
}
|
}
|
||||||
if torrent.Version != t.requiredVersion {
|
if torrent.Version != t.requiredVersion {
|
||||||
@@ -192,14 +191,72 @@ func (t *TorrentManager) readTorrentFromFile(torrentID string) *Torrent {
|
|||||||
return torrent
|
return torrent
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TorrentManager) deleteTorrentFile(torrentID string) {
|
func (t *TorrentManager) deleteTorrentFile(hash string) {
|
||||||
filePath := "data/" + torrentID + ".json"
|
filePath := "data/" + hash + ".json"
|
||||||
err := os.Remove(filePath)
|
err := os.Remove(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.log.Warnf("Cannot delete file %s: %v", filePath, err)
|
t.log.Warnf("Cannot delete file %s: %v", filePath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// end torrent functions
|
||||||
|
|
||||||
|
/// info functions
|
||||||
|
|
||||||
|
func (t *TorrentManager) writeInfoToFile(info *realdebrid.TorrentInfo) {
|
||||||
|
filePath := "data/" + info.ID + ".info"
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
t.log.Warnf("Cannot create info file %s: %v", filePath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(info)
|
||||||
|
if err != nil {
|
||||||
|
t.log.Warnf("Cannot marshal torrent info: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := file.Write(jsonData); err != nil {
|
||||||
|
t.log.Warnf("Cannot write to info file %s: %v", filePath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.log.Debugf("Saved torrent %s to info file", info.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TorrentManager) readInfoFromFile(torrentID string) *realdebrid.TorrentInfo {
|
||||||
|
filePath := "data/" + torrentID + ".info"
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
jsonData, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var info *realdebrid.TorrentInfo
|
||||||
|
if err := json.Unmarshal(jsonData, &info); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TorrentManager) deleteInfoFile(torrentID string) {
|
||||||
|
filePath := "data/" + torrentID + ".info"
|
||||||
|
err := os.Remove(filePath)
|
||||||
|
if err != nil {
|
||||||
|
t.log.Warnf("Cannot delete info file %s: %v", filePath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// end info functions
|
||||||
|
|
||||||
func (t *TorrentManager) mountDownloads() {
|
func (t *TorrentManager) mountDownloads() {
|
||||||
if !t.Config.EnableDownloadMount() {
|
if !t.Config.EnableDownloadMount() {
|
||||||
return
|
return
|
||||||
@@ -246,26 +303,10 @@ func (t *TorrentManager) StartDownloadsJob() {
|
|||||||
|
|
||||||
func (t *TorrentManager) initializeDirectories() {
|
func (t *TorrentManager) initializeDirectories() {
|
||||||
// create internal directories
|
// create internal directories
|
||||||
t.DirectoryMap.Set(INT_ALL, cmap.New[*Torrent]()) // key is GetAccessKey()
|
t.DirectoryMap.Set(INT_ALL, cmap.New[*Torrent]()) // key is GetAccessKey()
|
||||||
t.DirectoryMap.Set(INT_INFO_CACHE, cmap.New[*Torrent]()) // key is Torrent ID
|
|
||||||
// create directory maps
|
// create directory maps
|
||||||
for _, directory := range t.Config.GetDirectories() {
|
for _, directory := range t.Config.GetDirectories() {
|
||||||
t.DirectoryMap.Set(directory, cmap.New[*Torrent]())
|
t.DirectoryMap.Set(directory, cmap.New[*Torrent]())
|
||||||
// t.RootNode.AddChild(fs.NewFileNode(directory, true))
|
// t.RootNode.AddChild(fs.NewFileNode(directory, true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TorrentManager) saveTorrentChangesToDisk(torrent *Torrent, cb func(*Torrent)) {
|
|
||||||
infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE)
|
|
||||||
torrent.DownloadedIDs.Union(torrent.InProgressIDs).Each(func(id string) bool {
|
|
||||||
info, exists := infoCache.Get(id)
|
|
||||||
if !exists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if cb != nil {
|
|
||||||
cb(info)
|
|
||||||
}
|
|
||||||
t.writeTorrentToFile(id, info)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func (t *TorrentManager) refreshTorrents(isInitialRun bool) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
t.log.Infof("Fetched %d torrents", len(instances))
|
t.log.Infof("Fetched %d torrents", len(instances))
|
||||||
infoChan := make(chan *Torrent, len(instances))
|
torChan := make(chan *Torrent, len(instances))
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
for i := range instances {
|
for i := range instances {
|
||||||
@@ -29,60 +29,56 @@ func (t *TorrentManager) refreshTorrents(isInitialRun bool) []string {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
_ = t.workerPool.Submit(func() {
|
_ = t.workerPool.Submit(func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
infoChan <- t.getMoreInfo(instances[idx])
|
torChan <- t.getMoreInfo(instances[idx])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(infoChan)
|
close(torChan)
|
||||||
t.log.Infof("Fetched info for %d torrents", len(instances))
|
t.log.Infof("Fetched info for %d torrents", len(instances))
|
||||||
|
|
||||||
var updatedPaths []string
|
var updatedPaths []string
|
||||||
noInfoCount := 0
|
noInfoCount := 0
|
||||||
allTorrents, _ := t.DirectoryMap.Get(INT_ALL)
|
allTorrents, _ := t.DirectoryMap.Get(INT_ALL)
|
||||||
freshAccessKeys := mapset.NewSet[string]()
|
freshAccessKeys := mapset.NewSet[string]()
|
||||||
deletedIDs := t.allIDs.Clone()
|
for torrent := range torChan {
|
||||||
for info := range infoChan {
|
if torrent == nil {
|
||||||
if info == nil {
|
|
||||||
noInfoCount++
|
noInfoCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
infoID, _ := info.DownloadedIDs.Clone().Pop()
|
// there's only 1 component torrent at this point, let's get it
|
||||||
deletedIDs.Remove(infoID)
|
var tInfo *realdebrid.TorrentInfo
|
||||||
accessKey := t.GetKey(info)
|
for _, tInfo = range torrent.Components {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
accessKey := t.GetKey(torrent)
|
||||||
freshAccessKeys.Add(accessKey)
|
freshAccessKeys.Add(accessKey)
|
||||||
|
|
||||||
// update allTorrents
|
// update allTorrents
|
||||||
|
isNewID := false
|
||||||
mainTorrent, exists := allTorrents.Get(accessKey)
|
mainTorrent, exists := allTorrents.Get(accessKey)
|
||||||
if !exists {
|
if !exists {
|
||||||
allTorrents.Set(accessKey, info)
|
allTorrents.Set(accessKey, torrent)
|
||||||
} else {
|
mainTorrent = torrent
|
||||||
if !mainTorrent.DownloadedIDs.Contains(infoID) {
|
isNewID = true
|
||||||
merged := t.mergeToMain(mainTorrent, info)
|
} else if _, ok := mainTorrent.Components[tInfo.ID]; !ok {
|
||||||
allTorrents.Set(accessKey, merged)
|
merged := t.mergeToMain(mainTorrent, torrent)
|
||||||
}
|
allTorrents.Set(accessKey, merged)
|
||||||
|
mainTorrent = merged
|
||||||
|
isNewID = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for newly finished torrents for assigning to directories
|
if isNewID && tInfo.Progress == 100 {
|
||||||
isDone := info.DownloadedIDs.Cardinality() > 0 && info.InProgressIDs.IsEmpty()
|
// assign to directory
|
||||||
if isDone && !t.allIDs.Contains(infoID) {
|
t.assignedDirectoryCb(mainTorrent, func(directory string) {
|
||||||
var directories []string
|
|
||||||
mainTor, _ := allTorrents.Get(accessKey)
|
|
||||||
t.assignedDirectoryCb(mainTor, func(directory string) {
|
|
||||||
listing, _ := t.DirectoryMap.Get(directory)
|
listing, _ := t.DirectoryMap.Get(directory)
|
||||||
listing.Set(accessKey, mainTor)
|
listing.Set(accessKey, mainTorrent)
|
||||||
|
|
||||||
updatedPaths = append(updatedPaths, fmt.Sprintf("%s/%s", directory, accessKey))
|
updatedPaths = append(updatedPaths, fmt.Sprintf("%s/%s", directory, accessKey))
|
||||||
// this is just for the logs
|
|
||||||
if directory != config.ALL_TORRENTS {
|
|
||||||
directories = append(directories, directory)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
t.allIDs.Add(infoID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.allIDs.RemoveAll(deletedIDs.ToSlice()...)
|
|
||||||
t.log.Infof("Compiled into %d torrents, %d were missing info", allTorrents.Count(), noInfoCount)
|
t.log.Infof("Compiled into %d torrents, %d were missing info", allTorrents.Count(), noInfoCount)
|
||||||
|
|
||||||
// removed torrents
|
// removed torrents
|
||||||
@@ -138,24 +134,14 @@ func (t *TorrentManager) StartRefreshJob() {
|
|||||||
|
|
||||||
// getMoreInfo gets original name, size and files for a torrent
|
// getMoreInfo gets original name, size and files for a torrent
|
||||||
func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
|
func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
|
||||||
infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE)
|
info := t.readInfoFromFile(rdTorrent.ID)
|
||||||
|
if info == nil {
|
||||||
if cachedTor, exists := infoCache.Get(rdTorrent.ID); exists &&
|
var err error
|
||||||
cachedTor.SelectedFiles.Count() == len(rdTorrent.Links) {
|
info, err = t.api.GetTorrentInfo(rdTorrent.ID)
|
||||||
|
if err != nil {
|
||||||
return cachedTor
|
t.log.Warnf("Cannot get info for id=%s: %v", rdTorrent.ID, err)
|
||||||
|
return nil
|
||||||
} else if diskTor := t.readTorrentFromFile(rdTorrent.ID); diskTor != nil && !diskTor.AllInProgress() {
|
}
|
||||||
|
|
||||||
infoCache.Set(rdTorrent.ID, diskTor)
|
|
||||||
t.ResetSelectedFiles(diskTor)
|
|
||||||
return diskTor
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := t.api.GetTorrentInfo(rdTorrent.ID)
|
|
||||||
if err != nil {
|
|
||||||
t.log.Warnf("Cannot get info for id=%s: %v", rdTorrent.ID, err)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent := Torrent{
|
torrent := Torrent{
|
||||||
@@ -204,15 +190,10 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
|
|||||||
torrent.SelectedFiles.Set(filename, file)
|
torrent.SelectedFiles.Set(filename, file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
torrent.DownloadedIDs = mapset.NewSet[string]()
|
torrent.Components = map[string]*realdebrid.TorrentInfo{rdTorrent.ID: info}
|
||||||
torrent.InProgressIDs = mapset.NewSet[string]()
|
|
||||||
if rdTorrent.Progress == 100 {
|
if info.Progress == 100 {
|
||||||
torrent.DownloadedIDs.Add(info.ID)
|
t.writeInfoToFile(info)
|
||||||
// save to cache if it's not in progress anymore
|
|
||||||
infoCache.Set(rdTorrent.ID, &torrent)
|
|
||||||
t.saveTorrentChangesToDisk(&torrent, nil)
|
|
||||||
} else {
|
|
||||||
torrent.InProgressIDs.Add(info.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &torrent
|
return &torrent
|
||||||
@@ -247,6 +228,14 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) *Torrent {
|
|||||||
older = toMerge
|
older = toMerge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mergedComponents := map[string]*realdebrid.TorrentInfo{}
|
||||||
|
for k, v := range older.Components {
|
||||||
|
mergedComponents[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range newer.Components {
|
||||||
|
mergedComponents[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
// build the main torrent
|
// build the main torrent
|
||||||
mainTorrent := Torrent{
|
mainTorrent := Torrent{
|
||||||
Name: newer.Name,
|
Name: newer.Name,
|
||||||
@@ -255,8 +244,7 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) *Torrent {
|
|||||||
Hash: newer.Hash,
|
Hash: newer.Hash,
|
||||||
Added: newer.Added,
|
Added: newer.Added,
|
||||||
|
|
||||||
DownloadedIDs: newer.DownloadedIDs.Union(older.DownloadedIDs),
|
Components: mergedComponents,
|
||||||
InProgressIDs: newer.InProgressIDs.Union(older.InProgressIDs),
|
|
||||||
UnassignedLinks: newer.UnassignedLinks.Union(older.UnassignedLinks),
|
UnassignedLinks: newer.UnassignedLinks.Union(older.UnassignedLinks),
|
||||||
UnrepairableReason: newer.UnrepairableReason,
|
UnrepairableReason: newer.UnrepairableReason,
|
||||||
}
|
}
|
||||||
@@ -268,12 +256,6 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) *Torrent {
|
|||||||
mainTorrent.UnrepairableReason = older.UnrepairableReason
|
mainTorrent.UnrepairableReason = older.UnrepairableReason
|
||||||
}
|
}
|
||||||
|
|
||||||
// update in progress ids
|
|
||||||
mainTorrent.DownloadedIDs.Each(func(id string) bool {
|
|
||||||
mainTorrent.InProgressIDs.Remove(id)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
// the link can have the following values
|
// the link can have the following values
|
||||||
// 1. https://*** - the file is available
|
// 1. https://*** - the file is available
|
||||||
// 3. empty - the file is not available
|
// 3. empty - the file is not available
|
||||||
@@ -310,7 +292,10 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) *Torrent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TorrentManager) assignedDirectoryCb(tor *Torrent, cb func(string)) {
|
func (t *TorrentManager) assignedDirectoryCb(tor *Torrent, cb func(string)) {
|
||||||
torrentIDs := tor.DownloadedIDs.Union(tor.InProgressIDs).ToSlice()
|
torrentIDs := []string{}
|
||||||
|
for id := range tor.Components {
|
||||||
|
torrentIDs = append(torrentIDs, id)
|
||||||
|
}
|
||||||
// get filenames needed for directory conditions
|
// get filenames needed for directory conditions
|
||||||
var filenames []string
|
var filenames []string
|
||||||
var fileSizes []int64
|
var fileSizes []int64
|
||||||
|
|||||||
@@ -154,7 +154,11 @@ func (t *TorrentManager) Repair(torrent *Torrent, wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TorrentManager) repair(torrent *Torrent) {
|
func (t *TorrentManager) repair(torrent *Torrent) {
|
||||||
t.log.Infof("Started repair process for torrent %s (ids=%v)", t.GetKey(torrent), torrent.DownloadedIDs.Union(torrent.InProgressIDs).ToSlice())
|
torrentIDs := []string{}
|
||||||
|
for id := range torrent.Components {
|
||||||
|
torrentIDs = append(torrentIDs, id)
|
||||||
|
}
|
||||||
|
t.log.Infof("Started repair process for torrent %s (ids=%v)", t.GetKey(torrent), torrentIDs)
|
||||||
|
|
||||||
// handle torrents with incomplete links for selected files
|
// handle torrents with incomplete links for selected files
|
||||||
// torrent can be rare'ed by RD, so we need to check for that
|
// torrent can be rare'ed by RD, so we need to check for that
|
||||||
@@ -171,27 +175,14 @@ func (t *TorrentManager) repair(torrent *Torrent) {
|
|||||||
// first step: redownload the whole torrent
|
// first step: redownload the whole torrent
|
||||||
info, err := t.redownloadTorrent(torrent, "") // reinsert the torrent, passing ""
|
info, err := t.redownloadTorrent(torrent, "") // reinsert the torrent, passing ""
|
||||||
if info != nil && info.Progress != 100 {
|
if info != nil && info.Progress != 100 {
|
||||||
torrent.InProgressIDs.Add(info.ID)
|
|
||||||
t.saveTorrentChangesToDisk(torrent, nil)
|
|
||||||
t.log.Infof("Torrent %s (files=%s) is still in progress after redownloading but it should be repaired once done", t.GetKey(torrent), brokenFileIDs)
|
t.log.Infof("Torrent %s (files=%s) is still in progress after redownloading but it should be repaired once done", t.GetKey(torrent), brokenFileIDs)
|
||||||
return
|
return
|
||||||
} else if info != nil && info.Progress == 100 && !t.isStillBroken(info, brokenFiles) {
|
} else if info != nil && info.Progress == 100 && !t.isStillBroken(info, brokenFiles) {
|
||||||
selectedFiles := getSelectedFiles(info)
|
|
||||||
torrent.SelectedFiles.IterCb(func(_ string, oldFile *File) {
|
|
||||||
for _, newFile := range selectedFiles {
|
|
||||||
if oldFile.Bytes == newFile.Bytes {
|
|
||||||
oldFile.Link = newFile.Link
|
|
||||||
oldFile.IsBroken = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
torrent.DownloadedIDs.Add(info.ID)
|
|
||||||
t.saveTorrentChangesToDisk(torrent, nil)
|
|
||||||
t.log.Infof("Successfully repaired torrent %s (files=%s) by redownloading", t.GetKey(torrent), brokenFileIDs)
|
t.log.Infof("Successfully repaired torrent %s (files=%s) by redownloading", t.GetKey(torrent), brokenFileIDs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.log.Warnf("Cannot repair torrent %s by redownloading (error=%s)", t.GetKey(torrent), err.Error())
|
|
||||||
|
t.log.Warnf("Cannot repair torrent %s by redownloading all files (error=%s)", t.GetKey(torrent), err.Error())
|
||||||
|
|
||||||
if torrent.UnrepairableReason != "" {
|
if torrent.UnrepairableReason != "" {
|
||||||
t.log.Debugf("Torrent %s has been marked as unfixable during redownload (%s), ending repair process early", t.GetKey(torrent), torrent.UnrepairableReason)
|
t.log.Debugf("Torrent %s has been marked as unfixable during redownload (%s), ending repair process early", t.GetKey(torrent), torrent.UnrepairableReason)
|
||||||
@@ -209,7 +200,10 @@ func (t *TorrentManager) repair(torrent *Torrent) {
|
|||||||
} else if len(brokenFiles) > 1 {
|
} else if len(brokenFiles) > 1 {
|
||||||
t.log.Infof("Repairing by downloading 2 batches of the %d broken files of torrent %s", len(brokenFiles), t.GetKey(torrent))
|
t.log.Infof("Repairing by downloading 2 batches of the %d broken files of torrent %s", len(brokenFiles), t.GetKey(torrent))
|
||||||
|
|
||||||
oldTorrentIDs := torrent.DownloadedIDs.Union(torrent.InProgressIDs).ToSlice()
|
oldTorrentIDs := []string{}
|
||||||
|
for id := range torrent.Components {
|
||||||
|
oldTorrentIDs = append(torrentIDs, id)
|
||||||
|
}
|
||||||
|
|
||||||
newlyDownloadedIds := make([]string, 0)
|
newlyDownloadedIds := make([]string, 0)
|
||||||
group := make([]*File, 0)
|
group := make([]*File, 0)
|
||||||
@@ -270,7 +264,7 @@ func (t *TorrentManager) assignUnassignedLinks(torrent *Torrent) bool {
|
|||||||
assigned := false
|
assigned := false
|
||||||
torrent.SelectedFiles.IterCb(func(_ string, file *File) {
|
torrent.SelectedFiles.IterCb(func(_ string, file *File) {
|
||||||
// base it on size because why not?
|
// base it on size because why not?
|
||||||
if file.Bytes == unrestrict.Filesize || strings.HasSuffix(strings.ToLower(file.Path), strings.ToLower(unrestrict.Filename)) {
|
if (unrestrict.Filesize > 1_000_000 && file.Bytes == unrestrict.Filesize) || strings.HasSuffix(strings.ToLower(file.Path), strings.ToLower(unrestrict.Filename)) {
|
||||||
file.Link = link
|
file.Link = link
|
||||||
file.IsBroken = false
|
file.IsBroken = false
|
||||||
assigned = true
|
assigned = true
|
||||||
@@ -329,9 +323,8 @@ func (t *TorrentManager) assignUnassignedLinks(torrent *Torrent) bool {
|
|||||||
|
|
||||||
// empty/reset the unassigned links as we have assigned them already
|
// empty/reset the unassigned links as we have assigned them already
|
||||||
if torrent.UnassignedLinks.Cardinality() > 0 {
|
if torrent.UnassignedLinks.Cardinality() > 0 {
|
||||||
t.saveTorrentChangesToDisk(torrent, func(info *Torrent) {
|
torrent.UnassignedLinks = mapset.NewSet[string]()
|
||||||
info.UnassignedLinks = mapset.NewSet[string]()
|
t.writeTorrentToFile(torrent)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -343,7 +336,9 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection string) (
|
|||||||
oldTorrentIDs := make([]string, 0)
|
oldTorrentIDs := make([]string, 0)
|
||||||
if selection == "" {
|
if selection == "" {
|
||||||
// only delete the old torrent if we are redownloading all files
|
// only delete the old torrent if we are redownloading all files
|
||||||
oldTorrentIDs = torrent.DownloadedIDs.Union(torrent.InProgressIDs).ToSlice()
|
for id := range torrent.Components {
|
||||||
|
oldTorrentIDs = append(oldTorrentIDs, id)
|
||||||
|
}
|
||||||
tmpSelection := ""
|
tmpSelection := ""
|
||||||
torrent.SelectedFiles.IterCb(func(_ string, file *File) {
|
torrent.SelectedFiles.IterCb(func(_ string, file *File) {
|
||||||
tmpSelection += fmt.Sprintf("%d,", file.ID) // select all files
|
tmpSelection += fmt.Sprintf("%d,", file.ID) // select all files
|
||||||
@@ -488,9 +483,7 @@ func (t *TorrentManager) markAsUnplayable(torrent *Torrent, reason string) {
|
|||||||
func (t *TorrentManager) markAsUnfixable(torrent *Torrent, reason string) {
|
func (t *TorrentManager) markAsUnfixable(torrent *Torrent, reason string) {
|
||||||
t.log.Warnf("Marking torrent %s as unfixable - %s", t.GetKey(torrent), reason)
|
t.log.Warnf("Marking torrent %s as unfixable - %s", t.GetKey(torrent), reason)
|
||||||
torrent.UnrepairableReason = reason
|
torrent.UnrepairableReason = reason
|
||||||
t.saveTorrentChangesToDisk(torrent, func(t *Torrent) {
|
t.writeTorrentToFile(torrent)
|
||||||
t.UnrepairableReason = reason
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBrokenFiles returns the files that are not http links and not deleted
|
// getBrokenFiles returns the files that are not http links and not deleted
|
||||||
|
|||||||
@@ -3,38 +3,37 @@ package torrent
|
|||||||
import (
|
import (
|
||||||
stdjson "encoding/json"
|
stdjson "encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/debridmediamanager/zurg/pkg/realdebrid"
|
"github.com/debridmediamanager/zurg/pkg/realdebrid"
|
||||||
mapset "github.com/deckarep/golang-set/v2"
|
mapset "github.com/deckarep/golang-set/v2"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/looplab/fsm"
|
||||||
cmap "github.com/orcaman/concurrent-map/v2"
|
cmap "github.com/orcaman/concurrent-map/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
|
||||||
type Torrent struct {
|
type Torrent struct {
|
||||||
Name string `json:"Name"` // immutable
|
Name string `json:"Name"`
|
||||||
OriginalName string `json:"OriginalName"` // immutable
|
OriginalName string `json:"OriginalName"`
|
||||||
Hash string `json:"Hash"` // immutable
|
Hash string `json:"Hash"`
|
||||||
Added string `json:"Added"` // immutable
|
Added string `json:"Added"`
|
||||||
DownloadedIDs mapset.Set[string] `json:"DownloadedIDs"` // immutable
|
Components map[string]*realdebrid.TorrentInfo `json:"Components"`
|
||||||
InProgressIDs mapset.Set[string] `json:"InProgressIDs"` // immutable
|
|
||||||
UnassignedLinks mapset.Set[string] `json:"UnassignedLinks"` // immutable
|
UnassignedLinks mapset.Set[string] `json:"UnassignedLinks"` // when links are not complete, we cannot assign them to a file so we store them here until it's fixed
|
||||||
|
|
||||||
Rename string `json:"Rename"` // modified over time
|
Rename string `json:"Rename"` // modified over time
|
||||||
SelectedFiles cmap.ConcurrentMap[string, *File] `json:"-"` // modified over time
|
SelectedFiles cmap.ConcurrentMap[string, *File] `json:"-"` // modified over time
|
||||||
UnrepairableReason string `json:"Unfixable"` // modified over time
|
UnrepairableReason string `json:"Unfixable"` // modified over time
|
||||||
|
|
||||||
Version string `json:"Version"` // only used for files
|
State *fsm.FSM `json:"-"`
|
||||||
|
Version string `json:"Version"` // only used for files
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Torrent) MarshalJSON() ([]byte, error) {
|
func (t *Torrent) MarshalJSON() ([]byte, error) {
|
||||||
type Alias Torrent
|
type Alias Torrent
|
||||||
temp := &struct {
|
temp := &struct {
|
||||||
SelectedFilesJson stdjson.RawMessage `json:"SelectedFiles"`
|
SelectedFilesJson stdjson.RawMessage `json:"SelectedFiles"`
|
||||||
DownloadedIDsJson stdjson.RawMessage `json:"DownloadedIDs"`
|
|
||||||
InProgressIDsJson stdjson.RawMessage `json:"InProgressIDs"`
|
|
||||||
UnassignedLinksJson stdjson.RawMessage `json:"UnassignedLinks"`
|
UnassignedLinksJson stdjson.RawMessage `json:"UnassignedLinks"`
|
||||||
*Alias
|
*Alias
|
||||||
}{
|
}{
|
||||||
@@ -47,20 +46,6 @@ func (t *Torrent) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
temp.SelectedFilesJson = selectedFilesJson
|
temp.SelectedFilesJson = selectedFilesJson
|
||||||
|
|
||||||
if t.DownloadedIDs.IsEmpty() {
|
|
||||||
temp.DownloadedIDsJson = []byte(`""`)
|
|
||||||
} else {
|
|
||||||
downloadedIDsStr := `"` + strings.Join(t.DownloadedIDs.ToSlice(), ",") + `"`
|
|
||||||
temp.DownloadedIDsJson = []byte(downloadedIDsStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.InProgressIDs.IsEmpty() {
|
|
||||||
temp.InProgressIDsJson = []byte(`""`)
|
|
||||||
} else {
|
|
||||||
inProgressIDsStr := `"` + strings.Join(t.InProgressIDs.ToSlice(), ",") + `"`
|
|
||||||
temp.InProgressIDsJson = []byte(inProgressIDsStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.UnassignedLinks.IsEmpty() {
|
if t.UnassignedLinks.IsEmpty() {
|
||||||
temp.UnassignedLinksJson = []byte(`""`)
|
temp.UnassignedLinksJson = []byte(`""`)
|
||||||
} else {
|
} else {
|
||||||
@@ -75,8 +60,6 @@ func (t *Torrent) UnmarshalJSON(data []byte) error {
|
|||||||
type Alias Torrent
|
type Alias Torrent
|
||||||
temp := &struct {
|
temp := &struct {
|
||||||
SelectedFilesJson stdjson.RawMessage `json:"SelectedFiles"`
|
SelectedFilesJson stdjson.RawMessage `json:"SelectedFiles"`
|
||||||
DownloadedIDsJson stdjson.RawMessage `json:"DownloadedIDs"`
|
|
||||||
InProgressIDsJson stdjson.RawMessage `json:"InProgressIDs"`
|
|
||||||
UnassignedLinksJson stdjson.RawMessage `json:"UnassignedLinks"`
|
UnassignedLinksJson stdjson.RawMessage `json:"UnassignedLinks"`
|
||||||
*Alias
|
*Alias
|
||||||
}{
|
}{
|
||||||
@@ -93,20 +76,6 @@ func (t *Torrent) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(temp.DownloadedIDsJson) > 2 {
|
|
||||||
downloadedIDs := strings.Split(strings.ReplaceAll(string(temp.DownloadedIDsJson), `"`, ""), ",")
|
|
||||||
t.DownloadedIDs = mapset.NewSet[string](downloadedIDs...)
|
|
||||||
} else {
|
|
||||||
t.DownloadedIDs = mapset.NewSet[string]()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(temp.InProgressIDsJson) > 2 {
|
|
||||||
inProgressIDs := strings.Split(strings.ReplaceAll(string(temp.InProgressIDsJson), `"`, ""), ",")
|
|
||||||
t.InProgressIDs = mapset.NewSet[string](inProgressIDs...)
|
|
||||||
} else {
|
|
||||||
t.InProgressIDs = mapset.NewSet[string]()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(temp.UnassignedLinksJson) > 2 {
|
if len(temp.UnassignedLinksJson) > 2 {
|
||||||
unassignedLinks := strings.Split(strings.ReplaceAll(string(temp.UnassignedLinksJson), `"`, ""), ",")
|
unassignedLinks := strings.Split(strings.ReplaceAll(string(temp.UnassignedLinksJson), `"`, ""), ",")
|
||||||
t.UnassignedLinks = mapset.NewSet[string](unassignedLinks...)
|
t.UnassignedLinks = mapset.NewSet[string](unassignedLinks...)
|
||||||
@@ -118,11 +87,21 @@ func (t *Torrent) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Torrent) AnyInProgress() bool {
|
func (t *Torrent) AnyInProgress() bool {
|
||||||
return !t.InProgressIDs.IsEmpty()
|
for _, info := range t.Components {
|
||||||
|
if info.Progress != 100 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Torrent) AllInProgress() bool {
|
func (t *Torrent) AllInProgress() bool {
|
||||||
return t.DownloadedIDs.IsEmpty() && !t.InProgressIDs.IsEmpty()
|
for _, info := range t.Components {
|
||||||
|
if info.Progress == 100 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Torrent) ComputeTotalSize() int64 {
|
func (t *Torrent) ComputeTotalSize() int64 {
|
||||||
@@ -133,6 +112,7 @@ func (t *Torrent) ComputeTotalSize() int64 {
|
|||||||
return totalSize
|
return totalSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used for showing only the biggest file in directory
|
||||||
func (t *Torrent) ComputeBiggestFileSize() int64 {
|
func (t *Torrent) ComputeBiggestFileSize() int64 {
|
||||||
biggestSize := int64(0)
|
biggestSize := int64(0)
|
||||||
t.SelectedFiles.IterCb(func(key string, value *File) {
|
t.SelectedFiles.IterCb(func(key string, value *File) {
|
||||||
@@ -142,20 +122,3 @@ func (t *Torrent) ComputeBiggestFileSize() int64 {
|
|||||||
})
|
})
|
||||||
return biggestSize
|
return biggestSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Torrent) OlderThanDuration(duration time.Duration) bool {
|
|
||||||
added, err := time.Parse(time.RFC3339, t.Added)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return time.Since(added) > duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
realdebrid.File
|
|
||||||
Ended string `json:"Ended"`
|
|
||||||
Link string `json:"Link"`
|
|
||||||
IsBroken bool `json:"IsBroken"`
|
|
||||||
IsDeleted bool `json:"IsDeleted"`
|
|
||||||
Rename string `json:"Rename"`
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user