Access key computation without clearing data

This commit is contained in:
Ben Sarmiento
2024-01-10 02:36:12 +01:00
parent 91f6d35831
commit ed87c2bbcc
12 changed files with 146 additions and 78 deletions

View File

@@ -1,17 +1,17 @@
# zurg # zurg
A self-hosted Real-Debrid webdav server written from scratch. Together with [rclone](https://rclone.org/) it can mount your Real-Debrid torrent library into your filesystem just like Dropbox. A self-hosted Real-Debrid webdav server written from scratch. Together with [rclone](https://rclone.org/) it can mount your Real-Debrid torrent library into your file system like Dropbox.
## Download ## Download
### Latest version: v0.9.2-hotfix.4 ### Latest version: v0.9.3
[Download the binary](https://github.com/debridmediamanager/zurg-testing/tree/main/releases/v0.9.2-hotfix.4) or use docker [Download the binary](https://github.com/debridmediamanager/zurg-testing/tree/main/releases/v0.9.3) 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.2-hotfix.4 docker pull ghcr.io/debridmediamanager/zurg-testing:v0.9.3
``` ```
## How to run zurg in 5 steps for Plex ## How to run zurg in 5 steps for Plex
@@ -26,7 +26,7 @@ A webdav server is also exposed to your localhost via port `9999`.
## Why zurg? Why not X? ## Why zurg? Why not X?
- Better performance than anything out there; changes in your library appear instantly (assuming Plex picks it up fast enough) - Better performance than anything out there; changes in your library appear instantly ([assuming Plex picks it up fast enough](./plex_update.sh))
- You should be able to access every file even if the torrent names are the same so if you have a lot of these, you might notice that zurg will have more files compared to others (e.g. 2 torrents named "Simpsons" but have different seasons, zurg merges all contents in that directory) - You should be able to access every file even if the torrent names are the same so if you have a lot of these, you might notice that zurg will have more files compared to others (e.g. 2 torrents named "Simpsons" but have different seasons, zurg merges all contents in that directory)
- You can configure a flexible directory structure in `config.yml`; you can select individual torrents that should appear on a directory by the ID you see in [DMM](https://debridmediamanager.com/). - You can configure a flexible directory structure in `config.yml`; you can select individual torrents that should appear on a directory by the ID you see in [DMM](https://debridmediamanager.com/).
- If you've ever experienced Plex scanner being stuck on a file and thereby freezing Plex completely, it should not happen anymore because zurg does a comprehensive check if a torrent is dead or not. You can run `ps aux --sort=-time | grep "Plex Media Scanner"` to check for stuck scanner processes. - If you've ever experienced Plex scanner being stuck on a file and thereby freezing Plex completely, it should not happen anymore because zurg does a comprehensive check if a torrent is dead or not. You can run `ps aux --sort=-time | grep "Plex Media Scanner"` to check for stuck scanner processes.
@@ -35,6 +35,8 @@ A webdav server is also exposed to your localhost via port `9999`.
- [@I-am-PUID-0](https://github.com/I-am-PUID-0) - [pd_zurg](https://github.com/I-am-PUID-0/pd_zurg) - [@I-am-PUID-0](https://github.com/I-am-PUID-0) - [pd_zurg](https://github.com/I-am-PUID-0/pd_zurg)
- [@Pukabyte](https://github.com/Pukabyte) - [Guide: Zurg + RDT + Prowlarr + Arrs + Petio + Autoscan + Plex + Scannarr](https://puksthepirate.notion.site/Guide-Zurg-RDT-Prowlarr-Arrs-Petio-Autoscan-Plex-Scannarr-eebe27d130fa400c8a0536cab9d46eb3) - [@Pukabyte](https://github.com/Pukabyte) - [Guide: Zurg + RDT + Prowlarr + Arrs + Petio + Autoscan + Plex + Scannarr](https://puksthepirate.notion.site/Guide-Zurg-RDT-Prowlarr-Arrs-Petio-Autoscan-Plex-Scannarr-eebe27d130fa400c8a0536cab9d46eb3)
- [u/pg988](https://www.reddit.com/user/pg988/) - [Windows + zurg + Plex guide](https://www.reddit.com/r/RealDebrid/comments/18so926/windows_zurg_plex_guide/)
- [@ignamiranda](https://github.com/ignamiranda) - [Plex Debrid Zurg Windows Guide](https://github.com/ignamiranda/plex_debrid_zurg_scripts/)
## Please read our [wiki](https://github.com/debridmediamanager/zurg-testing/wiki) for more information! ## Please read our [wiki](https://github.com/debridmediamanager/zurg-testing/wiki) for more information!

View File

@@ -2,18 +2,44 @@
zurg: v1 zurg: v1
token: YOUR_RD_API_TOKEN # https://real-debrid.com/apitoken token: YOUR_RD_API_TOKEN # https://real-debrid.com/apitoken
# basic functionality # do not change this if you are running it inside a docker container
host: "[::]" # do not change this if you are running it inside a docker container host: "[::]"
port: 9999 # do not change this if you are running it inside a docker container port: 9999
# you can protect zurg server with username+password auth
username: yowmamasita
password: 1234
# How many requests in parallel should we send to Real-Debrid API?
concurrent_workers: 20 concurrent_workers: 20
# How often should we check Real-Debrid API for file changes?
check_for_changes_every_secs: 15 check_for_changes_every_secs: 15
# misc configs # if true, you can rename directories and files; if false, saved rename info (if it exists) will be ignored
retain_folder_name_extension: false # if true, zurg won't modify the filenames from real-debrid allow_renames: true
retain_rd_torrent_name: false # if true, it will strictly follow RD API torrent name property w/c should make this more compatible with rdt-client
auto_delete_rar_torrents: false # if true, zurg will delete unstreamable rar files (these torrents will always be compressed in a rar archive no matter what files you select) # if true, it will strictly follow Real-Debrid API filename property
use_download_cache: true # if true, during zurg initialization, it will fetch all downloads to unrestrict links faster # setting to true should make zurg more compatible with rdt-client
enable_repair: true # BEWARE! THERE CAN ONLY BE 1 INSTANCE OF ZURG THAT SHOULD REPAIR YOUR TORRENTS retain_rd_torrent_name: false
# note: this is for cosmetic purposes only
# if true, zurg won't drop file extensions from directories
retain_folder_name_extension: false
# if true, zurg will delete Real-Debrid rar'ed torrents
# they are always compressed in a rar archive no matter what files you select
auto_delete_rar_torrents: false
# if true, during zurg initialization, it will fetch all downloads to unrestrict links faster
# it will also mount your download links in a special directory
use_download_cache: true
# BEWARE! THERE CAN ONLY BE 1 INSTANCE OF ZURG THAT SHOULD REPAIR YOUR TORRENTS
enable_repair: true
# this is useful for ensuring Plex adds your new content immediately
# uncomment the next line for triggering a partial scan
# on_library_update: sh plex_update.sh "$@" # on_library_update: sh plex_update.sh "$@"
on_library_update: | on_library_update: |
for arg in "$@" for arg in "$@"
@@ -21,15 +47,34 @@ on_library_update: |
echo "detected update on: $arg" echo "detected update on: $arg"
done done
# network configs # buffer size when zurg is streaming files
network_buffer_size: 1048576 # 1 MiB network_buffer_size: 1048576 # 1 MiB
serve_from_rclone: false # serve file data from rclone, not from zurg (zurg will only provide rclone the link to download)
verify_download_link: true # if true, zurg will check if the link is truly streamable; only relevant if serve_from_rclone is set to true (as it already does this all the time if serve_from_rclone is false) # true = send link to rclone and rclone will stream the file
force_ipv6: false # force connect to real-debrid ipv6 addresses # false = zurg will stream the file
rate_limit_sleep_secs: 6 # wait time after getting a 429 from Real-Debrid API serve_from_rclone: false
realdebrid_timeout_secs: 60 # api timeout
retries_until_failed: 5 # api failures until considered failed # true = zurg will check if the link is really working
# preferred_hosts: # Run ./zurg network-test # only relevant if serve_from_rclone is set to true
# as it already does this all the time if serve_from_rclone is false
verify_download_link: true
# force connect to real-debrid ipv6 addresses
# useful if you are blocked
force_ipv6: false
# sleep time after getting a 429 from Real-Debrid API
rate_limit_sleep_secs: 6
# time to wait before timing out
realdebrid_timeout_secs: 60
# api response failures until considered failed
retries_until_failed: 5
# use the fastest hosts from your location
# Run ./zurg network-test
# preferred_hosts:
# - 20.download.real-debrid.com # - 20.download.real-debrid.com
# - 21.download.real-debrid.com # - 21.download.real-debrid.com
# - 22.download.real-debrid.com # - 22.download.real-debrid.com

View File

@@ -19,6 +19,7 @@ type ConfigInterface interface {
GetNetworkBufferSize() int GetNetworkBufferSize() int
EnableRetainFolderNameExtension() bool EnableRetainFolderNameExtension() bool
EnableRetainRDTorrentName() bool EnableRetainRDTorrentName() bool
ShouldAllowRenames() bool
GetRandomPreferredHost() string GetRandomPreferredHost() string
ShouldServeFromRclone() bool ShouldServeFromRclone() bool
ShouldVerifyDownloadLink() bool ShouldVerifyDownloadLink() bool
@@ -41,6 +42,7 @@ type ZurgConfig struct {
NumOfWorkers int `yaml:"concurrent_workers" json:"concurrent_workers"` NumOfWorkers int `yaml:"concurrent_workers" json:"concurrent_workers"`
RefreshEverySeconds int `yaml:"check_for_changes_every_secs" json:"check_for_changes_every_secs"` RefreshEverySeconds int `yaml:"check_for_changes_every_secs" json:"check_for_changes_every_secs"`
AllowRenames bool `yaml:"allow_renames" json:"allow_renames"`
RetainRDTorrentName bool `yaml:"retain_rd_torrent_name" json:"retain_rd_torrent_name"` RetainRDTorrentName bool `yaml:"retain_rd_torrent_name" json:"retain_rd_torrent_name"`
RetainFolderNameExtension bool `yaml:"retain_folder_name_extension" json:"retain_folder_name_extension"` RetainFolderNameExtension bool `yaml:"retain_folder_name_extension" json:"retain_folder_name_extension"`
@@ -127,6 +129,10 @@ func (z *ZurgConfig) EnableRetainRDTorrentName() bool {
return z.RetainRDTorrentName return z.RetainRDTorrentName
} }
func (z *ZurgConfig) ShouldAllowRenames() bool {
return z.AllowRenames
}
func (z *ZurgConfig) GetRandomPreferredHost() string { func (z *ZurgConfig) GetRandomPreferredHost() string {
if len(z.PreferredHosts) == 0 { if len(z.PreferredHosts) == 0 {
return "" return ""

View File

@@ -47,7 +47,7 @@ func ServeTorrentsListForInfuse(directory string, torMgr *torrent.TorrentManager
if !ok || tor.AllInProgress() { if !ok || tor.AllInProgress() {
continue continue
} }
buf.WriteString(dav.BaseDirectory(tor.AccessKey, tor.LatestAdded)) buf.WriteString(dav.BaseDirectory(torMgr.GetKey(tor), tor.LatestAdded))
} }
buf.WriteString("</d:multistatus>") buf.WriteString("</d:multistatus>")
return buf.Bytes(), nil return buf.Bytes(), nil

View File

@@ -50,7 +50,7 @@ func ServeTorrentsList(directory string, torMgr *torrent.TorrentManager) ([]byte
if !ok || tor.AllInProgress() { if !ok || tor.AllInProgress() {
continue continue
} }
buf.WriteString(dav.Directory(tor.AccessKey, tor.LatestAdded)) buf.WriteString(dav.Directory(torMgr.GetKey(tor), tor.LatestAdded))
} }
buf.WriteString("</d:multistatus>") buf.WriteString("</d:multistatus>")
return buf.Bytes(), nil return buf.Bytes(), nil
@@ -74,7 +74,7 @@ func ServeFilesList(directory, torrentName string, torMgr *torrent.TorrentManage
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString("<?xml version=\"1.0\" encoding=\"utf-8\"?><d:multistatus xmlns:d=\"DAV:\">") buf.WriteString("<?xml version=\"1.0\" encoding=\"utf-8\"?><d:multistatus xmlns:d=\"DAV:\">")
buf.WriteString(dav.BaseDirectory(filepath.Join(directory, tor.AccessKey), tor.LatestAdded)) buf.WriteString(dav.BaseDirectory(filepath.Join(directory, torMgr.GetKey(tor)), tor.LatestAdded))
filenames := tor.SelectedFiles.Keys() filenames := tor.SelectedFiles.Keys()
sort.Strings(filenames) sort.Strings(filenames)
for _, filename := range filenames { for _, filename := range filenames {
@@ -113,7 +113,7 @@ func HandleSingleFile(directory, torrentName, fileName string, torMgr *torrent.T
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString("<?xml version=\"1.0\" encoding=\"utf-8\"?><d:multistatus xmlns:d=\"DAV:\">") buf.WriteString("<?xml version=\"1.0\" encoding=\"utf-8\"?><d:multistatus xmlns:d=\"DAV:\">")
buf.WriteString(dav.BaseDirectory(filepath.Join(directory, tor.AccessKey), tor.LatestAdded)) buf.WriteString(dav.BaseDirectory(filepath.Join(directory, torMgr.GetKey(tor)), tor.LatestAdded))
buf.WriteString(dav.File(fileName, file.Bytes, file.Ended)) buf.WriteString(dav.File(fileName, file.Bytes, file.Ended))
buf.WriteString("</d:multistatus>") buf.WriteString("</d:multistatus>")
return buf.Bytes(), nil return buf.Bytes(), nil

View File

@@ -17,7 +17,7 @@ func HandleRenameTorrent(directory, torrentName, newName string, torMgr *torrent
} }
torrents.Remove(torrentName) torrents.Remove(torrentName)
torrents.Set(newName, torrent) torrents.Set(newName, torrent)
torrent.AccessKey = newName torrent.Rename = newName
return nil return nil
} }

View File

@@ -48,7 +48,7 @@ func ServeTorrentsList(directory string, torMgr *torrent.TorrentManager) ([]byte
if !ok || tor.AllInProgress() { if !ok || tor.AllInProgress() {
continue continue
} }
buf.WriteString(fmt.Sprintf("<li><a href=\"/http/%s/\">%s</a></li>", filepath.Join(directory, url.PathEscape(tor.AccessKey)), tor.AccessKey)) buf.WriteString(fmt.Sprintf("<li><a href=\"/http/%s/\">%s</a></li>", filepath.Join(directory, url.PathEscape(torMgr.GetKey(tor))), torMgr.GetKey(tor)))
} }
return buf.Bytes(), nil return buf.Bytes(), nil
} }

View File

@@ -15,7 +15,7 @@ func (t *TorrentManager) CheckDeletedStatus(torrent *Torrent) bool {
infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE) infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE)
torrent.DownloadedIDs.Each(func(id string) bool { torrent.DownloadedIDs.Each(func(id string) bool {
infoCache.Set(id, torrent) infoCache.Set(id, torrent)
t.writeTorrentToFile(id, torrent) t.writeTorrentToFile(id, torrent, false)
return false return false
}) })
} }

View File

@@ -51,7 +51,7 @@ func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, p
// create internal directories // create internal directories
t.DirectoryMap = cmap.New[cmap.ConcurrentMap[string, *Torrent]]() t.DirectoryMap = cmap.New[cmap.ConcurrentMap[string, *Torrent]]()
t.DirectoryMap.Set(INT_ALL, cmap.New[*Torrent]()) // key is AccessKey t.DirectoryMap.Set(INT_ALL, cmap.New[*Torrent]()) // key is GetAccessKey()
t.DirectoryMap.Set(INT_INFO_CACHE, cmap.New[*Torrent]()) // key is Torrent ID t.DirectoryMap.Set(INT_INFO_CACHE, cmap.New[*Torrent]()) // key is Torrent ID
// create directory maps // create directory maps
for _, directory := range cfg.GetDirectories() { for _, directory := range cfg.GetDirectories() {
@@ -165,7 +165,7 @@ func (t *TorrentManager) assignedDirectoryCb(tor *Torrent, cb func(string)) {
cb(config.UNPLAYABLE_TORRENTS) cb(config.UNPLAYABLE_TORRENTS)
break break
} }
if t.Config.MeetsConditions(directory, tor.AccessKey, tor.ComputeTotalSize(), torrentIDs, filenames, fileSizes) { if t.Config.MeetsConditions(directory, t.GetKey(tor), tor.ComputeTotalSize(), torrentIDs, filenames, fileSizes) {
cb(directory) cb(directory)
break break
} }
@@ -174,21 +174,24 @@ func (t *TorrentManager) assignedDirectoryCb(tor *Torrent, cb func(string)) {
} }
} }
func (t *TorrentManager) computeAccessKey(name, originalName string) string { func (t *TorrentManager) GetKey(torrent *Torrent) string {
if t.Config.ShouldAllowRenames() && torrent.Rename != "" {
return torrent.Rename
}
if t.Config.EnableRetainRDTorrentName() { if t.Config.EnableRetainRDTorrentName() {
return name return torrent.Name
} }
// drop the extension from the name // drop the extension from the name
if t.Config.EnableRetainFolderNameExtension() && strings.Contains(name, originalName) { if t.Config.EnableRetainFolderNameExtension() && strings.Contains(torrent.Name, torrent.OriginalName) {
return name return torrent.Name
} else { } else {
ret := strings.TrimSuffix(originalName, ".mp4") ret := strings.TrimSuffix(torrent.OriginalName, ".mp4")
ret = strings.TrimSuffix(ret, ".mkv") ret = strings.TrimSuffix(ret, ".mkv")
return ret return ret
} }
} }
func (t *TorrentManager) writeTorrentToFile(instanceID string, torrent *Torrent) { func (t *TorrentManager) writeTorrentToFile(instanceID string, torrent *Torrent, overwriteNames bool) {
filePath := "data/" + instanceID + ".json" filePath := "data/" + instanceID + ".json"
file, err := os.Create(filePath) file, err := os.Create(filePath)
if err != nil { if err != nil {
@@ -197,6 +200,13 @@ func (t *TorrentManager) writeTorrentToFile(instanceID string, torrent *Torrent)
} }
defer file.Close() defer file.Close()
if !overwriteNames {
infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE)
if cachedTorrent, exists := infoCache.Get(instanceID); exists {
torrent.Name = cachedTorrent.Name
torrent.OriginalName = cachedTorrent.OriginalName
}
}
torrent.Version = t.requiredVersion torrent.Version = t.requiredVersion
jsonData, err := json.Marshal(torrent) jsonData, err := json.Marshal(torrent)

View File

@@ -44,13 +44,13 @@ func (t *TorrentManager) RefreshTorrents() []string {
continue continue
} }
if !info.AnyInProgress() { if !info.AnyInProgress() {
freshKeys.Add(info.AccessKey) freshKeys.Add(t.GetKey(info))
} }
if torrent, exists := allTorrents.Get(info.AccessKey); !exists { if torrent, exists := allTorrents.Get(t.GetKey(info)); !exists {
allTorrents.Set(info.AccessKey, info) allTorrents.Set(t.GetKey(info), info)
} else if !info.DownloadedIDs.Difference(torrent.DownloadedIDs).IsEmpty() { } else if !info.DownloadedIDs.Difference(torrent.DownloadedIDs).IsEmpty() {
mainTorrent := t.mergeToMain(torrent, info) mainTorrent := t.mergeToMain(torrent, info)
allTorrents.Set(info.AccessKey, &mainTorrent) allTorrents.Set(t.GetKey(info), &mainTorrent)
} }
} }
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)
@@ -130,9 +130,10 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
} }
torrent := Torrent{ torrent := Torrent{
AccessKey: t.computeAccessKey(info.Name, info.OriginalName), Name: info.Name,
LatestAdded: info.Added, OriginalName: info.OriginalName,
Hash: info.Hash, LatestAdded: info.Added,
Hash: info.Hash,
} }
// SelectedFiles is a subset of Files with only the selected ones // SelectedFiles is a subset of Files with only the selected ones
// it also has a Link field, which can be empty // it also has a Link field, which can be empty
@@ -180,21 +181,23 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
torrent.InProgressIDs.Add(info.ID) torrent.InProgressIDs.Add(info.ID)
} }
t.writeTorrentToFile(rdTorrent.ID, &torrent) t.writeTorrentToFile(rdTorrent.ID, &torrent, true)
infoCache.Set(rdTorrent.ID, &torrent) infoCache.Set(rdTorrent.ID, &torrent)
return &torrent return &torrent
} }
func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) Torrent { func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) Torrent {
mainTorrent := Torrent{} mainTorrent := Torrent{
Name: existing.Name,
mainTorrent.AccessKey = existing.AccessKey OriginalName: existing.OriginalName,
mainTorrent.Hash = existing.Hash Rename: existing.Rename,
mainTorrent.DownloadedIDs = mapset.NewSet[string]() Hash: existing.Hash,
mainTorrent.InProgressIDs = mapset.NewSet[string]() DownloadedIDs: mapset.NewSet[string](),
mainTorrent.Unfixable = existing.Unfixable || toMerge.Unfixable InProgressIDs: mapset.NewSet[string](),
mainTorrent.UnassignedLinks = existing.UnassignedLinks.Union(toMerge.UnassignedLinks) Unfixable: existing.Unfixable || toMerge.Unfixable,
UnassignedLinks: existing.UnassignedLinks.Union(toMerge.UnassignedLinks),
}
// this function triggers only when we have a new DownloadedID // this function triggers only when we have a new DownloadedID
toMerge.DownloadedIDs.Difference(existing.DownloadedIDs).Each(func(id string) bool { toMerge.DownloadedIDs.Difference(existing.DownloadedIDs).Each(func(id string) bool {

View File

@@ -96,7 +96,7 @@ func (t *TorrentManager) repairAll() {
t.log.Debugf("Found %d broken torrents to repair in total", len(toRepair)) t.log.Debugf("Found %d broken torrents to repair in total", len(toRepair))
for i := range toRepair { for i := range toRepair {
torrent := toRepair[i] torrent := toRepair[i]
t.log.Infof("Repairing %s", torrent.AccessKey) t.log.Infof("Repairing %s", t.GetKey(torrent))
t.repair(torrent) t.repair(torrent)
} }
} }
@@ -105,19 +105,19 @@ func (t *TorrentManager) Repair(torrent *Torrent) {
infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE) infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE)
torrent.DownloadedIDs.Each(func(id string) bool { torrent.DownloadedIDs.Each(func(id string) bool {
infoCache.Set(id, torrent) infoCache.Set(id, torrent)
t.writeTorrentToFile(id, torrent) t.writeTorrentToFile(id, torrent, false)
return false return false
}) })
_ = t.repairWorker.Submit(func() { _ = t.repairWorker.Submit(func() {
t.log.Infof("Repairing torrent %s", torrent.AccessKey) t.log.Infof("Repairing torrent %s", t.GetKey(torrent))
t.repair(torrent) t.repair(torrent)
t.log.Infof("Finished repairing torrent %s", torrent.AccessKey) t.log.Infof("Finished repairing torrent %s", t.GetKey(torrent))
}) })
} }
func (t *TorrentManager) repair(torrent *Torrent) { func (t *TorrentManager) repair(torrent *Torrent) {
if torrent.AnyInProgress() { if torrent.AnyInProgress() {
t.log.Infof("Torrent %s is in progress, skipping repair until download is done", torrent.AccessKey) t.log.Infof("Torrent %s is in progress, skipping repair until download is done", t.GetKey(torrent))
return return
} }
@@ -129,18 +129,18 @@ func (t *TorrentManager) repair(torrent *Torrent) {
if torrent.OlderThanDuration(EXPIRED_LINK_TOLERANCE_HOURS * time.Hour) { if torrent.OlderThanDuration(EXPIRED_LINK_TOLERANCE_HOURS * time.Hour) {
// first solution: reinsert with same selection // first solution: reinsert with same selection
t.log.Infof("Torrent %s is older than %d hours, reinserting it", torrent.AccessKey, EXPIRED_LINK_TOLERANCE_HOURS) t.log.Infof("Torrent %s is older than %d hours, reinserting it", t.GetKey(torrent), EXPIRED_LINK_TOLERANCE_HOURS)
if t.reinsertTorrent(torrent, "") { if t.reinsertTorrent(torrent, "") {
t.log.Infof("Successfully downloaded torrent %s to repair it", torrent.AccessKey) t.log.Infof("Successfully downloaded torrent %s to repair it", t.GetKey(torrent))
return return
} else if torrent.Unfixable { } else if torrent.Unfixable {
t.log.Warnf("Cannot repair torrent %s", torrent.AccessKey) t.log.Warnf("Cannot repair torrent %s", t.GetKey(torrent))
return return
} else { } else {
t.log.Warnf("Failed to repair by reinserting torrent %s, will only redownload broken files...", torrent.AccessKey) t.log.Warnf("Failed to repair by reinserting torrent %s, will only redownload broken files...", t.GetKey(torrent))
} }
} else { } else {
t.log.Warnf("Torrent %s is not older than %d hours to be repaired by reinsertion, will only redownload broken files...", torrent.AccessKey, EXPIRED_LINK_TOLERANCE_HOURS) t.log.Warnf("Torrent %s is not older than %d hours to be repaired by reinsertion, will only redownload broken files...", t.GetKey(torrent), EXPIRED_LINK_TOLERANCE_HOURS)
} }
// sleep for 30 seconds to let the torrent accumulate more broken files if scanning // sleep for 30 seconds to let the torrent accumulate more broken files if scanning
@@ -174,12 +174,12 @@ func (t *TorrentManager) repair(torrent *Torrent) {
}) })
if assignedCount > 0 { if assignedCount > 0 {
t.log.Infof("Assigned %d links to selected files for torrent %s", assignedCount, torrent.AccessKey) t.log.Infof("Assigned %d links to selected files for torrent %s", assignedCount, t.GetKey(torrent))
} else if rarCount > 0 { } else if rarCount > 0 {
// this is a rar'ed torrent, nothing we can do // this is a rar'ed torrent, nothing we can do
if t.Config.ShouldDeleteRarFiles() { if t.Config.ShouldDeleteRarFiles() {
t.log.Warnf("Torrent %s is rar'ed and we cannot repair it, deleting it as configured", torrent.AccessKey) t.log.Warnf("Torrent %s is rar'ed and we cannot repair it, deleting it as configured", t.GetKey(torrent))
t.Delete(torrent.AccessKey, true) t.Delete(t.GetKey(torrent), true)
} else { } else {
for _, unassigned := range unassignedDownloads { for _, unassigned := range unassignedDownloads {
newFile := &File{ newFile := &File{
@@ -208,14 +208,14 @@ func (t *TorrentManager) repair(torrent *Torrent) {
file.Link = "repairing" file.Link = "repairing"
} }
}) })
t.log.Debugf("During repair, zurg found %d broken files for torrent %s", len(brokenFiles), torrent.AccessKey) t.log.Debugf("During repair, zurg found %d broken files for torrent %s", len(brokenFiles), t.GetKey(torrent))
if len(brokenFiles) == 1 && torrent.SelectedFiles.Count() > 2 { if len(brokenFiles) == 1 && torrent.SelectedFiles.Count() > 2 {
// if we download a single file, it will be named differently // if we download a single file, it will be named differently
// so we need to download 1 extra file to preserve the name // so we need to download 1 extra file to preserve the name
// this is only relevant if we enable retain_rd_torrent_name // this is only relevant if we enable retain_rd_torrent_name
// add the first file link encountered with a prefix of http // add the first file link encountered with a prefix of http
t.log.Debugf("Torrent %s has only 1 broken file, adding 1 extra file to preserve the name", torrent.AccessKey) t.log.Debugf("Torrent %s has only 1 broken file, adding 1 extra file to preserve the name", t.GetKey(torrent))
for _, file := range torrent.SelectedFiles.Items() { for _, file := range torrent.SelectedFiles.Items() {
if strings.HasPrefix(file.Link, "http") { if strings.HasPrefix(file.Link, "http") {
brokenFiles = append(brokenFiles, *file) brokenFiles = append(brokenFiles, *file)
@@ -225,15 +225,15 @@ func (t *TorrentManager) repair(torrent *Torrent) {
} }
if len(brokenFiles) > 0 { if len(brokenFiles) > 0 {
t.log.Infof("Redownloading %dof%d files for torrent %s", len(brokenFiles), torrent.SelectedFiles.Count(), torrent.AccessKey) t.log.Infof("Redownloading %dof%d files for torrent %s", len(brokenFiles), torrent.SelectedFiles.Count(), t.GetKey(torrent))
brokenFileIDs := strings.Join(getFileIDs(brokenFiles), ",") brokenFileIDs := strings.Join(getFileIDs(brokenFiles), ",")
if t.reinsertTorrent(torrent, brokenFileIDs) { if t.reinsertTorrent(torrent, brokenFileIDs) {
t.log.Infof("Successfully downloaded torrent %s to repair it", torrent.AccessKey) t.log.Infof("Successfully downloaded torrent %s to repair it", t.GetKey(torrent))
} else { } else {
t.log.Warnf("Cannot repair torrent %s", torrent.AccessKey) t.log.Warnf("Cannot repair torrent %s", t.GetKey(torrent))
} }
} else { } else {
t.log.Warnf("Torrent %s has no broken files to repair", torrent.AccessKey) t.log.Warnf("Torrent %s has no broken files to repair", t.GetKey(torrent))
} }
} }
@@ -356,22 +356,22 @@ func (t *TorrentManager) canCapacityHandle() bool {
} }
func (t *TorrentManager) markAsUnplayable(torrent *Torrent) { func (t *TorrentManager) markAsUnplayable(torrent *Torrent) {
t.log.Warnf("Marking torrent %s as unplayable", torrent.AccessKey) t.log.Warnf("Marking torrent %s as unplayable", t.GetKey(torrent))
t.DirectoryMap.IterCb(func(directory string, torrents cmap.ConcurrentMap[string, *Torrent]) { t.DirectoryMap.IterCb(func(directory string, torrents cmap.ConcurrentMap[string, *Torrent]) {
torrents.Remove(torrent.AccessKey) torrents.Remove(t.GetKey(torrent))
}) })
torrents, _ := t.DirectoryMap.Get(config.UNPLAYABLE_TORRENTS) torrents, _ := t.DirectoryMap.Get(config.UNPLAYABLE_TORRENTS)
torrents.Set(torrent.AccessKey, torrent) torrents.Set(t.GetKey(torrent), torrent)
} }
func (t *TorrentManager) markAsUnfixable(torrent *Torrent) { func (t *TorrentManager) markAsUnfixable(torrent *Torrent) {
t.log.Warnf("Marking torrent %s as unfixable", torrent.AccessKey) t.log.Warnf("Marking torrent %s as unfixable", t.GetKey(torrent))
torrent.Unfixable = true torrent.Unfixable = true
infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE) infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE)
torrent.DownloadedIDs.Each(func(id string) bool { torrent.DownloadedIDs.Each(func(id string) bool {
info, _ := infoCache.Get(id) info, _ := infoCache.Get(id)
info.Unfixable = true info.Unfixable = true
t.writeTorrentToFile(id, torrent) t.writeTorrentToFile(id, torrent, false)
return false return false
}) })
} }

View File

@@ -14,7 +14,9 @@ import (
var json = jsoniter.ConfigCompatibleWithStandardLibrary var json = jsoniter.ConfigCompatibleWithStandardLibrary
type Torrent struct { type Torrent struct {
AccessKey string `json:"AccessKey"` Name string `json:"Name"`
OriginalName string `json:"OriginalName"`
Rename string `json:"Rename"`
Hash string `json:"Hash"` Hash string `json:"Hash"`
SelectedFiles cmap.ConcurrentMap[string, *File] `json:"-"` SelectedFiles cmap.ConcurrentMap[string, *File] `json:"-"`
LatestAdded string `json:"LatestAdded"` LatestAdded string `json:"LatestAdded"`