diff --git a/SpotifyAPI/Web/SpotifyWebAPI.cs b/SpotifyAPI/Web/SpotifyWebAPI.cs index d8795ab2..1d7f1eaa 100644 --- a/SpotifyAPI/Web/SpotifyWebAPI.cs +++ b/SpotifyAPI/Web/SpotifyWebAPI.cs @@ -44,6 +44,28 @@ namespace SpotifyAPI.Web GC.SuppressFinalize(this); } + #region Configuration + /// + /// Specifies after how many miliseconds should a failed request be retried. + /// + public int RetryAfter { get; set; } = 50; + + /// + /// Should a failed request (specified by be automatically retried or not. + /// + public bool UseAutoRetry { get; set; } = false; + + /// + /// Maximum number of tries for one failed request. + /// + public int RetryTimes { get; set; } = 10; + + /// + /// Error codes that will trigger auto-retry if is enabled. + /// + public IEnumerable RetryErrorCodes { get; private set; } = new int[] { 500, 502, 503 }; + #endregion Configuration + #region Search /// @@ -697,24 +719,13 @@ namespace SpotifyAPI.Web { if (!UseAuth) throw new InvalidOperationException("Auth is required for IsFollowing"); - Tuple res = DownloadDataAlt(_builder.IsFollowing(followType, ids)); - ListResponse ret; - if (res.Item2 is JArray) - ret = new ListResponse - { - List = res.Item2.ToObject>(), - Error = null - }; - else - ret = new ListResponse - { - List = null, - Error = res.Item2["error"].ToObject() - }; - ret.AddResponseInfo(res.Item1); - return ret; + + var url = _builder.IsFollowing(followType, ids); + return DownloadList(url); } + + /// /// Check to see if the current user is following one or more artists or other Spotify users asynchronously. /// @@ -726,22 +737,9 @@ namespace SpotifyAPI.Web { if (!UseAuth) throw new InvalidOperationException("Auth is required for IsFollowing"); - Tuple res = await DownloadDataAltAsync(_builder.IsFollowing(followType, ids)); - ListResponse ret = null; - if (res.Item2 is JArray) - ret = new ListResponse - { - List = res.Item2.ToObject>(), - Error = null - }; - else - ret = new ListResponse - { - List = null, - Error = res.Item2["error"].ToObject() - }; - ret.AddResponseInfo(res.Item1); - return ret; + + var url = _builder.IsFollowing(followType, ids); + return await DownloadListAsync(url); } /// @@ -850,22 +848,9 @@ namespace SpotifyAPI.Web { if (!UseAuth) throw new InvalidOperationException("Auth is required for IsFollowingPlaylist"); - Tuple res = DownloadDataAlt(_builder.IsFollowingPlaylist(ownerId, playlistId, ids)); - ListResponse ret = null; - if (res.Item2 is JArray) - ret = new ListResponse - { - List = res.Item2.ToObject>(), - Error = null - }; - else - ret = new ListResponse - { - List = null, - Error = res.Item2["error"].ToObject() - }; - ret.AddResponseInfo(res.Item1); - return ret; + + var url = _builder.IsFollowingPlaylist(ownerId, playlistId, ids); + return DownloadList(url); } /// @@ -880,22 +865,9 @@ namespace SpotifyAPI.Web { if (!UseAuth) throw new InvalidOperationException("Auth is required for IsFollowingPlaylist"); - Tuple res = await DownloadDataAltAsync(_builder.IsFollowingPlaylist(ownerId, playlistId, ids)); - ListResponse ret = null; - if (res.Item2 is JArray) - ret = new ListResponse - { - List = res.Item2.ToObject>(), - Error = null - }; - else - ret = new ListResponse - { - List = null, - Error = res.Item2["error"].ToObject() - }; - ret.AddResponseInfo(res.Item1); - return ret; + + var url = _builder.IsFollowingPlaylist(ownerId, playlistId, ids); + return await DownloadListAsync(url); } /// @@ -1038,22 +1010,9 @@ namespace SpotifyAPI.Web { if (!UseAuth) throw new InvalidOperationException("Auth is required for CheckSavedTracks"); - Tuple res = DownloadDataAlt(_builder.CheckSavedTracks(ids)); - ListResponse ret = null; - if (res.Item2 is JArray) - ret = new ListResponse - { - List = res.Item2.ToObject>(), - Error = null - }; - else - ret = new ListResponse - { - List = null, - Error = res.Item2["error"].ToObject() - }; - ret.AddResponseInfo(res.Item1); - return ret; + + var url = _builder.CheckSavedTracks(ids); + return DownloadList(url); } /// @@ -1066,22 +1025,8 @@ namespace SpotifyAPI.Web { if (!UseAuth) throw new InvalidOperationException("Auth is required for CheckSavedTracks"); - Tuple res = await DownloadDataAltAsync(_builder.CheckSavedTracks(ids)); - ListResponse ret = null; - if (res.Item2 is JArray) - ret = new ListResponse - { - List = res.Item2.ToObject>(), - Error = null - }; - else - ret = new ListResponse - { - List = null, - Error = res.Item2["error"].ToObject() - }; - ret.AddResponseInfo(res.Item1); - return ret; + var url = _builder.CheckSavedTracks(ids); + return await DownloadListAsync(url); } /// @@ -1194,22 +1139,9 @@ namespace SpotifyAPI.Web { if (!UseAuth) throw new InvalidOperationException("Auth is required for CheckSavedTracks"); - Tuple res = DownloadDataAlt(_builder.CheckSavedAlbums(ids)); - ListResponse ret = null; - if (res.Item2 is JArray) - ret = new ListResponse - { - List = res.Item2.ToObject>(), - Error = null - }; - else - ret = new ListResponse - { - List = null, - Error = res.Item2["error"].ToObject() - }; - ret.AddResponseInfo(res.Item1); - return ret; + + var url = _builder.CheckSavedAlbums(ids); + return DownloadList(url); } /// @@ -1222,22 +1154,8 @@ namespace SpotifyAPI.Web { if (!UseAuth) throw new InvalidOperationException("Auth is required for CheckSavedAlbumsAsync"); - Tuple res = await DownloadDataAltAsync(_builder.CheckSavedAlbums(ids)); - ListResponse ret = null; - if (res.Item2 is JArray) - ret = new ListResponse - { - List = res.Item2.ToObject>(), - Error = null - }; - else - ret = new ListResponse - { - List = null, - Error = res.Item2["error"].ToObject() - }; - ret.AddResponseInfo(res.Item1); - return ret; + var url = _builder.CheckSavedAlbums(ids); + return await DownloadListAsync(url); } #endregion Library @@ -1911,15 +1829,94 @@ namespace SpotifyAPI.Web return await GetPreviousPageAsync, T>(paging); } + private ListResponse DownloadList(string url) + { + int triesLeft = RetryTimes + 1; + Error lastError = null; + + ListResponse data = null; + do + { + if (data != null) { System.Threading.Thread.Sleep(RetryAfter); } + Tuple res = DownloadDataAlt(url); + data = ExtractDataToListResponse(res); + + lastError = data.Error; + + triesLeft -= 1; + + } while (UseAutoRetry && triesLeft > 0 && lastError != null && RetryErrorCodes.Contains(lastError.Status)); + + return data; + } + + private async Task> DownloadListAsync(string url) + { + int triesLeft = RetryTimes + 1; + Error lastError = null; + + ListResponse data = null; + do + { + if (data != null) { await Task.Delay(RetryAfter); } + Tuple res = await DownloadDataAltAsync(url); + data = ExtractDataToListResponse(res); + + lastError = data.Error; + + triesLeft -= 1; + + } while (UseAutoRetry && triesLeft > 0 && lastError != null && RetryErrorCodes.Contains(lastError.Status)); + + return data; + } + + private static ListResponse ExtractDataToListResponse(Tuple res) + { + ListResponse ret = null; + if (res.Item2 is JArray) + { + ret = new ListResponse + { + List = res.Item2.ToObject>(), + Error = null + }; + } + else + { + ret = new ListResponse + { + List = null, + Error = res.Item2["error"].ToObject() + }; + } + ret.AddResponseInfo(res.Item1); + return ret; + } + public T UploadData(string url, string uploadData, string method = "POST") where T : BasicModel { if (!UseAuth) throw new InvalidOperationException("Auth is required for all Upload-Actions"); - WebClient.SetHeader("Authorization", TokenType + " " + AccessToken); - WebClient.SetHeader("Content-Type", "application/json"); + int triesLeft = RetryTimes + 1; + Error lastError = null; + + Tuple response = null; + do + { + WebClient.SetHeader("Authorization", TokenType + " " + AccessToken); + WebClient.SetHeader("Content-Type", "application/json"); + + if (response != null) { System.Threading.Thread.Sleep(RetryAfter); } + response = WebClient.UploadJson(url, uploadData, method); + + response.Item2.AddResponseInfo(response.Item1); + lastError = response.Item2.Error; + + triesLeft -= 1; + + } while (UseAutoRetry && triesLeft > 0 && lastError != null && RetryErrorCodes.Contains(lastError.Status)); - Tuple response = WebClient.UploadJson(url, uploadData, method); - response.Item2.AddResponseInfo(response.Item1); return response.Item2; } @@ -1927,35 +1924,70 @@ namespace SpotifyAPI.Web { if (!UseAuth) throw new InvalidOperationException("Auth is required for all Upload-Actions"); - WebClient.SetHeader("Authorization", TokenType + " " + AccessToken); - WebClient.SetHeader("Content-Type", "application/json"); - Tuple response = await WebClient.UploadJsonAsync(url, uploadData, method); - response.Item2.AddResponseInfo(response.Item1); + int triesLeft = RetryTimes + 1; + Error lastError = null; + + Tuple response = null; + do + { + WebClient.SetHeader("Authorization", TokenType + " " + AccessToken); + WebClient.SetHeader("Content-Type", "application/json"); + + if (response != null) { await Task.Delay(RetryAfter); } + response = await WebClient.UploadJsonAsync(url, uploadData, method); + + response.Item2.AddResponseInfo(response.Item1); + lastError = response.Item2.Error; + + triesLeft -= 1; + + } while (UseAutoRetry && triesLeft > 0 && lastError != null && RetryErrorCodes.Contains(lastError.Status)); + return response.Item2; } public T DownloadData(string url) where T : BasicModel { - if (UseAuth) - WebClient.SetHeader("Authorization", TokenType + " " + AccessToken); - else - WebClient.RemoveHeader("Authorization"); + int triesLeft = RetryTimes + 1; + Error lastError = null; + + Tuple response = null; + do + { + if(response != null) { System.Threading.Thread.Sleep(RetryAfter); } + response = DownloadDataAlt(url); + + response.Item2.AddResponseInfo(response.Item1); + lastError = response.Item2.Error; + + triesLeft -= 1; + + } while (UseAutoRetry && triesLeft > 0 && lastError != null && RetryErrorCodes.Contains(lastError.Status)); + - Tuple response = WebClient.DownloadJson(url); - response.Item2.AddResponseInfo(response.Item1); return response.Item2; } public async Task DownloadDataAsync(string url) where T : BasicModel { - if (UseAuth) - WebClient.SetHeader("Authorization", TokenType + " " + AccessToken); - else - WebClient.RemoveHeader("Authorization"); + int triesLeft = RetryTimes + 1; + Error lastError = null; + + Tuple response = null; + do + { + if (response != null) { await Task.Delay(RetryAfter); } + response = await DownloadDataAltAsync(url); + + response.Item2.AddResponseInfo(response.Item1); + lastError = response.Item2.Error; + + triesLeft -= 1; + + } while (UseAutoRetry && triesLeft > 0 && lastError != null && RetryErrorCodes.Contains(lastError.Status)); + - Tuple response = await WebClient.DownloadJsonAsync(url); - response.Item2.AddResponseInfo(response.Item1); return response.Item2; }