Add support for different folder naming; minor bug fixes
This commit is contained in:
@@ -30,6 +30,7 @@ type Debrid struct {
|
|||||||
TorrentRefreshInterval string `json:"torrent_refresh_interval"`
|
TorrentRefreshInterval string `json:"torrent_refresh_interval"`
|
||||||
DownloadLinksRefreshInterval string `json:"downloads_refresh_interval"`
|
DownloadLinksRefreshInterval string `json:"downloads_refresh_interval"`
|
||||||
TorrentRefreshWorkers int `json:"torrent_refresh_workers"`
|
TorrentRefreshWorkers int `json:"torrent_refresh_workers"`
|
||||||
|
WebDavFolderNaming string `json:"webdav_folder_naming"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
@@ -78,6 +79,10 @@ type WebDav struct {
|
|||||||
DownloadLinksRefreshInterval string `json:"download_links_refresh_interval"`
|
DownloadLinksRefreshInterval string `json:"download_links_refresh_interval"`
|
||||||
Workers int `json:"workers"`
|
Workers int `json:"workers"`
|
||||||
|
|
||||||
|
// Folder
|
||||||
|
FolderNaming string `json:"folder_naming"`
|
||||||
|
|
||||||
|
// Rclone
|
||||||
RcUrl string `json:"rc_url"`
|
RcUrl string `json:"rc_url"`
|
||||||
RcUser string `json:"rc_user"`
|
RcUser string `json:"rc_user"`
|
||||||
RcPass string `json:"rc_pass"`
|
RcPass string `json:"rc_pass"`
|
||||||
@@ -314,5 +319,8 @@ func (c *Config) GetDebridWebDav(d Debrid) Debrid {
|
|||||||
if d.TorrentRefreshWorkers == 0 {
|
if d.TorrentRefreshWorkers == 0 {
|
||||||
d.TorrentRefreshWorkers = cmp.Or(c.WebDav.Workers, 30) // 30 workers
|
d.TorrentRefreshWorkers = cmp.Or(c.WebDav.Workers, 30) // 30 workers
|
||||||
}
|
}
|
||||||
|
if d.WebDavFolderNaming == "" {
|
||||||
|
d.WebDavFolderNaming = cmp.Or(c.WebDav.FolderNaming, "original_no_ext")
|
||||||
|
}
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ func RemoveInvalidChars(value string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RemoveExtension(value string) string {
|
func RemoveExtension(value string) string {
|
||||||
value = RemoveInvalidChars(value)
|
|
||||||
re := regexp.MustCompile(VIDEOMATCH + "|" + MUSICMATCH)
|
re := regexp.MustCompile(VIDEOMATCH + "|" + MUSICMATCH)
|
||||||
|
|
||||||
// Find the last index of the matched extension
|
// Find the last index of the matched extension
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ func flattenFiles(files []MagnetFile, parentPath string, index *int) map[string]
|
|||||||
Name: fileName,
|
Name: fileName,
|
||||||
Size: f.Size,
|
Size: f.Size,
|
||||||
Path: currentPath,
|
Path: currentPath,
|
||||||
|
Link: f.Link,
|
||||||
}
|
}
|
||||||
result[file.Name] = file
|
result[file.Name] = file
|
||||||
}
|
}
|
||||||
@@ -239,7 +240,7 @@ func (ad *AllDebrid) GenerateDownloadLinks(t *types.Torrent) error {
|
|||||||
return nil
|
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)
|
url := fmt.Sprintf("%s/link/unlock", ad.Host)
|
||||||
query := gourl.Values{}
|
query := gourl.Values{}
|
||||||
query.Add("link", file.Link)
|
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)
|
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||||
resp, err := ad.client.MakeRequest(req)
|
resp, err := ad.client.MakeRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return "", err
|
||||||
}
|
}
|
||||||
var data DownloadLink
|
var data DownloadLink
|
||||||
if err = json.Unmarshal(resp, &data); err != nil {
|
if err = json.Unmarshal(resp, &data); err != nil {
|
||||||
return nil
|
return "", err
|
||||||
}
|
}
|
||||||
link := data.Data.Link
|
link := data.Data.Link
|
||||||
file.DownloadLink = link
|
if link == "" {
|
||||||
file.Generated = time.Now()
|
return "", fmt.Errorf("error getting download links %s", data.Error.Message)
|
||||||
return file
|
}
|
||||||
|
return link, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) GetCheckCached() bool {
|
func (ad *AllDebrid) GetCheckCached() bool {
|
||||||
@@ -264,7 +266,35 @@ func (ad *AllDebrid) GetCheckCached() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) GetTorrents() ([]*types.Torrent, error) {
|
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) {
|
func (ad *AllDebrid) GetDownloads() (map[string]types.DownloadLinks, error) {
|
||||||
@@ -279,10 +309,6 @@ func (ad *AllDebrid) GetDownloadUncached() bool {
|
|||||||
return ad.DownloadUncached
|
return ad.DownloadUncached
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AllDebrid) ConvertLinksToFiles(links []string) []types.File {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(dc config.Debrid) *AllDebrid {
|
func New(dc config.Debrid) *AllDebrid {
|
||||||
rl := request.ParseRateLimit(dc.RateLimit)
|
rl := request.ParseRateLimit(dc.RateLimit)
|
||||||
headers := map[string]string{
|
headers := map[string]string{
|
||||||
|
|||||||
@@ -40,6 +40,14 @@ type TorrentInfoResponse struct {
|
|||||||
Error *errorResponse `json:"error"`
|
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 {
|
type UploadMagnetResponse struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Data struct {
|
Data struct {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/sirrobot01/debrid-blackhole/internal/config"
|
||||||
"github.com/sirrobot01/debrid-blackhole/internal/logger"
|
"github.com/sirrobot01/debrid-blackhole/internal/logger"
|
||||||
"github.com/sirrobot01/debrid-blackhole/internal/utils"
|
"github.com/sirrobot01/debrid-blackhole/internal/utils"
|
||||||
"github.com/sirrobot01/debrid-blackhole/pkg/debrid/types"
|
"github.com/sirrobot01/debrid-blackhole/pkg/debrid/types"
|
||||||
@@ -16,8 +17,14 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"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 {
|
type DownloadLinkCache struct {
|
||||||
@@ -46,6 +53,7 @@ type Cache struct {
|
|||||||
listings atomic.Value
|
listings atomic.Value
|
||||||
downloadLinks map[string]string // key: file.Link, value: download link
|
downloadLinks map[string]string // key: file.Link, value: download link
|
||||||
PropfindResp *xsync.MapOf[string, PropfindResponse]
|
PropfindResp *xsync.MapOf[string, PropfindResponse]
|
||||||
|
folderNaming WebDavFolderNaming
|
||||||
|
|
||||||
// config
|
// config
|
||||||
workers int
|
workers int
|
||||||
@@ -62,77 +70,6 @@ type Cache struct {
|
|||||||
downloadLinksMutex sync.Mutex // for downloadLinks
|
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 {
|
func NewCache(dc config.Debrid, client types.Client) *Cache {
|
||||||
cfg := config.GetConfig()
|
cfg := config.GetConfig()
|
||||||
torrentRefreshInterval, err := time.ParseDuration(dc.TorrentRefreshInterval)
|
torrentRefreshInterval, err := time.ParseDuration(dc.TorrentRefreshInterval)
|
||||||
@@ -154,9 +91,77 @@ func NewCache(dc config.Debrid, client types.Client) *Cache {
|
|||||||
torrentRefreshInterval: torrentRefreshInterval,
|
torrentRefreshInterval: torrentRefreshInterval,
|
||||||
downloadLinksRefreshInterval: downloadLinksRefreshInterval,
|
downloadLinksRefreshInterval: downloadLinksRefreshInterval,
|
||||||
PropfindResp: xsync.NewMapOf[string, PropfindResponse](),
|
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 {
|
func (c *Cache) Start() error {
|
||||||
if err := os.MkdirAll(c.dir, 0755); err != nil {
|
if err := os.MkdirAll(c.dir, 0755); err != nil {
|
||||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
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 {
|
if len(ct.Files) != 0 {
|
||||||
// We can assume the torrent is complete
|
// We can assume the torrent is complete
|
||||||
ct.IsComplete = true
|
ct.IsComplete = true
|
||||||
ct.Torrent.Name = utils.RemoveExtension(ct.Torrent.OriginalFilename) // Update the name
|
|
||||||
torrents[ct.Id] = &ct
|
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)
|
c.logger.Trace().Msgf("Getting download link for %s", ct.Name)
|
||||||
f := c.client.GetDownloadLink(ct.Torrent, &file)
|
link, err := c.client.GetDownloadLink(ct.Torrent, &file)
|
||||||
if f == nil {
|
if err != nil {
|
||||||
|
c.logger.Error().Err(err).Msg("Failed to get download link")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
file.DownloadLink = f.DownloadLink
|
file.DownloadLink = link
|
||||||
|
file.Generated = time.Now()
|
||||||
ct.Files[filename] = file
|
ct.Files[filename] = file
|
||||||
|
|
||||||
go c.updateDownloadLink(f)
|
go c.updateDownloadLink(file)
|
||||||
go c.setTorrent(ct)
|
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()
|
c.downloadLinksMutex.Lock()
|
||||||
defer c.downloadLinksMutex.Unlock()
|
defer c.downloadLinksMutex.Unlock()
|
||||||
c.downloadLinks[file.Link] = file.DownloadLink
|
c.downloadLinks[file.Link] = file.DownloadLink
|
||||||
@@ -493,7 +499,7 @@ func (c *Cache) DeleteTorrents(ids []string) {
|
|||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
if t, ok := c.torrents[id]; ok {
|
if t, ok := c.torrents[id]; ok {
|
||||||
delete(c.torrents, id)
|
delete(c.torrents, id)
|
||||||
delete(c.torrentsNames, t.Name)
|
delete(c.torrentsNames, c.GetTorrentFolder(t.Torrent))
|
||||||
c.removeFromDB(id)
|
c.removeFromDB(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -507,6 +513,7 @@ func (c *Cache) removeFromDB(torrentId string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) OnRemove(torrentId string) {
|
func (c *Cache) OnRemove(torrentId string) {
|
||||||
|
c.logger.Debug().Msgf("OnRemove triggered for %s", torrentId)
|
||||||
go c.DeleteTorrent(torrentId)
|
go c.DeleteTorrent(torrentId)
|
||||||
go c.refreshListings()
|
go c.refreshListings()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,21 @@ import (
|
|||||||
"time"
|
"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() {
|
func (c *Cache) refreshListings() {
|
||||||
if c.listingRefreshMu.TryLock() {
|
if c.listingRefreshMu.TryLock() {
|
||||||
defer c.listingRefreshMu.Unlock()
|
defer c.listingRefreshMu.Unlock()
|
||||||
@@ -23,11 +38,9 @@ func (c *Cache) refreshListings() {
|
|||||||
}
|
}
|
||||||
// Copy the current torrents to avoid concurrent issues
|
// Copy the current torrents to avoid concurrent issues
|
||||||
c.torrentsMutex.RLock()
|
c.torrentsMutex.RLock()
|
||||||
torrents := make([]string, 0, len(c.torrents))
|
torrents := make([]string, 0, len(c.torrentsNames))
|
||||||
for _, t := range c.torrents {
|
for k, _ := range c.torrentsNames {
|
||||||
if t != nil && t.Torrent != nil {
|
torrents = append(torrents, k)
|
||||||
torrents = append(torrents, t.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
c.torrentsMutex.RUnlock()
|
c.torrentsMutex.RUnlock()
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"github.com/beevik/etree"
|
"github.com/beevik/etree"
|
||||||
"github.com/sirrobot01/debrid-blackhole/internal/request"
|
"github.com/sirrobot01/debrid-blackhole/internal/request"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
path "path/filepath"
|
path "path/filepath"
|
||||||
"time"
|
"time"
|
||||||
@@ -19,7 +18,7 @@ func (c *Cache) RefreshXml() error {
|
|||||||
return fmt.Errorf("failed to refresh XML for %s: %v", parent, err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +48,7 @@ func (c *Cache) refreshParentXml(torrents []os.FileInfo, parent string) error {
|
|||||||
torrentPath := fmt.Sprintf("/webdav/%s/%s/%s/",
|
torrentPath := fmt.Sprintf("/webdav/%s/%s/%s/",
|
||||||
c.client.GetName(),
|
c.client.GetName(),
|
||||||
parent,
|
parent,
|
||||||
url.PathEscape(name),
|
name,
|
||||||
)
|
)
|
||||||
|
|
||||||
addDirectoryResponse(multistatus, torrentPath, name, currentTime)
|
addDirectoryResponse(multistatus, torrentPath, name, currentTime)
|
||||||
|
|||||||
@@ -242,8 +242,8 @@ func (dl *DebridLink) GetDownloads() (map[string]types.DownloadLinks, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dl *DebridLink) GetDownloadLink(t *types.Torrent, file *types.File) *types.File {
|
func (dl *DebridLink) GetDownloadLink(t *types.Torrent, file *types.File) (string, error) {
|
||||||
return file
|
return file.DownloadLink, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dl *DebridLink) GetDownloadingStatus() []string {
|
func (dl *DebridLink) GetDownloadingStatus() []string {
|
||||||
@@ -281,9 +281,75 @@ func New(dc config.Debrid) *DebridLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dl *DebridLink) GetTorrents() ([]*types.Torrent, error) {
|
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 {
|
func (dl *DebridLink) getTorrents(page, perPage int) ([]*types.Torrent, error) {
|
||||||
return nil
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,16 +173,15 @@ func (r *RealDebrid) UpdateTorrent(t *types.Torrent) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
name := utils.RemoveExtension(data.OriginalFilename)
|
t.Name = data.OriginalFilename
|
||||||
t.Name = name
|
|
||||||
t.Bytes = data.Bytes
|
t.Bytes = data.Bytes
|
||||||
t.Folder = name
|
t.Folder = data.OriginalFilename
|
||||||
t.Progress = data.Progress
|
t.Progress = data.Progress
|
||||||
t.Status = data.Status
|
t.Status = data.Status
|
||||||
t.Speed = data.Speed
|
t.Speed = data.Speed
|
||||||
t.Seeders = data.Seeders
|
t.Seeders = data.Seeders
|
||||||
t.Filename = data.Filename
|
t.Filename = data.Filename
|
||||||
t.OriginalFilename = name
|
t.OriginalFilename = data.OriginalFilename
|
||||||
t.Links = data.Links
|
t.Links = data.Links
|
||||||
t.MountPath = r.MountPath
|
t.MountPath = r.MountPath
|
||||||
t.Debrid = r.Name
|
t.Debrid = r.Name
|
||||||
@@ -204,11 +203,10 @@ func (r *RealDebrid) CheckStatus(t *types.Torrent, isSymlink bool) (*types.Torre
|
|||||||
return t, err
|
return t, err
|
||||||
}
|
}
|
||||||
status := data.Status
|
status := data.Status
|
||||||
name := utils.RemoveExtension(data.OriginalFilename)
|
t.Name = data.Filename // Important because some magnet changes the name
|
||||||
t.Name = name // Important because some magnet changes the name
|
t.Folder = data.OriginalFilename
|
||||||
t.Folder = name
|
|
||||||
t.Filename = data.Filename
|
t.Filename = data.Filename
|
||||||
t.OriginalFilename = name
|
t.OriginalFilename = data.OriginalFilename
|
||||||
t.Bytes = data.Bytes
|
t.Bytes = data.Bytes
|
||||||
t.Progress = data.Progress
|
t.Progress = data.Progress
|
||||||
t.Speed = data.Speed
|
t.Speed = data.Speed
|
||||||
@@ -294,34 +292,7 @@ func (r *RealDebrid) GenerateDownloadLinks(t *types.Torrent) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) ConvertLinksToFiles(links []string) []types.File {
|
func (r *RealDebrid) GetDownloadLink(t *types.Torrent, file *types.File) (string, error) {
|
||||||
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 {
|
|
||||||
url := fmt.Sprintf("%s/unrestrict/link/", r.Host)
|
url := fmt.Sprintf("%s/unrestrict/link/", r.Host)
|
||||||
payload := gourl.Values{
|
payload := gourl.Values{
|
||||||
"link": {file.Link},
|
"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()))
|
req, _ := http.NewRequest(http.MethodPost, url, strings.NewReader(payload.Encode()))
|
||||||
resp, err := r.client.MakeRequest(req)
|
resp, err := r.client.MakeRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return "", err
|
||||||
}
|
}
|
||||||
var data UnrestrictResponse
|
var data UnrestrictResponse
|
||||||
if err = json.Unmarshal(resp, &data); err != nil {
|
if err = json.Unmarshal(resp, &data); err != nil {
|
||||||
return nil
|
return "", err
|
||||||
}
|
}
|
||||||
file.DownloadLink = data.Download
|
return data.Download, nil
|
||||||
file.Generated = time.Now()
|
|
||||||
return file
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RealDebrid) GetCheckCached() bool {
|
func (r *RealDebrid) GetCheckCached() bool {
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ func (tb *Torbox) GenerateDownloadLinks(t *types.Torrent) error {
|
|||||||
return nil
|
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)
|
url := fmt.Sprintf("%s/api/torrents/requestdl/", tb.Host)
|
||||||
query := gourl.Values{}
|
query := gourl.Values{}
|
||||||
query.Add("torrent_id", t.Id)
|
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)
|
req, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||||
resp, err := tb.client.MakeRequest(req)
|
resp, err := tb.client.MakeRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return "", err
|
||||||
}
|
}
|
||||||
var data DownloadLinksResponse
|
var data DownloadLinksResponse
|
||||||
if err = json.Unmarshal(resp, &data); err != nil {
|
if err = json.Unmarshal(resp, &data); err != nil {
|
||||||
return nil
|
return "", err
|
||||||
}
|
}
|
||||||
if data.Data == nil {
|
if data.Data == nil {
|
||||||
return nil
|
return "", fmt.Errorf("error getting download links")
|
||||||
}
|
}
|
||||||
link := *data.Data
|
link := *data.Data
|
||||||
file.DownloadLink = link
|
return link, nil
|
||||||
file.Generated = time.Now()
|
|
||||||
return file
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *Torbox) GetDownloadingStatus() []string {
|
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) {
|
func (tb *Torbox) GetDownloads() (map[string]types.DownloadLinks, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ type Client interface {
|
|||||||
SubmitMagnet(tr *Torrent) (*Torrent, error)
|
SubmitMagnet(tr *Torrent) (*Torrent, error)
|
||||||
CheckStatus(tr *Torrent, isSymlink bool) (*Torrent, error)
|
CheckStatus(tr *Torrent, isSymlink bool) (*Torrent, error)
|
||||||
GenerateDownloadLinks(tr *Torrent) error
|
GenerateDownloadLinks(tr *Torrent) error
|
||||||
GetDownloadLink(tr *Torrent, file *File) *File
|
GetDownloadLink(tr *Torrent, file *File) (string, error)
|
||||||
ConvertLinksToFiles(links []string) []File
|
|
||||||
DeleteTorrent(torrentId string)
|
DeleteTorrent(torrentId string)
|
||||||
IsAvailable(infohashes []string) map[string]bool
|
IsAvailable(infohashes []string) map[string]bool
|
||||||
GetCheckCached() bool
|
GetCheckCached() bool
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func (q *QBit) ProcessFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rclonePath := filepath.Join(debridTorrent.MountPath, debridTorrent.Name)
|
rclonePath := filepath.Join(debridTorrent.MountPath, cache.GetTorrentFolder(debridTorrent))
|
||||||
torrentSymlinkPath, err = q.createSymlinks(debridTorrent, rclonePath, debridTorrent.Name)
|
torrentSymlinkPath, err = q.createSymlinks(debridTorrent, rclonePath, debridTorrent.Name)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ func (f *File) Read(p []byte) (n int, err error) {
|
|||||||
if f.metadataOnly {
|
if f.metadataOnly {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
// If file content is preloaded, read from memory.
|
// If file content is preloaded, read from memory.
|
||||||
if f.content != nil {
|
if f.content != nil {
|
||||||
if f.offset >= int64(len(f.content)) {
|
if f.offset >= int64(len(f.content)) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package webdav
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/sirrobot01/debrid-blackhole/internal/request"
|
"github.com/sirrobot01/debrid-blackhole/internal/request"
|
||||||
@@ -12,7 +11,6 @@ import (
|
|||||||
"golang.org/x/net/webdav"
|
"golang.org/x/net/webdav"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
@@ -229,6 +227,7 @@ func (h *Handler) getFileInfos(torrent *types.Torrent) []os.FileInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Handle OPTIONS
|
// Handle OPTIONS
|
||||||
if r.Method == "OPTIONS" {
|
if r.Method == "OPTIONS" {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
@@ -473,50 +472,3 @@ func (h *Handler) serveDirectory(w http.ResponseWriter, r *http.Request, file we
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) ioCopy(reader io.Reader, w io.Writer) (int64, error) {
|
|
||||||
// Start with a smaller buffer for faster first byte delivery.
|
|
||||||
buf := make([]byte, 4*1024) // 8KB initial buffer
|
|
||||||
totalWritten := int64(0)
|
|
||||||
firstChunk := true
|
|
||||||
|
|
||||||
for {
|
|
||||||
n, err := reader.Read(buf)
|
|
||||||
if n > 0 {
|
|
||||||
nw, ew := w.Write(buf[:n])
|
|
||||||
if ew != nil {
|
|
||||||
var opErr *net.OpError
|
|
||||||
if errors.As(ew, &opErr) && opErr.Err.Error() == "write: broken pipe" {
|
|
||||||
h.logger.Debug().Msg("Client closed connection (normal for streaming)")
|
|
||||||
return totalWritten, ew
|
|
||||||
}
|
|
||||||
return totalWritten, ew
|
|
||||||
}
|
|
||||||
totalWritten += int64(nw)
|
|
||||||
|
|
||||||
// Flush immediately after the first chunk.
|
|
||||||
if firstChunk {
|
|
||||||
if flusher, ok := w.(http.Flusher); ok {
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
firstChunk = false
|
|
||||||
// Increase buffer size for subsequent reads.
|
|
||||||
buf = make([]byte, 512*1024) // 64KB buffer after first chunk
|
|
||||||
} else if totalWritten%(2*1024*1024) < int64(n) {
|
|
||||||
// Flush roughly every 2MB of data transferred.
|
|
||||||
if flusher, ok := w.(http.Flusher); ok {
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
h.logger.Error().Err(err).Msg("Error reading from file")
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalWritten, nil
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user