diff --git a/internal/torrent/manager.go b/internal/torrent/manager.go index 8ec2995..de6ee18 100644 --- a/internal/torrent/manager.go +++ b/internal/torrent/manager.go @@ -128,7 +128,7 @@ func (t *TorrentManager) assignedDirectoryCb(tor *Torrent, cb func(string)) { tor.SelectedFiles.IterCb(func(key string, file *File) { filenames = append(filenames, filepath.Base(file.Path)) fileSizes = append(fileSizes, file.Bytes) - if unplayable && utils.IsStreamable(file.Path) { + if !tor.Unfixable && unplayable && utils.IsStreamable(file.Path) { unplayable = false } }) diff --git a/internal/torrent/refresh.go b/internal/torrent/refresh.go index aae0aed..dd430c7 100644 --- a/internal/torrent/refresh.go +++ b/internal/torrent/refresh.go @@ -154,22 +154,7 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent { Link: "", // no link yet }) } - if len(selectedFiles) > len(info.Links) && info.Progress == 100 { - if len(info.Links) == 1 { - // this usually is a rar file issue - if t.Config.ShouldDeleteRarFiles() { - t.log.Warnf("Torrent %s id=%s is a rar file, it cannot be repaired. Deleting...", info.Name, info.ID) - t.Api.DeleteTorrent(info.ID) - } else { - t.log.Warnf("Torrent %s id=%s is a rar file, it cannot be repaired as it's a known Real-Debrid limitation. zurg recommends you delete this torrent or add auto_delete_rar_torrents: true in your config.yml", info.Name, info.ID) - torrent.Unfixable = true - } - return nil - } else { - t.log.Warnf("Torrent id=%s is partly expired. It has %d selected files but only %d links", info.ID, len(selectedFiles), len(info.Links)) - torrent.ForRepair = true - } - } else if len(selectedFiles) == len(info.Links) { + if len(selectedFiles) == len(info.Links) { // all links are still intact! good! for i, file := range selectedFiles { file.Link = info.Links[i] @@ -209,6 +194,7 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) Torrent { mainTorrent.Hash = existing.Hash mainTorrent.DownloadedIDs = mapset.NewSet[string]() mainTorrent.InProgressIDs = mapset.NewSet[string]() + mainTorrent.Unfixable = existing.Unfixable || toMerge.Unfixable // this function triggers only when we have a new DownloadedID toMerge.DownloadedIDs.Difference(existing.DownloadedIDs).Each(func(id string) bool { diff --git a/internal/torrent/repair.go b/internal/torrent/repair.go index 79d97e5..cccc5a8 100644 --- a/internal/torrent/repair.go +++ b/internal/torrent/repair.go @@ -20,10 +20,16 @@ func (t *TorrentManager) repairAll() { allTorrents, _ := t.DirectoryMap.Get(INT_ALL) var toRepair []*Torrent allTorrents.IterCb(func(_ string, torrent *Torrent) { - if torrent.AnyInProgress() && torrent.ForRepair { - t.log.Warnf("Skipping %s for repairs because it is still in progress", torrent.AccessKey) + if torrent.AnyInProgress() || torrent.Unfixable { return - } else if torrent.ForRepair && !torrent.Unfixable { + } + hasBrokenFiles := false + torrent.SelectedFiles.IterCb(func(_ string, file *File) { + if file.Link == "repair" || file.Link == "" { + hasBrokenFiles = true + } + }) + if hasBrokenFiles { toRepair = append(toRepair, torrent) } }) @@ -68,54 +74,60 @@ func (t *TorrentManager) repair(torrent *Torrent) { t.log.Infof("Successfully downloaded torrent %s to repair it", torrent.AccessKey) return } else { - t.log.Warnf("Failed to repair by reinserting torrent %s", torrent.AccessKey) + t.log.Warnf("Failed to repair by reinserting torrent %s, will only redownload broken files...", torrent.AccessKey) } } else { - t.log.Warnf("Torrent %s is not older than %d hours to be repaired by reinsertion", torrent.AccessKey, EXPIRED_LINK_TOLERANCE_HOURS) + t.log.Warnf("Torrent %s is not older than %d hours to be repaired by reinsertion, will only redownload broken files...", torrent.AccessKey, EXPIRED_LINK_TOLERANCE_HOURS) } // sleep for 30 seconds to let the torrent accumulate more broken files if scanning time.Sleep(30 * time.Second) - // second solution: add only the missing files - var missingFiles []File + // second solution: add only the broken files + var brokenFiles []File torrent.SelectedFiles.IterCb(func(_ string, file *File) { if file.Link == "repair" || file.Link == "" { - missingFiles = append(missingFiles, *file) + brokenFiles = append(brokenFiles, *file) file.Link = "repairing" } }) - t.log.Debugf("During repair, zurg found %d broken files for torrent %s", len(missingFiles), torrent.AccessKey) + t.log.Debugf("During repair, zurg found %d broken files for torrent %s", len(brokenFiles), torrent.AccessKey) - if len(missingFiles) == 1 && torrent.SelectedFiles.Count() == 1 { - t.log.Warnf("Torrent %s is unrepairable, the lone file is expired but still cached in Real-Debrid", torrent.AccessKey) - return - } else if len(missingFiles) == 1 && torrent.SelectedFiles.Count() > 1 { + // todo: to verify removed logic when there's only 1 selected file selected and it's broken + + // todo: handle rar'ed torrents + + if len(brokenFiles) == 1 && torrent.SelectedFiles.Count() > 1 { // if we download a single file, it will be named differently // so we need to download 1 extra file to preserve the name // this is only relevant if we enable retain_rd_torrent_name // add the first file link encountered with a prefix of http - t.log.Debugf("Torrent %s has only 1 missing file, adding 1 extra file to preserve the name", torrent.AccessKey) + t.log.Debugf("Torrent %s has only 1 broken file, adding 1 extra file to preserve the name", torrent.AccessKey) for _, file := range torrent.SelectedFiles.Items() { if strings.HasPrefix(file.Link, "http") { - missingFiles = append(missingFiles, *file) + brokenFiles = append(brokenFiles, *file) break } } } - if len(missingFiles) > 0 { - t.log.Infof("Redownloading the %d missing files for torrent %s", len(missingFiles), torrent.AccessKey) - missingFileIDs := strings.Join(getFileIDs(missingFiles), ",") - t.reinsertTorrent(torrent, missingFileIDs) + if len(brokenFiles) > 0 { + t.log.Infof("Redownloading the %d broken files for torrent %s", len(brokenFiles), torrent.AccessKey) + brokenFileIDs := strings.Join(getFileIDs(brokenFiles), ",") + if t.reinsertTorrent(torrent, brokenFileIDs) { + t.log.Infof("Successfully downloaded torrent %s to repair it", torrent.AccessKey) + } else { + t.log.Warnf("Failed to repair torrent %s", torrent.AccessKey) + torrent.Unfixable = true + } } else { - t.log.Warnf("Torrent %s has no missing files to repair", torrent.AccessKey) + t.log.Warnf("Torrent %s has no broken files to repair", torrent.AccessKey) } } -func (t *TorrentManager) reinsertTorrent(torrent *Torrent, missingFiles string) bool { +func (t *TorrentManager) reinsertTorrent(torrent *Torrent, brokenFiles string) bool { oldTorrentIDs := make([]string, 0) - // missing files means missing links - // if missingFiles is not provided - if missingFiles == "" { + // broken files means broken links + // if brokenFiles is not provided + if brokenFiles == "" { // only replace the torrent if we are reinserting all files oldTorrentIDs = torrent.DownloadedIDs.ToSlice() tmpSelection := "" @@ -125,7 +137,7 @@ func (t *TorrentManager) reinsertTorrent(torrent *Torrent, missingFiles string) if tmpSelection == "" { return true // nothing to repair } else { - missingFiles = tmpSelection[:len(tmpSelection)-1] + brokenFiles = tmpSelection[:len(tmpSelection)-1] } } @@ -139,7 +151,7 @@ func (t *TorrentManager) reinsertTorrent(torrent *Torrent, missingFiles string) // select files newTorrentID := resp.ID - err = t.Api.SelectTorrentFiles(newTorrentID, missingFiles) + err = t.Api.SelectTorrentFiles(newTorrentID, brokenFiles) if err != nil { t.log.Warnf("Cannot start redownloading: %v", err) t.Api.DeleteTorrent(newTorrentID) @@ -169,9 +181,9 @@ func (t *TorrentManager) reinsertTorrent(torrent *Torrent, missingFiles string) return true } - missingCount := len(strings.Split(missingFiles, ",")) - if len(info.Links) != missingCount { - t.log.Infof("It did not fix the issue for id=%s, only got %d files but we need %d, undoing", info.ID, len(info.Links), missingCount) + brokenCount := len(strings.Split(brokenFiles, ",")) + if len(info.Links) != brokenCount { + t.log.Infof("It did not fix the issue for id=%s, only got %d files but we need %d, undoing", info.ID, len(info.Links), brokenCount) t.Api.DeleteTorrent(newTorrentID) return false } diff --git a/internal/torrent/types.go b/internal/torrent/types.go index a3ba6e3..1a42437 100644 --- a/internal/torrent/types.go +++ b/internal/torrent/types.go @@ -18,7 +18,6 @@ type Torrent struct { Hash string `json:"Hash"` SelectedFiles cmap.ConcurrentMap[string, *File] `json:"-"` LatestAdded string `json:"LatestAdded"` - ForRepair bool `json:"ForRepair"` Unfixable bool `json:"Unfixable"` DownloadedIDs mapset.Set[string] `json:"DownloadedIDs"` InProgressIDs mapset.Set[string] `json:"InProgressIDs"` diff --git a/internal/universal/head.go b/internal/universal/head.go index 5cd6106..2f32b94 100644 --- a/internal/universal/head.go +++ b/internal/universal/head.go @@ -54,10 +54,16 @@ func getContentMimeType(filePath string) string { return "video/mp4" case ".avi": return "video/x-msvideo" + case ".wmv": + return "video/x-ms-wmv" + case ".m4v": + return "video/x-m4v" case ".mp3": return "audio/mpeg" case ".rar": return "application/x-rar-compressed" + case ".zip": + return "application/zip" default: return "application/octet-stream" }