diff options
| author | LukePulverenti <luke.pulverenti@gmail.com> | 2013-02-20 20:33:05 -0500 |
|---|---|---|
| committer | LukePulverenti <luke.pulverenti@gmail.com> | 2013-02-20 20:33:05 -0500 |
| commit | 767cdc1f6f6a63ce997fc9476911e2c361f9d402 (patch) | |
| tree | 49add55976f895441167c66cfa95e5c7688d18ce /MediaBrowser.ApiInteraction | |
| parent | 845554722efaed872948a9e0f7202e3ef52f1b6e (diff) | |
Pushing missing changes
Diffstat (limited to 'MediaBrowser.ApiInteraction')
| -rw-r--r-- | MediaBrowser.ApiInteraction/ApiClient.cs | 1198 | ||||
| -rw-r--r-- | MediaBrowser.ApiInteraction/AsyncHttpClient.cs | 234 | ||||
| -rw-r--r-- | MediaBrowser.ApiInteraction/BaseApiClient.cs | 1260 | ||||
| -rw-r--r-- | MediaBrowser.ApiInteraction/BaseHttpApiClient.cs | 611 | ||||
| -rw-r--r-- | MediaBrowser.ApiInteraction/DataSerializer.cs | 143 | ||||
| -rw-r--r-- | MediaBrowser.ApiInteraction/IAsyncHttpClient.cs | 49 | ||||
| -rw-r--r-- | MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj | 158 | ||||
| -rw-r--r-- | MediaBrowser.ApiInteraction/Properties/AssemblyInfo.cs | 69 | ||||
| -rw-r--r-- | MediaBrowser.ApiInteraction/SerializationFormats.cs | 28 | ||||
| -rw-r--r-- | MediaBrowser.ApiInteraction/ServerDiscovery.cs | 63 | ||||
| -rw-r--r-- | MediaBrowser.ApiInteraction/packages.config | 7 |
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 |
