Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f118c5b794 |
@@ -67,4 +67,9 @@
|
|||||||
- Add file download support(Sequential Download)
|
- Add file download support(Sequential Download)
|
||||||
- Fix http handler error
|
- Fix http handler error
|
||||||
- Fix *arrs map failing concurrently
|
- Fix *arrs map failing concurrently
|
||||||
- Fix cache not being updated
|
- Fix cache not being updated
|
||||||
|
|
||||||
|
#### 0.2.5
|
||||||
|
- Fix ContentPath not being set prior
|
||||||
|
- Rewrote Readme
|
||||||
|
- Cleaned up the code
|
||||||
10
README.md
10
README.md
@@ -68,16 +68,19 @@ Download the binary from the releases page and run it with the config file.
|
|||||||
"max_cache_size": 1000,
|
"max_cache_size": 1000,
|
||||||
"qbittorrent": {
|
"qbittorrent": {
|
||||||
"port": "8282",
|
"port": "8282",
|
||||||
"username": "admin", // deprecated
|
|
||||||
"password": "admin", // deprecated
|
|
||||||
"download_folder": "/media/symlinks/",
|
"download_folder": "/media/symlinks/",
|
||||||
"categories": ["sonarr", "radarr"],
|
"categories": ["sonarr", "radarr"],
|
||||||
"refresh_interval": 5 // in seconds
|
"refresh_interval": 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Config Notes
|
#### Config Notes
|
||||||
|
##### Max Cache Size
|
||||||
|
- The `max_cache_size` key is used to set the maximum number of infohashes that can be stored in the availability cache. This is used to prevent round trip to the debrid provider when using the proxy/Qbittorrent
|
||||||
|
- The default value is `1000`
|
||||||
|
- The cache is stored in memory and is not persisted on restart
|
||||||
|
|
||||||
##### Debrid Config
|
##### Debrid Config
|
||||||
- This config key is important as it's used for both Blackhole and Proxy
|
- This config key is important as it's used for both Blackhole and Proxy
|
||||||
|
|
||||||
@@ -93,6 +96,7 @@ Download the binary from the releases page and run it with the config file.
|
|||||||
- The `port` key is the port the qBittorrent will listen on
|
- The `port` key is the port the qBittorrent will listen on
|
||||||
- The `download_folder` is the folder where the torrents will be downloaded. e.g `/media/symlinks/`
|
- The `download_folder` is the folder where the torrents will be downloaded. e.g `/media/symlinks/`
|
||||||
- The `categories` key is used to filter out torrents based on the category. e.g `sonarr`, `radarr`
|
- The `categories` key is used to filter out torrents based on the category. e.g `sonarr`, `radarr`
|
||||||
|
- The `refresh_interval` key is used to set the interval in minutes to refresh the Arrs Monitored Downloads(it's in seconds). The default value is `5` seconds
|
||||||
|
|
||||||
### Proxy
|
### Proxy
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type Torrent struct {
|
|||||||
Magnet *common.Magnet `json:"magnet"`
|
Magnet *common.Magnet `json:"magnet"`
|
||||||
Files []TorrentFile `json:"files"`
|
Files []TorrentFile `json:"files"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Added string `json:"added"`
|
||||||
Progress float64 `json:"progress"`
|
Progress float64 `json:"progress"`
|
||||||
Speed int64 `json:"speed"`
|
Speed int64 `json:"speed"`
|
||||||
Seeders int `json:"seeders"`
|
Seeders int `json:"seeders"`
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ func (q *QBit) createSymLink(path string, torrentMountPath string, file debrid.T
|
|||||||
torrentFilePath := filepath.Join(torrentMountPath, file.Name) // debridFolder/MyTVShow/MyTVShow.S01E01.720p.mkv
|
torrentFilePath := filepath.Join(torrentMountPath, file.Name) // debridFolder/MyTVShow/MyTVShow.S01E01.720p.mkv
|
||||||
err := os.Symlink(torrentFilePath, fullPath)
|
err := os.Symlink(torrentFilePath, fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
q.logger.Printf("Failed to create symlink: %s\n", fullPath)
|
q.logger.Printf("Failed to create symlink: %s: %v\n", fullPath, err)
|
||||||
}
|
}
|
||||||
// Check if the file exists
|
// Check if the file exists
|
||||||
if !common.FileReady(fullPath) {
|
if !common.FileReady(fullPath) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ func (q *QBit) AddRoutes(r chi.Router) http.Handler {
|
|||||||
r.Get("/resume", q.handleTorrentsResume)
|
r.Get("/resume", q.handleTorrentsResume)
|
||||||
r.Get("/recheck", q.handleTorrentRecheck)
|
r.Get("/recheck", q.handleTorrentRecheck)
|
||||||
r.Get("/properties", q.handleTorrentProperties)
|
r.Get("/properties", q.handleTorrentProperties)
|
||||||
|
r.Get("/files", q.handleTorrentFiles)
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Route("/app", func(r chi.Router) {
|
r.Route("/app", func(r chi.Router) {
|
||||||
|
|||||||
@@ -5,6 +5,5 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (q *QBit) handleLogin(w http.ResponseWriter, r *http.Request) {
|
func (q *QBit) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
_, _ = w.Write([]byte("Ok."))
|
||||||
w.Write([]byte("Ok."))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,3 +163,13 @@ func (q *QBit) handleTorrentProperties(w http.ResponseWriter, r *http.Request) {
|
|||||||
properties := q.GetTorrentProperties(torrent)
|
properties := q.GetTorrentProperties(torrent)
|
||||||
JSONResponse(w, properties, http.StatusOK)
|
JSONResponse(w, properties, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *QBit) handleTorrentFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
|
hash := r.URL.Query().Get("hash")
|
||||||
|
torrent := q.storage.Get(hash)
|
||||||
|
if torrent == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
files := q.GetTorrentFiles(torrent)
|
||||||
|
JSONResponse(w, files, http.StatusOK)
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,9 +58,7 @@ func (q *QBit) Process(ctx context.Context, magnet *common.Magnet, category stri
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
torrent.ID = debridTorrent.Id
|
torrent = q.UpdateTorrentMin(torrent, debridTorrent)
|
||||||
torrent.DebridTorrent = debridTorrent
|
|
||||||
torrent.Name = debridTorrent.Name
|
|
||||||
q.storage.AddOrUpdate(torrent)
|
q.storage.AddOrUpdate(torrent)
|
||||||
go q.processFiles(torrent, debridTorrent, arr, isSymlink) // We can send async for file processing not to delay the response
|
go q.processFiles(torrent, debridTorrent, arr, isSymlink) // We can send async for file processing not to delay the response
|
||||||
return nil
|
return nil
|
||||||
@@ -74,23 +72,14 @@ func (q *QBit) CreateTorrentFromMagnet(magnet *common.Magnet, category string) *
|
|||||||
Size: magnet.Size,
|
Size: magnet.Size,
|
||||||
Category: category,
|
Category: category,
|
||||||
State: "downloading",
|
State: "downloading",
|
||||||
AddedOn: time.Now().Unix(),
|
|
||||||
MagnetUri: magnet.Link,
|
MagnetUri: magnet.Link,
|
||||||
|
|
||||||
Tracker: "udp://tracker.opentrackr.org:1337",
|
Tracker: "udp://tracker.opentrackr.org:1337",
|
||||||
UpLimit: -1,
|
UpLimit: -1,
|
||||||
DlLimit: -1,
|
DlLimit: -1,
|
||||||
FlPiecePrio: false,
|
AutoTmm: false,
|
||||||
ForceStart: false,
|
Ratio: 1,
|
||||||
AutoTmm: false,
|
RatioLimit: 1,
|
||||||
Availability: 2,
|
|
||||||
MaxRatio: -1,
|
|
||||||
MaxSeedingTime: -1,
|
|
||||||
NumComplete: 10,
|
|
||||||
NumIncomplete: 0,
|
|
||||||
NumLeechs: 1,
|
|
||||||
Ratio: 1,
|
|
||||||
RatioLimit: 1,
|
|
||||||
}
|
}
|
||||||
return torrent
|
return torrent
|
||||||
}
|
}
|
||||||
@@ -98,15 +87,17 @@ func (q *QBit) CreateTorrentFromMagnet(magnet *common.Magnet, category string) *
|
|||||||
func (q *QBit) processFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr, isSymlink bool) {
|
func (q *QBit) processFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr, isSymlink bool) {
|
||||||
for debridTorrent.Status != "downloaded" {
|
for debridTorrent.Status != "downloaded" {
|
||||||
progress := debridTorrent.Progress
|
progress := debridTorrent.Progress
|
||||||
q.logger.Printf("Progress: %.2f%%", progress)
|
q.logger.Printf("RD Download Progress: %.2f%%", progress)
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
dbT, err := q.debrid.CheckStatus(debridTorrent, isSymlink)
|
dbT, err := q.debrid.CheckStatus(debridTorrent, isSymlink)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
q.logger.Printf("Error checking status: %v", err)
|
q.logger.Printf("Error checking status: %v", err)
|
||||||
q.MarkAsFailed(torrent)
|
q.MarkAsFailed(torrent)
|
||||||
|
q.RefreshArr(arr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
debridTorrent = dbT
|
debridTorrent = dbT
|
||||||
|
torrent = q.UpdateTorrentMin(torrent, debridTorrent)
|
||||||
}
|
}
|
||||||
if isSymlink {
|
if isSymlink {
|
||||||
q.processSymlink(torrent, debridTorrent, arr)
|
q.processSymlink(torrent, debridTorrent, arr)
|
||||||
|
|||||||
@@ -174,20 +174,20 @@ type Torrent struct {
|
|||||||
TorrentPath string `json:"-"`
|
TorrentPath string `json:"-"`
|
||||||
|
|
||||||
AddedOn int64 `json:"added_on,omitempty"`
|
AddedOn int64 `json:"added_on,omitempty"`
|
||||||
AmountLeft int64 `json:"amount_left,omitempty"`
|
AmountLeft int64 `json:"amount_left"`
|
||||||
AutoTmm bool `json:"auto_tmm"`
|
AutoTmm bool `json:"auto_tmm"`
|
||||||
Availability float64 `json:"availability"`
|
Availability float64 `json:"availability,omitempty"`
|
||||||
Category string `json:"category,omitempty"`
|
Category string `json:"category,omitempty"`
|
||||||
Completed int64 `json:"completed,omitempty"`
|
Completed int64 `json:"completed"`
|
||||||
CompletionOn int64 `json:"completion_on,omitempty"`
|
CompletionOn int64 `json:"completion_on,omitempty"`
|
||||||
ContentPath string `json:"content_path,omitempty"`
|
ContentPath string `json:"content_path"`
|
||||||
DlLimit int64 `json:"dl_limit,omitempty"`
|
DlLimit int64 `json:"dl_limit"`
|
||||||
Dlspeed int64 `json:"dlspeed,omitempty"`
|
Dlspeed int64 `json:"dlspeed"`
|
||||||
Downloaded int64 `json:"downloaded,omitempty"`
|
Downloaded int64 `json:"downloaded"`
|
||||||
DownloadedSession int64 `json:"downloaded_session,omitempty"`
|
DownloadedSession int64 `json:"downloaded_session"`
|
||||||
Eta int64 `json:"eta,omitempty"`
|
Eta int64 `json:"eta"`
|
||||||
FlPiecePrio bool `json:"f_l_piece_prio"`
|
FlPiecePrio bool `json:"f_l_piece_prio,omitempty"`
|
||||||
ForceStart bool `json:"force_start"`
|
ForceStart bool `json:"force_start,omitempty"`
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
LastActivity int64 `json:"last_activity,omitempty"`
|
LastActivity int64 `json:"last_activity,omitempty"`
|
||||||
MagnetUri string `json:"magnet_uri,omitempty"`
|
MagnetUri string `json:"magnet_uri,omitempty"`
|
||||||
@@ -202,7 +202,7 @@ type Torrent struct {
|
|||||||
Progress float32 `json:"progress"`
|
Progress float32 `json:"progress"`
|
||||||
Ratio int64 `json:"ratio,omitempty"`
|
Ratio int64 `json:"ratio,omitempty"`
|
||||||
RatioLimit int64 `json:"ratio_limit,omitempty"`
|
RatioLimit int64 `json:"ratio_limit,omitempty"`
|
||||||
SavePath string `json:"save_path,omitempty"`
|
SavePath string `json:"save_path"`
|
||||||
SeedingTimeLimit int64 `json:"seeding_time_limit,omitempty"`
|
SeedingTimeLimit int64 `json:"seeding_time_limit,omitempty"`
|
||||||
SeenComplete int64 `json:"seen_complete,omitempty"`
|
SeenComplete int64 `json:"seen_complete,omitempty"`
|
||||||
SeqDl bool `json:"seq_dl"`
|
SeqDl bool `json:"seq_dl"`
|
||||||
@@ -259,6 +259,17 @@ type TorrentProperties struct {
|
|||||||
UpSpeedAvg int64 `json:"up_speed_avg,omitempty"`
|
UpSpeedAvg int64 `json:"up_speed_avg,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TorrentFile struct {
|
||||||
|
Index int `json:"index,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Size int64 `json:"size,omitempty"`
|
||||||
|
Progress int64 `json:"progress,omitempty"`
|
||||||
|
Priority int64 `json:"priority,omitempty"`
|
||||||
|
IsSeed bool `json:"is_seed,omitempty"`
|
||||||
|
PieceRange []int64 `json:"piece_range,omitempty"`
|
||||||
|
Availability float64 `json:"availability,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewAppPreferences() *AppPreferences {
|
func NewAppPreferences() *AppPreferences {
|
||||||
preferences := &AppPreferences{
|
preferences := &AppPreferences{
|
||||||
AddTrackers: "",
|
AddTrackers: "",
|
||||||
|
|||||||
@@ -16,6 +16,48 @@ func (q *QBit) MarkAsFailed(t *Torrent) *Torrent {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *QBit) UpdateTorrentMin(t *Torrent, debridTorrent *debrid.Torrent) *Torrent {
|
||||||
|
if debridTorrent == nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
addedOn, err := time.Parse(time.RFC3339, debridTorrent.Added)
|
||||||
|
if err != nil {
|
||||||
|
addedOn = time.Now()
|
||||||
|
}
|
||||||
|
totalSize := float64(debridTorrent.Bytes)
|
||||||
|
progress := cmp.Or(debridTorrent.Progress, 100.0)
|
||||||
|
progress = progress / 100.0
|
||||||
|
sizeCompleted := int64(totalSize * progress)
|
||||||
|
|
||||||
|
var speed int64
|
||||||
|
if debridTorrent.Speed != 0 {
|
||||||
|
speed = debridTorrent.Speed
|
||||||
|
}
|
||||||
|
var eta int64
|
||||||
|
if speed != 0 {
|
||||||
|
eta = int64((totalSize - float64(sizeCompleted)) / float64(speed))
|
||||||
|
}
|
||||||
|
t.ID = debridTorrent.Id
|
||||||
|
t.Name = debridTorrent.Name
|
||||||
|
t.AddedOn = addedOn.Unix()
|
||||||
|
t.DebridTorrent = debridTorrent
|
||||||
|
t.Size = int64(totalSize)
|
||||||
|
t.Completed = sizeCompleted
|
||||||
|
t.Downloaded = sizeCompleted
|
||||||
|
t.DownloadedSession = sizeCompleted
|
||||||
|
t.Uploaded = sizeCompleted
|
||||||
|
t.UploadedSession = sizeCompleted
|
||||||
|
t.AmountLeft = int64(totalSize) - sizeCompleted
|
||||||
|
t.Progress = float32(progress)
|
||||||
|
t.Eta = eta
|
||||||
|
t.Dlspeed = speed
|
||||||
|
t.Upspeed = speed
|
||||||
|
t.SavePath = filepath.Join(q.DownloadFolder, t.Category) + string(os.PathSeparator)
|
||||||
|
t.ContentPath = filepath.Join(t.SavePath, t.Name) + string(os.PathSeparator)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent {
|
func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent {
|
||||||
rcLoneMount := q.debrid.GetMountPath()
|
rcLoneMount := q.debrid.GetMountPath()
|
||||||
if debridTorrent == nil && t.ID != "" {
|
if debridTorrent == nil && t.ID != "" {
|
||||||
@@ -32,57 +74,33 @@ func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent
|
|||||||
if t.TorrentPath == "" {
|
if t.TorrentPath == "" {
|
||||||
t.TorrentPath = filepath.Base(debridTorrent.GetMountFolder(rcLoneMount))
|
t.TorrentPath = filepath.Base(debridTorrent.GetMountFolder(rcLoneMount))
|
||||||
}
|
}
|
||||||
|
|
||||||
totalSize := float64(cmp.Or(debridTorrent.Bytes, 1.0))
|
|
||||||
progress := cmp.Or(debridTorrent.Progress, 100.0)
|
|
||||||
progress = progress / 100.0
|
|
||||||
var sizeCompleted int64
|
|
||||||
|
|
||||||
sizeCompleted = int64(totalSize * progress)
|
|
||||||
savePath := filepath.Join(q.DownloadFolder, t.Category) + string(os.PathSeparator)
|
savePath := filepath.Join(q.DownloadFolder, t.Category) + string(os.PathSeparator)
|
||||||
torrentPath := filepath.Join(savePath, t.TorrentPath) + string(os.PathSeparator)
|
torrentPath := filepath.Join(savePath, t.TorrentPath) + string(os.PathSeparator)
|
||||||
|
t = q.UpdateTorrentMin(t, debridTorrent)
|
||||||
var speed int64
|
|
||||||
if debridTorrent.Speed != 0 {
|
|
||||||
speed = debridTorrent.Speed
|
|
||||||
}
|
|
||||||
var eta int64
|
|
||||||
if speed != 0 {
|
|
||||||
eta = int64((totalSize - float64(sizeCompleted)) / float64(speed))
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Size = debridTorrent.Bytes
|
|
||||||
t.DebridTorrent = debridTorrent
|
|
||||||
t.Completed = sizeCompleted
|
|
||||||
t.Downloaded = sizeCompleted
|
|
||||||
t.DownloadedSession = sizeCompleted
|
|
||||||
t.Uploaded = sizeCompleted
|
|
||||||
t.UploadedSession = sizeCompleted
|
|
||||||
t.AmountLeft = int64(totalSize) - sizeCompleted
|
|
||||||
t.Progress = float32(progress)
|
|
||||||
t.SavePath = savePath
|
|
||||||
t.ContentPath = torrentPath
|
t.ContentPath = torrentPath
|
||||||
t.Eta = eta
|
|
||||||
t.Dlspeed = speed
|
|
||||||
t.Upspeed = speed
|
|
||||||
|
|
||||||
if t.IsReady() {
|
if t.IsReady() {
|
||||||
t.State = "pausedUP"
|
t.State = "pausedUP"
|
||||||
q.storage.AddOrUpdate(t)
|
q.storage.Update(t)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
ticker := time.NewTicker(3 * time.Second)
|
|
||||||
|
ticker := time.NewTicker(2 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if t.IsReady() {
|
if t.IsReady() {
|
||||||
t.State = "pausedUP"
|
t.State = "pausedUP"
|
||||||
q.storage.AddOrUpdate(t)
|
q.storage.Update(t)
|
||||||
ticker.Stop()
|
|
||||||
return t
|
return t
|
||||||
} else {
|
|
||||||
return q.UpdateTorrent(t, debridTorrent)
|
|
||||||
}
|
}
|
||||||
|
updatedT := q.UpdateTorrent(t, debridTorrent)
|
||||||
|
t = updatedT
|
||||||
|
|
||||||
|
case <-time.After(10 * time.Minute): // Add a timeout
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,3 +141,18 @@ func (q *QBit) GetTorrentProperties(t *Torrent) *TorrentProperties {
|
|||||||
ShareRatio: 100,
|
ShareRatio: 100,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *QBit) GetTorrentFiles(t *Torrent) []*TorrentFile {
|
||||||
|
files := make([]*TorrentFile, 0)
|
||||||
|
if t.DebridTorrent == nil {
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
for index, file := range t.DebridTorrent.Files {
|
||||||
|
files = append(files, &TorrentFile{
|
||||||
|
Index: index,
|
||||||
|
Name: file.Path,
|
||||||
|
Size: file.Size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user