From 0743b0122348c07c09a779e206197459ecf3ba6e Mon Sep 17 00:00:00 2001 From: Ben Sarmiento Date: Tue, 21 May 2024 17:07:40 +0200 Subject: [PATCH] Rewrite state machines --- internal/dav/delete.go | 4 ++-- internal/dav/infuse.go | 2 +- internal/dav/listing.go | 4 ++-- internal/dav/rename.go | 2 +- internal/http/listing.go | 2 +- internal/torrent/delete.go | 2 +- internal/torrent/manager.go | 2 +- internal/torrent/refresh.go | 29 +++++++++++++++++--------- internal/torrent/repair.go | 35 +++++++++++++++++++++----------- internal/torrent/states.go | 30 +++++++++++---------------- internal/universal/check.go | 2 +- internal/universal/downloader.go | 10 ++++----- 12 files changed, 69 insertions(+), 55 deletions(-) diff --git a/internal/dav/delete.go b/internal/dav/delete.go index 7a239cf..3455107 100644 --- a/internal/dav/delete.go +++ b/internal/dav/delete.go @@ -30,14 +30,14 @@ 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.State.Is("ok") { + if !ok || !file.State.Is("ok_file") { return fmt.Errorf("cannot find file %s", fileName) } dirCfg := torMgr.Config.(*config.ZurgConfigV1).GetDirectoryConfig(directory) if dirCfg.OnlyShowTheBiggestFile { torMgr.Delete(torrentName, true) } else { - err := file.State.Event(context.Background(), "delete") + err := file.State.Event(context.Background(), "delete_file") if err != nil { return fmt.Errorf("cannot delete file %s: %v", fileName, err) } diff --git a/internal/dav/infuse.go b/internal/dav/infuse.go index efa27f4..4357851 100644 --- a/internal/dav/infuse.go +++ b/internal/dav/infuse.go @@ -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.State.Is("ok") { + if !file.State.Is("ok_file") { continue } if dirCfg.OnlyShowTheBiggestFile && file.Bytes < biggestFileSize { diff --git a/internal/dav/listing.go b/internal/dav/listing.go index 08075e5..9e3c25b 100644 --- a/internal/dav/listing.go +++ b/internal/dav/listing.go @@ -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.State.Is("ok") { + if !file.State.Is("ok_file") { 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.State.Is("ok") { + if !ok || !file.State.Is("ok_file") { return nil, fmt.Errorf("cannot find file %s", fileName) } diff --git a/internal/dav/rename.go b/internal/dav/rename.go index 0413d56..89bb2b8 100644 --- a/internal/dav/rename.go +++ b/internal/dav/rename.go @@ -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.State.Is("ok") { + if !ok || !file.State.Is("ok_file") { return fmt.Errorf("cannot find file %s", fileName) } oldName := torMgr.GetPath(file) diff --git a/internal/http/listing.go b/internal/http/listing.go index e2ca243..668f98c 100644 --- a/internal/http/listing.go +++ b/internal/http/listing.go @@ -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.State.Is("ok") { + if !file.State.Is("ok_file") { continue } if dirCfg.OnlyShowTheBiggestFile && file.Bytes < biggestFileSize { diff --git a/internal/torrent/delete.go b/internal/torrent/delete.go index abdffde..3c08494 100644 --- a/internal/torrent/delete.go +++ b/internal/torrent/delete.go @@ -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.State.Is("deleted") { + if file.State.Is("deleted_file") { deletedIDs = append(deletedIDs, file.ID) } }) diff --git a/internal/torrent/manager.go b/internal/torrent/manager.go index 66b61a3..cb02f4d 100644 --- a/internal/torrent/manager.go +++ b/internal/torrent/manager.go @@ -104,7 +104,7 @@ func (t *TorrentManager) UnrestrictLinkUntilOk(link string) *realdebrid.Download } func (t *TorrentManager) UnrestrictFileUntilOk(file *File) *realdebrid.Download { - if !file.State.Is("ok") { + if !file.State.Is("ok_file") { return nil } return t.UnrestrictLinkUntilOk(file.Link) diff --git a/internal/torrent/refresh.go b/internal/torrent/refresh.go index 3f56149..57f3cf7 100644 --- a/internal/torrent/refresh.go +++ b/internal/torrent/refresh.go @@ -1,6 +1,7 @@ package torrent import ( + "context" "fmt" "path/filepath" "strings" @@ -130,12 +131,10 @@ func (t *TorrentManager) refreshTorrents(isInitialRun bool) []string { if t.Config.EnableRepair() { if isInitialRun { t.removeExpiredFixers(instances) - t.processFixers(instances) - } else { - t.workerPool.Submit(func() { - t.processFixers(instances) - }) } + t.workerPool.Submit(func() { + t.processFixers(instances) + }) } return updatedPaths @@ -198,7 +197,7 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent { OriginalName: info.OriginalName, Added: info.Added, Hash: info.Hash, - State: NewTorrentState("ok"), + State: NewTorrentState("broken_torrent"), } // SelectedFiles is a subset of Files with only the selected ones @@ -214,16 +213,23 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent { File: file, Ended: info.Ended, Link: "", // no link yet, consider it broken - State: NewFileState("broken"), + State: NewFileState("broken_file"), }) } if len(selectedFiles) == len(info.Links) { // all links are still intact! good! for i, file := range selectedFiles { file.Link = info.Links[i] - file.State.SetState("ok") + err := file.State.Event(context.Background(), "repair_file") + if err != nil { + t.log.Warnf("Cannot repair file %s: %v", file.Path, err) + } } torrent.UnassignedLinks = mapset.NewSet[string]() + err := torrent.State.Event(context.Background(), "repair_torrent") + if err != nil { + t.log.Warnf("Cannot repair torrent %s: %v", torrent.Hash, err) + } } else { torrent.UnassignedLinks = mapset.NewSet[string](info.Links...) } @@ -315,9 +321,12 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) *Torrent { older.SelectedFiles.IterCb(func(key string, olderFile *File) { if !mainTorrent.SelectedFiles.Has(key) { mainTorrent.SelectedFiles.Set(key, olderFile) - } else if olderFile.State.Is("deleted") { + } else if olderFile.State.Is("deleted_file") { newerFile, _ := mainTorrent.SelectedFiles.Get(key) - newerFile.State.SetState("deleted") + err := newerFile.State.Event(context.Background(), "delete_file") + if err != nil { + t.log.Warnf("Cannot delete file %s: %v", key, err) + } } }) t.CheckDeletedStatus(&mainTorrent) diff --git a/internal/torrent/repair.go b/internal/torrent/repair.go index 58ff7c0..ae5a2ef 100644 --- a/internal/torrent/repair.go +++ b/internal/torrent/repair.go @@ -101,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.State.Is("broken") { + if file.State.Is("broken_file") { brokenFileIDs.Add(file.ID) } }) @@ -257,7 +257,6 @@ func (t *TorrentManager) assignUnassignedLinks(torrent *Torrent) bool { unrestrict := t.UnrestrictLinkUntilOk(link) if unrestrict == nil { expiredCount++ - // newUnassignedLinks.Set(link, nil) return false // next } @@ -265,9 +264,13 @@ func (t *TorrentManager) assignUnassignedLinks(torrent *Torrent) bool { assigned := false torrent.SelectedFiles.IterCb(func(_ string, file *File) { // 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)) { + if !assigned && file.State.Is("broken_file") && ((unrestrict.Filesize > 1_000_000 && file.Bytes == unrestrict.Filesize) || strings.HasSuffix(strings.ToLower(file.Path), strings.ToLower(unrestrict.Filename))) { file.Link = link - file.State.SetState("ok") + err := file.State.Event(context.Background(), "repair_done_file") + if err != nil { + t.log.Errorf("Failed to mark file %s as repaired: %v", file.Path, err) + return + } assigned = true assignedCount++ } @@ -290,6 +293,10 @@ func (t *TorrentManager) assignUnassignedLinks(torrent *Torrent) bool { return false }) + t.log.Debugf("Assigned %d links to the %d selected files of torrent %s", assignedCount, torrent.SelectedFiles.Count(), t.GetKey(torrent)) + t.log.Debugf("Expired %d links to the %d selected files of torrent %s", expiredCount, torrent.SelectedFiles.Count(), t.GetKey(torrent)) + t.log.Debugf("Rar'ed %d links to the %d selected files of torrent %s", rarCount, torrent.SelectedFiles.Count(), t.GetKey(torrent)) + t.log.Debugf("Unassigned %d links to the %d selected files of torrent %s", newUnassignedLinks.Count(), torrent.SelectedFiles.Count(), t.GetKey(torrent)) if assignedCount == 0 && rarCount == 1 { // this is a rar'ed torrent, nothing we can do @@ -311,7 +318,7 @@ func (t *TorrentManager) assignUnassignedLinks(torrent *Torrent) bool { }, Ended: torrent.Added, Link: unassigned.Link, - State: NewFileState("ok"), + State: NewFileState("ok_file"), } torrent.SelectedFiles.Set(unassigned.Filename, newFile) }) @@ -473,11 +480,11 @@ func (t *TorrentManager) canCapacityHandle() bool { } func (t *TorrentManager) markAsUnplayable(torrent *Torrent, reason string) { - if torrent.State.Is("unplayable") { + if torrent.State.Is("unplayable_torrent") { return } t.log.Warnf("Marking torrent %s as unplayable - %s", t.GetKey(torrent), reason) - err := torrent.State.Event(context.Background(), "mark_as_unplayable") + err := torrent.State.Event(context.Background(), "mark_as_unplayable_torrent") if err != nil { t.log.Errorf("Failed to mark torrent %s as unplayable: %v", t.GetKey(torrent), err) return @@ -500,7 +507,7 @@ func getBrokenFiles(torrent *Torrent) ([]*File, bool) { var brokenFiles []*File allBroken := true torrent.SelectedFiles.IterCb(func(_ string, file *File) { - if file.State.Is("broken") { + if file.State.Is("broken_file") { brokenFiles = append(brokenFiles, file) } else { allBroken = false @@ -520,18 +527,22 @@ func (t *TorrentManager) isStillBroken(info *realdebrid.TorrentInfo, brokenFiles selectedFiles = append(selectedFiles, &File{ File: file, Ended: info.Ended, - Link: "", // no link yet - State: NewFileState("broken"), + Link: "", + State: NewFileState("broken_file"), }) } if len(selectedFiles) == len(info.Links) { // all links are still intact! good! for i, file := range selectedFiles { file.Link = info.Links[i] - file.State.SetState("ok") + err := file.State.Event(context.Background(), "repair_file") + if err != nil { + t.log.Errorf("Failed to mark file %s as repaired: %v", file.Path, err) + return true + } } } else { - // if we can't assign links, it's still broken + // if we can't assign links then it's still broken return true } diff --git a/internal/torrent/states.go b/internal/torrent/states.go index 1050221..c214554 100644 --- a/internal/torrent/states.go +++ b/internal/torrent/states.go @@ -5,36 +5,30 @@ import ( ) func NewTorrentState(initial string) *fsm.FSM { - // ok - // broken - // under_repair - // deleted - // unplayable + // ok_torrent 1 + // broken_torrent 1 + // unplayable_torrent 1 return fsm.NewFSM( initial, fsm.Events{ - {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", "unplayable"}, Dst: "deleted"}, - {Name: "mark_as_unplayable", Src: []string{"ok", "under_repair"}, Dst: "unplayable"}, + {Name: "break_torrent", Src: []string{"ok_torrent", "unplayable_torrent"}, Dst: "broken_torrent"}, + {Name: "repair_torrent", Src: []string{"broken_torrent"}, Dst: "ok_torrent"}, + {Name: "mark_as_unplayable_torrent", Src: []string{"ok_torrent"}, Dst: "unplayable_torrent"}, }, fsm.Callbacks{}, ) } func NewFileState(initial string) *fsm.FSM { - // ok - // broken - // under_repair - // deleted + // ok_file 13 + // broken_file 5 + // deleted_file 3 return fsm.NewFSM( initial, fsm.Events{ - {Name: "break", Src: []string{"ok"}, 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: "break_file", Src: []string{"ok_file"}, Dst: "broken_file"}, + {Name: "repair_file", Src: []string{"broken_file"}, Dst: "ok_file"}, + {Name: "delete_file", Src: []string{"ok_file", "broken_file"}, Dst: "deleted_file"}, }, fsm.Callbacks{}, ) diff --git a/internal/universal/check.go b/internal/universal/check.go index 6f955f8..a43d639 100644 --- a/internal/universal/check.go +++ b/internal/universal/check.go @@ -27,7 +27,7 @@ func CheckFile(directory, torrentName, fileName string, w http.ResponseWriter, r } file, ok := torrent.SelectedFiles.Get(fileName) - if !ok || !file.State.Is("ok") { + if !ok || !file.State.Is("ok_file") { log.Warnf("Cannot find file %s from path %s", fileName, req.URL.Path) http.Error(w, "File not found", http.StatusNotFound) return diff --git a/internal/universal/downloader.go b/internal/universal/downloader.go index cf81a4d..9b018c3 100644 --- a/internal/universal/downloader.go +++ b/internal/universal/downloader.go @@ -50,20 +50,20 @@ func (dl *Downloader) DownloadFile( } file, ok := torrent.SelectedFiles.Get(fileName) - if !ok || !file.State.Is("ok") { + if !ok || !file.State.Is("ok_file") { log.Warnf("Cannot find file %s from path %s", fileName, req.URL.Path) http.Error(resp, "File not found", http.StatusNotFound) return } - if !file.State.Is("ok") { + if !file.State.Is("ok_file") { http.Error(resp, "File is not available", http.StatusNotFound) return } unrestrict := torMgr.UnrestrictFileUntilOk(file) if unrestrict == nil { - err := file.State.Event(context.Background(), "break") + err := file.State.Event(context.Background(), "break_file") if err != nil { log.Errorf("File %s is stale: %v", fileName, err) http.Error(resp, "File is stale, please try again", http.StatusLocked) @@ -154,7 +154,7 @@ func (dl *Downloader) streamFileToResponse( downloadResp, err := dl.client.Do(dlReq) if err != nil { if file != nil && unrestrict.Streamable == 1 { - err := file.State.Event(context.Background(), "break") + err := file.State.Event(context.Background(), "break_file") if err != nil { log.Errorf("File %s is stale: %v", file.Path, err) http.Error(resp, "File is stale, please try again", http.StatusLocked) @@ -177,7 +177,7 @@ func (dl *Downloader) streamFileToResponse( // Check if the download was not successful if downloadResp.StatusCode/100 != 2 { if file != nil && unrestrict.Streamable == 1 { - err := file.State.Event(context.Background(), "break") + err := file.State.Event(context.Background(), "break_file") if err != nil { log.Errorf("File %s is stale: %v", file.Path, err) http.Error(resp, "File is stale, please try again", http.StatusLocked)