mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat(releases): retry failed downloads (#491)
* feat(download): implement parsing and retry * feat: retry torrent file downloads * refactor: error handling downloadtorrentfile * feat: add tests for download torrent file * build: add runs-on self-hosted * build: add runs-on self-hosted
This commit is contained in:
parent
5183f7683a
commit
2d8f7aeb4e
10 changed files with 349 additions and 143 deletions
|
@ -1,14 +1,15 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
"github.com/autobrr/autobrr/pkg/errors"
|
||||
|
||||
"github.com/anacrolix/torrent/metainfo"
|
||||
"github.com/avast/retry-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
|
@ -35,58 +36,6 @@ func NewHttpClient() *HttpClient {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *HttpClient) DownloadFile(url string, opts map[string]string) (*DownloadFileResponse, error) {
|
||||
if url == "" {
|
||||
return nil, errors.New("download_file: url can't be empty")
|
||||
}
|
||||
|
||||
// Create tmp file
|
||||
tmpFile, err := os.CreateTemp("", "autobrr-")
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("error creating temp file")
|
||||
return nil, err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
|
||||
// Get the data
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msgf("error downloading file from %v", url)
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// retry logic
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Error().Stack().Err(err).Msgf("error downloading file from: %v - bad status: %d", url, resp.StatusCode)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(tmpFile, resp.Body)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msgf("error writing downloaded file: %v", tmpFile.Name())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// remove file if fail
|
||||
|
||||
res := DownloadFileResponse{
|
||||
Body: &resp.Body,
|
||||
FileName: tmpFile.Name(),
|
||||
}
|
||||
|
||||
if res.FileName == "" || res.Body == nil {
|
||||
log.Error().Stack().Err(err).Msgf("tmp file error - empty body: %v", url)
|
||||
return nil, errors.New("error downloading file, no tmp file")
|
||||
}
|
||||
|
||||
log.Debug().Msgf("successfully downloaded file: %v", tmpFile.Name())
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (c *HttpClient) DownloadTorrentFile(url string, opts map[string]string) (*DownloadTorrentFileResponse, error) {
|
||||
if url == "" {
|
||||
return nil, errors.New("download_file: url can't be empty")
|
||||
|
@ -100,47 +49,63 @@ func (c *HttpClient) DownloadTorrentFile(url string, opts map[string]string) (*D
|
|||
}
|
||||
defer tmpFile.Close()
|
||||
|
||||
// Get the data
|
||||
resp, err := http.Get(url)
|
||||
res := &DownloadTorrentFileResponse{}
|
||||
// try request and if fail run 3 retries
|
||||
err = retry.Do(func() error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return errors.New("error downloading file: %q", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("error downloading file bad status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
nuke := func() {
|
||||
tmpFile.Seek(0, io.SeekStart)
|
||||
tmpFile.Truncate(0)
|
||||
}
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(tmpFile, resp.Body)
|
||||
if err != nil {
|
||||
nuke()
|
||||
return errors.New("error writing downloaded file: %v | %q", tmpFile.Name(), err)
|
||||
}
|
||||
|
||||
meta, err := metainfo.Load(resp.Body)
|
||||
if err != nil {
|
||||
nuke()
|
||||
return errors.New("metainfo could not load file contents: %v | %q", tmpFile.Name(), err)
|
||||
}
|
||||
|
||||
res = &DownloadTorrentFileResponse{
|
||||
MetaInfo: meta,
|
||||
TmpFileName: tmpFile.Name(),
|
||||
}
|
||||
|
||||
if res.TmpFileName == "" || res.MetaInfo == nil {
|
||||
nuke()
|
||||
return errors.New("tmp file error - empty body")
|
||||
}
|
||||
|
||||
if len(res.MetaInfo.InfoBytes) < 1 {
|
||||
nuke()
|
||||
return errors.New("could not read infohash")
|
||||
}
|
||||
|
||||
log.Debug().Msgf("successfully downloaded file: %v", tmpFile.Name())
|
||||
return nil
|
||||
},
|
||||
//retry.OnRetry(func(n uint, err error) { c.log.Printf("%q: attempt %d - %v\n", err, n, url) }),
|
||||
retry.Delay(time.Second*5),
|
||||
retry.Attempts(3),
|
||||
retry.MaxJitter(time.Second*1))
|
||||
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msgf("error downloading file from %v", url)
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// retry logic
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Error().Stack().Err(err).Msgf("error downloading file from: %v - bad status: %d", url, resp.StatusCode)
|
||||
return nil, err
|
||||
res = nil
|
||||
}
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(tmpFile, resp.Body)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msgf("error writing downloaded file: %v", tmpFile.Name())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
meta, err := metainfo.Load(resp.Body)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msgf("metainfo could not load file contents: %v", tmpFile.Name())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// remove file if fail
|
||||
|
||||
res := DownloadTorrentFileResponse{
|
||||
MetaInfo: meta,
|
||||
TmpFileName: tmpFile.Name(),
|
||||
}
|
||||
|
||||
if res.TmpFileName == "" || res.MetaInfo == nil {
|
||||
log.Error().Stack().Err(err).Msgf("tmp file error - empty body: %v", url)
|
||||
return nil, errors.New("error downloading file, no tmp file")
|
||||
}
|
||||
|
||||
log.Debug().Msgf("successfully downloaded file: %v", tmpFile.Name())
|
||||
|
||||
return &res, nil
|
||||
return res, err
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue