Implement new fsm
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package dav
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/debridmediamanager/zurg/internal/config"
|
||||
@@ -29,14 +30,17 @@ func HandleDeleteFile(directory, torrentName, fileName string, torMgr *torrent.T
|
||||
return fmt.Errorf("cannot find torrent %s", torrentName)
|
||||
}
|
||||
file, ok := torrent.SelectedFiles.Get(fileName)
|
||||
if !ok || file.IsDeleted {
|
||||
if !ok || !file.State.Is("ok") {
|
||||
return fmt.Errorf("cannot find file %s", fileName)
|
||||
}
|
||||
dirCfg := torMgr.Config.(*config.ZurgConfigV1).GetDirectoryConfig(directory)
|
||||
if dirCfg.OnlyShowTheBiggestFile {
|
||||
torMgr.Delete(torrentName, true)
|
||||
} else {
|
||||
file.IsDeleted = true
|
||||
err := file.State.Event(context.Background(), "delete")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete file %s: %v", fileName, err)
|
||||
}
|
||||
if torMgr.CheckDeletedStatus(torrent) {
|
||||
torMgr.Delete(torrentName, true)
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func ServeFilesListForInfuse(directory, torrentName string, torMgr *torrent.Torr
|
||||
sort.Strings(filenames)
|
||||
for _, filename := range filenames {
|
||||
file, _ := tor.SelectedFiles.Get(filename)
|
||||
if file.IsDeleted {
|
||||
if !file.State.Is("ok") {
|
||||
continue
|
||||
}
|
||||
if dirCfg.OnlyShowTheBiggestFile && file.Bytes < biggestFileSize {
|
||||
|
||||
@@ -79,7 +79,7 @@ func ServeFilesList(directory, torrentName string, torMgr *torrent.TorrentManage
|
||||
sort.Strings(filenames)
|
||||
for _, filename := range filenames {
|
||||
file, _ := tor.SelectedFiles.Get(filename)
|
||||
if file.IsDeleted {
|
||||
if !file.State.Is("ok") {
|
||||
continue
|
||||
}
|
||||
if dirCfg.OnlyShowTheBiggestFile && file.Bytes < biggestFileSize {
|
||||
@@ -107,7 +107,7 @@ func HandleSingleFile(directory, torrentName, fileName string, torMgr *torrent.T
|
||||
return nil, fmt.Errorf("cannot find torrent %s", torrentName)
|
||||
}
|
||||
file, ok := tor.SelectedFiles.Get(fileName)
|
||||
if !ok || file.IsDeleted {
|
||||
if !ok || !file.State.Is("ok") {
|
||||
return nil, fmt.Errorf("cannot find file %s", fileName)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func HandleRenameFile(directory, torrentName, fileName, newName string, torMgr *
|
||||
return fmt.Errorf("cannot find torrent %s", torrentName)
|
||||
}
|
||||
file, ok := torrent.SelectedFiles.Get(fileName)
|
||||
if !ok || file.IsDeleted {
|
||||
if !ok || !file.State.Is("ok") {
|
||||
return fmt.Errorf("cannot find file %s", fileName)
|
||||
}
|
||||
oldName := torMgr.GetPath(file)
|
||||
|
||||
@@ -75,7 +75,7 @@ func ServeFilesList(directory, torrentName string, torMgr *torrent.TorrentManage
|
||||
sort.Strings(filenames)
|
||||
for _, filename := range filenames {
|
||||
file, _ := tor.SelectedFiles.Get(filename)
|
||||
if file.IsDeleted {
|
||||
if !file.State.Is("ok") {
|
||||
continue
|
||||
}
|
||||
if dirCfg.OnlyShowTheBiggestFile && file.Bytes < biggestFileSize {
|
||||
|
||||
@@ -6,7 +6,7 @@ import cmap "github.com/orcaman/concurrent-map/v2"
|
||||
func (t *TorrentManager) CheckDeletedStatus(torrent *Torrent) bool {
|
||||
var deletedIDs []int
|
||||
torrent.SelectedFiles.IterCb(func(_ string, file *File) {
|
||||
if file.IsDeleted {
|
||||
if file.State.Is("deleted") {
|
||||
deletedIDs = append(deletedIDs, file.ID)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package torrent
|
||||
|
||||
import (
|
||||
stdjson "encoding/json"
|
||||
|
||||
"github.com/debridmediamanager/zurg/pkg/realdebrid"
|
||||
"github.com/looplab/fsm"
|
||||
)
|
||||
@@ -10,9 +12,39 @@ type File struct {
|
||||
Link string `json:"Link"`
|
||||
Ended string `json:"Ended"`
|
||||
|
||||
IsBroken bool `json:"IsBroken"`
|
||||
IsDeleted bool `json:"IsDeleted"`
|
||||
State *fsm.FSM `json:"-"`
|
||||
State *fsm.FSM `json:"-"`
|
||||
|
||||
Rename string `json:"Rename"`
|
||||
}
|
||||
|
||||
func (f *File) MarshalJSON() ([]byte, error) {
|
||||
type Alias File
|
||||
temp := &struct {
|
||||
StateJson stdjson.RawMessage `json:"State"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(f),
|
||||
}
|
||||
|
||||
temp.StateJson = []byte(`"` + f.State.Current() + `"`)
|
||||
|
||||
return json.Marshal(temp)
|
||||
}
|
||||
|
||||
func (f *File) UnmarshalJSON(data []byte) error {
|
||||
type Alias File
|
||||
temp := &struct {
|
||||
StateJson string `json:"State"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(f),
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.State = NewFileState(temp.StateJson)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func (t *TorrentManager) UnrestrictLinkUntilOk(link string) *realdebrid.Download
|
||||
}
|
||||
|
||||
func (t *TorrentManager) UnrestrictFileUntilOk(file *File) *realdebrid.Download {
|
||||
if file.IsBroken || file.IsDeleted {
|
||||
if !file.State.Is("ok") {
|
||||
return nil
|
||||
}
|
||||
return t.UnrestrictLinkUntilOk(file.Link)
|
||||
|
||||
@@ -159,6 +159,7 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
|
||||
OriginalName: info.OriginalName,
|
||||
Added: info.Added,
|
||||
Hash: info.Hash,
|
||||
State: NewTorrentState("ok"),
|
||||
}
|
||||
|
||||
// SelectedFiles is a subset of Files with only the selected ones
|
||||
@@ -171,17 +172,17 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent {
|
||||
continue
|
||||
}
|
||||
selectedFiles = append(selectedFiles, &File{
|
||||
File: file,
|
||||
Ended: info.Ended,
|
||||
Link: "", // no link yet, consider it broken
|
||||
IsBroken: true,
|
||||
File: file,
|
||||
Ended: info.Ended,
|
||||
Link: "", // no link yet, consider it broken
|
||||
State: NewFileState("broken"),
|
||||
})
|
||||
}
|
||||
if len(selectedFiles) == len(info.Links) {
|
||||
// all links are still intact! good!
|
||||
for i, file := range selectedFiles {
|
||||
file.Link = info.Links[i]
|
||||
file.IsBroken = false
|
||||
file.State.SetState("ok")
|
||||
}
|
||||
torrent.UnassignedLinks = mapset.NewSet[string]()
|
||||
} else {
|
||||
@@ -254,6 +255,8 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) *Torrent {
|
||||
Components: mergedComponents,
|
||||
UnassignedLinks: newer.UnassignedLinks.Union(older.UnassignedLinks),
|
||||
UnrepairableReason: newer.UnrepairableReason,
|
||||
|
||||
State: older.State,
|
||||
}
|
||||
|
||||
// unrepairable reason
|
||||
@@ -268,32 +271,17 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) *Torrent {
|
||||
// 3. empty - the file is not available
|
||||
mainTorrent.SelectedFiles = cmap.New[*File]()
|
||||
newer.SelectedFiles.IterCb(func(key string, newerFile *File) {
|
||||
if !newerFile.IsBroken {
|
||||
mainTorrent.SelectedFiles.Set(key, newerFile)
|
||||
return
|
||||
}
|
||||
olderFile, ok := older.SelectedFiles.Get(key)
|
||||
if ok && !olderFile.IsBroken {
|
||||
mainTorrent.SelectedFiles.Set(key, olderFile)
|
||||
return
|
||||
}
|
||||
mainTorrent.SelectedFiles.Set(key, newerFile)
|
||||
})
|
||||
inconsistentDeletes := false
|
||||
older.SelectedFiles.IterCb(func(key string, olderFile *File) {
|
||||
if !mainTorrent.SelectedFiles.Has(key) {
|
||||
mainTorrent.SelectedFiles.Set(key, olderFile)
|
||||
return
|
||||
}
|
||||
newerFile, _ := mainTorrent.SelectedFiles.Get(key)
|
||||
if olderFile.IsDeleted && !newerFile.IsDeleted {
|
||||
newerFile.IsDeleted = true
|
||||
inconsistentDeletes = true
|
||||
} else if olderFile.State.Is("deleted") {
|
||||
newerFile, _ := mainTorrent.SelectedFiles.Get(key)
|
||||
newerFile.State.SetState("deleted")
|
||||
}
|
||||
})
|
||||
if inconsistentDeletes {
|
||||
t.CheckDeletedStatus(&mainTorrent)
|
||||
}
|
||||
t.CheckDeletedStatus(&mainTorrent)
|
||||
|
||||
return &mainTorrent
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package torrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
@@ -100,7 +101,7 @@ func (t *TorrentManager) repairAll(torrent *Torrent) {
|
||||
// check 1: for broken files
|
||||
brokenFileIDs := mapset.NewSet[int]()
|
||||
torrent.SelectedFiles.IterCb(func(_ string, file *File) {
|
||||
if file.IsBroken && !file.IsDeleted {
|
||||
if file.State.Is("broken") {
|
||||
brokenFileIDs.Add(file.ID)
|
||||
}
|
||||
})
|
||||
@@ -266,7 +267,7 @@ func (t *TorrentManager) assignUnassignedLinks(torrent *Torrent) bool {
|
||||
// base it on size because why not?
|
||||
if (unrestrict.Filesize > 1_000_000 && file.Bytes == unrestrict.Filesize) || strings.HasSuffix(strings.ToLower(file.Path), strings.ToLower(unrestrict.Filename)) {
|
||||
file.Link = link
|
||||
file.IsBroken = false
|
||||
file.State.SetState("ok")
|
||||
assigned = true
|
||||
assignedCount++
|
||||
}
|
||||
@@ -308,9 +309,9 @@ func (t *TorrentManager) assignUnassignedLinks(torrent *Torrent) bool {
|
||||
Bytes: unassigned.Filesize,
|
||||
Selected: 0,
|
||||
},
|
||||
Ended: torrent.Added,
|
||||
Link: unassigned.Link,
|
||||
IsBroken: false,
|
||||
Ended: torrent.Added,
|
||||
Link: unassigned.Link,
|
||||
State: NewFileState("ok"),
|
||||
}
|
||||
torrent.SelectedFiles.Set(unassigned.Filename, newFile)
|
||||
})
|
||||
@@ -473,6 +474,11 @@ func (t *TorrentManager) canCapacityHandle() bool {
|
||||
|
||||
func (t *TorrentManager) markAsUnplayable(torrent *Torrent, reason string) {
|
||||
t.log.Warnf("Marking torrent %s as unplayable - %s", t.GetKey(torrent), reason)
|
||||
err := torrent.State.Event(context.Background(), "mark_as_unplayable")
|
||||
if err != nil {
|
||||
t.log.Errorf("Failed to mark torrent %s as unplayable: %v", t.GetKey(torrent), err)
|
||||
return
|
||||
}
|
||||
t.DirectoryMap.IterCb(func(directory string, torrents cmap.ConcurrentMap[string, *Torrent]) {
|
||||
torrents.Remove(t.GetKey(torrent))
|
||||
})
|
||||
@@ -491,7 +497,7 @@ func getBrokenFiles(torrent *Torrent) ([]*File, bool) {
|
||||
var brokenFiles []*File
|
||||
allBroken := true
|
||||
torrent.SelectedFiles.IterCb(func(_ string, file *File) {
|
||||
if file.IsBroken && !file.IsDeleted {
|
||||
if file.State.Is("broken") {
|
||||
brokenFiles = append(brokenFiles, file)
|
||||
} else {
|
||||
allBroken = false
|
||||
@@ -509,17 +515,17 @@ func (t *TorrentManager) isStillBroken(info *realdebrid.TorrentInfo, brokenFiles
|
||||
continue
|
||||
}
|
||||
selectedFiles = append(selectedFiles, &File{
|
||||
File: file,
|
||||
Ended: info.Ended,
|
||||
Link: "", // no link yet
|
||||
IsBroken: true,
|
||||
File: file,
|
||||
Ended: info.Ended,
|
||||
Link: "", // no link yet
|
||||
State: NewFileState("broken"),
|
||||
})
|
||||
}
|
||||
if len(selectedFiles) == len(info.Links) {
|
||||
// all links are still intact! good!
|
||||
for i, file := range selectedFiles {
|
||||
file.Link = info.Links[i]
|
||||
file.IsBroken = false
|
||||
file.State.SetState("ok")
|
||||
}
|
||||
} else {
|
||||
// if we can't assign links, it's still broken
|
||||
|
||||
@@ -4,22 +4,32 @@ import (
|
||||
"github.com/looplab/fsm"
|
||||
)
|
||||
|
||||
func NewFileState() *fsm.FSM {
|
||||
func NewTorrentState(initial string) *fsm.FSM {
|
||||
// ok
|
||||
// broken
|
||||
// under_repair
|
||||
// deleted
|
||||
// unplayable
|
||||
return fsm.NewFSM(
|
||||
"ok",
|
||||
initial,
|
||||
fsm.Events{
|
||||
{Name: "break", Src: []string{"ok"}, Dst: "broken"},
|
||||
{Name: "break", Src: []string{"ok", "unplayable"}, Dst: "broken"},
|
||||
{Name: "repair", Src: []string{"broken"}, Dst: "under_repair"},
|
||||
{Name: "repair_done", Src: []string{"under_repair"}, Dst: "ok"},
|
||||
{Name: "delete", Src: []string{"ok", "broken", "under_repair"}, Dst: "deleted"},
|
||||
{Name: "delete", Src: []string{"ok", "broken", "under_repair", "unplayable"}, Dst: "deleted"},
|
||||
{Name: "mark_as_unplayable", Src: []string{"ok", "under_repair"}, Dst: "unplayable"},
|
||||
},
|
||||
fsm.Callbacks{},
|
||||
)
|
||||
}
|
||||
|
||||
func NewTorrentState() *fsm.FSM {
|
||||
func NewFileState(initial string) *fsm.FSM {
|
||||
// ok
|
||||
// broken
|
||||
// under_repair
|
||||
// deleted
|
||||
return fsm.NewFSM(
|
||||
"ok",
|
||||
initial,
|
||||
fsm.Events{
|
||||
{Name: "break", Src: []string{"ok"}, Dst: "broken"},
|
||||
{Name: "repair", Src: []string{"broken"}, Dst: "under_repair"},
|
||||
@@ -35,6 +35,7 @@ func (t *Torrent) MarshalJSON() ([]byte, error) {
|
||||
temp := &struct {
|
||||
SelectedFilesJson stdjson.RawMessage `json:"SelectedFiles"`
|
||||
UnassignedLinksJson stdjson.RawMessage `json:"UnassignedLinks"`
|
||||
StateJson stdjson.RawMessage `json:"State"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(t),
|
||||
@@ -53,6 +54,8 @@ func (t *Torrent) MarshalJSON() ([]byte, error) {
|
||||
temp.UnassignedLinksJson = []byte(unassignedLinksStr)
|
||||
}
|
||||
|
||||
temp.StateJson = []byte(`"` + t.State.Current() + `"`)
|
||||
|
||||
return json.Marshal(temp)
|
||||
}
|
||||
|
||||
@@ -61,6 +64,7 @@ func (t *Torrent) UnmarshalJSON(data []byte) error {
|
||||
temp := &struct {
|
||||
SelectedFilesJson stdjson.RawMessage `json:"SelectedFiles"`
|
||||
UnassignedLinksJson stdjson.RawMessage `json:"UnassignedLinks"`
|
||||
StateJson string `json:"State"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(t),
|
||||
@@ -83,6 +87,8 @@ func (t *Torrent) UnmarshalJSON(data []byte) error {
|
||||
t.UnassignedLinks = mapset.NewSet[string]()
|
||||
}
|
||||
|
||||
t.State = NewFileState(temp.StateJson)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func CheckFile(directory, torrentName, fileName string, w http.ResponseWriter, r
|
||||
}
|
||||
|
||||
file, ok := torrent.SelectedFiles.Get(fileName)
|
||||
if !ok || file.IsDeleted {
|
||||
if !ok || !file.State.Is("ok") {
|
||||
log.Warnf("Cannot find file %s from path %s", fileName, req.URL.Path)
|
||||
http.Error(w, "File not found", http.StatusNotFound)
|
||||
return
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package universal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
@@ -49,20 +50,25 @@ func (dl *Downloader) DownloadFile(
|
||||
}
|
||||
|
||||
file, ok := torrent.SelectedFiles.Get(fileName)
|
||||
if !ok || file.IsDeleted {
|
||||
if !ok || !file.State.Is("ok") {
|
||||
log.Warnf("Cannot find file %s from path %s", fileName, req.URL.Path)
|
||||
http.Error(resp, "File not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if file.IsBroken {
|
||||
if !file.State.Is("ok") {
|
||||
http.Error(resp, "File is not available", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
unrestrict := torMgr.UnrestrictFileUntilOk(file)
|
||||
if unrestrict == nil {
|
||||
file.IsBroken = true
|
||||
err := file.State.Event(context.Background(), "break")
|
||||
if err != nil {
|
||||
log.Errorf("File %s is stale: %v", fileName, err)
|
||||
http.Error(resp, "File is stale, please try again", http.StatusLocked)
|
||||
return
|
||||
}
|
||||
if cfg.EnableRepair() {
|
||||
log.Warnf("File %s cannot be unrestricted (link=%s) (repairing...)", fileName, file.Link)
|
||||
torMgr.TriggerRepair(torrent)
|
||||
@@ -148,7 +154,12 @@ func (dl *Downloader) streamFileToResponse(
|
||||
downloadResp, err := dl.client.Do(dlReq)
|
||||
if err != nil {
|
||||
if file != nil && unrestrict.Streamable == 1 {
|
||||
file.IsBroken = true
|
||||
err := file.State.Event(context.Background(), "break")
|
||||
if err != nil {
|
||||
log.Errorf("File %s is stale: %v", file.Path, err)
|
||||
http.Error(resp, "File is stale, please try again", http.StatusLocked)
|
||||
return
|
||||
}
|
||||
if cfg.EnableRepair() && torrent != nil {
|
||||
log.Warnf("Cannot download file %s: %v (repairing...)", unrestrict.Download, err)
|
||||
torMgr.TriggerRepair(torrent)
|
||||
@@ -166,7 +177,12 @@ func (dl *Downloader) streamFileToResponse(
|
||||
// Check if the download was not successful
|
||||
if downloadResp.StatusCode/100 != 2 {
|
||||
if file != nil && unrestrict.Streamable == 1 {
|
||||
file.IsBroken = true
|
||||
err := file.State.Event(context.Background(), "break")
|
||||
if err != nil {
|
||||
log.Errorf("File %s is stale: %v", file.Path, err)
|
||||
http.Error(resp, "File is stale, please try again", http.StatusLocked)
|
||||
return
|
||||
}
|
||||
if cfg.EnableRepair() && torrent != nil {
|
||||
log.Warnf("Received a %s status code for file %s (repairing...)", downloadResp.Status, file.Path)
|
||||
torMgr.TriggerRepair(torrent)
|
||||
|
||||
Reference in New Issue
Block a user