diff --git a/README.md b/README.md index addb6f9..cf6a447 100644 --- a/README.md +++ b/README.md @@ -73,12 +73,12 @@ The documentation includes: } ], "qbittorrent": { - "port": "8282", "download_folder": "/mnt/symlinks/", "categories": ["sonarr", "radarr"] }, "use_auth": false, - "log_level": "info" + "log_level": "info", + "port": "8282" } ``` diff --git a/internal/config/config.go b/internal/config/config.go index 4cdb5ac..85b5b8d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -58,6 +58,7 @@ type Repair struct { AutoProcess bool `json:"auto_process,omitempty"` UseWebDav bool `json:"use_webdav,omitempty"` Workers int `json:"workers,omitempty"` + ReInsert bool `json:"reinsert,omitempty"` } type Auth struct { diff --git a/pkg/debrid/debrid/repair.go b/pkg/debrid/debrid/repair.go index 05461fa..ebe2cfe 100644 --- a/pkg/debrid/debrid/repair.go +++ b/pkg/debrid/debrid/repair.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "github.com/puzpuzpuz/xsync/v3" + "github.com/sirrobot01/decypharr/internal/config" "github.com/sirrobot01/decypharr/internal/request" "github.com/sirrobot01/decypharr/internal/utils" "github.com/sirrobot01/decypharr/pkg/debrid/types" @@ -53,6 +54,21 @@ func (c *Cache) IsTorrentBroken(t *CachedTorrent, filenames []string) bool { } } } + cfg := config.Get() + // Try to reinsert the torrent if it's broken + if cfg.Repair.ReInsert && isBroken && t.Torrent != nil { + // Check if the torrent is already in progress + if _, inProgress := c.repairsInProgress.Load(t.Torrent.Id); !inProgress { + if err := c.reInsertTorrent(t); err != nil { + c.logger.Error().Err(err).Str("torrentId", t.Torrent.Id).Msg("Failed to reinsert torrent") + return true + } else { + c.logger.Debug().Str("torrentId", t.Torrent.Id).Msg("Reinserted torrent") + return false + } + } + } + return isBroken } diff --git a/pkg/debrid/debrid_link/debrid_link.go b/pkg/debrid/debrid_link/debrid_link.go index c773515..f42f73f 100644 --- a/pkg/debrid/debrid_link/debrid_link.go +++ b/pkg/debrid/debrid_link/debrid_link.go @@ -99,7 +99,7 @@ func (dl *DebridLink) UpdateTorrent(t *types.Torrent) error { if err != nil { return err } - var res TorrentInfo + var res torrentInfo err = json.Unmarshal(resp, &res) if err != nil { return err @@ -336,7 +336,7 @@ func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) { if err != nil { return torrents, err } - var res TorrentInfo + var res torrentInfo err = json.Unmarshal(resp, &res) if err != nil { dl.logger.Info().Msgf("Error unmarshalling torrent info: %s", err) diff --git a/pkg/debrid/debrid_link/types.go b/pkg/debrid/debrid_link/types.go index 156e918..ca44b2f 100644 --- a/pkg/debrid/debrid_link/types.go +++ b/pkg/debrid/debrid_link/types.go @@ -40,6 +40,6 @@ type debridLinkTorrentInfo struct { UploadSpeed int64 `json:"uploadSpeed"` } -type TorrentInfo APIResponse[[]debridLinkTorrentInfo] +type torrentInfo APIResponse[[]debridLinkTorrentInfo] type SubmitTorrentInfo APIResponse[debridLinkTorrentInfo] diff --git a/pkg/debrid/realdebrid/realdebrid.go b/pkg/debrid/realdebrid/realdebrid.go index 854d0ca..15ad0ac 100644 --- a/pkg/debrid/realdebrid/realdebrid.go +++ b/pkg/debrid/realdebrid/realdebrid.go @@ -105,7 +105,7 @@ func (r *RealDebrid) GetLogger() zerolog.Logger { return r.logger } -func getSelectedFiles(t *types.Torrent, data TorrentInfo) map[string]types.File { +func getSelectedFiles(t *types.Torrent, data torrentInfo) map[string]types.File { selectedFiles := make([]types.File, 0) for _, f := range data.Files { if f.Selected == 1 { @@ -133,7 +133,7 @@ func getSelectedFiles(t *types.Torrent, data TorrentInfo) map[string]types.File // getTorrentFiles returns a list of torrent files from the torrent info // validate is used to determine if the files should be validated // if validate is false, selected files will be returned -func getTorrentFiles(t *types.Torrent, data TorrentInfo) map[string]types.File { +func getTorrentFiles(t *types.Torrent, data torrentInfo) map[string]types.File { files := make(map[string]types.File) cfg := config.Get() idx := 0 @@ -268,7 +268,7 @@ func (r *RealDebrid) UpdateTorrent(t *types.Torrent) error { if err != nil { return err } - var data TorrentInfo + var data torrentInfo err = json.Unmarshal(resp, &data) if err != nil { return err @@ -299,7 +299,7 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torre r.logger.Info().Msgf("ERROR Checking file: %v", err) return t, err } - var data TorrentInfo + var data torrentInfo if err = json.Unmarshal(resp, &data); err != nil { return t, err } diff --git a/pkg/debrid/realdebrid/types.go b/pkg/debrid/realdebrid/types.go index 7bf54d8..dcb789a 100644 --- a/pkg/debrid/realdebrid/types.go +++ b/pkg/debrid/realdebrid/types.go @@ -70,7 +70,7 @@ type AddMagnetSchema struct { Uri string `json:"uri"` } -type TorrentInfo struct { +type torrentInfo struct { ID string `json:"id"` Filename string `json:"filename"` OriginalFilename string `json:"original_filename"` diff --git a/pkg/qbit/downloader.go b/pkg/qbit/downloader.go index 98454a4..ec0c6c7 100644 --- a/pkg/qbit/downloader.go +++ b/pkg/qbit/downloader.go @@ -151,10 +151,10 @@ func (q *QBit) ProcessSymlink(torrent *Torrent) (string, error) { func (q *QBit) createSymlinks(debridTorrent *debrid.Torrent, rclonePath, torrentFolder string) (string, error) { files := debridTorrent.Files - torrentSymlinkPath := filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentFolder) - err := os.MkdirAll(torrentSymlinkPath, os.ModePerm) + symlinkPath := filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentFolder) // /mnt/symlinks/{category}/MyTVShow/ + err := os.MkdirAll(symlinkPath, os.ModePerm) if err != nil { - return "", fmt.Errorf("failed to create directory: %s: %v", torrentSymlinkPath, err) + return "", fmt.Errorf("failed to create directory: %s: %v", symlinkPath, err) } pending := make(map[string]debrid.File) @@ -168,18 +168,33 @@ func (q *QBit) createSymlinks(debridTorrent *debrid.Torrent, rclonePath, torrent for len(pending) > 0 { <-ticker.C for path, file := range pending { - fullFilePath := filepath.Join(rclonePath, file.Path) + fullFilePath := filepath.Join(rclonePath, file.Name) if _, err := os.Stat(fullFilePath); !os.IsNotExist(err) { - q.logger.Info().Msgf("File is ready: %s", file.Path) - _filePath := q.createSymLink(torrentSymlinkPath, rclonePath, file) - filePaths = append(filePaths, _filePath) + q.logger.Info().Msgf("File is ready: %s", file.Name) + fileSymlinkPath := filepath.Join(symlinkPath, file.Name) + if err := os.Symlink(fullFilePath, fileSymlinkPath); err != nil { + q.logger.Debug().Msgf("Failed to create symlink: %s: %v", fileSymlinkPath, err) + } + filePaths = append(filePaths, fileSymlinkPath) delete(pending, path) + } else if file.Name != file.Path { + // This is likely alldebrid nested files(not using webdav) + fullFilePath = filepath.Join(rclonePath, file.Path) + if _, err := os.Stat(fullFilePath); !os.IsNotExist(err) { + q.logger.Info().Msgf("File is ready: %s", file.Path) + fileSymlinkPath := filepath.Join(symlinkPath, file.Path) + if err := os.Symlink(fullFilePath, fileSymlinkPath); err != nil { + q.logger.Debug().Msgf("Failed to create symlink: %s: %v", fileSymlinkPath, err) + } + filePaths = append(filePaths, fileSymlinkPath) + delete(pending, path) + } } } } if q.SkipPreCache { - return torrentSymlinkPath, nil + return symlinkPath, nil } go func() { @@ -191,7 +206,7 @@ func (q *QBit) createSymlinks(debridTorrent *debrid.Torrent, rclonePath, torrent } }() // Pre-cache the files in the background // Pre-cache the first 256KB and 1MB of the file - return torrentSymlinkPath, nil + return symlinkPath, nil } func (q *QBit) getTorrentPath(rclonePath string, debridTorrent *debrid.Torrent) (string, error) { @@ -205,18 +220,13 @@ func (q *QBit) getTorrentPath(rclonePath string, debridTorrent *debrid.Torrent) } } -func (q *QBit) createSymLink(path string, torrentMountPath string, file debrid.File) string { - - // Combine the directory and filename to form a full path - fullPath := filepath.Join(path, file.Name) // /mnt/symlinks/{category}/MyTVShow/MyTVShow.S01E01.720p.mkv - // Create a symbolic link if file doesn't exist - torrentFilePath := filepath.Join(torrentMountPath, file.Path) // debridFolder/MyTVShow/MyTVShow.S01E01.720p.mkv - err := os.Symlink(torrentFilePath, fullPath) +func (q *QBit) createSymLink(torrentFileMountPath, filePath string) string { + err := os.Symlink(torrentFileMountPath, filePath) if err != nil { // It's okay if the symlink already exists - q.logger.Debug().Msgf("Failed to create symlink: %s: %v", fullPath, err) + q.logger.Debug().Msgf("Failed to create symlink: %s: %v", filePath, err) } - return torrentFilePath + return filePath } func (q *QBit) preCacheFile(name string, filePaths []string) error { diff --git a/pkg/web/templates/config.html b/pkg/web/templates/config.html index d2f43de..afc90c0 100644 --- a/pkg/web/templates/config.html +++ b/pkg/web/templates/config.html @@ -149,10 +149,12 @@ -