/// <summary> /// /// </summary> /// <param name="settings"></param> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentException"></exception> public BeatSaverFeed(BeatSaverFeedSettings settings) { if (settings == null) { throw new ArgumentNullException(nameof(settings), "settings cannot be null when creating a new BeatSaverFeed."); } BeatSaverFeedSettings = (BeatSaverFeedSettings)settings.Clone(); Feed = BeatSaverFeedSettings.Feed; FeedInfo = Feeds[BeatSaverFeedSettings.Feed]; if ((Feed == BeatSaverFeedName.Search || Feed == BeatSaverFeedName.Author) && !BeatSaverFeedSettings.SearchQuery.HasValue) { throw new ArgumentException(nameof(settings), $"SearchQuery cannot be null in settings for feed {FeedInfo.DisplayName}."); } SearchQuery = BeatSaverFeedSettings.SearchQuery; if (settings.MaxPages > 0) { if (settings.StartingPage == 1) { configuredLastPage = settings.MaxPages; } else { configuredLastPage = settings.StartingPage + settings.MaxPages - 1; } } }
/// <summary> /// Searches for songs with the specified criteria and search type and returns them in a FeedResult. /// TODO: PageResults is always an empty array. /// </summary> /// <param name="criteria"></param> /// <param name="type"></param> /// <param name="settings"></param> /// <returns></returns> public static async Task <FeedResult> SearchAsync(BeatSaverFeedSettings settings, CancellationToken cancellationToken) { if (settings.SearchType == BeatSaverSearchType.key) { var songDict = new Dictionary <string, ScrapedSong>(); var song = await GetSongByKeyAsync(settings.Criteria, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(song?.Hash)) { songDict.Add(song.Hash, song); } return(new FeedResult(songDict, null)); } if (settings.SearchType == BeatSaverSearchType.user) { var authorNames = await GetAuthorNamesByIDAsync(settings.Criteria, cancellationToken).ConfigureAwait(false); return(await GetSongsByUploaderIdAsync(authorNames.FirstOrDefault(), cancellationToken).ConfigureAwait(false)); } if (settings.SearchType == BeatSaverSearchType.hash) { var songDict = new Dictionary <string, ScrapedSong>(); var result = await GetSongByHashAsync(settings.Criteria, cancellationToken).ConfigureAwait(false); var song = result.Songs.FirstOrDefault(); if (!string.IsNullOrEmpty(song?.Hash)) { songDict.Add(song.Hash, song); } return(new FeedResult(songDict, new List <PageReadResult>(1) { result })); } StringBuilder url; int maxSongs = 0; int maxPages = 0; //int lastPage; //int nextPage; int pageIndex = 0; if (settings != null) { maxSongs = settings.MaxSongs; maxPages = settings.MaxPages; pageIndex = Math.Max(settings.StartingPage - 1, 0); } bool useMaxPages = maxPages > 0; bool useMaxSongs = maxSongs > 0; if (useMaxPages && pageIndex > 0) { maxPages = maxPages + pageIndex; } bool continueLooping = true; var songs = new Dictionary <string, ScrapedSong>(); PageReadResult newSongs; List <PageReadResult> pageResults = new List <PageReadResult>(); do { url = new StringBuilder(Feeds[BeatSaverFeed.Search].BaseUrl); url.Replace(SEARCHTYPEKEY, settings.SearchType == BeatSaverSearchType.all ? "text" : "advanced"); url.Replace(SEARCHQUERY, settings.Criteria); url.Replace(PAGEKEY, pageIndex.ToString()); var uri = new Uri(url.ToString()); newSongs = await GetSongsFromPageAsync(uri, cancellationToken).ConfigureAwait(false); if (!useMaxSongs || songs.Count < maxSongs) { pageResults.Add(newSongs); } foreach (var song in newSongs.Songs) { if (songs.ContainsKey(song.Hash)) { continue; } if (!useMaxSongs || songs.Count < maxSongs) { songs.Add(song.Hash, song); } } pageIndex++; if (newSongs.Count == 0) { continueLooping = false; } if (useMaxPages && (pageIndex >= maxPages)) { continueLooping = false; } if (useMaxSongs && pageIndex * SongsPerPage >= maxSongs) { continueLooping = false; } } while (continueLooping); return(new FeedResult(songs, pageResults)); }
public static FeedResult Search(BeatSaverFeedSettings settings, CancellationToken cancellationToken) { return(SearchAsync(settings, cancellationToken).Result); }
/// <summary> /// /// </summary> /// <param name="settings"></param> /// <returns></returns> /// <exception cref="ArgumentNullException">Thrown when settings is null.</exception> public static async Task <FeedResult> GetBeatSaverSongsAsync(BeatSaverFeedSettings settings, CancellationToken cancellationToken) { if (settings == null) { throw new ArgumentNullException(nameof(settings), "settings cannot be null for BeatSaverReader.GetBeatSaverSongsAsync"); } if (cancellationToken.IsCancellationRequested) { return(FeedResult.CancelledResult); } // TODO: double checks the first page int feedIndex = settings.FeedIndex; bool useMaxPages = settings.MaxPages != 0; bool useMaxSongs = settings.MaxSongs != 0; Dictionary <string, ScrapedSong> songs = new Dictionary <string, ScrapedSong>(); string pageText = string.Empty; JObject result = new JObject(); var pageUri = GetPageUrl(feedIndex); IWebResponseMessage response = null; try { response = await GetBeatSaverAsync(pageUri, cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); pageText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); result = JObject.Parse(pageText); } catch (WebClientException ex) { string errorText = string.Empty; if (ex.Response != null) { switch (ex.Response.StatusCode) { case 408: errorText = "Timeout"; break; default: errorText = "Site Error"; break; } } string message = $"{errorText} getting a response from {pageUri}: {ex.Message}"; Logger?.Debug(message); Logger?.Debug($"{ex.Message}\n{ex.StackTrace}"); return(new FeedResult(null, null, new FeedReaderException(message, ex, FeedReaderFailureCode.SourceFailed), FeedResultError.Error)); } catch (JsonReaderException ex) { string message = "Unable to parse JSON from text on first page in GetBeatSaverSongAsync()"; Logger?.Debug(message); Logger?.Debug($"{ex.Message}\n{ex.StackTrace}"); return(new FeedResult(null, null, new FeedReaderException(message, ex, FeedReaderFailureCode.SourceFailed), FeedResultError.Error)); } catch (OperationCanceledException) { return(FeedResult.CancelledResult); } catch (Exception ex) { string message = $"Uncaught error getting the first page in BeatSaverReader.GetBeatSaverSongsAsync(): {ex.Message}"; return(new FeedResult(null, null, new FeedReaderException(message, ex, FeedReaderFailureCode.SourceFailed), FeedResultError.Error)); } finally { response?.Dispose(); response = null; } int?numSongs = result["totalDocs"]?.Value <int>(); int?lastPage = result["lastPage"]?.Value <int>(); if (numSongs == null || lastPage == null || numSongs == 0) { Logger?.Warning($"Error checking Beat Saver's {settings.FeedName} feed."); return(new FeedResult(null, null, new FeedReaderException($"Error getting the first page in GetBeatSaverSongsAsync()", null, FeedReaderFailureCode.SourceFailed), FeedResultError.Error)); } Logger?.Info($"Checking Beat Saver's {settings.FeedName} feed, {numSongs} songs available"); int maxPages = settings.MaxPages; int pageNum = Math.Max(settings.StartingPage - 1, 0); if (pageNum > 0 && useMaxPages) { maxPages += pageNum; // Add starting page to maxPages so we actually get songs if maxPages < starting page } List <Task <PageReadResult> > pageReadTasks = new List <Task <PageReadResult> >(); bool continueLooping = true; do { pageUri = GetPageUrl(feedIndex, pageNum); Logger?.Trace($"Creating task for {pageUri.ToString()}"); pageReadTasks.Add(GetSongsFromPageAsync(pageUri, cancellationToken)); pageNum++; if ((pageNum > lastPage)) { continueLooping = false; } if (useMaxPages && (pageNum >= maxPages)) { continueLooping = false; } if (useMaxSongs && pageNum * SongsPerPage >= settings.MaxSongs) { continueLooping = false; } } while (continueLooping); try { // TODO: Won't hit max songs if a song is added while reading the pages. (It'll bump all the songs down and we'll get a repeat) await Task.WhenAll(pageReadTasks.ToArray()).ConfigureAwait(false); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { string message = $"Error waiting for pageReadTasks: {ex.Message}"; // TODO: Probably don't need logging here. Logger?.Debug(message); Logger?.Debug(ex.StackTrace); return(new FeedResult(null, null, new FeedReaderException(message, ex, FeedReaderFailureCode.SourceFailed), FeedResultError.Error)); } var pageResults = new List <PageReadResult>(); foreach (var job in pageReadTasks) { var pageResult = await job.ConfigureAwait(false); pageResults.Add(pageResult); foreach (var song in pageResult.Songs) { if (songs.ContainsKey(song.Hash)) { continue; } if (!useMaxSongs || songs.Count < settings.MaxSongs) { songs.Add(song.Hash, song); } } } return(new FeedResult(songs, pageResults)); }
public static FeedResult GetBeatSaverSongs(BeatSaverFeedSettings settings) { return(GetBeatSaverSongsAsync(settings, CancellationToken.None).Result); }