From b505400f609feecf7085206b8ca81ef8ea63d34d Mon Sep 17 00:00:00 2001 From: Ben Sarmiento Date: Mon, 29 Jan 2024 22:28:27 +0100 Subject: [PATCH] Reimplement deletes and marking files as broken --- internal/dav/delete.go | 4 +-- internal/dav/infuse.go | 6 ++-- internal/dav/listing.go | 8 ++--- internal/dav/rename.go | 7 ++-- internal/http/listing.go | 6 ++-- internal/torrent/delete.go | 16 ++++----- internal/torrent/manager.go | 6 ++-- internal/torrent/refresh.go | 31 ++---------------- internal/torrent/repair.go | 15 ++------- internal/torrent/types.go | 7 ++-- internal/universal/check.go | 14 +------- internal/universal/downloader.go | 56 ++++++++++++++------------------ 12 files changed, 64 insertions(+), 112 deletions(-) diff --git a/internal/dav/delete.go b/internal/dav/delete.go index dd00806..1db718b 100644 --- a/internal/dav/delete.go +++ b/internal/dav/delete.go @@ -29,14 +29,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 { + if !ok || file.IsDeleted { return fmt.Errorf("cannot find file %s", fileName) } dirCfg := torMgr.Config.(*config.ZurgConfigV1).GetDirectoryConfig(directory) if dirCfg.OnlyShowTheBiggestFile { torMgr.Delete(torrentName, true) } else { - file.Link = "unselect" + file.IsDeleted = true if torMgr.CheckDeletedStatus(torrent) { torMgr.Delete(torrentName, true) } diff --git a/internal/dav/infuse.go b/internal/dav/infuse.go index 27b919d..a2ec364 100644 --- a/internal/dav/infuse.go +++ b/internal/dav/infuse.go @@ -74,8 +74,8 @@ func ServeFilesListForInfuse(directory, torrentName string, torMgr *torrent.Torr filenames := tor.SelectedFiles.Keys() sort.Strings(filenames) for _, filename := range filenames { - file, ok := tor.SelectedFiles.Get(filename) - if !ok || !strings.HasPrefix(file.Link, "http") { + file, _ := tor.SelectedFiles.Get(filename) + if file.IsDeleted { continue } if dirCfg.OnlyShowTheBiggestFile && file.Bytes < biggestFileSize { @@ -104,7 +104,7 @@ func ServeDownloadsListForInfuse(torMgr *torrent.TorrentManager) ([]byte, error) sort.Strings(filenames) for _, filename := range filenames { download, ok := torMgr.DownloadMap.Get(filename) - if !ok || !strings.HasPrefix(download.Link, "http") { + if !ok { continue } buf.WriteString(dav.File(download.Filename, download.Filesize, download.Generated)) diff --git a/internal/dav/listing.go b/internal/dav/listing.go index 9d763fb..6270b34 100644 --- a/internal/dav/listing.go +++ b/internal/dav/listing.go @@ -78,8 +78,8 @@ func ServeFilesList(directory, torrentName string, torMgr *torrent.TorrentManage filenames := tor.SelectedFiles.Keys() sort.Strings(filenames) for _, filename := range filenames { - file, ok := tor.SelectedFiles.Get(filename) - if !ok || !strings.HasPrefix(file.Link, "http") { + file, _ := tor.SelectedFiles.Get(filename) + if file.IsDeleted { 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 || !strings.HasPrefix(file.Link, "http") { + if !ok || file.IsDeleted { return nil, fmt.Errorf("cannot find file %s", fileName) } @@ -131,7 +131,7 @@ func ServeDownloadsList(torMgr *torrent.TorrentManager) ([]byte, error) { sort.Strings(filenames) for _, filename := range filenames { download, ok := torMgr.DownloadMap.Get(filename) - if !ok || !strings.HasPrefix(download.Link, "http") { + if !ok { continue } buf.WriteString(dav.File(download.Filename, download.Filesize, download.Generated)) diff --git a/internal/dav/rename.go b/internal/dav/rename.go index aa769a7..aac6d6e 100644 --- a/internal/dav/rename.go +++ b/internal/dav/rename.go @@ -31,11 +31,12 @@ func HandleRenameFile(directory, torrentName, fileName, newName string, torMgr * return fmt.Errorf("cannot find torrent %s", torrentName) } file, ok := torrent.SelectedFiles.Get(fileName) - if !ok { + if !ok || file.IsDeleted { return fmt.Errorf("cannot find file %s", fileName) } - torrent.SelectedFiles.Remove(torrentName) + oldName := torMgr.GetPath(file) + file.Rename = newName torrent.SelectedFiles.Set(newName, file) - file.Path = newName + torrent.SelectedFiles.Remove(oldName) return nil } diff --git a/internal/http/listing.go b/internal/http/listing.go index 110850a..2eb481f 100644 --- a/internal/http/listing.go +++ b/internal/http/listing.go @@ -74,8 +74,8 @@ func ServeFilesList(directory, torrentName string, torMgr *torrent.TorrentManage filenames := tor.SelectedFiles.Keys() sort.Strings(filenames) for _, filename := range filenames { - file, ok := tor.SelectedFiles.Get(filename) - if !ok || !strings.HasPrefix(file.Link, "http") { + file, _ := tor.SelectedFiles.Get(filename) + if file.IsDeleted { continue } if dirCfg.OnlyShowTheBiggestFile && file.Bytes < biggestFileSize { @@ -104,7 +104,7 @@ func ServeDownloadsList(torMgr *torrent.TorrentManager) ([]byte, error) { sort.Strings(filenames) for _, filename := range filenames { download, ok := torMgr.DownloadMap.Get(filename) - if !ok || !strings.HasPrefix(download.Link, "http") { + if !ok { continue } filePath := filepath.Join(config.DOWNLOADS, url.PathEscape(filename)) diff --git a/internal/torrent/delete.go b/internal/torrent/delete.go index bb46b4b..4275588 100644 --- a/internal/torrent/delete.go +++ b/internal/torrent/delete.go @@ -3,20 +3,20 @@ package torrent import cmap "github.com/orcaman/concurrent-map/v2" func (t *TorrentManager) CheckDeletedStatus(torrent *Torrent) bool { - var unselectedIDs []int + var deletedIDs []int torrent.SelectedFiles.IterCb(func(_ string, file *File) { - if file.Link == "unselect" { - unselectedIDs = append(unselectedIDs, file.ID) + if file.IsDeleted { + deletedIDs = append(deletedIDs, file.ID) } }) - if len(unselectedIDs) == torrent.SelectedFiles.Count() && len(unselectedIDs) > 0 { + if len(deletedIDs) == torrent.SelectedFiles.Count() && len(deletedIDs) > 0 { return true - } else if len(unselectedIDs) > 0 { + } else if len(deletedIDs) > 0 { t.saveTorrentChangesToDisk(torrent, func(info *Torrent) { info.SelectedFiles.IterCb(func(_ string, file *File) { - for _, unselectedID := range unselectedIDs { - if file.ID == unselectedID { - file.Link = "unselect" + for _, deletedID := range deletedIDs { + if file.ID == deletedID { + file.IsDeleted = true break } } diff --git a/internal/torrent/manager.go b/internal/torrent/manager.go index 53ea670..5165450 100644 --- a/internal/torrent/manager.go +++ b/internal/torrent/manager.go @@ -27,7 +27,6 @@ type TorrentManager struct { DownloadCache cmap.ConcurrentMap[string, *realdebrid.Download] DownloadMap cmap.ConcurrentMap[string, *realdebrid.Download] fixers cmap.ConcurrentMap[string, *Torrent] - deleteOnceDone mapset.Set[string] allAccessKeys mapset.Set[string] latestState *LibraryState requiredVersion string @@ -56,7 +55,6 @@ func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, w RefreshKillSwitch: make(chan struct{}, 1), RepairKillSwitch: make(chan struct{}, 1), fixers: cmap.New[*Torrent](), - deleteOnceDone: mapset.NewSet[string](), allAccessKeys: mapset.NewSet[string](), latestState: &LibraryState{}, requiredVersion: "0.9.3-hotfix.3", @@ -78,6 +76,7 @@ func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, w // proxy func (t *TorrentManager) UnrestrictUntilOk(link string) *realdebrid.Download { + // check if it's a valid link if !strings.HasPrefix(link, "http") { return nil } @@ -114,6 +113,9 @@ func (t *TorrentManager) GetKey(torrent *Torrent) string { } func (t *TorrentManager) GetPath(file *File) string { + if !t.Config.ShouldIgnoreRenames() && file.Rename != "" { + return file.Rename + } if t.Config.ShouldExposeFullPath() { filename := strings.TrimPrefix(file.Path, "/") filename = strings.ReplaceAll(filename, "/", " - ") diff --git a/internal/torrent/refresh.go b/internal/torrent/refresh.go index 3514b5a..d5eaccb 100644 --- a/internal/torrent/refresh.go +++ b/internal/torrent/refresh.go @@ -24,10 +24,8 @@ func (t *TorrentManager) refreshTorrents() []string { var wg sync.WaitGroup allTorrents, _ := t.DirectoryMap.Get(INT_ALL) - doesNotExist := t.deleteOnceDone.Clone() for i := range instances { idx := i - doesNotExist.Remove(instances[idx].ID) // remove if existing wg.Add(1) _ = t.workerPool.Submit(func() { defer wg.Done() @@ -48,30 +46,6 @@ func (t *TorrentManager) refreshTorrents() []string { close(infoChan) t.log.Infof("Fetched info for %d torrents", len(instances)) - // delete expired fixers - doesNotExist.Each(func(fixerID string) bool { - t.fixers.Remove(fixerID) - t.deleteOnceDone.Remove(fixerID) - return false - }) - - // ensure delete - infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE) - for { - fixerToDel, ok := t.deleteOnceDone.Pop() - if !ok { - break - } - torrent, exists := infoCache.Get(fixerToDel) - if exists && torrent.AnyInProgress() { - continue - } - t.log.Debugf("Ensuring that torrent id=%s is deleted", fixerToDel) - t.Delete(t.GetKey(torrent), true) - t.Api.DeleteTorrent(fixerToDel) - infoCache.Remove(fixerToDel) - } - newlyFetchedKeys := mapset.NewSet[string]() noInfoCount := 0 for info := range infoChan { @@ -169,7 +143,7 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent { hasBrokenFiles := false torrentFromFile.SelectedFiles.IterCb(func(filepath string, file *File) { - if !strings.HasPrefix(file.Link, "http") && file.Link != "unselect" { + if file.IsBroken && !file.IsDeleted { hasBrokenFiles = true } }) @@ -307,7 +281,6 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) Torrent { // the link can have the following values // 1. https://*** - the file is available - // 2. unselect - the file is deleted // 3. empty - the file is not available mainTorrent.SelectedFiles.IterCb(func(key string, file *File) { if file.Link == "" { @@ -320,7 +293,7 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) Torrent { older.SelectedFiles.IterCb(func(key string, file *File) { if !mainTorrent.SelectedFiles.Has(key) { mainTorrent.SelectedFiles.Set(key, file) - } else if file.Link == "unselect" { + } else if file.IsDeleted { mainTorrent.SelectedFiles.Set(key, file) } }) diff --git a/internal/torrent/repair.go b/internal/torrent/repair.go index 4622cc6..e353648 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 !strings.HasPrefix(file.Link, "http") && file.Link != "unselect" { + if file.IsBroken && !file.IsDeleted { brokenFileIDs.Add(file.ID) } }) @@ -337,7 +337,6 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection string) ( err = t.Api.SelectTorrentFiles(newTorrentID, selection) if err != nil { t.Api.DeleteTorrent(newTorrentID) - t.deleteOnceDone.Add(newTorrentID) return nil, fmt.Errorf("cannot start redownloading: %v", err) } @@ -348,7 +347,6 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection string) ( info, err := t.Api.GetTorrentInfo(newTorrentID) if err != nil { t.Api.DeleteTorrent(newTorrentID) - t.deleteOnceDone.Add(newTorrentID) return nil, fmt.Errorf("cannot get info on redownloaded torrent %s (id=%s) : %v", t.GetKey(torrent), newTorrentID, err) } @@ -364,7 +362,6 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection string) ( } if !isOkStatus { t.Api.DeleteTorrent(newTorrentID) - t.deleteOnceDone.Add(newTorrentID) return nil, fmt.Errorf("the redownloaded torrent %s (id=%s) is in error state: %s", t.GetKey(torrent), newTorrentID, info.Status) } @@ -372,7 +369,6 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection string) ( selectionCount := len(strings.Split(selection, ",")) if info.Progress == 100 && len(info.Links) != selectionCount { t.Api.DeleteTorrent(newTorrentID) - t.deleteOnceDone.Add(newTorrentID) return nil, fmt.Errorf("it did not fix the issue for %s (id=%s), only got %d files but we need %d, undoing", t.GetKey(torrent), info.ID, len(info.Links), selectionCount) } @@ -384,13 +380,11 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection string) ( for _, id := range oldTorrentIDs { torrent.DownloadedIDs.Remove(id) t.Api.DeleteTorrent(id) - t.deleteOnceDone.Add(id) infoCache.Remove(id) } } else { // it's a fixer t.fixers.Set(newTorrentID, torrent) - t.deleteOnceDone.Add(newTorrentID) } return info, nil } @@ -455,11 +449,11 @@ func (t *TorrentManager) markAsUnfixable(torrent *Torrent, reason string) { }) } -// getBrokenFiles returns the files that are not http links and not unselect +// getBrokenFiles returns the files that are not http links and not deleted func getBrokenFiles(torrent *Torrent) []*File { var brokenFiles []*File torrent.SelectedFiles.IterCb(func(_ string, file *File) { - if !strings.HasPrefix(file.Link, "http") && file.Link != "unselect" { + if file.IsBroken && !file.IsDeleted { brokenFiles = append(brokenFiles, file) } }) @@ -515,7 +509,6 @@ func (t *TorrentManager) handleFixers(fixer realdebrid.Torrent) *Torrent { if torrent == nil { t.log.Warnf("repair_method#2: Fixer for %s (id=%s) is done but torrent has been deleted, deleting fixer...", fixer.Name, fixer.ID) t.Api.DeleteTorrent(fixer.ID) - t.deleteOnceDone.Add(fixer.ID) return nil } @@ -542,7 +535,6 @@ func (t *TorrentManager) handleFixers(fixer realdebrid.Torrent) *Torrent { } else { t.log.Warnf("repair_method#2: Fixer is done but torrent %s is still broken; let's keep the fixer", t.GetKey(torrent)) t.Api.DeleteTorrent(info.ID) - t.deleteOnceDone.Add(fixer.ID) return t.getMoreInfo(fixer) } } else { @@ -550,7 +542,6 @@ func (t *TorrentManager) handleFixers(fixer realdebrid.Torrent) *Torrent { } t.Api.DeleteTorrent(fixer.ID) // delete the fixer - t.deleteOnceDone.Add(fixer.ID) return nil } diff --git a/internal/torrent/types.go b/internal/torrent/types.go index be9ba10..f4167a1 100644 --- a/internal/torrent/types.go +++ b/internal/torrent/types.go @@ -152,6 +152,9 @@ func (t *Torrent) OlderThanDuration(duration time.Duration) bool { type File struct { realdebrid.File - Ended string `json:"Ended"` - Link string `json:"Link"` + Ended string `json:"Ended"` + Link string `json:"Link"` + IsBroken bool `json:"IsBroken"` + IsDeleted bool `json:"IsDeleted"` + Rename string `json:"Rename"` } diff --git a/internal/universal/check.go b/internal/universal/check.go index f4484de..b3b38cd 100644 --- a/internal/universal/check.go +++ b/internal/universal/check.go @@ -27,17 +27,11 @@ func CheckFile(directory, torrentName, fileName string, w http.ResponseWriter, r } file, ok := torrent.SelectedFiles.Get(fileName) - if !ok { + if !ok || file.IsDeleted { log.Warnf("Cannot find file %s from path %s", fileName, req.URL.Path) http.Error(w, "Cannot find file", http.StatusNotFound) return } - if !strings.HasPrefix(file.Link, "http") { - // This is a dead file, serve an alternate file - log.Warnf("File %s is no longer available", fileName) - http.Error(w, "Cannot find file", http.StatusNotFound) - return - } contentType := getContentMimeType(fileName) contentLength := fmt.Sprintf("%d", file.Bytes) lastModified := file.Ended @@ -48,12 +42,6 @@ func CheckFile(directory, torrentName, fileName string, w http.ResponseWriter, r } func CheckDownloadLink(download *realdebrid.Download, w http.ResponseWriter, req *http.Request, torMgr *torrent.TorrentManager, log *logutil.Logger) { - if !strings.HasPrefix(download.Link, "http") { - // This is a dead file, serve an alternate file - log.Warnf("File %s is no longer available", download.Filename) - http.Error(w, "Cannot find file", http.StatusNotFound) - return - } contentType := getContentMimeType(download.Filename) contentLength := fmt.Sprintf("%d", download.Filesize) lastModified := download.Generated diff --git a/internal/universal/downloader.go b/internal/universal/downloader.go index 8089b6f..c1cfa46 100644 --- a/internal/universal/downloader.go +++ b/internal/universal/downloader.go @@ -31,6 +31,7 @@ func (dl *Downloader) DownloadFile(directory, torrentName, fileName string, resp http.Error(resp, "File not found", http.StatusNotFound) return } + torrent, ok := torrents.Get(torrentName) if !ok { log.Warnf("Cannot find torrent %sfrom path %s", torrentName, req.URL.Path) @@ -39,31 +40,28 @@ func (dl *Downloader) DownloadFile(directory, torrentName, fileName string, resp } file, ok := torrent.SelectedFiles.Get(fileName) - if !ok { + if !ok || file.IsDeleted { log.Warnf("Cannot find file %s from path %s", fileName, req.URL.Path) http.Error(resp, "File not found", http.StatusNotFound) return } - if !strings.HasPrefix(file.Link, "http") { - // This is a dead file, serve an alternate file - log.Warnf("File %s is not available", fileName) - http.Error(resp, "File is not available", http.StatusNotFound) + // log.Debugf("Opening file %s from torrent %s (%s)", fileName, torMgr.GetKey(torrent), file.Link) + if file.IsBroken { + http.Error(resp, "File is not available", http.StatusInternalServerError) return } - // log.Debugf("Opening file %s from torrent %s (%s)", fileName, torMgr.GetKey(torrent), file.Link) - unrestrict := torMgr.UnrestrictUntilOk(file.Link) if unrestrict == nil { - log.Warnf("File %s cannot be unrestricted (link=%s)", fileName, file.Link) - file.Link = "" + file.IsBroken = true if cfg.EnableRepair() { + log.Warnf("File %s cannot be unrestricted (link=%s) (repairing...)", fileName, file.Link) torMgr.TriggerRepair(torrent) } else { - log.Debugf("Repair is disabled, skipping repair for unavailable file %s (link=%s)", fileName, file.Link) + log.Warnf("Repair is disabled, skipping repair for unavailable file %s (link=%s)", fileName, file.Link) } - http.Error(resp, "File is not available", http.StatusNotFound) + http.Error(resp, "File is not available", http.StatusInternalServerError) return } else { if unrestrict.Filesize != file.Bytes { @@ -81,7 +79,7 @@ func (dl *Downloader) DownloadFile(directory, torrentName, fileName string, resp if cfg.ShouldVerifyDownloadLink() { if !dl.client.CanFetchFirstByte(unrestrict.Download) { log.Warnf("File %s is not available", fileName) - http.Error(resp, "File is not available", http.StatusNotFound) + http.Error(resp, "File is not available", http.StatusInternalServerError) return } } @@ -95,19 +93,11 @@ func (dl *Downloader) DownloadFile(directory, torrentName, fileName string, resp // DownloadLink handles a GET request for downloads func (dl *Downloader) DownloadLink(fileName, link string, resp http.ResponseWriter, req *http.Request, torMgr *intTor.TorrentManager, cfg config.ConfigInterface, log *logutil.Logger) { - if !strings.HasPrefix(link, "http") { - // This is a dead file, serve an alternate file - log.Warnf("File %s is not available", fileName) - http.Error(resp, "File is not available", http.StatusNotFound) - return - } - // log.Debugf("Opening file %s (%s)", fileName, link) - unrestrict := torMgr.UnrestrictUntilOk(link) if unrestrict == nil { log.Warnf("File %s cannot be unrestricted (link=%s)", fileName, link) - http.Error(resp, "File is not available", http.StatusNotFound) + http.Error(resp, "File is not available", http.StatusInternalServerError) return } else { lFilename := strings.ToLower(fileName) @@ -127,7 +117,7 @@ func (dl *Downloader) DownloadLink(fileName, link string, resp http.ResponseWrit if cfg.ShouldVerifyDownloadLink() { if !dl.client.CanFetchFirstByte(unrestrict.Download) { log.Warnf("File %s is not available", fileName) - http.Error(resp, "File is not available", http.StatusNotFound) + http.Error(resp, "File is not available", http.StatusInternalServerError) return } } @@ -146,7 +136,7 @@ func (dl *Downloader) streamFileToResponse(torrent *intTor.Torrent, file *intTor if file != nil { log.Errorf("Error creating new request for file %s: %v", file.Path, err) } - http.Error(resp, "File is not available", http.StatusNotFound) + http.Error(resp, "File is not available", http.StatusInternalServerError) return } @@ -165,31 +155,35 @@ func (dl *Downloader) streamFileToResponse(torrent *intTor.Torrent, file *intTor download, err := dl.client.Do(dlReq) if err != nil { - log.Warnf("Cannot download file %s: %v", unrestrict.Download, err) if file != nil && unrestrict.Streamable == 1 { - file.Link = "" + file.IsBroken = true if cfg.EnableRepair() && torrent != nil { + log.Warnf("Cannot download file %s: %v (repairing...)", unrestrict.Download, err) torMgr.TriggerRepair(torrent) } else { - log.Debugf("Repair is disabled, skipping repair for unavailable file %s (link=%s)", file.Path, file.Link) + log.Warnf("Repair is disabled, skipping repair for unavailable file %s (link=%s)", file.Path, file.Link) } + } else { + log.Warnf("Cannot download file %s: %v", unrestrict.Download, err) } - http.Error(resp, "File is not available", http.StatusNotFound) + http.Error(resp, "File is not available", http.StatusInternalServerError) return } defer download.Body.Close() if download.StatusCode/100 != 2 { if file != nil && unrestrict.Streamable == 1 { - log.Warnf("Received a %s status code for file %s", download.Status, file.Path) - file.Link = "" + file.IsBroken = true if cfg.EnableRepair() && torrent != nil { + log.Warnf("Received a %s status code for file %s (repairing...)", download.Status, file.Path) torMgr.TriggerRepair(torrent) } else { - log.Debugf("Repair is disabled, skipping repair for unavailable file %s (link=%s)", file.Path, file.Link) + log.Warnf("Repair is disabled, skipping repair for unavailable file %s (link=%s)", file.Path, file.Link) } + } else { + log.Warnf("Received a %s status code for file %s", download.Status, unrestrict.Download) } - http.Error(resp, "File is not available", http.StatusNotFound) + http.Error(resp, "File is not available", http.StatusInternalServerError) return }