From d313ed0712dc8e86185ebb2f84721a8c70b5150f Mon Sep 17 00:00:00 2001 From: Mukhtar Akere Date: Mon, 26 May 2025 00:16:46 +0100 Subject: [PATCH] hotfix non-webdav symlinker --- README.md | 8 +-- pkg/qbit/downloader.go | 116 ++++++++++++++++++----------------------- 2 files changed, 55 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 15fdb3d..840d092 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# DecyphArr +# Decypharr ![ui](docs/docs/images/main.png) -**DecyphArr** is an implementation of QbitTorrent with **Multiple Debrid service support**, written in Go. +**Decypharr** is an implementation of QbitTorrent with **Multiple Debrid service support**, written in Go. -## What is DecyphArr? +## What is Decypharr? -DecyphArr combines the power of QBittorrent with popular Debrid services to enhance your media management. It provides a familiar interface for Sonarr, Radarr, and other \*Arr applications while leveraging the capabilities of Debrid providers. +Decypharr combines the power of QBittorrent with popular Debrid services to enhance your media management. It provides a familiar interface for Sonarr, Radarr, and other \*Arr applications while leveraging the capabilities of Debrid providers. ## Features diff --git a/pkg/qbit/downloader.go b/pkg/qbit/downloader.go index 0071865..d940d3d 100644 --- a/pkg/qbit/downloader.go +++ b/pkg/qbit/downloader.go @@ -164,7 +164,57 @@ func (q *QBit) ProcessSymlink(torrent *Torrent) (string, error) { torrentFolder = utils.RemoveExtension(torrentFolder) torrentRclonePath = rCloneBase // /mnt/rclone/magnets/ // Remove the filename since it's in the root folder } - return q.createSymlinks(debridTorrent, torrentRclonePath, torrentFolder) // verify cos we're using external webdav + torrentSymlinkPath := filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentFolder) // /mnt/symlinks/{category}/MyTVShow/ + err = os.MkdirAll(torrentSymlinkPath, os.ModePerm) + if err != nil { + return "", fmt.Errorf("failed to create directory: %s: %v", torrentSymlinkPath, err) + } + + pending := make(map[string]debridTypes.File) + filePaths := make([]string, 0, len(files)) + for _, file := range files { + pending[file.Path] = file + } + ticker := time.NewTicker(200 * time.Millisecond) + defer ticker.Stop() + + timeout := time.After(30 * time.Minute) // Adjust timeout duration as needed + + for len(pending) > 0 { + select { + case <-ticker.C: + for path, file := range pending { + fullFilePath := filepath.Join(torrentRclonePath, file.Path) + if _, err := os.Stat(fullFilePath); !os.IsNotExist(err) { + fileSymlinkPath := filepath.Join(torrentSymlinkPath, file.Name) + if err := os.Symlink(fullFilePath, fileSymlinkPath); err != nil && !os.IsExist(err) { + q.logger.Debug().Msgf("Failed to create symlink: %s: %v", fileSymlinkPath, err) + } else { + filePaths = append(filePaths, fileSymlinkPath) + delete(pending, path) + q.logger.Info().Msgf("File is ready: %s", file.Name) + } + + } + } + case <-timeout: + q.logger.Warn().Msgf("Timeout waiting for files, %d files still pending", len(pending)) + return torrentSymlinkPath, fmt.Errorf("timeout waiting for files: %d files still pending", len(pending)) + } + } + if q.SkipPreCache { + return torrentSymlinkPath, nil + } + + go func() { + + if err := q.preCacheFile(debridTorrent.Name, filePaths); err != nil { + q.logger.Error().Msgf("Failed to pre-cache file: %s", err) + } else { + q.logger.Trace().Msgf("Pre-cached %d files", len(filePaths)) + } + }() + return torrentSymlinkPath, nil } func (q *QBit) createSymlinksWebdav(debridTorrent *debridTypes.Torrent, rclonePath, torrentFolder string) (string, error) { @@ -232,70 +282,6 @@ func (q *QBit) createSymlinksWebdav(debridTorrent *debridTypes.Torrent, rclonePa return symlinkPath, nil } -func (q *QBit) createSymlinks(debridTorrent *debridTypes.Torrent, rclonePath, torrentFolder string) (string, error) { - files := debridTorrent.Files - 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", symlinkPath, err) - } - - remainingFiles := make(map[string]debridTypes.File) - for _, file := range files { - remainingFiles[file.Path] = file - } - - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - timeout := time.After(30 * time.Minute) - filePaths := make([]string, 0, len(files)) - - for len(remainingFiles) > 0 { - select { - case <-ticker.C: - entries, err := os.ReadDir(rclonePath) - if err != nil { - continue - } - - // Check which files exist in this batch - for _, entry := range entries { - filename := entry.Name() - if file, exists := remainingFiles[filename]; exists { - fullFilePath := filepath.Join(rclonePath, filename) - fileSymlinkPath := filepath.Join(symlinkPath, file.Name) - - if err := os.Symlink(fullFilePath, fileSymlinkPath); err != nil && !os.IsExist(err) { - q.logger.Debug().Msgf("Failed to create symlink: %s: %v", fileSymlinkPath, err) - } else { - filePaths = append(filePaths, fileSymlinkPath) - delete(remainingFiles, filename) - q.logger.Info().Msgf("File is ready: %s", file.Name) - } - } - } - - case <-timeout: - q.logger.Warn().Msgf("Timeout waiting for files, %d files still pending", len(remainingFiles)) - return symlinkPath, fmt.Errorf("timeout waiting for files") - } - } - - if q.SkipPreCache { - return symlinkPath, nil - } - - go func() { - - if err := q.preCacheFile(debridTorrent.Name, filePaths); err != nil { - q.logger.Error().Msgf("Failed to pre-cache file: %s", err) - } else { - q.logger.Trace().Msgf("Pre-cached %d files", len(filePaths)) - } - }() // Pre-cache the files in the background - return symlinkPath, nil -} - func (q *QBit) getTorrentPath(rclonePath string, debridTorrent *debridTypes.Torrent) (string, error) { for { torrentPath, err := debridTorrent.GetMountFolder(rclonePath)