diff --git a/Emby.ApiClient/ConnectService.cs b/Emby.ApiClient/ConnectService.cs deleted file mode 100644 index ec08df7..0000000 --- a/Emby.ApiClient/ConnectService.cs +++ /dev/null @@ -1,324 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Connect; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Emby.ApiClient.Cryptography; -using Emby.ApiClient.Net; - -namespace Emby.ApiClient -{ - public class ConnectService - { - internal IJsonSerializer JsonSerializer { get; set; } - private readonly ILogger _logger; - private readonly IAsyncHttpClient _httpClient; - private readonly ICryptographyProvider _cryptographyProvider; - private readonly string _appName; - private readonly string _appVersion; - - public ConnectService(IJsonSerializer jsonSerializer, ILogger logger, IAsyncHttpClient httpClient, ICryptographyProvider cryptographyProvider, string appName, string appVersion) - { - JsonSerializer = jsonSerializer; - _logger = logger; - _httpClient = httpClient; - _cryptographyProvider = cryptographyProvider; - _appName = appName; - _appVersion = appVersion; - } - - public static string GetConnectPasswordMd5(string password, ICryptographyProvider cryptographyProvider) - { - password = ConnectPassword.PerformPreHashFilter(password ?? string.Empty); - - var bytes = Encoding.UTF8.GetBytes(password); - - bytes = cryptographyProvider.CreateMD5(bytes); - - var hash = BitConverter.ToString(bytes, 0, bytes.Length).Replace("-", string.Empty); - - return hash; - } - - public Task Authenticate(string username, string password) - { - var md5 = GetConnectPasswordMd5(password ?? string.Empty, _cryptographyProvider); - - var args = new Dictionary - { - {"nameOrEmail",username}, - {"password",md5} - }; - - return PostAsync(GetConnectUrl("user/authenticate"), args); - } - - public Task Logout(string accessToken) - { - if (string.IsNullOrWhiteSpace(accessToken)) - { - throw new ArgumentNullException("accessToken"); - } - - var args = new Dictionary - { - }; - - return PostAsync(GetConnectUrl("user/logout"), args, accessToken); - } - - public Task CreatePin(string deviceId) - { - var args = new Dictionary - { - {"deviceId",deviceId} - }; - - return PostAsync(GetConnectUrl("pin"), args); - } - - public async Task GetPinStatus(PinCreationResult pin) - { - var dict = new QueryStringDictionary(); - - dict.Add("deviceId", pin.DeviceId); - dict.Add("pin", pin.Pin); - - var url = GetConnectUrl("pin") + "?" + dict.GetQueryString(); - - var request = new HttpRequest - { - Method = "GET", - Url = url - - }; - - AddAppInfo(request, _appName, _appVersion); - - using (var stream = await _httpClient.SendAsync(request).ConfigureAwait(false)) - { - return JsonSerializer.DeserializeFromStream(stream); - } - } - - public Task ExchangePin(PinCreationResult pin) - { - var args = new Dictionary - { - {"deviceId",pin.DeviceId}, - {"pin",pin.Pin} - }; - - return PostAsync(GetConnectUrl("pin/authenticate"), args); - } - - private async Task PostAsync(string url, Dictionary args, string userAccessToken = null) - where T : class - { - var request = new HttpRequest - { - Url = url, - Method = "POST" - }; - - request.SetPostData(args); - - if (!string.IsNullOrEmpty(userAccessToken)) - { - AddUserAccessToken(request, userAccessToken); - } - - AddAppInfo(request, _appName, _appVersion); - - using (var stream = await _httpClient.SendAsync(request).ConfigureAwait(false)) - { - return JsonSerializer.DeserializeFromStream(stream); - } - } - - public async Task GetConnectUser(ConnectUserQuery query, string accessToken, CancellationToken cancellationToken = default(CancellationToken)) - { - if (string.IsNullOrWhiteSpace(accessToken)) - { - throw new ArgumentNullException("accessToken"); - } - - var dict = new QueryStringDictionary(); - - if (!string.IsNullOrWhiteSpace(query.Id)) - { - dict.Add("id", query.Id); - } - else if (!string.IsNullOrWhiteSpace(query.NameOrEmail)) - { - dict.Add("nameOrEmail", query.NameOrEmail); - } - else if (!string.IsNullOrWhiteSpace(query.Name)) - { - dict.Add("name", query.Name); - } - else if (!string.IsNullOrWhiteSpace(query.Email)) - { - dict.Add("email", query.Email); - } - else - { - throw new ArgumentException("Empty ConnectUserQuery supplied"); - } - - var url = GetConnectUrl("user") + "?" + dict.GetQueryString(); - - var request = new HttpRequest - { - Method = "GET", - Url = url, - CancellationToken = cancellationToken - }; - - AddUserAccessToken(request, accessToken); - AddAppInfo(request, _appName, _appVersion); - - using (var stream = await _httpClient.SendAsync(request).ConfigureAwait(false)) - { - return JsonSerializer.DeserializeFromStream(stream); - } - } - - public async Task GetServers(string userId, string accessToken, CancellationToken cancellationToken = default(CancellationToken)) - { - if (string.IsNullOrWhiteSpace(userId)) - { - throw new ArgumentNullException("userId"); - } - if (string.IsNullOrWhiteSpace(accessToken)) - { - throw new ArgumentNullException("accessToken"); - } - - var dict = new QueryStringDictionary(); - - dict.Add("userId", userId); - - var url = GetConnectUrl("servers") + "?" + dict.GetQueryString(); - - var request = new HttpRequest - { - Method = "GET", - Url = url, - CancellationToken = cancellationToken - }; - - AddUserAccessToken(request, accessToken); - AddAppInfo(request, _appName, _appVersion); - - using (var stream = await _httpClient.SendAsync(request).ConfigureAwait(false)) - { - using (var reader = new StreamReader(stream)) - { - var json = await reader.ReadToEndAsync().ConfigureAwait(false); - - _logger.Debug("Connect servers response: {0}", json); - - return JsonSerializer.DeserializeFromString(json); - } - } - } - - private void AddUserAccessToken(HttpRequest request, string accessToken) - { - if (string.IsNullOrWhiteSpace(accessToken)) - { - throw new ArgumentNullException("accessToken"); - } - request.RequestHeaders["X-Connect-UserToken"] = accessToken; - } - - private void AddAppInfo(HttpRequest request, string appName, string appVersion) - { - if (string.IsNullOrWhiteSpace(appName)) - { - throw new ArgumentNullException("appName"); - } - if (string.IsNullOrWhiteSpace(appVersion)) - { - throw new ArgumentNullException("appVersion"); - } - request.RequestHeaders["X-Application"] = appName + "/" + appVersion; - } - - private string GetConnectUrl(string handler) - { - if (string.IsNullOrWhiteSpace(handler)) - { - throw new ArgumentNullException("handler"); - } - return "https://connect.emby.media/service/" + handler; - } - - public async Task SignupForConnect(string email, string username, string password) - { - if (string.IsNullOrWhiteSpace(email)) - { - throw new ArgumentNullException("email"); - } - if (string.IsNullOrWhiteSpace(username)) - { - throw new ArgumentNullException("username"); - } - if (string.IsNullOrWhiteSpace(password)) - { - throw new ArgumentNullException("password"); - } - if (password.Length < 8) - { - throw new ArgumentException("password must be at least 8 characters"); - } - var request = new HttpRequest - { - Url = GetConnectUrl("register"), - Method = "POST" - }; - - var dict = new QueryStringDictionary(); - - dict.Add("email", Uri.EscapeDataString(email)); - dict.Add("userName", username); - dict.Add("password", password); - request.SetPostData(dict); - - request.RequestHeaders["X-Connect-Token"] = "CONNECT-REGISTER"; - AddAppInfo(request, _appName, _appVersion); - - using (var response = await _httpClient.GetResponse(request, true).ConfigureAwait(false)) - { - var responseObject = JsonSerializer.DeserializeFromStream(response.Content); - - if (string.Equals(responseObject.Status, "SUCCESS", StringComparison.OrdinalIgnoreCase)) - { - return ConnectSignupResponse.Success; - } - if (string.Equals(responseObject.Status, "USERNAME_IN_USE", StringComparison.OrdinalIgnoreCase)) - { - return ConnectSignupResponse.UsernameInUser; - } - if (string.Equals(responseObject.Status, "EMAIL_IN_USE", StringComparison.OrdinalIgnoreCase)) - { - return ConnectSignupResponse.EmailInUse; - } - return ConnectSignupResponse.Failure; - } - } - - private class RawConnectResponse - { - public string Status { get; set; } - public string Message { get; set; } - } - } -} diff --git a/Emby.ApiClient/ConnectionManager.cs b/Emby.ApiClient/ConnectionManager.cs deleted file mode 100644 index cd5b2de..0000000 --- a/Emby.ApiClient/ConnectionManager.cs +++ /dev/null @@ -1,914 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Connect; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Session; -using MediaBrowser.Model.System; -using MediaBrowser.Model.Users; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Emby.ApiClient.Cryptography; -using Emby.ApiClient.Data; -using Emby.ApiClient.Model; -using Emby.ApiClient.Net; - -namespace Emby.ApiClient -{ - public class ConnectionManager : IConnectionManager - { - public event EventHandler> LocalUserSignIn; - public event EventHandler> ConnectUserSignIn; - public event EventHandler> LocalUserSignOut; - public event EventHandler ConnectUserSignOut; - public event EventHandler RemoteLoggedOut; - - public event EventHandler> Connected; - - private readonly ICredentialProvider _credentialProvider; - private readonly INetworkConnection _networkConnectivity; - private readonly ILogger _logger; - private readonly IServerLocator _serverDiscovery; - private readonly IAsyncHttpClient _httpClient; - private readonly Func _webSocketFactory; - private readonly ICryptographyProvider _cryptographyProvider; - private readonly ILocalAssetManager _localAssetManager; - - public Dictionary ApiClients { get; private set; } - - public string ApplicationName { get; private set; } - public string ApplicationVersion { get; private set; } - public IDevice Device { get; private set; } - public ClientCapabilities ClientCapabilities { get; private set; } - - public IApiClient CurrentApiClient { get; private set; } - - private readonly ConnectService _connectService; - - public ConnectUser ConnectUser { get; private set; } - - public ConnectionManager(ILogger logger, - ICredentialProvider credentialProvider, - INetworkConnection networkConnectivity, - IServerLocator serverDiscovery, - string applicationName, - string applicationVersion, - IDevice device, - ClientCapabilities clientCapabilities, - ICryptographyProvider cryptographyProvider, - Func webSocketFactory = null, - ILocalAssetManager localAssetManager = null) - { - _credentialProvider = credentialProvider; - _networkConnectivity = networkConnectivity; - _logger = logger; - _serverDiscovery = serverDiscovery; - _httpClient = AsyncHttpClientFactory.Create(logger); - ClientCapabilities = clientCapabilities; - _webSocketFactory = webSocketFactory; - _cryptographyProvider = cryptographyProvider; - _localAssetManager = localAssetManager; - - Device = device; - ApplicationVersion = applicationVersion; - ApplicationName = applicationName; - ApiClients = new Dictionary(StringComparer.OrdinalIgnoreCase); - SaveLocalCredentials = true; - - var jsonSerializer = new NewtonsoftJsonSerializer(); - _connectService = new ConnectService(jsonSerializer, _logger, _httpClient, _cryptographyProvider, applicationName, applicationVersion); - } - - public IJsonSerializer JsonSerializer - { - get { return _connectService.JsonSerializer; } - set { _connectService.JsonSerializer = value; } - } - - public bool SaveLocalCredentials { get; set; } - - private IApiClient GetOrAddApiClient(ServerInfo server, ConnectionMode connectionMode) - { - IApiClient apiClient; - - if (!ApiClients.TryGetValue(server.Id, out apiClient)) - { - var address = server.GetAddress(connectionMode); - - apiClient = new ApiClient(_logger, address, ApplicationName, Device, ApplicationVersion, _cryptographyProvider, _localAssetManager) - { - JsonSerializer = JsonSerializer, - OnAuthenticated = ApiClientOnAuthenticated - }; - - ApiClients[server.Id] = apiClient; - } - - if (string.IsNullOrEmpty(server.AccessToken)) - { - apiClient.ClearAuthenticationInfo(); - } - else - { - apiClient.SetAuthenticationInfo(server.AccessToken, server.UserId); - } - - return apiClient; - } - - private Task ApiClientOnAuthenticated(IApiClient apiClient, AuthenticationResult result) - { - return OnAuthenticated(apiClient, result, new ConnectionOptions(), SaveLocalCredentials); - } - - private async void AfterConnected(IApiClient apiClient, ConnectionOptions options) - { - if (options.ReportCapabilities) - { - try - { - await apiClient.ReportCapabilities(ClientCapabilities).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error reporting capabilities", ex); - } - } - - if (options.EnableWebSocket) - { - if (_webSocketFactory != null) - { - ((ApiClient)apiClient).OpenWebSocket(_webSocketFactory); - } - } - } - - public async Task> GetAvailableServers(CancellationToken cancellationToken = default(CancellationToken)) - { - var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); - - _logger.Debug("{0} servers in saved credentials", credentials.Servers.Count); - - if (_networkConnectivity.GetNetworkStatus().GetIsAnyLocalNetworkAvailable()) - { - foreach (var server in await FindServers(cancellationToken).ConfigureAwait(false)) - { - credentials.AddOrUpdateServer(server); - } - } - - if (!string.IsNullOrWhiteSpace(credentials.ConnectAccessToken)) - { - await EnsureConnectUser(credentials, cancellationToken).ConfigureAwait(false); - - var connectServers = await GetConnectServers(credentials.ConnectUserId, credentials.ConnectAccessToken, cancellationToken) - .ConfigureAwait(false); - - foreach (var server in connectServers) - { - credentials.AddOrUpdateServer(server); - } - - // Remove old servers - var newServerList = credentials.Servers - .Where(i => string.IsNullOrWhiteSpace(i.ExchangeToken) || - connectServers.Any(c => string.Equals(c.Id, i.Id, StringComparison.OrdinalIgnoreCase))); - - credentials.Servers = newServerList.ToList(); - } - - await _credentialProvider.SaveServerCredentials(credentials).ConfigureAwait(false); - - return credentials.Servers.OrderByDescending(i => i.DateLastAccessed).ToList(); - } - - private async Task> GetConnectServers(string userId, string accessToken, CancellationToken cancellationToken) - { - try - { - var servers = await _connectService.GetServers(userId, accessToken, cancellationToken).ConfigureAwait(false); - - _logger.Debug("User has {0} connect servers", servers.Length); - - return servers.Select(i => new ServerInfo - { - ExchangeToken = i.AccessKey, - Id = i.SystemId, - Name = i.Name, - RemoteAddress = i.Url, - LocalAddress = i.LocalAddress, - UserLinkType = string.Equals(i.UserType, "guest", StringComparison.OrdinalIgnoreCase) ? UserLinkType.Guest : UserLinkType.LinkedUser - - }).ToList(); - } - catch - { - return new List(); - } - } - - private async Task> FindServers(CancellationToken cancellationToken) - { - List servers; - - try - { - servers = await _serverDiscovery.FindServers(1500, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - _logger.Debug("No servers found via local discovery."); - - servers = new List(); - } - catch (Exception ex) - { - _logger.ErrorException("Error discovering servers.", ex); - - servers = new List(); - } - - return servers.Select(i => new ServerInfo - { - Id = i.Id, - LocalAddress = ConvertEndpointAddressToManualAddress(i) ?? i.Address, - Name = i.Name - }) - .ToList(); - } - - private string ConvertEndpointAddressToManualAddress(ServerDiscoveryInfo info) - { - if (!string.IsNullOrWhiteSpace(info.Address) && !string.IsNullOrWhiteSpace(info.EndpointAddress)) - { - var address = info.EndpointAddress.Split(':').First(); - - // Determine the port, if any - var parts = info.Address.Split(':'); - if (parts.Length > 1) - { - var portString = parts.Last(); - int port; - if (int.TryParse(portString, NumberStyles.Any, CultureInfo.InvariantCulture, out port)) - { - address += ":" + portString; - } - } - - return NormalizeAddress(address); - } - - return null; - } - - public async Task Connect(CancellationToken cancellationToken = default(CancellationToken)) - { - var servers = await GetAvailableServers(cancellationToken).ConfigureAwait(false); - - return await Connect(servers, cancellationToken).ConfigureAwait(false); - } - - /// - /// Loops through a list of servers and returns the first that is available for connection - /// - private async Task Connect(List servers, CancellationToken cancellationToken) - { - servers = servers - .OrderByDescending(i => i.DateLastAccessed) - .ToList(); - - if (servers.Count == 1) - { - _logger.Debug("1 server in the list."); - - var result = await Connect(servers[0], cancellationToken).ConfigureAwait(false); - - if (result.State == ConnectionState.Unavailable) - { - result.State = result.ConnectUser == null ? - ConnectionState.ConnectSignIn : - ConnectionState.ServerSelection; - } - - return result; - } - - var firstServer = servers.FirstOrDefault(); - // See if we have any saved credentials and can auto sign in - if (firstServer != null && !string.IsNullOrEmpty(firstServer.AccessToken)) - { - var result = await Connect(firstServer, cancellationToken).ConfigureAwait(false); - - if (result.State == ConnectionState.SignedIn) - { - return result; - } - } - - var finalResult = new ConnectionResult - { - Servers = servers, - ConnectUser = ConnectUser - }; - - finalResult.State = servers.Count == 0 && finalResult.ConnectUser == null ? - ConnectionState.ConnectSignIn : - ConnectionState.ServerSelection; - - return finalResult; - } - - /// - /// Attempts to connect to a server - /// - public Task Connect(ServerInfo server, CancellationToken cancellationToken = default(CancellationToken)) - { - return Connect(server, new ConnectionOptions(), cancellationToken); - } - - public async Task Connect(ServerInfo server, ConnectionOptions options, CancellationToken cancellationToken = default(CancellationToken)) - { - var result = new ConnectionResult - { - State = ConnectionState.Unavailable - }; - - PublicSystemInfo systemInfo = null; - var connectionMode = ConnectionMode.Manual; - - var tests = new[] { ConnectionMode.Manual, ConnectionMode.Local, ConnectionMode.Remote }.ToList(); - - // If we've connected to the server before, try to optimize by starting with the last used connection mode - if (server.LastConnectionMode.HasValue) - { - tests.Remove(server.LastConnectionMode.Value); - tests.Insert(0, server.LastConnectionMode.Value); - } - - var isLocalNetworkAvailable = _networkConnectivity.GetNetworkStatus().GetIsAnyLocalNetworkAvailable(); - - // Kick off wake on lan on a separate thread (if applicable) - var sendWakeOnLan = server.WakeOnLanInfos.Count > 0 && isLocalNetworkAvailable; - - var wakeOnLanTask = sendWakeOnLan ? - Task.Run(() => WakeServer(server, cancellationToken), cancellationToken) : - Task.FromResult(true); - - var wakeOnLanSendTime = DateTime.Now; - - foreach (var mode in tests) - { - _logger.Debug("Attempting to connect to server {0}. ConnectionMode: {1}", server.Name, mode.ToString()); - - if (mode == ConnectionMode.Local) - { - // Try connect locally if there's a local address, - // and we're either on localhost or the device has a local connection - if (!string.IsNullOrEmpty(server.LocalAddress) && isLocalNetworkAvailable) - { - // Try to connect to the local address - systemInfo = await TryConnect(server.LocalAddress, 8000, cancellationToken).ConfigureAwait(false); - } - } - else if (mode == ConnectionMode.Manual) - { - // Try manual address if there is one, but only if it's different from the local/remote addresses - if (!string.IsNullOrEmpty(server.ManualAddress) - && !string.Equals(server.ManualAddress, server.LocalAddress, StringComparison.OrdinalIgnoreCase) - && !string.Equals(server.ManualAddress, server.RemoteAddress, StringComparison.OrdinalIgnoreCase)) - { - // Try to connect to the local address - systemInfo = await TryConnect(server.ManualAddress, 15000, cancellationToken).ConfigureAwait(false); - } - } - else if (mode == ConnectionMode.Remote) - { - if (!string.IsNullOrEmpty(server.RemoteAddress)) - { - systemInfo = await TryConnect(server.RemoteAddress, 15000, cancellationToken).ConfigureAwait(false); - } - } - - if (systemInfo != null) - { - connectionMode = mode; - break; - } - } - - if (systemInfo == null && !string.IsNullOrEmpty(server.LocalAddress) && isLocalNetworkAvailable && sendWakeOnLan) - { - await wakeOnLanTask.ConfigureAwait(false); - - // After wake on lan finishes, make sure at least 10 seconds have elapsed since the time it was first sent out - var waitTime = TimeSpan.FromSeconds(10).TotalMilliseconds - - (DateTime.Now - wakeOnLanSendTime).TotalMilliseconds; - - if (waitTime > 0) - { - await Task.Delay(Convert.ToInt32(waitTime, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false); - } - - systemInfo = await TryConnect(server.LocalAddress, 15000, cancellationToken).ConfigureAwait(false); - } - - if (systemInfo != null) - { - await OnSuccessfulConnection(server, options, systemInfo, result, connectionMode, cancellationToken) - .ConfigureAwait(false); - } - - result.ConnectUser = ConnectUser; - return result; - } - - private async Task OnSuccessfulConnection(ServerInfo server, - ConnectionOptions options, - PublicSystemInfo systemInfo, - ConnectionResult result, - ConnectionMode connectionMode, - CancellationToken cancellationToken) - { - server.ImportInfo(systemInfo); - - var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); - - if (!string.IsNullOrWhiteSpace(credentials.ConnectAccessToken)) - { - await EnsureConnectUser(credentials, cancellationToken).ConfigureAwait(false); - - if (!string.IsNullOrWhiteSpace(server.ExchangeToken)) - { - await AddAuthenticationInfoFromConnect(server, connectionMode, credentials, cancellationToken).ConfigureAwait(false); - } - } - - if (!string.IsNullOrWhiteSpace(server.AccessToken)) - { - await ValidateAuthentication(server, connectionMode, options, cancellationToken).ConfigureAwait(false); - } - - credentials.AddOrUpdateServer(server); - - if (options.UpdateDateLastAccessed) - { - server.DateLastAccessed = DateTime.UtcNow; - } - server.LastConnectionMode = connectionMode; - - await _credentialProvider.SaveServerCredentials(credentials).ConfigureAwait(false); - - result.ApiClient = GetOrAddApiClient(server, connectionMode); - result.State = string.IsNullOrEmpty(server.AccessToken) ? - ConnectionState.ServerSignIn : - ConnectionState.SignedIn; - - ((ApiClient)result.ApiClient).EnableAutomaticNetworking(server, connectionMode, _networkConnectivity); - - if (result.State == ConnectionState.SignedIn) - { - AfterConnected(result.ApiClient, options); - } - - CurrentApiClient = result.ApiClient; - - result.Servers.Add(server); - - if (Connected != null) - { - Connected(this, new GenericEventArgs(result)); - } - } - - public Task Connect(IApiClient apiClient, CancellationToken cancellationToken = default(CancellationToken)) - { - var client = (ApiClient)apiClient; - return Connect(client.ServerInfo, cancellationToken); - } - - private async Task AddAuthenticationInfoFromConnect(ServerInfo server, - ConnectionMode connectionMode, - ServerCredentials credentials, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(credentials.ConnectUserId)) - { - throw new ArgumentException("server"); - } - - if (string.IsNullOrWhiteSpace(server.ExchangeToken)) - { - throw new ArgumentException("server"); - } - - _logger.Debug("Adding authentication info from Connect"); - - var url = server.GetAddress(connectionMode); - - url += "/emby/Connect/Exchange?format=json&ConnectUserId=" + credentials.ConnectUserId; - - var headers = new HttpHeaders(); - headers.SetAccessToken(server.ExchangeToken); - headers["X-Emby-Authorization"] = "MediaBrowser Client=\"" + ApplicationName + "\", Device=\"" + Device.DeviceName + "\", DeviceId=\"" + Device.DeviceId + "\", Version=\"" + ApplicationVersion + "\""; - - try - { - using (var stream = await _httpClient.SendAsync(new HttpRequest - { - CancellationToken = cancellationToken, - Method = "GET", - RequestHeaders = headers, - Url = url - - }).ConfigureAwait(false)) - { - var auth = JsonSerializer.DeserializeFromStream(stream); - - server.UserId = auth.LocalUserId; - server.AccessToken = auth.AccessToken; - } - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - // Already logged at a lower level - - server.UserId = null; - server.AccessToken = null; - } - } - - private async Task EnsureConnectUser(ServerCredentials credentials, CancellationToken cancellationToken) - { - if (ConnectUser != null && string.Equals(ConnectUser.Id, credentials.ConnectUserId, StringComparison.Ordinal)) - { - return; - } - - ConnectUser = null; - - if (!string.IsNullOrWhiteSpace(credentials.ConnectUserId) && !string.IsNullOrWhiteSpace(credentials.ConnectAccessToken)) - { - try - { - ConnectUser = await _connectService.GetConnectUser(new ConnectUserQuery - { - Id = credentials.ConnectUserId - - }, credentials.ConnectAccessToken, cancellationToken).ConfigureAwait(false); - - OnConnectUserSignIn(ConnectUser); - } - catch - { - // Already logged at lower levels - } - } - } - - private async Task ValidateAuthentication(ServerInfo server, ConnectionMode connectionMode, ConnectionOptions options, CancellationToken cancellationToken) - { - _logger.Debug("Validating saved authentication"); - - var url = server.GetAddress(connectionMode); - - var headers = new HttpHeaders(); - headers.SetAccessToken(server.AccessToken); - - var request = new HttpRequest - { - CancellationToken = cancellationToken, - Method = "GET", - RequestHeaders = headers, - Url = url + "/emby/system/info?format=json" - }; - - try - { - using (var stream = await _httpClient.SendAsync(request).ConfigureAwait(false)) - { - var systemInfo = JsonSerializer.DeserializeFromStream(stream); - - server.ImportInfo(systemInfo); - } - - if (!string.IsNullOrEmpty(server.UserId)) - { - request.Url = url + "/mediabrowser/users/" + server.UserId + "?format=json"; - - using (var stream = await _httpClient.SendAsync(request).ConfigureAwait(false)) - { - var localUser = JsonSerializer.DeserializeFromStream(stream); - - OnLocalUserSignIn(options, localUser); - } - } - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - // Already logged at a lower level - - server.UserId = null; - server.AccessToken = null; - } - } - - private async Task TryConnect(string url, int timeout, CancellationToken cancellationToken) - { - url += "/emby/system/info/public?format=json"; - - try - { - using (var stream = await _httpClient.SendAsync(new HttpRequest - { - Url = url, - CancellationToken = cancellationToken, - Timeout = timeout, - Method = "GET" - - }).ConfigureAwait(false)) - { - return JsonSerializer.DeserializeFromStream(stream); - } - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - // Already logged at a lower level - - return null; - } - } - - /// - /// Wakes all servers. - /// - /// The cancellation token. - /// Task. - private async Task WakeAllServers(CancellationToken cancellationToken) - { - var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); - - foreach (var server in credentials.Servers.ToList()) - { - await WakeServer(server, cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Wakes a server - /// - private async Task WakeServer(ServerInfo server, CancellationToken cancellationToken) - { - foreach (var info in server.WakeOnLanInfos) - { - await WakeServer(info, cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Wakes a device based on mac address - /// - private async Task WakeServer(WakeOnLanInfo info, CancellationToken cancellationToken) - { - try - { - await _networkConnectivity.SendWakeOnLan(info.MacAddress, info.Port, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending wake on lan command", ex); - } - } - - public void Dispose() - { - foreach (var client in ApiClients.Values.ToList()) - { - client.Dispose(); - } - } - - public IApiClient GetApiClient(IHasServerId item) - { - return GetApiClient(item.ServerId); - } - - public IApiClient GetApiClient(string serverId) - { - return ApiClients.Values.OfType().FirstOrDefault(i => string.Equals(i.ServerInfo.Id, serverId, StringComparison.OrdinalIgnoreCase)); - } - - public async Task Connect(string address, CancellationToken cancellationToken = default(CancellationToken)) - { - address = NormalizeAddress(address); - - var publicInfo = await TryConnect(address, 15000, cancellationToken).ConfigureAwait(false); - - if (publicInfo == null) - { - return new ConnectionResult - { - State = ConnectionState.Unavailable, - ConnectUser = ConnectUser - }; - } - - var server = new ServerInfo - { - ManualAddress = address, - LastConnectionMode = ConnectionMode.Manual - }; - - server.ImportInfo(publicInfo); - - return await Connect(server, cancellationToken).ConfigureAwait(false); - } - - private string NormalizeAddress(string address) - { - if (string.IsNullOrEmpty(address)) - { - throw new ArgumentNullException("address"); - } - - if (!address.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - address = "http://" + address; - } - - return address; - } - - private async Task OnAuthenticated(IApiClient apiClient, - AuthenticationResult result, - ConnectionOptions options, - bool saveCredentials) - { - var server = ((ApiClient)apiClient).ServerInfo; - - var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); - - if (options.UpdateDateLastAccessed) - { - server.DateLastAccessed = DateTime.UtcNow; - } - - if (saveCredentials) - { - server.UserId = result.User.Id; - server.AccessToken = result.AccessToken; - } - else - { - server.UserId = null; - server.AccessToken = null; - } - - credentials.AddOrUpdateServer(server); - await _credentialProvider.SaveServerCredentials(credentials).ConfigureAwait(false); - - AfterConnected(apiClient, options); - - OnLocalUserSignIn(options, result.User); - } - - private void OnLocalUserSignIn(ConnectionOptions options, UserDto user) - { - // TODO: Create a separate property for this - if (options.UpdateDateLastAccessed) - { - if (LocalUserSignIn != null) - { - LocalUserSignIn(this, new GenericEventArgs(user)); - } - } - } - - private void OnLocalUserSignout(IApiClient apiClient) - { - if (LocalUserSignOut != null) - { - LocalUserSignOut(this, new GenericEventArgs(apiClient)); - } - } - - private void OnConnectUserSignIn(ConnectUser user) - { - ConnectUser = user; - - if (ConnectUserSignIn != null) - { - ConnectUserSignIn(this, new GenericEventArgs(ConnectUser)); - } - } - - public async Task Logout() - { - foreach (var client in ApiClients.Values.ToList()) - { - if (!string.IsNullOrEmpty(client.AccessToken)) - { - await client.Logout().ConfigureAwait(false); - OnLocalUserSignout(client); - } - } - - var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); - - var servers = credentials.Servers - .Where(i => !i.UserLinkType.HasValue || i.UserLinkType.Value != UserLinkType.Guest) - .ToList(); - - foreach (var server in servers) - { - server.AccessToken = null; - server.UserId = null; - server.ExchangeToken = null; - } - - credentials.Servers = servers; - credentials.ConnectAccessToken = null; - credentials.ConnectUserId = null; - - await _credentialProvider.SaveServerCredentials(credentials).ConfigureAwait(false); - - if (ConnectUser != null) - { - ConnectUser = null; - - if (ConnectUserSignOut != null) - { - ConnectUserSignOut(this, EventArgs.Empty); - } - } - } - - public async Task LoginToConnect(string username, string password) - { - var result = await _connectService.Authenticate(username, password).ConfigureAwait(false); - - var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); - - credentials.ConnectAccessToken = result.AccessToken; - credentials.ConnectUserId = result.User.Id; - - await _credentialProvider.SaveServerCredentials(credentials).ConfigureAwait(false); - - OnConnectUserSignIn(result.User); - } - - public Task CreatePin() - { - return _connectService.CreatePin(Device.DeviceId); - } - - public Task GetPinStatus(PinCreationResult pin) - { - return _connectService.GetPinStatus(pin); - } - - public async Task ExchangePin(PinCreationResult pin) - { - var result = await _connectService.ExchangePin(pin); - - var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); - - credentials.ConnectAccessToken = result.AccessToken; - credentials.ConnectUserId = result.UserId; - - await EnsureConnectUser(credentials, CancellationToken.None).ConfigureAwait(false); - - await _credentialProvider.SaveServerCredentials(credentials).ConfigureAwait(false); - } - - public async Task GetServerInfo(string id) - { - var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); - - return credentials.Servers.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); - } - - public Task SignupForConnect(string email, string username, string password, CancellationToken cancellationToken) - { - return _connectService.SignupForConnect(email, username, password); - } - } -} diff --git a/Emby.ApiClient/Cryptography/CryptographyProvider.cs b/Emby.ApiClient/Cryptography/CryptographyProvider.cs deleted file mode 100644 index d3afb2a..0000000 --- a/Emby.ApiClient/Cryptography/CryptographyProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Security.Cryptography; - -namespace Emby.ApiClient.Cryptography -{ - public class CryptographyProvider : ICryptographyProvider - { - public byte[] CreateSha1(byte[] value) - { - using (var provider = SHA1.Create()) - { - return provider.ComputeHash(value); - } - } - - public byte[] CreateMD5(byte[] value) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(value); - } - } - } -} diff --git a/Emby.ApiClient/Cryptography/ICryptographyProvider.cs b/Emby.ApiClient/Cryptography/ICryptographyProvider.cs deleted file mode 100644 index 282aadf..0000000 --- a/Emby.ApiClient/Cryptography/ICryptographyProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ - -namespace Emby.ApiClient.Cryptography -{ - public interface ICryptographyProvider - { - byte[] CreateSha1(byte[] value); - byte[] CreateMD5(byte[] value); - } -} diff --git a/Emby.ApiClient/Data/IFileRepository.cs b/Emby.ApiClient/Data/IFileRepository.cs deleted file mode 100644 index f3c4f0a..0000000 --- a/Emby.ApiClient/Data/IFileRepository.cs +++ /dev/null @@ -1,92 +0,0 @@ -using MediaBrowser.Model.Sync; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -namespace Emby.ApiClient.Data -{ - public interface IFileRepository - { - /// - /// Gets the file system entries. - /// - /// The path. - /// Task<List<DeviceFileInfo>>. - Task> GetFileSystemEntries(string path); - - /// - /// Saves the file. - /// - /// The stream. - /// The path. - /// Task. - Task SaveFile(Stream stream, string path); - -#if WINDOWS_UWP - /// - /// Saves the file. - /// - /// The file. - /// The path. - /// Task. - Task SaveFile(Windows.Storage.IStorageFile file, string path); -#endif - - /// - /// Deletes the file. - /// - /// The path. - /// Task. - Task DeleteFile(string path); - - /// - /// Deletes the directory. - /// - /// The path. - /// Task. - Task DeleteFolder(string path); - - /// - /// Strips invalid characters from a given name - /// - /// The name. - /// System.String. - string GetValidFileName(string name); - - /// - /// Files the exists. - /// - /// The path. - /// Task<System.Boolean>. - Task FileExists(string path); - - /// - /// Gets the full local path. - /// - /// The path. - /// System.String. - string GetFullLocalPath(IEnumerable path); - - /// - /// Gets the parent directory path. - /// - /// The path. - /// System.String. - string GetParentDirectoryPath(string path); - - /// - /// Gets the file stream. - /// - /// The path. - /// Task<Stream>. - Task GetFileStream(string path); -#if WINDOWS_UWP - /// - /// Gets the file. - /// - /// The path. - /// Task<File>. - Task GetFile(string path); -#endif - } -} diff --git a/Emby.ApiClient/Data/IImageRepository.cs b/Emby.ApiClient/Data/IImageRepository.cs deleted file mode 100644 index 4b324e6..0000000 --- a/Emby.ApiClient/Data/IImageRepository.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.IO; -using System.Threading.Tasks; - -namespace Emby.ApiClient.Data -{ - public interface IImageRepository - { - /// - /// Saves the image. - /// - /// The item identifier. - /// The image identifier. - /// The stream. - /// Task. - Task SaveImage(string itemId, string imageId, Stream stream); - /// - /// Gets the image. - /// - /// The item identifier. - /// The image identifier. - /// Task<Stream>. - Task GetImage(string itemId, string imageId); - /// - /// Deletes the image. - /// - /// The item identifier. - /// The image identifier. - /// Task. - Task DeleteImage(string itemId, string imageId); - /// - /// Determines whether the specified item identifier has image. - /// - /// The item identifier. - /// The image identifier. - Task HasImage(string itemId, string imageId); - /// - /// Deletes the images. - /// - /// The item identifier. - /// Task. - Task DeleteImages(string itemId); - } -} diff --git a/Emby.ApiClient/Data/IItemRepository.cs b/Emby.ApiClient/Data/IItemRepository.cs deleted file mode 100644 index a3c4932..0000000 --- a/Emby.ApiClient/Data/IItemRepository.cs +++ /dev/null @@ -1,85 +0,0 @@ -using MediaBrowser.Model.Sync; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Emby.ApiClient.Data -{ - /// - /// Interface IItemRepository - /// - public interface IItemRepository - { - /// - /// Adds the or update. - /// - /// The item. - /// Task. - Task AddOrUpdate(LocalItem item); - - /// - /// Gets the item. - /// - /// The identifier. - /// Task<BaseItemDto>. - Task Get(string id); - - /// - /// Deletes the specified identifier. - /// - /// The identifier. - /// Task. - Task Delete(string id); - - /// - /// Gets the server item ids. - /// - /// The server identifier. - /// Task<List<System.String>>. - Task> GetServerItemIds(string serverId); - - /// - /// Queries all items for a server Id and returns a list of unique item types. - /// - /// The server identifier. - /// The user identifier. - /// Task<List<System.String>>. - Task> GetItemTypes(string serverId, string userId); - - /// - /// Gets the items. - /// - /// The query. - /// Task<List<LocalItem>>. - Task> GetItems(LocalItemQuery query); - - /// - /// Gets a list of unique AlbumArtist values - /// - /// The server identifier. - /// The user identifier. - /// Task<List<System.String>>. - Task> GetAlbumArtists(string serverId, string userId); - - /// - /// Gets a list of unique series, by id - /// Name = Album property - /// Id = SeriesId property - /// PrimaryImageTag = SeriesPrimaryImageTag - /// - /// The server identifier. - /// The user identifier. - /// Task<List<LocalItemInfo>>. - Task> GetTvSeries(string serverId, string userId); - - /// - /// Gets a list of unique photo albums, by Id - /// Name = Album property - /// Id = AlbumId property - /// PrimaryImageTag = AlbumPrimaryImageTag - /// - /// The server identifier. - /// The user identifier. - /// Task<List<LocalItemInfo>>. - Task> GetPhotoAlbums(string serverId, string userId); - } -} diff --git a/Emby.ApiClient/Data/ILocalAssetManager.cs b/Emby.ApiClient/Data/ILocalAssetManager.cs deleted file mode 100644 index 70da563..0000000 --- a/Emby.ApiClient/Data/ILocalAssetManager.cs +++ /dev/null @@ -1,237 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Sync; -using MediaBrowser.Model.Users; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using Emby.ApiClient.Model; - -namespace Emby.ApiClient.Data -{ - public interface ILocalAssetManager - { - /// - /// Records the user action. - /// - /// The action. - /// Task. - Task RecordUserAction(UserAction action); - - /// - /// Deletes the specified action. - /// - /// The action. - /// Task. - Task Delete(UserAction action); - - /// - /// Deletes the specified item. - /// - /// The item. - /// Task. - Task Delete(LocalItem item); - - /// - /// Gets all user actions by serverId - /// - /// - /// - Task> GetUserActions(string serverId); - - /// - /// Adds the or update. - /// - /// The item. - /// Task. - Task AddOrUpdate(LocalItem item); - - /// - /// Gets the files. - /// - /// The item. - /// Task<List<ItemFileInfo>>. - Task> GetFiles(LocalItem item); - - /// - /// Deletes the specified file. - /// - /// The path. - /// Task. - Task DeleteFile(string path); - - /// - /// Saves the subtitles. - /// - /// The stream. - /// The format. - /// The item. - /// The language. - /// if set to true [is forced]. - /// Task<System.String>. - Task SaveSubtitles(Stream stream, - string format, - LocalItem item, - string language, - bool isForced); - - /// - /// Saves the media. - /// - /// The stream. - /// The local item. - /// The server. - /// Task. - Task SaveMedia(Stream stream, LocalItem localItem, ServerInfo server); -#if WINDOWS_UWP - /// - /// Saves the media. - /// - /// The file. - /// The local item. - /// The server. - /// Task. - Task SaveMedia(Windows.Storage.IStorageFile file, LocalItem localItem, ServerInfo server); -#endif - /// - /// Creates the local item. - /// - /// The library item. - /// The server. - /// The synchronize job item identifier. - /// Name of the original file. - /// LocalItem. - LocalItem CreateLocalItem(BaseItemDto libraryItem, ServerInfo server, string syncJobItemId, string originalFileName); - /// - /// Gets the local item. - /// - /// The local identifier. - /// Task<LocalItem>. - Task GetLocalItem(string localId); - /// - /// Gets the local item. - /// - /// The server identifier. - /// The item identifier. - /// Task<LocalItem>. - Task GetLocalItem(string serverId, string itemId); - /// - /// Files the exists. - /// - /// The path. - /// Task<System.Boolean>. - Task FileExists(string path); - /// - /// Gets the server item ids. - /// - /// The server identifier. - /// Task<List<System.String>>. - Task> GetServerItemIds(string serverId); - /// - /// Gets the file stream. - /// - /// The information. - /// Task<Stream>. - Task GetFileStream(StreamInfo info); - /// - /// Gets the file stream. - /// - /// The path. - /// Task<Stream>. - Task GetFileStream(string path); - /// - /// Saves the offline user. - /// - /// The user. - /// Task. - Task SaveOfflineUser(UserDto user); - /// - /// Deletes the offline user. - /// - /// The identifier. - /// Task. - Task DeleteOfflineUser(string id); - /// - /// Saves the user image. - /// - /// The user. - /// The stream. - /// Task. - Task SaveImage(UserDto user, Stream stream); - /// - /// Gets the user image. - /// - /// The user. - /// Task<Stream>. - Task GetImage(UserDto user); - /// - /// Deletes the user image. - /// - /// The user. - /// Task. - Task DeleteImage(UserDto user); - /// - /// Determines whether the specified user has image. - /// - /// The user. - Task HasImage(UserDto user); - /// - /// Saves the item image. - /// - /// The server identifier. - /// The item identifier. - /// The image identifier. - /// The stream. - /// Task. - Task SaveImage(string serverId, string itemId, string imageId, Stream stream); - /// - /// Determines whether the specified server identifier has image. - /// - /// The server identifier. - /// The item identifier. - /// The image identifier. - Task HasImage(string serverId, string itemId, string imageId); - /// - /// Gets the image. - /// - /// The server identifier. - /// The item identifier. - /// The image identifier. - /// Task<Stream>. - Task GetImage(string serverId, string itemId, string imageId); - /// - /// Determines whether the specified item has image. - /// - /// The item. - /// The image identifier. - Task HasImage(BaseItemDto item, string imageId); - /// - /// Gets the image. - /// - /// The item. - /// The image identifier. - /// Task<Stream>. - Task GetImage(BaseItemDto item, string imageId); - /// - /// Gets the views. - /// - /// The server identifier. - /// The user identifier. - /// Task<List<BaseItemDto>>. - Task> GetViews(string serverId, string userId); - /// - /// Gets the items. - /// - /// The user. - /// The parent item. - /// Task<List<BaseItemDto>>. - Task> GetItems(UserDto user, BaseItemDto parentItem); - /// - /// Gets the user. - /// - /// The identifier. - /// Task<UserDto>. - Task GetUser(string id); - } -} \ No newline at end of file diff --git a/Emby.ApiClient/Data/IUserActionRepository.cs b/Emby.ApiClient/Data/IUserActionRepository.cs deleted file mode 100644 index 2948d1e..0000000 --- a/Emby.ApiClient/Data/IUserActionRepository.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MediaBrowser.Model.Users; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Emby.ApiClient.Data -{ - public interface IUserActionRepository - { - /// - /// Creates the specified action. - /// - /// The action. - /// Task. - Task Create(UserAction action); - - /// - /// Deletes the specified action. - /// - /// The action. - /// Task. - Task Delete(UserAction action); - - /// - /// Gets all user actions by serverId - /// - /// - /// - Task> Get(string serverId); - } -} diff --git a/Emby.ApiClient/Data/IUserRepository.cs b/Emby.ApiClient/Data/IUserRepository.cs deleted file mode 100644 index b1dbbc0..0000000 --- a/Emby.ApiClient/Data/IUserRepository.cs +++ /dev/null @@ -1,34 +0,0 @@ -using MediaBrowser.Model.Dto; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Emby.ApiClient.Data -{ - public interface IUserRepository - { - /// - /// Adds the or update. - /// - /// The identifier. - /// The user. - /// Task. - Task AddOrUpdate(string id, UserDto user); - /// - /// Deletes the specified identifier. - /// - /// The identifier. - /// Task. - Task Delete(string id); - /// - /// Gets the specified identifier. - /// - /// The identifier. - /// Task<UserDto>. - Task Get(string id); - /// - /// Gets all. - /// - /// Task<List<UserDto>>. - Task> GetAll(); - } -} diff --git a/Emby.ApiClient/Data/LocalAssetManager.cs b/Emby.ApiClient/Data/LocalAssetManager.cs deleted file mode 100644 index 9515f29..0000000 --- a/Emby.ApiClient/Data/LocalAssetManager.cs +++ /dev/null @@ -1,695 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Sync; -using MediaBrowser.Model.Users; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Emby.ApiClient.Cryptography; -using Emby.ApiClient.Model; - -namespace Emby.ApiClient.Data -{ - public class LocalAssetManager : ILocalAssetManager - { - private readonly IUserActionRepository _userActionRepository; - private readonly IItemRepository _itemRepository; - private readonly IFileRepository _fileRepository; - private readonly ICryptographyProvider _cryptographyProvider; - private readonly ILogger _logger; - private readonly IUserRepository _userRepository; - private readonly IImageRepository _imageRepository; - - public LocalAssetManager(IUserActionRepository userActionRepository, IItemRepository itemRepository, IFileRepository fileRepository, ICryptographyProvider cryptographyProvider, ILogger logger, IUserRepository userRepository, IImageRepository iImageRepository) - { - _userActionRepository = userActionRepository; - _itemRepository = itemRepository; - _fileRepository = fileRepository; - _cryptographyProvider = cryptographyProvider; - _logger = logger; - _userRepository = userRepository; - _imageRepository = iImageRepository; - } - - /// - /// Records the user action. - /// - /// The action. - /// Task. - public Task RecordUserAction(UserAction action) - { - action.Id = Guid.NewGuid().ToString("N"); - - return _userActionRepository.Create(action); - } - - /// - /// Deletes the specified action. - /// - /// The action. - /// Task. - public Task Delete(UserAction action) - { - return _userActionRepository.Delete(action); - } - - /// - /// Deletes the specified item. - /// - /// The item. - /// Task. - public Task Delete(LocalItem item) - { - return _itemRepository.Delete(item.Id); - } - - /// - /// Gets all user actions by serverId - /// - /// - /// - public Task> GetUserActions(string serverId) - { - return _userActionRepository.Get(serverId); - } - - /// - /// Adds the or update. - /// - /// The item. - /// Task. - public Task AddOrUpdate(LocalItem item) - { - return _itemRepository.AddOrUpdate(item); - } - - /// - /// Gets the files. - /// - /// The item. - /// Task<List<ItemFileInfo>>. - public async Task> GetFiles(LocalItem item) - { - var path = item.LocalPath; - path = _fileRepository.GetParentDirectoryPath(path); - - var list = await _fileRepository.GetFileSystemEntries(path).ConfigureAwait(false); - - var itemFiles = new List(); - - var name = Path.GetFileNameWithoutExtension(item.LocalPath); - - foreach (var file in list.Where(f => f.Name.Contains(name))) - { - var itemFile = new ItemFileInfo - { - Path = file.Path, - Name = file.Name - }; - - if (IsSubtitleFile(file.Name)) - { - itemFile.Type = ItemFileType.Subtitles; - } - else if (!IsImageFile(file.Name)) - { - itemFile.Type = ItemFileType.Media; - } - - itemFiles.Add(itemFile); - } - - return itemFiles; - } - - private static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".webp" }; - private bool IsImageFile(string path) - { - var ext = Path.GetExtension(path) ?? string.Empty; - - return SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); - } - - private static readonly string[] SupportedSubtitleExtensions = { ".srt", ".vtt" }; - private bool IsSubtitleFile(string path) - { - var ext = Path.GetExtension(path) ?? string.Empty; - - return SupportedSubtitleExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); - } - - /// - /// Deletes the specified file. - /// - /// The path. - /// Task. - public Task DeleteFile(string path) - { - return _fileRepository.DeleteFile(path); - } - - public async Task SaveSubtitles(Stream stream, - string format, - LocalItem item, - string language, - bool isForced) - { - var path = item.LocalPath; - - var filename = GetSubtitleSaveFileName(item, language, isForced) + "." + format.ToLower(); - - var parentPath = _fileRepository.GetParentDirectoryPath(path); - - path = Path.Combine(parentPath, filename); - - await _fileRepository.SaveFile(stream, path); - - return path; - } - - private string GetSubtitleSaveFileName(LocalItem item, string language, bool isForced) - { - var path = item.LocalPath; - - var name = Path.GetFileNameWithoutExtension(path); - - if (!string.IsNullOrWhiteSpace(language)) - { - name += "." + language.ToLower(); - } - - if (isForced) - { - name += ".foreign"; - } - - return name; - } - - public Task SaveMedia(Stream stream, LocalItem localItem, ServerInfo server) - { - _logger.Debug("Saving media to " + localItem.LocalPath); - return _fileRepository.SaveFile(stream, localItem.LocalPath); - } - -#if WINDOWS_UWP - public Task SaveMedia(Windows.Storage.IStorageFile file, LocalItem localItem, ServerInfo server) - { - _logger.Debug("Saving media to " + localItem.LocalPath); - return _fileRepository.SaveFile(file, localItem.LocalPath); - } -#endif - - private List GetDirectoryPath(BaseItemDto item, ServerInfo server) - { - var parts = new List - { - server.Name - }; - - if (item.IsType("episode")) - { - parts.Add("TV"); - parts.Add(item.SeriesName); - - if (!string.IsNullOrWhiteSpace(item.SeasonName)) - { - parts.Add(item.SeasonName); - } - } - else if (string.Equals(item.MediaType, "video", StringComparison.OrdinalIgnoreCase)) - { - parts.Add("Videos"); - parts.Add(item.Name); - } - else if (string.Equals(item.MediaType, "audio", StringComparison.OrdinalIgnoreCase)) - { - parts.Add("Music"); - - if (!string.IsNullOrWhiteSpace(item.AlbumArtist)) - { - parts.Add(item.AlbumArtist); - } - - if (!string.IsNullOrWhiteSpace(item.Album)) - { - parts.Add(item.Album); - } - } - else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) - { - parts.Add("Photos"); - - if (!string.IsNullOrWhiteSpace(item.Album)) - { - parts.Add(item.Album); - } - } - - return parts.Select(_fileRepository.GetValidFileName).ToList(); - } - - public LocalItem CreateLocalItem(BaseItemDto libraryItem, ServerInfo server, string syncJobItemId, string originalFileName) - { - var path = GetDirectoryPath(libraryItem, server); - path.Add(GetLocalFileName(libraryItem, originalFileName)); - - var localPath = _fileRepository.GetFullLocalPath(path); - - foreach (var mediaSource in libraryItem.MediaSources) - { - mediaSource.Path = localPath; - mediaSource.Protocol = MediaProtocol.File; - } - - return new LocalItem - { - Item = libraryItem, - ItemId = libraryItem.Id, - ServerId = server.Id, - LocalPath = localPath, - Id = GetLocalId(server.Id, libraryItem.Id), - SyncJobItemId = syncJobItemId - }; - } - - private string GetLocalFileName(BaseItemDto item, string originalFileName) - { - var filename = originalFileName; - - if (string.IsNullOrEmpty(filename)) - { - filename = item.Name; - } - - return _fileRepository.GetValidFileName(filename); - } - - private string GetLocalId(string serverId, string itemId) - { - var bytes = Encoding.UTF8.GetBytes(serverId + itemId); - bytes = _cryptographyProvider.CreateMD5(bytes); - return BitConverter.ToString(bytes, 0, bytes.Length).Replace("-", string.Empty); - } - - public Task GetLocalItem(string localId) - { - return _itemRepository.Get(localId); - } - - public Task GetLocalItem(string serverId, string itemId) - { - return GetLocalItem(GetLocalId(serverId, itemId)); - } - - public Task FileExists(string path) - { - return _fileRepository.FileExists(path); - } - - public Task> GetServerItemIds(string serverId) - { - return _itemRepository.GetServerItemIds(serverId); - } - - public Task GetFileStream(StreamInfo info) - { - return GetFileStream(info.ToUrl(null, null)); - } - - public Task GetFileStream(string path) - { - return _fileRepository.GetFileStream(path); - } - - public Task SaveOfflineUser(UserDto user) - { - return _userRepository.AddOrUpdate(user.Id, user); - } - - public Task DeleteOfflineUser(string id) - { - return _userRepository.Delete(id); - } - - public async Task SaveImage(UserDto user, Stream stream) - { - await DeleteImage(user).ConfigureAwait(false); - - await _imageRepository.SaveImage(GetImageRepositoryId(user.ServerId, user.Id), user.PrimaryImageTag, stream).ConfigureAwait(false); - } - - public Task GetImage(UserDto user) - { - return _imageRepository.GetImage(user.Id, user.PrimaryImageTag); - } - - public Task GetUser(string id) - { - return _userRepository.Get(id); - } - - public Task DeleteImage(UserDto user) - { - return _imageRepository.DeleteImages(GetImageRepositoryId(user.ServerId, user.Id)); - } - - public Task HasImage(UserDto user) - { - return _imageRepository.HasImage(GetImageRepositoryId(user.ServerId, user.Id), user.PrimaryImageTag); - } - - public Task SaveImage(string serverId, string itemId, string imageId, Stream stream) - { - return _imageRepository.SaveImage(GetImageRepositoryId(serverId, itemId), imageId, stream); - } - - public Task HasImage(string serverId, string itemId, string imageId) - { - return _imageRepository.HasImage(GetImageRepositoryId(serverId, itemId), imageId); - } - - public Task GetImage(string serverId, string itemId, string imageId) - { - return _imageRepository.GetImage(GetImageRepositoryId(serverId, itemId), imageId); - } - - private string GetImageRepositoryId(string serverId, string itemId) - { - return GetLocalId(serverId, itemId); - } - - public Task HasImage(BaseItemDto item, string imageId) - { - return _imageRepository.HasImage(GetImageRepositoryId(item.ServerId, item.Id), imageId); - } - - public Task GetImage(BaseItemDto item, string imageId) - { - return _imageRepository.GetImage(GetImageRepositoryId(item.ServerId, item.Id), imageId); - } - - public async Task> GetViews(string serverId, string userId) - { - var list = new List(); - - var types = await _itemRepository.GetItemTypes(serverId, userId).ConfigureAwait(false); - - if (types.Contains("Audio", StringComparer.OrdinalIgnoreCase)) - { - list.Add(new BaseItemDto - { - Name = "Music", - ServerId = serverId, - Id = "MusicView", - Type = "MusicView", - CollectionType = CollectionType.Music - }); - } - - if (types.Contains("Photo", StringComparer.OrdinalIgnoreCase)) - { - list.Add(new BaseItemDto - { - Name = "Photos", - ServerId = serverId, - Id = "PhotosView", - Type = "PhotosView", - CollectionType = CollectionType.Photos - }); - } - - if (types.Contains("Episode", StringComparer.OrdinalIgnoreCase)) - { - list.Add(new BaseItemDto - { - Name = "TV", - ServerId = serverId, - Id = "TVView", - Type = "TVView", - CollectionType = CollectionType.TvShows - }); - } - - if (types.Contains("Video", StringComparer.OrdinalIgnoreCase) || - types.Contains("Movie", StringComparer.OrdinalIgnoreCase) || - types.Contains("MusicVideo", StringComparer.OrdinalIgnoreCase)) - { - list.Add(new BaseItemDto - { - Name = "Videos", - ServerId = serverId, - Id = "VideosView", - Type = "VideosView", - CollectionType = CollectionType.HomeVideos - }); - } - - return list; - } - - public Task> GetItems(UserDto user, BaseItemDto parentItem) - { - if (string.Equals(parentItem.Type, "MusicView")) - { - return GetMusicArtists(user, parentItem); - } - if (string.Equals(parentItem.Type, "MusicArtist")) - { - return GetMusicAlbums(user, parentItem); - } - if (string.Equals(parentItem.Type, "MusicAlbum")) - { - return GetAlbumSongs(user, parentItem); - } - if (string.Equals(parentItem.Type, "PhotosView")) - { - return GetPhotoAlbums(user, parentItem); - } - if (string.Equals(parentItem.Type, "PhotoAlbum")) - { - return GetPhotos(user, parentItem); - } - if (string.Equals(parentItem.Type, "VideosView")) - { - return GetVideos(user, parentItem); - } - if (string.Equals(parentItem.Type, "TVView")) - { - return GetTvSeries(user, parentItem); - } - if (string.Equals(parentItem.Type, "Series")) - { - return GetTvEpisodes(user, parentItem); - } - - return Task.FromResult(new List()); - } - - private async Task> GetMusicArtists(UserDto user, BaseItemDto parentItem) - { - var artists = await _itemRepository.GetAlbumArtists(user.ServerId, user.Id).ConfigureAwait(false); - - return artists - .OrderBy(i => i.Name) - .Select(i => - { - var item = new BaseItemDto - { - Name = i.Name, - Id = i.Id, - Type = "MusicArtist", - ServerId = i.ServerId, - ImageTags = new Dictionary() - }; - - if (!string.IsNullOrWhiteSpace(i.PrimaryImageTag)) - { - item.ImageTags[ImageType.Primary] = i.PrimaryImageTag; - } - - return item; - }) - .ToList(); - } - - private async Task> GetMusicAlbums(UserDto user, BaseItemDto parentItem) - { - var items = await _itemRepository.GetItems(new LocalItemQuery - { - AlbumArtistId = parentItem.Id, - ServerId = user.ServerId, - Type = "Audio" - }); - - var dict = new Dictionary>(); - - foreach (var item in FilterByUserAccess(items, user)) - { - if (!string.IsNullOrWhiteSpace(item.Item.AlbumId)) - { - List subItems; - if (!dict.TryGetValue(item.Item.AlbumId, out subItems)) - { - subItems = new List(); - dict[item.Item.AlbumId] = subItems; - } - subItems.Add(item.Item); - } - } - - return dict - .OrderBy(i => i.Value[0].Album) - .Select(i => new BaseItemDto - { - Name = i.Value[0].Album, - Id = i.Key, - Type = "MusicAlbum", - ServerId = user.ServerId, - SongCount = i.Value.Count, - ChildCount = i.Value.Count, - AlbumPrimaryImageTag = i.Value[0].AlbumPrimaryImageTag, - Genres = i.Value.SelectMany(m => m.Genres).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(m => m).ToList(), - Artists = i.Value.SelectMany(m => m.Artists).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(m => m).ToArray() - }) - .ToList(); - } - - private async Task> GetAlbumSongs(UserDto user, BaseItemDto parentItem) - { - var items = await _itemRepository.GetItems(new LocalItemQuery - { - AlbumId = parentItem.Id, - ServerId = user.ServerId, - MediaType = "Audio" - }); - - return FilterByUserAccess(items, user) - .Select(i => i.Item) - .OrderBy(i => i.SortName) - .ToList(); - } - - private async Task> GetPhotoAlbums(UserDto user, BaseItemDto parentItem) - { - var albums = await _itemRepository.GetPhotoAlbums(user.ServerId, user.Id).ConfigureAwait(false); - - return albums - .OrderBy(i => i.Name) - .Select(i => - { - var item = new BaseItemDto - { - Name = i.Name, - Id = i.Id, - Type = "PhotoAlbum", - ServerId = i.ServerId, - ImageTags = new Dictionary() - }; - - if (!string.IsNullOrWhiteSpace(i.PrimaryImageTag)) - { - item.ImageTags[ImageType.Primary] = i.PrimaryImageTag; - } - - return item; - }) - .ToList(); - } - - private async Task> GetPhotos(UserDto user, BaseItemDto parentItem) - { - var items = await _itemRepository.GetItems(new LocalItemQuery - { - AlbumId = parentItem.Id, - ServerId = user.ServerId, - MediaType = "Photo" - }); - - return FilterByUserAccess(items, user) - .Select(i => i.Item) - .OrderBy(i => i.SortName) - .ToList(); - } - - private async Task> GetTvSeries(UserDto user, BaseItemDto parentItem) - { - var shows = await _itemRepository.GetTvSeries(user.ServerId, user.Id).ConfigureAwait(false); - - return shows - .OrderBy(i => i.Name) - .Select(i => - { - var item = new BaseItemDto - { - Name = i.Name, - Id = i.Id, - Type = "Series", - ServerId = i.ServerId, - ImageTags = new Dictionary() - }; - - if (!string.IsNullOrWhiteSpace(i.PrimaryImageTag)) - { - item.ImageTags[ImageType.Primary] = i.PrimaryImageTag; - } - - return item; - }) - .ToList(); - } - - private async Task> GetTvEpisodes(UserDto user, BaseItemDto parentItem) - { - var items = await _itemRepository.GetItems(new LocalItemQuery - { - SeriesId = parentItem.Id, - ServerId = user.ServerId, - MediaType = "Video", - Type = "Episode" - }); - - return FilterByUserAccess(items, user) - .Select(i => i.Item) - .OrderBy(i => i.SortName) - .ToList(); - } - - private async Task> GetVideos(UserDto user, BaseItemDto parentItem) - { - var items = await _itemRepository.GetItems(new LocalItemQuery - { - ServerId = user.ServerId, - MediaType = "Video", - ExcludeTypes = new[] { "Episode" } - }); - - return FilterByUserAccess(items, user) - .Select(i => i.Item) - .OrderBy(i => i.SortName) - .ToList(); - } - - private IEnumerable FilterByUserAccess(IEnumerable items, UserDto user) - { - return items.Where(i => - { - var result = i.UserIdsWithAccess.Contains(user.Id, StringComparer.OrdinalIgnoreCase); - - if (!result) - { - _logger.Debug("Offline item {0} is blocked from user {1}", i.Item.Name, user.Name); - } - - return result; - }); - } - } -} diff --git a/Emby.ApiClient/Device.cs b/Emby.ApiClient/Device.cs deleted file mode 100644 index aa4d7c7..0000000 --- a/Emby.ApiClient/Device.cs +++ /dev/null @@ -1,33 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Devices; -using Microsoft.Win32; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Emby.ApiClient.Model; - -namespace Emby.ApiClient -{ - public class Device : IDevice - { - public string DeviceName { get; set; } - public string DeviceId { get; set; } - - public virtual async Task> GetLocalPhotos() - { - return new List(); - } - - public virtual async Task> GetLocalVideos() - { - return new List(); - } - - public Task UploadFile(LocalFileInfo file, IApiClient apiClient, CancellationToken cancellationToken = default(CancellationToken)) - { - return apiClient.UploadFile(File.OpenRead(file.Id), file, cancellationToken); - } - } -} diff --git a/Emby.ApiClient/Emby.ApiClient.csproj b/Emby.ApiClient/Emby.ApiClient.csproj deleted file mode 100644 index 5cfe03f..0000000 --- a/Emby.ApiClient/Emby.ApiClient.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - netstandard2.0;net46 - 1.1.1.0 - 1.1.1.0 - true - MediaBrowser.ApiClient - 3.1.1 - Emby - Emby - Api libraries for connecting to Emby Server. - http://www.mb3admin.com/images/mb3icons1-1.png - https://github.com/MediaBrowser/Emby.ApiClient - https://github.com/MediaBrowser/Emby.ApiClient - - - - - - - - - diff --git a/Emby.ApiClient/Model/ConnectionMode.cs b/Emby.ApiClient/Model/ConnectionMode.cs deleted file mode 100644 index f2abdf9..0000000 --- a/Emby.ApiClient/Model/ConnectionMode.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Emby.ApiClient.Model -{ - public enum ConnectionMode - { - Local = 1, - Remote = 2, - Manual = 3 - } -} \ No newline at end of file diff --git a/Emby.ApiClient/Model/IDevice.cs b/Emby.ApiClient/Model/IDevice.cs deleted file mode 100644 index 59b82bd..0000000 --- a/Emby.ApiClient/Model/IDevice.cs +++ /dev/null @@ -1,40 +0,0 @@ -using MediaBrowser.Model.Devices; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Emby.ApiClient.Model -{ - public interface IDevice - { - /// - /// Gets the name of the device. - /// - /// The name of the device. - string DeviceName { get; } - /// - /// Gets the device identifier. - /// - /// The device identifier. - string DeviceId { get; } - /// - /// Gets the local images. - /// - /// IEnumerable<LocalFileInfo>. - Task> GetLocalPhotos(); - /// - /// Gets the local videos. - /// - /// IEnumerable<LocalFileInfo>. - Task> GetLocalVideos(); - /// - /// Uploads the file. - /// - /// The file. - /// The API client. - /// The cancellation token. - /// Task. - Task UploadFile(LocalFileInfo file, IApiClient apiClient, CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/Emby.ApiClient/Model/ServerInfo.cs b/Emby.ApiClient/Model/ServerInfo.cs deleted file mode 100644 index 60c4a47..0000000 --- a/Emby.ApiClient/Model/ServerInfo.cs +++ /dev/null @@ -1,77 +0,0 @@ -using MediaBrowser.Model.Connect; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.System; -using System; -using System.Collections.Generic; -using MediaBrowser.Model.ApiClient; - -namespace Emby.ApiClient.Model -{ - public class ServerInfo - { - public String Name { get; set; } - public String Id { get; set; } - public String ConnectServerId { get; set; } - public String LocalAddress { get; set; } - public String RemoteAddress { get; set; } - public String ManualAddress { get; set; } - public String UserId { get; set; } - public String AccessToken { get; set; } - public List WakeOnLanInfos { get; set; } - public DateTime DateLastAccessed { get; set; } - public String ExchangeToken { get; set; } - public UserLinkType? UserLinkType { get; set; } - public ConnectionMode? LastConnectionMode { get; set; } - - public ServerInfo() - { - WakeOnLanInfos = new List(); - } - - public void ImportInfo(PublicSystemInfo systemInfo) - { - Name = systemInfo.ServerName; - Id = systemInfo.Id; - - if (!string.IsNullOrEmpty(systemInfo.LocalAddress)) - { - LocalAddress = systemInfo.LocalAddress; - } - - if (!string.IsNullOrEmpty(systemInfo.WanAddress)) - { - RemoteAddress = systemInfo.WanAddress; - } - - var fullSystemInfo = systemInfo as SystemInfo; - - if (fullSystemInfo != null) - { - WakeOnLanInfos = new List(); - - if (!string.IsNullOrEmpty(fullSystemInfo.MacAddress)) - { - WakeOnLanInfos.Add(new WakeOnLanInfo - { - MacAddress = fullSystemInfo.MacAddress - }); - } - } - } - - public string GetAddress(ConnectionMode mode) - { - switch (mode) - { - case ConnectionMode.Local: - return LocalAddress; - case ConnectionMode.Manual: - return ManualAddress; - case ConnectionMode.Remote: - return RemoteAddress; - default: - throw new ArgumentException("Unexpected ConnectionMode"); - } - } - } -} diff --git a/Emby.ApiClient/Net/INetworkConnection.cs b/Emby.ApiClient/Net/INetworkConnection.cs deleted file mode 100644 index fda6e3e..0000000 --- a/Emby.ApiClient/Net/INetworkConnection.cs +++ /dev/null @@ -1,45 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using System; -using System.Threading; -using System.Threading.Tasks; -using Emby.ApiClient.Model; - -namespace Emby.ApiClient.Net -{ - public interface INetworkConnection - { - /// - /// Occurs when [network changed]. - /// - event EventHandler NetworkChanged; - - /// - /// Sends the wake on lan. - /// - /// The mac address. - /// The ip address. - /// The port. - /// The cancellation token. - /// Task. - Task SendWakeOnLan(string macAddress, string ipAddress, int port, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Sends the wake on lan. - /// - /// The mac address. - /// The port. - /// The cancellation token. - /// Task. - Task SendWakeOnLan(string macAddress, int port, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Gets the network status. - /// - /// NetworkStatus. - NetworkStatus GetNetworkStatus(); - -#if WINDOWS_UWP - bool HasUnmeteredConnection(); -#endif - } -} diff --git a/Emby.ApiClient/Sync/ContentUploader.cs b/Emby.ApiClient/Sync/ContentUploader.cs deleted file mode 100644 index 291bc62..0000000 --- a/Emby.ApiClient/Sync/ContentUploader.cs +++ /dev/null @@ -1,66 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Emby.ApiClient.Model; - -namespace Emby.ApiClient.Sync -{ - public class ContentUploader - { - private readonly IApiClient _apiClient; - private readonly ILogger _logger; - - public ContentUploader(IApiClient apiClient, ILogger logger) - { - _apiClient = apiClient; - _logger = logger; - } - - public async Task UploadImages(IProgress progress, CancellationToken cancellationToken = default(CancellationToken)) - { - var device = _apiClient.Device; - - var deviceId = device.DeviceId; - - var history = await _apiClient.GetContentUploadHistory(deviceId).ConfigureAwait(false); - - var files = (await device.GetLocalPhotos().ConfigureAwait(false)) - .ToList(); - - files.AddRange((await device.GetLocalVideos().ConfigureAwait(false))); - - files = files - .Where(i => !history.FilesUploaded.Any(f => string.Equals(f.Id, i.Id, StringComparison.OrdinalIgnoreCase))) - .ToList(); - - var numComplete = 0; - - foreach (var file in files) - { - cancellationToken.ThrowIfCancellationRequested(); - - _logger.Debug("Uploading {0}", file.Id); - - try - { - await device.UploadFile(file, _apiClient, cancellationToken); - } - catch (Exception ex) - { - _logger.ErrorException("Error uploading file", ex); - } - - numComplete++; - double percent = numComplete; - percent /= files.Count; - - progress.Report(100 * percent); - } - - progress.Report(100); - } - } -} diff --git a/Emby.ApiClient/Sync/DoubleProgress.cs b/Emby.ApiClient/Sync/DoubleProgress.cs deleted file mode 100644 index 8dae0a5..0000000 --- a/Emby.ApiClient/Sync/DoubleProgress.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Emby.ApiClient.Sync -{ - public class DoubleProgress : Progress, IDisposable - { - /// - /// The _actions - /// - private readonly List> _actions = new List>(); - - /// - /// Registers the action. - /// - /// The action. - public void RegisterAction(Action action) - { - _actions.Add(action); - - ProgressChanged -= ActionableProgress_ProgressChanged; - ProgressChanged += ActionableProgress_ProgressChanged; - } - - /// - /// Actionables the progress_ progress changed. - /// - /// The sender. - /// The e. - void ActionableProgress_ProgressChanged(object sender, double e) - { - foreach (var action in _actions) - { - action(e); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - ProgressChanged -= ActionableProgress_ProgressChanged; - _actions.Clear(); - } - } - } -} diff --git a/Emby.ApiClient/Sync/FileTransferManager.cs b/Emby.ApiClient/Sync/FileTransferManager.cs deleted file mode 100644 index 456da8c..0000000 --- a/Emby.ApiClient/Sync/FileTransferManager.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Sync; -using System; -using System.Threading; -using System.Threading.Tasks; -using Emby.ApiClient.Data; -using Emby.ApiClient.Model; - -namespace Emby.ApiClient.Sync -{ - class FileTransferManager : IFileTransferManager - { - private readonly ILocalAssetManager _localAssetManager; - private readonly ILogger _logger; - - internal FileTransferManager(ILocalAssetManager localAssetManager, ILogger logger) - { - _localAssetManager = localAssetManager; - _logger = logger; - } - - public async Task GetItemFileAsync(IApiClient apiClient, - ServerInfo server, - LocalItem item, - string syncJobItemId, - IProgress transferProgress, - CancellationToken cancellationToken = default(CancellationToken)) - { - _logger.Debug("Downloading media with Id {0} to local repository", item.Item.Id); - - using (var stream = await apiClient.GetSyncJobItemFile(syncJobItemId, cancellationToken).ConfigureAwait(false)) - { - await _localAssetManager.SaveMedia(stream, item, server).ConfigureAwait(false); - } - transferProgress.Report(100); - } - } -} diff --git a/Emby.ApiClient/Sync/IFileTransferManager.cs b/Emby.ApiClient/Sync/IFileTransferManager.cs deleted file mode 100644 index 1f8072d..0000000 --- a/Emby.ApiClient/Sync/IFileTransferManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Threading; -using Emby.ApiClient.Model; -using MediaBrowser.Model.Sync; - -namespace Emby.ApiClient.Sync -{ - public interface IFileTransferManager - { - System.Threading.Tasks.Task GetItemFileAsync(IApiClient apiClient, ServerInfo server, LocalItem item, string syncJobItemId, IProgress transferProgress, System.Threading.CancellationToken cancellationToken = default(CancellationToken)); - } -} diff --git a/Emby.ApiClient/Sync/IMediaSync.cs b/Emby.ApiClient/Sync/IMediaSync.cs deleted file mode 100644 index 11b475a..0000000 --- a/Emby.ApiClient/Sync/IMediaSync.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Emby.ApiClient.Model; -using MediaBrowser.Model.ApiClient; - -namespace Emby.ApiClient.Sync -{ - public interface IMediaSync - { - Task Sync(IApiClient apiClient, - ServerInfo serverInfo, - IProgress progress, - CancellationToken cancellationToken = default(CancellationToken)); - } -} \ No newline at end of file diff --git a/Emby.ApiClient/Sync/IMultiServerSync.cs b/Emby.ApiClient/Sync/IMultiServerSync.cs deleted file mode 100644 index a0f5acf..0000000 --- a/Emby.ApiClient/Sync/IMultiServerSync.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace Emby.ApiClient.Sync -{ - public interface IMultiServerSync - { - Task Sync(IProgress progress, - List cameraUploadServers, - bool syncOnlyOnLocalNetwork, - CancellationToken cancellationToken = default(CancellationToken)); - } -} \ No newline at end of file diff --git a/Emby.ApiClient/Sync/IServerSync.cs b/Emby.ApiClient/Sync/IServerSync.cs deleted file mode 100644 index b59cd04..0000000 --- a/Emby.ApiClient/Sync/IServerSync.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Emby.ApiClient.Model; -using MediaBrowser.Model.ApiClient; - -namespace Emby.ApiClient.Sync -{ - public interface IServerSync - { - Task Sync(ServerInfo server, bool enableCameraUpload, IProgress progress, CancellationToken cancellationToken = default(CancellationToken)); - } -} \ No newline at end of file diff --git a/Emby.ApiClient/Sync/MediaSync.cs b/Emby.ApiClient/Sync/MediaSync.cs deleted file mode 100644 index 5917c7e..0000000 --- a/Emby.ApiClient/Sync/MediaSync.cs +++ /dev/null @@ -1,329 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Sync; -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Emby.ApiClient.Data; -using Emby.ApiClient.Model; - -namespace Emby.ApiClient.Sync -{ - public class MediaSync : IMediaSync - { - private readonly IFileTransferManager _fileTransferManager; - private readonly ILocalAssetManager _localAssetManager; - private readonly ILogger _logger; - - public MediaSync(ILocalAssetManager localAssetManager, ILogger logger, IFileTransferManager fileTransferManager) - { - _localAssetManager = localAssetManager; - _logger = logger; - _fileTransferManager = fileTransferManager; - } - - public async Task Sync(IApiClient apiClient, - ServerInfo serverInfo, - IProgress progress, - CancellationToken cancellationToken = default(CancellationToken)) - { - _logger.Debug("Beginning media sync process with server Id: {0}", serverInfo.Id); - - // First report actions to the server that occurred while offline - await ReportOfflineActions(apiClient, serverInfo, cancellationToken).ConfigureAwait(false); - progress.Report(1); - - await SyncData(apiClient, serverInfo, cancellationToken).ConfigureAwait(false); - progress.Report(3); - - var innerProgress = new DoubleProgress(); - innerProgress.RegisterAction(pct => - { - var totalProgress = pct * .97; - totalProgress += 1; - progress.Report(totalProgress); - }); - - await GetNewMedia(apiClient, serverInfo, innerProgress, cancellationToken); - progress.Report(100); - - // Do the data sync twice so the server knows what was removed from the device - await SyncData(apiClient, serverInfo, cancellationToken).ConfigureAwait(false); - } - - private async Task GetNewMedia(IApiClient apiClient, - ServerInfo server, - IProgress progress, - CancellationToken cancellationToken) - { - var jobItems = await apiClient.GetReadySyncItems(apiClient.DeviceId).ConfigureAwait(false); - - var numComplete = 0; - double startingPercent = 0; - double percentPerItem = 1; - if (jobItems.Count > 0) - { - percentPerItem /= jobItems.Count; - } - - foreach (var jobItem in jobItems) - { - cancellationToken.ThrowIfCancellationRequested(); - - var currentPercent = startingPercent; - var innerProgress = new DoubleProgress(); - innerProgress.RegisterAction(pct => - { - var totalProgress = pct * percentPerItem; - totalProgress += currentPercent; - progress.Report(totalProgress); - }); - - try - { - await GetItem(apiClient, server, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error syncing new media", ex); - } - - numComplete++; - startingPercent = numComplete; - startingPercent /= jobItems.Count; - startingPercent *= 100; - progress.Report(startingPercent); - } - } - - private async Task GetItem(IApiClient apiClient, - ServerInfo server, - SyncedItem jobItem, - IProgress progress, - CancellationToken cancellationToken) - { - var libraryItem = jobItem.Item; - - var localItem = _localAssetManager.CreateLocalItem(libraryItem, server, jobItem.SyncJobItemId, jobItem.OriginalFileName); - - // Create db record - await _localAssetManager.AddOrUpdate(localItem).ConfigureAwait(false); - - var fileTransferProgress = new DoubleProgress(); - fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92)); - - // Download item file - await _fileTransferManager.GetItemFileAsync(apiClient, server, localItem, jobItem.SyncJobItemId, fileTransferProgress, cancellationToken).ConfigureAwait(false); - progress.Report(92); - - // Download images - await GetItemImages(apiClient, localItem, cancellationToken).ConfigureAwait(false); - progress.Report(95); - - // Download subtitles - await GetItemSubtitles(apiClient, jobItem, localItem, cancellationToken).ConfigureAwait(false); - progress.Report(99); - - // Let the server know it was successfully downloaded - await apiClient.ReportSyncJobItemTransferred(jobItem.SyncJobItemId).ConfigureAwait(false); - progress.Report(100); - } - - public bool HasPrimaryImage(BaseItemDto item) - { - return item.ImageTags != null && item.ImageTags.ContainsKey(ImageType.Primary); - } - - private async Task GetItemImages(IApiClient apiClient, - LocalItem item, - CancellationToken cancellationToken) - { - var libraryItem = item.Item; - - if (HasPrimaryImage(libraryItem)) - { - await DownloadImage(apiClient, item.ServerId, libraryItem.Id, libraryItem.ImageTags[ImageType.Primary], ImageType.Primary, cancellationToken) - .ConfigureAwait(false); - } - - // Container images - - // Series Primary - if (!string.IsNullOrWhiteSpace(libraryItem.SeriesPrimaryImageTag)) - { - await DownloadImage(apiClient, item.ServerId, libraryItem.SeriesId, libraryItem.SeriesPrimaryImageTag, ImageType.Primary, cancellationToken) - .ConfigureAwait(false); - } - - // Series Thumb - if (!string.IsNullOrWhiteSpace(libraryItem.SeriesThumbImageTag)) - { - await DownloadImage(apiClient, item.ServerId, libraryItem.SeriesId, libraryItem.SeriesThumbImageTag, ImageType.Thumb, cancellationToken) - .ConfigureAwait(false); - } - - // Album Primary - if (!string.IsNullOrWhiteSpace(libraryItem.AlbumPrimaryImageTag)) - { - await DownloadImage(apiClient, item.ServerId, libraryItem.AlbumId, libraryItem.AlbumPrimaryImageTag, ImageType.Primary, cancellationToken) - .ConfigureAwait(false); - } - } - - private async Task DownloadImage(IApiClient apiClient, - string serverId, - string itemId, - string imageTag, - ImageType imageType, - CancellationToken cancellationToken) - { - var hasImage = await _localAssetManager.HasImage(serverId, itemId, imageTag).ConfigureAwait(false); - - if (hasImage) - { - return; - } - - var url = apiClient.GetImageUrl(itemId, new ImageOptions - { - ImageType = imageType, - Tag = imageTag - }); - - using (var response = await apiClient.GetResponse(url, cancellationToken).ConfigureAwait(false)) - { - await _localAssetManager.SaveImage(serverId, itemId, imageTag, response.Content).ConfigureAwait(false); - } - } - - private async Task GetItemSubtitles(IApiClient apiClient, - SyncedItem jobItem, - LocalItem item, - CancellationToken cancellationToken) - { - var hasDownloads = false; - - var mediaSource = jobItem.Item.MediaSources.FirstOrDefault(); - - if (mediaSource == null) - { - _logger.Error("Cannot download subtitles because video has no media source info."); - return; - } - - foreach (var file in jobItem.AdditionalFiles.Where(i => i.Type == ItemFileType.Subtitles)) - { - var subtitleStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Subtitle && i.Index == file.Index); - - if (subtitleStream != null) - { - using (var response = await apiClient.GetSyncJobItemAdditionalFile(jobItem.SyncJobItemId, file.Name, cancellationToken).ConfigureAwait(false)) - { - var path = await _localAssetManager.SaveSubtitles(response, subtitleStream.Codec, item, subtitleStream.Language, subtitleStream.IsForced).ConfigureAwait(false); - - subtitleStream.Path = path; - - var files = item.AdditionalFiles.ToList(); - files.Add(path); - item.AdditionalFiles = files.ToArray(); - } - - hasDownloads = true; - } - else - { - _logger.Error("Cannot download subtitles because matching stream info wasn't found."); - } - } - - // Save the changes to the item - if (hasDownloads) - { - await _localAssetManager.AddOrUpdate(item).ConfigureAwait(false); - } - } - - private async Task SyncData(IApiClient apiClient, - ServerInfo serverInfo, - CancellationToken cancellationToken) - { - _logger.Debug("Beginning SyncData with server {0}", serverInfo.Name); - - var localIds = await _localAssetManager.GetServerItemIds(serverInfo.Id).ConfigureAwait(false); - - var result = await apiClient.SyncData(new SyncDataRequest - { - TargetId = apiClient.DeviceId, - LocalItemIds = localIds.ToArray() - - }).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - foreach (var itemIdToRemove in result.ItemIdsToRemove) - { - try - { - await RemoveItem(serverInfo.Id, itemIdToRemove).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting item from device. Id: {0}", ex, itemIdToRemove); - } - } - } - - private async Task ReportOfflineActions(IApiClient apiClient, - ServerInfo serverInfo, - CancellationToken cancellationToken) - { - var actions = await _localAssetManager.GetUserActions(serverInfo.Id).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - var actionList = actions - .OrderBy(i => i.Date) - .ToList(); - - _logger.Debug("Reporting {0} offline actions to server {1}", - actionList.Count, - serverInfo.Id); - - if (actionList.Count > 0) - { - await apiClient.ReportOfflineActions(actionList).ConfigureAwait(false); - } - - foreach (var action in actionList) - { - await _localAssetManager.Delete(action).ConfigureAwait(false); - } - } - - private async Task RemoveItem(string serverId, string itemId) - { - _logger.Debug("Removing item. ServerId: {0}, ItemId: {1}", serverId, itemId); - var localItem = await _localAssetManager.GetLocalItem(serverId, itemId); - - if (localItem == null) - { - return; - } - - var additionalFiles = localItem.AdditionalFiles.ToList(); - var localPath = localItem.LocalPath; - - await _localAssetManager.Delete(localItem).ConfigureAwait(false); - - foreach (var file in additionalFiles) - { - await _localAssetManager.DeleteFile(file).ConfigureAwait(false); - } - - await _localAssetManager.DeleteFile(localPath).ConfigureAwait(false); - } - } -} diff --git a/Emby.ApiClient/Sync/MultiServerSync.cs b/Emby.ApiClient/Sync/MultiServerSync.cs deleted file mode 100644 index 00f446c..0000000 --- a/Emby.ApiClient/Sync/MultiServerSync.cs +++ /dev/null @@ -1,108 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using System.Linq; -using Emby.ApiClient.Data; -using Emby.ApiClient.Model; - -namespace Emby.ApiClient.Sync -{ - public class MultiServerSync : IMultiServerSync - { - private readonly IConnectionManager _connectionManager; - private readonly ILogger _logger; - private readonly ILocalAssetManager _localAssetManager; - private readonly IFileTransferManager _fileTransferManager; - - public MultiServerSync(IConnectionManager connectionManager, ILogger logger, ILocalAssetManager userActionAssetManager, IFileTransferManager fileTransferManager) - { - _connectionManager = connectionManager; - _logger = logger; - _localAssetManager = userActionAssetManager; - _fileTransferManager = fileTransferManager; - } - - public async Task Sync(IProgress progress, - List cameraUploadServers, - bool syncOnlyOnLocalNetwork, - CancellationToken cancellationToken = default(CancellationToken)) - { - var servers = await _connectionManager.GetAvailableServers(cancellationToken).ConfigureAwait(false); - - await Sync(servers, cameraUploadServers, syncOnlyOnLocalNetwork, progress, cancellationToken).ConfigureAwait(false); - } - - private async Task Sync(List servers, List cameraUploadServers, bool syncOnlyOnLocalNetwork, IProgress progress, CancellationToken cancellationToken = default(CancellationToken)) - { - var numComplete = 0; - double startingPercent = 0; - double percentPerServer = 1; - if (servers.Count > 0) - { - percentPerServer /= servers.Count; - } - - _logger.Debug("Beginning MultiServerSync with {0} servers", servers.Count); - - foreach (var server in servers) - { - cancellationToken.ThrowIfCancellationRequested(); - - var currentPercent = startingPercent; - var serverProgress = new DoubleProgress(); - serverProgress.RegisterAction(pct => - { - var totalProgress = pct * percentPerServer; - totalProgress += currentPercent; - progress.Report(totalProgress); - }); - - // Grab the latest info from the connection manager about that server - var serverInfo = await _connectionManager.GetServerInfo(server.Id).ConfigureAwait(false); - - if (serverInfo == null) - { - serverInfo = server; - } - - if (syncOnlyOnLocalNetwork) - { - var result = await _connectionManager.Connect(server, new ConnectionOptions - { - EnableWebSocket = false, - ReportCapabilities = false, - UpdateDateLastAccessed = false - - }, cancellationToken).ConfigureAwait(false); - - var apiClient = result.ApiClient; - - var endpointInfo = await apiClient.GetEndPointInfo(cancellationToken).ConfigureAwait(false); - - _logger.Debug("Server: {0}, Id: {1}, IsInNetwork:{2}", server.Name, server.Id, endpointInfo.IsInNetwork); - - if (!endpointInfo.IsInNetwork) - { - continue; - } - } - - var enableCameraUpload = cameraUploadServers.Contains(serverInfo.Id, StringComparer.OrdinalIgnoreCase); - - await new ServerSync(_connectionManager, _logger, _localAssetManager, _fileTransferManager, _connectionManager.ClientCapabilities) - .Sync(serverInfo, enableCameraUpload, serverProgress, cancellationToken).ConfigureAwait(false); - - numComplete++; - startingPercent = numComplete; - startingPercent /= servers.Count; - startingPercent *= 100; - progress.Report(startingPercent); - } - - progress.Report(100); - } - } -} diff --git a/Emby.ApiClient/Sync/ServerSync.cs b/Emby.ApiClient/Sync/ServerSync.cs deleted file mode 100644 index 57f1a80..0000000 --- a/Emby.ApiClient/Sync/ServerSync.cs +++ /dev/null @@ -1,115 +0,0 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Session; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Emby.ApiClient.Data; -using Emby.ApiClient.Model; - -namespace Emby.ApiClient.Sync -{ - public class ServerSync : IServerSync - { - private readonly IConnectionManager _connectionManager; - private readonly IFileTransferManager _fileTransferManager; - private readonly ILogger _logger; - private readonly ILocalAssetManager _localAssetManager; - - private readonly ClientCapabilities _clientCapabilities; - private static readonly Dictionary SemaphoreLocks = new Dictionary(StringComparer.OrdinalIgnoreCase); - - public ServerSync(IConnectionManager connectionManager, ILogger logger, ILocalAssetManager localAssetManager, IFileTransferManager fileTransferManager, ClientCapabilities clientCapabilities) - { - _connectionManager = connectionManager; - _fileTransferManager = fileTransferManager; - _clientCapabilities = clientCapabilities; - _logger = logger; - _localAssetManager = localAssetManager; - } - - public async Task Sync(ServerInfo server, bool enableCameraUpload, IProgress progress, CancellationToken cancellationToken = default(CancellationToken)) - { - var semaphore = GetLock(server.Id); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - await SyncInternal(server, enableCameraUpload, progress, cancellationToken).ConfigureAwait(false); - } - finally - { - semaphore.Release(); - } - } - - private static SemaphoreSlim GetLock(string serverId) - { - SemaphoreSlim semaphore; - - if (SemaphoreLocks.TryGetValue(serverId, out semaphore)) - { - return semaphore; - } - - semaphore = new SemaphoreSlim(1, 1); - SemaphoreLocks[serverId] = semaphore; - - return semaphore; - } - - private async Task SyncInternal(ServerInfo server, bool enableCameraUpload, IProgress progress, CancellationToken cancellationToken) - { - _logger.Debug("Beginning ServerSync with server {0}, Id {1}", server.Name, server.Id); - - if (string.IsNullOrWhiteSpace(server.AccessToken) && string.IsNullOrWhiteSpace(server.ExchangeToken)) - { - _logger.Info("Skipping sync process for server " + server.Name + ". No server authentication information available."); - progress.Report(100); - return; - } - - // Don't need these here - var result = await _connectionManager.Connect(server, new ConnectionOptions - { - EnableWebSocket = false, - ReportCapabilities = false, - UpdateDateLastAccessed = false - - }, cancellationToken).ConfigureAwait(false); - - if (result.State == ConnectionState.SignedIn) - { - await SyncInternal(server, result.ApiClient, enableCameraUpload, progress, cancellationToken).ConfigureAwait(false); - progress.Report(100); - } - else - { - _logger.Info("Skipping sync process for server " + server.Name + ". ConnectionManager returned a state of {0}", result.State.ToString()); - progress.Report(100); - } - } - - private async Task SyncInternal(ServerInfo server, IApiClient apiClient, bool enableCameraUpload, IProgress progress, CancellationToken cancellationToken) - { - const double cameraUploadTotalPercentage = .25; - - var uploadProgress = new DoubleProgress(); - uploadProgress.RegisterAction(p => progress.Report(p * cameraUploadTotalPercentage)); - - if (enableCameraUpload) - { - await new ContentUploader(apiClient, _logger) - .UploadImages(uploadProgress, cancellationToken).ConfigureAwait(false); - } - - var syncProgress = new DoubleProgress(); - syncProgress.RegisterAction(p => progress.Report((cameraUploadTotalPercentage * 100) + (p * (1 - cameraUploadTotalPercentage)))); - - await new MediaSync(_localAssetManager, _logger, _fileTransferManager) - .Sync(apiClient, server, uploadProgress, cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/Emby.ApiClient.sln b/Jellyfin.ApiClient.sln similarity index 85% rename from Emby.ApiClient.sln rename to Jellyfin.ApiClient.sln index e985465..d25cb44 100644 --- a/Emby.ApiClient.sln +++ b/Jellyfin.ApiClient.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.ApiClient", "Emby.ApiClient\Emby.ApiClient.csproj", "{78198B43-83EF-4B5B-ADBC-443E54FF7E8E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.ApiClient", "Jellyfin.ApiClient\Jellyfin.ApiClient.csproj", "{78198B43-83EF-4B5B-ADBC-443E54FF7E8E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Emby.ApiClient/ApiClient.cs b/Jellyfin.ApiClient/ApiClient.cs similarity index 71% rename from Emby.ApiClient/ApiClient.cs rename to Jellyfin.ApiClient/ApiClient.cs index 4911df1..3f4a87d 100644 --- a/Emby.ApiClient/ApiClient.cs +++ b/Jellyfin.ApiClient/ApiClient.cs @@ -1,4 +1,9 @@ -using MediaBrowser.Model.ApiClient; +using Jellyfin.ApiClient.Model; +using Jellyfin.ApiClient.Model.Dto; +using Jellyfin.ApiClient.Model.Notifications; +using Jellyfin.ApiClient.Model.Querying; +using Jellyfin.ApiClient.Net; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Devices; @@ -7,34 +12,28 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.LiveTv; -using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; -using MediaBrowser.Model.Notifications; using MediaBrowser.Model.Playlists; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Sync; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Linq; using System.Net; -using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.ApiClient.Cryptography; -using Emby.ApiClient.Data; -using Emby.ApiClient.Model; -using Emby.ApiClient.Net; +using System.Web; -namespace Emby.ApiClient +namespace Jellyfin.ApiClient { /// /// Provides api methods centered around an HttpClient @@ -42,7 +41,7 @@ namespace Emby.ApiClient public partial class ApiClient : BaseApiClient, IApiClient { public event EventHandler> RemoteLoggedOut; - public event EventHandler> Authenticated; + public event EventHandler> OnAuthenticated; /// /// Gets the HTTP client. @@ -50,24 +49,18 @@ public partial class ApiClient : BaseApiClient, IApiClient /// The HTTP client. protected IAsyncHttpClient HttpClient { get; private set; } - private readonly ICryptographyProvider _cryptographyProvider; - private readonly ILocalAssetManager _localAssetManager = null; - /// /// Initializes a new instance of the class. /// /// The logger. /// The server address. /// The access token. - /// The cryptography provider. public ApiClient(ILogger logger, - string serverAddress, - string accessToken, - ICryptographyProvider cryptographyProvider) + Uri serverAddress, + string accessToken) : base(logger, new NewtonsoftJsonSerializer(), serverAddress, accessToken) { CreateHttpClient(logger); - _cryptographyProvider = cryptographyProvider; ResetHttpHeaders(); } @@ -80,70 +73,40 @@ public ApiClient(ILogger logger, /// Name of the client. /// The device. /// The application version. - /// The cryptography provider. public ApiClient(ILogger logger, - string serverAddress, + Uri serverAddress, string clientName, IDevice device, - string applicationVersion, - ICryptographyProvider cryptographyProvider) + string applicationVersion) : base(logger, new NewtonsoftJsonSerializer(), serverAddress, clientName, device, applicationVersion) { CreateHttpClient(logger); - _cryptographyProvider = cryptographyProvider; ResetHttpHeaders(); } - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The server address. - /// Name of the client. - /// The device. - /// The application version. - /// The cryptography provider. - /// The local asset manager. - public ApiClient(ILogger logger, - string serverAddress, - string clientName, - IDevice device, - string applicationVersion, - ICryptographyProvider cryptographyProvider, - ILocalAssetManager localAssetManager) - : this(logger, serverAddress, clientName, device, applicationVersion, cryptographyProvider) - { - _localAssetManager = localAssetManager; - } - private void CreateHttpClient(ILogger logger) { HttpClient = AsyncHttpClientFactory.Create(logger); HttpClient.HttpResponseReceived += HttpClient_HttpResponseReceived; } - void HttpClient_HttpResponseReceived(object sender, HttpResponseEventArgs e) + void HttpClient_HttpResponseReceived(object sender, HttpWebResponse e) { if (e.StatusCode == HttpStatusCode.Unauthorized) { - if (RemoteLoggedOut != null) - { - RemoteLoggedOut(this, new GenericEventArgs()); - } + RemoteLoggedOut?.Invoke(this, new GenericEventArgs()); } } - private ConnectionMode ConnectionMode { get; set; } internal ServerInfo ServerInfo { get; set; } private INetworkConnection NetworkConnection { get; set; } - public void EnableAutomaticNetworking(ServerInfo info, ConnectionMode initialMode, INetworkConnection networkConnection) + public void EnableAutomaticNetworking(ServerInfo info, INetworkConnection networkConnection) { NetworkConnection = networkConnection; - ConnectionMode = initialMode; ServerInfo = info; - ServerAddress = info.GetAddress(initialMode); + ServerAddress = info.Address; } private async Task SendAsync(HttpRequest request, bool enableFailover = true) @@ -154,7 +117,6 @@ private async Task SendAsync(HttpRequest request, bool enableFailover = return await HttpClient.SendAsync(request).ConfigureAwait(false); } - var initialConnectionMode = ConnectionMode; var originalRequestTime = DateTime.UtcNow; Exception timeoutException; @@ -174,7 +136,7 @@ private async Task SendAsync(HttpRequest request, bool enableFailover = try { - await ValidateConnection(originalRequestTime, initialConnectionMode, request.CancellationToken).ConfigureAwait(false); + await ValidateConnection(originalRequestTime, request.CancellationToken).ConfigureAwait(false); } catch { @@ -183,7 +145,7 @@ private async Task SendAsync(HttpRequest request, bool enableFailover = throw timeoutException; } - request.Url = ReplaceServerAddress(request.Url, initialConnectionMode); + request.Url = ReplaceServerAddress(request.Url); return await HttpClient.SendAsync(request).ConfigureAwait(false); } @@ -192,7 +154,7 @@ private async Task SendAsync(HttpRequest request, bool enableFailover = private DateTime _lastConnectionValidationTime = DateTime.MinValue; - private async Task ValidateConnection(DateTime originalRequestTime, ConnectionMode initialConnectionMode, CancellationToken cancellationToken) + private async Task ValidateConnection(DateTime originalRequestTime, CancellationToken cancellationToken) { await _validateConnectionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -200,7 +162,7 @@ private async Task ValidateConnection(DateTime originalRequestTime, ConnectionMo { if (originalRequestTime > _lastConnectionValidationTime) { - await ValidateConnectionInternal(initialConnectionMode, cancellationToken).ConfigureAwait(false); + await ValidateConnectionInternal(cancellationToken).ConfigureAwait(false); } } finally @@ -209,9 +171,9 @@ private async Task ValidateConnection(DateTime originalRequestTime, ConnectionMo } } - private async Task ValidateConnectionInternal(ConnectionMode initialConnectionMode, CancellationToken cancellationToken) + private async Task ValidateConnectionInternal(CancellationToken cancellationToken) { - Logger.Debug("Connection to server dropped. Attempting to reconnect."); + Logger.LogDebug("Connection to server dropped. Attempting to reconnect."); const int maxWaitMs = 10000; const int waitIntervalMs = 100; @@ -231,10 +193,9 @@ private async Task ValidateConnectionInternal(ConnectionMode initialConnectionMo networkStatus = NetworkConnection.GetNetworkStatus(); } - var urlList = new List> + var urlList = new List { - new Tuple(ServerInfo.LocalAddress, ConnectionMode.Local), - new Tuple(ServerInfo.RemoteAddress, ConnectionMode.Remote) + ServerInfo.Address, }; if (!networkStatus.GetIsAnyLocalNetworkAvailable()) @@ -242,22 +203,17 @@ private async Task ValidateConnectionInternal(ConnectionMode initialConnectionMo urlList.Reverse(); } - if (!string.IsNullOrEmpty(ServerInfo.ManualAddress)) + if (ServerInfo.Address != null) { - if (!string.Equals(ServerInfo.ManualAddress, ServerInfo.LocalAddress, StringComparison.OrdinalIgnoreCase) && - !string.Equals(ServerInfo.ManualAddress, ServerInfo.RemoteAddress, StringComparison.OrdinalIgnoreCase)) - { - urlList.Insert(0, new Tuple(ServerInfo.ManualAddress, ConnectionMode.Manual)); - } + urlList.Insert(0, ServerInfo.Address); } foreach (var url in urlList) { - var connected = await TryConnect(url.Item1, cancellationToken).ConfigureAwait(false); + var connected = await TryConnect(url, cancellationToken).ConfigureAwait(false); if (connected) { - ConnectionMode = url.Item2; break; } } @@ -265,9 +221,9 @@ private async Task ValidateConnectionInternal(ConnectionMode initialConnectionMo _lastConnectionValidationTime = DateTime.UtcNow; } - private async Task TryConnect(string baseUrl, CancellationToken cancellationToken) + private async Task TryConnect(Uri baseUrl, CancellationToken cancellationToken) { - var fullUrl = baseUrl + "/system/info/public"; + var fullUrl = new Uri(baseUrl, new Uri("/system/info/public", UriKind.Relative)); fullUrl = AddDataFormat(fullUrl); @@ -286,29 +242,27 @@ private async Task TryConnect(string baseUrl, CancellationToken cancellati return true; } } - catch (Exception ex) + catch (Exception) { return false; } } - private string ReplaceServerAddress(string url, ConnectionMode initialConnectionMode) + private Uri ReplaceServerAddress(Uri url) { - var baseUrl = ServerInfo.GetAddress(ConnectionMode); + var baseUrl = ServerInfo.Address; - var index = url.IndexOf("/mediabrowser", StringComparison.OrdinalIgnoreCase); + var index = url.ToString().IndexOf("/mediabrowser", StringComparison.OrdinalIgnoreCase); if (index != -1) { - return baseUrl.TrimEnd('/') + url.Substring(index); + return new Uri(baseUrl, url.ToString().Substring(index)); } return url; - - //return url.Replace(ServerInfo.GetAddress(initialConnectionMode), ServerInfo.GetAddress(ConnectionMode), StringComparison.OrdinalIgnoreCase); } - public Task GetStream(string url, CancellationToken cancellationToken = default(CancellationToken)) + public Task GetStream(Uri url, CancellationToken cancellationToken = default) { return SendAsync(new HttpRequest { @@ -320,7 +274,7 @@ private string ReplaceServerAddress(string url, ConnectionMode initialConnection } - public Task GetResponse(string url, CancellationToken cancellationToken = default(CancellationToken)) + public Task GetResponse(Uri url, CancellationToken cancellationToken = default) { return HttpClient.GetResponse(new HttpRequest { @@ -338,9 +292,9 @@ private string ReplaceServerAddress(string url, ConnectionMode initialConnection /// The cancellation token. /// Task{Stream}. /// url - public Task GetImageStreamAsync(string url, CancellationToken cancellationToken = default(CancellationToken)) + public Task GetImageStreamAsync(Uri url, CancellationToken cancellationToken = default) { - if (string.IsNullOrEmpty(url)) + if (url == null) { throw new ArgumentNullException("url"); } @@ -367,7 +321,7 @@ public async Task GetItemAsync(string id, string userId) throw new ArgumentNullException("userId"); } - var url = GetApiUrl("Users/" + userId + "/Items/" + id); + var url = GetApiUrl(new Uri("Users/" + userId + "/Items/" + id, UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -394,8 +348,7 @@ public async Task> GetIntrosAsync(string itemId, string throw new ArgumentNullException("userId"); } - var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/Intros"); - + var url = GetApiUrl(new Uri("Users/" + userId + "/Items/" + itemId + "/Intros", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { return DeserializeFromStream>(stream); @@ -415,12 +368,12 @@ public async Task GetItemCountsAsync(ItemCountsQuery query) throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("UserId", query.UserId); dict.AddIfNotNull("IsFavorite", query.IsFavorite); - var url = GetApiUrl("Items/Counts", dict); + var url = GetApiUrl(new Uri("Items/Counts", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -441,7 +394,7 @@ public async Task GetRootFolderAsync(string userId) throw new ArgumentNullException("userId"); } - var url = GetApiUrl("Users/" + userId + "/Items/Root"); + var url = GetApiUrl(new Uri("Users/" + userId + "/Items/Root", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -455,12 +408,12 @@ public async Task GetRootFolderAsync(string userId) /// Task{UserDto[]}. public async Task GetUsersAsync(UserQuery query) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection(); queryString.AddIfNotNull("IsDisabled", query.IsDisabled); queryString.AddIfNotNull("IsHidden", query.IsHidden); - var url = GetApiUrl("Users", queryString); + var url = GetApiUrl(new Uri("Users", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -468,9 +421,9 @@ public async Task GetUsersAsync(UserQuery query) } } - public async Task GetPublicUsersAsync(CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetPublicUsersAsync(CancellationToken cancellationToken = default) { - var url = GetApiUrl("Users/Public"); + var url = GetApiUrl(new Uri("Users/Public", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -484,11 +437,11 @@ public async Task GetUsersAsync(UserQuery query) /// Task{SessionInfoDto[]}. public async Task GetClientSessionsAsync(SessionQuery query) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection(); queryString.AddIfNotNullOrEmpty("ControllableByUserId", query.ControllableByUserId); - var url = GetApiUrl("Sessions", queryString); + var url = GetApiUrl(new Uri("Sessions", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -496,18 +449,13 @@ public async Task GetClientSessionsAsync(SessionQuery query) } } - public Task GetRegistrationInfo() - { - throw new NotImplementedException(); - } - /// /// Queries for items /// /// The query. /// Task{ItemsResult}. /// query - public async Task> GetItemsAsync(ItemQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetItemsAsync(ItemQuery query, CancellationToken cancellationToken = default) { if (query == null) { @@ -528,7 +476,7 @@ public Task GetRegistrationInfo() /// The query. /// Task{ItemsResult}. /// query - public async Task> GetNextUpEpisodesAsync(NextUpQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetNextUpEpisodesAsync(NextUpQuery query, CancellationToken cancellationToken = default) { if (query == null) { @@ -550,12 +498,7 @@ public async Task> GetUpcomingEpisodesAsync(UpcomingEpi throw new ArgumentNullException("query"); } - if (query == null) - { - throw new ArgumentNullException("query"); - } - - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; if (query.Fields != null) { @@ -577,7 +520,7 @@ public async Task> GetUpcomingEpisodesAsync(UpcomingEpi } dict.AddIfNotNull("ImageTypeLimit", query.ImageTypeLimit); - var url = GetApiUrl("Shows/Upcoming", dict); + var url = GetApiUrl(new Uri("Shows/Upcoming", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -585,14 +528,14 @@ public async Task> GetUpcomingEpisodesAsync(UpcomingEpi } } - public async Task> GetEpisodesAsync(EpisodeQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetEpisodesAsync(EpisodeQuery query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection{ }; dict.AddIfNotNull("StartIndex", query.StartIndex); dict.AddIfNotNull("Limit", query.Limit); @@ -611,7 +554,7 @@ public async Task> GetUpcomingEpisodesAsync(UpcomingEpi dict.AddIfNotNull("IsMissing", query.IsMissing); dict.AddIfNotNull("IsVirtualUnaired", query.IsVirtualUnaired); - var url = GetApiUrl("Shows/" + query.SeriesId + "/Episodes", dict); + var url = GetApiUrl(new Uri("Shows/" + query.SeriesId + "/Episodes", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -619,14 +562,14 @@ public async Task> GetUpcomingEpisodesAsync(UpcomingEpi } } - public async Task> GetSeasonsAsync(SeasonQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetSeasonsAsync(SeasonQuery query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("UserId", query.UserId); @@ -639,7 +582,7 @@ public async Task> GetUpcomingEpisodesAsync(UpcomingEpi dict.AddIfNotNull("IsVirtualUnaired", query.IsVirtualUnaired); dict.AddIfNotNull("IsSpecialSeason", query.IsSpecialSeason); - var url = GetApiUrl("Shows/" + query.SeriesId + "/Seasons", dict); + var url = GetApiUrl(new Uri("Shows/" + query.SeriesId + "/Seasons", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -653,13 +596,17 @@ public async Task> GetUpcomingEpisodesAsync(UpcomingEpi /// The query. /// Task{ItemsResult}. /// userId - public async Task> GetPeopleAsync(PersonsQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetPeopleAsync(PersonsQuery query, CancellationToken cancellationToken = default) { var url = GetItemByNameListUrl("Persons", query); if (query.PersonTypes != null && query.PersonTypes.Length > 0) { - url += "&PersonTypes=" + string.Join(",", query.PersonTypes); + var uriBuilder = new UriBuilder(url); + var uriQuery = HttpUtility.ParseQueryString(uriBuilder.Query); + uriQuery["PersonTypes"] = string.Join(",", query.PersonTypes); + uriBuilder.Query = uriQuery.ToQueryString(); + url = uriBuilder.Uri; } using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) @@ -736,18 +683,18 @@ public async Task> GetAlbumArtistsAsync(ArtistsQuery qu /// Task. public Task RestartServerAsync() { - var url = GetApiUrl("System/Restart"); + var url = GetApiUrl(new Uri("System/Restart", UriKind.Relative)); - return PostAsync(url, new QueryStringDictionary(), CancellationToken.None); + return PostAsync(url, new NameValueCollection(), CancellationToken.None); } /// /// Gets the system status async. /// /// Task{SystemInfo}. - public async Task GetSystemInfoAsync(CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetSystemInfoAsync(CancellationToken cancellationToken = default) { - var url = GetApiUrl("System/Info"); + var url = GetApiUrl(new Uri("System/Info", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -760,9 +707,9 @@ public Task RestartServerAsync() /// /// The cancellation token. /// Task<PublicSystemInfo>. - public async Task GetPublicSystemInfoAsync(CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetPublicSystemInfoAsync(CancellationToken cancellationToken = default) { - var url = GetApiUrl("System/Info/Public"); + var url = GetApiUrl(new Uri("System/Info/Public", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -776,7 +723,7 @@ public Task RestartServerAsync() /// Task{PluginInfo[]}. public async Task GetInstalledPluginsAsync() { - var url = GetApiUrl("Plugins"); + var url = GetApiUrl(new Uri("Plugins", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -790,7 +737,7 @@ public async Task GetInstalledPluginsAsync() /// Task{ServerConfiguration}. public async Task GetServerConfigurationAsync() { - var url = GetApiUrl("System/Configuration"); + var url = GetApiUrl(new Uri("System/Configuration", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -804,7 +751,7 @@ public async Task GetServerConfigurationAsync() /// Task{TaskInfo[]}. public async Task GetScheduledTasksAsync() { - var url = GetApiUrl("ScheduledTasks"); + var url = GetApiUrl(new Uri("ScheduledTasks", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -825,7 +772,7 @@ public async Task GetScheduledTaskAsync(string id) throw new ArgumentNullException("id"); } - var url = GetApiUrl("ScheduledTasks/" + id); + var url = GetApiUrl(new Uri("ScheduledTasks/" + id, UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -846,7 +793,7 @@ public async Task GetUserAsync(string id) throw new ArgumentNullException("id"); } - var url = GetApiUrl("Users/" + id); + var url = GetApiUrl(new Uri("Users/" + id, UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -860,7 +807,7 @@ public async Task GetUserAsync(string id) /// Task{List{ParentalRating}}. public async Task> GetParentalRatingsAsync() { - var url = GetApiUrl("Localization/ParentalRatings"); + var url = GetApiUrl(new Uri("Localization/ParentalRatings", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -886,7 +833,7 @@ public async Task GetLocalTrailersAsync(string userId, string ite throw new ArgumentNullException("itemId"); } - var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/LocalTrailers"); + var url = GetApiUrl(new Uri("Users/" + userId + "/Items/" + itemId + "/LocalTrailers", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -912,7 +859,7 @@ public async Task GetSpecialFeaturesAsync(string userId, string i throw new ArgumentNullException("itemId"); } - var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/SpecialFeatures"); + var url = GetApiUrl(new Uri("Users/" + userId + "/Items/" + itemId + "/SpecialFeatures", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -926,7 +873,7 @@ public async Task GetSpecialFeaturesAsync(string userId, string i /// Task{CultureDto[]}. public async Task GetCulturesAsync() { - var url = GetApiUrl("Localization/Cultures"); + var url = GetApiUrl(new Uri("Localization/Cultures", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -940,7 +887,7 @@ public async Task GetCulturesAsync() /// Task{CountryInfo[]}. public async Task GetCountriesAsync() { - var url = GetApiUrl("Localization/Countries"); + var url = GetApiUrl(new Uri("Localization/Countries", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -948,20 +895,6 @@ public async Task GetCountriesAsync() } } - /// - /// Gets the game system summaries async. - /// - /// Task{List{GameSystemSummary}}. - public async Task> GetGameSystemSummariesAsync(CancellationToken cancellationToken = default(CancellationToken)) - { - var url = GetApiUrl("Games/SystemSummaries"); - - using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) - { - return DeserializeFromStream>(stream); - } - } - public Task MarkPlayedAsync(string itemId, string userId, DateTime? datePlayed) { if (string.IsNullOrEmpty(itemId)) @@ -973,16 +906,16 @@ public Task MarkPlayedAsync(string itemId, string userId, DateT throw new ArgumentNullException("userId"); } - var dict = new QueryStringDictionary(); + var dict = new NameValueCollection(); if (datePlayed.HasValue) { - dict.Add("DatePlayed", datePlayed.Value.ToString("yyyyMMddHHmmss")); + dict.Add("DatePlayed", datePlayed.Value.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)); } - var url = GetApiUrl("Users/" + userId + "/PlayedItems/" + itemId, dict); + var url = GetApiUrl(new Uri("Users/" + userId + "/PlayedItems/" + itemId, UriKind.Relative), dict); - return PostAsync(url, new Dictionary(), CancellationToken.None); + return PostAsync(url, new NameValueCollection(), CancellationToken.None); } /// @@ -1007,7 +940,7 @@ public Task MarkUnplayedAsync(string itemId, string userId) throw new ArgumentNullException("userId"); } - var url = GetApiUrl("Users/" + userId + "/PlayedItems/" + itemId); + var url = GetApiUrl(new Uri("Users/" + userId + "/PlayedItems/" + itemId, UriKind.Relative)); return DeleteAsync(url, CancellationToken.None); } @@ -1031,11 +964,11 @@ public Task UpdateFavoriteStatusAsync(string itemId, string use throw new ArgumentNullException("userId"); } - var url = GetApiUrl("Users/" + userId + "/FavoriteItems/" + itemId); + var url = GetApiUrl(new Uri("Users/" + userId + "/FavoriteItems/" + itemId, UriKind.Relative)); if (isFavorite) { - return PostAsync(url, new Dictionary(), CancellationToken.None); + return PostAsync(url, new NameValueCollection(), CancellationToken.None); } return DeleteAsync(url, CancellationToken.None); @@ -1054,9 +987,9 @@ public Task ReportPlaybackStartAsync(PlaybackStartInfo info) throw new ArgumentNullException("info"); } - Logger.Debug("ReportPlaybackStart: Item {0}", info.ItemId); + Logger.LogDebug("ReportPlaybackStart: Item {0}", info.ItemId); - var url = GetApiUrl("Sessions/Playing"); + var url = GetApiUrl(new Uri("Sessions/Playing", UriKind.Relative)); return PostAsync(url, info, CancellationToken.None); } @@ -1079,7 +1012,7 @@ public Task ReportPlaybackProgressAsync(PlaybackProgressInfo info) return SendWebSocketMessage("ReportPlaybackProgress", JsonSerializer.SerializeToString(info)); } - var url = GetApiUrl("Sessions/Playing/Progress"); + var url = GetApiUrl(new Uri("Sessions/Playing/Progress", UriKind.Relative)); return PostAsync(url, info, CancellationToken.None); } @@ -1097,7 +1030,7 @@ public Task ReportPlaybackStoppedAsync(PlaybackStopInfo info) throw new ArgumentNullException("info"); } - var url = GetApiUrl("Sessions/Playing/Stopped"); + var url = GetApiUrl(new Uri("Sessions/Playing/Stopped", UriKind.Relative)); return PostAsync(url, info, CancellationToken.None); } @@ -1151,14 +1084,17 @@ public Task SendPlayCommandAsync(string sessionId, PlayRequest request) throw new ArgumentNullException("request"); } - var dict = new QueryStringDictionary(); - dict.Add("ItemIds", request.ItemIds); + var dict = new NameValueCollection + { + { "ItemIds", request.ItemIds.Select(o => o.ToString("N", CultureInfo.InvariantCulture)).ToList() }, + { "PlayCommand", request.PlayCommand.ToString() } + }; + dict.AddIfNotNull("StartPositionTicks", request.StartPositionTicks); - dict.Add("PlayCommand", request.PlayCommand.ToString()); - var url = GetApiUrl("Sessions/" + sessionId + "/Playing", dict); + var url = GetApiUrl(new Uri("Sessions/" + sessionId + "/Playing", UriKind.Relative), dict); - return PostAsync(url, new Dictionary(), CancellationToken.None); + return PostAsync(url, new NameValueCollection(), CancellationToken.None); } public Task SendMessageCommandAsync(string sessionId, MessageCommand command) @@ -1173,7 +1109,7 @@ public Task SendMessageCommandAsync(string sessionId, MessageCommand command) if (command.TimeoutMs.HasValue) { - cmd.Arguments["Timeout"] = command.TimeoutMs.Value.ToString(CultureInfo.InvariantCulture); + cmd.Arguments["Timeout"] = command.TimeoutMs?.ToString(CultureInfo.InvariantCulture); } return SendCommandAsync(sessionId, cmd); @@ -1193,7 +1129,7 @@ public Task SendCommandAsync(string sessionId, GeneralCommand command) throw new ArgumentNullException("sessionId"); } - var url = GetApiUrl("Sessions/" + sessionId + "/Command"); + var url = GetApiUrl(new Uri("Sessions/" + sessionId + "/Command", UriKind.Relative)); return PostAsync(url, command, CancellationToken.None); } @@ -1206,12 +1142,12 @@ public Task SendCommandAsync(string sessionId, GeneralCommand command) /// Task. public Task SendPlaystateCommandAsync(string sessionId, PlaystateRequest request) { - var dict = new QueryStringDictionary(); + var dict = new NameValueCollection(); dict.AddIfNotNull("SeekPositionTicks", request.SeekPositionTicks); - var url = GetApiUrl("Sessions/" + sessionId + "/Playing/" + request.Command.ToString(), dict); + var url = GetApiUrl(new Uri("Sessions/" + sessionId + "/Playing/" + request.Command.ToString(), UriKind.Relative), dict); - return PostAsync(url, new Dictionary(), CancellationToken.None); + return PostAsync(url, new NameValueCollection(), CancellationToken.None); } /// @@ -1233,7 +1169,7 @@ public Task ClearUserItemRatingAsync(string itemId, string user throw new ArgumentNullException("userId"); } - var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/Rating"); + var url = GetApiUrl(new Uri("Users/" + userId + "/Items/" + itemId + "/Rating", UriKind.Relative)); return DeleteAsync(url, CancellationToken.None); } @@ -1258,17 +1194,15 @@ public Task UpdateUserItemRatingAsync(string itemId, string use throw new ArgumentNullException("userId"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.Add("likes", likes); - var url = GetApiUrl("Users/" + userId + "/Items/" + itemId + "/Rating", dict); + var url = GetApiUrl(new Uri("Users/" + userId + "/Items/" + itemId + "/Rating", UriKind.Relative), dict); - return PostAsync(url, new Dictionary(), CancellationToken.None); + return PostAsync(url, new NameValueCollection(), CancellationToken.None); } - internal Func OnAuthenticated { get; set; } - /// /// Authenticates a user and returns the result /// @@ -1284,26 +1218,19 @@ public async Task AuthenticateUserAsync(string username, s throw new ArgumentNullException("username"); } - var url = GetApiUrl("Users/AuthenticateByName"); - - var args = new Dictionary(); + var url = GetApiUrl(new Uri("Users/AuthenticateByName", UriKind.Relative)); - args["username"] = Uri.EscapeDataString(username); - args["pw"] = password; - - var bytes = Encoding.UTF8.GetBytes(password ?? string.Empty); - args["password"] = BitConverter.ToString(_cryptographyProvider.CreateSha1(bytes)).Replace("-", string.Empty); - - args["passwordMD5"] = ConnectService.GetConnectPasswordMd5(password ?? string.Empty, _cryptographyProvider); + var authRequest = new AuthenticationRequest + { + Username = username, + Pw = password + }; - var result = await PostAsync(url, args, CancellationToken.None); + var result = await PostAsync(url, authRequest, CancellationToken.None); SetAuthenticationInfo(result.AccessToken, result.User.Id); - if (OnAuthenticated != null) - { - await OnAuthenticated(this, result).ConfigureAwait(false); - } + OnAuthenticated?.Invoke(this, new GenericEventArgs(result)); return result; } @@ -1321,7 +1248,7 @@ public Task UpdateServerConfigurationAsync(ServerConfiguration configuration) throw new ArgumentNullException("configuration"); } - var url = GetApiUrl("System/Configuration"); + var url = GetApiUrl(new Uri("System/Configuration", UriKind.Relative)); return PostAsync(url, configuration, CancellationToken.None); } @@ -1345,7 +1272,7 @@ public Task UpdateScheduledTaskTriggersAsync(string id, TaskTriggerInfo[] trigge throw new ArgumentNullException("triggers"); } - var url = GetApiUrl("ScheduledTasks/" + id + "/Triggers"); + var url = GetApiUrl(new Uri("ScheduledTasks/" + id + "/Triggers", UriKind.Relative)); return PostAsync(url, triggers, CancellationToken.None); } @@ -1357,14 +1284,15 @@ public Task UpdateScheduledTaskTriggersAsync(string id, TaskTriggerInfo[] trigge /// The user id. /// The client. /// Task{BaseItemDto}. - public async Task GetDisplayPreferencesAsync(string id, string userId, string client, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetDisplayPreferencesAsync(string id, string userId, string client, CancellationToken cancellationToken = default) { - var dict = new QueryStringDictionary(); - - dict.Add("userId", userId); - dict.Add("client", client); + var dict = new NameValueCollection + { + { "userId", userId }, + { "client", client } + }; - var url = GetApiUrl("DisplayPreferences/" + id, dict); + var url = GetApiUrl(new Uri("DisplayPreferences/" + id, UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1378,19 +1306,20 @@ public Task UpdateScheduledTaskTriggersAsync(string id, TaskTriggerInfo[] trigge /// The display preferences. /// Task{DisplayPreferences}. /// userId - public Task UpdateDisplayPreferencesAsync(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateDisplayPreferencesAsync(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken = default) { if (displayPreferences == null) { throw new ArgumentNullException("displayPreferences"); } - var dict = new QueryStringDictionary(); - - dict.Add("userId", userId); - dict.Add("client", client); + var dict = new NameValueCollection + { + { "userId", userId }, + { "client", client } + }; - var url = GetApiUrl("DisplayPreferences/" + displayPreferences.Id, dict); + var url = GetApiUrl(new Uri("DisplayPreferences/" + displayPreferences.Id, UriKind.Relative), dict); return PostAsync(url, displayPreferences, cancellationToken); } @@ -1403,14 +1332,13 @@ public Task UpdateScheduledTaskTriggersAsync(string id, TaskTriggerInfo[] trigge /// The args. /// The cancellation token. /// Task{``0}. - public async Task PostAsync(string url, Dictionary args, CancellationToken cancellationToken = default(CancellationToken)) + public async Task PostAsync(Uri url, NameValueCollection query, CancellationToken cancellationToken = default) where T : class { url = AddDataFormat(url); // Create the post body - var strings = args.Keys.Select(key => string.Format("{0}={1}", key, args[key])); - var postContent = string.Join("&", strings.ToArray()); + var postContent = query.ToQueryString(); const string contentType = "application/x-www-form-urlencoded"; @@ -1435,7 +1363,7 @@ public Task UpdateScheduledTaskTriggersAsync(string id, TaskTriggerInfo[] trigge /// The URL. /// The cancellation token. /// Task{``0}. - private async Task DeleteAsync(string url, CancellationToken cancellationToken = default(CancellationToken)) + private async Task DeleteAsync(Uri url, CancellationToken cancellationToken = default) where T : class { url = AddDataFormat(url); @@ -1462,7 +1390,7 @@ public Task UpdateScheduledTaskTriggersAsync(string id, TaskTriggerInfo[] trigge /// The obj. /// The cancellation token. /// Task{``1}. - private async Task PostAsync(string url, TInputType obj, CancellationToken cancellationToken = default(CancellationToken)) + private async Task PostAsync(Uri url, TInputType obj, CancellationToken cancellationToken = default) where TOutputType : class { url = AddDataFormat(url); @@ -1491,21 +1419,21 @@ public Task UpdateScheduledTaskTriggersAsync(string id, TaskTriggerInfo[] trigge /// The URL. /// The cancellation token. /// Task{Stream}. - public Task GetSerializedStreamAsync(string url, CancellationToken cancellationToken) + public Task GetSerializedStreamAsync(Uri url, CancellationToken cancellationToken) { url = AddDataFormat(url); return GetStream(url, cancellationToken); } - public Task GetSerializedStreamAsync(string url) + public Task GetSerializedStreamAsync(Uri url) { return GetSerializedStreamAsync(url, CancellationToken.None); } public async Task GetNotificationsSummary(string userId) { - var url = GetApiUrl("Notifications/" + userId + "/Summary"); + var url = GetApiUrl(new Uri("Notifications/" + userId + "/Summary", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -1515,11 +1443,11 @@ public async Task GetNotificationsSummary(string userId) public Task MarkNotificationsRead(string userId, IEnumerable notificationIdList, bool isRead) { - var url = "Notifications/" + userId; + Uri url = new Uri("Notifications/" + userId, UriKind.Relative); - url += isRead ? "/Read" : "/Unread"; + url = new Uri(url, isRead ? "/Read" : "/Unread"); - var dict = new QueryStringDictionary(); + var dict = new NameValueCollection(); var ids = notificationIdList.ToArray(); @@ -1527,14 +1455,14 @@ public Task MarkNotificationsRead(string userId, IEnumerable notificatio url = GetApiUrl(url, dict); - return PostAsync(url, new Dictionary(), CancellationToken.None); + return PostAsync(url, new NameValueCollection(), CancellationToken.None); } public async Task GetNotificationsAsync(NotificationQuery query) { - var url = "Notifications/" + query.UserId; + var url = new Uri("Notifications/" + query.UserId, UriKind.Relative); - var dict = new QueryStringDictionary(); + var dict = new NameValueCollection(); dict.AddIfNotNull("ItemIds", query.IsRead); dict.AddIfNotNull("StartIndex", query.StartIndex); dict.AddIfNotNull("Limit", query.Limit); @@ -1547,14 +1475,16 @@ public async Task GetNotificationsAsync(NotificationQuery qu } } - public async Task GetAllThemeMediaAsync(string userId, string itemId, bool inheritFromParent, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetAllThemeMediaAsync(string userId, string itemId, bool inheritFromParent, CancellationToken cancellationToken = default) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection + { + { "InheritFromParent", inheritFromParent } + }; - queryString.Add("InheritFromParent", inheritFromParent); queryString.AddIfNotNullOrEmpty("UserId", userId); - var url = GetApiUrl("Items/" + itemId + "/ThemeMedia", queryString); + var url = GetApiUrl(new Uri("Items/" + itemId + "/ThemeMedia", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1569,10 +1499,10 @@ public async Task GetSearchHintsAsync(SearchQuery query) throw new ArgumentNullException("query"); } - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection(); queryString.AddIfNotNullOrEmpty("SearchTerm", query.SearchTerm); - queryString.AddIfNotNullOrEmpty("UserId", query.UserId); + queryString.AddIfNotNullOrEmpty("UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture)); queryString.AddIfNotNullOrEmpty("ParentId", query.ParentId); queryString.AddIfNotNull("StartIndex", query.StartIndex); queryString.AddIfNotNull("Limit", query.Limit); @@ -1593,7 +1523,7 @@ public async Task GetSearchHintsAsync(SearchQuery query) queryString.AddIfNotNull("IsSports", query.IsSports); queryString.AddIfNotNull("MediaTypes", query.MediaTypes); - var url = GetApiUrl("Search/Hints", queryString); + var url = GetApiUrl(new Uri("Search/Hints", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -1601,14 +1531,16 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task GetThemeSongsAsync(string userId, string itemId, bool inheritFromParent, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetThemeSongsAsync(string userId, string itemId, bool inheritFromParent, CancellationToken cancellationToken = default) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection + { + { "InheritFromParent", inheritFromParent } + }; - queryString.Add("InheritFromParent", inheritFromParent); queryString.AddIfNotNullOrEmpty("UserId", userId); - var url = GetApiUrl("Items/" + itemId + "/ThemeSongs", queryString); + var url = GetApiUrl(new Uri("Items/" + itemId + "/ThemeSongs", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1616,14 +1548,16 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task GetThemeVideosAsync(string userId, string itemId, bool inheritFromParent, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetThemeVideosAsync(string userId, string itemId, bool inheritFromParent, CancellationToken cancellationToken = default) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection + { + { "InheritFromParent", inheritFromParent } + }; - queryString.Add("InheritFromParent", inheritFromParent); queryString.AddIfNotNullOrEmpty("UserId", userId); - var url = GetApiUrl("Items/" + itemId + "/ThemeVideos", queryString); + var url = GetApiUrl(new Uri("Items/" + itemId + "/ThemeVideos", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1643,19 +1577,19 @@ public async Task GetSearchHintsAsync(SearchQuery query) /// or /// userId /// - public async Task> GetCriticReviews(string itemId, CancellationToken cancellationToken = default(CancellationToken), int? startIndex = null, int? limit = null) + public async Task> GetCriticReviews(string itemId, CancellationToken cancellationToken = default, int? startIndex = null, int? limit = null) { if (string.IsNullOrEmpty(itemId)) { throw new ArgumentNullException("itemId"); } - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection(); queryString.AddIfNotNull("startIndex", startIndex); queryString.AddIfNotNull("limit", limit); - var url = GetApiUrl("Items/" + itemId + "/CriticReviews", queryString); + var url = GetApiUrl(new Uri("Items/" + itemId + "/CriticReviews", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1663,7 +1597,7 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task GetAsync(string url, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetAsync(Uri url, CancellationToken cancellationToken = default) where T : class { using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) @@ -1678,13 +1612,13 @@ public async Task GetSearchHintsAsync(SearchQuery query) /// The user id. /// The cancellation token. /// Task{List{ItemIndex}}. - public async Task> GetGamePlayerIndex(string userId, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetGamePlayerIndex(string userId, CancellationToken cancellationToken = default) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection(); queryString.AddIfNotNullOrEmpty("UserId", userId); - var url = GetApiUrl("Games/PlayerIndex", queryString); + var url = GetApiUrl(new Uri("Games/PlayerIndex", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1699,14 +1633,14 @@ public async Task GetSearchHintsAsync(SearchQuery query) /// The include item types. /// The cancellation token. /// Task{List{ItemIndex}}. - public async Task> GetYearIndex(string userId, string[] includeItemTypes, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetYearIndex(string userId, string[] includeItemTypes, CancellationToken cancellationToken = default) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection(); queryString.AddIfNotNullOrEmpty("UserId", userId); queryString.AddIfNotNull("IncludeItemTypes", includeItemTypes); - var url = GetApiUrl("Items/YearIndex", queryString); + var url = GetApiUrl(new Uri("Items/YearIndex", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1714,21 +1648,21 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public Task ReportCapabilities(ClientCapabilities capabilities, CancellationToken cancellationToken = default(CancellationToken)) + public Task ReportCapabilities(ClientCapabilities capabilities, CancellationToken cancellationToken = default) { if (capabilities == null) { throw new ArgumentNullException("capabilities"); } - var url = GetApiUrl("Sessions/Capabilities/Full"); + var url = GetApiUrl(new Uri("Sessions/Capabilities/Full", UriKind.Relative)); return PostAsync(url, capabilities, cancellationToken); } - public async Task GetLiveTvInfoAsync(CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetLiveTvInfoAsync(CancellationToken cancellationToken = default) { - var url = GetApiUrl("LiveTv/Info"); + var url = GetApiUrl(new Uri("LiveTv/Info", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1736,18 +1670,18 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task> GetLiveTvRecordingGroupsAsync(RecordingGroupQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetLiveTvRecordingGroupsAsync(RecordingGroupQuery query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("UserId", query.UserId); - var url = GetApiUrl("LiveTv/Recordings/Groups", dict); + var url = GetApiUrl(new Uri("LiveTv/Recordings/Groups", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1755,18 +1689,17 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task> GetLiveTvRecordingsAsync(RecordingQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetLiveTvRecordingsAsync(RecordingQuery query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; - dict.AddIfNotNullOrEmpty("UserId", query.UserId); + dict.AddIfNotNullOrEmpty("UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture)); dict.AddIfNotNullOrEmpty("ChannelId", query.ChannelId); - dict.AddIfNotNullOrEmpty("GroupId", query.GroupId); dict.AddIfNotNullOrEmpty("Id", query.Id); dict.AddIfNotNullOrEmpty("SeriesTimerId", query.SeriesTimerId); dict.AddIfNotNull("IsInProgress", query.IsInProgress); @@ -1778,7 +1711,7 @@ public async Task GetSearchHintsAsync(SearchQuery query) dict.Add("EnableTotalRecordCount", query.EnableTotalRecordCount); } - var url = GetApiUrl("LiveTv/Recordings", dict); + var url = GetApiUrl(new Uri("LiveTv/Recordings", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1786,16 +1719,16 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task> GetLiveTvChannelsAsync(LiveTvChannelQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetLiveTvChannelsAsync(LiveTvChannelQuery query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; - dict.AddIfNotNullOrEmpty("UserId", query.UserId); + dict.AddIfNotNullOrEmpty("UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture)); dict.AddIfNotNull("StartIndex", query.StartIndex); dict.AddIfNotNull("Limit", query.Limit); dict.AddIfNotNull("IsFavorite", query.IsFavorite); @@ -1809,7 +1742,7 @@ public async Task GetSearchHintsAsync(SearchQuery query) dict.Add("ChannelType", query.ChannelType.Value.ToString()); } - var url = GetApiUrl("LiveTv/Channels", dict); + var url = GetApiUrl(new Uri("LiveTv/Channels", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1817,16 +1750,16 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public Task CancelLiveTvSeriesTimerAsync(string id, CancellationToken cancellationToken = default(CancellationToken)) + public Task CancelLiveTvSeriesTimerAsync(string id, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; - var url = GetApiUrl("LiveTv/SeriesTimers/" + id, dict); + var url = GetApiUrl(new Uri("LiveTv/SeriesTimers/" + id, UriKind.Relative), dict); return SendAsync(new HttpRequest { @@ -1837,16 +1770,16 @@ public async Task GetSearchHintsAsync(SearchQuery query) }); } - public Task CancelLiveTvTimerAsync(string id, CancellationToken cancellationToken = default(CancellationToken)) + public Task CancelLiveTvTimerAsync(string id, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; - var url = GetApiUrl("LiveTv/Timers/" + id, dict); + var url = GetApiUrl(new Uri("LiveTv/Timers/" + id, UriKind.Relative), dict); return SendAsync(new HttpRequest { @@ -1857,17 +1790,17 @@ public async Task GetSearchHintsAsync(SearchQuery query) }); } - public async Task GetLiveTvChannelAsync(string id, string userId, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetLiveTvChannelAsync(string id, string userId, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("userId", userId); - var url = GetApiUrl("LiveTv/Channels/" + id, dict); + var url = GetApiUrl(new Uri("LiveTv/Channels/" + id, UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1875,17 +1808,17 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task GetLiveTvRecordingAsync(string id, string userId, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetLiveTvRecordingAsync(string id, string userId, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("userId", userId); - var url = GetApiUrl("LiveTv/Recordings/" + id, dict); + var url = GetApiUrl(new Uri("LiveTv/Recordings/" + id, UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1893,17 +1826,17 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task GetLiveTvRecordingGroupAsync(string id, string userId, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetLiveTvRecordingGroupAsync(string id, string userId, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("userId", userId); - var url = GetApiUrl("LiveTv/Recordings/Groups/" + id, dict); + var url = GetApiUrl(new Uri("LiveTv/Recordings/Groups/" + id, UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1911,16 +1844,16 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task GetLiveTvSeriesTimerAsync(string id, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetLiveTvSeriesTimerAsync(string id, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; - var url = GetApiUrl("LiveTv/SeriesTimers/" + id, dict); + var url = GetApiUrl(new Uri("LiveTv/SeriesTimers/" + id, UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1928,19 +1861,19 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task> GetLiveTvSeriesTimersAsync(SeriesTimerQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetLiveTvSeriesTimersAsync(SeriesTimerQuery query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("SortBy", query.SortBy); dict.Add("SortOrder", query.SortOrder.ToString()); - var url = GetApiUrl("LiveTv/SeriesTimers", dict); + var url = GetApiUrl(new Uri("LiveTv/SeriesTimers", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1948,16 +1881,16 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task GetLiveTvTimerAsync(string id, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetLiveTvTimerAsync(string id, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; - var url = GetApiUrl("LiveTv/Timers/" + id, dict); + var url = GetApiUrl(new Uri("LiveTv/Timers/" + id, UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1965,19 +1898,19 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task> GetLiveTvTimersAsync(TimerQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetLiveTvTimersAsync(TimerQuery query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("ChannelId", query.ChannelId); dict.AddIfNotNullOrEmpty("SeriesTimerId", query.SeriesTimerId); - var url = GetApiUrl("LiveTv/Timers", dict); + var url = GetApiUrl(new Uri("LiveTv/Timers", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -1985,32 +1918,32 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task> GetLiveTvProgramsAsync(ProgramQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetLiveTvProgramsAsync(ProgramQuery query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; const string isoDateFormat = "o"; if (query.MaxEndDate.HasValue) { - dict.Add("MaxEndDate", query.MaxEndDate.Value.ToUniversalTime().ToString(isoDateFormat)); + dict.Add("MaxEndDate", query.MaxEndDate.Value.ToUniversalTime().ToString(isoDateFormat, CultureInfo.InvariantCulture)); } if (query.MaxStartDate.HasValue) { - dict.Add("MaxStartDate", query.MaxStartDate.Value.ToUniversalTime().ToString(isoDateFormat)); + dict.Add("MaxStartDate", query.MaxStartDate.Value.ToUniversalTime().ToString(isoDateFormat, CultureInfo.InvariantCulture)); } if (query.MinEndDate.HasValue) { - dict.Add("MinEndDate", query.MinEndDate.Value.ToUniversalTime().ToString(isoDateFormat)); + dict.Add("MinEndDate", query.MinEndDate.Value.ToUniversalTime().ToString(isoDateFormat, CultureInfo.InvariantCulture)); } if (query.MinStartDate.HasValue) { - dict.Add("MinStartDate", query.MinStartDate.Value.ToUniversalTime().ToString(isoDateFormat)); + dict.Add("MinStartDate", query.MinStartDate.Value.ToUniversalTime().ToString(isoDateFormat, CultureInfo.InvariantCulture)); } dict.AddIfNotNullOrEmpty("UserId", query.UserId); @@ -2026,7 +1959,7 @@ public async Task GetSearchHintsAsync(SearchQuery query) } // TODO: This endpoint supports POST if the query string is too long - var url = GetApiUrl("LiveTv/Programs", dict); + var url = GetApiUrl(new Uri("LiveTv/Programs", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2034,14 +1967,14 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task> GetRecommendedLiveTvProgramsAsync(RecommendedProgramQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetRecommendedLiveTvProgramsAsync(RecommendedProgramQuery query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("UserId", query.UserId); dict.AddIfNotNull("Limit", query.Limit); @@ -2053,7 +1986,7 @@ public async Task GetSearchHintsAsync(SearchQuery query) dict.Add("EnableTotalRecordCount", query.EnableTotalRecordCount); } - var url = GetApiUrl("LiveTv/Programs/Recommended", dict); + var url = GetApiUrl(new Uri("LiveTv/Programs/Recommended", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2061,42 +1994,42 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public Task CreateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken = default) { if (timer == null) { throw new ArgumentNullException("timer"); } - var url = GetApiUrl("LiveTv/SeriesTimers"); + var url = GetApiUrl(new Uri("LiveTv/SeriesTimers", UriKind.Relative)); return PostAsync(url, timer, cancellationToken); } - public Task CreateLiveTvTimerAsync(BaseTimerInfoDto timer, CancellationToken cancellationToken = default(CancellationToken)) + public Task CreateLiveTvTimerAsync(BaseTimerInfoDto timer, CancellationToken cancellationToken = default) { if (timer == null) { throw new ArgumentNullException("timer"); } - var url = GetApiUrl("LiveTv/Timers"); + var url = GetApiUrl(new Uri("LiveTv/Timers", UriKind.Relative)); return PostAsync(url, timer, cancellationToken); } - public async Task GetDefaultLiveTvTimerInfo(string programId, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetDefaultLiveTvTimerInfo(string programId, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(programId)) { throw new ArgumentNullException("programId"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("programId", programId); - var url = GetApiUrl("LiveTv/Timers/Defaults", dict); + var url = GetApiUrl(new Uri("LiveTv/Timers/Defaults", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2104,9 +2037,9 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task GetDefaultLiveTvTimerInfo(CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetDefaultLiveTvTimerInfo(CancellationToken cancellationToken = default) { - var url = GetApiUrl("LiveTv/Timers/Defaults"); + var url = GetApiUrl(new Uri("LiveTv/Timers/Defaults", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2114,9 +2047,9 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task GetLiveTvGuideInfo(CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetLiveTvGuideInfo(CancellationToken cancellationToken = default) { - var url = GetApiUrl("LiveTv/GuideInfo"); + var url = GetApiUrl(new Uri("LiveTv/GuideInfo", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2124,17 +2057,17 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public async Task GetLiveTvProgramAsync(string id, string userId, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetLiveTvProgramAsync(string id, string userId, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("userId", userId); - var url = GetApiUrl("LiveTv/Programs/" + id, dict); + var url = GetApiUrl(new Uri("LiveTv/Programs/" + id, UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2142,26 +2075,26 @@ public async Task GetSearchHintsAsync(SearchQuery query) } } - public Task UpdateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken = default) { if (timer == null) { throw new ArgumentNullException("timer"); } - var url = GetApiUrl("LiveTv/SeriesTimers/" + timer.Id); + var url = GetApiUrl(new Uri("LiveTv/SeriesTimers/" + timer.Id, UriKind.Relative)); return PostAsync(url, timer, cancellationToken); } - public Task UpdateLiveTvTimerAsync(TimerInfoDto timer, CancellationToken cancellationToken = default(CancellationToken)) + public Task UpdateLiveTvTimerAsync(TimerInfoDto timer, CancellationToken cancellationToken = default) { if (timer == null) { throw new ArgumentNullException("timer"); } - var url = GetApiUrl("LiveTv/Timers/" + timer.Id); + var url = GetApiUrl(new Uri("LiveTv/Timers/" + timer.Id, UriKind.Relative)); return PostAsync(url, timer, cancellationToken); } @@ -2216,11 +2149,11 @@ public Task SetVolume(string sessionId, int volume) public async Task> GetAdditionalParts(string itemId, string userId) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection(); queryString.AddIfNotNullOrEmpty("UserId", userId); - var url = GetApiUrl("Videos/" + itemId + "/AdditionalParts", queryString); + var url = GetApiUrl(new Uri("Videos/" + itemId + "/AdditionalParts", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, CancellationToken.None).ConfigureAwait(false)) { @@ -2228,9 +2161,9 @@ public async Task> GetAdditionalParts(string itemId, st } } - public async Task GetChannelFeatures(string channelId, CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetChannelFeatures(string channelId, CancellationToken cancellationToken = default) { - var url = GetApiUrl("Channels/" + channelId + "/Features"); + var url = GetApiUrl(new Uri("Channels/" + channelId + "/Features", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2238,9 +2171,9 @@ public async Task> GetAdditionalParts(string itemId, st } } - public async Task> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken = default) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection(); queryString.AddIfNotNullOrEmpty("UserId", query.UserId); queryString.AddIfNotNull("StartIndex", query.StartIndex); @@ -2266,7 +2199,7 @@ public async Task> GetAdditionalParts(string itemId, st queryString.AddIfNotNull("SortBy", sortBy); queryString.AddIfNotNull("SortOrder", sortOrder); - var url = GetApiUrl("Channels/" + query.ChannelId + "/Items", queryString); + var url = GetApiUrl(new Uri("Channels/" + query.ChannelId + "/Items", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2274,17 +2207,17 @@ public async Task> GetAdditionalParts(string itemId, st } } - public async Task> GetChannels(ChannelQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetChannels(ChannelQuery query, CancellationToken cancellationToken = default) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection(); - queryString.AddIfNotNullOrEmpty("UserId", query.UserId); + queryString.AddIfNotNullOrEmpty("UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture)); queryString.AddIfNotNull("SupportsLatestItems", query.SupportsLatestItems); queryString.AddIfNotNull("StartIndex", query.StartIndex); queryString.AddIfNotNull("Limit", query.Limit); queryString.AddIfNotNull("IsFavorite", query.IsFavorite); - var url = GetApiUrl("Channels", queryString); + var url = GetApiUrl(new Uri("Channels", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2292,12 +2225,14 @@ public async Task> GetAdditionalParts(string itemId, st } } - public async Task GetCurrentSessionAsync(CancellationToken cancellationToken = default(CancellationToken)) + public async Task GetCurrentSessionAsync(CancellationToken cancellationToken = default) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection + { + { "DeviceId", DeviceId } + }; - queryString.Add("DeviceId", DeviceId); - var url = GetApiUrl("Sessions", queryString); + var url = GetApiUrl(new Uri("Sessions", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2309,11 +2244,13 @@ public async Task> GetAdditionalParts(string itemId, st public Task StopTranscodingProcesses(string deviceId, string playSessionId) { - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection + { + { "DeviceId", DeviceId } + }; - queryString.Add("DeviceId", DeviceId); queryString.AddIfNotNullOrEmpty("PlaySessionId", playSessionId); - var url = GetApiUrl("Videos/ActiveEncodings", queryString); + Uri url = GetApiUrl(new Uri("Videos/ActiveEncodings", UriKind.Relative), queryString); return SendAsync(new HttpRequest { @@ -2323,7 +2260,7 @@ public Task StopTranscodingProcesses(string deviceId, string playSessionId) }); } - public async Task> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken = default) { if (query == null) { @@ -2335,10 +2272,14 @@ public Task StopTranscodingProcesses(string deviceId, string playSessionId) throw new ArgumentNullException("userId"); } - var queryString = new QueryStringDictionary(); - queryString.Add("UserId", query.UserId); + var queryString = new NameValueCollection + { + { "UserId", query.UserId } + }; + queryString.AddIfNotNull("StartIndex", query.StartIndex); queryString.AddIfNotNull("Limit", query.Limit); + if (query.Filters != null) { queryString.Add("Filters", query.Filters.Select(f => f.ToString())); @@ -2350,7 +2291,7 @@ public Task StopTranscodingProcesses(string deviceId, string playSessionId) queryString.AddIfNotNull("ChannelIds", query.ChannelIds); - var url = GetApiUrl("Channels/Items/Latest"); + var url = GetApiUrl(new Uri("Channels/Items/Latest", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url, CancellationToken.None).ConfigureAwait(false)) { @@ -2362,44 +2303,32 @@ public async Task Logout() { try { - var url = GetApiUrl("Sessions/Logout"); + var url = GetApiUrl(new Uri("Sessions/Logout", UriKind.Relative)); - await PostAsync(url, new Dictionary(), CancellationToken.None); + await PostAsync(url, new NameValueCollection(), CancellationToken.None); } catch (Exception ex) { - Logger.ErrorException("Error logging out", ex); + Logger.LogError("Error logging out", ex); } ClearAuthenticationInfo(); } - public async Task> GetUserViews(string userId, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetUserViews(string userId, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(userId)) { throw new ArgumentNullException("userId"); } - var url = GetApiUrl("Users/" + userId + "/Views"); + var url = GetApiUrl(new Uri("Users/" + userId + "/Views", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { var result = DeserializeFromStream>(stream); var serverInfo = ServerInfo; - if (serverInfo != null && _localAssetManager != null) - { - var offlineView = await GetOfflineView(serverInfo.Id, userId).ConfigureAwait(false); - - if (offlineView != null) - { - var list = result.Items.ToList(); - list.Add(offlineView); - result.Items = list.ToArray(); - result.TotalRecordCount = list.Count; - } - } return result; } @@ -2412,15 +2341,15 @@ public async Task GetLatestItems(LatestItemsQuery query) throw new ArgumentNullException("query"); } - if (string.IsNullOrEmpty(query.UserId)) + if (string.IsNullOrEmpty(query.UserId.ToString("N", CultureInfo.InvariantCulture))) { throw new ArgumentNullException("userId"); } - var queryString = new QueryStringDictionary(); + var queryString = new NameValueCollection(); queryString.AddIfNotNull("GroupItems", query.GroupItems); queryString.AddIfNotNull("IncludeItemTypes", query.IncludeItemTypes); - queryString.AddIfNotNullOrEmpty("ParentId", query.ParentId); + queryString.AddIfNotNullOrEmpty("ParentId", query.ParentId.ToString("N", CultureInfo.InvariantCulture)); queryString.AddIfNotNull("IsPlayed", query.IsPlayed); queryString.AddIfNotNull("StartIndex", query.StartIndex); queryString.AddIfNotNull("Limit", query.Limit); @@ -2430,7 +2359,7 @@ public async Task GetLatestItems(LatestItemsQuery query) queryString.Add("fields", query.Fields.Select(f => f.ToString())); } - var url = GetApiUrl("Users/" + query.UserId + "/Items/Latest", queryString); + var url = GetApiUrl(new Uri("Users/" + query.UserId + "/Items/Latest", UriKind.Relative), queryString); using (var stream = await GetSerializedStreamAsync(url, CancellationToken.None).ConfigureAwait(false)) { @@ -2438,24 +2367,6 @@ public async Task GetLatestItems(LatestItemsQuery query) } } - private async Task GetOfflineView(string serverId, string userId) - { - var views = await _localAssetManager.GetViews(serverId, userId).ConfigureAwait(false); - - if (views.Count > 0) - { - return new BaseItemDto - { - Name = "AnyTime", - ServerId = serverId, - Id = "OfflineView", - Type = "OfflineView" - }; - } - - return null; - } - public Task AddToPlaylist(string playlistId, IEnumerable itemIds, string userId) { if (playlistId == null) @@ -2468,16 +2379,16 @@ public Task AddToPlaylist(string playlistId, IEnumerable itemIds, string throw new ArgumentNullException("itemIds"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNull("Ids", itemIds); - var url = GetApiUrl(string.Format("Playlists/{0}/Items", playlistId), dict); - return PostAsync(url, new Dictionary(), CancellationToken.None); + var url = GetApiUrl(new Uri(string.Format("Playlists/{0}/Items", playlistId), UriKind.Relative), dict); + return PostAsync(url, new NameValueCollection(), CancellationToken.None); } public async Task CreatePlaylist(PlaylistCreationRequest request) { - if (string.IsNullOrEmpty(request.UserId)) + if (string.IsNullOrEmpty(request.UserId.ToString("N", CultureInfo.InvariantCulture))) { throw new ArgumentNullException("userId"); } @@ -2487,20 +2398,21 @@ public async Task CreatePlaylist(PlaylistCreationRequest throw new ArgumentNullException("must provide either MediaType or Ids"); } - var queryString = new QueryStringDictionary(); - - queryString.Add("UserId", request.UserId); - queryString.Add("Name", request.Name); + var queryString = new NameValueCollection + { + { "UserId", request.UserId.ToString("N", CultureInfo.InvariantCulture) }, + { "Name", request.Name } + }; if (!string.IsNullOrEmpty(request.MediaType)) queryString.Add("MediaType", request.MediaType); if (request.ItemIdList != null && request.ItemIdList.Any()) - queryString.Add("Ids", request.ItemIdList); + queryString.Add("Ids", request.ItemIdList.Select(o => o.ToString("N", CultureInfo.InvariantCulture)).ToList()); - var url = GetApiUrl("Playlists/", queryString); + var url = GetApiUrl(new Uri("Playlists/", UriKind.Relative), queryString); - return await PostAsync(url, new Dictionary(), CancellationToken.None); + return await PostAsync(url, new NameValueCollection(), CancellationToken.None); } @@ -2511,7 +2423,7 @@ public async Task> GetPlaylistItems(PlaylistItemQuery q throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNull("StartIndex", query.StartIndex); @@ -2523,7 +2435,7 @@ public async Task> GetPlaylistItems(PlaylistItemQuery q dict.Add("fields", query.Fields.Select(f => f.ToString())); } - var url = GetApiUrl("Playlists/" + query.Id + "/Items", dict); + var url = GetApiUrl(new Uri("Playlists/" + query.Id + "/Items", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -2543,20 +2455,20 @@ public Task RemoveFromPlaylist(string playlistId, IEnumerable entryIds) throw new ArgumentNullException("entryIds"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNull("EntryIds", entryIds); - var url = GetApiUrl(string.Format("Playlists/{0}/Items", playlistId), dict); + var url = GetApiUrl(new Uri(string.Format("Playlists/{0}/Items", playlistId), UriKind.Relative), dict); return DeleteAsync(url, CancellationToken.None); } public async Task GetContentUploadHistory(string deviceId) { - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.Add("DeviceId", deviceId); - var url = GetApiUrl("Devices/CameraUploads", dict); + var url = GetApiUrl(new Uri("Devices/CameraUploads", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -2564,16 +2476,16 @@ public async Task GetContentUploadHistory(string deviceId) } } - public async Task UploadFile(Stream stream, LocalFileInfo file, CancellationToken cancellationToken = default(CancellationToken)) + public async Task UploadFile(Stream stream, LocalFileInfo file, CancellationToken cancellationToken = default) { - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.Add("DeviceId", DeviceId); dict.Add("Name", file.Name); dict.Add("Id", file.Id); dict.AddIfNotNullOrEmpty("Album", file.Album); - var url = GetApiUrl("Devices/CameraUploads", dict); + var url = GetApiUrl(new Uri("Devices/CameraUploads", UriKind.Relative), dict); using (stream) { @@ -2592,9 +2504,9 @@ await SendAsync(new HttpRequest public async Task GetDevicesOptions() { - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; - var url = GetApiUrl("System/Configuration/devices", dict); + var url = GetApiUrl(new Uri("System/Configuration/devices", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -2604,11 +2516,11 @@ public async Task GetDevicesOptions() public async Task GetPlaybackInfo(PlaybackInfoRequest request) { - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; - dict.AddIfNotNullOrEmpty("UserId", request.UserId); + dict.AddIfNotNullOrEmpty("UserId", request.UserId.ToString("N", CultureInfo.InvariantCulture)); - var url = GetApiUrl("Items/" + request.Id + "/PlaybackInfo", dict); + var url = GetApiUrl(new Uri("Items/" + request.Id + "/PlaybackInfo", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -2626,107 +2538,6 @@ public Task UpdateItem(BaseItemDto item) throw new NotImplementedException(); } - public Task CreateSyncJob(SyncJobRequest request) - { - if (request == null) - { - throw new ArgumentNullException("request"); - } - - var url = GetApiUrl("Sync/Jobs"); - - return PostAsync(url, request, CancellationToken.None); - } - - public Task UpdateSyncJob(SyncJob job) - { - if (job == null) - { - throw new ArgumentNullException("job"); - } - - var url = GetApiUrl("Sync/Jobs/" + job.Id); - - return PostAsync(url, job, CancellationToken.None); - } - - public async Task> GetSyncJobItems(SyncJobItemQuery query) - { - var dict = new QueryStringDictionary { }; - - dict.AddIfNotNullOrEmpty("JobId", query.JobId); - dict.AddIfNotNull("Limit", query.Limit); - dict.AddIfNotNull("StartIndex", query.StartIndex); - dict.AddIfNotNullOrEmpty("TargetId", query.TargetId); - dict.AddIfNotNull("AddMetadata", query.AddMetadata); - - if (query.Statuses.Length > 0) - { - dict.Add("Statuses", string.Join(",", query.Statuses.Select(i => i.ToString()).ToArray())); - } - - var url = GetApiUrl("Sync/JobItems", dict); - - using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) - { - return DeserializeFromStream>(stream); - } - } - - public async Task> GetSyncJobs(SyncJobQuery query) - { - var dict = new QueryStringDictionary { }; - - dict.AddIfNotNull("Limit", query.Limit); - dict.AddIfNotNull("StartIndex", query.StartIndex); - dict.AddIfNotNull("SyncNewContent", query.SyncNewContent); - dict.AddIfNotNullOrEmpty("TargetId", query.TargetId); - - if (query.Statuses.Length > 0) - { - dict.Add("Statuses", string.Join(",", query.Statuses.Select(i => i.ToString()).ToArray())); - } - - var url = GetApiUrl("Sync/Jobs", dict); - - using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) - { - return DeserializeFromStream>(stream); - } - } - - public Task ReportSyncJobItemTransferred(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - var url = GetApiUrl("Sync/JobItems/" + id + "/Transferred"); - - return PostAsync(url, new Dictionary()); - } - - public Task GetSyncJobItemFile(string id, CancellationToken cancellationToken = default(CancellationToken)) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - return GetStream(GetSyncJobItemFileUrl(id), cancellationToken); - } - - public string GetSyncJobItemFileUrl(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - return GetApiUrl("Sync/JobItems/" + id + "/File"); - } - public Task UpdateUserConfiguration(string userId, UserConfiguration configuration) { if (configuration == null) @@ -2734,107 +2545,11 @@ public Task UpdateUserConfiguration(string userId, UserConfiguration configurati throw new ArgumentNullException("configuration"); } - var url = GetApiUrl("Users/" + userId + "/Configuration"); + var url = GetApiUrl(new Uri("Users/" + userId + "/Configuration", UriKind.Relative)); return PostAsync(url, configuration, CancellationToken.None); } - public Task ReportOfflineActions(List actions) - { - if (actions == null || actions.Count == 0) - { - throw new ArgumentNullException("actions"); - } - - var url = GetApiUrl("Sync/OfflineActions"); - - return PostAsync, EmptyRequestResult>(url, actions, CancellationToken.None); - } - - public async Task> GetReadySyncItems(string targetId) - { - var dict = new QueryStringDictionary { }; - - dict.AddIfNotNullOrEmpty("TargetId", targetId); - - var url = GetApiUrl("Sync/Items/Ready", dict); - - using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) - { - return DeserializeFromStream>(stream); - } - } - - public Task SyncData(SyncDataRequest request) - { - var url = GetApiUrl("Sync/Data"); - - return PostAsync(url, request, CancellationToken.None); - } - - public Task GetSyncJobItemAdditionalFile(string id, string name, CancellationToken cancellationToken = default(CancellationToken)) - { - var dict = new QueryStringDictionary { }; - - dict.AddIfNotNullOrEmpty("Name", name); - - var url = GetApiUrl("Sync/JobItems/" + id + "/AdditionalFiles", dict); - - return GetStream(url, cancellationToken); - } - - public Task CancelSyncJob(string id) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - var url = GetApiUrl("Sync/Jobs/" + id); - - return DeleteAsync(url, CancellationToken.None); - } - - public Task CancelSyncJobItem(string id) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - var url = GetApiUrl("Sync/JobItems/" + id); - - return DeleteAsync(url, CancellationToken.None); - } - - public Task EnableCancelledSyncJobItem(string id) - { - var url = GetApiUrl("Sync/JobItems/" + id + "/Enable"); - - return PostAsync(url, new QueryStringDictionary(), CancellationToken.None); - } - - public Task MarkSyncJobItemForRemoval(string id) - { - var url = GetApiUrl("Sync/JobItems/" + id + "/MarkForRemoval"); - - return PostAsync(url, new QueryStringDictionary(), CancellationToken.None); - } - - public Task QueueFailedSyncJobItemForRetry(string id) - { - var url = GetApiUrl("Sync/JobItems/" + id + "/Enable"); - - return PostAsync(url, new QueryStringDictionary(), CancellationToken.None); - } - - public Task UnmarkSyncJobItemForRemoval(string id) - { - var url = GetApiUrl("Sync/JobItems/" + id + "/UnmarkForRemoval"); - - return PostAsync(url, new QueryStringDictionary(), CancellationToken.None); - } - public async Task GetOfflineUserAsync(string id) { if (string.IsNullOrEmpty(id)) @@ -2842,7 +2557,7 @@ public async Task GetOfflineUserAsync(string id) throw new ArgumentNullException("id"); } - var url = GetApiUrl("Users/" + id + "/Offline"); + var url = GetApiUrl(new Uri("Users/" + id + "/Offline", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -2850,69 +2565,9 @@ public async Task GetOfflineUserAsync(string id) } } - public async Task GetSyncOptions(SyncJobRequest jobInfo) - { - var dict = new QueryStringDictionary(); - - dict.AddIfNotNullOrEmpty("UserId", jobInfo.UserId); - dict.AddIfNotNullOrEmpty("ParentId", jobInfo.ParentId); - dict.AddIfNotNullOrEmpty("TargetId", jobInfo.TargetId); - - if (jobInfo.Category.HasValue) - { - dict.AddIfNotNullOrEmpty("Category", jobInfo.Category.Value.ToString()); - } - - if (jobInfo.ItemIds != null) - { - var list = jobInfo.ItemIds.ToList(); - if (list.Count > 0) - { - dict.Add("ItemIds", list); - } - } - - var url = GetApiUrl("Sync/Options", dict); - - using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) - { - return DeserializeFromStream(stream); - } - } - - public async Task GetSyncOptions(SyncJob jobInfo) - { - var dict = new QueryStringDictionary(); - - dict.AddIfNotNullOrEmpty("UserId", jobInfo.UserId); - dict.AddIfNotNullOrEmpty("ParentId", jobInfo.ParentId); - dict.AddIfNotNullOrEmpty("TargetId", jobInfo.TargetId); - - if (jobInfo.Category.HasValue) - { - dict.AddIfNotNullOrEmpty("Category", jobInfo.Category.Value.ToString()); - } - - if (jobInfo.RequestedItemIds != null) - { - var list = jobInfo.RequestedItemIds.ToList(); - if (list.Count > 0) - { - dict.Add("ItemIds", list); - } - } - - var url = GetApiUrl("Sync/Options", dict); - - using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) - { - return DeserializeFromStream(stream); - } - } - public async Task> GetMovieRecommendations(MovieRecommendationQuery query) { - var dict = new QueryStringDictionary(); + var dict = new NameValueCollection(); dict.AddIfNotNullOrEmpty("UserId", query.UserId); dict.AddIfNotNullOrEmpty("ParentId", query.ParentId); @@ -2924,7 +2579,7 @@ public async Task> GetMovieRecommendations(MovieRecommen dict.Add("fields", query.Fields.Select(f => f.ToString())); } - var url = GetApiUrl("Movies/Recommendations", dict); + var url = GetApiUrl(new Uri("Movies/Recommendations", UriKind.Relative), dict); using (var stream = await GetSerializedStreamAsync(url).ConfigureAwait(false)) { @@ -2934,22 +2589,11 @@ public async Task> GetMovieRecommendations(MovieRecommen public Task OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) { - var url = GetApiUrl("LiveStreams/Open"); + var url = GetApiUrl(new Uri("LiveStreams/Open", UriKind.Relative)); return PostAsync(url, request, cancellationToken); } - public Task CancelSyncLibraryItems(string targetId, IEnumerable itemIds) - { - var dict = new QueryStringDictionary(); - - dict.Add("ItemIds", itemIds); - - var url = GetApiUrl("Sync/" + targetId + "/Items", dict); - - return DeleteAsync(url, CancellationToken.None); - } - public Task DetectMaxBitrate(CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -2957,7 +2601,7 @@ public Task DetectMaxBitrate(CancellationToken cancellationToken) public async Task GetEndPointInfo(CancellationToken cancellationToken) { - var url = GetApiUrl("System/Endpoint"); + var url = GetApiUrl(new Uri("System/Endpoint", UriKind.Relative)); using (var stream = await GetSerializedStreamAsync(url, cancellationToken).ConfigureAwait(false)) { @@ -2980,7 +2624,7 @@ public async Task> GetInstantMixFromItemAsync(SimilarIt } } - public async Task> GetSimilarItemsAsync(SimilarItemsQuery query, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetSimilarItemsAsync(SimilarItemsQuery query, CancellationToken cancellationToken = default) { if (query == null) { diff --git a/Emby.ApiClient/ApiWebSocket.cs b/Jellyfin.ApiClient/ApiWebSocket.cs similarity index 83% rename from Emby.ApiClient/ApiWebSocket.cs rename to Jellyfin.ApiClient/ApiWebSocket.cs index 6fe1ec2..4556b66 100644 --- a/Emby.ApiClient/ApiWebSocket.cs +++ b/Jellyfin.ApiClient/ApiWebSocket.cs @@ -1,15 +1,16 @@ -using MediaBrowser.Model.ApiClient; +using Jellyfin.ApiClient.Model; +using Jellyfin.ApiClient.Net; +using Jellyfin.ApiClient.WebSocket; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; -using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Sync; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Updates; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Globalization; @@ -17,10 +18,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.ApiClient.Model; -using Newtonsoft.Json; -namespace Emby.ApiClient +namespace Jellyfin.ApiClient { public partial class ApiClient : IServerEvents, IDisposable { @@ -58,10 +57,6 @@ public partial class ApiClient : IServerEvents, IDisposable public event EventHandler> PlaybackStart; public event EventHandler> PlaybackStopped; public event EventHandler> SessionEnded; - public event EventHandler> SyncJobCreated; - public event EventHandler> SyncJobCancelled; - public event EventHandler>> SyncJobsUpdated; - public event EventHandler> SyncJobUpdated; /// /// The _web socket @@ -108,17 +103,17 @@ private async Task EnsureConnectionAsync(CancellationToken cancellationToken) { var socket = _webSocketFactory(); - Logger.Info("Created new web socket of type {0}", socket.GetType().Name); + Logger.LogInformation("Created new web socket of type {0}", socket.GetType().Name); - Logger.Info("Connecting to {0}", url); + Logger.LogInformation("Connecting to {0}", url); await socket.ConnectAsync(url, cancellationToken).ConfigureAwait(false); - Logger.Info("Connected to {0}", url); + Logger.LogInformation("Connected to {0}", url); socket.OnReceiveBytes = OnMessageReceived; socket.OnReceive = OnMessageReceived; - socket.Closed += _currentWebSocket_Closed; + socket.Closed += OnCurrentWebSocketClosed; //try //{ @@ -135,7 +130,7 @@ private async Task EnsureConnectionAsync(CancellationToken cancellationToken) } catch (Exception ex) { - Logger.ErrorException("Error connecting to {0}", ex, url); + Logger.LogError("Error connecting to {0}", ex, url); } } } @@ -161,14 +156,11 @@ private void ReplaceSocket(IClientWebSocket socket) /// /// The source of the event. /// The instance containing the event data. - void _currentWebSocket_Closed(object sender, EventArgs e) + void OnCurrentWebSocketClosed(object sender, EventArgs e) { - Logger.Warn("Web socket connection closed."); + Logger.LogWarning("Web socket connection closed."); - if (WebSocketClosed != null) - { - WebSocketClosed(this, EventArgs.Empty); - } + WebSocketClosed?.Invoke(this, EventArgs.Empty); } /// @@ -201,7 +193,7 @@ public async Task SendWebSocketMessage(string messageName, T data, Cancellati } catch (Exception ex) { - Logger.ErrorException("Error sending web socket message", ex); + Logger.LogError("Error sending web socket message", ex); throw; } @@ -216,7 +208,7 @@ public async Task SendWebSocketMessage(string messageName, T data, Cancellati /// An optional, client-specific value indicating the area or section being browsed /// The cancellation token. /// Task. - public Task SendContextMessageAsync(string itemType, string itemId, string itemName, string context, CancellationToken cancellationToken = default(CancellationToken)) + public Task SendContextMessageAsync(string itemType, string itemId, string itemName, string context, CancellationToken cancellationToken = default) { var vals = new List { @@ -252,37 +244,6 @@ public Task StopReceivingSessionUpdates() return SendWebSocketMessage("SessionsStop", string.Empty); } - public Task StartReceivingSyncJobsUpdates(int intervalMs, string userId, string targetId) - { - var options = new List(); - options.Add(intervalMs.ToString(CultureInfo.InvariantCulture)); - options.Add(intervalMs.ToString(CultureInfo.InvariantCulture)); - options.Add(userId ?? string.Empty); - options.Add(targetId ?? string.Empty); - - return SendWebSocketMessage("SyncJobsStart", string.Join(",", options.ToArray())); - } - - public Task StopReceivingSyncJobsUpdates() - { - return SendWebSocketMessage("SyncJobsStop", string.Empty); - } - - public Task StartReceivingSyncJobUpdates(int intervalMs, string jobId) - { - var options = new List(); - options.Add(intervalMs.ToString(CultureInfo.InvariantCulture)); - options.Add(intervalMs.ToString(CultureInfo.InvariantCulture)); - options.Add(jobId ?? string.Empty); - - return SendWebSocketMessage("SyncJobStart", string.Join(",", options.ToArray())); - } - - public Task StopReceivingSyncJobUpdates() - { - return SendWebSocketMessage("SyncJobStop", string.Empty); - } - protected override void Dispose(bool disposing) { base.Dispose(disposing); @@ -299,7 +260,7 @@ private void DisposeWebSocket() if (socket != null) { - Logger.Debug("Disposing client web socket"); + Logger.LogDebug("Disposing client web socket"); try { @@ -307,7 +268,7 @@ private void DisposeWebSocket() } catch (Exception ex) { - Logger.ErrorException("Error disposing web socket {0}", ex); + Logger.LogError("Error disposing web socket {0}", ex); } _currentWebSocket = null; } @@ -318,14 +279,20 @@ private void DisposeWebSocket() /// /// The server address. /// System.String. - protected string GetWebSocketUrl(string serverAddress) + protected Uri GetWebSocketUrl(Uri serverAddress) { if (string.IsNullOrWhiteSpace(AccessToken)) { throw new ArgumentException("Cannot open web socket without an access token."); } - return serverAddress.Replace("http:", "ws:").Replace("https:", "wss:") + "/embywebsocket?api_key=" + AccessToken + "&deviceId=" + DeviceId; + var uriBuilder = new UriBuilder(new Uri(serverAddress, "/socket")) + { + Scheme = serverAddress.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.InvariantCultureIgnoreCase) ? "wss" : "ws", + Query = "?api_key=" + AccessToken + "&deviceId=" + DeviceId, + }; + + return uriBuilder.Uri; } /// @@ -351,7 +318,7 @@ protected void OnMessageReceived(string json) } catch (Exception ex) { - Logger.ErrorException("Error in OnMessageReceivedInternal", ex); + Logger.LogError("Error in OnMessageReceivedInternal", ex); } } @@ -360,7 +327,7 @@ private void OnMessageReceivedInternal(string json) // deserialize the WebSocketMessage with an object payload var messageType = GetMessageType(json); - Logger.Info("Received web socket message: {0}", messageType); + Logger.LogInformation("Received web socket message: {0}", messageType); if (string.Equals(messageType, "LibraryChanged")) { @@ -476,20 +443,6 @@ private void OnMessageReceivedInternal(string json) Sessions = JsonSerializer.DeserializeFromString>(json).Data })); } - else if (string.Equals(messageType, "SyncJobs")) - { - FireEvent(SyncJobsUpdated, this, new GenericEventArgs> - { - Argument = JsonSerializer.DeserializeFromString>>(json).Data - }); - } - else if (string.Equals(messageType, "SyncJob")) - { - FireEvent(SyncJobUpdated, this, new GenericEventArgs - { - Argument = JsonSerializer.DeserializeFromString>(json).Data - }); - } else if (string.Equals(messageType, "UserDataChanged")) { FireEvent(UserDataChanged, this, new GenericEventArgs @@ -504,20 +457,6 @@ private void OnMessageReceivedInternal(string json) Argument = JsonSerializer.DeserializeFromString>(json).Data }); } - else if (string.Equals(messageType, "SyncJobCreated")) - { - FireEvent(SyncJobCreated, this, new GenericEventArgs - { - Argument = JsonSerializer.DeserializeFromString>(json).Data - }); - } - else if (string.Equals(messageType, "SyncJobCancelled")) - { - FireEvent(SyncJobCancelled, this, new GenericEventArgs - { - Argument = JsonSerializer.DeserializeFromString>(json).Data - }); - } else if (string.Equals(messageType, "PlaybackStart")) { FireEvent(PlaybackStart, this, new GenericEventArgs @@ -554,13 +493,10 @@ private void OnGeneralCommand(string json) { if (args.KnownCommandType.Value == GeneralCommandType.DisplayContent) { - string itemId; - string itemName; - string itemType; - args.Command.Arguments.TryGetValue("ItemId", out itemId); - args.Command.Arguments.TryGetValue("ItemName", out itemName); - args.Command.Arguments.TryGetValue("ItemType", out itemType); + args.Command.Arguments.TryGetValue("ItemId", out string itemId); + args.Command.Arguments.TryGetValue("ItemName", out string itemName); + args.Command.Arguments.TryGetValue("ItemType", out string itemType); FireEvent(BrowseCommand, this, new GenericEventArgs { @@ -575,15 +511,12 @@ private void OnGeneralCommand(string json) } if (args.KnownCommandType.Value == GeneralCommandType.DisplayMessage) { - string header; - string text; - string timeoutMs; - args.Command.Arguments.TryGetValue("Header", out header); - args.Command.Arguments.TryGetValue("Text", out text); - args.Command.Arguments.TryGetValue("TimeoutMs", out timeoutMs); + args.Command.Arguments.TryGetValue("Header", out string header); + args.Command.Arguments.TryGetValue("Text", out string text); + args.Command.Arguments.TryGetValue("TimeoutMs", out string timeoutMs); - long? timeoutVal = string.IsNullOrEmpty(timeoutMs) ? (long?)null : long.Parse(timeoutMs, CultureInfo.InvariantCulture); + long? timeoutVal = string.IsNullOrEmpty(timeoutMs) ? (long?)null : long.Parse(timeoutMs); FireEvent(MessageCommand, this, new GenericEventArgs { @@ -598,9 +531,8 @@ private void OnGeneralCommand(string json) } if (args.KnownCommandType.Value == GeneralCommandType.SetVolume) { - string volume; - args.Command.Arguments.TryGetValue("Volume", out volume); + args.Command.Arguments.TryGetValue("Volume", out string volume); FireEvent(SetVolumeCommand, this, new GenericEventArgs { @@ -610,9 +542,8 @@ private void OnGeneralCommand(string json) } if (args.KnownCommandType.Value == GeneralCommandType.SetAudioStreamIndex) { - string index; - args.Command.Arguments.TryGetValue("Index", out index); + args.Command.Arguments.TryGetValue("Index", out string index); FireEvent(SetAudioStreamIndexCommand, this, new GenericEventArgs { @@ -622,9 +553,8 @@ private void OnGeneralCommand(string json) } if (args.KnownCommandType.Value == GeneralCommandType.SetSubtitleStreamIndex) { - string index; - args.Command.Arguments.TryGetValue("Index", out index); + args.Command.Arguments.TryGetValue("Index", out string index); FireEvent(SetSubtitleStreamIndexCommand, this, new GenericEventArgs { @@ -634,9 +564,8 @@ private void OnGeneralCommand(string json) } if (args.KnownCommandType.Value == GeneralCommandType.SendString) { - string val; - args.Command.Arguments.TryGetValue("String", out val); + args.Command.Arguments.TryGetValue("String", out string val); FireEvent(SendStringCommand, this, new GenericEventArgs { @@ -704,7 +633,7 @@ protected void FireEvent(EventHandler handler, object sender, T args) } catch (Exception ex) { - Logger.ErrorException("Error in event handler", ex); + Logger.LogError("Error in event handler", ex); } } } diff --git a/Emby.ApiClient/BaseApiClient.cs b/Jellyfin.ApiClient/BaseApiClient.cs similarity index 85% rename from Emby.ApiClient/BaseApiClient.cs rename to Jellyfin.ApiClient/BaseApiClient.cs index 6eae1bb..767be5c 100644 --- a/Emby.ApiClient/BaseApiClient.cs +++ b/Jellyfin.ApiClient/BaseApiClient.cs @@ -1,18 +1,20 @@ -using MediaBrowser.Model.ApiClient; +using Jellyfin.ApiClient.Model; +using Jellyfin.ApiClient.Model.Dto; +using Jellyfin.ApiClient.Model.Querying; +using Jellyfin.ApiClient.Net; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.LiveTv; -using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; using System.IO; using System.Linq; -using Emby.ApiClient.Model; -using Emby.ApiClient.Net; +using System.Web; -namespace Emby.ApiClient +namespace Jellyfin.ApiClient { /// /// Provides api methods that are usable on all platforms @@ -38,48 +40,46 @@ public abstract class BaseApiClient : IDisposable /// public int? ImageQuality { get; set; } - protected BaseApiClient(ILogger logger, IJsonSerializer jsonSerializer, string serverAddress, string clientName, IDevice device, string applicationVersion) + protected BaseApiClient(ILogger logger, IJsonSerializer jsonSerializer, Uri serverAddress, string clientName, IDevice device, string applicationVersion) { - if (logger == null) + if (serverAddress == null) { - throw new ArgumentNullException("logger"); + throw new ArgumentNullException("serverAddress"); } + if (jsonSerializer == null) { throw new ArgumentNullException("jsonSerializer"); } - if (string.IsNullOrEmpty(serverAddress)) + + if (logger == null) { - throw new ArgumentNullException("serverAddress"); + throw new ArgumentNullException("logger"); } - JsonSerializer = jsonSerializer; - Logger = logger; - ClientName = clientName; Device = device; ApplicationVersion = applicationVersion; ServerAddress = serverAddress; } - protected BaseApiClient(ILogger logger, IJsonSerializer jsonSerializer, string serverAddress, string accessToken) + protected BaseApiClient(ILogger logger, IJsonSerializer jsonSerializer, Uri serverAddress, string accessToken) { - if (logger == null) + if (serverAddress == null) { - throw new ArgumentNullException("logger"); + throw new ArgumentNullException("serverAddress"); } + if (jsonSerializer == null) { throw new ArgumentNullException("jsonSerializer"); } - if (string.IsNullOrEmpty(serverAddress)) + + if (logger == null) { - throw new ArgumentNullException("serverAddress"); + throw new ArgumentNullException("logger"); } - JsonSerializer = jsonSerializer; - Logger = logger; - AccessToken = accessToken; ServerAddress = serverAddress; } @@ -88,20 +88,20 @@ protected BaseApiClient(ILogger logger, IJsonSerializer jsonSerializer, string s /// Gets the name of the server host. /// /// The name of the server host. - public string ServerAddress { get; protected set; } + public Uri ServerAddress { get; protected set; } /// /// Changes the server location. /// /// The address. /// - public void ChangeServerLocation(string address, bool keepExistingAuth = false) + public void ChangeServerLocation(Uri address, bool keepExistingAuth = false) { ServerAddress = address; if (!keepExistingAuth) { - SetAuthenticationInfo(null, null); + SetAuthenticationInfo(null, Guid.Empty); } } @@ -146,17 +146,17 @@ public string DeviceId /// Gets or sets the current user id. /// /// The current user id. - public string CurrentUserId { get; private set; } + public Guid CurrentUserId { get; private set; } /// /// Gets the current api url based on hostname and port. /// /// The API URL. - public string ApiUrl + public Uri ApiUrl { get { - return ServerAddress + "/emby"; + return new Uri(ServerAddress, "/emby"); } } @@ -184,7 +184,7 @@ protected string AuthorizationParameter var header = string.Format("Client=\"{0}\", DeviceId=\"{1}\", Device=\"{2}\", Version=\"{3}\"", ClientName, DeviceId, DeviceName, ApplicationVersion); - if (!string.IsNullOrEmpty(CurrentUserId)) + if (CurrentUserId != Guid.Empty) { header += string.Format(", UserId=\"{0}\"", CurrentUserId); } @@ -199,12 +199,12 @@ protected string AuthorizationParameter /// The handler. /// System.String. /// handler - public string GetApiUrl(string handler) + public Uri GetApiUrl(Uri handler) { - return GetApiUrl(handler, new QueryStringDictionary()); + return GetApiUrl(handler, new NameValueCollection()); } - public void SetAuthenticationInfo(string accessToken, string userId) + public void SetAuthenticationInfo(string accessToken, Guid userId) { CurrentUserId = userId; AccessToken = accessToken; @@ -213,14 +213,14 @@ public void SetAuthenticationInfo(string accessToken, string userId) public void ClearAuthenticationInfo() { - CurrentUserId = null; + CurrentUserId = Guid.Empty; AccessToken = null; ResetHttpHeaders(); } public void SetAuthenticationInfo(string accessToken) { - CurrentUserId = null; + CurrentUserId = Guid.Empty; AccessToken = accessToken; ResetHttpHeaders(); } @@ -259,24 +259,24 @@ private void ClearHttpRequestHeader(string name) /// /// The handler. /// The query string. - /// System.String. + /// System.Uri. /// handler - protected string GetApiUrl(string handler, QueryStringDictionary queryString) + protected Uri GetApiUrl(Uri handler, NameValueCollection queryString) { - if (string.IsNullOrEmpty(handler)) + if (handler == null) { throw new ArgumentNullException("handler"); } - if (queryString == null) + var uriBuilder = new UriBuilder(new Uri(ApiUrl, handler)) { - throw new ArgumentNullException("queryString"); - } + Query = queryString.ToQueryString(), + }; - return queryString.GetUrl(ApiUrl + "/" + handler); + return uriBuilder.Uri; } - public string GetSubtitleUrl(SubtitleDownloadOptions options) + public Uri GetSubtitleUrl(SubtitleDownloadOptions options) { if (options == null) { @@ -295,7 +295,7 @@ public string GetSubtitleUrl(SubtitleDownloadOptions options) throw new ArgumentNullException("options"); } - return GetApiUrl("Videos/" + options.ItemId + "/" + options.MediaSourceId + "/Subtitles/" + options.StreamIndex + "/Stream." + options.Format); + return GetApiUrl(new Uri("Videos/" + options.ItemId + "/" + options.MediaSourceId + "/Subtitles/" + options.StreamIndex + "/Stream." + options.Format, UriKind.Relative)); } /// @@ -304,14 +304,14 @@ public string GetSubtitleUrl(SubtitleDownloadOptions options) /// The query. /// System.String. /// query - protected string GetItemListUrl(ItemQuery query) + protected Uri GetItemListUrl(ItemQuery query) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("ParentId", query.ParentId); @@ -418,7 +418,7 @@ protected string GetItemListUrl(ItemQuery query) dict.AddIfNotNull("AiredDuringSeason", query.AiredDuringSeason); - return GetApiUrl("Users/" + query.UserId + "/Items", dict); + return GetApiUrl(new Uri("Users/" + query.UserId + "/Items", UriKind.Relative), dict); } /// @@ -427,14 +427,14 @@ protected string GetItemListUrl(ItemQuery query) /// The query. /// System.String. /// query - protected string GetNextUpUrl(NextUpQuery query) + protected Uri GetNextUpUrl(NextUpQuery query) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; if (query.Fields != null) { @@ -449,7 +449,7 @@ protected string GetNextUpUrl(NextUpQuery query) dict.AddIfNotNullOrEmpty("SeriesId", query.SeriesId); - dict.Add("UserId", query.UserId); + dict.Add("UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture)); dict.AddIfNotNull("EnableImages", query.EnableImages); if (query.EnableImageTypes != null) @@ -458,7 +458,7 @@ protected string GetNextUpUrl(NextUpQuery query) } dict.AddIfNotNull("ImageTypeLimit", query.ImageTypeLimit); - return GetApiUrl("Shows/NextUp", dict); + return GetApiUrl(new Uri("Shows/NextUp", UriKind.Relative), dict); } /// @@ -472,7 +472,7 @@ protected string GetNextUpUrl(NextUpQuery query) /// or /// type /// - protected string GetSimilarItemListUrl(SimilarItemsQuery query, string type) + protected Uri GetSimilarItemListUrl(SimilarItemsQuery query, string type) { if (query == null) { @@ -483,7 +483,7 @@ protected string GetSimilarItemListUrl(SimilarItemsQuery query, string type) throw new ArgumentNullException("type"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNull("Limit", query.Limit); dict.AddIfNotNullOrEmpty("UserId", query.UserId); @@ -498,7 +498,7 @@ protected string GetSimilarItemListUrl(SimilarItemsQuery query, string type) throw new ArgumentNullException("query"); } - return GetApiUrl(type + "/" + query.Id + "/Similar", dict); + return GetApiUrl(new Uri(type + "/" + query.Id + "/Similar", UriKind.Relative), dict); } /// @@ -512,7 +512,7 @@ protected string GetSimilarItemListUrl(SimilarItemsQuery query, string type) /// or /// type /// - protected string GetInstantMixUrl(SimilarItemsQuery query, string type) + protected Uri GetInstantMixUrl(SimilarItemsQuery query, string type) { if (query == null) { @@ -523,7 +523,7 @@ protected string GetInstantMixUrl(SimilarItemsQuery query, string type) throw new ArgumentNullException("type"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNull("Limit", query.Limit); dict.AddIfNotNullOrEmpty("UserId", query.UserId); @@ -538,7 +538,7 @@ protected string GetInstantMixUrl(SimilarItemsQuery query, string type) throw new ArgumentNullException("query"); } - return GetApiUrl(type + "/" + query.Id + "/InstantMix", dict); + return GetApiUrl(new Uri(type + "/" + query.Id + "/InstantMix", UriKind.Relative), dict); } /// @@ -548,14 +548,14 @@ protected string GetInstantMixUrl(SimilarItemsQuery query, string type) /// The query. /// System.String. /// query - protected string GetItemByNameListUrl(string type, ItemsByNameQuery query) + protected Uri GetItemByNameListUrl(string type, ItemsByNameQuery query) { if (query == null) { throw new ArgumentNullException("query"); } - var dict = new QueryStringDictionary { }; + var dict = new NameValueCollection { }; dict.AddIfNotNullOrEmpty("ParentId", query.ParentId); @@ -604,7 +604,7 @@ protected string GetItemByNameListUrl(string type, ItemsByNameQuery query) } dict.AddIfNotNull("ImageTypeLimit", query.ImageTypeLimit); - return GetApiUrl(type, dict); + return GetApiUrl(new Uri(type, UriKind.Relative), dict); } /// @@ -615,7 +615,7 @@ protected string GetItemByNameListUrl(string type, ItemsByNameQuery query) /// The query params. /// System.String. /// options - private string GetImageUrl(string baseUrl, ImageOptions options, QueryStringDictionary queryParams) + private Uri GetImageUrl(Uri baseUrl, ImageOptions options, NameValueCollection queryParams) { if (options == null) { @@ -629,7 +629,7 @@ private string GetImageUrl(string baseUrl, ImageOptions options, QueryStringDict if (options.ImageIndex.HasValue) { - baseUrl += "/" + options.ImageIndex.Value; + baseUrl = new Uri(baseUrl, "/" + options.ImageIndex.Value); } queryParams.AddIfNotNull("Width", options.Width); @@ -665,7 +665,7 @@ private string GetImageUrl(string baseUrl, ImageOptions options, QueryStringDict /// The options. /// System.String. /// item - public string GetImageUrl(BaseItemDto item, ImageOptions options) + public Uri GetImageUrl(BaseItemDto item, ImageOptions options) { if (item == null) { @@ -679,10 +679,10 @@ public string GetImageUrl(BaseItemDto item, ImageOptions options) options.Tag = GetImageTag(item, options); - return GetImageUrl(item.Id, options); + return GetImageUrl(item, options); } - public string GetImageUrl(ChannelInfoDto item, ImageOptions options) + public Uri GetImageUrl(ChannelInfoDto item, ImageOptions options) { if (item == null) { @@ -706,16 +706,16 @@ public string GetImageUrl(ChannelInfoDto item, ImageOptions options) /// The options. /// System.String. /// itemId - public string GetImageUrl(string itemId, ImageOptions options) + public Uri GetImageUrl(string itemId, ImageOptions options) { if (string.IsNullOrEmpty(itemId)) { throw new ArgumentNullException("itemId"); } - var url = "Items/" + itemId + "/Images/" + options.ImageType; + var url = new Uri("Items/" + itemId + "/Images/" + options.ImageType, UriKind.Relative); - return GetImageUrl(url, options, new QueryStringDictionary()); + return GetImageUrl(url, options, new NameValueCollection()); } /// @@ -725,7 +725,7 @@ public string GetImageUrl(string itemId, ImageOptions options) /// The options. /// System.String. /// user - public string GetUserImageUrl(UserDto user, ImageOptions options) + public Uri GetUserImageUrl(UserDto user, ImageOptions options) { if (user == null) { @@ -749,16 +749,16 @@ public string GetUserImageUrl(UserDto user, ImageOptions options) /// The options. /// System.String. /// userId - public string GetUserImageUrl(string userId, ImageOptions options) + public Uri GetUserImageUrl(Guid userId, ImageOptions options) { - if (string.IsNullOrEmpty(userId)) + if (userId.Equals(Guid.Empty)) { throw new ArgumentNullException("userId"); } - var url = "Users/" + userId + "/Images/" + options.ImageType; + var url = new Uri("Users/" + userId.ToString("N", CultureInfo.InvariantCulture) + "/Images/" + options.ImageType, UriKind.Relative); - return GetImageUrl(url, options, new QueryStringDictionary()); + return GetImageUrl(url, options, new NameValueCollection()); } /// @@ -768,7 +768,7 @@ public string GetUserImageUrl(string userId, ImageOptions options) /// The options. /// System.String. /// item - public string GetPersonImageUrl(BaseItemPerson item, ImageOptions options) + public Uri GetPersonImageUrl(BaseItemPerson item, ImageOptions options) { if (item == null) { @@ -818,7 +818,7 @@ private string GetImageTag(BaseItemDto item, ImageOptions options) /// The options. /// System.String[][]. /// item - public string[] GetBackdropImageUrls(BaseItemDto item, ImageOptions options) + public Uri[] GetBackdropImageUrls(BaseItemDto item, ImageOptions options) { if (item == null) { @@ -842,16 +842,16 @@ public string[] GetBackdropImageUrls(BaseItemDto item, ImageOptions options) } else { - backdropItemId = item.Id; + backdropItemId = item.Id.ToString("N", CultureInfo.InvariantCulture); backdropImageTags = item.BackdropImageTags; } if (string.IsNullOrEmpty(backdropItemId)) { - return new string[] { }; + return new Uri[] { }; } - var files = new string[backdropImageTags.Length]; + var files = new Uri[backdropImageTags.Length]; for (var i = 0; i < backdropImageTags.Length; i++) { @@ -871,7 +871,7 @@ public string[] GetBackdropImageUrls(BaseItemDto item, ImageOptions options) /// The options. /// System.String. /// item - public string GetLogoImageUrl(BaseItemDto item, ImageOptions options) + public Uri GetLogoImageUrl(BaseItemDto item, ImageOptions options) { if (item == null) { @@ -885,7 +885,7 @@ public string GetLogoImageUrl(BaseItemDto item, ImageOptions options) options.ImageType = ImageType.Logo; - var logoItemId = HasLogo(item) ? item.Id : item.ParentLogoItemId; + var logoItemId = HasLogo(item) ? item.Id.ToString("N", CultureInfo.InvariantCulture) : item.ParentLogoItemId; var imageTag = HasLogo(item) ? item.ImageTags[ImageType.Logo] : item.ParentLogoImageTag; if (!string.IsNullOrEmpty(logoItemId)) @@ -898,7 +898,7 @@ public string GetLogoImageUrl(BaseItemDto item, ImageOptions options) return null; } - public string GetThumbImageUrl(BaseItemDto item, ImageOptions options) + public Uri GetThumbImageUrl(BaseItemDto item, ImageOptions options) { if (item == null) { @@ -912,7 +912,7 @@ public string GetThumbImageUrl(BaseItemDto item, ImageOptions options) options.ImageType = ImageType.Thumb; - var itemId = HasThumb(item) ? item.Id : item.SeriesThumbImageTag != null ? item.SeriesId : item.ParentThumbItemId; + var itemId = HasThumb(item) ? item.Id.ToString("N", CultureInfo.InvariantCulture) : item.SeriesThumbImageTag != null ? item.SeriesId.ToString("N", CultureInfo.InvariantCulture) : item.ParentThumbItemId; var imageTag = HasThumb(item) ? item.ImageTags[ImageType.Thumb] : item.SeriesThumbImageTag ?? item.ParentThumbImageTag; if (!string.IsNullOrEmpty(itemId)) @@ -932,7 +932,7 @@ public string GetThumbImageUrl(BaseItemDto item, ImageOptions options) /// The options. /// System.String. /// item - public string GetArtImageUrl(BaseItemDto item, ImageOptions options) + public Uri GetArtImageUrl(BaseItemDto item, ImageOptions options) { if (item == null) { @@ -946,7 +946,7 @@ public string GetArtImageUrl(BaseItemDto item, ImageOptions options) options.ImageType = ImageType.Art; - var artItemId = HasArtImage(item) ? item.Id : item.ParentArtItemId; + var artItemId = HasArtImage(item) ? item.Id.ToString("N", CultureInfo.InvariantCulture) : item.ParentArtItemId; var imageTag = HasArtImage(item) ? item.ImageTags[ImageType.Art] : item.ParentArtImageTag; if (!string.IsNullOrEmpty(artItemId)) @@ -1010,21 +1010,16 @@ protected string SerializeToJson(object obj) /// Adds the data format. /// /// The URL. - /// System.String. - protected string AddDataFormat(string url) + /// System.Uri. + protected Uri AddDataFormat(Uri url) { const string format = "json"; - if (url.IndexOf('?') == -1) - { - url += "?format=" + format; - } - else - { - url += "&format=" + format; - } - - return url; + UriBuilder uriBuilder = new UriBuilder(url); + NameValueCollection query = HttpUtility.ParseQueryString(uriBuilder.Query); + query["format"] = format; + uriBuilder.Query = query.ToQueryString(); + return uriBuilder.Uri; } /// diff --git a/Jellyfin.ApiClient/ConnectionManager.cs b/Jellyfin.ApiClient/ConnectionManager.cs new file mode 100644 index 0000000..94aa610 --- /dev/null +++ b/Jellyfin.ApiClient/ConnectionManager.cs @@ -0,0 +1,519 @@ +using Jellyfin.ApiClient.Model; +using Jellyfin.ApiClient.Net; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Model.ApiClient; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.System; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Jellyfin.ApiClient +{ + public class ConnectionManager : IConnectionManager + { + public event EventHandler> LocalUserSignIn; + public event EventHandler> LocalUserSignOut; + public event EventHandler RemoteLoggedOut; + + public event EventHandler> Connected; + + private readonly ICredentialProvider _credentialProvider; + private readonly INetworkConnection _networkConnectivity; + private readonly ILogger _logger; + private readonly IServerLocator _serverDiscovery; + private readonly IAsyncHttpClient _httpClient; + private readonly Func _webSocketFactory; + + public Dictionary ApiClients { get; private set; } + + public string ApplicationName { get; private set; } + public string ApplicationVersion { get; private set; } + public IDevice Device { get; private set; } + public ClientCapabilities ClientCapabilities { get; private set; } + + public IApiClient CurrentApiClient { get; private set; } + + public ConnectionManager(ILogger logger, + ICredentialProvider credentialProvider, + INetworkConnection networkConnectivity, + IServerLocator serverDiscovery, + string applicationName, + string applicationVersion, + IDevice device, + ClientCapabilities clientCapabilities, + Func webSocketFactory = null) + { + _credentialProvider = credentialProvider; + _networkConnectivity = networkConnectivity; + _logger = logger; + _serverDiscovery = serverDiscovery; + _httpClient = AsyncHttpClientFactory.Create(logger); + ClientCapabilities = clientCapabilities; + _webSocketFactory = webSocketFactory; + + Device = device; + ApplicationVersion = applicationVersion; + ApplicationName = applicationName; + ApiClients = new Dictionary(StringComparer.OrdinalIgnoreCase); + SaveLocalCredentials = true; + } + + public IJsonSerializer JsonSerializer = new NewtonsoftJsonSerializer(); + + public bool SaveLocalCredentials { get; set; } + + private IApiClient GetOrAddApiClient(ServerInfo server) + { + + if (!ApiClients.TryGetValue(server.Id, out IApiClient apiClient)) + { + var address = server.Address; + + apiClient = new ApiClient(_logger, address, ApplicationName, Device, ApplicationVersion) + { + JsonSerializer = JsonSerializer, + }; + + apiClient.OnAuthenticated += ApiClientOnAuthenticated; + + ApiClients[server.Id] = apiClient; + } + + if (string.IsNullOrEmpty(server.AccessToken)) + { + apiClient.ClearAuthenticationInfo(); + } + else + { + apiClient.SetAuthenticationInfo(server.AccessToken, server.UserId); + } + + return apiClient; + } + + private async void ApiClientOnAuthenticated(object apiClient, GenericEventArgs result) + { + await OnAuthenticated((IApiClient)apiClient, result.Argument, new ConnectionOptions(), SaveLocalCredentials); + } + + private async void AfterConnected(IApiClient apiClient, ConnectionOptions options) + { + if (options.ReportCapabilities) + { + try + { + await apiClient.ReportCapabilities(ClientCapabilities).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError("Error reporting capabilities", ex); + } + } + + if (options.EnableWebSocket) + { + if (_webSocketFactory != null) + { + ((ApiClient)apiClient).OpenWebSocket(_webSocketFactory); + } + } + } + + public async Task> GetAvailableServers(CancellationToken cancellationToken = default) + { + var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); + + _logger.LogDebug("{0} servers in saved credentials", credentials.Servers.Count); + + if (_networkConnectivity.GetNetworkStatus().GetIsAnyLocalNetworkAvailable()) + { + foreach (var server in await FindServers(cancellationToken).ConfigureAwait(false)) + { + credentials.AddOrUpdateServer(server); + } + } + + await _credentialProvider.SaveServerCredentials(credentials).ConfigureAwait(false); + + return credentials.Servers.OrderByDescending(i => i.DateLastAccessed).ToList(); + } + + private async Task> FindServers(CancellationToken cancellationToken) + { + List servers; + + try + { + servers = await _serverDiscovery.FindServers(1500, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + _logger.LogDebug("No servers found via local discovery."); + + servers = new List(); + } + catch (Exception ex) + { + _logger.LogError("Error discovering servers.", ex); + + servers = new List(); + } + + return servers.Select(i => new ServerInfo + { + Id = i.Id, + Address = ConvertEndpointAddressToManualAddress(i) ?? new Uri(i.Address), + Name = i.Name + }) + .ToList(); + } + + private Uri ConvertEndpointAddressToManualAddress(ServerDiscoveryInfo info) + { + if (!string.IsNullOrWhiteSpace(info.Address) && !string.IsNullOrWhiteSpace(info.EndpointAddress)) + { + var uriBuilder = new UriBuilder(info.EndpointAddress.Split(':').First()) + { + Port = new Uri(info.Address).Port + }; + + return uriBuilder.Uri; + } + + return null; + } + + public async Task Connect(CancellationToken cancellationToken = default) + { + var servers = await GetAvailableServers(cancellationToken).ConfigureAwait(false); + + return await Connect(servers, cancellationToken).ConfigureAwait(false); + } + + /// + /// Loops through a list of servers and returns the first that is available for connection + /// + private async Task Connect(List servers, CancellationToken cancellationToken) + { + servers = servers + .OrderByDescending(i => i.DateLastAccessed) + .ToList(); + + if (servers.Count == 1) + { + _logger.LogDebug("1 server in the list."); + + var result = await Connect(servers[0], cancellationToken).ConfigureAwait(false); + + if (result.State == ConnectionState.Unavailable) + { + result.State = ConnectionState.ServerSelection; + } + + return result; + } + + var firstServer = servers.FirstOrDefault(); + // See if we have any saved credentials and can auto sign in + if (firstServer != null && !string.IsNullOrEmpty(firstServer.AccessToken)) + { + var result = await Connect(firstServer, cancellationToken).ConfigureAwait(false); + + if (result.State == ConnectionState.SignedIn) + { + return result; + } + } + + var finalResult = new ConnectionResult + { + Servers = servers + }; + + finalResult.State = ConnectionState.ServerSelection; + + return finalResult; + } + + /// + /// Attempts to connect to a server + /// + public Task Connect(ServerInfo server, CancellationToken cancellationToken = default) + { + return Connect(server, new ConnectionOptions(), cancellationToken); + } + + public async Task Connect(ServerInfo server, ConnectionOptions options, CancellationToken cancellationToken = default) + { + if (server.Address == null) + { + // TODO: on failed connection + return new ConnectionResult { State = ConnectionState.Unavailable }; + } + + int timeout = 100; + + await TryConnect(server.Address, timeout, cancellationToken); + + // TODO: this isn't right + return new ConnectionResult { State = ConnectionState.Unavailable }; + } + + private async Task OnSuccessfulConnection(ServerInfo server, + ConnectionOptions options, + PublicSystemInfo systemInfo, + ConnectionResult result, + CancellationToken cancellationToken) + { + server.ImportInfo(systemInfo); + + var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); + + if (!string.IsNullOrWhiteSpace(server.AccessToken)) + { + await ValidateAuthentication(server, options, cancellationToken).ConfigureAwait(false); + } + + credentials.AddOrUpdateServer(server); + + if (options.UpdateDateLastAccessed) + { + server.DateLastAccessed = DateTime.UtcNow; + } + + await _credentialProvider.SaveServerCredentials(credentials).ConfigureAwait(false); + + result.ApiClient = GetOrAddApiClient(server); + result.State = string.IsNullOrEmpty(server.AccessToken) ? + ConnectionState.ServerSignIn : + ConnectionState.SignedIn; + + ((ApiClient)result.ApiClient).EnableAutomaticNetworking(server, _networkConnectivity); + + if (result.State == ConnectionState.SignedIn) + { + AfterConnected(result.ApiClient, options); + } + + CurrentApiClient = result.ApiClient; + + result.Servers.Add(server); + + Connected?.Invoke(this, new GenericEventArgs(result)); + } + + public Task Connect(IApiClient apiClient, CancellationToken cancellationToken = default) + { + var client = (ApiClient)apiClient; + return Connect(client.ServerInfo, cancellationToken); + } + + private async Task ValidateAuthentication(ServerInfo server, ConnectionOptions options, CancellationToken cancellationToken) + { + _logger.LogDebug("Validating saved authentication"); + + var url = server.Address; + + var headers = new HttpHeaders(); + headers.SetAccessToken(server.AccessToken); + + var request = new HttpRequest + { + CancellationToken = cancellationToken, + Method = "GET", + RequestHeaders = headers, + Url = new Uri(url, "/emby/system/info?format=json") + }; + + try + { + using (var stream = await _httpClient.SendAsync(request).ConfigureAwait(false)) + { + var systemInfo = JsonSerializer.DeserializeFromStream(stream); + + server.ImportInfo(systemInfo); + } + + if (server.UserId != Guid.Empty) + { + request.Url = new Uri(url, "/mediabrowser/users/" + server.UserId + "?format=json"); + + using (var stream = await _httpClient.SendAsync(request).ConfigureAwait(false)) + { + var localUser = JsonSerializer.DeserializeFromStream(stream); + + OnLocalUserSignIn(options, localUser); + } + } + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception) + { + // Already logged at a lower level + + server.UserId = Guid.Empty; + server.AccessToken = null; + } + } + + private async Task TryConnect(Uri url, int timeout, CancellationToken cancellationToken) + { + url = new Uri(url, "/emby/system/info/public?format=json"); + + try + { + using (var stream = await _httpClient.SendAsync(new HttpRequest + { + Url = url, + CancellationToken = cancellationToken, + Timeout = timeout, + Method = "GET" + + }).ConfigureAwait(false)) + { + return JsonSerializer.DeserializeFromStream(stream); + } + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception) + { + // Already logged at a lower level + + return null; + } + } + + public void Dispose() + { + foreach (var client in ApiClients.Values.ToList()) + { + client.Dispose(); + } + } + + public IApiClient GetApiClient(IHasServerId item) + { + return GetApiClient(item.ServerId); + } + + public IApiClient GetApiClient(string serverId) + { + return ApiClients.Values.OfType().FirstOrDefault(i => string.Equals(i.ServerInfo.Id, serverId, StringComparison.OrdinalIgnoreCase)); + } + + public async Task Connect(Uri address, CancellationToken cancellationToken = default) + { + var publicInfo = await TryConnect(address, 15000, cancellationToken).ConfigureAwait(false); + + if (publicInfo == null) + { + return new ConnectionResult + { + State = ConnectionState.Unavailable + }; + } + + var server = new ServerInfo + { + Address = address + }; + + server.ImportInfo(publicInfo); + + return await Connect(server, cancellationToken).ConfigureAwait(false); + } + + private async Task OnAuthenticated(IApiClient apiClient, + AuthenticationResult result, + ConnectionOptions options, + bool saveCredentials) + { + var server = ((ApiClient)apiClient).ServerInfo; + + var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); + + if (options.UpdateDateLastAccessed) + { + server.DateLastAccessed = DateTime.UtcNow; + } + + if (saveCredentials) + { + server.UserId = result.User.Id; + server.AccessToken = result.AccessToken; + } + else + { + server.UserId = Guid.Empty; + server.AccessToken = null; + } + + credentials.AddOrUpdateServer(server); + await _credentialProvider.SaveServerCredentials(credentials).ConfigureAwait(false); + + AfterConnected(apiClient, options); + + OnLocalUserSignIn(options, result.User); + } + + private void OnLocalUserSignIn(ConnectionOptions options, UserDto user) + { + // TODO: Create a separate property for this + if (options.UpdateDateLastAccessed) + { + LocalUserSignIn?.Invoke(this, new GenericEventArgs(user)); + } + } + + private void OnLocalUserSignout(IApiClient apiClient) + { + LocalUserSignOut?.Invoke(this, new GenericEventArgs(apiClient)); + } + + public async Task Logout() + { + foreach (var client in ApiClients.Values.ToList()) + { + if (!string.IsNullOrEmpty(client.AccessToken)) + { + await client.Logout().ConfigureAwait(false); + OnLocalUserSignout(client); + } + } + + var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); + + var servers = credentials.Servers.ToList(); + + foreach (var server in servers) + { + server.AccessToken = null; + server.UserId = Guid.Empty; + } + + credentials.Servers = servers; + + await _credentialProvider.SaveServerCredentials(credentials).ConfigureAwait(false); + } + + public async Task GetServerInfo(string id) + { + var credentials = await _credentialProvider.GetServerCredentials().ConfigureAwait(false); + + return credentials.Servers.FirstOrDefault(i => i.Id.Equals(id, StringComparison.OrdinalIgnoreCase)); + } + } +} diff --git a/Jellyfin.ApiClient/Device.cs b/Jellyfin.ApiClient/Device.cs new file mode 100644 index 0000000..c0c4e57 --- /dev/null +++ b/Jellyfin.ApiClient/Device.cs @@ -0,0 +1,10 @@ +using Jellyfin.ApiClient.Model; + +namespace Jellyfin.ApiClient +{ + public class Device : IDevice + { + public string DeviceName { get; set; } + public string DeviceId { get; set; } + } +} diff --git a/Emby.ApiClient/ICredentialProvider.cs b/Jellyfin.ApiClient/ICredentialProvider.cs similarity index 84% rename from Emby.ApiClient/ICredentialProvider.cs rename to Jellyfin.ApiClient/ICredentialProvider.cs index 564c4b9..d3f8417 100644 --- a/Emby.ApiClient/ICredentialProvider.cs +++ b/Jellyfin.ApiClient/ICredentialProvider.cs @@ -1,8 +1,7 @@ -using MediaBrowser.Model.ApiClient; +using Jellyfin.ApiClient.Model; using System.Threading.Tasks; -using Emby.ApiClient.Model; -namespace Emby.ApiClient +namespace Jellyfin.ApiClient { public interface ICredentialProvider { diff --git a/Emby.ApiClient/IServerLocator.cs b/Jellyfin.ApiClient/IServerLocator.cs similarity index 78% rename from Emby.ApiClient/IServerLocator.cs rename to Jellyfin.ApiClient/IServerLocator.cs index 5d7e78a..ef24f2f 100644 --- a/Emby.ApiClient/IServerLocator.cs +++ b/Jellyfin.ApiClient/IServerLocator.cs @@ -3,13 +3,13 @@ using System.Threading; using System.Threading.Tasks; -namespace Emby.ApiClient +namespace Jellyfin.ApiClient { public interface IServerLocator { /// /// Attemps to discover the server within a local network /// - Task> FindServers(int timeoutMs, CancellationToken cancellationToken = default(CancellationToken)); + Task> FindServers(int timeoutMs, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/Jellyfin.ApiClient/Jellyfin.ApiClient.csproj b/Jellyfin.ApiClient/Jellyfin.ApiClient.csproj new file mode 100644 index 0000000..4736a81 --- /dev/null +++ b/Jellyfin.ApiClient/Jellyfin.ApiClient.csproj @@ -0,0 +1,36 @@ + + + + netstandard2.0 + 1.1.1.0 + 1.1.1.0 + true + Jellyfin.ApiClient + 10.4.3 + Jellyfin + Jellyfin + Api libraries for connecting to Jellyfin Server. + https://github.com/Jellyfin/jellyfin-apiclient-csharp + https://github.com/Jellyfin/jellyfin-apiclient-csharp + true + + + + + + + + + + + 10.4.3 + + + 10.4.3 + + + 10.4.3 + + + + diff --git a/Jellyfin.ApiClient/Model/AuthenticationRequest.cs b/Jellyfin.ApiClient/Model/AuthenticationRequest.cs new file mode 100644 index 0000000..8ac3cfe --- /dev/null +++ b/Jellyfin.ApiClient/Model/AuthenticationRequest.cs @@ -0,0 +1,8 @@ +namespace Jellyfin.ApiClient.Model +{ + class AuthenticationRequest + { + public string Username { get; set; } + public string Pw { get; set; } + } +} diff --git a/Emby.ApiClient/Model/ConnectionOptions.cs b/Jellyfin.ApiClient/Model/ConnectionOptions.cs similarity index 96% rename from Emby.ApiClient/Model/ConnectionOptions.cs rename to Jellyfin.ApiClient/Model/ConnectionOptions.cs index 0699464..8991291 100644 --- a/Emby.ApiClient/Model/ConnectionOptions.cs +++ b/Jellyfin.ApiClient/Model/ConnectionOptions.cs @@ -1,5 +1,4 @@ - -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { public class ConnectionOptions { diff --git a/Emby.ApiClient/Model/ConnectionResult.cs b/Jellyfin.ApiClient/Model/ConnectionResult.cs similarity index 74% rename from Emby.ApiClient/Model/ConnectionResult.cs rename to Jellyfin.ApiClient/Model/ConnectionResult.cs index 2574e4e..5e16226 100644 --- a/Emby.ApiClient/Model/ConnectionResult.cs +++ b/Jellyfin.ApiClient/Model/ConnectionResult.cs @@ -1,15 +1,13 @@ -using MediaBrowser.Model.Connect; -using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Dto; using System.Collections.Generic; -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { public class ConnectionResult { public ConnectionState State { get; set; } public List Servers { get; set; } public IApiClient ApiClient { get; set; } - public ConnectUser ConnectUser { get; set; } public UserDto OfflineUser { get; set; } public ConnectionResult() diff --git a/Emby.ApiClient/Model/ConnectionState.cs b/Jellyfin.ApiClient/Model/ConnectionState.cs similarity index 86% rename from Emby.ApiClient/Model/ConnectionState.cs rename to Jellyfin.ApiClient/Model/ConnectionState.cs index 8fab06a..a266737 100644 --- a/Emby.ApiClient/Model/ConnectionState.cs +++ b/Jellyfin.ApiClient/Model/ConnectionState.cs @@ -1,4 +1,4 @@ -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { public enum ConnectionState { diff --git a/Jellyfin.ApiClient/Model/Dto/ChannelInfoDto.cs b/Jellyfin.ApiClient/Model/Dto/ChannelInfoDto.cs new file mode 100644 index 0000000..23cb7cb --- /dev/null +++ b/Jellyfin.ApiClient/Model/Dto/ChannelInfoDto.cs @@ -0,0 +1,120 @@ +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Library; +using MediaBrowser.Model.LiveTv; +using System.Collections.Generic; + +namespace Jellyfin.ApiClient.Model.Dto +{ + /// + /// Class ChannelInfoDto + /// + public class ChannelInfoDto : IItemDto, IHasServerId + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the server identifier. + /// + /// The server identifier. + public string ServerId { get; set; } + + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + + /// + /// Gets or sets the external identifier. + /// + /// The external identifier. + public string ExternalId { get; set; } + + /// + /// Gets or sets the media sources. + /// + /// The media sources. + public MediaSourceInfo[] MediaSources { get; set; } + + /// + /// Gets or sets the image tags. + /// + /// The image tags. + public Dictionary ImageTags { get; set; } + + /// + /// Gets or sets the number. + /// + /// The number. + public string Number { get; set; } + + /// + /// Gets or sets the play access. + /// + /// The play access. + public PlayAccess PlayAccess { get; set; } + + /// + /// Gets or sets the name of the service. + /// + /// The name of the service. + public string ServiceName { get; set; } + + /// + /// Gets or sets the type of the channel. + /// + /// The type of the channel. + public ChannelType ChannelType { get; set; } + + /// + /// Gets or sets the type. + /// + /// The type. + public string Type { get; set; } + + /// + /// Gets or sets the type of the media. + /// + /// The type of the media. + public string MediaType { get; set; } + + /// + /// Gets or sets the user data. + /// + /// The user data. + public UserItemDataDto UserData { get; set; } + + /// + /// Gets or sets the now playing program. + /// + /// The now playing program. + public BaseItemDto CurrentProgram { get; set; } + + /// + /// Gets or sets the primary image aspect ratio, after image enhancements. + /// + /// The primary image aspect ratio. + public double? PrimaryImageAspectRatio { get; set; } + + /// + /// Gets a value indicating whether this instance has primary image. + /// + /// true if this instance has primary image; otherwise, false. + [IgnoreDataMember] + public bool HasPrimaryImage + { + get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Primary); } + } + + public ChannelInfoDto() + { + ImageTags = new Dictionary(); + MediaSources = new MediaSourceInfo[] { }; + } + } +} diff --git a/Jellyfin.ApiClient/Model/Dto/SubtitleDownloadOptions.cs b/Jellyfin.ApiClient/Model/Dto/SubtitleDownloadOptions.cs new file mode 100644 index 0000000..bddc8cc --- /dev/null +++ b/Jellyfin.ApiClient/Model/Dto/SubtitleDownloadOptions.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Jellyfin.ApiClient.Model.Dto +{ + public class SubtitleDownloadOptions + { + /// + /// Gets or sets the item identifier. + /// + /// The item identifier. + public string ItemId { get; set; } + + /// + /// Gets or sets the media source identifier. + /// + /// The media source identifier. + public string MediaSourceId { get; set; } + + /// + /// Gets or sets the index of the stream. + /// + /// The index of the stream. + public int StreamIndex { get; set; } + + /// + /// Gets or sets the format. + /// + /// The format. + public string Format { get; set; } + } +} diff --git a/Emby.ApiClient/Model/IApiClient.cs b/Jellyfin.ApiClient/Model/IApiClient.cs similarity index 80% rename from Emby.ApiClient/Model/IApiClient.cs rename to Jellyfin.ApiClient/Model/IApiClient.cs index e05c4bd..ddc4b51 100644 --- a/Emby.ApiClient/Model/IApiClient.cs +++ b/Jellyfin.ApiClient/Model/IApiClient.cs @@ -1,4 +1,8 @@ -using MediaBrowser.Model.Channels; +using Jellyfin.ApiClient.Model.Dto; +using Jellyfin.ApiClient.Model.Notifications; +using Jellyfin.ApiClient.Model.Querying; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Model.Channels; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Dto; @@ -8,24 +12,23 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; -using MediaBrowser.Model.Notifications; using MediaBrowser.Model.Playlists; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Sync; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Users; using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; +using System.Net; using System.Threading; using System.Threading.Tasks; -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { /// /// Interface IApiClient @@ -41,15 +44,8 @@ public interface IApiClient : IServerEvents, IDisposable /// Gets the API URL. /// /// The handler. - /// System.String. - string GetApiUrl(string handler); - - /// - /// Gets the game system summaries async. - /// - /// The cancellation token. - /// Task{List{GameSystemSummary}}. - Task> GetGameSystemSummariesAsync(CancellationToken cancellationToken = default(CancellationToken)); + /// System.Uri. + Uri GetApiUrl(Uri handler); /// /// Gets the async. @@ -58,7 +54,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The URL. /// The cancellation token. /// Task{``0}. - Task GetAsync(string url, CancellationToken cancellationToken = default(CancellationToken)) + Task GetAsync(Uri url, CancellationToken cancellationToken = default) where T : class; /// @@ -67,7 +63,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The capabilities. /// The cancellation token. /// Task. - Task ReportCapabilities(ClientCapabilities capabilities, CancellationToken cancellationToken = default(CancellationToken)); + Task ReportCapabilities(ClientCapabilities capabilities, CancellationToken cancellationToken = default); /// /// Logouts this instance. @@ -81,7 +77,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The user id. /// The cancellation token. /// Task{List{ItemIndex}}. - Task> GetGamePlayerIndex(string userId, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetGamePlayerIndex(string userId, CancellationToken cancellationToken = default); /// /// Gets the index of the year. @@ -90,7 +86,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The include item types. /// The cancellation token. /// Task{List{ItemIndex}}. - Task> GetYearIndex(string userId, string[] includeItemTypes, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetYearIndex(string userId, string[] includeItemTypes, CancellationToken cancellationToken = default); /// /// Gets the critic reviews. @@ -100,7 +96,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The start index. /// The limit. /// Task{ItemReviewsResult}. - Task> GetCriticReviews(string itemId, CancellationToken cancellationToken = default(CancellationToken), int? startIndex = null, int? limit = null); + Task> GetCriticReviews(string itemId, CancellationToken cancellationToken = default, int? startIndex = null, int? limit = null); /// /// Gets the theme songs async. @@ -110,7 +106,7 @@ public interface IApiClient : IServerEvents, IDisposable /// if set to true [inherit from parents]. /// The cancellation token. /// Task{ThemeMediaResult}. - Task GetThemeSongsAsync(string userId, string itemId, bool inheritFromParents, CancellationToken cancellationToken = default(CancellationToken)); + Task GetThemeSongsAsync(string userId, string itemId, bool inheritFromParents, CancellationToken cancellationToken = default); /// /// Gets the search hints async. @@ -137,7 +133,7 @@ public interface IApiClient : IServerEvents, IDisposable /// if set to true [inherit from parents]. /// The cancellation token. /// Task{ThemeMediaResult}. - Task GetThemeVideosAsync(string userId, string itemId, bool inheritFromParents, CancellationToken cancellationToken = default(CancellationToken)); + Task GetThemeVideosAsync(string userId, string itemId, bool inheritFromParents, CancellationToken cancellationToken = default); /// /// Gets all theme media async. @@ -147,7 +143,7 @@ public interface IApiClient : IServerEvents, IDisposable /// if set to true [inherit from parents]. /// The cancellation token. /// Task{AllThemeMediaResult}. - Task GetAllThemeMediaAsync(string userId, string itemId, bool inheritFromParents, CancellationToken cancellationToken = default(CancellationToken)); + Task GetAllThemeMediaAsync(string userId, string itemId, bool inheritFromParents, CancellationToken cancellationToken = default); /// /// Marks the notifications read. @@ -179,7 +175,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The cancellation token. /// Task{Stream}. /// url - Task GetImageStreamAsync(string url, CancellationToken cancellationToken = default(CancellationToken)); + Task GetImageStreamAsync(Uri url, CancellationToken cancellationToken = default); /// /// Gets the stream. @@ -187,7 +183,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The URL. /// The cancellation token. /// Task<Stream>. - Task GetStream(string url, CancellationToken cancellationToken = default(CancellationToken)); + Task GetStream(Uri url, CancellationToken cancellationToken = default); /// /// Gets the response. @@ -195,7 +191,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The URL. /// The cancellation token. /// Task<HttpResponse>. - Task GetResponse(string url, CancellationToken cancellationToken = default(CancellationToken)); + Task GetResponse(Uri url, CancellationToken cancellationToken = default); /// /// Updates the user configuration. @@ -264,7 +260,7 @@ public interface IApiClient : IServerEvents, IDisposable /// /// The cancellation token. /// Task{UserDto[]}. - Task GetPublicUsersAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task GetPublicUsersAsync(CancellationToken cancellationToken = default); /// /// Gets active client sessions. @@ -277,7 +273,7 @@ public interface IApiClient : IServerEvents, IDisposable /// Gets the client session asynchronous. /// /// Task{SessionInfoDto}. - Task GetCurrentSessionAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task GetCurrentSessionAsync(CancellationToken cancellationToken = default); /// /// Gets the item counts async. @@ -292,7 +288,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The query. /// The cancellation token. /// Task{ItemsResult}. - Task> GetEpisodesAsync(EpisodeQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetEpisodesAsync(EpisodeQuery query, CancellationToken cancellationToken = default); /// /// Gets the seasons asynchronous. @@ -300,9 +296,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The query. /// The cancellation token. /// Task{ItemsResult}. - Task> GetSeasonsAsync(SeasonQuery query, CancellationToken cancellationToken = default(CancellationToken)); - - Task GetRegistrationInfo(); + Task> GetSeasonsAsync(SeasonQuery query, CancellationToken cancellationToken = default); /// /// Queries for items @@ -311,7 +305,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The cancellation token. /// Task{ItemsResult}. /// query - Task> GetItemsAsync(ItemQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetItemsAsync(ItemQuery query, CancellationToken cancellationToken = default); /// /// Gets the user views. @@ -319,7 +313,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The user identifier. /// The cancellation token. /// Task<ItemsResult>. - Task> GetUserViews(string userId, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetUserViews(string userId, CancellationToken cancellationToken = default); /// /// Gets the instant mix from item asynchronous. @@ -334,7 +328,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The query. /// The cancellation token. /// Task{ItemsResult}. - Task> GetSimilarItemsAsync(SimilarItemsQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetSimilarItemsAsync(SimilarItemsQuery query, CancellationToken cancellationToken = default); /// /// Gets the people async. @@ -343,7 +337,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The cancellation token. /// Task{ItemsResult}. /// userId - Task> GetPeopleAsync(PersonsQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetPeopleAsync(PersonsQuery query, CancellationToken cancellationToken = default); /// /// Gets the artists. @@ -366,7 +360,7 @@ public interface IApiClient : IServerEvents, IDisposable /// The query. /// The cancellation token. /// Task{ItemsResult}. - Task> GetNextUpEpisodesAsync(NextUpQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetNextUpEpisodesAsync(NextUpQuery query, CancellationToken cancellationToken = default); /// /// Gets the upcoming episodes asynchronous. @@ -400,14 +394,14 @@ public interface IApiClient : IServerEvents, IDisposable /// /// The cancellation token. /// Task{SystemInfo}. - Task GetSystemInfoAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task GetSystemInfoAsync(CancellationToken cancellationToken = default); /// /// Gets the public system information asynchronous. /// /// The cancellation token. /// Task<PublicSystemInfo>. - Task GetPublicSystemInfoAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task GetPublicSystemInfoAsync(CancellationToken cancellationToken = default); /// /// Gets a list of plugins installed on the server @@ -641,6 +635,9 @@ public interface IApiClient : IServerEvents, IDisposable /// itemId Task UpdateUserItemRatingAsync(string itemId, string userId, bool likes); + + event EventHandler> OnAuthenticated; + /// /// Authenticates a user and returns the result /// @@ -676,7 +673,7 @@ Task AuthenticateUserAsync(string username, /// The client. /// The cancellation token. /// Task{BaseItemDto}. - Task GetDisplayPreferencesAsync(string id, string userId, string client, CancellationToken cancellationToken = default(CancellationToken)); + Task GetDisplayPreferencesAsync(string id, string userId, string client, CancellationToken cancellationToken = default); /// /// Updates display preferences for a user @@ -687,7 +684,7 @@ Task AuthenticateUserAsync(string username, /// The cancellation token. /// Task{DisplayPreferences}. /// userId - Task UpdateDisplayPreferencesAsync(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken = default(CancellationToken)); + Task UpdateDisplayPreferencesAsync(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken = default); /// /// Posts a set of data to a url, and deserializes the return stream into T @@ -697,7 +694,7 @@ Task AuthenticateUserAsync(string username, /// The args. /// The cancellation token. /// Task{``0}. - Task PostAsync(string url, Dictionary args, CancellationToken cancellationToken = default(CancellationToken)) + Task PostAsync(Uri url, NameValueCollection args, CancellationToken cancellationToken = default) where T : class; /// @@ -705,7 +702,7 @@ Task AuthenticateUserAsync(string username, /// /// The URL. /// Task{Stream}. - Task GetSerializedStreamAsync(string url); + Task GetSerializedStreamAsync(Uri url); /// /// Gets the json serializer. @@ -717,7 +714,7 @@ Task AuthenticateUserAsync(string username, /// Gets or sets the server address /// /// The server address. - string ServerAddress { get; } + Uri ServerAddress { get; } /// /// Gets or sets the type of the client. @@ -747,7 +744,7 @@ Task AuthenticateUserAsync(string username, /// Gets or sets the current user id. /// /// The current user id. - string CurrentUserId { get; } + Guid CurrentUserId { get; } /// /// Gets the access token. @@ -760,7 +757,7 @@ Task AuthenticateUserAsync(string username, /// /// The access token. /// The user identifier. - void SetAuthenticationInfo(string accessToken, string userId); + void SetAuthenticationInfo(string accessToken, Guid userId); /// /// Sets the authentication information. @@ -778,36 +775,7 @@ Task AuthenticateUserAsync(string username, /// /// The address. /// if set to true [keep existing authentication]. - void ChangeServerLocation(string address, bool keepExistingAuth = false); - - /// - /// Starts the receiving synchronize job updates. - /// - /// The interval ms. - /// The job identifier. - /// Task. - Task StartReceivingSyncJobUpdates(int intervalMs, string jobId); - - /// - /// Stops the receiving synchronize job updates. - /// - /// Task. - Task StopReceivingSyncJobUpdates(); - - /// - /// Starts the receiving synchronize jobs updates. - /// - /// The interval ms. - /// The user identifier. - /// The target identifier. - /// Task. - Task StartReceivingSyncJobsUpdates(int intervalMs, string userId, string targetId); - - /// - /// Stops the receiving synchronize jobs updates. - /// - /// Task. - Task StopReceivingSyncJobsUpdates(); + void ChangeServerLocation(Uri address, bool keepExistingAuth = false); /// /// Starts the receiving session updates. @@ -829,7 +797,7 @@ Task AuthenticateUserAsync(string username, /// The options. /// System.String. /// item - string GetImageUrl(BaseItemDto item, ImageOptions options); + Uri GetImageUrl(BaseItemDto item, ImageOptions options); /// /// Gets the image URL. @@ -837,14 +805,14 @@ Task AuthenticateUserAsync(string username, /// The item. /// The options. /// System.String. - string GetImageUrl(ChannelInfoDto item, ImageOptions options); + Uri GetImageUrl(ChannelInfoDto item, ImageOptions options); /// /// Gets the subtitle URL. /// /// The options. /// System.String. - string GetSubtitleUrl(SubtitleDownloadOptions options); + Uri GetSubtitleUrl(SubtitleDownloadOptions options); /// /// Gets an image url that can be used to download an image from the api @@ -853,7 +821,7 @@ Task AuthenticateUserAsync(string username, /// The options. /// System.String. /// itemId - string GetImageUrl(string itemId, ImageOptions options); + Uri GetImageUrl(string itemId, ImageOptions options); /// /// Gets the user image URL. @@ -862,7 +830,7 @@ Task AuthenticateUserAsync(string username, /// The options. /// System.String. /// user - string GetUserImageUrl(UserDto user, ImageOptions options); + Uri GetUserImageUrl(UserDto user, ImageOptions options); /// /// Gets an image url that can be used to download an image from the api @@ -871,7 +839,7 @@ Task AuthenticateUserAsync(string username, /// The options. /// System.String. /// userId - string GetUserImageUrl(string userId, ImageOptions options); + Uri GetUserImageUrl(Guid userId, ImageOptions options); /// /// Gets the person image URL. @@ -880,7 +848,7 @@ Task AuthenticateUserAsync(string username, /// The options. /// System.String. /// item - string GetPersonImageUrl(BaseItemPerson item, ImageOptions options); + Uri GetPersonImageUrl(BaseItemPerson item, ImageOptions options); /// /// 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. @@ -889,7 +857,7 @@ Task AuthenticateUserAsync(string username, /// The options. /// System.String[][]. /// item - string[] GetBackdropImageUrls(BaseItemDto item, ImageOptions options); + Uri[] GetBackdropImageUrls(BaseItemDto item, ImageOptions options); /// /// 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. @@ -898,7 +866,7 @@ Task AuthenticateUserAsync(string username, /// The options. /// System.String. /// item - string GetLogoImageUrl(BaseItemDto item, ImageOptions options); + Uri GetLogoImageUrl(BaseItemDto item, ImageOptions options); /// /// Gets the art image URL. @@ -906,7 +874,7 @@ Task AuthenticateUserAsync(string username, /// The item. /// The options. /// System.String. - string GetArtImageUrl(BaseItemDto item, ImageOptions options); + Uri GetArtImageUrl(BaseItemDto item, ImageOptions options); /// /// Gets the thumb image URL. @@ -914,14 +882,14 @@ Task AuthenticateUserAsync(string username, /// The item. /// The options. /// System.String. - string GetThumbImageUrl(BaseItemDto item, ImageOptions options); + Uri GetThumbImageUrl(BaseItemDto item, ImageOptions options); /// /// Gets the live tv information asynchronous. /// /// The cancellation token. /// Task{LiveTvInfo}. - Task GetLiveTvInfoAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task GetLiveTvInfoAsync(CancellationToken cancellationToken = default); /// /// Gets the live tv channels asynchronous. @@ -929,7 +897,7 @@ Task AuthenticateUserAsync(string username, /// The query. /// The cancellation token. /// Task{LiveTvInfo}. - Task> GetLiveTvChannelsAsync(LiveTvChannelQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetLiveTvChannelsAsync(LiveTvChannelQuery query, CancellationToken cancellationToken = default); /// /// Gets the live tv channel asynchronous. @@ -938,7 +906,7 @@ Task AuthenticateUserAsync(string username, /// The user identifier. /// The cancellation token. /// Task{ChannelInfoDto}. - Task GetLiveTvChannelAsync(string id, string userId, CancellationToken cancellationToken = default(CancellationToken)); + Task GetLiveTvChannelAsync(string id, string userId, CancellationToken cancellationToken = default); /// /// Gets the live tv recordings asynchronous. @@ -946,7 +914,7 @@ Task AuthenticateUserAsync(string username, /// The query. /// The cancellation token. /// Task{QueryResult{RecordingInfoDto}}. - Task> GetLiveTvRecordingsAsync(RecordingQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetLiveTvRecordingsAsync(RecordingQuery query, CancellationToken cancellationToken = default); /// /// Gets the live tv recording asynchronous. @@ -955,7 +923,7 @@ Task AuthenticateUserAsync(string username, /// The user identifier. /// The cancellation token. /// Task{RecordingInfoDto}. - Task GetLiveTvRecordingAsync(string id, string userId, CancellationToken cancellationToken = default(CancellationToken)); + Task GetLiveTvRecordingAsync(string id, string userId, CancellationToken cancellationToken = default); /// /// Gets the live tv recording groups asynchronous. @@ -963,7 +931,7 @@ Task AuthenticateUserAsync(string username, /// The query. /// The cancellation token. /// Task{QueryResult{RecordingGroupDto}}. - Task> GetLiveTvRecordingGroupsAsync(RecordingGroupQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetLiveTvRecordingGroupsAsync(RecordingGroupQuery query, CancellationToken cancellationToken = default); /// /// Gets the live tv recording group asynchronous. @@ -972,7 +940,7 @@ Task AuthenticateUserAsync(string username, /// The user identifier. /// The cancellation token. /// Task{RecordingGroupDto}. - Task GetLiveTvRecordingGroupAsync(string id, string userId, CancellationToken cancellationToken = default(CancellationToken)); + Task GetLiveTvRecordingGroupAsync(string id, string userId, CancellationToken cancellationToken = default); /// /// Gets the live tv timers asynchronous. @@ -980,7 +948,7 @@ Task AuthenticateUserAsync(string username, /// The query. /// The cancellation token. /// Task{QueryResult{TimerInfoDto}}. - Task> GetLiveTvTimersAsync(TimerQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetLiveTvTimersAsync(TimerQuery query, CancellationToken cancellationToken = default); /// /// Gets the live tv programs asynchronous. @@ -988,7 +956,7 @@ Task AuthenticateUserAsync(string username, /// The query. /// The cancellation token. /// Task{QueryResult{ProgramInfoDto}}. - Task> GetLiveTvProgramsAsync(ProgramQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetLiveTvProgramsAsync(ProgramQuery query, CancellationToken cancellationToken = default); /// /// Gets the live tv program asynchronous. @@ -997,7 +965,7 @@ Task AuthenticateUserAsync(string username, /// The user identifier. /// The cancellation token. /// Task{ProgramInfoDto}. - Task GetLiveTvProgramAsync(string id, string userId, CancellationToken cancellationToken = default(CancellationToken)); + Task GetLiveTvProgramAsync(string id, string userId, CancellationToken cancellationToken = default); /// /// Gets the recommended live tv programs asynchronous. @@ -1005,7 +973,7 @@ Task AuthenticateUserAsync(string username, /// The query. /// The cancellation token. /// Task{QueryResult{ProgramInfoDto}}. - Task> GetRecommendedLiveTvProgramsAsync(RecommendedProgramQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetRecommendedLiveTvProgramsAsync(RecommendedProgramQuery query, CancellationToken cancellationToken = default); /// /// Creates the live tv timer asynchronous. @@ -1013,7 +981,7 @@ Task AuthenticateUserAsync(string username, /// The timer. /// The cancellation token. /// Task. - Task CreateLiveTvTimerAsync(BaseTimerInfoDto timer, CancellationToken cancellationToken = default(CancellationToken)); + Task CreateLiveTvTimerAsync(BaseTimerInfoDto timer, CancellationToken cancellationToken = default); /// /// Updates the live tv timer asynchronous. @@ -1021,7 +989,7 @@ Task AuthenticateUserAsync(string username, /// The timer. /// The cancellation token. /// Task. - Task UpdateLiveTvTimerAsync(TimerInfoDto timer, CancellationToken cancellationToken = default(CancellationToken)); + Task UpdateLiveTvTimerAsync(TimerInfoDto timer, CancellationToken cancellationToken = default); /// /// Creates the live tv series timer asynchronous. @@ -1029,7 +997,7 @@ Task AuthenticateUserAsync(string username, /// The timer. /// The cancellation token. /// Task. - Task CreateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken = default(CancellationToken)); + Task CreateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken = default); /// /// Updates the live tv series timer asynchronous. @@ -1037,7 +1005,7 @@ Task AuthenticateUserAsync(string username, /// The timer. /// The cancellation token. /// Task. - Task UpdateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken = default(CancellationToken)); + Task UpdateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken = default); /// /// Gets the live tv timer asynchronous. @@ -1045,7 +1013,7 @@ Task AuthenticateUserAsync(string username, /// The identifier. /// The cancellation token. /// Task{TimerInfoDto}. - Task GetLiveTvTimerAsync(string id, CancellationToken cancellationToken = default(CancellationToken)); + Task GetLiveTvTimerAsync(string id, CancellationToken cancellationToken = default); /// /// Gets the live tv series timers asynchronous. @@ -1053,7 +1021,7 @@ Task AuthenticateUserAsync(string username, /// The query. /// The cancellation token. /// Task{QueryResult{SeriesTimerInfoDto}}. - Task> GetLiveTvSeriesTimersAsync(SeriesTimerQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetLiveTvSeriesTimersAsync(SeriesTimerQuery query, CancellationToken cancellationToken = default); /// /// Gets the live tv series timer asynchronous. @@ -1061,7 +1029,7 @@ Task AuthenticateUserAsync(string username, /// The identifier. /// The cancellation token. /// Task{SeriesTimerInfoDto}. - Task GetLiveTvSeriesTimerAsync(string id, CancellationToken cancellationToken = default(CancellationToken)); + Task GetLiveTvSeriesTimerAsync(string id, CancellationToken cancellationToken = default); /// /// Cancels the live tv timer asynchronous. @@ -1069,7 +1037,7 @@ Task AuthenticateUserAsync(string username, /// The identifier. /// The cancellation token. /// Task. - Task CancelLiveTvTimerAsync(string id, CancellationToken cancellationToken = default(CancellationToken)); + Task CancelLiveTvTimerAsync(string id, CancellationToken cancellationToken = default); /// /// Cancels the live tv series timer asynchronous. @@ -1077,21 +1045,21 @@ Task AuthenticateUserAsync(string username, /// The identifier. /// The cancellation token. /// Task. - Task CancelLiveTvSeriesTimerAsync(string id, CancellationToken cancellationToken = default(CancellationToken)); + Task CancelLiveTvSeriesTimerAsync(string id, CancellationToken cancellationToken = default); /// /// Gets the default timer information. /// /// The cancellation token. /// Task{SeriesTimerInfoDto}. - Task GetDefaultLiveTvTimerInfo(CancellationToken cancellationToken = default(CancellationToken)); + Task GetDefaultLiveTvTimerInfo(CancellationToken cancellationToken = default); /// /// Gets the live tv guide information. /// /// The cancellation token. /// Task{GuideInfo}. - Task GetLiveTvGuideInfo(CancellationToken cancellationToken = default(CancellationToken)); + Task GetLiveTvGuideInfo(CancellationToken cancellationToken = default); /// /// Gets the default timer information. @@ -1099,7 +1067,7 @@ Task AuthenticateUserAsync(string username, /// The program identifier. /// The cancellation token. /// Task{SeriesTimerInfoDto}. - Task GetDefaultLiveTvTimerInfo(string programId, CancellationToken cancellationToken = default(CancellationToken)); + Task GetDefaultLiveTvTimerInfo(string programId, CancellationToken cancellationToken = default); /// /// Gets the channel features. @@ -1107,7 +1075,7 @@ Task AuthenticateUserAsync(string username, /// The channel identifier. /// The cancellation token. /// Task{ChannelFeatures}. - Task GetChannelFeatures(string channelId, CancellationToken cancellationToken = default(CancellationToken)); + Task GetChannelFeatures(string channelId, CancellationToken cancellationToken = default); /// /// Gets the channel items. @@ -1115,7 +1083,7 @@ Task AuthenticateUserAsync(string username, /// The query. /// The cancellation token. /// Task{QueryResult{BaseItemDto}}. - Task> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken = default); /// /// Gets the channels. @@ -1123,7 +1091,7 @@ Task AuthenticateUserAsync(string username, /// The query. /// The cancellation token. /// Task{QueryResult{BaseItemDto}}. - Task> GetChannels(ChannelQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetChannels(ChannelQuery query, CancellationToken cancellationToken = default); /// /// Gets the latest channel items. @@ -1131,7 +1099,7 @@ Task AuthenticateUserAsync(string username, /// The query. /// The cancellation token. /// Task{QueryResult{BaseItemDto}}. - Task> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken = default); /// /// Creates the playlist. @@ -1174,7 +1142,7 @@ Task AuthenticateUserAsync(string username, /// The cancellation token. /// Task. Task SendContextMessageAsync(string itemType, string itemId, string itemName, string context, - CancellationToken cancellationToken = default(CancellationToken)); + CancellationToken cancellationToken = default); /// /// Gets the content upload history. @@ -1192,7 +1160,7 @@ Task SendContextMessageAsync(string itemType, string itemId, string itemName, st /// Task. Task UploadFile(Stream stream, LocalFileInfo file, - CancellationToken cancellationToken = default(CancellationToken)); + CancellationToken cancellationToken = default); /// /// Gets the devices options options. @@ -1207,137 +1175,11 @@ Task UploadFile(Stream stream, /// Task. Task UpdateItem(BaseItemDto item); - /// - /// Creates the synchronize job. - /// - /// The request. - /// Task<SyncJob>. - Task CreateSyncJob(SyncJobRequest request); - - /// - /// Updates the synchronize job. - /// - /// The job. - /// Task. - Task UpdateSyncJob(SyncJob job); - - /// - /// Gets the synchronize jobs. - /// - /// The query. - /// Task<QueryResult<SyncJob>>. - Task> GetSyncJobs(SyncJobQuery query); - - /// - /// Gets the synchronize job items. - /// - /// The query. - /// Task<QueryResult<SyncJobItem>>. - Task> GetSyncJobItems(SyncJobItemQuery query); - - /// - /// Reports the synchronize job item transferred. - /// - /// The identifier. - /// Task. - Task ReportSyncJobItemTransferred(string id); - - /// - /// Gets the synchronize job item file. - /// - /// The identifier. - /// The cancellation token. - /// Task<Stream>. - Task GetSyncJobItemFile(string id, CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Gets the synchronize job item additional file. - /// - /// The identifier. - /// The name. - /// The cancellation token. - /// Task<Stream>. - Task GetSyncJobItemAdditionalFile(string id, string name, CancellationToken cancellationToken); - /// /// Opens the web socket. /// void OpenWebSocket(Func webSocketFactory); - /// - /// Reports the offline actions. - /// - /// The actions. - /// Task. - Task ReportOfflineActions(List actions); - - /// - /// Gets the ready synchronize items. - /// - /// The target identifier. - /// List<SyncedItem>. - Task> GetReadySyncItems(string targetId); - - /// - /// Synchronizes the data. - /// - /// The request. - /// Task<SyncDataResponse>. - Task SyncData(SyncDataRequest request); - /// - /// Gets the synchronize job item file URL. - /// - /// The identifier. - /// System.String. - string GetSyncJobItemFileUrl(string id); - /// - /// Marks the synchronize job item for removal. - /// - /// The identifier. - /// Task. - Task MarkSyncJobItemForRemoval(string id); - /// - /// Unmarks the synchronize job item for removal. - /// - /// The identifier. - /// Task. - Task UnmarkSyncJobItemForRemoval(string id); - /// - /// Queues the failed synchronize job item for retry. - /// - /// The identifier. - /// Task. - Task QueueFailedSyncJobItemForRetry(string id); - /// - /// Cancels the synchronize job. - /// - /// The identifier. - /// Task. - Task CancelSyncJob(string id); - /// - /// Cancels the synchronize job item. - /// - /// The identifier. - /// Task. - Task CancelSyncJobItem(string id); - /// - /// Enables the cancelled synchronize job item. - /// - /// The identifier. - /// Task. - Task EnableCancelledSyncJobItem(string id); - /// - /// Gets the synchronize options. - /// - /// The job information. - /// Task<SyncOptions>. - Task GetSyncOptions(SyncJobRequest jobInfo); - /// - /// Gets the synchronize options. - /// - /// The job information. - /// Task<SyncDialogOptions>. - Task GetSyncOptions(SyncJob jobInfo); /// /// Gets the movie recommendations. /// @@ -1352,13 +1194,6 @@ Task UploadFile(Stream stream, /// Task<LiveStreamResponse>. Task OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken); /// - /// Cancels the synchronize library items. - /// - /// The target identifier. - /// The item ids. - /// Task. - Task CancelSyncLibraryItems(string targetId, IEnumerable itemIds); - /// /// Gets the supported bitrate. /// /// The cancellation token. diff --git a/Emby.ApiClient/Model/IClientWebSocket.cs b/Jellyfin.ApiClient/Model/IClientWebSocket.cs similarity index 91% rename from Emby.ApiClient/Model/IClientWebSocket.cs rename to Jellyfin.ApiClient/Model/IClientWebSocket.cs index cce8731..24c8b70 100644 --- a/Emby.ApiClient/Model/IClientWebSocket.cs +++ b/Jellyfin.ApiClient/Model/IClientWebSocket.cs @@ -1,9 +1,9 @@ -using MediaBrowser.Model.Net; +using Jellyfin.ApiClient.WebSocket; using System; using System.Threading; using System.Threading.Tasks; -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { /// /// Interface IClientWebSocket @@ -27,7 +27,7 @@ public interface IClientWebSocket : IDisposable /// The URL. /// The cancellation token. /// Task. - Task ConnectAsync(string url, CancellationToken cancellationToken); + Task ConnectAsync(Uri url, CancellationToken cancellationToken); /// /// Gets or sets the receive action. diff --git a/Emby.ApiClient/Model/IConnectionManager.cs b/Jellyfin.ApiClient/Model/IConnectionManager.cs similarity index 65% rename from Emby.ApiClient/Model/IConnectionManager.cs rename to Jellyfin.ApiClient/Model/IConnectionManager.cs index 7aa84fc..da3c519 100644 --- a/Emby.ApiClient/Model/IConnectionManager.cs +++ b/Jellyfin.ApiClient/Model/IConnectionManager.cs @@ -1,14 +1,12 @@ -using MediaBrowser.Model.Connect; -using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.ApiClient; -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { public interface IConnectionManager { @@ -21,18 +19,10 @@ public interface IConnectionManager /// event EventHandler> LocalUserSignIn; /// - /// Occurs when [connect user sign in]. - /// - event EventHandler> ConnectUserSignIn; - /// /// Occurs when [local user sign out]. /// event EventHandler> LocalUserSignOut; /// - /// Occurs when [connect user sign out]. - /// - event EventHandler ConnectUserSignOut; - /// /// Occurs when [remote logged out]. /// event EventHandler RemoteLoggedOut; @@ -43,12 +33,6 @@ public interface IConnectionManager /// The device. IDevice Device { get; } - /// - /// Gets the connect user. - /// - /// The connect user. - ConnectUser ConnectUser { get; } - /// /// Gets or sets a value indicating whether [save local credentials]. /// @@ -80,7 +64,7 @@ public interface IConnectionManager /// /// The cancellation token. /// Task<ConnectionResult>. - Task Connect(CancellationToken cancellationToken = default(CancellationToken)); + Task Connect(CancellationToken cancellationToken = default); /// /// Connects the specified API client. @@ -88,7 +72,7 @@ public interface IConnectionManager /// The API client. /// The cancellation token. /// Task<ConnectionResult>. - Task Connect(IApiClient apiClient, CancellationToken cancellationToken = default(CancellationToken)); + Task Connect(IApiClient apiClient, CancellationToken cancellationToken = default); /// /// Connects the specified server. @@ -96,7 +80,7 @@ public interface IConnectionManager /// The server. /// The cancellation token. /// Task<ConnectionResult>. - Task Connect(ServerInfo server, CancellationToken cancellationToken = default(CancellationToken)); + Task Connect(ServerInfo server, CancellationToken cancellationToken = default); /// /// Connects the specified server. @@ -105,7 +89,7 @@ public interface IConnectionManager /// The options. /// The cancellation token. /// Task<ConnectionResult>. - Task Connect(ServerInfo server, ConnectionOptions options, CancellationToken cancellationToken = default(CancellationToken)); + Task Connect(ServerInfo server, ConnectionOptions options, CancellationToken cancellationToken = default); /// /// Connects the specified server. @@ -113,7 +97,7 @@ public interface IConnectionManager /// The address. /// The cancellation token. /// Task<ConnectionResult>. - Task Connect(string address, CancellationToken cancellationToken = default(CancellationToken)); + Task Connect(Uri address, CancellationToken cancellationToken = default); /// /// Logouts this instance. @@ -121,37 +105,11 @@ public interface IConnectionManager /// Task<ConnectionResult>. Task Logout(); - /// - /// Logins to connect. - /// - /// Task. - Task LoginToConnect(string username, string password); - /// /// Gets the active api client instance /// IApiClient CurrentApiClient { get; } - /// - /// Creates the pin. - /// - /// Task<PinCreationResult>. - Task CreatePin(); - - /// - /// Gets the pin status. - /// - /// The pin. - /// Task<PinStatusResult>. - Task GetPinStatus(PinCreationResult pin); - - /// - /// Exchanges the pin. - /// - /// The pin. - /// Task. - Task ExchangePin(PinCreationResult pin); - /// /// Gets the server information. /// @@ -163,16 +121,6 @@ public interface IConnectionManager /// Gets the available servers. /// /// The cancellation token. - Task> GetAvailableServers(CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Signups for connect. - /// - /// The email. - /// The username. - /// The password. - /// The cancellation token. - /// Task. - Task SignupForConnect(string email, string username, string password, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetAvailableServers(CancellationToken cancellationToken = default); } } diff --git a/Jellyfin.ApiClient/Model/IDevice.cs b/Jellyfin.ApiClient/Model/IDevice.cs new file mode 100644 index 0000000..6cb76cc --- /dev/null +++ b/Jellyfin.ApiClient/Model/IDevice.cs @@ -0,0 +1,16 @@ +namespace Jellyfin.ApiClient.Model +{ + public interface IDevice + { + /// + /// Gets the name of the device. + /// + /// The name of the device. + string DeviceName { get; } + /// + /// Gets the device identifier. + /// + /// The device identifier. + string DeviceId { get; } + } +} diff --git a/Emby.ApiClient/Model/IServerEvents.cs b/Jellyfin.ApiClient/Model/IServerEvents.cs similarity index 85% rename from Emby.ApiClient/Model/IServerEvents.cs rename to Jellyfin.ApiClient/Model/IServerEvents.cs index f1cfd67..986c7b5 100644 --- a/Emby.ApiClient/Model/IServerEvents.cs +++ b/Jellyfin.ApiClient/Model/IServerEvents.cs @@ -1,16 +1,14 @@ -using MediaBrowser.Model.Dto; +using Jellyfin.ApiClient.Net; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Sync; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Updates; using System; -using System.Collections.Generic; -using MediaBrowser.Model.ApiClient; -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { /// /// Interface IServerEvents @@ -133,21 +131,5 @@ public interface IServerEvents /// Occurs when [session ended]. /// event EventHandler> SessionEnded; - /// - /// Occurs when [synchronize job created]. - /// - event EventHandler> SyncJobCreated; - /// - /// Occurs when [synchronize job cancelled]. - /// - event EventHandler> SyncJobCancelled; - /// - /// Occurs when [synchronize jobs updated]. - /// - event EventHandler>> SyncJobsUpdated; - /// - /// Occurs when [synchronize job updated]. - /// - event EventHandler> SyncJobUpdated; } } diff --git a/Jellyfin.ApiClient/Model/IgnoreDataMemberAttribute.cs b/Jellyfin.ApiClient/Model/IgnoreDataMemberAttribute.cs new file mode 100644 index 0000000..90ec611 --- /dev/null +++ b/Jellyfin.ApiClient/Model/IgnoreDataMemberAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Jellyfin.ApiClient.Model +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] + public sealed class IgnoreDataMemberAttribute : Attribute + { + public IgnoreDataMemberAttribute() + { + } + } +} diff --git a/Emby.ApiClient/Model/ItemQuery.cs b/Jellyfin.ApiClient/Model/ItemQuery.cs similarity index 98% rename from Emby.ApiClient/Model/ItemQuery.cs rename to Jellyfin.ApiClient/Model/ItemQuery.cs index 7f23c1e..03eadd9 100644 --- a/Emby.ApiClient/Model/ItemQuery.cs +++ b/Jellyfin.ApiClient/Model/ItemQuery.cs @@ -1,8 +1,8 @@ using MediaBrowser.Model.Entities; -using System; using MediaBrowser.Model.Querying; +using System; -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { /// /// Contains all the possible parameters that can be used to query for items @@ -13,7 +13,7 @@ public class ItemQuery /// The user to localize search results for /// /// The user id. - public string UserId { get; set; } + public Guid UserId { get; set; } /// /// Specify this to localize the search to a specific item or folder. Omit to use the root. @@ -282,11 +282,8 @@ public class ItemQuery public int? ImageTypeLimit { get; set; } public ImageType[] EnableImageTypes { get; set; } - [Obsolete] public string[] Artists { get; set; } - [Obsolete] public string[] Studios { get; set; } - [Obsolete] public string Person { get; set; } public bool EnableTotalRecordCount { get; set; } diff --git a/Jellyfin.ApiClient/Model/ItemReview.cs b/Jellyfin.ApiClient/Model/ItemReview.cs new file mode 100644 index 0000000..05bf010 --- /dev/null +++ b/Jellyfin.ApiClient/Model/ItemReview.cs @@ -0,0 +1,52 @@ +using System; + +namespace Jellyfin.ApiClient.Model +{ + /// + /// Class ItemReview + /// + public class ItemReview + { + /// + /// Gets or sets the name of the reviewer. + /// + /// The name of the reviewer. + public string ReviewerName { get; set; } + + /// + /// Gets or sets the publisher. + /// + /// The publisher. + public string Publisher { get; set; } + + /// + /// Gets or sets the date. + /// + /// The date. + public DateTime Date { get; set; } + + /// + /// Gets or sets the score. + /// + /// The score. + public float? Score { get; set; } + + /// + /// Gets or sets a value indicating whether this is likes. + /// + /// null if [likes] contains no value, true if [likes]; otherwise, false. + public bool? Likes { get; set; } + + /// + /// Gets or sets the URL. + /// + /// The URL. + public string Url { get; set; } + + /// + /// Gets or sets the caption. + /// + /// The caption. + public string Caption { get; set; } + } +} diff --git a/Emby.ApiClient/Model/NetworkStatus.cs b/Jellyfin.ApiClient/Model/NetworkStatus.cs similarity index 96% rename from Emby.ApiClient/Model/NetworkStatus.cs rename to Jellyfin.ApiClient/Model/NetworkStatus.cs index cf8358b..d133ea9 100644 --- a/Emby.ApiClient/Model/NetworkStatus.cs +++ b/Jellyfin.ApiClient/Model/NetworkStatus.cs @@ -1,5 +1,4 @@ - -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { public class NetworkStatus { diff --git a/Jellyfin.ApiClient/Model/Notifications/Notification.cs b/Jellyfin.ApiClient/Model/Notifications/Notification.cs new file mode 100644 index 0000000..f58ea88 --- /dev/null +++ b/Jellyfin.ApiClient/Model/Notifications/Notification.cs @@ -0,0 +1,16 @@ +using System; + +namespace Jellyfin.ApiClient.Model.Notifications +{ + public class Notification + { + public string Id { get; set; } + public string UserId { get; set; } + public DateTime Date { get; set; } + public bool IsRead { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string Url { get; set; } + public NotificationLevel Level { get; set; } + } +} diff --git a/Jellyfin.ApiClient/Model/Notifications/NotificationLevel.cs b/Jellyfin.ApiClient/Model/Notifications/NotificationLevel.cs new file mode 100644 index 0000000..3012a88 --- /dev/null +++ b/Jellyfin.ApiClient/Model/Notifications/NotificationLevel.cs @@ -0,0 +1,9 @@ +namespace Jellyfin.ApiClient.Model.Notifications +{ + public enum NotificationLevel + { + Normal = 0, + Warning = 1, + Error = 2 + } +} diff --git a/Jellyfin.ApiClient/Model/Notifications/NotificationQuery.cs b/Jellyfin.ApiClient/Model/Notifications/NotificationQuery.cs new file mode 100644 index 0000000..2dd715e --- /dev/null +++ b/Jellyfin.ApiClient/Model/Notifications/NotificationQuery.cs @@ -0,0 +1,10 @@ +namespace Jellyfin.ApiClient.Model.Notifications +{ + public class NotificationQuery + { + public string UserId { get; set; } + public bool? IsRead { get; set; } + public int? StartIndex { get; set; } + public int? Limit { get; set; } + } +} diff --git a/Jellyfin.ApiClient/Model/Notifications/NotificationResult.cs b/Jellyfin.ApiClient/Model/Notifications/NotificationResult.cs new file mode 100644 index 0000000..0c5be2e --- /dev/null +++ b/Jellyfin.ApiClient/Model/Notifications/NotificationResult.cs @@ -0,0 +1,8 @@ +namespace Jellyfin.ApiClient.Model.Notifications +{ + public class NotificationResult + { + public Notification[] Notifications { get; set; } + public int TotalRecordCount { get; set; } + } +} diff --git a/Jellyfin.ApiClient/Model/Notifications/NotificationsSummary.cs b/Jellyfin.ApiClient/Model/Notifications/NotificationsSummary.cs new file mode 100644 index 0000000..2a475f8 --- /dev/null +++ b/Jellyfin.ApiClient/Model/Notifications/NotificationsSummary.cs @@ -0,0 +1,8 @@ +namespace Jellyfin.ApiClient.Model.Notifications +{ + public class NotificationsSummary + { + public int UnreadCount { get; set; } + public NotificationLevel MaxUnreadNotificationLevel { get; set; } + } +} diff --git a/Jellyfin.ApiClient/Model/Querying/AllChannelMediaQuery.cs b/Jellyfin.ApiClient/Model/Querying/AllChannelMediaQuery.cs new file mode 100644 index 0000000..53de502 --- /dev/null +++ b/Jellyfin.ApiClient/Model/Querying/AllChannelMediaQuery.cs @@ -0,0 +1,61 @@ +using MediaBrowser.Model.Channels; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; + +namespace Jellyfin.ApiClient.Model.Querying +{ + public class AllChannelMediaQuery + { + /// + /// Gets or sets the channel ids. + /// + /// The channel ids. + public string[] ChannelIds { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + public int? Limit { get; set; } + + /// + /// Gets or sets the content types. + /// + /// The content types. + public ChannelMediaContentType[] ContentTypes { get; set; } + + /// + /// Gets or sets the extra types. + /// + /// The extra types. + public ExtraType[] ExtraTypes { get; set; } + public TrailerType[] TrailerTypes { get; set; } + + public AllChannelMediaQuery() + { + ChannelIds = new string[] { }; + + ContentTypes = new ChannelMediaContentType[] { }; + ExtraTypes = new ExtraType[] { }; + TrailerTypes = new TrailerType[] { }; + + Filters = new ItemFilter[] { }; + Fields = new ItemFields[] { }; + } + + public ItemFilter[] Filters { get; set; } + public ItemFields[] Fields { get; set; } + } +} diff --git a/Jellyfin.ApiClient/Model/Querying/ArtistsQuery.cs b/Jellyfin.ApiClient/Model/Querying/ArtistsQuery.cs new file mode 100644 index 0000000..f4c39f6 --- /dev/null +++ b/Jellyfin.ApiClient/Model/Querying/ArtistsQuery.cs @@ -0,0 +1,14 @@ +namespace Jellyfin.ApiClient.Model.Querying +{ + /// + /// Class ArtistsQuery + /// + public class ArtistsQuery : ItemsByNameQuery + { + /// + /// Filter by artists that are on tour, or not + /// + /// null if [is on tour] contains no value, true if [is on tour]; otherwise, false. + public bool? IsOnTour { get; set; } + } +} diff --git a/Jellyfin.ApiClient/Model/Querying/ChannelItemQuery.cs b/Jellyfin.ApiClient/Model/Querying/ChannelItemQuery.cs new file mode 100644 index 0000000..43b5dbf --- /dev/null +++ b/Jellyfin.ApiClient/Model/Querying/ChannelItemQuery.cs @@ -0,0 +1,50 @@ +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using System; + +namespace Jellyfin.ApiClient.Model.Querying +{ + public class ChannelItemQuery + { + /// + /// Gets or sets the channel identifier. + /// + /// The channel identifier. + public string ChannelId { get; set; } + + /// + /// Gets or sets the category identifier. + /// + /// The category identifier. + public string FolderId { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + public int? Limit { get; set; } + + public ItemFilter[] Filters { get; set; } + public ItemFields[] Fields { get; set; } + public Tuple[] OrderBy { get; set; } + + public ChannelItemQuery() + { + Filters = new ItemFilter[] { }; + Fields = new ItemFields[] { }; + OrderBy = new Tuple[] { }; + } + } +} diff --git a/Jellyfin.ApiClient/Model/Querying/ItemsByNameQuery.cs b/Jellyfin.ApiClient/Model/Querying/ItemsByNameQuery.cs new file mode 100644 index 0000000..8582ae2 --- /dev/null +++ b/Jellyfin.ApiClient/Model/Querying/ItemsByNameQuery.cs @@ -0,0 +1,137 @@ +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; + +namespace Jellyfin.ApiClient.Model.Querying +{ + /// + /// Class ItemsByNameQuery + /// + public class ItemsByNameQuery + { + /// + /// Gets or sets the user id. + /// + /// The user id. + public string UserId { get; set; } + /// + /// Gets or sets the start index. + /// + /// The start index. + public int? StartIndex { get; set; } + /// + /// Gets or sets the size of the page. + /// + /// The size of the page. + public int? Limit { get; set; } + /// + /// Gets or sets a value indicating whether this is recursive. + /// + /// true if recursive; otherwise, false. + public bool Recursive { get; set; } + /// + /// Gets or sets the sort order. + /// + /// The sort order. + public SortOrder? SortOrder { get; set; } + /// + /// Gets or sets the parent id. + /// + /// The parent id. + public string ParentId { get; set; } + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + public ItemFields[] Fields { get; set; } + + /// + /// Gets or sets the filters. + /// + /// The filters. + public ItemFilter[] Filters { get; set; } + + /// + /// Gets or sets the exclude item types. + /// + /// The exclude item types. + public string[] ExcludeItemTypes { get; set; } + + /// + /// Gets or sets the include item types. + /// + /// The include item types. + public string[] IncludeItemTypes { get; set; } + + /// + /// Gets or sets the media types. + /// + /// The media types. + public string[] MediaTypes { get; set; } + + /// + /// What to sort the results by + /// + /// The sort by. + public string[] SortBy { get; set; } + + /// + /// Gets or sets the image types. + /// + /// The image types. + public ImageType[] ImageTypes { get; set; } + + /// + /// Gets or sets the name starts with or greater. + /// + /// The name starts with or greater. + public string NameStartsWithOrGreater { get; set; } + + /// + /// Gets or sets the name starts with + /// + /// The name starts with or greater. + public string NameStartsWith { get; set; } + /// + /// Gets or sets the name less than. + /// + /// The name less than. + public string NameLessThan { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is played. + /// + /// null if [is played] contains no value, true if [is played]; otherwise, false. + public bool? IsPlayed { get; set; } + /// + /// Gets or sets a value indicating whether [enable images]. + /// + /// null if [enable images] contains no value, true if [enable images]; otherwise, false. + public bool? EnableImages { get; set; } + /// + /// Gets or sets the image type limit. + /// + /// The image type limit. + public int? ImageTypeLimit { get; set; } + /// + /// Gets or sets the enable image types. + /// + /// The enable image types. + public ImageType[] EnableImageTypes { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public ItemsByNameQuery() + { + ImageTypes = new ImageType[] { }; + Filters = new ItemFilter[] { }; + Fields = new ItemFields[] { }; + Recursive = true; + MediaTypes = new string[] { }; + SortBy = new string[] { }; + ExcludeItemTypes = new string[] { }; + IncludeItemTypes = new string[] { }; + EnableImageTypes = new ImageType[] { }; + } + } +} diff --git a/Jellyfin.ApiClient/Model/Querying/PersonsQuery.cs b/Jellyfin.ApiClient/Model/Querying/PersonsQuery.cs new file mode 100644 index 0000000..febaf3c --- /dev/null +++ b/Jellyfin.ApiClient/Model/Querying/PersonsQuery.cs @@ -0,0 +1,22 @@ +namespace Jellyfin.ApiClient.Model.Querying +{ + /// + /// Class PersonsQuery + /// + public class PersonsQuery : ItemsByNameQuery + { + /// + /// Gets or sets the person types. + /// + /// The person types. + public string[] PersonTypes { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public PersonsQuery() + { + PersonTypes = new string[] { }; + } + } +} diff --git a/Jellyfin.ApiClient/Model/Querying/ProgramQuery.cs b/Jellyfin.ApiClient/Model/Querying/ProgramQuery.cs new file mode 100644 index 0000000..5e5509d --- /dev/null +++ b/Jellyfin.ApiClient/Model/Querying/ProgramQuery.cs @@ -0,0 +1,117 @@ +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using System; + +namespace Jellyfin.ApiClient.Model.Querying +{ + /// + /// Class ProgramQuery. + /// + public class ProgramQuery + { + public ProgramQuery() + { + ChannelIds = new string[] { }; + OrderBy = new Tuple[] { }; + Genres = new string[] { }; + GenreIds = new string[] { }; + EnableTotalRecordCount = true; + EnableUserData = true; + } + + public bool EnableTotalRecordCount { get; set; } + public bool EnableUserData { get; set; } + + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + public ItemFields[] Fields { get; set; } + public bool? EnableImages { get; set; } + public int? ImageTypeLimit { get; set; } + public ImageType[] EnableImageTypes { get; set; } + + /// + /// Gets or sets the channel ids. + /// + /// The channel ids. + public string[] ChannelIds { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + public string SeriesTimerId { get; set; } + public string Name { get; set; } + + /// + /// The earliest date for which a program starts to return + /// + public DateTime? MinStartDate { get; set; } + + /// + /// The latest date for which a program starts to return + /// + public DateTime? MaxStartDate { get; set; } + + /// + /// The earliest date for which a program ends to return + /// + public DateTime? MinEndDate { get; set; } + + /// + /// The latest date for which a program ends to return + /// + public DateTime? MaxEndDate { get; set; } + + /// + /// Used to specific whether to return news or not + /// + /// If set to null, all programs will be returned + public bool? IsNews { get; set; } + + /// + /// Used to specific whether to return movies or not + /// + /// If set to null, all programs will be returned + public bool? IsMovie { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is kids. + /// + /// null if [is kids] contains no value, true if [is kids]; otherwise, false. + public bool? IsKids { get; set; } + /// + /// Gets or sets a value indicating whether this instance is sports. + /// + /// null if [is sports] contains no value, true if [is sports]; otherwise, false. + public bool? IsSports { get; set; } + + /// + /// Skips over a given number of items within the results. Use for paging. + /// + public int? StartIndex { get; set; } + public bool? IsSeries { get; set; } + + /// + /// Gets or sets a value indicating whether this instance has aired. + /// + /// null if [has aired] contains no value, true if [has aired]; otherwise, false. + public bool? HasAired { get; set; } + + /// + /// The maximum number of items to return + /// + public int? Limit { get; set; } + + public Tuple[] OrderBy { get; set; } + + /// + /// Limit results to items containing specific genres + /// + /// The genres. + public string[] GenreIds { get; set; } + public string[] Genres { get; set; } + } +} diff --git a/Jellyfin.ApiClient/Model/Querying/RecommendedProgramQuery.cs b/Jellyfin.ApiClient/Model/Querying/RecommendedProgramQuery.cs new file mode 100644 index 0000000..c78fc13 --- /dev/null +++ b/Jellyfin.ApiClient/Model/Querying/RecommendedProgramQuery.cs @@ -0,0 +1,70 @@ +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; + +namespace Jellyfin.ApiClient.Model.Querying +{ + public class RecommendedProgramQuery + { + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + public ItemFields[] Fields { get; set; } + public bool? EnableImages { get; set; } + public int? ImageTypeLimit { get; set; } + public ImageType[] EnableImageTypes { get; set; } + + public bool EnableTotalRecordCount { get; set; } + + public RecommendedProgramQuery() + { + EnableTotalRecordCount = true; + } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is airing. + /// + /// true if this instance is airing; otherwise, false. + public bool? IsAiring { get; set; } + + /// + /// Gets or sets a value indicating whether this instance has aired. + /// + /// null if [has aired] contains no value, true if [has aired]; otherwise, false. + public bool? HasAired { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + public int? Limit { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is movie. + /// + /// null if [is movie] contains no value, true if [is movie]; otherwise, false. + public bool? IsNews { get; set; } + /// + /// Gets or sets a value indicating whether this instance is movie. + /// + /// null if [is movie] contains no value, true if [is movie]; otherwise, false. + public bool? IsMovie { get; set; } + public bool? IsSeries { get; set; } + /// + /// Gets or sets a value indicating whether this instance is kids. + /// + /// null if [is kids] contains no value, true if [is kids]; otherwise, false. + public bool? IsKids { get; set; } + /// + /// Gets or sets a value indicating whether this instance is sports. + /// + /// null if [is sports] contains no value, true if [is sports]; otherwise, false. + public bool? IsSports { get; set; } + } +} diff --git a/Jellyfin.ApiClient/Model/Querying/RecordingGroupQuery.cs b/Jellyfin.ApiClient/Model/Querying/RecordingGroupQuery.cs new file mode 100644 index 0000000..bce2333 --- /dev/null +++ b/Jellyfin.ApiClient/Model/Querying/RecordingGroupQuery.cs @@ -0,0 +1,11 @@ +namespace Jellyfin.ApiClient.Model.Querying +{ + public class RecordingGroupQuery + { + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + } +} diff --git a/Emby.ApiClient/Model/RemoteLogoutReason.cs b/Jellyfin.ApiClient/Model/RemoteLogoutReason.cs similarity index 76% rename from Emby.ApiClient/Model/RemoteLogoutReason.cs rename to Jellyfin.ApiClient/Model/RemoteLogoutReason.cs index f94820c..9da2e61 100644 --- a/Emby.ApiClient/Model/RemoteLogoutReason.cs +++ b/Jellyfin.ApiClient/Model/RemoteLogoutReason.cs @@ -1,5 +1,4 @@ - -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { public enum RemoteLogoutReason { diff --git a/Emby.ApiClient/Model/SeasonQuery.cs b/Jellyfin.ApiClient/Model/SeasonQuery.cs similarity index 92% rename from Emby.ApiClient/Model/SeasonQuery.cs rename to Jellyfin.ApiClient/Model/SeasonQuery.cs index 878c967..f8e56b3 100644 --- a/Emby.ApiClient/Model/SeasonQuery.cs +++ b/Jellyfin.ApiClient/Model/SeasonQuery.cs @@ -1,21 +1,15 @@ using MediaBrowser.Model.Querying; -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { public class SeasonQuery { public string UserId { get; set; } - public string SeriesId { get; set; } - public bool? IsMissing { get; set; } - public bool? IsVirtualUnaired { get; set; } - public ItemFields[] Fields { get; set; } - public bool? IsSpecialSeason { get; set; } - public SeasonQuery() { Fields = new ItemFields[] { }; diff --git a/Emby.ApiClient/Model/ServerCredentials.cs b/Jellyfin.ApiClient/Model/ServerCredentials.cs similarity index 61% rename from Emby.ApiClient/Model/ServerCredentials.cs rename to Jellyfin.ApiClient/Model/ServerCredentials.cs index 4b1cd67..8e3bdc1 100644 --- a/Emby.ApiClient/Model/ServerCredentials.cs +++ b/Jellyfin.ApiClient/Model/ServerCredentials.cs @@ -1,14 +1,12 @@ using MediaBrowser.Model.Extensions; using System; using System.Collections.Generic; -using MediaBrowser.Model.ApiClient; -namespace Emby.ApiClient.Model +namespace Jellyfin.ApiClient.Model { public class ServerCredentials { public List Servers { get; set; } - public string ConnectUserId { get; set; } public string ConnectAccessToken { get; set; } @@ -42,50 +40,20 @@ public void AddOrUpdateServer(ServerInfo server) { existing.DateLastAccessed = server.DateLastAccessed; } - - existing.UserLinkType = server.UserLinkType; if (!string.IsNullOrEmpty(server.AccessToken)) { existing.AccessToken = server.AccessToken; existing.UserId = server.UserId; } - if (!string.IsNullOrEmpty(server.ExchangeToken)) - { - existing.ExchangeToken = server.ExchangeToken; - } - if (!string.IsNullOrEmpty(server.RemoteAddress)) - { - existing.RemoteAddress = server.RemoteAddress; - } - if (!string.IsNullOrEmpty(server.ConnectServerId)) - { - existing.ConnectServerId = server.ConnectServerId; - } - if (!string.IsNullOrEmpty(server.LocalAddress)) + if (!string.IsNullOrEmpty(server.Address.ToString())) { - existing.LocalAddress = server.LocalAddress; - } - if (!string.IsNullOrEmpty(server.ManualAddress)) - { - existing.ManualAddress = server.ManualAddress; + existing.Address = server.Address; } if (!string.IsNullOrEmpty(server.Name)) { existing.Name = server.Name; } - if (server.WakeOnLanInfos != null && server.WakeOnLanInfos.Count > 0) - { - existing.WakeOnLanInfos = new List(); - foreach (WakeOnLanInfo info in server.WakeOnLanInfos) - { - existing.WakeOnLanInfos.Add(info); - } - } - if (server.LastConnectionMode.HasValue) - { - existing.LastConnectionMode = server.LastConnectionMode; - } } else { diff --git a/Jellyfin.ApiClient/Model/ServerInfo.cs b/Jellyfin.ApiClient/Model/ServerInfo.cs new file mode 100644 index 0000000..3a8f7fe --- /dev/null +++ b/Jellyfin.ApiClient/Model/ServerInfo.cs @@ -0,0 +1,35 @@ +using MediaBrowser.Model.System; +using System; + +namespace Jellyfin.ApiClient.Model +{ + public class ServerInfo + { + public String Name { get; set; } + public String Id { get; set; } + public Uri Address { get; set; } + public Guid UserId { get; set; } + public String AccessToken { get; set; } + public DateTime DateLastAccessed { get; set; } + + public ServerInfo() + { + } + + public void ImportInfo(PublicSystemInfo systemInfo) + { + Name = systemInfo.ServerName; + Id = systemInfo.Id; + + if (!string.IsNullOrEmpty(systemInfo.LocalAddress)) + { + Address = new Uri(systemInfo.LocalAddress, UriKind.Relative); + } + + if (!string.IsNullOrEmpty(systemInfo.LocalAddress)) + { + Address = new Uri(systemInfo.LocalAddress, UriKind.Relative); + } + } + } +} diff --git a/Jellyfin.ApiClient/Model/SessionInfoDto.cs b/Jellyfin.ApiClient/Model/SessionInfoDto.cs new file mode 100644 index 0000000..32c6e66 --- /dev/null +++ b/Jellyfin.ApiClient/Model/SessionInfoDto.cs @@ -0,0 +1,119 @@ +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Session; +using System; + +namespace Jellyfin.ApiClient.Model +{ + public class SessionInfoDto + { + /// + /// Gets or sets the supported commands. + /// + /// The supported commands. + public string[] SupportedCommands { get; set; } + + /// + /// Gets or sets the playable media types. + /// + /// The playable media types. + public string[] PlayableMediaTypes { get; set; } + + /// + /// Gets or sets the id. + /// + /// The id. + public string Id { get; set; } + + public string ServerId { get; set; } + + /// + /// Gets or sets the user id. + /// + /// The user id. + public string UserId { get; set; } + + /// + /// Gets or sets the user primary image tag. + /// + /// The user primary image tag. + public string UserPrimaryImageTag { get; set; } + + /// + /// Gets or sets the name of the user. + /// + /// The name of the user. + public string UserName { get; set; } + + /// + /// Gets or sets the additional users present. + /// + /// The additional users present. + public SessionUserInfo[] AdditionalUsers { get; set; } + + /// + /// Gets or sets the application version. + /// + /// The application version. + public string ApplicationVersion { get; set; } + + /// + /// Gets or sets the type of the client. + /// + /// The type of the client. + public string Client { get; set; } + + /// + /// Gets or sets the last activity date. + /// + /// The last activity date. + public DateTime LastActivityDate { get; set; } + + /// + /// Gets or sets the now viewing item. + /// + /// The now viewing item. + public BaseItemDto NowViewingItem { get; set; } + + /// + /// Gets or sets the name of the device. + /// + /// The name of the device. + public string DeviceName { get; set; } + + /// + /// Gets or sets the now playing item. + /// + /// The now playing item. + public BaseItemDto NowPlayingItem { get; set; } + + /// + /// Gets or sets the device id. + /// + /// The device id. + public string DeviceId { get; set; } + + /// + /// Gets or sets the application icon URL. + /// + /// The application icon URL. + public string AppIconUrl { get; set; } + + /// + /// Gets or sets a value indicating whether [supports remote control]. + /// + /// true if [supports remote control]; otherwise, false. + public bool SupportsRemoteControl { get; set; } + + public PlayerStateInfo PlayState { get; set; } + + public TranscodingInfo TranscodingInfo { get; set; } + + public SessionInfoDto() + { + AdditionalUsers = new SessionUserInfo[] { }; + + PlayableMediaTypes = new string[] { }; + SupportedCommands = new string[] { }; + } + } +} diff --git a/Emby.ApiClient/QueryStringDictionary.cs b/Jellyfin.ApiClient/NameValueCollectionExtensions.cs similarity index 57% rename from Emby.ApiClient/QueryStringDictionary.cs rename to Jellyfin.ApiClient/NameValueCollectionExtensions.cs index 7444338..62083e9 100644 --- a/Emby.ApiClient/QueryStringDictionary.cs +++ b/Jellyfin.ApiClient/NameValueCollectionExtensions.cs @@ -1,31 +1,25 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Globalization; using System.Linq; +using System.Web; -namespace Emby.ApiClient +namespace Jellyfin.ApiClient { /// - /// Class QueryStringDictionary + /// Extensions for NameValueCollection /// - public class QueryStringDictionary : Dictionary + public static class NameValueCollectionExtensions { - /// - /// Initializes a new instance of the class. - /// - public QueryStringDictionary() - : base(StringComparer.OrdinalIgnoreCase) - { - } - /// /// Adds the specified name. /// /// The name. /// The value. - public void Add(string name, int value) + public static void Add(this NameValueCollection col, string name, int value) { - Add(name, value.ToString(CultureInfo.InvariantCulture)); + col.Add(name, value.ToString(CultureInfo.InvariantCulture)); } /// @@ -33,9 +27,9 @@ public void Add(string name, int value) /// /// The name. /// The value. - public void Add(string name, long value) + public static void Add(this NameValueCollection col, string name, long value) { - Add(name, value.ToString(CultureInfo.InvariantCulture)); + col.Add(name, value.ToString(CultureInfo.InvariantCulture)); } /// @@ -43,9 +37,9 @@ public void Add(string name, long value) /// /// The name. /// The value. - public void Add(string name, double value) + public static void Add(this NameValueCollection col, string name, double value) { - Add(name, value.ToString(CultureInfo.InvariantCulture)); + col.Add(name, value.ToString(CultureInfo.InvariantCulture)); } /// @@ -53,11 +47,11 @@ public void Add(string name, double value) /// /// The name. /// The value. - public void AddIfNotNullOrEmpty(string name, string value) + public static void AddIfNotNullOrEmpty(this NameValueCollection col, string name, string value) { if (!string.IsNullOrEmpty(value)) { - Add(name, value); + col.Add(name, value); } } @@ -66,11 +60,11 @@ public void AddIfNotNullOrEmpty(string name, string value) /// /// The name. /// The value. - public void AddIfNotNull(string name, int? value) + public static void AddIfNotNull(this NameValueCollection col, string name, int? value) { if (value.HasValue) { - Add(name, value.Value); + col.Add(name, value.Value); } } @@ -79,11 +73,11 @@ public void AddIfNotNull(string name, int? value) /// /// The name. /// The value. - public void AddIfNotNull(string name, double? value) + public static void AddIfNotNull(this NameValueCollection col, string name, double? value) { if (value.HasValue) { - Add(name, value.Value); + col.Add(name, value.Value); } } @@ -92,11 +86,11 @@ public void AddIfNotNull(string name, double? value) /// /// The name. /// The value. - public void AddIfNotNull(string name, long? value) + public static void AddIfNotNull(this NameValueCollection col, string name, long? value) { if (value.HasValue) { - Add(name, value.Value); + col.Add(name, value.Value); } } @@ -105,9 +99,9 @@ public void AddIfNotNull(string name, long? value) /// /// The name. /// if set to true [value]. - public void Add(string name, bool value) + public static void Add(this NameValueCollection col, string name, bool value) { - Add(name, value.ToString()); + col.Add(name, value.ToString(CultureInfo.InvariantCulture)); } /// @@ -115,11 +109,11 @@ public void Add(string name, bool value) /// /// The name. /// if set to true [value]. - public void AddIfNotNull(string name, bool? value) + public static void AddIfNotNull(this NameValueCollection col, string name, bool? value) { if (value.HasValue) { - Add(name, value.Value); + col.Add(name, value.Value); } } @@ -129,14 +123,14 @@ public void AddIfNotNull(string name, bool? value) /// The name. /// The value. /// value - public void Add(string name, IEnumerable value) + public static void Add(this NameValueCollection col, string name, IEnumerable value) { if (value == null) { throw new ArgumentNullException("value"); } - Add(name, string.Join(",", value.Select(v => v.ToString(CultureInfo.InvariantCulture)).ToArray())); + col.Add(name, string.Join(",", value.Select(v => v.ToString(CultureInfo.InvariantCulture)).ToArray())); } /// @@ -144,11 +138,11 @@ public void Add(string name, IEnumerable value) /// /// The name. /// The value. - public void AddIfNotNull(string name, IEnumerable value) + public static void AddIfNotNull(this NameValueCollection col, string name, IEnumerable value) { if (value != null) { - Add(name, value); + col.Add(name, value); } } @@ -158,16 +152,14 @@ public void AddIfNotNull(string name, IEnumerable value) /// The name. /// The value. /// value - public void Add(string name, IEnumerable value) + public static void Add(this NameValueCollection col, string name, IEnumerable value) { if (value == null) { throw new ArgumentNullException("value"); } - string paramValue = string.Join(",", value.ToArray()); - - Add(name, paramValue); + col.Add(name, string.Join(",", value.ToArray())); } /// @@ -175,11 +167,11 @@ public void Add(string name, IEnumerable value) /// /// The name. /// The value. - public void AddIfNotNull(string name, IEnumerable value) + public static void AddIfNotNull(this NameValueCollection col, string name, IEnumerable value) { if (value != null) { - Add(name, value); + col.Add(name, value); } } @@ -190,16 +182,14 @@ public void AddIfNotNull(string name, IEnumerable value) /// The value. /// The delimiter. /// value - public void Add(string name, IEnumerable value, string delimiter) + public static void Add(this NameValueCollection col, string name, IEnumerable value, string delimiter) { if (value == null) { throw new ArgumentNullException("value"); } - string paramValue = string.Join(delimiter, value.ToArray()); - - Add(name, paramValue); + col.Add(name, string.Join(delimiter, value.ToArray())); } /// @@ -208,50 +198,22 @@ public void Add(string name, IEnumerable value, string delimiter) /// The name. /// The value. /// The delimiter. - public void AddIfNotNull(string name, IEnumerable value, string delimiter) + public static void AddIfNotNull(this NameValueCollection col, string name, IEnumerable value, string delimiter) { if (value != null) { - Add(name, value, delimiter); + col.Add(name, value, delimiter); } } - /// - /// Gets the query string. - /// - /// System.String. - public string GetQueryString() + public static string ToQueryString(this NameValueCollection col) { - string[] queryParams = this.Select(i => string.Format("{0}={1}", i.Key, GetEncodedValue(i.Value))).ToArray(); - - return string.Join("&", queryParams); - } - - /// - /// Gets the encoded value. - /// - /// The value. - /// System.String. - private string GetEncodedValue(string value) - { - return value; - } - - /// - /// Gets the URL. - /// - /// The prefix. - /// System.String. - public string GetUrl(string prefix) - { - string query = GetQueryString(); - - if (string.IsNullOrEmpty(query)) - { - return prefix; - } - - return prefix + "?" + query; + var queryArray = ( + from key in col.AllKeys + from value in col.GetValues(key) + select string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value)) + ).ToArray(); + return string.Join("&", queryArray); } } } diff --git a/Jellyfin.ApiClient/Net/GeneralCommandEventArgs.cs b/Jellyfin.ApiClient/Net/GeneralCommandEventArgs.cs new file mode 100644 index 0000000..9958ee7 --- /dev/null +++ b/Jellyfin.ApiClient/Net/GeneralCommandEventArgs.cs @@ -0,0 +1,22 @@ +using MediaBrowser.Model.Session; + +namespace Jellyfin.ApiClient.Net +{ + /// + /// Class SystemCommandEventArgs + /// + public class GeneralCommandEventArgs + { + /// + /// Gets or sets the command. + /// + /// The command. + public GeneralCommand Command { get; set; } + + /// + /// Gets or sets the type of the known command. + /// + /// The type of the known command. + public GeneralCommandType? KnownCommandType { get; set; } + } +} diff --git a/Emby.ApiClient/Net/HttpHeaders.cs b/Jellyfin.ApiClient/Net/HttpHeaders.cs similarity index 96% rename from Emby.ApiClient/Net/HttpHeaders.cs rename to Jellyfin.ApiClient/Net/HttpHeaders.cs index f9b5a0d..b545526 100644 --- a/Emby.ApiClient/Net/HttpHeaders.cs +++ b/Jellyfin.ApiClient/Net/HttpHeaders.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Emby.ApiClient.Net +namespace Jellyfin.ApiClient.Net { public class HttpHeaders : Dictionary { diff --git a/Emby.ApiClient/Net/HttpRequest.cs b/Jellyfin.ApiClient/Net/HttpRequest.cs similarity index 83% rename from Emby.ApiClient/Net/HttpRequest.cs rename to Jellyfin.ApiClient/Net/HttpRequest.cs index 1c3e9c2..8742012 100644 --- a/Emby.ApiClient/Net/HttpRequest.cs +++ b/Jellyfin.ApiClient/Net/HttpRequest.cs @@ -1,9 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; -namespace Emby.ApiClient.Net +namespace Jellyfin.ApiClient.Net { public class HttpRequest { @@ -12,7 +13,7 @@ public class HttpRequest public string RequestContent { get; set; } public string RequestContentType { get; set; } public HttpHeaders RequestHeaders { get; set; } - public string Url { get; set; } + public Uri Url { get; set; } public Stream RequestStream { get; set; } public int Timeout { get; set; } @@ -29,7 +30,7 @@ public void SetPostData(IDictionary values) var postContent = string.Join("&", strings.ToArray()); RequestContent = postContent; - RequestContentType = "application/x-www-form-urlencoded"; + RequestContentType = "application/json"; } } } diff --git a/Emby.ApiClient/Net/HttpWebRequestClient.cs b/Jellyfin.ApiClient/Net/HttpWebRequestClient.cs similarity index 69% rename from Emby.ApiClient/Net/HttpWebRequestClient.cs rename to Jellyfin.ApiClient/Net/HttpWebRequestClient.cs index b757477..934ae8e 100644 --- a/Emby.ApiClient/Net/HttpWebRequestClient.cs +++ b/Jellyfin.ApiClient/Net/HttpWebRequestClient.cs @@ -1,6 +1,5 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; +using MediaBrowser.Model.Net; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.IO; @@ -10,14 +9,14 @@ using System.Threading; using System.Threading.Tasks; -namespace Emby.ApiClient.Net +namespace Jellyfin.ApiClient.Net { /// /// Class HttpWebRequestClient /// public class HttpWebRequestClient : IAsyncHttpClient { - public event EventHandler HttpResponseReceived; + public event EventHandler HttpResponseReceived; private readonly IHttpWebRequestFactory _requestFactory; /// @@ -28,32 +27,16 @@ public class HttpWebRequestClient : IAsyncHttpClient /// The status code. /// The headers. /// The request time. - private void OnResponseReceived(string url, + private void OnResponseReceived(Uri url, string verb, - HttpStatusCode statusCode, - Dictionary headers, + HttpWebResponse response, DateTime requestTime) { var duration = DateTime.Now - requestTime; - Logger.Debug("Received {0} status code after {1} ms from {2}: {3}", (int)statusCode, duration.TotalMilliseconds, verb, url); + Logger.LogDebug("Received {0} status code after {1} ms from {2}: {3}", (int)response.StatusCode, duration.TotalMilliseconds, verb, url); - if (HttpResponseReceived != null) - { - try - { - HttpResponseReceived(this, new HttpResponseEventArgs - { - Url = url, - StatusCode = statusCode, - Headers = headers - }); - } - catch (Exception ex) - { - Logger.ErrorException("Error in HttpResponseReceived event handler", ex); - } - } + HttpResponseReceived?.Invoke(this, response); } /// @@ -73,7 +56,7 @@ public HttpWebRequestClient(ILogger logger, IHttpWebRequestFactory requestFactor _requestFactory = requestFactory; } - public async Task GetResponse(HttpRequest options, bool sendFailureResponse = false) + public async Task GetResponse(HttpRequest options, bool sendFailureResponse = false) { options.CancellationToken.ThrowIfCancellationRequested(); @@ -105,7 +88,7 @@ public async Task GetResponse(HttpRequest options, bool sendFailur } } - Logger.Debug(options.Method + " {0}", options.Url); + Logger.LogDebug(options.Method + " {0}", options.Url); var requestTime = DateTime.Now; @@ -117,14 +100,13 @@ public async Task GetResponse(HttpRequest options, bool sendFailur var httpResponse = (HttpWebResponse)response; - var headers = ConvertHeaders(response); - OnResponseReceived(options.Url, options.Method, httpResponse.StatusCode, headers, requestTime); + OnResponseReceived(options.Url, options.Method, httpResponse, requestTime); EnsureSuccessStatusCode(httpResponse); options.CancellationToken.ThrowIfCancellationRequested(); - return GetResponse(httpResponse, headers); + return httpResponse; } catch (OperationCanceledException ex) { @@ -139,11 +121,9 @@ public async Task GetResponse(HttpRequest options, bool sendFailur var webException = ex as WebException ?? ex.InnerException as WebException; if (webException != null) { - var response = webException.Response as HttpWebResponse; - if (response != null) + if (webException.Response is HttpWebResponse response) { - var headers = ConvertHeaders(response); - return GetResponse(response, headers); + return response; } } } @@ -152,53 +132,11 @@ public async Task GetResponse(HttpRequest options, bool sendFailur } } - private HttpResponse GetResponse(HttpWebResponse httpResponse, Dictionary headers) - { - return new HttpResponse(httpResponse) - { - Content = httpResponse.GetResponseStream(), - - StatusCode = httpResponse.StatusCode, - - ContentType = httpResponse.ContentType, - - Headers = headers, - - ContentLength = GetContentLength(httpResponse), - - ResponseUrl = httpResponse.ResponseUri.ToString() - }; - } - - private long? GetContentLength(HttpWebResponse response) - { - var length = response.ContentLength; - - if (length == 0) - { - return null; - } - - return length; - } - public async Task SendAsync(HttpRequest options) { var response = await GetResponse(options).ConfigureAwait(false); - return response.Content; - } - - /// - /// Converts the headers. - /// - /// The response. - /// Dictionary<System.String, System.String>. - private Dictionary ConvertHeaders(WebResponse response) - { - var headers = response.Headers; - - return headers.Cast().ToDictionary(p => p, p => headers[p]); + return response.GetResponseStream(); } private Exception GetExceptionToThrow(Exception ex, HttpRequest options, DateTime requestTime) @@ -207,15 +145,14 @@ private Exception GetExceptionToThrow(Exception ex, HttpRequest options, DateTim if (webException != null) { - Logger.ErrorException("Error getting response from " + options.Url, ex); + Logger.LogError("Error getting response from " + options.Url, ex); var httpException = new HttpException(ex.Message, ex); - - var response = webException.Response as HttpWebResponse; - if (response != null) + + if (webException.Response is HttpWebResponse response) { httpException.StatusCode = response.StatusCode; - OnResponseReceived(options.Url, options.Method, response.StatusCode, ConvertHeaders(response), requestTime); + OnResponseReceived(options.Url, options.Method, response, requestTime); } return httpException; @@ -224,7 +161,7 @@ private Exception GetExceptionToThrow(Exception ex, HttpRequest options, DateTim var timeoutException = ex as TimeoutException ?? ex.InnerException as TimeoutException; if (timeoutException != null) { - Logger.ErrorException("Request timeout to " + options.Url, ex); + Logger.LogError("Request timeout to " + options.Url, ex); var httpException = new HttpException(ex.Message, ex) { @@ -234,7 +171,7 @@ private Exception GetExceptionToThrow(Exception ex, HttpRequest options, DateTim return httpException; } - Logger.ErrorException("Error getting response from " + options.Url, ex); + Logger.LogError("Error getting response from " + options.Url, ex); return ex; } @@ -259,14 +196,14 @@ private void ApplyHeaders(HttpHeaders headers, HttpWebRequest request) /// The cancellation token. /// The exception. /// Exception. - private Exception GetCancellationException(string url, CancellationToken cancellationToken, OperationCanceledException exception) + private Exception GetCancellationException(Uri url, CancellationToken cancellationToken, OperationCanceledException exception) { // 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); + Logger.LogError(msg); // Throw an HttpException so that the caller doesn't think it was cancelled by user code return new HttpException(msg, exception) diff --git a/Emby.ApiClient/Net/HttpWebRequestFactory.cs b/Jellyfin.ApiClient/Net/HttpWebRequestFactory.cs similarity index 83% rename from Emby.ApiClient/Net/HttpWebRequestFactory.cs rename to Jellyfin.ApiClient/Net/HttpWebRequestFactory.cs index 8a7b54c..7c2e56c 100644 --- a/Emby.ApiClient/Net/HttpWebRequestFactory.cs +++ b/Jellyfin.ApiClient/Net/HttpWebRequestFactory.cs @@ -2,19 +2,17 @@ using System.Collections.Generic; using System.IO; using System.Net; -using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; -namespace Emby.ApiClient.Net +namespace Jellyfin.ApiClient.Net { public class HttpWebRequestFactory : IHttpWebRequestFactory { - private static PropertyInfo _httpBehaviorPropertyInfo; public HttpWebRequest Create(HttpRequest options) { - var request = HttpWebRequest.CreateHttp(options.Url); + var request = WebRequest.CreateHttp(options.Url); request.AutomaticDecompression = DecompressionMethods.Deflate; request.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.Revalidate); @@ -23,14 +21,6 @@ public HttpWebRequest Create(HttpRequest options) request.Pipelined = true; request.Timeout = options.Timeout; - // This is a hack to prevent KeepAlive from getting disabled internally by the HttpWebRequest - /*var sp = request.ServicePoint; - if (_httpBehaviorPropertyInfo == null) - { - _httpBehaviorPropertyInfo = sp.GetType().GetProperty("HttpBehaviour", BindingFlags.Instance | BindingFlags.NonPublic); - } - _httpBehaviorPropertyInfo?.SetValue(sp, (byte)0, null);*/ - if (!string.IsNullOrEmpty(options.RequestContent) || string.Equals(options.Method, "post", StringComparison.OrdinalIgnoreCase)) { diff --git a/Emby.ApiClient/Net/IAsyncHttpClient.cs b/Jellyfin.ApiClient/Net/IAsyncHttpClient.cs similarity index 74% rename from Emby.ApiClient/Net/IAsyncHttpClient.cs rename to Jellyfin.ApiClient/Net/IAsyncHttpClient.cs index 03bc9cb..5bafff4 100644 --- a/Emby.ApiClient/Net/IAsyncHttpClient.cs +++ b/Jellyfin.ApiClient/Net/IAsyncHttpClient.cs @@ -1,10 +1,9 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Net; -using System; +using System; using System.IO; +using System.Net; using System.Threading.Tasks; -namespace Emby.ApiClient.Net +namespace Jellyfin.ApiClient.Net { /// /// Interface IHttpClient @@ -14,7 +13,7 @@ public interface IAsyncHttpClient : IDisposable /// /// Occurs when [HTTP response received]. /// - event EventHandler HttpResponseReceived; + event EventHandler HttpResponseReceived; /// /// Sends the asynchronous. @@ -31,6 +30,6 @@ public interface IAsyncHttpClient : IDisposable /// /// Task<HttpResponse>. /// - Task GetResponse(HttpRequest options, bool sendFailureResponse = false); + Task GetResponse(HttpRequest options, bool sendFailureResponse = false); } } diff --git a/Jellyfin.ApiClient/Net/INetworkConnection.cs b/Jellyfin.ApiClient/Net/INetworkConnection.cs new file mode 100644 index 0000000..fd2d4ae --- /dev/null +++ b/Jellyfin.ApiClient/Net/INetworkConnection.cs @@ -0,0 +1,25 @@ +using Jellyfin.ApiClient.Model; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Jellyfin.ApiClient.Net +{ + public interface INetworkConnection + { + /// + /// Occurs when [network changed]. + /// + event EventHandler NetworkChanged; + + /// + /// Gets the network status. + /// + /// NetworkStatus. + NetworkStatus GetNetworkStatus(); + +#if WINDOWS_UWP + bool HasUnmeteredConnection(); +#endif + } +} diff --git a/Emby.ApiClient/Net/IServerLocator.cs b/Jellyfin.ApiClient/Net/IServerLocator.cs similarity index 78% rename from Emby.ApiClient/Net/IServerLocator.cs rename to Jellyfin.ApiClient/Net/IServerLocator.cs index dc534fe..a872572 100644 --- a/Emby.ApiClient/Net/IServerLocator.cs +++ b/Jellyfin.ApiClient/Net/IServerLocator.cs @@ -3,13 +3,13 @@ using System.Threading; using System.Threading.Tasks; -namespace Emby.ApiClient.Net +namespace Jellyfin.ApiClient.Net { public interface IServerLocator { /// /// Attemps to discover the server within a local network /// - Task> FindServers(int timeoutMs, CancellationToken cancellationToken = default(CancellationToken)); + Task> FindServers(int timeoutMs, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/Emby.ApiClient/Net/NetworkConnection.cs b/Jellyfin.ApiClient/Net/NetworkConnection.cs similarity index 67% rename from Emby.ApiClient/Net/NetworkConnection.cs rename to Jellyfin.ApiClient/Net/NetworkConnection.cs index 35d9590..518306f 100644 --- a/Emby.ApiClient/Net/NetworkConnection.cs +++ b/Jellyfin.ApiClient/Net/NetworkConnection.cs @@ -1,14 +1,13 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; +using Jellyfin.ApiClient.Model; +using Microsoft.Extensions.Logging; using System; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using Emby.ApiClient.Model; -namespace Emby.ApiClient.Net +namespace Jellyfin.ApiClient.Net { public class NetworkConnection : INetworkConnection { @@ -38,43 +37,6 @@ void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailability public event EventHandler NetworkChanged; - public Task SendWakeOnLan(string macAddress, string ipAddress, int port, CancellationToken cancellationToken) - { - return SendWakeOnLan(macAddress, new IPEndPoint(IPAddress.Parse(ipAddress), port), cancellationToken); - } - - public Task SendWakeOnLan(string macAddress, int port, CancellationToken cancellationToken) - { - return SendWakeOnLan(macAddress, new IPEndPoint(IPAddress.Broadcast, port), cancellationToken); - } - - private async Task SendWakeOnLan(string macAddress, IPEndPoint endPoint, CancellationToken cancellationToken) - { - const int payloadSize = 102; - - var macBytes = PhysicalAddress.Parse(macAddress).GetAddressBytes(); - _logger.Debug(string.Format("Sending magic packet to {0}", macAddress)); - - // Construct magic packet - var payload = new byte[payloadSize]; - Buffer.BlockCopy(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, 0, payload, 0, 6); - - for (var i = 1; i < 17; i++) - { - Buffer.BlockCopy(macBytes, 0, payload, 6 * i, 6); - } - - // Send packet LAN - using (var udp = new UdpClient()) - { - udp.Connect(endPoint); - - cancellationToken.ThrowIfCancellationRequested(); - - await udp.SendAsync(payload, payloadSize).ConfigureAwait(false); - } - } - public NetworkStatus GetNetworkStatus() { return new NetworkStatus @@ -92,8 +54,7 @@ public NetworkStatus GetNetworkStatus() /// private bool IsNetworkAvailable() { - return true; - //return IsNetworkAvailable(0); + return IsNetworkAvailable(0); } /// diff --git a/Emby.ApiClient/Net/ServerLocator.cs b/Jellyfin.ApiClient/Net/ServerLocator.cs similarity index 90% rename from Emby.ApiClient/Net/ServerLocator.cs rename to Jellyfin.ApiClient/Net/ServerLocator.cs index 364f2da..e82c3e3 100644 --- a/Emby.ApiClient/Net/ServerLocator.cs +++ b/Jellyfin.ApiClient/Net/ServerLocator.cs @@ -1,6 +1,7 @@ using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -11,7 +12,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Emby.ApiClient.Net +namespace Jellyfin.ApiClient.Net { public class ServerLocator : IServerLocator { @@ -19,7 +20,7 @@ public class ServerLocator : IServerLocator private readonly ILogger _logger; public ServerLocator() - : this(new NullLogger()) + : this(NullLogger.Instance) { } @@ -28,12 +29,12 @@ public ServerLocator(ILogger logger) _logger = logger; } - public Task> FindServers(int timeoutMs, CancellationToken cancellationToken = default(CancellationToken)) + public Task> FindServers(int timeoutMs, CancellationToken cancellationToken = default) { var taskCompletionSource = new TaskCompletionSource>(); var serversFound = new ConcurrentBag(); - _logger.Debug("Searching for servers with timeout of {0} ms", timeoutMs); + _logger.LogDebug("Searching for servers with timeout of {0} ms", timeoutMs); var innerCancellationSource = new CancellationTokenSource(); var linkedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource( @@ -95,7 +96,7 @@ private async void FindServers(Action serverFound, Action serverFound, Action + /// Class SessionUpdatesEventArgs + /// + public class SessionUpdatesEventArgs + { + public SessionInfoDto[] Sessions { get; set; } + } +} diff --git a/Emby.ApiClient/NewtonsoftJsonSerializer.cs b/Jellyfin.ApiClient/NewtonsoftJsonSerializer.cs similarity index 84% rename from Emby.ApiClient/NewtonsoftJsonSerializer.cs rename to Jellyfin.ApiClient/NewtonsoftJsonSerializer.cs index a044a8e..4b41240 100644 --- a/Emby.ApiClient/NewtonsoftJsonSerializer.cs +++ b/Jellyfin.ApiClient/NewtonsoftJsonSerializer.cs @@ -2,8 +2,9 @@ using Newtonsoft.Json; using System; using System.IO; +using System.Threading.Tasks; -namespace Emby.ApiClient +namespace Jellyfin.ApiClient { /// /// Class NewtonsoftJsonSerializer @@ -26,6 +27,18 @@ public void SerializeToStream(object obj, Stream stream) } } + /// + /// Serializes to stream. + /// + /// + /// The obj. + /// The stream. + /// obj + public void SerializeToStream(T obj, Stream stream) + { + SerializeToStream((object)obj, stream); + } + /// /// Deserializes from stream. /// @@ -131,5 +144,15 @@ public T DeserializeFromFile(string file) where T : class { throw new NotImplementedException(); } + + public Task DeserializeFromStreamAsync(Stream stream, Type type) + { + return Task.FromResult(DeserializeFromStream(stream, type)); + } + + public Task DeserializeFromStreamAsync(Stream stream) + { + return Task.FromResult(DeserializeFromStream(stream)); + } } } diff --git a/Emby.ApiClient/Playback/IPlaybackManager.cs b/Jellyfin.ApiClient/Playback/IPlaybackManager.cs similarity index 96% rename from Emby.ApiClient/Playback/IPlaybackManager.cs rename to Jellyfin.ApiClient/Playback/IPlaybackManager.cs index 3d9d527..96443bf 100644 --- a/Emby.ApiClient/Playback/IPlaybackManager.cs +++ b/Jellyfin.ApiClient/Playback/IPlaybackManager.cs @@ -1,12 +1,12 @@ -using MediaBrowser.Model.ApiClient; +using Jellyfin.ApiClient.Model; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Session; +using System; using System.Collections.Generic; using System.Threading.Tasks; -using Emby.ApiClient.Model; -namespace Emby.ApiClient.Playback +namespace Jellyfin.ApiClient.Playback { public interface IPlaybackManager { @@ -99,6 +99,6 @@ public interface IPlaybackManager /// if set to true [is offline]. /// The current apiClient. It can be null if offline /// Task. - Task ReportPlaybackStopped(PlaybackStopInfo info, StreamInfo streamInfo, string serverId, string userId, bool isOffline, IApiClient apiClient); + Task ReportPlaybackStopped(PlaybackStopInfo info, StreamInfo streamInfo, string serverId, Guid userId, bool isOffline, IApiClient apiClient); } } \ No newline at end of file diff --git a/Jellyfin.ApiClient/Playback/PlaybackException.cs b/Jellyfin.ApiClient/Playback/PlaybackException.cs new file mode 100644 index 0000000..9abedcd --- /dev/null +++ b/Jellyfin.ApiClient/Playback/PlaybackException.cs @@ -0,0 +1,10 @@ +using MediaBrowser.Model.Dlna; +using System; + +namespace Jellyfin.ApiClient.Playback +{ + public class PlaybackException : Exception + { + public PlaybackErrorCode ErrorCode { get; set; } + } +} diff --git a/Emby.ApiClient/Playback/PlaybackManager.cs b/Jellyfin.ApiClient/Playback/PlaybackManager.cs similarity index 72% rename from Emby.ApiClient/Playback/PlaybackManager.cs rename to Jellyfin.ApiClient/Playback/PlaybackManager.cs index 4b8f8b8..8ddb91c 100644 --- a/Emby.ApiClient/Playback/PlaybackManager.cs +++ b/Jellyfin.ApiClient/Playback/PlaybackManager.cs @@ -1,46 +1,34 @@ -using MediaBrowser.Model.ApiClient; +using Jellyfin.ApiClient.Model; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Emby.ApiClient.Data; -using Emby.ApiClient.Model; -using Emby.ApiClient.Net; -namespace Emby.ApiClient.Playback +namespace Jellyfin.ApiClient.Playback { public class PlaybackManager : IPlaybackManager { - private readonly ILocalAssetManager _localAssetManager; private readonly ILogger _logger; private readonly IDevice _device; /// /// Initializes a new instance of the class. /// - /// The local asset manager. /// The device. /// The logger. - public PlaybackManager(ILocalAssetManager localAssetManager, IDevice device, ILogger logger) + public PlaybackManager(IDevice device, ILogger logger) { - _localAssetManager = localAssetManager; _device = device; _logger = logger; } - public PlaybackManager(ILocalAssetManager localAssetManager, IDevice device, ILogger logger, INetworkConnection network) - : this(localAssetManager, device, logger) - { - } - /// /// Gets the pre playback selectable audio streams. /// @@ -49,7 +37,7 @@ public PlaybackManager(ILocalAssetManager localAssetManager, IDevice device, ILo /// Task<IEnumerable<MediaStream>>. public async Task> GetPrePlaybackSelectableAudioStreams(string serverId, VideoOptions options) { - var info = await GetVideoStreamInfoInternal(serverId, options).ConfigureAwait(false); + var info = await GetVideoStreamInfoInternal(options).ConfigureAwait(false); return info.GetSelectableAudioStreams(); } @@ -62,7 +50,7 @@ public async Task> GetPrePlaybackSelectableAudioStreams /// Task<IEnumerable<MediaStream>>. public async Task> GetPrePlaybackSelectableSubtitleStreams(string serverId, VideoOptions options) { - var info = await GetVideoStreamInfoInternal(serverId, options).ConfigureAwait(false); + var info = await GetVideoStreamInfoInternal(options).ConfigureAwait(false); return info.GetSelectableSubtitleStreams(); } @@ -108,36 +96,6 @@ public async Task GetAudioStreamInfo(string serverId, AudioOptions o { var streamBuilder = GetStreamBuilder(); - var localItem = await _localAssetManager.GetLocalItem(serverId, options.ItemId); - if (localItem != null) - { - var localMediaSource = localItem.Item.MediaSources[0]; - - // Use the local media source, unless a specific server media source was requested - if (string.IsNullOrWhiteSpace(options.MediaSourceId) || - string.Equals(localMediaSource.Id, options.MediaSourceId, - StringComparison.OrdinalIgnoreCase)) - { - // Finally, check to make sure the local file is actually available at this time - var fileExists = await _localAssetManager.FileExists(localMediaSource.Path).ConfigureAwait(false); - - if (fileExists) - { - options.MediaSources = localItem.Item.MediaSources.ToArray(); - - var result = streamBuilder.BuildAudioItem(options); - if (result == null) - { - _logger.Warn("LocalItem returned no compatible streams. Will dummy up a StreamInfo to force it to direct play."); - var mediaSource = localItem.Item.MediaSources.First(); - result = GetForcedDirectPlayStreamInfo(DlnaProfileType.Audio, options, mediaSource); - } - result.PlayMethod = PlayMethod.DirectPlay; - return result; - } - } - } - PlaybackInfoResponse playbackInfo = null; string playSessionId = null; if (!isOffline) @@ -177,7 +135,7 @@ public async Task GetAudioStreamInfo(string serverId, AudioOptions o if (playbackInfo != null) { - streamInfo.AllMediaSources = playbackInfo.MediaSources.ToList(); + //streamInfo.AllMediaSources = playbackInfo.MediaSources.ToList(); streamInfo.PlaySessionId = playbackInfo.PlaySessionId; } @@ -222,14 +180,14 @@ public async Task ChangeVideoStream(StreamInfo currentInfo, string s { await StopStranscoding(currentInfo, apiClient).ConfigureAwait(false); - if (currentInfo.AllMediaSources != null) - { - options.MediaSources = currentInfo.AllMediaSources.ToArray(); - } + //if (currentInfo.AllMediaSources != null) + //{ + // options.MediaSources = currentInfo.AllMediaSources.ToArray(); + //} - var streamInfo = await GetVideoStreamInfoInternal(serverId, options).ConfigureAwait(false); + var streamInfo = await GetVideoStreamInfoInternal(options).ConfigureAwait(false); streamInfo.PlaySessionId = currentInfo.PlaySessionId; - streamInfo.AllMediaSources = currentInfo.AllMediaSources; + //streamInfo.AllMediaSources = currentInfo.AllMediaSources; return streamInfo; } @@ -268,7 +226,7 @@ public async Task GetVideoStreamInfo(string serverId, VideoOptions o playSessionId = playbackInfo.PlaySessionId; } - var streamInfo = await GetVideoStreamInfoInternal(serverId, options).ConfigureAwait(false); + var streamInfo = await GetVideoStreamInfoInternal(options).ConfigureAwait(false); if (!isOffline) { @@ -284,51 +242,20 @@ public async Task GetVideoStreamInfo(string serverId, VideoOptions o if (playbackInfo != null) { - streamInfo.AllMediaSources = playbackInfo.MediaSources.ToList(); + //streamInfo.AllMediaSources = playbackInfo.MediaSources.ToList(); streamInfo.PlaySessionId = playbackInfo.PlaySessionId; } return streamInfo; } - private async Task GetVideoStreamInfoInternal(string serverId, VideoOptions options) + private Task GetVideoStreamInfoInternal(VideoOptions options) { var streamBuilder = GetStreamBuilder(); - var localItem = await _localAssetManager.GetLocalItem(serverId, options.ItemId); - - if (localItem != null) - { - var localMediaSource = localItem.Item.MediaSources[0]; - - // Use the local media source, unless a specific server media source was requested - if (string.IsNullOrWhiteSpace(options.MediaSourceId) || - string.Equals(localMediaSource.Id, options.MediaSourceId, - StringComparison.OrdinalIgnoreCase)) - { - // Finally, check to make sure the local file is actually available at this time - var fileExists = await _localAssetManager.FileExists(localMediaSource.Path).ConfigureAwait(false); - - if (fileExists) - { - options.MediaSources = localItem.Item.MediaSources.ToArray(); - - var result = streamBuilder.BuildVideoItem(options); - if (result == null) - { - _logger.Warn("LocalItem returned no compatible streams. Will dummy up a StreamInfo to force it to direct play."); - var mediaSource = localItem.Item.MediaSources.First(); - result = GetForcedDirectPlayStreamInfo(DlnaProfileType.Video, options, mediaSource); - } - result.PlayMethod = PlayMethod.DirectPlay; - return result; - } - } - } - var streamInfo = streamBuilder.BuildVideoItem(options); EnsureSuccess(streamInfo); - return streamInfo; + return Task.FromResult(streamInfo); } private StreamInfo GetForcedDirectPlayStreamInfo(DlnaProfileType mediaType, AudioOptions options, MediaSourceInfo mediaSource) @@ -408,11 +335,11 @@ public async Task ReportPlaybackProgress(PlaybackProgressInfo info, StreamInfo s /// if set to true [is offline]. /// The current apiClient. It can be null if offline /// Task. - public async Task ReportPlaybackStopped(PlaybackStopInfo info, StreamInfo streamInfo, string serverId, string userId, bool isOffline, IApiClient apiClient) + public async Task ReportPlaybackStopped(PlaybackStopInfo info, StreamInfo streamInfo, string serverId, Guid userId, bool isOffline, IApiClient apiClient) { if (isOffline) { - var action = new UserAction + var _ = new UserAction { Date = DateTime.UtcNow, ItemId = info.ItemId, @@ -422,7 +349,6 @@ public async Task ReportPlaybackStopped(PlaybackStopInfo info, StreamInfo stream UserId = userId }; - await _localAssetManager.RecordUserAction(action).ConfigureAwait(false); return; } @@ -443,7 +369,7 @@ public async Task ReportPlaybackStopped(PlaybackStopInfo info, StreamInfo stream } catch (Exception ex) { - _logger.ErrorException("Error in ReportPlaybackStoppedAsync", ex); + _logger.LogError("Error in ReportPlaybackStoppedAsync", ex); } } @@ -467,7 +393,7 @@ private async Task StopStranscoding(StreamInfo streamInfo, IApiClient apiClient) } catch (Exception ex) { - _logger.ErrorException("Error in StopStranscoding", ex); + _logger.LogError("Error in StopStranscoding", ex); } } } diff --git a/Emby.ApiClient/PortableHttpWebRequestFactory.cs b/Jellyfin.ApiClient/PortableHttpWebRequestFactory.cs similarity index 92% rename from Emby.ApiClient/PortableHttpWebRequestFactory.cs rename to Jellyfin.ApiClient/PortableHttpWebRequestFactory.cs index ea513cd..e5a3754 100644 --- a/Emby.ApiClient/PortableHttpWebRequestFactory.cs +++ b/Jellyfin.ApiClient/PortableHttpWebRequestFactory.cs @@ -1,10 +1,11 @@ -using System; +using Jellyfin.ApiClient.Net; +using System; +using System.Globalization; using System.IO; using System.Net; using System.Threading.Tasks; -using Emby.ApiClient.Net; -namespace Emby.ApiClient +namespace Jellyfin.ApiClient { public class PortableHttpWebRequestFactory : IHttpWebRequestFactory { @@ -19,7 +20,7 @@ public HttpWebRequest Create(HttpRequest options) public void SetContentLength(HttpWebRequest request, long length) { - //request.Headers["Content-Length"] = length.ToString(CultureInfo.InvariantCulture); + request.Headers["Content-Length"] = length.ToString(CultureInfo.InvariantCulture); } public Task GetResponseAsync(HttpWebRequest request, int timeoutMs) diff --git a/Emby.ApiClient/ServerLocator.cs b/Jellyfin.ApiClient/ServerLocator.cs similarity index 90% rename from Emby.ApiClient/ServerLocator.cs rename to Jellyfin.ApiClient/ServerLocator.cs index b025647..e52006d 100644 --- a/Emby.ApiClient/ServerLocator.cs +++ b/Jellyfin.ApiClient/ServerLocator.cs @@ -1,6 +1,7 @@ using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -10,9 +11,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.ApiClient.Net; -namespace Emby.ApiClient +namespace Jellyfin.ApiClient { public class ServerLocator : IServerLocator { @@ -20,7 +20,7 @@ public class ServerLocator : IServerLocator private readonly ILogger _logger; public ServerLocator() - : this(new NullLogger()) + : this(NullLogger.Instance) { } @@ -29,12 +29,12 @@ public ServerLocator(ILogger logger) _logger = logger; } - public Task> FindServers(int timeoutMs, CancellationToken cancellationToken = default(CancellationToken)) + public Task> FindServers(int timeoutMs, CancellationToken cancellationToken = default) { var taskCompletionSource = new TaskCompletionSource>(); var serversFound = new ConcurrentBag(); - _logger.Debug("Searching for servers with timeout of {0} ms", timeoutMs); + _logger.LogDebug("Searching for servers with timeout of {0} ms", timeoutMs); var innerCancellationSource = new CancellationTokenSource(); var linkedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource( @@ -96,7 +96,7 @@ private async void FindServers(Action serverFound, Action serverFound, Action /// Class ClientWebSocketFactory @@ -35,7 +36,7 @@ public static IClientWebSocket CreateWebSocket(ILogger logger) /// IClientWebSocket. public static IClientWebSocket CreateWebSocket() { - return CreateWebSocket(new NullLogger()); + return CreateWebSocket(NullLogger.Instance); } } @@ -43,7 +44,7 @@ public static class SocketExtensions { public static void OpenWebSocket(this ApiClient client) { - client.OpenWebSocket(() => ClientWebSocketFactory.CreateWebSocket(new NullLogger())); + client.OpenWebSocket(() => ClientWebSocketFactory.CreateWebSocket(NullLogger.Instance)); } } } diff --git a/Emby.ApiClient/WebSocket/NativeClientWebSocket.cs b/Jellyfin.ApiClient/WebSocket/NativeClientWebSocket.cs similarity index 79% rename from Emby.ApiClient/WebSocket/NativeClientWebSocket.cs rename to Jellyfin.ApiClient/WebSocket/NativeClientWebSocket.cs index a3eab2b..8af4d67 100644 --- a/Emby.ApiClient/WebSocket/NativeClientWebSocket.cs +++ b/Jellyfin.ApiClient/WebSocket/NativeClientWebSocket.cs @@ -1,12 +1,11 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; +using Jellyfin.ApiClient.Model; +using Microsoft.Extensions.Logging; using System; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; -using Emby.ApiClient.Model; -namespace Emby.ApiClient.WebSocket +namespace Jellyfin.ApiClient.WebSocket { /// /// Class NativeClientWebSocket @@ -62,15 +61,15 @@ public NativeClientWebSocket(ILogger logger) /// The URL. /// The cancellation token. /// Task. - public async Task ConnectAsync(string url, CancellationToken cancellationToken = default(CancellationToken)) + public async Task ConnectAsync(Uri url, CancellationToken cancellationToken = default) { try { - await _client.ConnectAsync(new Uri(url), cancellationToken).ConfigureAwait(false); + await _client.ConnectAsync(url, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { - _logger.ErrorException("Error connecting to {0}", ex, url); + _logger.LogError("Error connecting to {0}", ex, url); throw; } @@ -98,7 +97,7 @@ private async void Receive() } catch (Exception ex) { - _logger.ErrorException("Error receiving web socket message", ex); + _logger.LogError("Error receiving web socket message", ex); break; } @@ -109,10 +108,7 @@ private async void Receive() break; } - if (OnReceiveBytes != null) - { - OnReceiveBytes(bytes); - } + OnReceiveBytes?.Invoke(bytes); } } @@ -146,17 +142,16 @@ private async Task ReceiveBytesAsync(CancellationToken cancellationToken /// if set to true [end of message]. /// The cancellation token. /// Task. - public async Task SendAsync(byte[] bytes, MediaBrowser.Model.Net.WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken = default(CancellationToken)) + public async Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken = default) { await _sendResource.WaitAsync(cancellationToken).ConfigureAwait(false); try { - WebSocketMessageType nativeType; - if (!Enum.TryParse(type.ToString(), true, out nativeType)) + if (!Enum.TryParse(type.ToString(), true, out System.Net.WebSockets.WebSocketMessageType nativeType)) { - _logger.Warn("Unrecognized WebSocketMessageType: {0}", type.ToString()); + _logger.LogWarning("Unrecognized WebSocketMessageType: {0}", type.ToString()); } var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token); @@ -173,20 +168,19 @@ private async Task ReceiveBytesAsync(CancellationToken cancellationToken /// Gets or sets the state. /// /// The state. - public MediaBrowser.Model.Net.WebSocketState State + public WebSocketState State { get { if (_client == null) { - return MediaBrowser.Model.Net.WebSocketState.None; + return WebSocketState.None; } - MediaBrowser.Model.Net.WebSocketState commonState; - if (!Enum.TryParse(_client.State.ToString(), true, out commonState)) + if (!Enum.TryParse(_client.State.ToString(), true, out WebSocketState commonState)) { - _logger.Warn("Unrecognized WebSocketState: {0}", _client.State.ToString()); + _logger.LogWarning("Unrecognized WebSocketState: {0}", _client.State.ToString()); } return commonState; @@ -198,10 +192,7 @@ public MediaBrowser.Model.Net.WebSocketState State /// void OnClosed() { - if (Closed != null) - { - Closed(this, EventArgs.Empty); - } + Closed?.Invoke(this, EventArgs.Empty); } /// @@ -224,9 +215,9 @@ protected virtual void Dispose(bool dispose) if (_client != null) { - if (_client.State == WebSocketState.Open) + if (_client.State == System.Net.WebSockets.WebSocketState.Open) { - _logger.Info("Sending web socket close message."); + _logger.LogInformation("Sending web socket close message."); // Can't wait on this. Try to close gracefully though. _client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); diff --git a/Emby.ApiClient/WebSocket/WebSocket4NetClientWebSocket.cs b/Jellyfin.ApiClient/WebSocket/WebSocket4NetClientWebSocket.cs similarity index 65% rename from Emby.ApiClient/WebSocket/WebSocket4NetClientWebSocket.cs rename to Jellyfin.ApiClient/WebSocket/WebSocket4NetClientWebSocket.cs index 36a72a5..ec3cca2 100644 --- a/Emby.ApiClient/WebSocket/WebSocket4NetClientWebSocket.cs +++ b/Jellyfin.ApiClient/WebSocket/WebSocket4NetClientWebSocket.cs @@ -1,12 +1,11 @@ -using MediaBrowser.Model.ApiClient; -using MediaBrowser.Model.Logging; +using Jellyfin.ApiClient.Model; +using Microsoft.Extensions.Logging; using System; using System.Threading; using System.Threading.Tasks; -using Emby.ApiClient.Model; using WebSocket4Net; -namespace Emby.ApiClient.WebSocket +namespace Jellyfin.ApiClient.WebSocket { /// /// Class WebSocket4NetClientWebSocket @@ -29,25 +28,25 @@ public WebSocket4NetClientWebSocket(ILogger logger) /// Gets or sets the state. /// /// The state. - public MediaBrowser.Model.Net.WebSocketState State + public WebSocketState State { get { switch (_socket.State) { - case WebSocketState.Closed: - return MediaBrowser.Model.Net.WebSocketState.Closed; - case WebSocketState.Closing: - return MediaBrowser.Model.Net.WebSocketState.Closed; - case WebSocketState.Connecting: - return MediaBrowser.Model.Net.WebSocketState.Connecting; - case WebSocketState.None: - return MediaBrowser.Model.Net.WebSocketState.None; - case WebSocketState.Open: - return MediaBrowser.Model.Net.WebSocketState.Open; + case WebSocket4Net.WebSocketState.Closed: + return WebSocketState.Closed; + case WebSocket4Net.WebSocketState.Closing: + return WebSocketState.Closed; + case WebSocket4Net.WebSocketState.Connecting: + return WebSocketState.Connecting; + case WebSocket4Net.WebSocketState.None: + return WebSocketState.None; + case WebSocket4Net.WebSocketState.Open: + return WebSocketState.Open; default: - return MediaBrowser.Model.Net.WebSocketState.None; + return WebSocketState.None; } } } @@ -58,20 +57,20 @@ public MediaBrowser.Model.Net.WebSocketState State /// The URL. /// The cancellation token. /// Task. - public Task ConnectAsync(string url, CancellationToken cancellationToken = default(CancellationToken)) + public Task ConnectAsync(Uri url, CancellationToken cancellationToken = default) { var taskCompletionSource = new TaskCompletionSource(); try { - _socket = new WebSocket4Net.WebSocket(url); + _socket = new WebSocket4Net.WebSocket(url.ToString()); - _socket.MessageReceived += websocket_MessageReceived; + _socket.MessageReceived += Websocket_MessageReceived; _socket.Open(); _socket.Opened += (sender, args) => taskCompletionSource.TrySetResult(true); - _socket.Closed += _socket_Closed; + _socket.Closed += Socket_Closed; } catch (Exception ex) { @@ -88,12 +87,9 @@ public MediaBrowser.Model.Net.WebSocketState State /// /// The source of the event. /// The instance containing the event data. - void _socket_Closed(object sender, EventArgs e) + void Socket_Closed(object sender, EventArgs e) { - if (Closed != null) - { - Closed(this, EventArgs.Empty); - } + Closed?.Invoke(this, EventArgs.Empty); } /// @@ -101,12 +97,9 @@ void _socket_Closed(object sender, EventArgs e) /// /// The source of the event. /// The instance containing the event data. - void websocket_MessageReceived(object sender, MessageReceivedEventArgs e) + void Websocket_MessageReceived(object sender, MessageReceivedEventArgs e) { - if (OnReceive != null) - { - OnReceive(e.Message); - } + OnReceive?.Invoke(e.Message); } /// @@ -129,7 +122,7 @@ void websocket_MessageReceived(object sender, MessageReceivedEventArgs e) /// if set to true [end of message]. /// The cancellation token. /// Task. - public Task SendAsync(byte[] bytes, MediaBrowser.Model.Net.WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken = default(CancellationToken)) + public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken = default) { return Task.Run(() => _socket.Send(bytes, 0, bytes.Length), cancellationToken); } @@ -143,9 +136,9 @@ public void Dispose() { var state = State; - if (state == MediaBrowser.Model.Net.WebSocketState.Open || state == MediaBrowser.Model.Net.WebSocketState.Connecting) + if (state == WebSocketState.Open || state == WebSocketState.Connecting) { - _logger.Info("Sending web socket close message"); + _logger.LogInformation("Sending web socket close message"); _socket.Close(); } diff --git a/Jellyfin.ApiClient/WebSocket/WebSocketMessageType.cs b/Jellyfin.ApiClient/WebSocket/WebSocketMessageType.cs new file mode 100644 index 0000000..56765ac --- /dev/null +++ b/Jellyfin.ApiClient/WebSocket/WebSocketMessageType.cs @@ -0,0 +1,9 @@ +namespace Jellyfin.ApiClient.WebSocket +{ + public enum WebSocketMessageType + { + Text, + Binary, + Close + } +} diff --git a/Jellyfin.ApiClient/WebSocket/WebSocketState.cs b/Jellyfin.ApiClient/WebSocket/WebSocketState.cs new file mode 100644 index 0000000..4ff631f --- /dev/null +++ b/Jellyfin.ApiClient/WebSocket/WebSocketState.cs @@ -0,0 +1,13 @@ +namespace Jellyfin.ApiClient.WebSocket +{ + public enum WebSocketState + { + None, + Connecting, + Open, + CloseSent, + CloseReceived, + Closed, + Aborted + } +} diff --git a/README.md b/README.md index 2605832..3a9c827 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ -Emby.ApiClient +Jellyfin.ApiClient ====================== -This portable class library makes it very easy to harness the power of the Embyr API. This is available as a Nuget package: - -[Emby.ApiClient](https://www.nuget.org/packages/MediaBrowser.ApiClient/) +This portable class library makes it very easy to harness the power of the Jellyfin API. # Single Server Example #