4 Commits

Author SHA1 Message Date
Mukhtar Akere
e9d3e120f3 Hotfix
Some checks failed
Release / goreleaser (push) Failing after 2m2s
2024-12-25 00:06:47 +01:00
Mukhtar Akere
104df3c33c Changelog 0.3.2 2024-12-25 00:00:47 +01:00
Kai Gohegan
810c9d705e Update storage.go (#10) 2024-12-24 14:59:33 -08:00
Kai Gohegan
4ff00859a3 Update Dockerfile (#9) 2024-12-24 14:59:08 -08:00
17 changed files with 130 additions and 63 deletions

View File

@@ -96,4 +96,13 @@
#### 0.3.1
- Add DebridLink Support
- Refactor error handling
- Refactor error handling
#### 0.3.2
- Fix DebridLink not downloading
- Fix Torbox with uncached torrents
- Add new /internal/cached endpoint to check if an hash is cached
- implement per-debrid local cache
- Fix file check for torbox
- Other minor bug fixes

View File

@@ -24,5 +24,7 @@ COPY --from=builder /app/README.md /README.md
EXPOSE 8181
VOLUME ["/app"]
# Run
CMD ["/blackhole", "--config", "/app/config.json"]
CMD ["/blackhole", "--config", "/app/config.json"]

View File

@@ -12,9 +12,8 @@ import (
func Start(ctx context.Context, config *common.Config) error {
maxCacheSize := cmp.Or(config.MaxCacheSize, 1000)
cache := common.NewCache(maxCacheSize)
deb := debrid.NewDebrid(config.Debrids, cache)
deb := debrid.NewDebrid(config.Debrids, maxCacheSize)
var wg sync.WaitGroup
errChan := make(chan error, 2)
@@ -23,7 +22,7 @@ func Start(ctx context.Context, config *common.Config) error {
wg.Add(1)
go func() {
defer wg.Done()
if err := proxy.NewProxy(*config, deb, cache).Start(ctx); err != nil {
if err := proxy.NewProxy(*config, deb).Start(ctx); err != nil {
errChan <- err
}
}()
@@ -32,7 +31,7 @@ func Start(ctx context.Context, config *common.Config) error {
wg.Add(1)
go func() {
defer wg.Done()
if err := qbit.Start(ctx, config, deb, cache); err != nil {
if err := qbit.Start(ctx, config, deb); err != nil {
errChan <- err
}
}()

View File

@@ -40,14 +40,16 @@ func (c *Cache) AddMultiple(values map[string]bool) {
c.mu.Lock()
defer c.mu.Unlock()
for value := range values {
if _, exists := c.data[value]; !exists {
if len(c.order) >= c.maxItems {
delete(c.data, c.order[0])
c.order = c.order[1:]
for value, exists := range values {
if !exists {
if _, exists := c.data[value]; !exists {
if len(c.order) >= c.maxItems {
delete(c.data, c.order[0])
c.order = c.order[1:]
}
c.data[value] = struct{}{}
c.order = append(c.order, value)
}
c.data[value] = struct{}{}
c.order = append(c.order, value)
}
}
}

View File

@@ -34,10 +34,13 @@ type Service interface {
GetLogger() *log.Logger
}
func NewDebrid(debs []common.DebridConfig, cache *common.Cache) *DebridService {
func NewDebrid(debs []common.DebridConfig, maxCachedSize int) *DebridService {
debrids := make([]Service, 0)
// Divide the cache size by the number of debrids
maxCacheSize := maxCachedSize / len(debs)
for _, dc := range debs {
d := createDebrid(dc, cache)
d := createDebrid(dc, common.NewCache(maxCacheSize))
d.GetLogger().Println("Debrid Service started")
debrids = append(debrids, d)
}
@@ -114,26 +117,27 @@ func getTorrentInfo(filePath string) (*Torrent, error) {
func GetLocalCache(infohashes []string, cache *common.Cache) ([]string, map[string]bool) {
result := make(map[string]bool)
hashes := make([]string, 0)
//if len(infohashes) == 0 {
// return hashes, result
//}
//if len(infohashes) == 1 {
// if cache.Exists(infohashes[0]) {
// return hashes, map[string]bool{infohashes[0]: true}
// }
// return infohashes, result
//}
//
//cachedHashes := cache.GetMultiple(infohashes)
//for _, h := range infohashes {
// _, exists := cachedHashes[h]
// if !exists {
// hashes = append(hashes, h)
// } else {
// result[h] = true
// }
//}
if len(infohashes) == 0 {
return hashes, result
}
if len(infohashes) == 1 {
if cache.Exists(infohashes[0]) {
return hashes, map[string]bool{infohashes[0]: true}
}
return infohashes, result
}
cachedHashes := cache.GetMultiple(infohashes)
for _, h := range infohashes {
_, exists := cachedHashes[h]
if !exists {
hashes = append(hashes, h)
} else {
result[h] = true
}
}
return infohashes, result
}

View File

@@ -39,8 +39,8 @@ func (r *DebridLink) IsAvailable(infohashes []string) map[string]bool {
}
// Divide hashes into groups of 100
for i := 0; i < len(hashes); i += 200 {
end := i + 200
for i := 0; i < len(hashes); i += 100 {
end := i + 100
if end > len(hashes) {
end = len(hashes)
}

View File

@@ -168,7 +168,6 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
var data structs.RealDebridTorrentInfo
err = json.Unmarshal(resp, &data)
status := data.Status
fmt.Println("RD STATUS: ", status)
name := common.RemoveInvalidChars(data.OriginalFilename)
torrent.Name = name // Important because some magnet changes the name
torrent.Folder = name
@@ -181,7 +180,7 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
torrent.Links = data.Links
torrent.Status = status
torrent.Debrid = r
downloading_status := []string{"downloading", "magnet_conversion", "queued", "compressing", "uploading"}
downloadingStatus := []string{"downloading", "magnet_conversion", "queued", "compressing", "uploading"}
if status == "error" || status == "dead" || status == "magnet_error" {
return torrent, fmt.Errorf("torrent: %s has error: %s", torrent.Name, status)
} else if status == "waiting_files_selection" {
@@ -214,7 +213,7 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, er
}
}
break
} else if slices.Contains(downloading_status, status) {
} else if slices.Contains(downloadingStatus, status) {
if !r.DownloadUncached {
return torrent, fmt.Errorf("torrent: %s not cached", torrent.Name)
}

View File

@@ -11,3 +11,12 @@ func (d *DebridService) Get() Service {
}
return d.debrids[d.lastUsed]
}
func (d *DebridService) GetByName(name string) Service {
for _, deb := range d.debrids {
if deb.GetName() == name {
return deb
}
}
return nil
}

View File

@@ -45,8 +45,8 @@ func (r *Torbox) IsAvailable(infohashes []string) map[string]bool {
}
// Divide hashes into groups of 100
for i := 0; i < len(hashes); i += 200 {
end := i + 200
for i := 0; i < len(hashes); i += 100 {
end := i + 100
if end > len(hashes) {
end = len(hashes)
}
@@ -165,9 +165,6 @@ func (r *Torbox) GetTorrent(id string) (*Torrent, error) {
torrent.Filename = name
torrent.OriginalFilename = name
files := make([]TorrentFile, 0)
if len(data.Files) == 0 {
return torrent, fmt.Errorf("no files found for torrent: %s", name)
}
for _, f := range data.Files {
fileName := filepath.Base(f.Name)
if (!common.RegexMatch(common.VIDEOMATCH, fileName) &&
@@ -183,10 +180,13 @@ func (r *Torbox) GetTorrent(id string) (*Torrent, error) {
}
files = append(files, file)
}
if len(files) == 0 {
return torrent, fmt.Errorf("no video files found")
var cleanPath string
if len(files) > 0 {
cleanPath = path.Clean(data.Files[0].Name)
} else {
cleanPath = path.Clean(data.Name)
}
cleanPath := path.Clean(data.Files[0].Name)
torrent.OriginalFilename = strings.Split(cleanPath, "/")[0]
torrent.Files = files
torrent.Debrid = r
@@ -229,7 +229,7 @@ func (r *Torbox) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error)
}
func (r *Torbox) DeleteTorrent(torrent *Torrent) {
url := fmt.Sprintf("%s/api//torrents/controltorrent/%s", r.Host, torrent.Id)
url := fmt.Sprintf("%s/api/torrents/controltorrent/%s", r.Host, torrent.Id)
payload := map[string]string{"torrent_id": torrent.Id, "action": "Delete"}
jsonPayload, _ := json.Marshal(payload)
req, _ := http.NewRequest(http.MethodDelete, url, bytes.NewBuffer(jsonPayload))

View File

@@ -75,11 +75,10 @@ type Proxy struct {
password string
cachedOnly bool
debrid debrid.Service
cache *common.Cache
logger *log.Logger
}
func NewProxy(config common.Config, deb *debrid.DebridService, cache *common.Cache) *Proxy {
func NewProxy(config common.Config, deb *debrid.DebridService) *Proxy {
cfg := config.Proxy
port := cmp.Or(os.Getenv("PORT"), cfg.Port, "8181")
return &Proxy{
@@ -90,7 +89,6 @@ func NewProxy(config common.Config, deb *debrid.DebridService, cache *common.Cac
password: cfg.Password,
cachedOnly: *cfg.CachedOnly,
debrid: deb.Get(),
cache: cache,
logger: common.NewLogger("Proxy", os.Stdout),
}
}

View File

@@ -8,8 +8,8 @@ import (
"goBlack/pkg/qbit/server"
)
func Start(ctx context.Context, config *common.Config, deb *debrid.DebridService, cache *common.Cache) error {
srv := server.NewServer(config, deb, cache)
func Start(ctx context.Context, config *common.Config, deb *debrid.DebridService) error {
srv := server.NewServer(config, deb)
if err := srv.Start(ctx); err != nil {
return fmt.Errorf("failed to start qbit server: %w", err)
}

View File

@@ -45,6 +45,7 @@ func (s *Server) Routes(r chi.Router) http.Handler {
r.Get("/episodes/{contentId}", s.handleEpisodes)
r.Post("/add", s.handleAddContent)
r.Get("/search", s.handleSearch)
r.Get("/cached", s.handleCheckCached)
})
return r
}

View File

@@ -22,9 +22,9 @@ type Server struct {
debug bool
}
func NewServer(config *common.Config, deb *debrid.DebridService, cache *common.Cache) *Server {
func NewServer(config *common.Config, deb *debrid.DebridService) *Server {
logger := common.NewLogger("QBit", os.Stdout)
q := shared.NewQBit(config, deb, cache, logger)
q := shared.NewQBit(config, deb, logger)
return &Server{
qbit: q,
logger: logger,

View File

@@ -5,8 +5,10 @@ import (
"encoding/json"
"goBlack/common"
"goBlack/pkg/arr"
"goBlack/pkg/debrid"
"html/template"
"net/http"
"strings"
)
type AddRequest struct {
@@ -112,3 +114,36 @@ func (s *Server) handleAddContent(w http.ResponseWriter, r *http.Request) {
}
common.JSONResponse(w, importReq, http.StatusOK)
}
func (s *Server) handleCheckCached(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_hashes := r.URL.Query().Get("hash")
if _hashes == "" {
http.Error(w, "No hashes provided", http.StatusBadRequest)
return
}
hashes := strings.Split(_hashes, ",")
if len(hashes) == 0 {
http.Error(w, "No hashes provided", http.StatusBadRequest)
return
}
db := r.URL.Query().Get("debrid")
var deb debrid.Service
if db == "" {
// use the first debrid
deb = s.qbit.Debrid.Get()
} else {
deb = s.qbit.Debrid.GetByName(db)
}
if deb == nil {
http.Error(w, "Invalid debrid", http.StatusBadRequest)
return
}
res := deb.IsAvailable(hashes)
result := make(map[string]bool)
for _, h := range hashes {
_, exists := res[h]
result[h] = exists
}
common.JSONResponse(w, result, http.StatusOK)
}

View File

@@ -16,7 +16,6 @@ type QBit struct {
DownloadFolder string `json:"download_folder"`
Categories []string `json:"categories"`
Debrid *debrid.DebridService
cache *common.Cache
Storage *TorrentStorage
debug bool
logger *log.Logger
@@ -24,7 +23,7 @@ type QBit struct {
RefreshInterval int
}
func NewQBit(config *common.Config, deb *debrid.DebridService, cache *common.Cache, logger *log.Logger) *QBit {
func NewQBit(config *common.Config, deb *debrid.DebridService, logger *log.Logger) *QBit {
cfg := config.QBitTorrent
port := cmp.Or(cfg.Port, os.Getenv("QBIT_PORT"), "8182")
refreshInterval := cmp.Or(cfg.RefreshInterval, 10)
@@ -36,7 +35,6 @@ func NewQBit(config *common.Config, deb *debrid.DebridService, cache *common.Cac
DownloadFolder: cfg.DownloadFolder,
Categories: cfg.Categories,
Debrid: deb,
cache: cache,
debug: cfg.Debug,
Storage: NewTorrentStorage("torrents.json"),
logger: logger,

View File

@@ -10,6 +10,7 @@ type TorrentStorage struct {
torrents map[string]*Torrent
mu sync.RWMutex
order []string
filename string // Added to store the filename for persistence
}
func loadTorrentsFromJSON(filename string) (map[string]*Torrent, error) {
@@ -25,7 +26,7 @@ func loadTorrentsFromJSON(filename string) (map[string]*Torrent, error) {
}
func NewTorrentStorage(filename string) *TorrentStorage {
// Open the json file and read the data
// Open the JSON file and read the data
torrents, err := loadTorrentsFromJSON(filename)
if err != nil {
torrents = make(map[string]*Torrent)
@@ -38,6 +39,7 @@ func NewTorrentStorage(filename string) *TorrentStorage {
return &TorrentStorage{
torrents: torrents,
order: order,
filename: filename,
}
}
@@ -46,6 +48,7 @@ func (ts *TorrentStorage) Add(torrent *Torrent) {
defer ts.mu.Unlock()
ts.torrents[torrent.Hash] = torrent
ts.order = append(ts.order, torrent.Hash)
_ = ts.saveToFile()
}
func (ts *TorrentStorage) AddOrUpdate(torrent *Torrent) {
@@ -55,6 +58,7 @@ func (ts *TorrentStorage) AddOrUpdate(torrent *Torrent) {
ts.order = append(ts.order, torrent.Hash)
}
ts.torrents[torrent.Hash] = torrent
_ = ts.saveToFile()
}
func (ts *TorrentStorage) GetByID(id string) *Torrent {
@@ -104,6 +108,7 @@ func (ts *TorrentStorage) Update(torrent *Torrent) {
ts.mu.Lock()
defer ts.mu.Unlock()
ts.torrents[torrent.Hash] = torrent
_ = ts.saveToFile()
}
func (ts *TorrentStorage) Delete(hash string) {
@@ -127,14 +132,20 @@ func (ts *TorrentStorage) Delete(hash string) {
return
}
}
_ = ts.saveToFile()
}
func (ts *TorrentStorage) Save(filename string) error {
func (ts *TorrentStorage) Save() error {
ts.mu.RLock()
defer ts.mu.RUnlock()
data, err := json.Marshal(ts.torrents)
return ts.saveToFile()
}
// saveToFile is a helper function to write the current state to the JSON file
func (ts *TorrentStorage) saveToFile() error {
data, err := json.MarshalIndent(ts.torrents, "", " ")
if err != nil {
return err
}
return os.WriteFile(filename, data, 0644)
return os.WriteFile(ts.filename, data, 0644)
}

View File

@@ -92,7 +92,7 @@ func (q *QBit) ProcessFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr
for debridTorrent.Status != "downloaded" {
progress := debridTorrent.Progress
q.logger.Printf("%s Download Progress: %.2f%%", debridTorrent.Debrid.GetName(), progress)
time.Sleep(5 * time.Second)
time.Sleep(4 * time.Second)
dbT, err := debridTorrent.Debrid.CheckStatus(debridTorrent, isSymlink)
if err != nil {
q.logger.Printf("Error checking status: %v", err)