Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f5b1f100e2 | |||
| 4cf8246550 | |||
| 3f96382e76 | |||
| 43a94118f4 | |||
| 38d59cb01d | |||
| b306248db6 | |||
| 3a5289cb1d |
55
.gitea/workflows/ci.yml
Normal file
55
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: CI/CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master, main]
|
||||||
|
pull_request:
|
||||||
|
branches: [master, main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
name: Build & Push Docker Image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||||
|
outputs:
|
||||||
|
image_tag: ${{ steps.meta.outputs.tag }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Generate image metadata
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||||
|
echo "tag=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||||
|
echo "Image will be tagged: ${SHORT_SHA}"
|
||||||
|
|
||||||
|
- name: Login to registry
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.johnogle.info -u ${{ secrets.REGISTRY_USERNAME }} --password-stdin
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
registry.johnogle.info/johno/decypharr:${{ steps.meta.outputs.tag }}
|
||||||
|
registry.johnogle.info/johno/decypharr:latest
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
build-args: |
|
||||||
|
VERSION=${{ steps.meta.outputs.tag }}
|
||||||
|
CHANNEL=dev
|
||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -19,4 +19,11 @@ auth.json
|
|||||||
node_modules/
|
node_modules/
|
||||||
.venv/
|
.venv/
|
||||||
.stignore
|
.stignore
|
||||||
.stfolder/**
|
.stfolder/**
|
||||||
|
|
||||||
|
# Gas Town (added by gt)
|
||||||
|
.runtime/
|
||||||
|
.claude/
|
||||||
|
.logs/
|
||||||
|
.beads/
|
||||||
|
state.json
|
||||||
|
|||||||
@@ -171,13 +171,24 @@ func (tb *Torbox) SubmitMagnet(torrent *types.Torrent) (*types.Torrent, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tb *Torbox) getTorboxStatus(status string, finished bool) string {
|
func (tb *Torbox) getTorboxStatus(status string, finished bool) string {
|
||||||
if finished {
|
// Log raw values for debugging
|
||||||
|
tb.logger.Debug().
|
||||||
|
Str("download_state", status).
|
||||||
|
Bool("download_finished", finished).
|
||||||
|
Msg("getTorboxStatus called")
|
||||||
|
|
||||||
|
// For cached/completed torrents, content is immediately available even if
|
||||||
|
// DownloadFinished=false (no download actually happened - it was already cached)
|
||||||
|
// Use case-insensitive comparison for safety
|
||||||
|
statusLower := strings.ToLower(status)
|
||||||
|
if finished || statusLower == "cached" || statusLower == "completed" {
|
||||||
return "downloaded"
|
return "downloaded"
|
||||||
}
|
}
|
||||||
downloading := []string{"completed", "cached", "paused", "downloading", "uploading",
|
downloading := []string{"paused", "downloading", "uploading",
|
||||||
"checkingResumeData", "metaDL", "pausedUP", "queuedUP", "checkingUP",
|
"checkingResumeData", "metaDL", "pausedUP", "queuedUP", "checkingUP",
|
||||||
"forcedUP", "allocating", "downloading", "metaDL", "pausedDL",
|
"forcedUP", "allocating", "downloading", "metaDL", "pausedDL",
|
||||||
"queuedDL", "checkingDL", "forcedDL", "checkingResumeData", "moving"}
|
"queuedDL", "checkingDL", "forcedDL", "checkingResumeData", "moving",
|
||||||
|
"checking"}
|
||||||
|
|
||||||
var determinedStatus string
|
var determinedStatus string
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ func (s *Store) processFiles(torrent *Torrent, debridTorrent *types.Torrent, imp
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.logger.Debug().
|
||||||
|
Str("torrent_name", debridTorrent.Name).
|
||||||
|
Str("debrid_status", debridTorrent.Status).
|
||||||
|
Str("torrent_state", torrent.State).
|
||||||
|
Msg("processFiles started")
|
||||||
|
|
||||||
deb := s.debrid.Debrid(debridTorrent.Debrid)
|
deb := s.debrid.Debrid(debridTorrent.Debrid)
|
||||||
client := deb.Client()
|
client := deb.Client()
|
||||||
downloadingStatuses := client.GetDownloadingStatus()
|
downloadingStatuses := client.GetDownloadingStatus()
|
||||||
@@ -96,6 +102,12 @@ func (s *Store) processFiles(torrent *Torrent, debridTorrent *types.Torrent, imp
|
|||||||
nextInterval := min(s.refreshInterval*2, 30*time.Second)
|
nextInterval := min(s.refreshInterval*2, 30*time.Second)
|
||||||
backoff.Reset(nextInterval)
|
backoff.Reset(nextInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.logger.Debug().
|
||||||
|
Str("torrent_name", debridTorrent.Name).
|
||||||
|
Str("debrid_status", debridTorrent.Status).
|
||||||
|
Msg("Download loop exited, proceeding to post-processing")
|
||||||
|
|
||||||
var torrentSymlinkPath, torrentRclonePath string
|
var torrentSymlinkPath, torrentRclonePath string
|
||||||
debridTorrent.Arr = _arr
|
debridTorrent.Arr = _arr
|
||||||
|
|
||||||
@@ -114,8 +126,28 @@ func (s *Store) processFiles(torrent *Torrent, debridTorrent *types.Torrent, imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSuccess := func(torrentSymlinkPath string) {
|
onSuccess := func(torrentSymlinkPath string) {
|
||||||
|
s.logger.Debug().
|
||||||
|
Str("torrent_name", debridTorrent.Name).
|
||||||
|
Str("symlink_path", torrentSymlinkPath).
|
||||||
|
Str("debrid_status", debridTorrent.Status).
|
||||||
|
Msg("onSuccess called")
|
||||||
torrent.TorrentPath = torrentSymlinkPath
|
torrent.TorrentPath = torrentSymlinkPath
|
||||||
s.updateTorrent(torrent, debridTorrent)
|
s.updateTorrent(torrent, debridTorrent)
|
||||||
|
|
||||||
|
// Safety check: ensure state is set correctly after updateTorrent
|
||||||
|
// This catches any edge cases where updateTorrent doesn't set the state
|
||||||
|
if torrent.State != "pausedUP" && torrentSymlinkPath != "" {
|
||||||
|
s.logger.Warn().
|
||||||
|
Str("torrent_name", debridTorrent.Name).
|
||||||
|
Str("current_state", torrent.State).
|
||||||
|
Str("debrid_status", debridTorrent.Status).
|
||||||
|
Msg("State not pausedUP after updateTorrent, forcing state update")
|
||||||
|
torrent.State = "pausedUP"
|
||||||
|
torrent.Progress = 1.0
|
||||||
|
torrent.AmountLeft = 0
|
||||||
|
s.torrents.Update(torrent)
|
||||||
|
}
|
||||||
|
|
||||||
s.logger.Info().Msgf("Adding %s took %s", debridTorrent.Name, time.Since(timer))
|
s.logger.Info().Msgf("Adding %s took %s", debridTorrent.Name, time.Since(timer))
|
||||||
|
|
||||||
go importReq.markAsCompleted(torrent, debridTorrent) // Mark the import request as completed, send callback if needed
|
go importReq.markAsCompleted(torrent, debridTorrent) // Mark the import request as completed, send callback if needed
|
||||||
@@ -201,7 +233,12 @@ func (s *Store) processFiles(torrent *Torrent, debridTorrent *types.Torrent, imp
|
|||||||
if torrentSymlinkPath == "" {
|
if torrentSymlinkPath == "" {
|
||||||
err = fmt.Errorf("symlink path is empty for %s", debridTorrent.Name)
|
err = fmt.Errorf("symlink path is empty for %s", debridTorrent.Name)
|
||||||
onFailed(err)
|
onFailed(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
s.logger.Debug().
|
||||||
|
Str("torrent_name", debridTorrent.Name).
|
||||||
|
Str("symlink_path", torrentSymlinkPath).
|
||||||
|
Msg("Symlink processing complete, calling onSuccess")
|
||||||
onSuccess(torrentSymlinkPath)
|
onSuccess(torrentSymlinkPath)
|
||||||
return
|
return
|
||||||
case "download":
|
case "download":
|
||||||
@@ -270,6 +307,12 @@ func (s *Store) partialTorrentUpdate(t *Torrent, debridTorrent *types.Torrent) *
|
|||||||
if math.IsNaN(progress) || math.IsInf(progress, 0) {
|
if math.IsNaN(progress) || math.IsInf(progress, 0) {
|
||||||
progress = 0
|
progress = 0
|
||||||
}
|
}
|
||||||
|
// When debrid reports download complete, force progress to 100% to ensure
|
||||||
|
// IsReady() returns true. This fixes a race condition where TorBox can report
|
||||||
|
// DownloadFinished=true but Progress < 1.0, causing state to stay "downloading".
|
||||||
|
if debridTorrent.Status == "downloaded" {
|
||||||
|
progress = 1.0
|
||||||
|
}
|
||||||
sizeCompleted := int64(float64(totalSize) * progress)
|
sizeCompleted := int64(float64(totalSize) * progress)
|
||||||
|
|
||||||
var speed int64
|
var speed int64
|
||||||
@@ -314,6 +357,13 @@ func (s *Store) updateTorrent(t *Torrent, debridTorrent *types.Torrent) *Torrent
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.logger.Debug().
|
||||||
|
Str("torrent_name", t.Name).
|
||||||
|
Str("debrid_status", debridTorrent.Status).
|
||||||
|
Str("torrent_path", t.TorrentPath).
|
||||||
|
Str("current_state", t.State).
|
||||||
|
Msg("updateTorrent called")
|
||||||
|
|
||||||
if debridClient := s.debrid.Clients()[debridTorrent.Debrid]; debridClient != nil {
|
if debridClient := s.debrid.Clients()[debridTorrent.Debrid]; debridClient != nil {
|
||||||
if debridTorrent.Status != "downloaded" {
|
if debridTorrent.Status != "downloaded" {
|
||||||
_ = debridClient.UpdateTorrent(debridTorrent)
|
_ = debridClient.UpdateTorrent(debridTorrent)
|
||||||
@@ -322,7 +372,34 @@ func (s *Store) updateTorrent(t *Torrent, debridTorrent *types.Torrent) *Torrent
|
|||||||
t = s.partialTorrentUpdate(t, debridTorrent)
|
t = s.partialTorrentUpdate(t, debridTorrent)
|
||||||
t.ContentPath = t.TorrentPath
|
t.ContentPath = t.TorrentPath
|
||||||
|
|
||||||
|
// When debrid reports download complete and we have a path, mark as ready.
|
||||||
|
// This is a direct fix for TorBox where IsReady() might fail due to
|
||||||
|
// progress/AmountLeft calculation issues.
|
||||||
|
if debridTorrent.Status == "downloaded" && t.TorrentPath != "" {
|
||||||
|
s.logger.Debug().
|
||||||
|
Str("torrent_name", t.Name).
|
||||||
|
Msg("Setting state to pausedUP (downloaded + path)")
|
||||||
|
t.State = "pausedUP"
|
||||||
|
t.Progress = 1.0
|
||||||
|
t.AmountLeft = 0
|
||||||
|
s.torrents.Update(t)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log why the primary condition failed
|
||||||
|
s.logger.Debug().
|
||||||
|
Str("torrent_name", t.Name).
|
||||||
|
Str("debrid_status", debridTorrent.Status).
|
||||||
|
Str("torrent_path", t.TorrentPath).
|
||||||
|
Bool("is_ready", t.IsReady()).
|
||||||
|
Float64("progress", t.Progress).
|
||||||
|
Int64("amount_left", t.AmountLeft).
|
||||||
|
Msg("Primary pausedUP condition failed, checking IsReady")
|
||||||
|
|
||||||
if t.IsReady() {
|
if t.IsReady() {
|
||||||
|
s.logger.Debug().
|
||||||
|
Str("torrent_name", t.Name).
|
||||||
|
Msg("Setting state to pausedUP (IsReady=true)")
|
||||||
t.State = "pausedUP"
|
t.State = "pausedUP"
|
||||||
s.torrents.Update(t)
|
s.torrents.Update(t)
|
||||||
return t
|
return t
|
||||||
|
|||||||
Reference in New Issue
Block a user