public async Task <IActionResult> GetGenre( [FromQuery] int?id, [FromQuery] string name) { _logger.LogDebug("GetGenre"); Genre genre = null; if (id.HasValue) { genre = await DbContext.Genres .Include(g => g.Albums) .Where(g => g.GenreId == id.Value) .FirstOrDefaultAsync(); } else { genre = await DbContext.Genres .Include(g => g.Albums) .Where(g => g.Name == name) .FirstOrDefaultAsync(); } if (genre == null) { return(NotFound()); } var result = GenreJson.From(genre); return(new ObjectResult(result)); }
public async Task <List <GenreJson> > GetGenres() { var genres = await DbContext.Genres .Include(g => g.Albums) .ToListAsync(); return(GenreJson.From(genres)); }
public async Task <Genre> GetOrAddGenre(GenreJson genreJson) { var genreFromDb = await GetGenreFromDb(genreJson.Name); if (genreFromDb != null) { return(genreFromDb); } return(await AddGenreToDb(genreJson)); }
/// <summary> /// Get the favorites movies /// </summary> /// <param name="genre">The genre of the movies</param> /// <param name="ratingFilter">Used to filter by rating</param> /// <returns>Favorites movies</returns> public async Task <IEnumerable <MovieJson> > GetFavoritesMoviesAsync(GenreJson genre, double ratingFilter) { var watch = Stopwatch.StartNew(); var movies = new List <MovieJson>(); try { using (var context = new ApplicationDbContext()) { var movieHistory = await context.MovieHistory.FirstOrDefaultAsync(); if (movieHistory == null) { return(movies); } if (genre != null) { movies.AddRange(movieHistory.Movies.Where( p => p.IsFavorite && p.Genres.Any(g => g.Name == genre.EnglishName) && p.Rating >= ratingFilter) .Select(MovieEntityToMovieJson)); } else { movies.AddRange(movieHistory.Movies.Where( p => p.IsFavorite) .Select(MovieEntityToMovieJson)); } } } catch (Exception exception) { Logger.Error( $"GetFavoritesMoviesIdAsync: {exception.Message}"); } finally { watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Logger.Debug( $"GetFavoritesMoviesIdAsync in {elapsedMs} milliseconds."); } return(movies); }
private async Task <Genre> AddGenreToDb(GenreJson genreJson) { // TODO replace with system user id var genre = new Genre { MovieDbId = genreJson.Id, Name = genreJson.Name, CreatedAt = DateTime.UtcNow, CreatedBy = _systemGuid }; await _context.Genres.AddAsync(genre); await _context.SaveChangesAsync(); _logger.LogInformation("GenreJson [{0}] added to db with id [{1}] and MovieDbId [{2}]", genre.Name, genre.Id, genre.MovieDbId); return(genre); }
/// <summary> /// Load genres asynchronously /// </summary> public async Task LoadGenresAsync() { var genres = new ObservableCollection <GenreJson>( await _movieService.GetGenresAsync(_cancellationLoadingGenres.Token)); if (_cancellationLoadingGenres.IsCancellationRequested) { return; } genres.Insert(0, new GenreJson { TmdbGenre = new Genre { Id = int.MaxValue, Name = LocalizationProviderHelper.GetLocalizedValue <string>("AllLabel") }, EnglishName = string.Empty }); MovieGenres = genres; SelectedGenre = genres.ElementAt(0); }
public GenresViewModel(GenreJson genreJson) { MoviesGenres = new List <Genre>(genreJson.genres); }
/// <summary> /// Search movies by criteria /// </summary> /// <param name="criteria">Criteria used for search</param> /// <param name="page">Page to return</param> /// <param name="limit">The maximum number of movies to return</param> /// <param name="genre">The genre to filter</param> /// <param name="ratingFilter">Used to filter by rating</param> /// <param name="ct">Cancellation token</param> /// <returns>Searched movies and the number of movies found</returns> public async Task <(IEnumerable <MovieLightJson> movies, int nbMovies)> SearchMoviesAsync(string criteria, int page, int limit, GenreJson genre, double ratingFilter, CancellationToken ct) { var timeoutPolicy = Policy.TimeoutAsync(Constants.DefaultRequestTimeoutInSecond, TimeoutStrategy.Optimistic); try { return(await timeoutPolicy.ExecuteAsync(async cancellation => { var watch = Stopwatch.StartNew(); var wrapper = new MovieLightResponse(); if (limit < 1 || limit > 50) { limit = Constants.MaxMoviesPerPage; } if (page < 1) { page = 1; } var restClient = new RestClient(Constants.PopcornApi); var request = new RestRequest("/{segment}", Method.GET); request.AddUrlSegment("segment", "movies"); request.AddParameter("limit", limit); request.AddParameter("page", page); request.AddParameter("genre", genre != null ? genre.EnglishName : string.Empty); request.AddParameter("minimum_rating", Convert.ToInt32(ratingFilter)); request.AddParameter("query_term", criteria); request.AddParameter("sort_by", "year"); try { var response = await restClient.ExecuteTaskAsync(request, cancellation); if (response.ErrorException != null) { throw response.ErrorException; } wrapper = JsonSerializer.Deserialize <MovieLightResponse>(response.RawBytes); foreach (var movie in wrapper.Movies) { movie.TranslationLanguage = (await _tmdbService.GetClient).DefaultLanguage; } } catch (Exception exception) when(exception is TaskCanceledException) { Logger.Debug( "SearchMoviesAsync cancelled."); } catch (Exception exception) { Logger.Error( $"SearchMoviesAsync: {exception.Message}"); throw; } finally { watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Logger.Trace( $"SearchMoviesAsync ({criteria}, {page}, {limit}) in {elapsedMs} milliseconds."); } var result = wrapper?.Movies ?? new List <MovieLightJson>(); await ProcessTranslations(result); var nbResult = wrapper?.TotalMovies ?? 0; return (result, nbResult); }, ct)); } catch (Exception ex) { Logger.Error(ex); throw; } }
/// <summary> /// Get similar movies /// </summary> /// <param name="page">Page</param> /// <param name="limit">Limit</param> /// <param name="ratingFilter">Rating</param> /// <param name="sortBy">SortBy</param> /// <param name="imdbIds">The imdbIds of the movies, split by comma</param> /// <param name="ct">Cancellation token</param> /// <param name="genre">Genre</param> /// <returns>Similar movies</returns> public async Task <(IEnumerable <MovieLightJson> movies, int nbMovies)> GetSimilar(int page, int limit, double ratingFilter, string sortBy, IEnumerable <string> imdbIds, CancellationToken ct, GenreJson genre = null) { var timeoutPolicy = Policy.TimeoutAsync(Utils.Constants.DefaultRequestTimeoutInSecond, TimeoutStrategy.Pessimistic); try { return(await timeoutPolicy.ExecuteAsync(async cancellation => { var watch = Stopwatch.StartNew(); var wrapper = new MovieLightResponse(); if (limit < 1 || limit > 50) { limit = Utils.Constants.MaxMoviesPerPage; } if (page < 1) { page = 1; } var restClient = new RestClient(Utils.Constants.PopcornApi); var request = new RestRequest("/{segment}/{subsegment}", Method.POST); request.AddUrlSegment("segment", "movies"); request.AddUrlSegment("subsegment", "similar"); request.AddQueryParameter("limit", limit.ToString()); request.AddQueryParameter("page", page.ToString()); if (genre != null) { request.AddQueryParameter("genre", genre.EnglishName); } request.AddQueryParameter("minimum_rating", Convert.ToInt32(ratingFilter).ToString()); request.AddQueryParameter("sort_by", sortBy); request.AddJsonBody(imdbIds); try { var response = await restClient.ExecuteTaskAsync(request, cancellation); if (response.ErrorException != null) { throw response.ErrorException; } wrapper = JsonSerializer.Deserialize <MovieLightResponse>(response.RawBytes); foreach (var movie in wrapper.Movies) { movie.TranslationLanguage = (await _tmdbService.GetClient).DefaultLanguage; } } catch (Exception exception) when(exception is TaskCanceledException) { Logger.Debug( "GetMoviesByIds cancelled."); } catch (Exception exception) { Logger.Error( $"GetMoviesByIds: {exception.Message}"); throw; } finally { watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Logger.Trace( $"GetMoviesByIds ({string.Join(",", imdbIds)}) in {elapsedMs} milliseconds."); } var result = wrapper?.Movies ?? new List <MovieLightJson>(); await ProcessTranslations(result); var nbResult = wrapper?.TotalMovies ?? 0; return (result, nbResult); }, ct)); } catch (Exception ex) { Logger.Error(ex); return(new List <MovieLightJson>(), 0); } }
/// <summary> /// Search movies by criteria /// </summary> /// <param name="criteria">Criteria used for search</param> /// <param name="page">Page to return</param> /// <param name="limit">The maximum number of movies to return</param> /// <param name="genre">The genre to filter</param> /// <param name="ratingFilter">Used to filter by rating</param> /// <param name="ct">Cancellation token</param> /// <returns>Searched movies and the number of movies found</returns> public async Task <(IEnumerable <MovieLightJson> movies, int nbMovies)> SearchMoviesAsync(string criteria, int page, int limit, GenreJson genre, double ratingFilter, CancellationToken ct) { var watch = Stopwatch.StartNew(); var wrapper = new MovieLightResponse(); if (limit < 1 || limit > 50) { limit = Utils.Constants.MaxMoviesPerPage; } if (page < 1) { page = 1; } var restClient = new RestClient(Utils.Constants.PopcornApi); var request = new RestRequest("/{segment}", Method.GET); request.AddUrlSegment("segment", "movies"); request.AddParameter("limit", limit); request.AddParameter("page", page); if (genre != null) { request.AddParameter("genre", genre.EnglishName); } request.AddParameter("minimum_rating", Convert.ToInt32(ratingFilter)); request.AddParameter("query_term", criteria); try { var response = await restClient.ExecuteTaskAsync <MovieLightResponse>(request, ct); if (response.ErrorException != null) { throw response.ErrorException; } wrapper = response.Data; } catch (Exception exception) when(exception is TaskCanceledException) { Logger.Debug( "SearchMoviesAsync cancelled."); } catch (Exception exception) { Logger.Error( $"SearchMoviesAsync: {exception.Message}"); throw; } finally { watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Logger.Debug( $"SearchMoviesAsync ({criteria}, {page}, {limit}) in {elapsedMs} milliseconds."); } var result = wrapper?.Movies ?? new List <MovieLightJson>(); Task.Run(async() => { await ProcessTranslations(result).ConfigureAwait(false); }).ConfigureAwait(false); var nbResult = wrapper?.TotalMovies ?? 0; return(result, nbResult); }
/// <summary> /// Get popular shows by page /// </summary> /// <param name="page">Page to return</param> /// <param name="limit">The maximum number of shows to return</param> /// <param name="ct">Cancellation token</param> /// <param name="genre">The genre to filter</param> /// <param name="sortBy">The sort</param> /// <param name="ratingFilter">Used to filter by rating</param> /// <returns>Popular shows and the number of shows found</returns> public async Task <(IEnumerable <ShowLightJson> shows, int nbShows)> GetShowsAsync(int page, int limit, double ratingFilter, string sortBy, CancellationToken ct, GenreJson genre = null) { var timeoutPolicy = Policy.TimeoutAsync(Utils.Constants.DefaultRequestTimeoutInSecond, TimeoutStrategy.Pessimistic); try { return(await timeoutPolicy.ExecuteAsync(async cancellation => { var watch = Stopwatch.StartNew(); var wrapper = new ShowLightResponse(); if (limit < 1 || limit > 50) { limit = Utils.Constants.MaxShowsPerPage; } if (page < 1) { page = 1; } var restClient = new RestClient(Utils.Constants.PopcornApi); var request = new RestRequest("/{segment}", Method.GET); request.AddUrlSegment("segment", "shows"); request.AddParameter("limit", limit); request.AddParameter("page", page); request.AddParameter("genre", genre != null ? genre.EnglishName : string.Empty); request.AddParameter("minimum_rating", Convert.ToInt32(ratingFilter)); request.AddParameter("sort_by", sortBy); request.AddParameter("query_term", string.Empty); try { var response = await restClient.ExecuteTaskAsync(request, cancellation); if (response.ErrorException != null) { throw response.ErrorException; } wrapper = JsonSerializer.Deserialize <ShowLightResponse>(response.RawBytes); } catch (Exception exception) when(exception is TaskCanceledException) { Logger.Debug( "GetShowsAsync cancelled."); } catch (Exception exception) { Logger.Error( $"GetShowsAsync: {exception.Message}"); throw; } finally { watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Logger.Trace( $"GetShowsAsync ({page}, {limit}) in {elapsedMs} milliseconds."); } var shows = wrapper?.Shows ?? new List <ShowLightJson>(); var nbShows = wrapper?.TotalShows ?? 0; return (shows, nbShows); }, ct)); } catch (Exception ex) { Logger.Error(ex); throw; } }
/// <summary> /// Search shows by criteria /// </summary> /// <param name="criteria">Criteria used for search</param> /// <param name="page">Page to return</param> /// <param name="limit">The maximum number of movies to return</param> /// <param name="genre">The genre to filter</param> /// <param name="ratingFilter">Used to filter by rating</param> /// <param name="sortBy">Sort by</param> /// <param name="ct">Cancellation token</param> /// <returns>Searched shows and the number of movies found</returns> public async Task <(IEnumerable <ShowLightJson> shows, int nbShows)> SearchShowsAsync(string criteria, int page, int limit, GenreJson genre, double ratingFilter, string sortBy, CancellationToken ct) { var timeoutPolicy = Policy.TimeoutAsync(Constants.DefaultRequestTimeoutInSecond, TimeoutStrategy.Optimistic); try { return(await timeoutPolicy.ExecuteAsync(async cancellation => { var optionsBuilder = new DbContextOptionsBuilder <PopcornContext>(); optionsBuilder.UseSqlServer( (ConfigurationManager.GetSection("settings") as NameValueCollection)["SQLConnectionString"], builder => { builder.CommandTimeout(Convert.ToInt32(TimeSpan.FromSeconds(60).TotalSeconds)); builder.EnableRetryOnFailure(); }); await using var context = new PopcornContext(optionsBuilder.Options); var watch = Stopwatch.StartNew(); if (limit < 1 || limit > 50) { limit = Constants.MaxShowsPerPage; } if (page < 1) { page = 1; } var count = 0; var shows = new List <ShowLightJson>(); var skipParameter = new SqlParameter("@skip", (page - 1) * limit); var takeParameter = new SqlParameter("@take", limit); var ratingParameter = new SqlParameter("@rating", Convert.ToInt32(ratingFilter).ToString()); var queryParameter = new SqlParameter("@Keywords", string.Format(@"""{0}""", criteria)); var genreParameter = new SqlParameter("@genre", genre != null ? genre.EnglishName : string.Empty); var query = @" SELECT Show.Title, Show.Year, Rating.Percentage, Rating.Loved, Rating.Votes, Rating.Hated, Rating.Watching, Show.LastUpdated, Image.Banner, Image.Poster, Show.ImdbId, Show.TvdbId, Show.GenreNames, COUNT(*) OVER () as TotalCount FROM ShowSet AS Show INNER JOIN ImageShowSet AS Image ON Image.Id = Show.ImagesId INNER JOIN RatingSet AS Rating ON Rating.Id = Show.RatingId WHERE Show.NumSeasons <> 0"; if (ratingFilter >= 0 && ratingFilter <= 100) { query += @" AND Rating.Percentage >= @rating"; } if (!string.IsNullOrWhiteSpace(criteria)) { query += @" AND (CONTAINS(Title, @Keywords) OR CONTAINS(ImdbId, @Keywords) OR CONTAINS(TvdbId, @Keywords))"; } if (!string.IsNullOrWhiteSpace(genre != null ? genre.EnglishName : string.Empty)) { query += @" AND CONTAINS(GenreNames, @genre)"; } if (!string.IsNullOrWhiteSpace(sortBy)) { switch (sortBy) { case "title": query += " ORDER BY Show.Title ASC"; break; case "year": query += " ORDER BY Show.Year DESC"; break; case "rating": query += " ORDER BY Rating.Percentage DESC"; break; case "loved": query += " ORDER BY Rating.Loved DESC"; break; case "votes": query += " ORDER BY Rating.Votes DESC"; break; case "watching": query += " ORDER BY Rating.Watching DESC"; break; case "date_added": query += " ORDER BY Show.LastUpdated DESC"; break; default: query += " ORDER BY Show.LastUpdated DESC"; break; } } else { query += " ORDER BY Show.LastUpdated DESC"; } query += @" OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY"; try { await using var command = context.Database.GetDbConnection().CreateCommand(); command.CommandText = query; command.CommandType = CommandType.Text; command.Parameters.Add(skipParameter); command.Parameters.Add(takeParameter); command.Parameters.Add(ratingParameter); command.Parameters.Add(queryParameter); command.Parameters.Add(genreParameter); await context.Database.OpenConnectionAsync(cancellation); await using var reader = await command.ExecuteReaderAsync(cancellation); while (await reader.ReadAsync(cancellation)) { var show = new ShowLightJson { Title = !await reader.IsDBNullAsync(0, cancellation) ? reader.GetString(0) : string.Empty, Year = !await reader.IsDBNullAsync(1, cancellation) ? reader.GetInt32(1) : 0, Rating = new RatingJson { Percentage = !await reader.IsDBNullAsync(2, cancellation) ? reader.GetInt32(2) : 0, Loved = !await reader.IsDBNullAsync(3, cancellation) ? reader.GetInt32(3) : 0, Votes = !await reader.IsDBNullAsync(4, cancellation) ? reader.GetInt32(4) : 0, Hated = !await reader.IsDBNullAsync(5, cancellation) ? reader.GetInt32(5) : 0, Watching = !await reader.IsDBNullAsync(6, cancellation) ? reader.GetInt32(6) : 0 }, Images = new ImageShowJson { Banner = !await reader.IsDBNullAsync(8, cancellation) ? reader.GetString(8) : string.Empty, Poster = !await reader.IsDBNullAsync(9, cancellation) ? reader.GetString(9) : string.Empty, }, ImdbId = !await reader.IsDBNullAsync(10, cancellation) ? reader.GetString(10) : string.Empty, TvdbId = !await reader.IsDBNullAsync(11, cancellation) ? reader.GetString(11) : string.Empty, Genres = !await reader.IsDBNullAsync(12, cancellation) ? reader.GetString(12) : string.Empty }; shows.Add(show); count = !await reader.IsDBNullAsync(13, cancellation) ? reader.GetInt32(13) : 0; } } catch (Exception exception) when(exception is TaskCanceledException) { Logger.Debug( "SearchShowsAsync cancelled."); } catch (Exception exception) { Logger.Error( $"SearchShowsAsync: {exception.Message}"); throw; } finally { watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Logger.Trace( $"SearchShowsAsync ({criteria}, {page}, {limit}) in {elapsedMs} milliseconds."); } return (shows, count); }, ct)); } catch (Exception ex) { Logger.Error(ex); throw; } }
/// <summary> /// Get popular shows by page /// </summary> /// <param name="page">Page to return</param> /// <param name="limit">The maximum number of shows to return</param> /// <param name="ct">Cancellation token</param> /// <param name="genre">The genre to filter</param> /// <param name="sortBy">The sort</param> /// <param name="ratingFilter">Used to filter by rating</param> /// <returns>Popular shows and the number of shows found</returns> public async Task <(IEnumerable <ShowLightJson> shows, int nbShows)> GetShowsAsync(int page, int limit, double ratingFilter, string sortBy, CancellationToken ct, GenreJson genre = null) { return(await SearchShowsAsync(string.Empty, page, limit, genre, ratingFilter, sortBy, ct)); }
/// <summary> /// Get recent movies by page /// </summary> /// <param name="page">Page to return</param> /// <param name="limit">The maximum number of movies to return</param> /// <param name="ct">Cancellation token</param> /// <param name="genre">The genre to filter</param> /// <param name="ratingFilter">Used to filter by rating</param> /// <returns>Recent movies and the number of movies found</returns> public async Task <(IEnumerable <MovieJson> movies, int nbMovies)> GetRecentMoviesAsync(int page, int limit, double ratingFilter, CancellationToken ct, GenreJson genre = null) { var watch = Stopwatch.StartNew(); var wrapper = new MovieResponse(); if (limit < 1 || limit > 50) { limit = Constants.Constants.MaxMoviesPerPage; } if (page < 1) { page = 1; } var restClient = new RestClient(Constants.Constants.PopcornApi); var request = new RestRequest("/{segment}", Method.GET); request.AddUrlSegment("segment", "movies"); request.AddParameter("limit", limit); request.AddParameter("page", page); if (genre != null) { request.AddParameter("genre", genre.EnglishName); } request.AddParameter("minimum_rating", ratingFilter); request.AddParameter("sort_by", "seeds"); try { var response = await restClient.ExecuteGetTaskAsync <MovieResponse>(request, ct); if (response.ErrorException != null) { throw response.ErrorException; } wrapper = response.Data; } catch (Exception exception) when(exception is TaskCanceledException) { Logger.Debug( "GetRecentMoviesAsync cancelled."); } catch (Exception exception) { Logger.Error( $"GetRecentMoviesAsync: {exception.Message}"); throw; } finally { watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Logger.Debug( $"GetRecentMoviesAsync ({page}, {limit}) in {elapsedMs} milliseconds."); } var result = wrapper?.Movies ?? new List <MovieJson>(); await ProcessTranslations(result); var nbResult = wrapper?.TotalMovies ?? 0; return(result, nbResult); }
/// <summary> /// Get popular shows by page /// </summary> /// <param name="page">Page to return</param> /// <param name="limit">The maximum number of shows to return</param> /// <param name="ct">Cancellation token</param> /// <param name="genre">The genre to filter</param> /// <param name="sortBy">The sort</param> /// <param name="ratingFilter">Used to filter by rating</param> /// <returns>Popular shows and the number of shows found</returns> public async Task <(IEnumerable <ShowLightJson> shows, int nbShows)> GetShowsAsync(int page, int limit, double ratingFilter, string sortBy, CancellationToken ct, GenreJson genre = null) { var watch = Stopwatch.StartNew(); var wrapper = new ShowLightResponse(); if (limit < 1 || limit > 50) { limit = Utils.Constants.MaxShowsPerPage; } if (page < 1) { page = 1; } var restClient = new RestClient(Utils.Constants.PopcornApi); var request = new RestRequest("/{segment}", Method.GET); request.AddUrlSegment("segment", "shows"); request.AddParameter("limit", limit); request.AddParameter("page", page); if (genre != null) { request.AddParameter("genre", genre.EnglishName); } request.AddParameter("minimum_rating", Convert.ToInt32(ratingFilter)); request.AddParameter("sort_by", sortBy); try { var response = await restClient.ExecuteTaskAsync <ShowLightResponse>(request, ct).ConfigureAwait(false); if (response.ErrorException != null) { throw response.ErrorException; } wrapper = response.Data; } catch (Exception exception) when(exception is TaskCanceledException) { Logger.Debug( "GetShowsAsync cancelled."); } catch (Exception exception) { Logger.Error( $"GetShowsAsync: {exception.Message}"); throw; } finally { watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Logger.Debug( $"GetShowsAsync ({page}, {limit}) in {elapsedMs} milliseconds."); } var shows = wrapper?.Shows ?? new List <ShowLightJson>(); var nbShows = wrapper?.TotalShows ?? 0; return(shows, nbShows); }
/// <summary> /// Search movies by criteria /// </summary> /// <param name="criteria">Criteria used for search</param> /// <param name="page">Page to return</param> /// <param name="limit">The maximum number of movies to return</param> /// <param name="genre">The genre to filter</param> /// <param name="ratingFilter">Used to filter by rating</param> /// <param name="ct">Cancellation token</param> /// <returns>Searched movies and the number of movies found</returns> public async Task <Tuple <IEnumerable <MovieJson>, int> > SearchMoviesAsync(string criteria, int page, int limit, GenreJson genre, double ratingFilter, CancellationToken ct) { var watch = Stopwatch.StartNew(); var wrapper = new MovieResponse(); if (limit < 1 || limit > 50) { limit = Constants.MaxMoviesPerPage; } if (page < 1) { page = 1; } var restClient = new RestClient(Constants.PopcornApi); var request = new RestRequest("/{segment}", Method.GET); request.AddUrlSegment("segment", "movies"); request.AddParameter("limit", limit); request.AddParameter("page", page); if (genre != null) { request.AddParameter("genre", genre.EnglishName); } request.AddParameter("minimum_rating", ratingFilter); request.AddParameter("query_term", criteria); try { var response = await restClient.ExecuteGetTaskAsync <MovieResponse>(request, ct); if (response.ErrorException != null) { throw response.ErrorException; } wrapper = response.Data; } catch (Exception exception) when(exception is TaskCanceledException) { Logger.Debug( "SearchMoviesAsync cancelled."); } catch (Exception exception) { Logger.Error( $"SearchMoviesAsync: {exception.Message}"); throw; } finally { watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Logger.Debug( $"SearchMoviesAsync ({criteria}, {page}, {limit}) in {elapsedMs} milliseconds."); } var movies = wrapper.Movies ?? new List <MovieJson>(); Parallel.ForEach(movies, async movie => { await TranslateMovieAsync(movie); }); var nbMovies = wrapper.TotalMovies; return(new Tuple <IEnumerable <MovieJson>, int>(movies, nbMovies)); }