aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.ApiInteraction
diff options
context:
space:
mode:
authorLukePulverenti <luke.pulverenti@gmail.com>2013-02-20 20:33:05 -0500
committerLukePulverenti <luke.pulverenti@gmail.com>2013-02-20 20:33:05 -0500
commit767cdc1f6f6a63ce997fc9476911e2c361f9d402 (patch)
tree49add55976f895441167c66cfa95e5c7688d18ce /MediaBrowser.ApiInteraction
parent845554722efaed872948a9e0f7202e3ef52f1b6e (diff)
Pushing missing changes
Diffstat (limited to 'MediaBrowser.ApiInteraction')
-rw-r--r--MediaBrowser.ApiInteraction/ApiClient.cs1198
-rw-r--r--MediaBrowser.ApiInteraction/AsyncHttpClient.cs234
-rw-r--r--MediaBrowser.ApiInteraction/BaseApiClient.cs1260
-rw-r--r--MediaBrowser.ApiInteraction/BaseHttpApiClient.cs611
-rw-r--r--MediaBrowser.ApiInteraction/DataSerializer.cs143
-rw-r--r--MediaBrowser.ApiInteraction/IAsyncHttpClient.cs49
-rw-r--r--MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj158
-rw-r--r--MediaBrowser.ApiInteraction/Properties/AssemblyInfo.cs69
-rw-r--r--MediaBrowser.ApiInteraction/SerializationFormats.cs28
-rw-r--r--MediaBrowser.ApiInteraction/ServerDiscovery.cs63
-rw-r--r--MediaBrowser.ApiInteraction/packages.config7
11 files changed, 2543 insertions, 1277 deletions
diff --git a/MediaBrowser.ApiInteraction/ApiClient.cs b/MediaBrowser.ApiInteraction/ApiClient.cs
index 3d5ecde22b..1d7216f59a 100644
--- a/MediaBrowser.ApiInteraction/ApiClient.cs
+++ b/MediaBrowser.ApiInteraction/ApiClient.cs
@@ -1,18 +1,1180 @@
-using System.Net.Cache;
-using System.Net.Http;
-
-namespace MediaBrowser.ApiInteraction
-{
- public class ApiClient : BaseHttpApiClient
- {
- public ApiClient(HttpClientHandler handler)
- : base(handler)
- {
- }
-
- public ApiClient()
- : this(new WebRequestHandler { CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate) })
- {
- }
- }
-}
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.DTO;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.System;
+using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Weather;
+using MediaBrowser.Model.Web;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.ApiInteraction
+{
+ /// <summary>
+ /// Provides api methods centered around an HttpClient
+ /// </summary>
+ public class ApiClient : BaseApiClient
+ {
+ /// <summary>
+ /// Gets the HTTP client.
+ /// </summary>
+ /// <value>The HTTP client.</value>
+ protected IAsyncHttpClient HttpClient { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ApiClient" /> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="httpClient">The HTTP client.</param>
+ /// <exception cref="System.ArgumentNullException">httpClient</exception>
+ public ApiClient(ILogger logger, IAsyncHttpClient httpClient)
+ : base(logger)
+ {
+ if (httpClient == null)
+ {
+ throw new ArgumentNullException("httpClient");
+ }
+
+ HttpClient = httpClient;
+ }
+
+ /// <summary>
+ /// Sets the authorization header.
+ /// </summary>
+ /// <param name="header">The header.</param>
+ protected override void SetAuthorizationHeader(string header)
+ {
+ HttpClient.SetAuthorizationHeader(header);
+ }
+
+ /// <summary>
+ /// Gets an image stream based on a url
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <returns>Task{Stream}.</returns>
+ /// <exception cref="System.ArgumentNullException">url</exception>
+ public Task<Stream> GetImageStreamAsync(string url)
+ {
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new ArgumentNullException("url");
+ }
+
+ return HttpClient.GetStreamAsync(url, Logger, CancellationToken.None);
+ }
+
+ /// <summary>
+ /// Gets a BaseItem
+ /// </summary>
+ /// <param name="id">The id.</param>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task{DtoBaseItem}.</returns>
+ /// <exception cref="System.ArgumentNullException">id</exception>
+ public async Task<DtoBaseItem> GetItemAsync(string id, Guid userId)
+ {
+ if (string.IsNullOrEmpty(id))
+ {
+ throw new ArgumentNullException("id");
+ }
+
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var url = GetApiUrl("Users/" + userId + "/Items/" + id);
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<DtoBaseItem>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets the intros async.
+ /// </summary>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task{System.String[]}.</returns>
+ /// <exception cref="System.ArgumentNullException">id</exception>
+ public async Task<string[]> GetIntrosAsync(string itemId, Guid userId)
+ {
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/Intros");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<string[]>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets a BaseItem
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task{DtoBaseItem}.</returns>
+ /// <exception cref="System.ArgumentNullException">userId</exception>
+ public async Task<DtoBaseItem> GetRootFolderAsync(Guid userId)
+ {
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var url = GetApiUrl("Users/" + userId + "/Items/Root");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<DtoBaseItem>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets all Users
+ /// </summary>
+ /// <returns>Task{DtoUser[]}.</returns>
+ public async Task<DtoUser[]> GetAllUsersAsync()
+ {
+ var url = GetApiUrl("Users");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<DtoUser[]>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Queries for items
+ /// </summary>
+ /// <param name="query">The query.</param>
+ /// <returns>Task{ItemsResult}.</returns>
+ /// <exception cref="System.ArgumentNullException">query</exception>
+ public async Task<ItemsResult> GetItemsAsync(ItemQuery query)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException("query");
+ }
+
+ var url = GetItemListUrl(query);
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<ItemsResult>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets all People
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <param name="itemId">Optional itemId, to localize the search to a specific item or folder</param>
+ /// <param name="personTypes">Use this to limit results to specific person types</param>
+ /// <param name="startIndex">Used to skip over a given number of items. Use if paging.</param>
+ /// <param name="limit">The maximum number of items to return</param>
+ /// <param name="sortOrder">The sort order</param>
+ /// <param name="recursive">if set to true items will be searched recursively.</param>
+ /// <returns>Task{IbnItemsResult}.</returns>
+ /// <exception cref="System.ArgumentNullException">userId</exception>
+ public async Task<ItemsResult> GetAllPeopleAsync(
+ Guid userId,
+ string itemId = null,
+ IEnumerable<string> personTypes = null,
+ int? startIndex = null,
+ int? limit = null,
+ SortOrder? sortOrder = null,
+ bool recursive = false)
+ {
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var dict = new QueryStringDictionary();
+
+ dict.AddIfNotNull("startIndex", startIndex);
+ dict.AddIfNotNull("limit", limit);
+
+ dict.Add("recursive", recursive);
+
+ if (sortOrder.HasValue)
+ {
+ dict["sortOrder"] = sortOrder.Value.ToString();
+ }
+
+ dict.AddIfNotNull("personTypes", personTypes);
+
+ var url = string.IsNullOrEmpty(itemId) ? "Users/" + userId + "/Items/Root/Persons" : "Users/" + userId + "/Items/" + itemId + "/Persons";
+ url = GetApiUrl(url, dict);
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<ItemsResult>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets a studio
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <returns>Task{DtoBaseItem}.</returns>
+ /// <exception cref="System.ArgumentNullException">userId</exception>
+ public async Task<DtoBaseItem> GetStudioAsync(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ var url = GetApiUrl("Library/Studios/" + name);
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<DtoBaseItem>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets a genre
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <returns>Task{DtoBaseItem}.</returns>
+ /// <exception cref="System.ArgumentNullException">userId</exception>
+ public async Task<DtoBaseItem> GetGenreAsync(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ var url = GetApiUrl("Library/Genres/" + name);
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<DtoBaseItem>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Restarts the kernel or the entire server if necessary
+ /// If the server application is restarting this request will fail to return, even if
+ /// the operation is successful.
+ /// </summary>
+ /// <returns>Task.</returns>
+ public Task PerformPendingRestartAsync()
+ {
+ var url = GetApiUrl("System/Restart");
+
+ return PostAsync<EmptyRequestResult>(url, new QueryStringDictionary());
+ }
+
+ /// <summary>
+ /// Gets the system status async.
+ /// </summary>
+ /// <returns>Task{SystemInfo}.</returns>
+ public async Task<SystemInfo> GetSystemInfoAsync()
+ {
+ var url = GetApiUrl("System/Info");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<SystemInfo>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets a person
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <returns>Task{DtoBaseItem}.</returns>
+ /// <exception cref="System.ArgumentNullException">userId</exception>
+ public async Task<DtoBaseItem> GetPersonAsync(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ var url = GetApiUrl("Library/Persons/" + name);
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<DtoBaseItem>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets a year
+ /// </summary>
+ /// <param name="year">The year.</param>
+ /// <returns>Task{DtoBaseItem}.</returns>
+ /// <exception cref="System.ArgumentNullException">userId</exception>
+ public async Task<DtoBaseItem> GetYearAsync(int year)
+ {
+ var url = GetApiUrl("Library/Years/" + year);
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<DtoBaseItem>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets a list of plugins installed on the server
+ /// </summary>
+ /// <returns>Task{PluginInfo[]}.</returns>
+ public async Task<PluginInfo[]> GetInstalledPluginsAsync()
+ {
+ var url = GetApiUrl("Plugins");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<PluginInfo[]>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets a list of plugins installed on the server
+ /// </summary>
+ /// <param name="plugin">The plugin.</param>
+ /// <returns>Task{Stream}.</returns>
+ /// <exception cref="System.ArgumentNullException">plugin</exception>
+ public Task<Stream> GetPluginAssemblyAsync(PluginInfo plugin)
+ {
+ if (plugin == null)
+ {
+ throw new ArgumentNullException("plugin");
+ }
+
+ var url = GetApiUrl("Plugins/" + plugin.UniqueId + "/Assembly");
+
+ return HttpClient.GetStreamAsync(url, Logger, CancellationToken.None);
+ }
+
+ /// <summary>
+ /// Gets the current server configuration
+ /// </summary>
+ /// <returns>Task{ServerConfiguration}.</returns>
+ public async Task<ServerConfiguration> GetServerConfigurationAsync()
+ {
+ var url = GetApiUrl("System/Configuration");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<ServerConfiguration>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets the scheduled tasks.
+ /// </summary>
+ /// <returns>Task{TaskInfo[]}.</returns>
+ public async Task<TaskInfo[]> GetScheduledTasksAsync()
+ {
+ var url = GetApiUrl("ScheduledTasks");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<TaskInfo[]>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets the scheduled task async.
+ /// </summary>
+ /// <param name="id">The id.</param>
+ /// <returns>Task{TaskInfo}.</returns>
+ /// <exception cref="System.ArgumentNullException">id</exception>
+ public async Task<TaskInfo> GetScheduledTaskAsync(Guid id)
+ {
+ if (id == Guid.Empty)
+ {
+ throw new ArgumentNullException("id");
+ }
+
+ var url = GetApiUrl("ScheduledTasks/" + id);
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<TaskInfo>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets the plugin configuration file in plain text.
+ /// </summary>
+ /// <param name="pluginId">The plugin id.</param>
+ /// <returns>Task{Stream}.</returns>
+ /// <exception cref="System.ArgumentNullException">assemblyFileName</exception>
+ public async Task<Stream> GetPluginConfigurationFileAsync(Guid pluginId)
+ {
+ if (pluginId == Guid.Empty)
+ {
+ throw new ArgumentNullException("pluginId");
+ }
+
+ var url = GetApiUrl("Plugins/" + pluginId + "/ConfigurationFile");
+
+ return await HttpClient.GetStreamAsync(url, Logger, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Gets a user by id
+ /// </summary>
+ /// <param name="id">The id.</param>
+ /// <returns>Task{DtoUser}.</returns>
+ /// <exception cref="System.ArgumentNullException">id</exception>
+ public async Task<DtoUser> GetUserAsync(Guid id)
+ {
+ if (id == Guid.Empty)
+ {
+ throw new ArgumentNullException("id");
+ }
+
+ var url = GetApiUrl("Users/" + id);
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<DtoUser>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets the parental ratings async.
+ /// </summary>
+ /// <returns>Task{List{ParentalRating}}.</returns>
+ public async Task<List<ParentalRating>> GetParentalRatingsAsync()
+ {
+ var url = GetApiUrl("Localization/ParentalRatings");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<List<ParentalRating>>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets weather information for the default location as set in configuration
+ /// </summary>
+ /// <returns>Task{WeatherInfo}.</returns>
+ public async Task<WeatherInfo> GetWeatherInfoAsync()
+ {
+ var url = GetApiUrl("Weather");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<WeatherInfo>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets weather information for a specific location
+ /// Location can be a US zipcode, or "city,state", "city,state,country", "city,country"
+ /// It can also be an ip address, or "latitude,longitude"
+ /// </summary>
+ /// <param name="location">The location.</param>
+ /// <returns>Task{WeatherInfo}.</returns>
+ /// <exception cref="System.ArgumentNullException">location</exception>
+ public async Task<WeatherInfo> GetWeatherInfoAsync(string location)
+ {
+ if (string.IsNullOrEmpty(location))
+ {
+ throw new ArgumentNullException("location");
+ }
+
+ var dict = new QueryStringDictionary();
+
+ dict.Add("location", location);
+
+ var url = GetApiUrl("Weather", dict);
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<WeatherInfo>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets local trailers for an item
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <param name="itemId">The item id.</param>
+ /// <returns>Task{ItemsResult}.</returns>
+ /// <exception cref="System.ArgumentNullException">query</exception>
+ public async Task<DtoBaseItem[]> GetLocalTrailersAsync(Guid userId, string itemId)
+ {
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/LocalTrailers");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<DtoBaseItem[]>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets special features for an item
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <param name="itemId">The item id.</param>
+ /// <returns>Task{DtoBaseItem[]}.</returns>
+ /// <exception cref="System.ArgumentNullException">userId</exception>
+ public async Task<DtoBaseItem[]> GetSpecialFeaturesAsync(Guid userId, string itemId)
+ {
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/SpecialFeatures");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<DtoBaseItem[]>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets the cultures async.
+ /// </summary>
+ /// <returns>Task{CultureDto[]}.</returns>
+ public async Task<CultureDto[]> GetCulturesAsync()
+ {
+ var url = GetApiUrl("Localization/Cultures");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<CultureDto[]>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Gets the countries async.
+ /// </summary>
+ /// <returns>Task{CountryInfo[]}.</returns>
+ public async Task<CountryInfo[]> GetCountriesAsync()
+ {
+ var url = GetApiUrl("Localization/Countries");
+
+ using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<CountryInfo[]>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Marks an item as played or unplayed.
+ /// This should not be used to update playstate following playback.
+ /// There are separate playstate check-in methods for that. This should be used for a
+ /// separate option to reset playstate.
+ /// </summary>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="userId">The user id.</param>
+ /// <param name="wasPlayed">if set to <c>true</c> [was played].</param>
+ /// <returns>Task.</returns>
+ /// <exception cref="System.ArgumentNullException">itemId</exception>
+ public Task UpdatePlayedStatusAsync(string itemId, Guid userId, bool wasPlayed)
+ {
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var url = GetApiUrl("Users/" + userId + "/PlayedItems/" + itemId);
+
+ if (wasPlayed)
+ {
+ return PostAsync<EmptyRequestResult>(url, new Dictionary<string, string>());
+ }
+
+ return HttpClient.DeleteAsync(url, Logger, CancellationToken.None);
+ }
+
+ /// <summary>
+ /// Updates the favorite status async.
+ /// </summary>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="userId">The user id.</param>
+ /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
+ /// <returns>Task.</returns>
+ /// <exception cref="System.ArgumentNullException">itemId</exception>
+ public Task UpdateFavoriteStatusAsync(string itemId, Guid userId, bool isFavorite)
+ {
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var url = GetApiUrl("Users/" + userId + "/FavoriteItems/" + itemId);
+
+ if (isFavorite)
+ {
+ return PostAsync<EmptyRequestResult>(url, new Dictionary<string, string>());
+ }
+
+ return HttpClient.DeleteAsync(url, Logger, CancellationToken.None);
+ }
+
+ /// <summary>
+ /// Reports to the server that the user has begun playing an item
+ /// </summary>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task{DtoUserItemData}.</returns>
+ /// <exception cref="System.ArgumentNullException">itemId</exception>
+ public Task<DtoUserItemData> ReportPlaybackStartAsync(string itemId, Guid userId)
+ {
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var dict = new QueryStringDictionary();
+ dict.Add("id", itemId);
+ dict.Add("userId", userId);
+ dict.Add("type", "start");
+
+ var url = GetApiUrl("playbackcheckin", dict);
+
+ return PostAsync<DtoUserItemData>(url, new Dictionary<string, string>());
+ }
+
+ /// <summary>
+ /// Reports playback progress to the server
+ /// </summary>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="userId">The user id.</param>
+ /// <param name="positionTicks">The position ticks.</param>
+ /// <returns>Task{DtoUserItemData}.</returns>
+ /// <exception cref="System.ArgumentNullException">itemId</exception>
+ public Task<DtoUserItemData> ReportPlaybackProgressAsync(string itemId, Guid userId, long? positionTicks)
+ {
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var dict = new QueryStringDictionary();
+ dict.Add("id", itemId);
+ dict.Add("userId", userId);
+ dict.Add("type", "progress");
+
+ dict.AddIfNotNull("positionTicks", positionTicks);
+
+ var url = GetApiUrl("playbackcheckin", dict);
+
+ return PostAsync<DtoUserItemData>(url, new Dictionary<string, string>());
+ }
+
+ /// <summary>
+ /// Reports to the server that the user has stopped playing an item
+ /// </summary>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="userId">The user id.</param>
+ /// <param name="positionTicks">The position ticks.</param>
+ /// <returns>Task{DtoUserItemData}.</returns>
+ /// <exception cref="System.ArgumentNullException">itemId</exception>
+ public Task<DtoUserItemData> ReportPlaybackStoppedAsync(string itemId, Guid userId, long? positionTicks)
+ {
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var dict = new QueryStringDictionary();
+ dict.Add("id", itemId);
+ dict.Add("userId", userId);
+ dict.Add("type", "stopped");
+
+ dict.AddIfNotNull("positionTicks", positionTicks);
+
+ var url = GetApiUrl("playbackcheckin", dict);
+
+ return PostAsync<DtoUserItemData>(url, new Dictionary<string, string>());
+ }
+
+ /// <summary>
+ /// Clears a user's rating for an item
+ /// </summary>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task{DtoUserItemData}.</returns>
+ /// <exception cref="System.ArgumentNullException">itemId</exception>
+ public Task ClearUserItemRatingAsync(string itemId, Guid userId)
+ {
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/Rating");
+
+ return HttpClient.DeleteAsync(url, Logger, CancellationToken.None);
+ }
+
+ /// <summary>
+ /// Updates a user's rating for an item, based on likes or dislikes
+ /// </summary>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="userId">The user id.</param>
+ /// <param name="likes">if set to <c>true</c> [likes].</param>
+ /// <returns>Task{DtoUserItemData}.</returns>
+ /// <exception cref="System.ArgumentNullException">itemId</exception>
+ public Task<DtoUserItemData> UpdateUserItemRatingAsync(string itemId, Guid userId, bool likes)
+ {
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var dict = new QueryStringDictionary { };
+
+ dict.Add("likes", likes);
+
+ var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/Rating", dict);
+
+ return PostAsync<DtoUserItemData>(url, new Dictionary<string, string>());
+ }
+
+ /// <summary>
+ /// Authenticates a user and returns the result
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <param name="password">The password.</param>
+ /// <exception cref="System.ArgumentNullException">userId</exception>
+ public Task AuthenticateUserAsync(Guid userId, string password)
+ {
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var url = GetApiUrl("Users/" + userId + "/Authenticate");
+
+ var args = new Dictionary<string, string>();
+
+ if (!string.IsNullOrEmpty(password))
+ {
+ args["password"] = password;
+ }
+
+ return PostAsync<EmptyRequestResult>(url, args);
+ }
+
+ /// <summary>
+ /// Uploads the user image async.
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <param name="imageType">Type of the image.</param>
+ /// <param name="image">The image.</param>
+ /// <returns>Task{RequestResult}.</returns>
+ /// <exception cref="System.NotImplementedException"></exception>
+ public Task UploadUserImageAsync(Guid userId, ImageType imageType, Stream image)
+ {
+ // Implement when needed
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Updates the server configuration async.
+ /// </summary>
+ /// <param name="configuration">The configuration.</param>
+ /// <returns>Task.</returns>
+ /// <exception cref="System.ArgumentNullException">configuration</exception>
+ public Task UpdateServerConfigurationAsync(ServerConfiguration configuration)
+ {
+ if (configuration == null)
+ {
+ throw new ArgumentNullException("configuration");
+ }
+
+ var url = GetApiUrl("System/Configuration");
+
+ return PostAsync<ServerConfiguration, EmptyRequestResult>(url, configuration);
+ }
+
+ /// <summary>
+ /// Updates the scheduled task triggers.
+ /// </summary>
+ /// <param name="id">The id.</param>
+ /// <param name="triggers">The triggers.</param>
+ /// <returns>Task{RequestResult}.</returns>
+ /// <exception cref="System.ArgumentNullException">id</exception>
+ public Task UpdateScheduledTaskTriggersAsync(Guid id, TaskTriggerInfo[] triggers)
+ {
+ if (id == Guid.Empty)
+ {
+ throw new ArgumentNullException("id");
+ }
+
+ if (triggers == null)
+ {
+ throw new ArgumentNullException("triggers");
+ }
+
+ var url = GetApiUrl("ScheduledTasks/" + id + "/Triggers");
+
+ return PostAsync<TaskTriggerInfo[], EmptyRequestResult>(url, triggers);
+ }
+
+ /// <summary>
+ /// Adds a virtual folder to either the default view or a user view
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task{RequestResult}.</returns>
+ /// <exception cref="System.ArgumentNullException">name</exception>
+ public Task AddVirtualFolderAsync(string name, Guid? userId = null)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ var dict = new QueryStringDictionary();
+ dict.Add("name", name);
+ dict.Add("action", "AddVirtualFolder");
+
+ dict.AddIfNotNull("userId", userId);
+
+ var url = GetApiUrl("UpdateMediaLibrary", dict);
+
+ return PostAsync<EmptyRequestResult>(url, new Dictionary<string, string>());
+ }
+
+ /// <summary>
+ /// Removes a virtual folder, within either the default view or a user view
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task.</returns>
+ /// <exception cref="System.ArgumentNullException">name</exception>
+ public Task RemoveVirtualFolderAsync(string name, Guid? userId = null)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ var dict = new QueryStringDictionary();
+ dict.Add("name", name);
+ dict.Add("action", "RemoveVirtualFolder");
+
+ dict.AddIfNotNull("userId", userId);
+
+ var url = GetApiUrl("UpdateMediaLibrary", dict);
+
+ return PostAsync<EmptyRequestResult>(url, new Dictionary<string, string>());
+ }
+
+ /// <summary>
+ /// Renames a virtual folder, within either the default view or a user view
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="newName">The new name.</param>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task.</returns>
+ /// <exception cref="System.ArgumentNullException">name</exception>
+ public Task RenameVirtualFolderAsync(string name, string newName, Guid? userId = null)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ if (string.IsNullOrEmpty(newName))
+ {
+ throw new ArgumentNullException("newName");
+ }
+
+ var dict = new QueryStringDictionary();
+ dict.Add("name", name);
+ dict.Add("newName", newName);
+ dict.Add("action", "RenameVirtualFolder");
+
+ dict.AddIfNotNull("userId", userId);
+
+ var url = GetApiUrl("UpdateMediaLibrary", dict);
+
+ return PostAsync<EmptyRequestResult>(url, new Dictionary<string, string>());
+ }
+
+ /// <summary>
+ /// Adds a media path to a virtual folder, within either the default view or a user view
+ /// </summary>
+ /// <param name="virtualFolderName">Name of the virtual folder.</param>
+ /// <param name="mediaPath">The media path.</param>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task.</returns>
+ /// <exception cref="System.ArgumentNullException">virtualFolderName</exception>
+ public Task AddMediaPathAsync(string virtualFolderName, string mediaPath, Guid? userId = null)
+ {
+ if (string.IsNullOrEmpty(virtualFolderName))
+ {
+ throw new ArgumentNullException("virtualFolderName");
+ }
+
+ if (string.IsNullOrEmpty(mediaPath))
+ {
+ throw new ArgumentNullException("mediaPath");
+ }
+
+ var dict = new QueryStringDictionary();
+ dict.Add("virtualFolderName", virtualFolderName);
+ dict.Add("mediaPath", mediaPath);
+ dict.Add("action", "AddMediaPath");
+
+ dict.AddIfNotNull("userId", userId);
+
+ var url = GetApiUrl("UpdateMediaLibrary", dict);
+
+ return PostAsync<EmptyRequestResult>(url, new Dictionary<string, string>());
+ }
+
+ /// <summary>
+ /// Removes a media path from a virtual folder, within either the default view or a user view
+ /// </summary>
+ /// <param name="virtualFolderName">Name of the virtual folder.</param>
+ /// <param name="mediaPath">The media path.</param>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task.</returns>
+ /// <exception cref="System.ArgumentNullException">virtualFolderName</exception>
+ public Task RemoveMediaPathAsync(string virtualFolderName, string mediaPath, Guid? userId = null)
+ {
+ if (string.IsNullOrEmpty(virtualFolderName))
+ {
+ throw new ArgumentNullException("virtualFolderName");
+ }
+
+ if (string.IsNullOrEmpty(mediaPath))
+ {
+ throw new ArgumentNullException("mediaPath");
+ }
+
+ var dict = new QueryStringDictionary();
+
+ dict.Add("virtualFolderName", virtualFolderName);
+ dict.Add("mediaPath", mediaPath);
+ dict.Add("action", "RemoveMediaPath");
+
+ dict.AddIfNotNull("userId", userId);
+
+ var url = GetApiUrl("UpdateMediaLibrary", dict);
+
+ return PostAsync<EmptyRequestResult>(url, new Dictionary<string, string>());
+ }
+
+ /// <summary>
+ /// Updates display preferences for a user
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="displayPreferences">The display preferences.</param>
+ /// <returns>Task{DisplayPreferences}.</returns>
+ /// <exception cref="System.ArgumentNullException">userId</exception>
+ public Task UpdateDisplayPreferencesAsync(Guid userId, string itemId, DisplayPreferences displayPreferences)
+ {
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (displayPreferences == null)
+ {
+ throw new ArgumentNullException("displayPreferences");
+ }
+
+ var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/DisplayPreferences");
+
+ return PostAsync<DisplayPreferences, EmptyRequestResult>(url, displayPreferences);
+ }
+
+ /// <summary>
+ /// Posts a set of data to a url, and deserializes the return stream into T
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="url">The URL.</param>
+ /// <param name="args">The args.</param>
+ /// <returns>Task{``0}.</returns>
+ private Task<T> PostAsync<T>(string url, Dictionary<string, string> args)
+ where T : class
+ {
+ return PostAsync<T>(url, args, SerializationFormat);
+ }
+
+ /// <summary>
+ /// Posts a set of data to a url, and deserializes the return stream into T
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="url">The URL.</param>
+ /// <param name="args">The args.</param>
+ /// <param name="serializationFormat">The serialization format.</param>
+ /// <returns>Task{``0}.</returns>
+ private async Task<T> PostAsync<T>(string url, Dictionary<string, string> args, SerializationFormats serializationFormat)
+ where T : class
+ {
+ url = AddDataFormat(url, serializationFormat);
+
+ // Create the post body
+ var strings = args.Keys.Select(key => string.Format("{0}={1}", key, args[key]));
+ var postContent = string.Join("&", strings.ToArray());
+
+ const string contentType = "application/x-www-form-urlencoded";
+
+ using (var stream = await HttpClient.PostAsync(url, contentType, postContent, Logger, CancellationToken.None).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<T>(stream);
+ }
+ }
+
+ /// <summary>
+ /// Posts an object of type TInputType to a given url, and deserializes the response into an object of type TOutputType
+ /// </summary>
+ /// <typeparam name="TInputType">The type of the T input type.</typeparam>
+ /// <typeparam name="TOutputType">The type of the T output type.</typeparam>
+ /// <param name="url">The URL.</param>
+ /// <param name="obj">The obj.</param>
+ /// <returns>Task{``1}.</returns>
+ private Task<TOutputType> PostAsync<TInputType, TOutputType>(string url, TInputType obj)
+ where TOutputType : class
+ {
+ return PostAsync<TInputType, TOutputType>(url, obj, SerializationFormat);
+ }
+
+ /// <summary>
+ /// Posts an object of type TInputType to a given url, and deserializes the response into an object of type TOutputType
+ /// </summary>
+ /// <typeparam name="TInputType">The type of the T input type.</typeparam>
+ /// <typeparam name="TOutputType">The type of the T output type.</typeparam>
+ /// <param name="url">The URL.</param>
+ /// <param name="obj">The obj.</param>
+ /// <param name="serializationFormat">The serialization format.</param>
+ /// <returns>Task{``1}.</returns>
+ private async Task<TOutputType> PostAsync<TInputType, TOutputType>(string url, TInputType obj, SerializationFormats serializationFormat)
+ where TOutputType : class
+ {
+ url = AddDataFormat(url, serializationFormat);
+
+ const string contentType = "application/x-www-form-urlencoded";
+
+ var postContent = DataSerializer.SerializeToJsonString(obj);
+
+ using (var stream = await HttpClient.PostAsync(url, contentType, postContent, Logger, CancellationToken.None).ConfigureAwait(false))
+ {
+ return DeserializeFromStream<TOutputType>(stream);
+ }
+ }
+
+ /// <summary>
+ /// This is a helper around getting a stream from the server that contains serialized data
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <returns>Task{Stream}.</returns>
+ public Task<Stream> GetSerializedStreamAsync(string url)
+ {
+ return GetSerializedStreamAsync(url, SerializationFormat);
+ }
+
+ /// <summary>
+ /// This is a helper around getting a stream from the server that contains serialized data
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="serializationFormat">The serialization format.</param>
+ /// <returns>Task{Stream}.</returns>
+ public Task<Stream> GetSerializedStreamAsync(string url, SerializationFormats serializationFormat)
+ {
+ url = AddDataFormat(url, serializationFormat);
+
+ return HttpClient.GetStreamAsync(url, Logger, CancellationToken.None);
+ }
+
+
+ /// <summary>
+ /// Adds the data format.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="serializationFormat">The serialization format.</param>
+ /// <returns>System.String.</returns>
+ private string AddDataFormat(string url, SerializationFormats serializationFormat)
+ {
+ var format = serializationFormat == SerializationFormats.Protobuf ? "x-protobuf" : serializationFormat.ToString();
+
+ if (url.IndexOf('?') == -1)
+ {
+ url += "?format=" + format;
+ }
+ else
+ {
+ url += "&format=" + format;
+ }
+
+ return url;
+ }
+ }
+}
diff --git a/MediaBrowser.ApiInteraction/AsyncHttpClient.cs b/MediaBrowser.ApiInteraction/AsyncHttpClient.cs
new file mode 100644
index 0000000000..ec8176d8a1
--- /dev/null
+++ b/MediaBrowser.ApiInteraction/AsyncHttpClient.cs
@@ -0,0 +1,234 @@
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.ApiInteraction
+{
+ /// <summary>
+ /// Class AsyncHttpClient
+ /// </summary>
+ public class AsyncHttpClient : IAsyncHttpClient
+ {
+ /// <summary>
+ /// Gets or sets the HTTP client.
+ /// </summary>
+ /// <value>The HTTP client.</value>
+ private HttpClient HttpClient { get; set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ApiClient" /> class.
+ /// </summary>
+ public AsyncHttpClient(HttpMessageHandler handler)
+ {
+ HttpClient = new HttpClient(handler);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ApiClient" /> class.
+ /// </summary>
+ public AsyncHttpClient()
+ {
+ HttpClient = new HttpClient();
+ }
+
+ /// <summary>
+ /// Gets the stream async.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
+ public async Task<Stream> GetStreamAsync(string url, ILogger logger, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ logger.Debug("Sending Http Get to {0}", url);
+
+ try
+ {
+ var msg = await HttpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
+
+ EnsureSuccessStatusCode(msg);
+
+ return await msg.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ }
+ catch (HttpRequestException ex)
+ {
+ logger.ErrorException("Error getting response from " + url, ex);
+
+ throw new HttpException(ex.Message, ex);
+ }
+ catch (OperationCanceledException ex)
+ {
+ throw GetCancellationException(url, cancellationToken, ex, logger);
+ }
+ catch (Exception ex)
+ {
+ logger.ErrorException("Error requesting {0}", ex, url);
+
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Posts the async.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="contentType">Type of the content.</param>
+ /// <param name="postContent">Content of the post.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
+ public async Task<Stream> PostAsync(string url, string contentType, string postContent, ILogger logger, CancellationToken cancellationToken)
+ {
+ logger.Debug("Sending Http Post to {0}", url);
+
+ var content = new StringContent(postContent, Encoding.UTF8, contentType);
+
+ try
+ {
+ var msg = await HttpClient.PostAsync(url, content).ConfigureAwait(false);
+
+ EnsureSuccessStatusCode(msg);
+
+ return await msg.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ }
+ catch (HttpRequestException ex)
+ {
+ logger.ErrorException("Error getting response from " + url, ex);
+
+ throw new HttpException(ex.Message, ex);
+ }
+ catch (OperationCanceledException ex)
+ {
+ throw GetCancellationException(url, cancellationToken, ex, logger);
+ }
+ catch (Exception ex)
+ {
+ logger.ErrorException("Error posting {0}", ex, url);
+
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Deletes the async.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ public async Task DeleteAsync(string url, ILogger logger, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ logger.Debug("Sending Http Delete to {0}", url);
+
+ try
+ {
+ using (var msg = await HttpClient.DeleteAsync(url, cancellationToken).ConfigureAwait(false))
+ {
+ EnsureSuccessStatusCode(msg);
+ }
+ }
+ catch (HttpRequestException ex)
+ {
+ logger.ErrorException("Error getting response from " + url, ex);
+
+ throw new HttpException(ex.Message, ex);
+ }
+ catch (OperationCanceledException ex)
+ {
+ throw GetCancellationException(url, cancellationToken, ex, logger);
+ }
+ catch (Exception ex)
+ {
+ logger.ErrorException("Error requesting {0}", ex, url);
+
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Throws the cancellation exception.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="exception">The exception.</param>
+ /// <param name="logger">The logger.</param>
+ /// <returns>Exception.</returns>
+ private Exception GetCancellationException(string url, CancellationToken cancellationToken, OperationCanceledException exception, ILogger logger)
+ {
+ // If the HttpClient's timeout is reached, it will cancel the Task internally
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ var msg = string.Format("Connection to {0} timed out", url);
+
+ logger.Error(msg);
+
+ // Throw an HttpException so that the caller doesn't think it was cancelled by user code
+ return new HttpException(msg, exception) { IsTimedOut = true };
+ }
+
+ return exception;
+ }
+
+ /// <summary>
+ /// Ensures the success status code.
+ /// </summary>
+ /// <param name="response">The response.</param>
+ /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
+ private void EnsureSuccessStatusCode(HttpResponseMessage response)
+ {
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new HttpException(response.ReasonPhrase) { StatusCode = response.StatusCode };
+ }
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ HttpClient.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Sets the authorization header that should be supplied on every request
+ /// </summary>
+ /// <param name="header">The header.</param>
+ /// <exception cref="System.NotImplementedException"></exception>
+ public void SetAuthorizationHeader(string header)
+ {
+ if (string.IsNullOrEmpty(header))
+ {
+ HttpClient.DefaultRequestHeaders.Remove("Authorization");
+ }
+ else
+ {
+ HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("MediaBrowser", header);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.ApiInteraction/BaseApiClient.cs b/MediaBrowser.ApiInteraction/BaseApiClient.cs
index 466869c765..67ae3da759 100644
--- a/MediaBrowser.ApiInteraction/BaseApiClient.cs
+++ b/MediaBrowser.ApiInteraction/BaseApiClient.cs
@@ -1,446 +1,814 @@
-using MediaBrowser.Model.DTO;
-using MediaBrowser.Model.Entities;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-
-namespace MediaBrowser.ApiInteraction
-{
- /// <summary>
- /// Provides api methods that are usable on all platforms
- /// </summary>
- public abstract class BaseApiClient : IDisposable
- {
- protected BaseApiClient()
- {
- DataSerializer.Configure();
- }
-
- /// <summary>
- /// Gets or sets the server host name (myserver or 192.168.x.x)
- /// </summary>
- public string ServerHostName { get; set; }
-
- /// <summary>
- /// Gets or sets the port number used by the API
- /// </summary>
- public int ServerApiPort { get; set; }
-
- /// <summary>
- /// Gets the current api url based on hostname and port.
- /// </summary>
- protected string ApiUrl
- {
- get
- {
- return string.Format("http://{0}:{1}/mediabrowser/api", ServerHostName, ServerApiPort);
- }
- }
-
- /// <summary>
- /// Gets the default data format to request from the server
- /// </summary>
- protected SerializationFormats SerializationFormat
- {
- get
- {
- return SerializationFormats.Protobuf;
- }
- }
-
- /// <summary>
- /// Gets an image url that can be used to download an image from the api
- /// </summary>
- /// <param name="itemId">The Id of the item</param>
- /// <param name="imageType">The type of image requested</param>
- /// <param name="imageIndex">The image index, if there are multiple. Currently only applies to backdrops. Supply null or 0 for first backdrop.</param>
- /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
- /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
- /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
- /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
- /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
- public string GetImageUrl(Guid itemId, ImageType imageType, int? imageIndex = null, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
- {
- string url = ApiUrl + "/image";
-
- url += "?id=" + itemId.ToString();
- url += "&type=" + imageType.ToString();
-
- if (imageIndex.HasValue)
- {
- url += "&index=" + imageIndex;
- }
- if (width.HasValue)
- {
- url += "&width=" + width;
- }
- if (height.HasValue)
- {
- url += "&height=" + height;
- }
- if (maxWidth.HasValue)
- {
- url += "&maxWidth=" + maxWidth;
- }
- if (maxHeight.HasValue)
- {
- url += "&maxHeight=" + maxHeight;
- }
- if (quality.HasValue)
- {
- url += "&quality=" + quality;
- }
-
- return url;
- }
-
- /// <summary>
- /// Gets an image url that can be used to download an image from the api
- /// </summary>
- /// <param name="userId">The Id of the user</param>
- /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
- /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
- /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
- /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
- /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
- public string GetUserImageUrl(Guid userId, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
- {
- string url = ApiUrl + "/image";
-
- url += "?userId=" + userId.ToString();
-
- if (width.HasValue)
- {
- url += "&width=" + width;
- }
- if (height.HasValue)
- {
- url += "&height=" + height;
- }
- if (maxWidth.HasValue)
- {
- url += "&maxWidth=" + maxWidth;
- }
- if (maxHeight.HasValue)
- {
- url += "&maxHeight=" + maxHeight;
- }
- if (quality.HasValue)
- {
- url += "&quality=" + quality;
- }
-
- return url;
- }
-
- /// <summary>
- /// Gets an image url that can be used to download an image from the api
- /// </summary>
- /// <param name="name">The name of the person</param>
- /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
- /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
- /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
- /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
- /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
- public string GetPersonImageUrl(string name, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
- {
- string url = ApiUrl + "/image";
-
- url += "?personname=" + name;
-
- if (width.HasValue)
- {
- url += "&width=" + width;
- }
- if (height.HasValue)
- {
- url += "&height=" + height;
- }
- if (maxWidth.HasValue)
- {
- url += "&maxWidth=" + maxWidth;
- }
- if (maxHeight.HasValue)
- {
- url += "&maxHeight=" + maxHeight;
- }
- if (quality.HasValue)
- {
- url += "&quality=" + quality;
- }
-
- return url;
- }
-
- /// <summary>
- /// Gets an image url that can be used to download an image from the api
- /// </summary>
- /// <param name="year">The year</param>
- /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
- /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
- /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
- /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
- /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
- public string GetYearImageUrl(int year, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
- {
- string url = ApiUrl + "/image";
-
- url += "?year=" + year;
-
- if (width.HasValue)
- {
- url += "&width=" + width;
- }
- if (height.HasValue)
- {
- url += "&height=" + height;
- }
- if (maxWidth.HasValue)
- {
- url += "&maxWidth=" + maxWidth;
- }
- if (maxHeight.HasValue)
- {
- url += "&maxHeight=" + maxHeight;
- }
- if (quality.HasValue)
- {
- url += "&quality=" + quality;
- }
-
- return url;
- }
-
- /// <summary>
- /// Gets an image url that can be used to download an image from the api
- /// </summary>
- /// <param name="name">The name of the genre</param>
- /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
- /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
- /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
- /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
- /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
- public string GetGenreImageUrl(string name, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
- {
- string url = ApiUrl + "/image";
-
- url += "?genre=" + name;
-
- if (width.HasValue)
- {
- url += "&width=" + width;
- }
- if (height.HasValue)
- {
- url += "&height=" + height;
- }
- if (maxWidth.HasValue)
- {
- url += "&maxWidth=" + maxWidth;
- }
- if (maxHeight.HasValue)
- {
- url += "&maxHeight=" + maxHeight;
- }
- if (quality.HasValue)
- {
- url += "&quality=" + quality;
- }
-
- return url;
- }
-
- /// <summary>
- /// Gets an image url that can be used to download an image from the api
- /// </summary>
- /// <param name="name">The name of the studio</param>
- /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
- /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
- /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
- /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
- /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
- public string GetStudioImageUrl(string name, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
- {
- string url = ApiUrl + "/image";
-
- url += "?studio=" + name;
-
- if (width.HasValue)
- {
- url += "&width=" + width;
- }
- if (height.HasValue)
- {
- url += "&height=" + height;
- }
- if (maxWidth.HasValue)
- {
- url += "&maxWidth=" + maxWidth;
- }
- if (maxHeight.HasValue)
- {
- url += "&maxHeight=" + maxHeight;
- }
- if (quality.HasValue)
- {
- url += "&quality=" + quality;
- }
-
- return url;
- }
-
- /// <summary>
- /// This is a helper to get a list of backdrop url's from a given ApiBaseItemWrapper. If the actual item does not have any backdrops it will return backdrops from the first parent that does.
- /// </summary>
- /// <param name="item">A given item.</param>
- /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
- /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
- /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
- /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
- /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
- public string[] GetBackdropImageUrls(DtoBaseItem item, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
- {
- Guid? backdropItemId;
- int backdropCount;
-
- if (item.BackdropCount == 0)
- {
- backdropItemId = item.ParentBackdropItemId;
- backdropCount = item.ParentBackdropCount ?? 0;
- }
- else
- {
- backdropItemId = item.Id;
- backdropCount = item.BackdropCount;
- }
-
- if (backdropItemId == null)
- {
- return new string[] { };
- }
-
- var files = new string[backdropCount];
-
- for (int i = 0; i < backdropCount; i++)
- {
- files[i] = GetImageUrl(backdropItemId.Value, ImageType.Backdrop, i, width, height, maxWidth, maxHeight, quality);
- }
-
- return files;
- }
-
- /// <summary>
- /// This is a helper to get the logo image url from a given ApiBaseItemWrapper. If the actual item does not have a logo, it will return the logo from the first parent that does, or null.
- /// </summary>
- /// <param name="item">A given item.</param>
- /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
- /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
- /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
- /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
- /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
- public string GetLogoImageUrl(DtoBaseItem item, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
- {
- Guid? logoItemId = item.HasLogo ? item.Id : item.ParentLogoItemId;
-
- if (logoItemId.HasValue)
- {
- return GetImageUrl(logoItemId.Value, ImageType.Logo, null, width, height, maxWidth, maxHeight, quality);
- }
-
- return null;
- }
-
- /// <summary>
- /// Gets the url needed to stream an audio file
- /// </summary>
- /// <param name="itemId">The id of the item</param>
- /// <param name="supportedOutputFormats">List all the output formats the decice is capable of playing. The more, the better, as it will decrease the likelyhood of having to encode, which will put a load on the server.</param>
- /// <param name="maxAudioChannels">The maximum number of channels that the device can play. Omit this if it doesn't matter. Phones and tablets should generally specify 2.</param>
- /// <param name="maxAudioSampleRate">The maximum sample rate that the device can play. This should generally be omitted. The server will default this to 44100, so only override if a different max is needed.</param>
- public string GetAudioStreamUrl(Guid itemId, IEnumerable<AudioOutputFormats> supportedOutputFormats, int? maxAudioChannels = null, int? maxAudioSampleRate = null)
- {
- string url = ApiUrl + "/audio?id=" + itemId;
-
- url += "&outputformats=" + string.Join(",", supportedOutputFormats.Select(s => s.ToString()).ToArray());
-
- if (maxAudioChannels.HasValue)
- {
- url += "&audiochannels=" + maxAudioChannels.Value;
- }
-
- if (maxAudioSampleRate.HasValue)
- {
- url += "&audiosamplerate=" + maxAudioSampleRate.Value;
- }
-
- return url;
- }
-
- /// <summary>
- /// Gets the url needed to stream a video file
- /// </summary>
- /// <param name="itemId">The id of the item</param>
- /// <param name="supportedOutputFormats">List all the output formats the decice is capable of playing. The more, the better, as it will decrease the likelyhood of having to encode, which will put a load on the server.</param>
- /// <param name="maxAudioChannels">The maximum number of channels that the device can play. Omit this if it doesn't matter. Phones and tablets should generally specify 2.</param>
- /// <param name="maxAudioSampleRate">The maximum sample rate that the device can play. This should generally be omitted. The server will default this to 44100, so only override if a different max is needed.</param>
- /// <param name="width">Specify this is a fixed video width is required</param>
- /// <param name="height">Specify this is a fixed video height is required</param>
- /// <param name="maxWidth">Specify this is a max video width is required</param>
- /// <param name="maxHeight">Specify this is a max video height is required</param>
- public string GetVideoStreamUrl(Guid itemId,
- IEnumerable<VideoOutputFormats> supportedOutputFormats,
- int? maxAudioChannels = null,
- int? maxAudioSampleRate = null,
- int? width = null,
- int? height = null,
- int? maxWidth = null,
- int? maxHeight = null)
- {
- string url = ApiUrl + "/video?id=" + itemId;
-
- url += "&outputformats=" + string.Join(",", supportedOutputFormats.Select(s => s.ToString()).ToArray());
-
- if (maxAudioChannels.HasValue)
- {
- url += "&audiochannels=" + maxAudioChannels.Value;
- }
-
- if (maxAudioSampleRate.HasValue)
- {
- url += "&audiosamplerate=" + maxAudioSampleRate.Value;
- }
-
- if (width.HasValue)
- {
- url += "&width=" + width.Value;
- }
-
- if (height.HasValue)
- {
- url += "&height=" + height.Value;
- }
-
- if (maxWidth.HasValue)
- {
- url += "&maxWidth=" + maxWidth.Value;
- }
-
- if (maxHeight.HasValue)
- {
- url += "&maxHeight=" + maxHeight.Value;
- }
- return url;
- }
-
- protected T DeserializeFromStream<T>(Stream stream)
- where T : class
- {
- return DataSerializer.DeserializeFromStream<T>(stream, SerializationFormat);
- }
-
- public virtual void Dispose()
- {
- }
- }
-}
+using MediaBrowser.Model.Connectivity;
+using MediaBrowser.Model.DTO;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Web;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.ApiInteraction
+{
+ /// <summary>
+ /// Provides api methods that are usable on all platforms
+ /// </summary>
+ public abstract class BaseApiClient : IDisposable
+ {
+ /// <summary>
+ /// Gets the logger.
+ /// </summary>
+ /// <value>The logger.</value>
+ protected ILogger Logger { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BaseApiClient" /> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <exception cref="System.ArgumentNullException">logger</exception>
+ protected BaseApiClient(ILogger logger)
+ {
+ if (logger == null)
+ {
+ throw new ArgumentNullException("logger");
+ }
+
+ Logger = logger;
+
+ DataSerializer.Configure();
+ }
+
+ /// <summary>
+ /// Gets or sets the server host name (myserver or 192.168.x.x)
+ /// </summary>
+ /// <value>The name of the server host.</value>
+ public string ServerHostName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the port number used by the API
+ /// </summary>
+ /// <value>The server API port.</value>
+ public int ServerApiPort { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type of the client.
+ /// </summary>
+ /// <value>The type of the client.</value>
+ public ClientType ClientType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name of the device.
+ /// </summary>
+ /// <value>The name of the device.</value>
+ public string DeviceName { get; set; }
+
+ private Guid? _currentUserId;
+
+ /// <summary>
+ /// Gets or sets the current user id.
+ /// </summary>
+ /// <value>The current user id.</value>
+ public virtual Guid? CurrentUserId
+ {
+ get { return _currentUserId; }
+ set
+ {
+ _currentUserId = value;
+ ResetAuthorizationHeader();
+ }
+ }
+
+ /// <summary>
+ /// Gets the current api url based on hostname and port.
+ /// </summary>
+ /// <value>The API URL.</value>
+ protected string ApiUrl
+ {
+ get
+ {
+ return string.Format("http://{0}:{1}/mediabrowser", ServerHostName, ServerApiPort);
+ }
+ }
+
+ private SerializationFormats _serializationFormat = SerializationFormats.Protobuf;
+ /// <summary>
+ /// Gets the default data format to request from the server
+ /// </summary>
+ /// <value>The serialization format.</value>
+ public SerializationFormats SerializationFormat
+ {
+ get
+ {
+ return _serializationFormat;
+ }
+ set
+ {
+ _serializationFormat = value;
+ }
+ }
+
+ /// <summary>
+ /// Resets the authorization header.
+ /// </summary>
+ private void ResetAuthorizationHeader()
+ {
+ if (!CurrentUserId.HasValue)
+ {
+ SetAuthorizationHeader(null);
+ return;
+ }
+
+ var header = string.Format("UserId=\"{0}\", Client=\"{1}\"", CurrentUserId.Value, ClientType);
+
+ if (!string.IsNullOrEmpty(DeviceName))
+ {
+ header += string.Format(", Device=\"{0}\"", DeviceName);
+ }
+
+ SetAuthorizationHeader(header);
+ }
+
+ /// <summary>
+ /// Sets the authorization header.
+ /// </summary>
+ /// <param name="header">The header.</param>
+ protected abstract void SetAuthorizationHeader(string header);
+
+ /// <summary>
+ /// Gets the API URL.
+ /// </summary>
+ /// <param name="handler">The handler.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">handler</exception>
+ protected string GetApiUrl(string handler)
+ {
+ return GetApiUrl(handler, new QueryStringDictionary());
+ }
+
+ /// <summary>
+ /// Gets the API URL.
+ /// </summary>
+ /// <param name="handler">The handler.</param>
+ /// <param name="queryString">The query string.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">handler</exception>
+ protected string GetApiUrl(string handler, QueryStringDictionary queryString)
+ {
+ if (string.IsNullOrEmpty(handler))
+ {
+ throw new ArgumentNullException("handler");
+ }
+
+ if (queryString == null)
+ {
+ throw new ArgumentNullException("queryString");
+ }
+
+ return queryString.GetUrl(ApiUrl + "/" + handler);
+ }
+
+ /// <summary>
+ /// Creates a url to return a list of items
+ /// </summary>
+ /// <param name="query">The query.</param>
+ /// <param name="listType">The type of list to retrieve.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">query</exception>
+ protected string GetItemListUrl(ItemQuery query, string listType = null)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException("query");
+ }
+
+ var dict = new QueryStringDictionary { };
+
+ dict.AddIfNotNullOrEmpty("listtype", listType);
+ dict.AddIfNotNullOrEmpty("ParentId", query.ParentId);
+
+ dict.AddIfNotNull("startindex", query.StartIndex);
+
+ dict.AddIfNotNull("limit", query.Limit);
+
+ if (query.SortBy != null)
+ {
+ dict["sortBy"] = string.Join(",", query.SortBy.Select(s => s.ToString()));
+ }
+
+ if (query.SortOrder.HasValue)
+ {
+ dict["sortOrder"] = query.SortOrder.ToString();
+ }
+
+ if (query.Fields != null)
+ {
+ dict.Add("fields", query.Fields.Select(f => f.ToString()));
+ }
+ if (query.Filters != null)
+ {
+ dict.Add("Filters", query.Filters.Select(f => f.ToString()));
+ }
+ if (query.ImageTypes != null)
+ {
+ dict.Add("ImageTypes", query.ImageTypes.Select(f => f.ToString()));
+ }
+
+ dict.Add("recursive", query.Recursive);
+
+ dict.AddIfNotNull("genres", query.Genres);
+ dict.AddIfNotNull("studios", query.Studios);
+ dict.AddIfNotNull("ExcludeItemTypes", query.ExcludeItemTypes);
+ dict.AddIfNotNull("IncludeItemTypes", query.IncludeItemTypes);
+
+ dict.AddIfNotNullOrEmpty("person", query.Person);
+ dict.AddIfNotNullOrEmpty("personType", query.PersonType);
+
+ dict.AddIfNotNull("years", query.Years);
+
+ dict.AddIfNotNullOrEmpty("indexBy", query.IndexBy);
+ dict.AddIfNotNullOrEmpty("dynamicSortBy", query.DynamicSortBy);
+ dict.AddIfNotNullOrEmpty("SearchTerm", query.SearchTerm);
+
+ return GetApiUrl("Users/" + query.UserId + "/Items", dict);
+ }
+
+ /// <summary>
+ /// Gets the image URL.
+ /// </summary>
+ /// <param name="baseUrl">The base URL.</param>
+ /// <param name="options">The options.</param>
+ /// <param name="queryParams">The query params.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">options</exception>
+ private string GetImageUrl(string baseUrl, ImageOptions options, QueryStringDictionary queryParams)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ if (queryParams == null)
+ {
+ throw new ArgumentNullException("queryParams");
+ }
+
+ if (options.ImageIndex.HasValue)
+ {
+ baseUrl += "/" + options.ImageIndex.Value;
+ }
+
+ queryParams.AddIfNotNull("width", options.Width);
+ queryParams.AddIfNotNull("height", options.Height);
+ queryParams.AddIfNotNull("maxWidth", options.MaxWidth);
+ queryParams.AddIfNotNull("maxHeight", options.MaxHeight);
+ queryParams.AddIfNotNull("Quality", options.Quality);
+
+ queryParams.AddIfNotNull("tag", options.Tag);
+
+ return GetApiUrl(baseUrl, queryParams);
+ }
+
+ /// <summary>
+ /// Gets the image URL.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">item</exception>
+ public string GetImageUrl(DtoBaseItem item, ImageOptions options)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ var index = options.ImageIndex ?? 0;
+
+ if (options.ImageType == ImageType.Backdrop)
+ {
+ options.Tag = item.BackdropImageTags[index];
+ }
+ else if (options.ImageType == ImageType.ChapterImage)
+ {
+ options.Tag = item.Chapters[index].ImageTag;
+ }
+ else
+ {
+ options.Tag = item.ImageTags[options.ImageType];
+ }
+
+ return GetImageUrl(item.Id, options);
+ }
+
+ /// <summary>
+ /// Gets an image url that can be used to download an image from the api
+ /// </summary>
+ /// <param name="itemId">The Id of the item</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">itemId</exception>
+ public string GetImageUrl(string itemId, ImageOptions options)
+ {
+ if (string.IsNullOrEmpty(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ var url = "Items/" + itemId + "/Images/" + options.ImageType;
+
+ return GetImageUrl(url, options, new QueryStringDictionary());
+ }
+
+ /// <summary>
+ /// Gets the user image URL.
+ /// </summary>
+ /// <param name="user">The user.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">user</exception>
+ public string GetUserImageUrl(DtoUser user, ImageOptions options)
+ {
+ if (user == null)
+ {
+ throw new ArgumentNullException("user");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ options.Tag = user.PrimaryImageTag;
+
+ return GetUserImageUrl(user.Id, options);
+ }
+
+ /// <summary>
+ /// Gets an image url that can be used to download an image from the api
+ /// </summary>
+ /// <param name="userId">The Id of the user</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">userId</exception>
+ public string GetUserImageUrl(Guid userId, ImageOptions options)
+ {
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+
+ var url = "Users/" + userId + "/Images/" + options.ImageType;
+
+ return GetImageUrl(url, options, new QueryStringDictionary());
+ }
+
+ /// <summary>
+ /// Gets the person image URL.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">item</exception>
+ public string GetPersonImageUrl(BaseItemPerson item, ImageOptions options)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ options.Tag = item.PrimaryImageTag;
+
+ return GetPersonImageUrl(item.Name, options);
+ }
+
+ /// <summary>
+ /// Gets the person image URL.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">item</exception>
+ public string GetPersonImageUrl(DtoBaseItem item, ImageOptions options)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ options.Tag = item.ImageTags[ImageType.Primary];
+
+ return GetPersonImageUrl(item.Name, options);
+ }
+
+ /// <summary>
+ /// Gets an image url that can be used to download an image from the api
+ /// </summary>
+ /// <param name="name">The name of the person</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">name</exception>
+ public string GetPersonImageUrl(string name, ImageOptions options)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ var url = "Persons/" + name + "/Images/" + options.ImageType;
+
+ return GetImageUrl(url, options, new QueryStringDictionary());
+ }
+
+ /// <summary>
+ /// Gets the year image URL.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">item</exception>
+ public string GetYearImageUrl(DtoBaseItem item, ImageOptions options)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ options.Tag = item.ImageTags[ImageType.Primary];
+
+ return GetYearImageUrl(int.Parse(item.Name), options);
+ }
+
+ /// <summary>
+ /// Gets an image url that can be used to download an image from the api
+ /// </summary>
+ /// <param name="year">The year.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ public string GetYearImageUrl(int year, ImageOptions options)
+ {
+ var url = "Years/" + year + "/Images/" + options.ImageType;
+
+ return GetImageUrl(url, options, new QueryStringDictionary());
+ }
+
+ /// <summary>
+ /// Gets the genre image URL.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">item</exception>
+ public string GetGenreImageUrl(DtoBaseItem item, ImageOptions options)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ options.Tag = item.ImageTags[ImageType.Primary];
+
+ return GetGenreImageUrl(item.Name, options);
+ }
+
+ /// <summary>
+ /// Gets an image url that can be used to download an image from the api
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">name</exception>
+ public string GetGenreImageUrl(string name, ImageOptions options)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ var url = "Genres/" + name + "/Images/" + options.ImageType;
+
+ return GetImageUrl(url, options, new QueryStringDictionary());
+ }
+
+ /// <summary>
+ /// Gets the studio image URL.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">item</exception>
+ public string GetStudioImageUrl(DtoBaseItem item, ImageOptions options)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ options.Tag = item.ImageTags[ImageType.Primary];
+
+ return GetStudioImageUrl(item.Name, options);
+ }
+
+ /// <summary>
+ /// Gets an image url that can be used to download an image from the api
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">name</exception>
+ public string GetStudioImageUrl(string name, ImageOptions options)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ var url = "Studios/" + name + "/Images/" + options.ImageType;
+
+ return GetImageUrl(url, options, new QueryStringDictionary());
+ }
+
+ /// <summary>
+ /// This is a helper to get a list of backdrop url's from a given ApiBaseItemWrapper. If the actual item does not have any backdrops it will return backdrops from the first parent that does.
+ /// </summary>
+ /// <param name="item">A given item.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String[][].</returns>
+ /// <exception cref="System.ArgumentNullException">item</exception>
+ public string[] GetBackdropImageUrls(DtoBaseItem item, ImageOptions options)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ options.ImageType = ImageType.Backdrop;
+
+ string backdropItemId;
+ List<Guid> backdropImageTags;
+
+ if (item.BackdropCount == 0)
+ {
+ backdropItemId = item.ParentBackdropItemId;
+ backdropImageTags = item.ParentBackdropImageTags;
+ }
+ else
+ {
+ backdropItemId = item.Id;
+ backdropImageTags = item.BackdropImageTags;
+ }
+
+ if (string.IsNullOrEmpty(backdropItemId))
+ {
+ return new string[] { };
+ }
+
+ var files = new string[backdropImageTags.Count];
+
+ for (var i = 0; i < backdropImageTags.Count; i++)
+ {
+ options.ImageIndex = i;
+ options.Tag = backdropImageTags[i];
+
+ files[i] = GetImageUrl(backdropItemId, options);
+ }
+
+ return files;
+ }
+
+ /// <summary>
+ /// This is a helper to get the logo image url from a given ApiBaseItemWrapper. If the actual item does not have a logo, it will return the logo from the first parent that does, or null.
+ /// </summary>
+ /// <param name="item">A given item.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">item</exception>
+ public string GetLogoImageUrl(DtoBaseItem item, ImageOptions options)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ options.ImageType = ImageType.Logo;
+
+ var logoItemId = item.HasLogo ? item.Id : item.ParentLogoItemId;
+ var imageTag = item.HasLogo ? item.ImageTags[ImageType.Logo] : item.ParentLogoImageTag;
+
+ if (!string.IsNullOrEmpty(logoItemId))
+ {
+ options.Tag = imageTag;
+
+ return GetImageUrl(logoItemId, options);
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Gets the url needed to stream an audio file
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">options</exception>
+ public string GetAudioStreamUrl(StreamOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ var handler = "audio." + options.OutputFileExtension.TrimStart('.');
+
+ return GetMediaStreamUrl(handler, options, new QueryStringDictionary());
+ }
+
+ /// <summary>
+ /// Gets the url needed to stream a video file
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">options</exception>
+ public string GetVideoStreamUrl(VideoStreamOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ var handler = "video." + options.OutputFileExtension.TrimStart('.');
+
+ return GetVideoStreamUrl(handler, options);
+ }
+
+ /// <summary>
+ /// Formulates a url for streaming audio using the HLS protocol
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">options</exception>
+ public string GetHlsAudioStreamUrl(StreamOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ return GetMediaStreamUrl("audio.m3u8", options, new QueryStringDictionary());
+ }
+
+ /// <summary>
+ /// Formulates a url for streaming video using the HLS protocol
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">options</exception>
+ public string GetHlsVideoStreamUrl(VideoStreamOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ return GetVideoStreamUrl("video.m3u8", options);
+ }
+
+ /// <summary>
+ /// Gets the video stream URL.
+ /// </summary>
+ /// <param name="handler">The handler.</param>
+ /// <param name="options">The options.</param>
+ /// <returns>System.String.</returns>
+ private string GetVideoStreamUrl(string handler, VideoStreamOptions options)
+ {
+ var queryParams = new QueryStringDictionary();
+
+ if (options.VideoCodec.HasValue)
+ {
+ queryParams["VideoCodec"] = options.VideoCodec.Value.ToString();
+ }
+
+ queryParams.AddIfNotNull("VideoBitRate", options.VideoBitRate);
+ queryParams.AddIfNotNull("Width", options.Width);
+ queryParams.AddIfNotNull("Height", options.Height);
+ queryParams.AddIfNotNull("MaxWidth", options.MaxWidth);
+ queryParams.AddIfNotNull("MaxHeight", options.MaxHeight);
+ queryParams.AddIfNotNull("FrameRate", options.FrameRate);
+ queryParams.AddIfNotNull("AudioStreamIndex", options.AudioStreamIndex);
+ queryParams.AddIfNotNull("VideoStreamIndex", options.VideoStreamIndex);
+ queryParams.AddIfNotNull("SubtitleStreamIndex", options.SubtitleStreamIndex);
+
+ return GetMediaStreamUrl(handler, options, queryParams);
+ }
+
+ /// <summary>
+ /// Gets the media stream URL.
+ /// </summary>
+ /// <param name="handler">The handler.</param>
+ /// <param name="options">The options.</param>
+ /// <param name="queryParams">The query params.</param>
+ /// <returns>System.String.</returns>
+ /// <exception cref="System.ArgumentNullException">handler</exception>
+ private string GetMediaStreamUrl(string handler, StreamOptions options, QueryStringDictionary queryParams)
+ {
+ if (string.IsNullOrEmpty(handler))
+ {
+ throw new ArgumentNullException("handler");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
+ if (queryParams == null)
+ {
+ throw new ArgumentNullException("queryParams");
+ }
+
+ queryParams.Add("id", options.ItemId);
+
+ if (options.AudioCodec.HasValue)
+ {
+ queryParams["audioCodec"] = options.AudioCodec.Value.ToString();
+ }
+
+ queryParams.AddIfNotNull("audiochannels", options.MaxAudioChannels);
+ queryParams.AddIfNotNull("audiosamplerate", options.MaxAudioSampleRate);
+ queryParams.AddIfNotNull("AudioBitRate", options.AudioBitRate);
+ queryParams.AddIfNotNull("StartTimeTicks", options.StartTimeTicks);
+ queryParams.AddIfNotNull("Static", options.Static);
+
+ return GetApiUrl(handler, queryParams);
+ }
+
+ /// <summary>
+ /// Deserializes from stream.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="stream">The stream.</param>
+ /// <returns>``0.</returns>
+ protected T DeserializeFromStream<T>(Stream stream)
+ where T : class
+ {
+ return (T)DataSerializer.DeserializeFromStream(stream, SerializationFormat, typeof(T));
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+
+ }
+ }
+}
diff --git a/MediaBrowser.ApiInteraction/BaseHttpApiClient.cs b/MediaBrowser.ApiInteraction/BaseHttpApiClient.cs
deleted file mode 100644
index 8c6c1c2971..0000000000
--- a/MediaBrowser.ApiInteraction/BaseHttpApiClient.cs
+++ /dev/null
@@ -1,611 +0,0 @@
-using MediaBrowser.Model.Authentication;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.DTO;
-using MediaBrowser.Model.Weather;
-using System;
-using System.IO;
-using System.Net;
-using System.Text;
-using System.Threading.Tasks;
-#if WINDOWS_PHONE
-using SharpGIS;
-#else
-using System.Net.Http;
-#endif
-
-namespace MediaBrowser.ApiInteraction
-{
- /// <summary>
- /// Provides api methods centered around an HttpClient
- /// </summary>
- public abstract class BaseHttpApiClient : BaseApiClient
- {
-#if WINDOWS_PHONE
- public BaseHttpApiClient()
- {
- HttpClient = new GZipWebClient();
- }
-
- private WebClient HttpClient { get; set; }
-#else
- protected BaseHttpApiClient(HttpClientHandler handler)
- : base()
- {
- handler.AutomaticDecompression = DecompressionMethods.Deflate;
-
- HttpClient = new HttpClient(handler);
- }
-
- private HttpClient HttpClient { get; set; }
-#endif
-
- /// <summary>
- /// Gets an image stream based on a url
- /// </summary>
- public Task<Stream> GetImageStreamAsync(string url)
- {
- return GetStreamAsync(url);
- }
-
- /// <summary>
- /// Gets a BaseItem
- /// </summary>
- public async Task<DtoBaseItem> GetItemAsync(Guid id, Guid userId)
- {
- string url = ApiUrl + "/item?userId=" + userId.ToString();
-
- if (id != Guid.Empty)
- {
- url += "&id=" + id.ToString();
- }
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem>(stream);
- }
- }
-
- /// <summary>
- /// Gets all Users
- /// </summary>
- public async Task<DtoUser[]> GetAllUsersAsync()
- {
- string url = ApiUrl + "/users";
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoUser[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets all Genres
- /// </summary>
- public async Task<IbnItem[]> GetAllGenresAsync(Guid userId)
- {
- string url = ApiUrl + "/genres?userId=" + userId.ToString();
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<IbnItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets in-progress items
- /// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
- public async Task<DtoBaseItem[]> GetInProgressItemsItemsAsync(Guid userId, Guid? folderId = null)
- {
- string url = ApiUrl + "/itemlist?listtype=inprogressitems&userId=" + userId.ToString();
-
- if (folderId.HasValue)
- {
- url += "&id=" + folderId.ToString();
- }
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets recently added items
- /// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
- public async Task<DtoBaseItem[]> GetRecentlyAddedItemsAsync(Guid userId, Guid? folderId = null)
- {
- string url = ApiUrl + "/itemlist?listtype=recentlyaddeditems&userId=" + userId.ToString();
-
- if (folderId.HasValue)
- {
- url += "&id=" + folderId.ToString();
- }
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets favorite items
- /// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
- public async Task<DtoBaseItem[]> GetFavoriteItemsAsync(Guid userId, Guid? folderId = null)
- {
- string url = ApiUrl + "/itemlist?listtype=favorites&userId=" + userId.ToString();
-
- if (folderId.HasValue)
- {
- url += "&id=" + folderId.ToString();
- }
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets recently added items that are unplayed.
- /// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
- public async Task<DtoBaseItem[]> GetRecentlyAddedUnplayedItemsAsync(Guid userId, Guid? folderId = null)
- {
- string url = ApiUrl + "/itemlist?listtype=recentlyaddedunplayeditems&userId=" + userId.ToString();
-
- if (folderId.HasValue)
- {
- url += "&id=" + folderId.ToString();
- }
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets all Years
- /// </summary>
- public async Task<IbnItem[]> GetAllYearsAsync(Guid userId)
- {
- string url = ApiUrl + "/years?userId=" + userId.ToString();
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<IbnItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets all items that contain a given Year
- /// </summary>
- /// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
- public async Task<DtoBaseItem[]> GetItemsWithYearAsync(string name, Guid userId, Guid? folderId = null)
- {
- string url = ApiUrl + "/itemlist?listtype=itemswithyear&userId=" + userId.ToString() + "&name=" + name;
-
- if (folderId.HasValue)
- {
- url += "&id=" + folderId.ToString();
- }
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets all items that contain a given Genre
- /// </summary>
- /// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
- public async Task<DtoBaseItem[]> GetItemsWithGenreAsync(string name, Guid userId, Guid? folderId = null)
- {
- string url = ApiUrl + "/itemlist?listtype=itemswithgenre&userId=" + userId.ToString() + "&name=" + name;
-
- if (folderId.HasValue)
- {
- url += "&id=" + folderId.ToString();
- }
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets all items that contain a given Person
- /// </summary>
- /// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
- public async Task<DtoBaseItem[]> GetItemsWithPersonAsync(string name, Guid userId, Guid? folderId = null)
- {
- string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name;
-
- if (folderId.HasValue)
- {
- url += "&id=" + folderId.ToString();
- }
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets all items that contain a given Person
- /// </summary>
- /// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
- public async Task<DtoBaseItem[]> GetItemsWithPersonAsync(string name, string personType, Guid userId, Guid? folderId = null)
- {
- string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name;
-
- url += "&persontype=" + personType;
-
- if (folderId.HasValue)
- {
- url += "&id=" + folderId.ToString();
- }
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets all studious
- /// </summary>
- public async Task<IbnItem[]> GetAllStudiosAsync(Guid userId)
- {
- string url = ApiUrl + "/studios?userId=" + userId.ToString();
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<IbnItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets all items that contain a given Studio
- /// </summary>
- /// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
- public async Task<DtoBaseItem[]> GetItemsWithStudioAsync(string name, Guid userId, Guid? folderId = null)
- {
- string url = ApiUrl + "/itemlist?listtype=itemswithstudio&userId=" + userId.ToString() + "&name=" + name;
-
- if (folderId.HasValue)
- {
- url += "&id=" + folderId.ToString();
- }
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets a studio
- /// </summary>
- public async Task<IbnItem> GetStudioAsync(Guid userId, string name)
- {
- string url = ApiUrl + "/studio?userId=" + userId.ToString() + "&name=" + name;
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<IbnItem>(stream);
- }
- }
-
- /// <summary>
- /// Gets a genre
- /// </summary>
- public async Task<IbnItem> GetGenreAsync(Guid userId, string name)
- {
- string url = ApiUrl + "/genre?userId=" + userId.ToString() + "&name=" + name;
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<IbnItem>(stream);
- }
- }
-
- /// <summary>
- /// Gets a person
- /// </summary>
- public async Task<IbnItem> GetPersonAsync(Guid userId, string name)
- {
- string url = ApiUrl + "/person?userId=" + userId.ToString() + "&name=" + name;
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<IbnItem>(stream);
- }
- }
-
- /// <summary>
- /// Gets a year
- /// </summary>
- public async Task<IbnItem> GetYearAsync(Guid userId, int year)
- {
- string url = ApiUrl + "/year?userId=" + userId.ToString() + "&year=" + year;
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<IbnItem>(stream);
- }
- }
-
- /// <summary>
- /// Gets a list of plugins installed on the server
- /// </summary>
- public async Task<PluginInfo[]> GetInstalledPluginsAsync()
- {
- string url = ApiUrl + "/plugins";
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<PluginInfo[]>(stream);
- }
- }
-
- /// <summary>
- /// Gets a list of plugins installed on the server
- /// </summary>
- public Task<Stream> GetPluginAssemblyAsync(PluginInfo plugin)
- {
- string url = ApiUrl + "/pluginassembly?assemblyfilename=" + plugin.AssemblyFileName;
-
- return GetStreamAsync(url);
- }
-
- /// <summary>
- /// Gets the current server configuration
- /// </summary>
- public async Task<ServerConfiguration> GetServerConfigurationAsync()
- {
- string url = ApiUrl + "/ServerConfiguration";
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<ServerConfiguration>(stream);
- }
- }
-
- /// <summary>
- /// Gets weather information for the default location as set in configuration
- /// </summary>
- public async Task<object> GetPluginConfigurationAsync(PluginInfo plugin, Type configurationType)
- {
- string url = ApiUrl + "/PluginConfiguration?assemblyfilename=" + plugin.AssemblyFileName;
-
- // At the moment this can't be retrieved in protobuf format
- SerializationFormats format = DataSerializer.CanDeSerializeJsv ? SerializationFormats.Jsv : SerializationFormats.Json;
-
- using (Stream stream = await GetSerializedStreamAsync(url, format).ConfigureAwait(false))
- {
- return DataSerializer.DeserializeFromStream(stream, format, configurationType);
- }
- }
-
- /// <summary>
- /// Gets the default user
- /// </summary>
- public async Task<DtoUser> GetDefaultUserAsync()
- {
- string url = ApiUrl + "/user";
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoUser>(stream);
- }
- }
-
- /// <summary>
- /// Gets a user by id
- /// </summary>
- public async Task<DtoUser> GetUserAsync(Guid id)
- {
- string url = ApiUrl + "/user?id=" + id.ToString();
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoUser>(stream);
- }
- }
-
- /// <summary>
- /// Gets weather information for the default location as set in configuration
- /// </summary>
- public async Task<WeatherInfo> GetWeatherInfoAsync()
- {
- string url = ApiUrl + "/weather";
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<WeatherInfo>(stream);
- }
- }
-
- /// <summary>
- /// Gets weather information for a specific zip code
- /// </summary>
- public async Task<WeatherInfo> GetWeatherInfoAsync(string zipCode)
- {
- string url = ApiUrl + "/weather?zipcode=" + zipCode;
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<WeatherInfo>(stream);
- }
- }
-
- /// <summary>
- /// Gets special features for a Movie
- /// </summary>
- public async Task<DtoBaseItem[]> GetMovieSpecialFeaturesAsync(Guid itemId, Guid userId)
- {
- string url = ApiUrl + "/MovieSpecialFeatures?id=" + itemId;
- url += "&userid=" + userId;
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoBaseItem[]>(stream);
- }
- }
-
- /// <summary>
- /// Updates played status for an item
- /// </summary>
- public async Task<DtoUserItemData> UpdatePlayedStatusAsync(Guid itemId, Guid userId, bool wasPlayed)
- {
- string url = ApiUrl + "/PlayedStatus?id=" + itemId;
-
- url += "&userid=" + userId;
- url += "&played=" + (wasPlayed ? "1" : "0");
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoUserItemData>(stream);
- }
- }
-
- /// <summary>
- /// Updates a user's favorite status for an item and returns the updated UserItemData object.
- /// </summary>
- public async Task<DtoUserItemData> UpdateFavoriteStatusAsync(Guid itemId, Guid userId, bool isFavorite)
- {
- string url = ApiUrl + "/favoritestatus?id=" + itemId;
-
- url += "&userid=" + userId;
- url += "&isfavorite=" + (isFavorite ? "1" : "0");
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoUserItemData>(stream);
- }
- }
-
- /// <summary>
- /// Clears a user's rating for an item
- /// </summary>
- public async Task<DtoUserItemData> ClearUserItemRatingAsync(Guid itemId, Guid userId)
- {
- string url = ApiUrl + "/UserItemRating?id=" + itemId;
-
- url += "&userid=" + userId;
- url += "&clear=1";
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoUserItemData>(stream);
- }
- }
-
- /// <summary>
- /// Updates a user's rating for an item, based on likes or dislikes
- /// </summary>
- public async Task<DtoUserItemData> UpdateUserItemRatingAsync(Guid itemId, Guid userId, bool likes)
- {
- string url = ApiUrl + "/UserItemRating?id=" + itemId;
-
- url += "&userid=" + userId;
- url += "&likes=" + (likes ? "1" : "0");
-
- using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
- {
- return DeserializeFromStream<DtoUserItemData>(stream);
- }
- }
-
- /// <summary>
- /// Authenticates a user and returns the result
- /// </summary>
- public async Task<AuthenticationResult> AuthenticateUserAsync(Guid userId, string password)
- {
- string url = ApiUrl + "/UserAuthentication?dataformat=" + SerializationFormat.ToString();
-
- // Create the post body
- string postContent = string.Format("userid={0}", userId);
-
- if (!string.IsNullOrEmpty(password))
- {
- postContent += "&password=" + password;
- }
-
-#if WINDOWS_PHONE
- HttpClient.Headers["Content-Type"] = "application/x-www-form-urlencoded";
- var result = await HttpClient.UploadStringTaskAsync(url, "POST", postContent);
-
- var byteArray = Encoding.UTF8.GetBytes(result);
- using (MemoryStream stream = new MemoryStream(byteArray))
- {
- return DeserializeFromStream<AuthenticationResult>(stream);
- }
-#else
- HttpContent content = new StringContent(postContent, Encoding.UTF8, "application/x-www-form-urlencoded");
-
- HttpResponseMessage msg = await HttpClient.PostAsync(url, content).ConfigureAwait(false);
-
- using (Stream stream = await msg.Content.ReadAsStreamAsync().ConfigureAwait(false))
- {
- return DeserializeFromStream<AuthenticationResult>(stream);
- }
-#endif
- }
-
- /// <summary>
- /// This is a helper around getting a stream from the server that contains serialized data
- /// </summary>
- private Task<Stream> GetSerializedStreamAsync(string url)
- {
- return GetSerializedStreamAsync(url, SerializationFormat);
- }
-
- /// <summary>
- /// This is a helper around getting a stream from the server that contains serialized data
- /// </summary>
- private Task<Stream> GetSerializedStreamAsync(string url, SerializationFormats serializationFormat)
- {
- if (url.IndexOf('?') == -1)
- {
- url += "?dataformat=" + serializationFormat.ToString();
- }
- else
- {
- url += "&dataformat=" + serializationFormat.ToString();
- }
-
- return GetStreamAsync(url);
- }
-
- /// <summary>
- /// This is just a helper around HttpClient
- /// </summary>
- private Task<Stream> GetStreamAsync(string url)
- {
-#if WINDOWS_PHONE
- return HttpClient.OpenReadTaskAsync(url);
-#else
- return HttpClient.GetStreamAsync(url);
-#endif
- }
-
- public override void Dispose()
- {
-#if !WINDOWS_PHONE
- HttpClient.Dispose();
-#endif
- }
- }
-}
diff --git a/MediaBrowser.ApiInteraction/DataSerializer.cs b/MediaBrowser.ApiInteraction/DataSerializer.cs
index 3c3f8fae2f..cc13d55c88 100644
--- a/MediaBrowser.ApiInteraction/DataSerializer.cs
+++ b/MediaBrowser.ApiInteraction/DataSerializer.cs
@@ -1,77 +1,66 @@
-using ServiceStack.Text;
-using System;
-using System.IO;
-
-namespace MediaBrowser.ApiInteraction
-{
- public static class DataSerializer
- {
- /// <summary>
- /// This is an auto-generated Protobuf Serialization assembly for best performance.
- /// It is created during the Model project's post-build event.
- /// This means that this class can currently only handle types within the Model project.
- /// If we need to, we can always add a param indicating whether or not the model serializer should be used.
- /// </summary>
- private static readonly ProtobufModelSerializer ProtobufModelSerializer = new ProtobufModelSerializer();
-
- /// <summary>
- /// Deserializes an object using generics
- /// </summary>
- public static T DeserializeFromStream<T>(Stream stream, SerializationFormats format)
- where T : class
- {
- if (format == SerializationFormats.Protobuf)
- {
- //return Serializer.Deserialize<T>(stream);
- return ProtobufModelSerializer.Deserialize(stream, null, typeof(T)) as T;
- }
- if (format == SerializationFormats.Jsv)
- {
- return TypeSerializer.DeserializeFromStream<T>(stream);
- }
- if (format == SerializationFormats.Json)
- {
- return JsonSerializer.DeserializeFromStream<T>(stream);
- }
-
- throw new NotImplementedException();
- }
-
- /// <summary>
- /// Deserializes an object
- /// </summary>
- public static object DeserializeFromStream(Stream stream, SerializationFormats format, Type type)
- {
- if (format == SerializationFormats.Protobuf)
- {
- //throw new NotImplementedException();
- return ProtobufModelSerializer.Deserialize(stream, null, type);
- }
- if (format == SerializationFormats.Jsv)
- {
- return TypeSerializer.DeserializeFromStream(type, stream);
- }
- if (format == SerializationFormats.Json)
- {
- return JsonSerializer.DeserializeFromStream(type, stream);
- }
-
- throw new NotImplementedException();
- }
-
- public static void Configure()
- {
- JsConfig.DateHandler = JsonDateHandler.ISO8601;
- JsConfig.ExcludeTypeInfo = true;
- JsConfig.IncludeNullValues = false;
- }
-
- public static bool CanDeSerializeJsv
- {
- get
- {
- return true;
- }
- }
- }
-}
+using ProtoBuf;
+using ProtoBuf.Meta;
+using ServiceStack.Text;
+using System;
+using System.IO;
+
+namespace MediaBrowser.ApiInteraction
+{
+ /// <summary>
+ /// Class DataSerializer
+ /// </summary>
+ public static class DataSerializer
+ {
+ /// <summary>
+ /// Gets or sets the dynamically created serializer.
+ /// </summary>
+ /// <value>The dynamic serializer.</value>
+ public static TypeModel DynamicSerializer { get; set; }
+
+ /// <summary>
+ /// Deserializes an object
+ /// </summary>
+ /// <param name="stream">The stream.</param>
+ /// <param name="format">The format.</param>
+ /// <param name="type">The type.</param>
+ /// <returns>System.Object.</returns>
+ /// <exception cref="System.NotImplementedException"></exception>
+ public static object DeserializeFromStream(Stream stream, SerializationFormats format, Type type)
+ {
+ if (format == SerializationFormats.Protobuf)
+ {
+ if (DynamicSerializer != null)
+ {
+ return DynamicSerializer.Deserialize(stream, null, type);
+ }
+ return Serializer.NonGeneric.Deserialize(type, stream);
+ }
+ if (format == SerializationFormats.Json)
+ {
+ return JsonSerializer.DeserializeFromStream(type, stream);
+ }
+
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Serializes to json.
+ /// </summary>
+ /// <param name="obj">The obj.</param>
+ /// <returns>System.String.</returns>
+ public static string SerializeToJsonString(object obj)
+ {
+ return JsonSerializer.SerializeToString(obj, obj.GetType());
+ }
+
+ /// <summary>
+ /// Configures this instance.
+ /// </summary>
+ public static void Configure()
+ {
+ JsConfig.DateHandler = JsonDateHandler.ISO8601;
+ JsConfig.ExcludeTypeInfo = true;
+ JsConfig.IncludeNullValues = false;
+ }
+ }
+}
diff --git a/MediaBrowser.ApiInteraction/IAsyncHttpClient.cs b/MediaBrowser.ApiInteraction/IAsyncHttpClient.cs
new file mode 100644
index 0000000000..edd11829a6
--- /dev/null
+++ b/MediaBrowser.ApiInteraction/IAsyncHttpClient.cs
@@ -0,0 +1,49 @@
+using MediaBrowser.Model.Logging;
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.ApiInteraction
+{
+ /// <summary>
+ /// Interface IHttpClient
+ /// </summary>
+ public interface IAsyncHttpClient : IDisposable
+ {
+ /// <summary>
+ /// Sets the authorization header that should be supplied on every request
+ /// </summary>
+ /// <param name="header"></param>
+ void SetAuthorizationHeader(string header);
+
+ /// <summary>
+ /// Gets the stream async.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ Task<Stream> GetStreamAsync(string url, ILogger logger, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Deletes the async.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task DeleteAsync(string url, ILogger logger, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Posts the async.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="contentType">Type of the content.</param>
+ /// <param name="postContent">Content of the post.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ Task<Stream> PostAsync(string url, string contentType, string postContent, ILogger logger, CancellationToken cancellationToken);
+ }
+}
diff --git a/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj b/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj
index d38a25a074..23f43d125c 100644
--- a/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj
+++ b/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj
@@ -1,78 +1,82 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
- <PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProjectGuid>{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}</ProjectGuid>
- <OutputType>Library</OutputType>
- <AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>MediaBrowser.ApiInteraction</RootNamespace>
- <AssemblyName>MediaBrowser.ApiInteraction</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
- <FileAlignment>512</FileAlignment>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>full</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <DebugType>pdbonly</DebugType>
- <Optimize>true</Optimize>
- <OutputPath>bin\Release\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="protobuf-net">
- <HintPath>..\protobuf-net\Full\net30\protobuf-net.dll</HintPath>
- </Reference>
- <Reference Include="ProtobufModelSerializer">
- <HintPath>..\MediaBrowser.Model\bin\ProtobufModelSerializer.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.Text, Version=3.9.9.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\ServiceStack.Text.3.9.9\lib\net35\ServiceStack.Text.dll</HintPath>
- </Reference>
- <Reference Include="System" />
- <Reference Include="System.Core" />
- <Reference Include="System.Net.Http" />
- <Reference Include="System.Net.Http.WebRequest" />
- <Reference Include="System.Xml.Linq" />
- <Reference Include="System.Data.DataSetExtensions" />
- <Reference Include="Microsoft.CSharp" />
- <Reference Include="System.Data" />
- <Reference Include="System.Xml" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="ApiClient.cs" />
- <Compile Include="BaseApiClient.cs" />
- <Compile Include="BaseHttpApiClient.cs" />
- <Compile Include="DataSerializer.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="SerializationFormats.cs" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
- <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
- <Name>MediaBrowser.Model</Name>
- </ProjectReference>
- </ItemGroup>
- <ItemGroup>
- <None Include="packages.config" />
- </ItemGroup>
- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
- Other similar extension points exist, see Microsoft.Common.targets.
- <Target Name="BeforeBuild">
- </Target>
- <Target Name="AfterBuild">
- </Target>
- -->
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.ApiInteraction</RootNamespace>
+ <AssemblyName>MediaBrowser.ApiInteraction</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <RestorePackages>true</RestorePackages>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="protobuf-net, Version=2.0.0.621, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\protobuf-net.2.0.0.621\lib\net40\protobuf-net.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Text, Version=3.9.37.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.Text.3.9.37\lib\net35\ServiceStack.Text.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Net" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Net.Http.WebRequest" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AsyncHttpClient.cs" />
+ <Compile Include="BaseApiClient.cs" />
+ <Compile Include="ApiClient.cs" />
+ <Compile Include="DataSerializer.cs" />
+ <Compile Include="IAsyncHttpClient.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="SerializationFormats.cs" />
+ <Compile Include="ServerDiscovery.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
</Project> \ No newline at end of file
diff --git a/MediaBrowser.ApiInteraction/Properties/AssemblyInfo.cs b/MediaBrowser.ApiInteraction/Properties/AssemblyInfo.cs
index 74742759f3..bdacad9d17 100644
--- a/MediaBrowser.ApiInteraction/Properties/AssemblyInfo.cs
+++ b/MediaBrowser.ApiInteraction/Properties/AssemblyInfo.cs
@@ -1,35 +1,34 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("MediaBrowser.ApiInteraction")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("MediaBrowser.ApiInteraction")]
-[assembly: AssemblyCopyright("Copyright © 2012")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("677618f2-de4b-44f4-8dfd-a90176297ee2")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MediaBrowser.ApiInteraction")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.ApiInteraction")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("677618f2-de4b-44f4-8dfd-a90176297ee2")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("2.9.*")]
diff --git a/MediaBrowser.ApiInteraction/SerializationFormats.cs b/MediaBrowser.ApiInteraction/SerializationFormats.cs
index 21eb210d05..9bfba7c501 100644
--- a/MediaBrowser.ApiInteraction/SerializationFormats.cs
+++ b/MediaBrowser.ApiInteraction/SerializationFormats.cs
@@ -1,10 +1,18 @@
-
-namespace MediaBrowser.ApiInteraction
-{
- public enum SerializationFormats
- {
- Json,
- Jsv,
- Protobuf
- }
-}
+
+namespace MediaBrowser.ApiInteraction
+{
+ /// <summary>
+ /// Enum SerializationFormats
+ /// </summary>
+ public enum SerializationFormats
+ {
+ /// <summary>
+ /// The json
+ /// </summary>
+ Json,
+ /// <summary>
+ /// The protobuf
+ /// </summary>
+ Protobuf
+ }
+}
diff --git a/MediaBrowser.ApiInteraction/ServerDiscovery.cs b/MediaBrowser.ApiInteraction/ServerDiscovery.cs
new file mode 100644
index 0000000000..99a65db5d7
--- /dev/null
+++ b/MediaBrowser.ApiInteraction/ServerDiscovery.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.ApiInteraction
+{
+ public static class ServerDiscovery
+ {
+ /// <summary>
+ /// Attemps to discover the server within a local network
+ /// </summary>
+ public static async Task<IPEndPoint> DiscoverServer()
+ {
+ // Create a udp client
+ var client = new UdpClient(new IPEndPoint(IPAddress.Any, GetRandomUnusedPort()));
+
+ // Construct the message the server is expecting
+ var bytes = Encoding.UTF8.GetBytes("who is MediaBrowserServer?");
+
+ // Send it - must be IPAddress.Broadcast, 7359
+ var targetEndPoint = new IPEndPoint(IPAddress.Broadcast, 7359);
+
+ // Send it
+ await client.SendAsync(bytes, bytes.Length, targetEndPoint).ConfigureAwait(false);
+
+ // Get a result back
+ var result = await client.ReceiveAsync().ConfigureAwait(false);
+
+ if (result.RemoteEndPoint.Port == targetEndPoint.Port)
+ {
+ // Convert bytes to text
+ var text = Encoding.UTF8.GetString(result.Buffer);
+
+ // Expected response : MediaBrowserServer|192.168.1.1:1234
+ // If the response is what we're expecting, proceed
+ if (text.StartsWith("mediabrowserserver", StringComparison.OrdinalIgnoreCase))
+ {
+ text = text.Split('|')[1];
+
+ var vals = text.Split(':');
+
+ return new IPEndPoint(IPAddress.Parse(vals[0]), int.Parse(vals[1]));
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Gets a random port number that is currently available
+ /// </summary>
+ private static int GetRandomUnusedPort()
+ {
+ var listener = new TcpListener(IPAddress.Any, 0);
+ listener.Start();
+ var port = ((IPEndPoint)listener.LocalEndpoint).Port;
+ listener.Stop();
+ return port;
+ }
+ }
+}
diff --git a/MediaBrowser.ApiInteraction/packages.config b/MediaBrowser.ApiInteraction/packages.config
index 05294421d4..14eb42cace 100644
--- a/MediaBrowser.ApiInteraction/packages.config
+++ b/MediaBrowser.ApiInteraction/packages.config
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
- <package id="ServiceStack.Text" version="3.9.9" targetFramework="net45" />
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="protobuf-net" version="2.0.0.621" targetFramework="net45" />
+ <package id="ServiceStack.Text" version="3.9.37" targetFramework="net45" />
</packages> \ No newline at end of file