Add support for different folder naming; minor bug fixes

This commit is contained in:
Mukhtar Akere
2025-03-24 12:12:38 +01:00
parent 8c13da5d30
commit 9469c98df7
14 changed files with 249 additions and 210 deletions

View File

@@ -128,6 +128,7 @@ func flattenFiles(files []MagnetFile, parentPath string, index *int) map[string]
Name: fileName,
Size: f.Size,
Path: currentPath,
Link: f.Link,
}
result[file.Name] = file
}
@@ -239,7 +240,7 @@ func (ad *AllDebrid) GenerateDownloadLinks(t *types.Torrent) error {
return nil
}
func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) *types.File {
func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (string, error) {
url := fmt.Sprintf("%s/link/unlock", ad.Host)
query := gourl.Values{}
query.Add("link", file.Link)
@@ -247,16 +248,17 @@ func (ad *AllDebrid) GetDownloadLink(t *types.Torrent, file *types.File) *types.
req, _ := http.NewRequest(http.MethodGet, url, nil)
resp, err := ad.client.MakeRequest(req)
if err != nil {
return nil
return "", err
}
var data DownloadLink
if err = json.Unmarshal(resp, &data); err != nil {
return nil
return "", err
}
link := data.Data.Link
file.DownloadLink = link
file.Generated = time.Now()
return file
if link == "" {
return "", fmt.Errorf("error getting download links %s", data.Error.Message)
}
return link, nil
}
func (ad *AllDebrid) GetCheckCached() bool {
@@ -264,7 +266,35 @@ func (ad *AllDebrid) GetCheckCached() bool {
}
func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) {
return nil, nil
url := fmt.Sprintf("%s/magnet/status?status=ready", ad.Host)
req, _ := http.NewRequest(http.MethodGet, url, nil)
resp, err := ad.client.MakeRequest(req)
torrents := make([]*types.Torrent, 0)
if err != nil {
return torrents, err
}
var res TorrentsListResponse
err = json.Unmarshal(resp, &res)
if err != nil {
ad.logger.Info().Msgf("Error unmarshalling torrent info: %s", err)
return torrents, err
}
for _, magnet := range res.Data.Magnets {
torrents = append(torrents, &types.Torrent{
Id: strconv.Itoa(magnet.Id),
Name: magnet.Filename,
Bytes: magnet.Size,
Status: getAlldebridStatus(magnet.StatusCode),
Filename: magnet.Filename,
OriginalFilename: magnet.Filename,
Files: make(map[string]types.File),
InfoHash: magnet.Hash,
Debrid: ad.Name,
MountPath: ad.MountPath,
})
}
return torrents, nil
}
func (ad *AllDebrid) GetDownloads() (map[string]types.DownloadLinks, error) {
@@ -279,10 +309,6 @@ func (ad *AllDebrid) GetDownloadUncached() bool {
return ad.DownloadUncached
}
func (ad *AllDebrid) ConvertLinksToFiles(links []string) []types.File {
return nil
}
func New(dc config.Debrid) *AllDebrid {
rl := request.ParseRateLimit(dc.RateLimit)
headers := map[string]string{

View File

@@ -40,6 +40,14 @@ type TorrentInfoResponse struct {
Error *errorResponse `json:"error"`
}
type TorrentsListResponse struct {
Status string `json:"status"`
Data struct {
Magnets []magnetInfo `json:"magnets"`
} `json:"data"`
Error *errorResponse `json:"error"`
}
type UploadMagnetResponse struct {
Status string `json:"status"`
Data struct {

View File

@@ -7,6 +7,7 @@ import (
"github.com/goccy/go-json"
"github.com/puzpuzpuz/xsync/v3"
"github.com/rs/zerolog"
"github.com/sirrobot01/debrid-blackhole/internal/config"
"github.com/sirrobot01/debrid-blackhole/internal/logger"
"github.com/sirrobot01/debrid-blackhole/internal/utils"
"github.com/sirrobot01/debrid-blackhole/pkg/debrid/types"
@@ -16,8 +17,14 @@ import (
"sync"
"sync/atomic"
"time"
)
"github.com/sirrobot01/debrid-blackhole/internal/config"
type WebDavFolderNaming string
const (
WebDavUseOriginalName WebDavFolderNaming = "original"
WebDavUseID WebDavFolderNaming = "use_id"
WebDavUseOriginalNameNoExt WebDavFolderNaming = "original_no_ext"
)
type DownloadLinkCache struct {
@@ -46,6 +53,7 @@ type Cache struct {
listings atomic.Value
downloadLinks map[string]string // key: file.Link, value: download link
PropfindResp *xsync.MapOf[string, PropfindResponse]
folderNaming WebDavFolderNaming
// config
workers int
@@ -62,77 +70,6 @@ type Cache struct {
downloadLinksMutex sync.Mutex // for downloadLinks
}
type fileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
isDir bool
}
func (fi *fileInfo) Name() string { return fi.name }
func (fi *fileInfo) Size() int64 { return fi.size }
func (fi *fileInfo) Mode() os.FileMode { return fi.mode }
func (fi *fileInfo) ModTime() time.Time { return fi.modTime }
func (fi *fileInfo) IsDir() bool { return fi.isDir }
func (fi *fileInfo) Sys() interface{} { return nil }
func (c *Cache) setTorrent(t *CachedTorrent) {
c.torrentsMutex.Lock()
c.torrents[t.Id] = t
c.torrentsNames[t.Name] = t
c.torrentsMutex.Unlock()
c.refreshListings()
go func() {
if err := c.SaveTorrent(t); err != nil {
c.logger.Debug().Err(err).Msgf("Failed to save torrent %s", t.Id)
}
}()
}
func (c *Cache) GetListing() []os.FileInfo {
if v, ok := c.listings.Load().([]os.FileInfo); ok {
return v
}
return nil
}
func (c *Cache) setTorrents(torrents map[string]*CachedTorrent) {
c.torrentsMutex.Lock()
for _, t := range torrents {
c.torrents[t.Id] = t
c.torrentsNames[t.Name] = t
}
c.torrentsMutex.Unlock()
c.refreshListings()
go func() {
if err := c.SaveTorrents(); err != nil {
c.logger.Debug().Err(err).Msgf("Failed to save torrents")
}
}()
}
func (c *Cache) GetTorrents() map[string]*CachedTorrent {
c.torrentsMutex.RLock()
defer c.torrentsMutex.RUnlock()
result := make(map[string]*CachedTorrent, len(c.torrents))
for k, v := range c.torrents {
result[k] = v
}
return result
}
func (c *Cache) GetTorrentNames() map[string]*CachedTorrent {
c.torrentsMutex.RLock()
defer c.torrentsMutex.RUnlock()
return c.torrentsNames
}
func NewCache(dc config.Debrid, client types.Client) *Cache {
cfg := config.GetConfig()
torrentRefreshInterval, err := time.ParseDuration(dc.TorrentRefreshInterval)
@@ -154,9 +91,77 @@ func NewCache(dc config.Debrid, client types.Client) *Cache {
torrentRefreshInterval: torrentRefreshInterval,
downloadLinksRefreshInterval: downloadLinksRefreshInterval,
PropfindResp: xsync.NewMapOf[string, PropfindResponse](),
folderNaming: WebDavFolderNaming(dc.WebDavFolderNaming),
}
}
func (c *Cache) GetTorrentFolder(torrent *types.Torrent) string {
folderName := torrent.Name
if c.folderNaming == WebDavUseID {
folderName = torrent.Id
} else if c.folderNaming == WebDavUseOriginalNameNoExt {
folderName = utils.RemoveExtension(torrent.Name)
}
return folderName
}
func (c *Cache) setTorrent(t *CachedTorrent) {
c.torrentsMutex.Lock()
c.torrents[t.Id] = t
c.torrentsNames[c.GetTorrentFolder(t.Torrent)] = t
c.torrentsMutex.Unlock()
c.refreshListings()
go func() {
if err := c.SaveTorrent(t); err != nil {
c.logger.Debug().Err(err).Msgf("Failed to save torrent %s", t.Id)
}
}()
}
func (c *Cache) setTorrents(torrents map[string]*CachedTorrent) {
c.torrentsMutex.Lock()
for _, t := range torrents {
c.torrents[t.Id] = t
c.torrentsNames[c.GetTorrentFolder(t.Torrent)] = t
}
c.torrentsMutex.Unlock()
c.refreshListings()
go func() {
if err := c.SaveTorrents(); err != nil {
c.logger.Debug().Err(err).Msgf("Failed to save torrents")
}
}()
}
func (c *Cache) GetListing() []os.FileInfo {
if v, ok := c.listings.Load().([]os.FileInfo); ok {
return v
}
return nil
}
func (c *Cache) GetTorrents() map[string]*CachedTorrent {
c.torrentsMutex.RLock()
defer c.torrentsMutex.RUnlock()
result := make(map[string]*CachedTorrent, len(c.torrents))
for k, v := range c.torrents {
result[k] = v
}
return result
}
func (c *Cache) GetTorrentNames() map[string]*CachedTorrent {
c.torrentsMutex.RLock()
defer c.torrentsMutex.RUnlock()
return c.torrentsNames
}
func (c *Cache) Start() error {
if err := os.MkdirAll(c.dir, 0755); err != nil {
return fmt.Errorf("failed to create cache directory: %w", err)
@@ -220,7 +225,6 @@ func (c *Cache) load() (map[string]*CachedTorrent, error) {
if len(ct.Files) != 0 {
// We can assume the torrent is complete
ct.IsComplete = true
ct.Torrent.Name = utils.RemoveExtension(ct.Torrent.OriginalFilename) // Update the name
torrents[ct.Id] = &ct
}
}
@@ -445,19 +449,21 @@ func (c *Cache) GetDownloadLink(torrentId, filename, fileLink string) string {
}
c.logger.Trace().Msgf("Getting download link for %s", ct.Name)
f := c.client.GetDownloadLink(ct.Torrent, &file)
if f == nil {
link, err := c.client.GetDownloadLink(ct.Torrent, &file)
if err != nil {
c.logger.Error().Err(err).Msg("Failed to get download link")
return ""
}
file.DownloadLink = f.DownloadLink
file.DownloadLink = link
file.Generated = time.Now()
ct.Files[filename] = file
go c.updateDownloadLink(f)
go c.updateDownloadLink(file)
go c.setTorrent(ct)
return f.DownloadLink
return file.DownloadLink
}
func (c *Cache) updateDownloadLink(file *types.File) {
func (c *Cache) updateDownloadLink(file types.File) {
c.downloadLinksMutex.Lock()
defer c.downloadLinksMutex.Unlock()
c.downloadLinks[file.Link] = file.DownloadLink
@@ -493,7 +499,7 @@ func (c *Cache) DeleteTorrents(ids []string) {
for _, id := range ids {
if t, ok := c.torrents[id]; ok {
delete(c.torrents, id)
delete(c.torrentsNames, t.Name)
delete(c.torrentsNames, c.GetTorrentFolder(t.Torrent))
c.removeFromDB(id)
}
}
@@ -507,6 +513,7 @@ func (c *Cache) removeFromDB(torrentId string) {
}
func (c *Cache) OnRemove(torrentId string) {
c.logger.Debug().Msgf("OnRemove triggered for %s", torrentId)
go c.DeleteTorrent(torrentId)
go c.refreshListings()
}

View File

@@ -15,6 +15,21 @@ import (
"time"
)
type fileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
isDir bool
}
func (fi *fileInfo) Name() string { return fi.name }
func (fi *fileInfo) Size() int64 { return fi.size }
func (fi *fileInfo) Mode() os.FileMode { return fi.mode }
func (fi *fileInfo) ModTime() time.Time { return fi.modTime }
func (fi *fileInfo) IsDir() bool { return fi.isDir }
func (fi *fileInfo) Sys() interface{} { return nil }
func (c *Cache) refreshListings() {
if c.listingRefreshMu.TryLock() {
defer c.listingRefreshMu.Unlock()
@@ -23,11 +38,9 @@ func (c *Cache) refreshListings() {
}
// Copy the current torrents to avoid concurrent issues
c.torrentsMutex.RLock()
torrents := make([]string, 0, len(c.torrents))
for _, t := range c.torrents {
if t != nil && t.Torrent != nil {
torrents = append(torrents, t.Name)
}
torrents := make([]string, 0, len(c.torrentsNames))
for k, _ := range c.torrentsNames {
torrents = append(torrents, k)
}
c.torrentsMutex.RUnlock()

View File

@@ -5,7 +5,6 @@ import (
"github.com/beevik/etree"
"github.com/sirrobot01/debrid-blackhole/internal/request"
"net/http"
"net/url"
"os"
path "path/filepath"
"time"
@@ -19,7 +18,7 @@ func (c *Cache) RefreshXml() error {
return fmt.Errorf("failed to refresh XML for %s: %v", parent, err)
}
}
c.logger.Debug().Msgf("Refreshed XML cache for %s", c.client.GetName())
c.logger.Trace().Msgf("Refreshed XML cache for %s", c.client.GetName())
return nil
}
@@ -49,7 +48,7 @@ func (c *Cache) refreshParentXml(torrents []os.FileInfo, parent string) error {
torrentPath := fmt.Sprintf("/webdav/%s/%s/%s/",
c.client.GetName(),
parent,
url.PathEscape(name),
name,
)
addDirectoryResponse(multistatus, torrentPath, name, currentTime)

View File

@@ -242,8 +242,8 @@ func (dl *DebridLink) GetDownloads() (map[string]types.DownloadLinks, error) {
return nil, nil
}
func (dl *DebridLink) GetDownloadLink(t *types.Torrent, file *types.File) *types.File {
return file
func (dl *DebridLink) GetDownloadLink(t *types.Torrent, file *types.File) (string, error) {
return file.DownloadLink, nil
}
func (dl *DebridLink) GetDownloadingStatus() []string {
@@ -281,9 +281,75 @@ func New(dc config.Debrid) *DebridLink {
}
func (dl *DebridLink) GetTorrents() ([]*types.Torrent, error) {
return nil, nil
page := 0
perPage := 100
torrents := make([]*types.Torrent, 0)
for {
t, err := dl.getTorrents(page, perPage)
if err != nil {
break
}
if len(t) == 0 {
break
}
torrents = append(torrents, t...)
page++
}
return torrents, nil
}
func (dl *DebridLink) ConvertLinksToFiles(links []string) []types.File {
return nil
func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) {
url := fmt.Sprintf("%s/seedbox/list?page=%d&perPage=%d", dl.Host, page, perPage)
req, _ := http.NewRequest(http.MethodGet, url, nil)
resp, err := dl.client.MakeRequest(req)
torrents := make([]*types.Torrent, 0)
if err != nil {
return torrents, err
}
var res TorrentInfo
err = json.Unmarshal(resp, &res)
if err != nil {
dl.logger.Info().Msgf("Error unmarshalling torrent info: %s", err)
return torrents, err
}
data := *res.Value
if len(data) == 0 {
return torrents, nil
}
for _, t := range data {
if t.Status != 100 {
continue
}
torrent := &types.Torrent{
Id: t.ID,
Name: t.Name,
Bytes: t.TotalSize,
Status: "downloaded",
Filename: t.Name,
OriginalFilename: t.Name,
InfoHash: t.HashString,
Files: make(map[string]types.File),
Debrid: dl.Name,
MountPath: dl.MountPath,
}
cfg := config.GetConfig()
for _, f := range t.Files {
if !cfg.IsSizeAllowed(f.Size) {
continue
}
file := types.File{
Id: f.ID,
Name: f.Name,
Size: f.Size,
Path: f.Name,
DownloadLink: f.DownloadURL,
Link: f.DownloadURL,
}
torrent.Files[f.Name] = file
}
torrents = append(torrents, torrent)
}
return torrents, nil
}

View File

@@ -173,16 +173,15 @@ func (r *RealDebrid) UpdateTorrent(t *types.Torrent) error {
if err != nil {
return err
}
name := utils.RemoveExtension(data.OriginalFilename)
t.Name = name
t.Name = data.OriginalFilename
t.Bytes = data.Bytes
t.Folder = name
t.Folder = data.OriginalFilename
t.Progress = data.Progress
t.Status = data.Status
t.Speed = data.Speed
t.Seeders = data.Seeders
t.Filename = data.Filename
t.OriginalFilename = name
t.OriginalFilename = data.OriginalFilename
t.Links = data.Links
t.MountPath = r.MountPath
t.Debrid = r.Name
@@ -204,11 +203,10 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torre
return t, err
}
status := data.Status
name := utils.RemoveExtension(data.OriginalFilename)
t.Name = name // Important because some magnet changes the name
t.Folder = name
t.Name = data.Filename // Important because some magnet changes the name
t.Folder = data.OriginalFilename
t.Filename = data.Filename
t.OriginalFilename = name
t.OriginalFilename = data.OriginalFilename
t.Bytes = data.Bytes
t.Progress = data.Progress
t.Speed = data.Speed
@@ -294,34 +292,7 @@ func (r *RealDebrid) GenerateDownloadLinks(t *types.Torrent) error {
return nil
}
func (r *RealDebrid) ConvertLinksToFiles(links []string) []types.File {
files := make([]types.File, 0)
for _, l := range links {
url := fmt.Sprintf("%s/unrestrict/link/", r.Host)
payload := gourl.Values{
"link": {l},
}
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
resp, err := r.client.MakeRequest(req)
if err != nil {
continue
}
var data UnrestrictResponse
if err = json.Unmarshal(resp, &data); err != nil {
continue
}
files = append(files, types.File{
Name: data.Filename,
Size: data.Filesize,
Link: l,
DownloadLink: data.Download,
Generated: time.Now(),
})
}
return files
}
func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) *types.File {
func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (string, error) {
url := fmt.Sprintf("%s/unrestrict/link/", r.Host)
payload := gourl.Values{
"link": {file.Link},
@@ -329,15 +300,13 @@ func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) *types.
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
resp, err := r.client.MakeRequest(req)
if err != nil {
return nil
return "", err
}
var data UnrestrictResponse
if err = json.Unmarshal(resp, &data); err != nil {
return nil
return "", err
}
file.DownloadLink = data.Download
file.Generated = time.Now()
return file
return data.Download, nil
}
func (r *RealDebrid) GetCheckCached() bool {

View File

@@ -273,7 +273,7 @@ func (tb *Torbox) GenerateDownloadLinks(t *types.Torrent) error {
return nil
}
func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) *types.File {
func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) (string, error) {
url := fmt.Sprintf("%s/api/torrents/requestdl/", tb.Host)
query := gourl.Values{}
query.Add("torrent_id", t.Id)
@@ -283,19 +283,17 @@ func (tb *Torbox) GetDownloadLink(t *types.Torrent, file *types.File) *types.Fil
req, _ := http.NewRequest(http.MethodGet, url, nil)
resp, err := tb.client.MakeRequest(req)
if err != nil {
return nil
return "", err
}
var data DownloadLinksResponse
if err = json.Unmarshal(resp, &data); err != nil {
return nil
return "", err
}
if data.Data == nil {
return nil
return "", fmt.Errorf("error getting download links")
}
link := *data.Data
file.DownloadLink = link
file.Generated = time.Now()
return file
return link, nil
}
func (tb *Torbox) GetDownloadingStatus() []string {
@@ -336,10 +334,6 @@ func New(dc config.Debrid) *Torbox {
}
}
func (tb *Torbox) ConvertLinksToFiles(links []string) []types.File {
return nil
}
func (tb *Torbox) GetDownloads() (map[string]types.DownloadLinks, error) {
return nil, nil
}

View File

@@ -8,8 +8,7 @@ type Client interface {
SubmitMagnet(tr *Torrent) (*Torrent, error)
CheckStatus(tr *Torrent, isSymlink bool) (*Torrent, error)
GenerateDownloadLinks(tr *Torrent) error
GetDownloadLink(tr *Torrent, file *File) *File
ConvertLinksToFiles(links []string) []File
GetDownloadLink(tr *Torrent, file *File) (string, error)
DeleteTorrent(torrentId string)
IsAvailable(infohashes []string) map[string]bool
GetCheckCached() bool