Access key computation without clearing data
This commit is contained in:
12
README.md
12
README.md
@@ -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!
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
Reference in New Issue
Block a user