Selaa lähdekoodia

update connect

Luke Pulverenti 10 vuotta sitten
vanhempi
sitoutus
ab3c26c564
34 muutettua tiedostoa jossa 681 lisäystä ja 114 poistoa
  1. 45 0
      MediaBrowser.Api/ConnectService.cs
  2. 44 1
      MediaBrowser.Api/Devices/DeviceService.cs
  3. 2 2
      MediaBrowser.Api/Session/SessionsService.cs
  4. 2 2
      MediaBrowser.Common/Net/HttpRequestOptions.cs
  5. 1 0
      MediaBrowser.Controller/Connect/ConnectUser.cs
  6. 24 1
      MediaBrowser.Controller/Connect/IConnectManager.cs
  7. 16 1
      MediaBrowser.Controller/Devices/IDeviceManager.cs
  8. 9 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  9. 6 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  10. 4 2
      MediaBrowser.Model/ApiClient/ConnectionResult.cs
  11. 2 1
      MediaBrowser.Model/ApiClient/ConnectionState.cs
  12. 7 0
      MediaBrowser.Model/ApiClient/IApiClient.cs
  13. 54 0
      MediaBrowser.Model/ApiClient/IClientWebSocket.cs
  14. 12 0
      MediaBrowser.Model/ApiClient/IConnectionManager.cs
  15. 1 0
      MediaBrowser.Model/ApiClient/ServerInfo.cs
  16. 11 0
      MediaBrowser.Model/Connect/ConnectAuthorization.cs
  17. 2 2
      MediaBrowser.Model/Connect/UserLinkType.cs
  18. 24 4
      MediaBrowser.Model/Devices/DeviceInfo.cs
  19. 17 0
      MediaBrowser.Model/Devices/DeviceOptions.cs
  20. 1 0
      MediaBrowser.Model/Dto/BaseItemDto.cs
  21. 3 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  22. 3 14
      MediaBrowser.Server.Implementations/Connect/ConnectData.cs
  23. 300 59
      MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
  24. 2 0
      MediaBrowser.Server.Implementations/Connect/Responses.cs
  25. 34 7
      MediaBrowser.Server.Implementations/Devices/DeviceManager.cs
  26. 16 5
      MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
  27. 0 2
      MediaBrowser.Server.Implementations/Library/UserViewManager.cs
  28. 8 2
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  29. 23 1
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  30. 2 2
      MediaBrowser.ServerApplication/ApplicationHost.cs
  31. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  32. 1 1
      Nuget/MediaBrowser.Common.nuspec
  33. 1 1
      Nuget/MediaBrowser.Model.Signed.nuspec
  34. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 45 - 0
MediaBrowser.Api/ConnectService.cs

@@ -1,6 +1,8 @@
 using MediaBrowser.Controller.Connect;
 using MediaBrowser.Controller.Connect;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Connect;
 using ServiceStack;
 using ServiceStack;
+using System.Collections.Generic;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
@@ -22,6 +24,30 @@ namespace MediaBrowser.Api
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
+    [Route("/Connect/Invite", "POST", Summary = "Creates a Connect link for a user")]
+    public class CreateConnectInvite : IReturn<UserLinkResult>
+    {
+        [ApiMember(Name = "ConnectUsername", Description = "Connect username", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string ConnectUsername { get; set; }
+
+        [ApiMember(Name = "SendingUserId", Description = "Sending User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+        public string SendingUserId { get; set; }
+    }
+
+
+    [Route("/Connect/Pending", "GET", Summary = "Creates a Connect link for a user")]
+    public class GetPendingGuests : IReturn<List<ConnectAuthorization>>
+    {
+    }
+
+
+    [Route("/Connect/Pending", "DELETE", Summary = "Deletes a Connect link for a user")]
+    public class DeleteAuthorization : IReturnVoid
+    {
+        [ApiMember(Name = "Id", Description = "Authorization Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+        public string Id { get; set; }
+    }
+
     [Authenticated(Roles = "Admin")]
     [Authenticated(Roles = "Admin")]
     public class ConnectService : BaseApiService
     public class ConnectService : BaseApiService
     {
     {
@@ -37,11 +63,30 @@ namespace MediaBrowser.Api
             return _connectManager.LinkUser(request.Id, request.ConnectUsername);
             return _connectManager.LinkUser(request.Id, request.ConnectUsername);
         }
         }
 
 
+        public object Post(CreateConnectInvite request)
+        {
+            return _connectManager.InviteUser(request.SendingUserId, request.ConnectUsername);
+        }
+
         public void Delete(DeleteConnectLink request)
         public void Delete(DeleteConnectLink request)
         {
         {
             var task = _connectManager.RemoveLink(request.Id);
             var task = _connectManager.RemoveLink(request.Id);
 
 
             Task.WaitAll(task);
             Task.WaitAll(task);
         }
         }
+
+        public async Task<object> Get(GetPendingGuests request)
+        {
+            var result = await _connectManager.GetPendingGuests().ConfigureAwait(false);
+
+            return ToOptimizedResult(result);
+        }
+
+        public void Delete(DeleteAuthorization request)
+        {
+            var task = _connectManager.CancelAuthorization(request.Id);
+
+            Task.WaitAll(task);
+        }
     }
     }
 }
 }

+ 44 - 1
MediaBrowser.Api/Devices/DeviceService.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Devices;
+using MediaBrowser.Model.Session;
 using ServiceStack;
 using ServiceStack;
 using ServiceStack.Web;
 using ServiceStack.Web;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -49,6 +50,27 @@ namespace MediaBrowser.Api.Devices
         public Stream RequestStream { get; set; }
         public Stream RequestStream { get; set; }
     }
     }
 
 
+    [Route("/Devices/Info", "GET", Summary = "Gets device info")]
+    public class GetDeviceInfo : IReturn<DeviceInfo>
+    {
+        [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+        public string Id { get; set; }
+    }
+
+    [Route("/Devices/Capabilities", "GET", Summary = "Gets device capabilities")]
+    public class GetDeviceCapabilities : IReturn<ClientCapabilities>
+    {
+        [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+        public string Id { get; set; }
+    }
+
+    [Route("/Devices/Options", "POST", Summary = "Updates device options")]
+    public class PostDeviceOptions : DeviceOptions, IReturnVoid
+    {
+        [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+        public string Id { get; set; }
+    }
+    
     [Authenticated]
     [Authenticated]
     public class DeviceService : BaseApiService
     public class DeviceService : BaseApiService
     {
     {
@@ -59,6 +81,27 @@ namespace MediaBrowser.Api.Devices
             _deviceManager = deviceManager;
             _deviceManager = deviceManager;
         }
         }
 
 
+        public void Post(PostDeviceOptions request)
+        {
+            var task = _deviceManager.UpdateDeviceInfo(request.Id, new DeviceOptions
+            {
+                CustomName = request.CustomName,
+                CameraUploadPath = request.CameraUploadPath
+            });
+
+            Task.WaitAll(task);
+        }
+
+        public object Get(GetDeviceInfo request)
+        {
+            return ToOptimizedResult(_deviceManager.GetDevice(request.Id));
+        }
+
+        public object Get(GetDeviceCapabilities request)
+        {
+            return ToOptimizedResult(_deviceManager.GetCapabilities(request.Id));
+        }
+
         public object Get(GetDevices request)
         public object Get(GetDevices request)
         {
         {
             var devices = _deviceManager.GetDevices();
             var devices = _deviceManager.GetDevices();
@@ -67,7 +110,7 @@ namespace MediaBrowser.Api.Devices
             {
             {
                 var val = request.SupportsContentUploading.Value;
                 var val = request.SupportsContentUploading.Value;
 
 
-                devices = devices.Where(i => i.Capabilities.SupportsContentUploading == val);
+                devices = devices.Where(i => _deviceManager.GetCapabilities(i.Id).SupportsContentUploading == val);
             }
             }
 
 
             return ToOptimizedResult(devices.ToList());
             return ToOptimizedResult(devices.ToList());

+ 2 - 2
MediaBrowser.Api/Session/SessionsService.cs

@@ -499,9 +499,9 @@ namespace MediaBrowser.Api.Session
             }
             }
             _sessionManager.ReportCapabilities(request.Id, new ClientCapabilities
             _sessionManager.ReportCapabilities(request.Id, new ClientCapabilities
             {
             {
-                PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
+                PlayableMediaTypes = (request.PlayableMediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
 
 
-                SupportedCommands = request.SupportedCommands.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
+                SupportedCommands = (request.SupportedCommands ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
 
 
                 SupportsMediaControl = request.SupportsMediaControl,
                 SupportsMediaControl = request.SupportsMediaControl,
 
 

+ 2 - 2
MediaBrowser.Common/Net/HttpRequestOptions.cs

@@ -129,7 +129,7 @@ namespace MediaBrowser.Common.Net
 
 
     public enum CacheMode
     public enum CacheMode
     {
     {
-        None = 1,
-        Unconditional = 2
+        None = 0,
+        Unconditional = 1
     }
     }
 }
 }

+ 1 - 0
MediaBrowser.Controller/Connect/ConnectUser.cs

@@ -7,6 +7,7 @@ namespace MediaBrowser.Controller.Connect
         public string Name { get; set; }
         public string Name { get; set; }
         public string Email { get; set; }
         public string Email { get; set; }
         public bool IsActive { get; set; }
         public bool IsActive { get; set; }
+        public string ImageUrl { get; set; }
     }
     }
 
 
     public class ConnectUserQuery
     public class ConnectUserQuery

+ 24 - 1
MediaBrowser.Controller/Connect/IConnectManager.cs

@@ -1,4 +1,6 @@
-using System.Threading.Tasks;
+using MediaBrowser.Model.Connect;
+using System.Collections.Generic;
+using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Controller.Connect
 namespace MediaBrowser.Controller.Connect
 {
 {
@@ -24,5 +26,26 @@ namespace MediaBrowser.Controller.Connect
         /// <param name="userId">The user identifier.</param>
         /// <param name="userId">The user identifier.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         Task RemoveLink(string userId);
         Task RemoveLink(string userId);
+
+        /// <summary>
+        /// Invites the user.
+        /// </summary>
+        /// <param name="sendingUserId">The sending user identifier.</param>
+        /// <param name="connectUsername">The connect username.</param>
+        /// <returns>Task&lt;UserLinkResult&gt;.</returns>
+        Task<UserLinkResult> InviteUser(string sendingUserId, string connectUsername);
+
+        /// <summary>
+        /// Gets the pending guests.
+        /// </summary>
+        /// <returns>Task&lt;List&lt;ConnectAuthorization&gt;&gt;.</returns>
+        Task<List<ConnectAuthorization>> GetPendingGuests();
+
+        /// <summary>
+        /// Cancels the authorization.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <returns>Task.</returns>
+        Task CancelAuthorization(string id);
     }
     }
 }
 }

+ 16 - 1
MediaBrowser.Controller/Devices/IDeviceManager.cs

@@ -1,5 +1,7 @@
 using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Devices;
+using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Session;
 using MediaBrowser.Model.Session;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -8,6 +10,11 @@ namespace MediaBrowser.Controller.Devices
 {
 {
     public interface IDeviceManager
     public interface IDeviceManager
     {
     {
+        /// <summary>
+        /// Occurs when [device options updated].
+        /// </summary>
+        event EventHandler<GenericEventArgs<DeviceInfo>> DeviceOptionsUpdated;
+
         /// <summary>
         /// <summary>
         /// Registers the device.
         /// Registers the device.
         /// </summary>
         /// </summary>
@@ -16,7 +23,7 @@ namespace MediaBrowser.Controller.Devices
         /// <param name="appName">Name of the application.</param>
         /// <param name="appName">Name of the application.</param>
         /// <param name="usedByUserId">The used by user identifier.</param>
         /// <param name="usedByUserId">The used by user identifier.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        Task RegisterDevice(string reportedId, string name, string appName, string usedByUserId);
+        Task<DeviceInfo> RegisterDevice(string reportedId, string name, string appName, string usedByUserId);
 
 
         /// <summary>
         /// <summary>
         /// Saves the capabilities.
         /// Saves the capabilities.
@@ -40,6 +47,14 @@ namespace MediaBrowser.Controller.Devices
         /// <returns>DeviceInfo.</returns>
         /// <returns>DeviceInfo.</returns>
         DeviceInfo GetDevice(string id);
         DeviceInfo GetDevice(string id);
 
 
+        /// <summary>
+        /// Updates the device information.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <param name="options">The options.</param>
+        /// <returns>Task.</returns>
+        Task UpdateDeviceInfo(string id, DeviceOptions options);
+
         /// <summary>
         /// <summary>
         /// Gets the devices.
         /// Gets the devices.
         /// </summary>
         /// </summary>

+ 9 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -92,6 +92,9 @@
     <Compile Include="..\MediaBrowser.Model\ApiClient\IApiClient.cs">
     <Compile Include="..\MediaBrowser.Model\ApiClient\IApiClient.cs">
       <Link>ApiClient\IApiClient.cs</Link>
       <Link>ApiClient\IApiClient.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\ApiClient\IClientWebSocket.cs">
+      <Link>ApiClient\IClientWebSocket.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\ApiClient\IConnectionManager.cs">
     <Compile Include="..\MediaBrowser.Model\ApiClient\IConnectionManager.cs">
       <Link>ApiClient\IConnectionManager.cs</Link>
       <Link>ApiClient\IConnectionManager.cs</Link>
     </Compile>
     </Compile>
@@ -212,6 +215,9 @@
     <Compile Include="..\MediaBrowser.Model\Configuration\XbmcMetadataOptions.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\XbmcMetadataOptions.cs">
       <Link>Configuration\XbmcMetadataOptions.cs</Link>
       <Link>Configuration\XbmcMetadataOptions.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Connect\ConnectAuthorization.cs">
+      <Link>Connect\ConnectAuthorization.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Connect\UserLinkType.cs">
     <Compile Include="..\MediaBrowser.Model\Connect\UserLinkType.cs">
       <Link>Connect\UserLinkType.cs</Link>
       <Link>Connect\UserLinkType.cs</Link>
     </Compile>
     </Compile>
@@ -221,6 +227,9 @@
     <Compile Include="..\MediaBrowser.Model\Devices\DeviceInfo.cs">
     <Compile Include="..\MediaBrowser.Model\Devices\DeviceInfo.cs">
       <Link>Devices\DeviceInfo.cs</Link>
       <Link>Devices\DeviceInfo.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Devices\DeviceOptions.cs">
+      <Link>Devices\DeviceOptions.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Devices\DevicesOptions.cs">
     <Compile Include="..\MediaBrowser.Model\Devices\DevicesOptions.cs">
       <Link>Devices\DevicesOptions.cs</Link>
       <Link>Devices\DevicesOptions.cs</Link>
     </Compile>
     </Compile>

+ 6 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -181,6 +181,9 @@
     <Compile Include="..\MediaBrowser.Model\Configuration\XbmcMetadataOptions.cs">
     <Compile Include="..\MediaBrowser.Model\Configuration\XbmcMetadataOptions.cs">
       <Link>Configuration\XbmcMetadataOptions.cs</Link>
       <Link>Configuration\XbmcMetadataOptions.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Connect\ConnectAuthorization.cs">
+      <Link>Connect\ConnectAuthorization.cs</Link>
+    </Compile>
     <Compile Include="..\mediabrowser.model\connect\UserLinkType.cs">
     <Compile Include="..\mediabrowser.model\connect\UserLinkType.cs">
       <Link>Connect\UserLinkType.cs</Link>
       <Link>Connect\UserLinkType.cs</Link>
     </Compile>
     </Compile>
@@ -190,6 +193,9 @@
     <Compile Include="..\MediaBrowser.Model\Devices\DeviceInfo.cs">
     <Compile Include="..\MediaBrowser.Model\Devices\DeviceInfo.cs">
       <Link>Devices\DeviceInfo.cs</Link>
       <Link>Devices\DeviceInfo.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Devices\DeviceOptions.cs">
+      <Link>Devices\DeviceOptions.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Devices\DevicesOptions.cs">
     <Compile Include="..\MediaBrowser.Model\Devices\DevicesOptions.cs">
       <Link>Devices\DevicesOptions.cs</Link>
       <Link>Devices\DevicesOptions.cs</Link>
     </Compile>
     </Compile>

+ 4 - 2
MediaBrowser.Model/ApiClient/ConnectionResult.cs

@@ -1,15 +1,17 @@
-
+using System.Collections.Generic;
+
 namespace MediaBrowser.Model.ApiClient
 namespace MediaBrowser.Model.ApiClient
 {
 {
     public class ConnectionResult
     public class ConnectionResult
     {
     {
         public ConnectionState State { get; set; }
         public ConnectionState State { get; set; }
-        public ServerInfo ServerInfo { get; set; }
+        public List<ServerInfo> Servers { get; set; }
         public IApiClient ApiClient { get; set; }
         public IApiClient ApiClient { get; set; }
 
 
         public ConnectionResult()
         public ConnectionResult()
         {
         {
             State = ConnectionState.Unavailable;
             State = ConnectionState.Unavailable;
+            Servers = new List<ServerInfo>();
         }
         }
     }
     }
 }
 }

+ 2 - 1
MediaBrowser.Model/ApiClient/ConnectionState.cs

@@ -4,6 +4,7 @@ namespace MediaBrowser.Model.ApiClient
     {
     {
         Unavailable = 1,
         Unavailable = 1,
         ServerSignIn = 2,
         ServerSignIn = 2,
-        SignedIn = 3
+        SignedIn = 3,
+        ServerSelection = 4
     }
     }
 }
 }

+ 7 - 0
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -1350,5 +1350,12 @@ namespace MediaBrowser.Model.ApiClient
         /// </summary>
         /// </summary>
         /// <returns>Task&lt;DevicesOptions&gt;.</returns>
         /// <returns>Task&lt;DevicesOptions&gt;.</returns>
         Task<DevicesOptions> GetDevicesOptions();
         Task<DevicesOptions> GetDevicesOptions();
+
+        /// <summary>
+        /// Opens the web socket.
+        /// </summary>
+        /// <param name="webSocketFactory">The web socket factory.</param>
+        /// <param name="keepAliveTimerMs">The keep alive timer ms.</param>
+        void OpenWebSocket(Func<IClientWebSocket> webSocketFactory, int keepAliveTimerMs = 60000);
     }
     }
 }
 }

+ 54 - 0
MediaBrowser.Model/ApiClient/IClientWebSocket.cs

@@ -0,0 +1,54 @@
+using MediaBrowser.Model.Net;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Model.ApiClient
+{
+    /// <summary>
+    /// Interface IClientWebSocket
+    /// </summary>
+    public interface IClientWebSocket : IDisposable
+    {
+        /// <summary>
+        /// Occurs when [closed].
+        /// </summary>
+        event EventHandler Closed;
+
+        /// <summary>
+        /// Gets or sets the state.
+        /// </summary>
+        /// <value>The state.</value>
+        WebSocketState State { get; }
+
+        /// <summary>
+        /// Connects the async.
+        /// </summary>
+        /// <param name="url">The URL.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task ConnectAsync(string url, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets or sets the receive action.
+        /// </summary>
+        /// <value>The receive action.</value>
+        Action<byte[]> OnReceiveBytes { get; set; }
+
+        /// <summary>
+        /// Gets or sets the on receive.
+        /// </summary>
+        /// <value>The on receive.</value>
+        Action<string> OnReceive { get; set; }
+
+        /// <summary>
+        /// Sends the async.
+        /// </summary>
+        /// <param name="bytes">The bytes.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken);
+    }
+}

+ 12 - 0
MediaBrowser.Model/ApiClient/IConnectionManager.cs

@@ -53,5 +53,17 @@ namespace MediaBrowser.Model.ApiClient
         /// </summary>
         /// </summary>
         /// <returns>Task&lt;ConnectionResult&gt;.</returns>
         /// <returns>Task&lt;ConnectionResult&gt;.</returns>
         Task<ConnectionResult> Logout();
         Task<ConnectionResult> Logout();
+
+        /// <summary>
+        /// Logins to connect.
+        /// </summary>
+        /// <returns>Task.</returns>
+        Task LoginToConnect(string username, string password);
+
+        /// <summary>
+        /// Gets the active api client instance
+        /// </summary>
+        [Obsolete]
+        IApiClient CurrentApiClient { get; }
     }
     }
 }
 }

+ 1 - 0
MediaBrowser.Model/ApiClient/ServerInfo.cs

@@ -12,6 +12,7 @@ namespace MediaBrowser.Model.ApiClient
         public String UserId { get; set; }
         public String UserId { get; set; }
         public String AccessToken { get; set; }
         public String AccessToken { get; set; }
         public List<WakeOnLanInfo> WakeOnLanInfos { get; set; }
         public List<WakeOnLanInfo> WakeOnLanInfos { get; set; }
+        public DateTime DateLastAccessed { get; set; }
 
 
         public ServerInfo()
         public ServerInfo()
         {
         {

+ 11 - 0
MediaBrowser.Model/Connect/ConnectAuthorization.cs

@@ -0,0 +1,11 @@
+
+namespace MediaBrowser.Model.Connect
+{
+    public class ConnectAuthorization
+    {
+        public string ConnectUserId { get; set; }
+        public string UserName { get; set; }
+        public string ImageUrl { get; set; }
+        public string Id { get; set; }
+    }
+}

+ 2 - 2
MediaBrowser.Model/Connect/UserLinkType.cs

@@ -6,10 +6,10 @@ namespace MediaBrowser.Model.Connect
         /// <summary>
         /// <summary>
         /// The linked user
         /// The linked user
         /// </summary>
         /// </summary>
-        LinkedUser = 1,
+        LinkedUser = 0,
         /// <summary>
         /// <summary>
         /// The guest
         /// The guest
         /// </summary>
         /// </summary>
-        Guest = 2
+        Guest = 1
     }
     }
 }
 }

+ 24 - 4
MediaBrowser.Model/Devices/DeviceInfo.cs

@@ -1,15 +1,35 @@
-using System;
-using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Session;
+using System;
 
 
 namespace MediaBrowser.Model.Devices
 namespace MediaBrowser.Model.Devices
 {
 {
     public class DeviceInfo
     public class DeviceInfo
     {
     {
         /// <summary>
         /// <summary>
-        /// Gets or sets the name.
+        /// Gets or sets the name of the reported.
+        /// </summary>
+        /// <value>The name of the reported.</value>
+        public string ReportedName { get; set; }
+        /// <summary>
+        /// Gets or sets the name of the custom.
+        /// </summary>
+        /// <value>The name of the custom.</value>
+        public string CustomName { get; set; }
+        /// <summary>
+        /// Gets or sets the camera upload path.
+        /// </summary>
+        /// <value>The camera upload path.</value>
+        public string CameraUploadPath { get; set; }
+
+        /// <summary>
+        /// Gets the name.
         /// </summary>
         /// </summary>
         /// <value>The name.</value>
         /// <value>The name.</value>
-        public string Name { get; set; }
+        public string Name
+        {
+            get { return string.IsNullOrEmpty(CustomName) ? ReportedName : CustomName; }
+        }
+
         /// <summary>
         /// <summary>
         /// Gets or sets the identifier.
         /// Gets or sets the identifier.
         /// </summary>
         /// </summary>

+ 17 - 0
MediaBrowser.Model/Devices/DeviceOptions.cs

@@ -0,0 +1,17 @@
+
+namespace MediaBrowser.Model.Devices
+{
+    public class DeviceOptions
+    {
+        /// <summary>
+        /// Gets or sets the name of the custom.
+        /// </summary>
+        /// <value>The name of the custom.</value>
+        public string CustomName { get; set; }
+        /// <summary>
+        /// Gets or sets the camera upload path.
+        /// </summary>
+        /// <value>The camera upload path.</value>
+        public string CameraUploadPath { get; set; }
+    }
+}

+ 1 - 0
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -232,6 +232,7 @@ namespace MediaBrowser.Model.Dto
         /// Gets or sets the recursive unplayed item count.
         /// Gets or sets the recursive unplayed item count.
         /// </summary>
         /// </summary>
         /// <value>The recursive unplayed item count.</value>
         /// <value>The recursive unplayed item count.</value>
+        [Obsolete]
         public int? RecursiveUnplayedItemCount { get; set; }
         public int? RecursiveUnplayedItemCount { get; set; }
         
         
         /// <summary>
         /// <summary>

+ 3 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -66,6 +66,7 @@
     <Compile Include="ApiClient\HttpResponseEventArgs.cs" />
     <Compile Include="ApiClient\HttpResponseEventArgs.cs" />
     <Compile Include="ApiClient\IApiClient.cs" />
     <Compile Include="ApiClient\IApiClient.cs" />
     <Compile Include="ApiClient\ApiClientExtensions.cs" />
     <Compile Include="ApiClient\ApiClientExtensions.cs" />
+    <Compile Include="ApiClient\IClientWebSocket.cs" />
     <Compile Include="ApiClient\IConnectionManager.cs" />
     <Compile Include="ApiClient\IConnectionManager.cs" />
     <Compile Include="ApiClient\IDevice.cs" />
     <Compile Include="ApiClient\IDevice.cs" />
     <Compile Include="ApiClient\IServerEvents.cs" />
     <Compile Include="ApiClient\IServerEvents.cs" />
@@ -95,7 +96,9 @@
     <Compile Include="Configuration\PeopleMetadataOptions.cs" />
     <Compile Include="Configuration\PeopleMetadataOptions.cs" />
     <Compile Include="Configuration\XbmcMetadataOptions.cs" />
     <Compile Include="Configuration\XbmcMetadataOptions.cs" />
     <Compile Include="Configuration\SubtitlePlaybackMode.cs" />
     <Compile Include="Configuration\SubtitlePlaybackMode.cs" />
+    <Compile Include="Connect\ConnectAuthorization.cs" />
     <Compile Include="Connect\UserLinkType.cs" />
     <Compile Include="Connect\UserLinkType.cs" />
+    <Compile Include="Devices\DeviceOptions.cs" />
     <Compile Include="Devices\LocalFileInfo.cs" />
     <Compile Include="Devices\LocalFileInfo.cs" />
     <Compile Include="Devices\DeviceInfo.cs" />
     <Compile Include="Devices\DeviceInfo.cs" />
     <Compile Include="Devices\DevicesOptions.cs" />
     <Compile Include="Devices\DevicesOptions.cs" />

+ 3 - 14
MediaBrowser.Server.Implementations/Connect/ConnectData.cs

@@ -1,4 +1,4 @@
-using System;
+using MediaBrowser.Model.Connect;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
 namespace MediaBrowser.Server.Implementations.Connect
 namespace MediaBrowser.Server.Implementations.Connect
@@ -20,22 +20,11 @@ namespace MediaBrowser.Server.Implementations.Connect
         /// Gets or sets the authorizations.
         /// Gets or sets the authorizations.
         /// </summary>
         /// </summary>
         /// <value>The authorizations.</value>
         /// <value>The authorizations.</value>
-        public List<ConnectAuthorization> Authorizations { get; set; }
+        public List<ConnectAuthorization> PendingAuthorizations { get; set; }
 
 
         public ConnectData()
         public ConnectData()
         {
         {
-            Authorizations = new List<ConnectAuthorization>();
-        }
-    }
-
-    public class ConnectAuthorization
-    {
-        public string LocalUserId { get; set; }
-        public string AccessToken { get; set; }
-
-        public ConnectAuthorization()
-        {
-            AccessToken = new Guid().ToString("N");
+            PendingAuthorizations = new List<ConnectAuthorization>();
         }
         }
     }
     }
 }
 }

+ 300 - 59
MediaBrowser.Server.Implementations/Connect/ConnectManager.cs

@@ -5,8 +5,10 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Connect;
 using MediaBrowser.Controller.Connect;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Model.Connect;
 using MediaBrowser.Model.Connect;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
@@ -24,7 +26,7 @@ namespace MediaBrowser.Server.Implementations.Connect
 {
 {
     public class ConnectManager : IConnectManager
     public class ConnectManager : IConnectManager
     {
     {
-        private SemaphoreSlim _operationLock = new SemaphoreSlim(1,1);
+        private readonly SemaphoreSlim _operationLock = new SemaphoreSlim(1, 1);
 
 
         private readonly ILogger _logger;
         private readonly ILogger _logger;
         private readonly IApplicationPaths _appPaths;
         private readonly IApplicationPaths _appPaths;
@@ -34,6 +36,7 @@ namespace MediaBrowser.Server.Implementations.Connect
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
+        private readonly IProviderManager _providerManager;
 
 
         private ConnectData _data = new ConnectData();
         private ConnectData _data = new ConnectData();
 
 
@@ -90,7 +93,7 @@ namespace MediaBrowser.Server.Implementations.Connect
             IEncryptionManager encryption,
             IEncryptionManager encryption,
             IHttpClient httpClient,
             IHttpClient httpClient,
             IServerApplicationHost appHost,
             IServerApplicationHost appHost,
-            IServerConfigurationManager config, IUserManager userManager)
+            IServerConfigurationManager config, IUserManager userManager, IProviderManager providerManager)
         {
         {
             _logger = logger;
             _logger = logger;
             _appPaths = appPaths;
             _appPaths = appPaths;
@@ -100,6 +103,7 @@ namespace MediaBrowser.Server.Implementations.Connect
             _appHost = appHost;
             _appHost = appHost;
             _config = config;
             _config = config;
             _userManager = userManager;
             _userManager = userManager;
+            _providerManager = providerManager;
 
 
             LoadCachedData();
             LoadCachedData();
         }
         }
@@ -165,13 +169,15 @@ namespace MediaBrowser.Server.Implementations.Connect
                 }
                 }
 
 
                 await RefreshAuthorizationsInternal(CancellationToken.None).ConfigureAwait(false);
                 await RefreshAuthorizationsInternal(CancellationToken.None).ConfigureAwait(false);
+
+                await RefreshUserInfosInternal(CancellationToken.None).ConfigureAwait(false);
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
                 _logger.ErrorException("Error registering with Connect", ex);
                 _logger.ErrorException("Error registering with Connect", ex);
             }
             }
         }
         }
-        
+
         private async Task CreateServerRegistration(string wanApiAddress)
         private async Task CreateServerRegistration(string wanApiAddress)
         {
         {
             var url = "Servers";
             var url = "Servers";
@@ -181,7 +187,7 @@ namespace MediaBrowser.Server.Implementations.Connect
             {
             {
                 {"name", _appHost.FriendlyName}, 
                 {"name", _appHost.FriendlyName}, 
                 {"url", wanApiAddress}, 
                 {"url", wanApiAddress}, 
-                {"systemid", _appHost.SystemId}
+                {"systemId", _appHost.SystemId}
             };
             };
 
 
             using (var stream = await _httpClient.Post(url, postData, CancellationToken.None).ConfigureAwait(false))
             using (var stream = await _httpClient.Post(url, postData, CancellationToken.None).ConfigureAwait(false))
@@ -211,7 +217,7 @@ namespace MediaBrowser.Server.Implementations.Connect
             {
             {
                 {"name", _appHost.FriendlyName}, 
                 {"name", _appHost.FriendlyName}, 
                 {"url", wanApiAddress}, 
                 {"url", wanApiAddress}, 
-                {"systemid", _appHost.SystemId}
+                {"systemId", _appHost.SystemId}
             });
             });
 
 
             SetServerAccessToken(options);
             SetServerAccessToken(options);
@@ -222,6 +228,7 @@ namespace MediaBrowser.Server.Implementations.Connect
             }
             }
         }
         }
 
 
+        private readonly object _dataFileLock = new object();
         private string CacheFilePath
         private string CacheFilePath
         {
         {
             get { return Path.Combine(_appPaths.DataPath, "connect.txt"); }
             get { return Path.Combine(_appPaths.DataPath, "connect.txt"); }
@@ -239,7 +246,10 @@ namespace MediaBrowser.Server.Implementations.Connect
 
 
                 var encrypted = _encryption.EncryptString(json);
                 var encrypted = _encryption.EncryptString(json);
 
 
-                File.WriteAllText(path, encrypted, Encoding.UTF8);
+                lock (_dataFileLock)
+                {
+                    File.WriteAllText(path, encrypted, Encoding.UTF8);
+                }
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
@@ -253,11 +263,14 @@ namespace MediaBrowser.Server.Implementations.Connect
 
 
             try
             try
             {
             {
-                var encrypted = File.ReadAllText(path, Encoding.UTF8);
+                lock (_dataFileLock)
+                {
+                    var encrypted = File.ReadAllText(path, Encoding.UTF8);
 
 
-                var json = _encryption.DecryptString(encrypted);
+                    var json = _encryption.DecryptString(encrypted);
 
 
-                _data = _json.DeserializeFromString<ConnectData>(json);
+                    _data = _json.DeserializeFromString<ConnectData>(json);
+                }
             }
             }
             catch (IOException)
             catch (IOException)
             {
             {
@@ -287,6 +300,20 @@ namespace MediaBrowser.Server.Implementations.Connect
         }
         }
 
 
         public async Task<UserLinkResult> LinkUser(string userId, string connectUsername)
         public async Task<UserLinkResult> LinkUser(string userId, string connectUsername)
+        {
+            await _operationLock.WaitAsync().ConfigureAwait(false);
+
+            try
+            {
+                return await LinkUserInternal(userId, connectUsername).ConfigureAwait(false);
+            }
+            finally
+            {
+                _operationLock.Release();
+            }
+        }
+
+        private async Task<UserLinkResult> LinkUserInternal(string userId, string connectUsername)
         {
         {
             if (string.IsNullOrWhiteSpace(connectUsername))
             if (string.IsNullOrWhiteSpace(connectUsername))
             {
             {
@@ -350,56 +377,98 @@ namespace MediaBrowser.Server.Implementations.Connect
 
 
             await user.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
             await user.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
 
+            user.Configuration.SyncConnectImage = user.ConnectLinkType == UserLinkType.Guest;
+            user.Configuration.SyncConnectName = user.ConnectLinkType == UserLinkType.Guest;
+            _userManager.UpdateConfiguration(user, user.Configuration);
+
+            await RefreshAuthorizationsInternal(CancellationToken.None).ConfigureAwait(false);
+
             return result;
             return result;
         }
         }
-
-        public Task RemoveLink(string userId)
+        
+        public async Task<UserLinkResult> InviteUser(string sendingUserId, string connectUsername)
         {
         {
-            var user = GetUser(userId);
+            await _operationLock.WaitAsync().ConfigureAwait(false);
 
 
-            return RemoveLink(user, user.ConnectUserId);
+            try
+            {
+                return await InviteUserInternal(sendingUserId, connectUsername).ConfigureAwait(false);
+            }
+            finally
+            {
+                _operationLock.Release();
+            }
         }
         }
 
 
-        private async Task RemoveLink(User user, string connectUserId)
+        private async Task<UserLinkResult> InviteUserInternal(string sendingUserId, string connectUsername)
         {
         {
-            if (!string.IsNullOrWhiteSpace(connectUserId))
+            if (string.IsNullOrWhiteSpace(connectUsername))
             {
             {
-                var url = GetConnectUrl("ServerAuthorizations");
+                throw new ArgumentNullException("connectUsername");
+            }
 
 
-                var options = new HttpRequestOptions
-                {
-                    Url = url,
-                    CancellationToken = CancellationToken.None
-                };
+            var connectUser = await GetConnectUser(new ConnectUserQuery
+            {
+                Name = connectUsername
 
 
-                var postData = new Dictionary<string, string>
-                {
-                    {"serverId", ConnectServerId},
-                    {"userId", connectUserId}
-                };
+            }, CancellationToken.None).ConfigureAwait(false);
+
+            if (!connectUser.IsActive)
+            {
+                throw new ArgumentException("The Media Browser account has been disabled.");
+            }
 
 
-                options.SetPostData(postData);
+            var url = GetConnectUrl("ServerAuthorizations");
 
 
-                SetServerAccessToken(options);
+            var options = new HttpRequestOptions
+            {
+                Url = url,
+                CancellationToken = CancellationToken.None
+            };
 
 
-                try
-                {
-                    // No need to examine the response
-                    using (var stream = (await _httpClient.SendAsync(options, "DELETE").ConfigureAwait(false)).Content)
-                    {
-                    }
-                }
-                catch (HttpException ex)
-                {
-                    // If connect says the auth doesn't exist, we can handle that gracefully since this is a remove operation
+            var accessToken = Guid.NewGuid().ToString("N");
+            var sendingUser = GetUser(sendingUserId);
 
 
-                    if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
-                    {
-                        throw;
-                    }
+            var postData = new Dictionary<string, string>
+            {
+                {"serverId", ConnectServerId},
+                {"userId", connectUser.Id},
+                {"userType", "Guest"},
+                {"accessToken", accessToken},
+                {"requesterUserName", sendingUser.ConnectUserName}
+            };
 
 
-                    _logger.Debug("Connect returned a 404 when removing a user auth link. Handling it.");
-                }
+            options.SetPostData(postData);
+
+            SetServerAccessToken(options);
+
+            var result = new UserLinkResult();
+
+            // No need to examine the response
+            using (var stream = (await _httpClient.Post(options).ConfigureAwait(false)).Content)
+            {
+                var response = _json.DeserializeFromStream<ServerUserAuthorizationResponse>(stream);
+
+                result.IsPending = string.Equals(response.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase);
+            }
+
+            await RefreshAuthorizationsInternal(CancellationToken.None).ConfigureAwait(false);
+
+            return result;
+        }
+        
+        public Task RemoveLink(string userId)
+        {
+            var user = GetUser(userId);
+
+            return RemoveLink(user, user.ConnectUserId);
+        }
+
+        private async Task RemoveLink(User user, string connectUserId)
+        {
+            if (!string.IsNullOrWhiteSpace(connectUserId))
+            {
+                await CancelAuthorizationByConnectUserId(connectUserId).ConfigureAwait(false);
             }
             }
 
 
             user.ConnectAccessKey = null;
             user.ConnectAccessKey = null;
@@ -472,24 +541,19 @@ namespace MediaBrowser.Server.Implementations.Connect
         {
         {
             var url = GetConnectUrl("ServerAuthorizations");
             var url = GetConnectUrl("ServerAuthorizations");
 
 
+            url += "?serverId=" + ConnectServerId;
+
             var options = new HttpRequestOptions
             var options = new HttpRequestOptions
             {
             {
                 Url = url,
                 Url = url,
                 CancellationToken = cancellationToken
                 CancellationToken = cancellationToken
             };
             };
 
 
-            var postData = new Dictionary<string, string>
-                {
-                    {"serverId", ConnectServerId}
-                };
-
-            options.SetPostData(postData);
-
             SetServerAccessToken(options);
             SetServerAccessToken(options);
 
 
             try
             try
             {
             {
-                using (var stream = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
+                using (var stream = (await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)).Content)
                 {
                 {
                     var list = _json.DeserializeFromStream<List<ServerUserAuthorizationResponse>>(stream);
                     var list = _json.DeserializeFromStream<List<ServerUserAuthorizationResponse>>(stream);
 
 
@@ -521,10 +585,11 @@ namespace MediaBrowser.Server.Implementations.Connect
                         user.ConnectUserName = null;
                         user.ConnectUserName = null;
 
 
                         await _userManager.UpdateUser(user).ConfigureAwait(false);
                         await _userManager.UpdateUser(user).ConfigureAwait(false);
-                        
+
                         if (user.ConnectLinkType == UserLinkType.Guest)
                         if (user.ConnectLinkType == UserLinkType.Guest)
                         {
                         {
-                            await _userManager.DeleteUser(user).ConfigureAwait(false);
+                            _logger.Debug("Deleting guest user {0}", user.Name);
+                            //await _userManager.DeleteUser(user).ConfigureAwait(false);
                         }
                         }
                     }
                     }
                     else
                     else
@@ -544,19 +609,195 @@ namespace MediaBrowser.Server.Implementations.Connect
 
 
             users = _userManager.Users.ToList();
             users = _userManager.Users.ToList();
 
 
+            var pending = new List<ConnectAuthorization>();
+
             // TODO: Handle newly added guests that we don't know about
             // TODO: Handle newly added guests that we don't know about
             foreach (var connectEntry in list)
             foreach (var connectEntry in list)
             {
             {
-                if (string.Equals(connectEntry.UserType, "guest", StringComparison.OrdinalIgnoreCase) &&
-                    string.Equals(connectEntry.AcceptStatus, "accepted", StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(connectEntry.UserType, "guest", StringComparison.OrdinalIgnoreCase))
+                {
+                    if (string.Equals(connectEntry.AcceptStatus, "accepted", StringComparison.OrdinalIgnoreCase))
+                    {
+                        var user = users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectEntry.UserId, StringComparison.OrdinalIgnoreCase));
+
+                        if (user == null)
+                        {
+                            // Add user
+                        }
+                    }
+                    else if (string.Equals(connectEntry.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase))
+                    {
+                        pending.Add(new ConnectAuthorization
+                        {
+                             ConnectUserId = connectEntry.UserId,
+                             ImageUrl = connectEntry.ImageUrl,
+                             UserName = connectEntry.UserName,
+                             Id = connectEntry.Id
+                        });
+                    }
+                }
+            }
+
+            _data.PendingAuthorizations = pending;
+            CacheData();
+        }
+
+        public async Task RefreshUserInfos(CancellationToken cancellationToken)
+        {
+            await _operationLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                await RefreshUserInfosInternal(cancellationToken).ConfigureAwait(false);
+            }
+            finally
+            {
+                _operationLock.Release();
+            }
+        }
+
+        private readonly SemaphoreSlim _connectImageSemaphore = new SemaphoreSlim(5, 5);
+
+        private async Task RefreshUserInfosInternal(CancellationToken cancellationToken)
+        {
+            var users = _userManager.Users
+                .Where(i => !string.IsNullOrEmpty(i.ConnectUserId) &&
+                    (i.Configuration.SyncConnectImage || i.Configuration.SyncConnectName))
+                .ToList();
+
+            foreach (var user in users)
+            {
+                cancellationToken.ThrowIfCancellationRequested();
+
+                var connectUser = await GetConnectUser(new ConnectUserQuery
                 {
                 {
-                    var user = users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectEntry.UserId, StringComparison.OrdinalIgnoreCase));
+                    Id = user.ConnectUserId
 
 
-                    if (user == null)
+                }, cancellationToken).ConfigureAwait(false);
+
+                if (user.Configuration.SyncConnectName)
+                {
+                    var changed = !string.Equals(connectUser.Name, user.Name, StringComparison.OrdinalIgnoreCase);
+
+                    if (changed)
                     {
                     {
-                        // Add user
+                        await user.Rename(connectUser.Name).ConfigureAwait(false);
                     }
                     }
                 }
                 }
+
+                if (user.Configuration.SyncConnectImage)
+                {
+                    var imageUrl = connectUser.ImageUrl;
+
+                    if (!string.IsNullOrWhiteSpace(imageUrl))
+                    {
+                        var changed = false;
+
+                        if (!user.HasImage(ImageType.Primary))
+                        {
+                            changed = true;
+                        }
+                        else
+                        {
+                            using (var response = await _httpClient.SendAsync(new HttpRequestOptions
+                            {
+                                Url = imageUrl,
+                                CancellationToken = cancellationToken,
+                                BufferContent = false
+
+                            }, "HEAD").ConfigureAwait(false))
+                            {
+                                var length = response.ContentLength;
+
+                                if (length != new FileInfo(user.GetImageInfo(ImageType.Primary, 0).Path).Length)
+                                {
+                                    changed = true;
+                                }
+                            }
+                        }
+
+                        if (changed)
+                        {
+                            await _providerManager.SaveImage(user, imageUrl, _connectImageSemaphore, ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
+                            
+                            await user.RefreshMetadata(new MetadataRefreshOptions
+                            {
+                                ForceSave = true,
+
+                            }, cancellationToken).ConfigureAwait(false);
+                        }
+                    }
+                }
+            }
+        }
+
+        public async Task<List<ConnectAuthorization>> GetPendingGuests()
+        {
+            return _data.PendingAuthorizations.ToList();
+        }
+
+        public async Task CancelAuthorization(string id)
+        {
+            await _operationLock.WaitAsync().ConfigureAwait(false);
+
+            try
+            {
+                await CancelAuthorizationInternal(id).ConfigureAwait(false);
+            }
+            finally
+            {
+                _operationLock.Release();
+            }
+        }
+
+        private async Task CancelAuthorizationInternal(string id)
+        {
+            var connectUserId = _data.PendingAuthorizations
+                .First(i => string.Equals(i.Id, id, StringComparison.Ordinal))
+                .ConnectUserId;
+
+            await CancelAuthorizationByConnectUserId(connectUserId).ConfigureAwait(false);
+
+            await RefreshAuthorizationsInternal(CancellationToken.None).ConfigureAwait(false);
+        }
+
+        private async Task CancelAuthorizationByConnectUserId(string connectUserId)
+        {
+            var url = GetConnectUrl("ServerAuthorizations");
+
+            var options = new HttpRequestOptions
+            {
+                Url = url,
+                CancellationToken = CancellationToken.None
+            };
+
+            var postData = new Dictionary<string, string>
+                {
+                    {"serverId", ConnectServerId},
+                    {"userId", connectUserId}
+                };
+
+            options.SetPostData(postData);
+
+            SetServerAccessToken(options);
+
+            try
+            {
+                // No need to examine the response
+                using (var stream = (await _httpClient.SendAsync(options, "DELETE").ConfigureAwait(false)).Content)
+                {
+                }
+            }
+            catch (HttpException ex)
+            {
+                // If connect says the auth doesn't exist, we can handle that gracefully since this is a remove operation
+
+                if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
+                {
+                    throw;
+                }
+
+                _logger.Debug("Connect returned a 404 when removing a user auth link. Handling it.");
             }
             }
         }
         }
     }
     }

+ 2 - 0
MediaBrowser.Server.Implementations/Connect/Responses.cs

@@ -36,5 +36,7 @@ namespace MediaBrowser.Server.Implementations.Connect
         public bool IsActive { get; set; }
         public bool IsActive { get; set; }
         public string AcceptStatus { get; set; }
         public string AcceptStatus { get; set; }
         public string UserType { get; set; }
         public string UserType { get; set; }
+        public string ImageUrl { get; set; }
+        public string UserName { get; set; }
     }
     }
 }
 }

+ 34 - 7
MediaBrowser.Server.Implementations/Devices/DeviceManager.cs

@@ -1,8 +1,11 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Devices;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Session;
 using MediaBrowser.Model.Session;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -19,24 +22,31 @@ namespace MediaBrowser.Server.Implementations.Devices
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
         private readonly ILibraryMonitor _libraryMonitor;
         private readonly ILibraryMonitor _libraryMonitor;
         private readonly IConfigurationManager _config;
         private readonly IConfigurationManager _config;
-
-        public DeviceManager(IDeviceRepository repo, IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IConfigurationManager config)
+        private readonly ILogger _logger;
+
+        /// <summary>
+        /// Occurs when [device options updated].
+        /// </summary>
+        public event EventHandler<GenericEventArgs<DeviceInfo>> DeviceOptionsUpdated;
+        
+        public DeviceManager(IDeviceRepository repo, IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IConfigurationManager config, ILogger logger)
         {
         {
             _repo = repo;
             _repo = repo;
             _userManager = userManager;
             _userManager = userManager;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
             _libraryMonitor = libraryMonitor;
             _libraryMonitor = libraryMonitor;
             _config = config;
             _config = config;
+            _logger = logger;
         }
         }
 
 
-        public Task RegisterDevice(string reportedId, string name, string appName, string usedByUserId)
+        public async Task<DeviceInfo> RegisterDevice(string reportedId, string name, string appName, string usedByUserId)
         {
         {
             var device = GetDevice(reportedId) ?? new DeviceInfo
             var device = GetDevice(reportedId) ?? new DeviceInfo
             {
             {
                 Id = reportedId
                 Id = reportedId
             };
             };
 
 
-            device.Name = name;
+            device.ReportedName = name;
             device.AppName = appName;
             device.AppName = appName;
 
 
             if (!string.IsNullOrWhiteSpace(usedByUserId))
             if (!string.IsNullOrWhiteSpace(usedByUserId))
@@ -49,7 +59,9 @@ namespace MediaBrowser.Server.Implementations.Devices
 
 
             device.DateLastModified = DateTime.UtcNow;
             device.DateLastModified = DateTime.UtcNow;
 
 
-            return _repo.SaveDevice(device);
+            await _repo.SaveDevice(device).ConfigureAwait(false);
+
+            return device;
         }
         }
 
 
         public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities)
         public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities)
@@ -114,10 +126,13 @@ namespace MediaBrowser.Server.Implementations.Devices
 
 
         private string GetUploadPath(string deviceId)
         private string GetUploadPath(string deviceId)
         {
         {
-            var config = _config.GetUploadOptions();
-
             var device = GetDevice(deviceId);
             var device = GetDevice(deviceId);
+            if (!string.IsNullOrWhiteSpace(device.CameraUploadPath))
+            {
+                return device.CameraUploadPath;
+            }
 
 
+            var config = _config.GetUploadOptions();
             if (!string.IsNullOrWhiteSpace(config.CameraUploadPath))
             if (!string.IsNullOrWhiteSpace(config.CameraUploadPath))
             {
             {
                 return config.CameraUploadPath;
                 return config.CameraUploadPath;
@@ -132,6 +147,18 @@ namespace MediaBrowser.Server.Implementations.Devices
 
 
             return path;
             return path;
         }
         }
+
+        public async Task UpdateDeviceInfo(string id, DeviceOptions options)
+        {
+            var device = GetDevice(id);
+
+            device.CustomName = options.CustomName;
+            device.CameraUploadPath = options.CameraUploadPath;
+
+            await _repo.SaveDevice(device).ConfigureAwait(false);
+
+            EventHelper.FireEventIfNotNull(DeviceOptionsUpdated, this, new GenericEventArgs<DeviceInfo>(device), _logger);
+        }
     }
     }
 
 
     public class DevicesConfigStore : IConfigurationFactory
     public class DevicesConfigStore : IConfigurationFactory

+ 16 - 5
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -310,12 +310,23 @@ namespace MediaBrowser.Server.Implementations.IO
         /// <param name="watcher">The watcher.</param>
         /// <param name="watcher">The watcher.</param>
         private void DisposeWatcher(FileSystemWatcher watcher)
         private void DisposeWatcher(FileSystemWatcher watcher)
         {
         {
-            Logger.Info("Stopping directory watching for path {0}", watcher.Path);
-
-            watcher.EnableRaisingEvents = false;
-            watcher.Dispose();
+            try
+            {
+                using (watcher)
+                {
+                    Logger.Info("Stopping directory watching for path {0}", watcher.Path);
 
 
-            RemoveWatcherFromList(watcher);
+                    watcher.EnableRaisingEvents = false;
+                }
+            }
+            catch
+            {
+                
+            }
+            finally
+            {
+                RemoveWatcherFromList(watcher);
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 0 - 2
MediaBrowser.Server.Implementations/Library/UserViewManager.cs

@@ -2,9 +2,7 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Localization;

+ 8 - 2
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -75,7 +75,7 @@
     "TabSecurity": "Security",
     "TabSecurity": "Security",
     "ButtonAddUser": "Add User",
     "ButtonAddUser": "Add User",
     "ButtonAddLocalUser": "Add Local User",
     "ButtonAddLocalUser": "Add Local User",
-    "ButtonInviteMediaBrowserUser": "Invite Media Browser User",
+    "ButtonInviteUser": "Invite User",
     "ButtonSave": "Save",
     "ButtonSave": "Save",
     "ButtonResetPassword": "Reset Password",
     "ButtonResetPassword": "Reset Password",
     "LabelNewPassword": "New password:",
     "LabelNewPassword": "New password:",
@@ -1225,5 +1225,11 @@
     "LabelCameraUploadPath": "Camera upload path:",
     "LabelCameraUploadPath": "Camera upload path:",
     "LabelCameraUploadPathHelp": "Select a custom upload path, if desired. If unspecified a default folder will be used.",
     "LabelCameraUploadPathHelp": "Select a custom upload path, if desired. If unspecified a default folder will be used.",
     "LabelCreateCameraUploadSubfolder": "Create a subfolder for each device",
     "LabelCreateCameraUploadSubfolder": "Create a subfolder for each device",
-    "LabelCreateCameraUploadSubfolderHelp": "Specific folders can be assigned to a device by clicking on it from the Devices page."
+    "LabelCreateCameraUploadSubfolderHelp": "Specific folders can be assigned to a device by clicking on it from the Devices page.",
+    "LabelCustomDeviceDisplayName": "Display name:",
+    "LabelCustomDeviceDisplayNameHelp": "Supply a custom display name or leave empty to use the name reported by the device.",
+    "HeaderInviteUser": "Invite User",
+    "LabelConnectInviteHelp": "This is the username or email used to sign in to the Media Browser website.",
+    "HeaderInviteUserHelp": "Sharing your media with friends is easier than ever before with Media Browser Connect.",
+    "ButtonSendInvitation": "Send Invitation"
 }
 }

+ 23 - 1
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -14,6 +14,7 @@ using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Devices;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Library;
 using MediaBrowser.Model.Library;
@@ -129,6 +130,19 @@ namespace MediaBrowser.Server.Implementations.Session
             _httpClient = httpClient;
             _httpClient = httpClient;
             _authRepo = authRepo;
             _authRepo = authRepo;
             _deviceManager = deviceManager;
             _deviceManager = deviceManager;
+
+            _deviceManager.DeviceOptionsUpdated += _deviceManager_DeviceOptionsUpdated;
+        }
+
+        void _deviceManager_DeviceOptionsUpdated(object sender, GenericEventArgs<DeviceInfo> e)
+        {
+            foreach (var session in Sessions)
+            {
+                if (string.Equals(session.DeviceId, e.Argument.Id))
+                {
+                    session.DeviceName = e.Argument.Name;
+                }
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -397,6 +411,7 @@ namespace MediaBrowser.Server.Implementations.Session
             try
             try
             {
             {
                 SessionInfo connection;
                 SessionInfo connection;
+                DeviceInfo device = null;
 
 
                 if (!_activeConnections.TryGetValue(key, out connection))
                 if (!_activeConnections.TryGetValue(key, out connection))
                 {
                 {
@@ -421,10 +436,17 @@ namespace MediaBrowser.Server.Implementations.Session
                     if (!string.IsNullOrEmpty(deviceId))
                     if (!string.IsNullOrEmpty(deviceId))
                     {
                     {
                         var userIdString = userId.HasValue ? userId.Value.ToString("N") : null;
                         var userIdString = userId.HasValue ? userId.Value.ToString("N") : null;
-                        await _deviceManager.RegisterDevice(deviceId, deviceName, clientType, userIdString).ConfigureAwait(false);
+                        device = await _deviceManager.RegisterDevice(deviceId, deviceName, clientType, userIdString).ConfigureAwait(false);
                     }
                     }
                 }
                 }
 
 
+                device = device ?? _deviceManager.GetDevice(deviceId);
+
+                if (!string.IsNullOrEmpty(device.CustomName))
+                {
+                    deviceName = device.CustomName;
+                }
+
                 connection.DeviceName = deviceName;
                 connection.DeviceName = deviceName;
                 connection.UserId = userId;
                 connection.UserId = userId;
                 connection.UserName = username;
                 connection.UserName = username;

+ 2 - 2
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -457,10 +457,10 @@ namespace MediaBrowser.ServerApplication
             var encryptionManager = new EncryptionManager();
             var encryptionManager = new EncryptionManager();
             RegisterSingleInstance<IEncryptionManager>(encryptionManager);
             RegisterSingleInstance<IEncryptionManager>(encryptionManager);
 
 
-            ConnectManager = new ConnectManager(LogManager.GetLogger("Connect"), ApplicationPaths, JsonSerializer, encryptionManager, HttpClient, this, ServerConfigurationManager, UserManager);
+            ConnectManager = new ConnectManager(LogManager.GetLogger("Connect"), ApplicationPaths, JsonSerializer, encryptionManager, HttpClient, this, ServerConfigurationManager, UserManager, ProviderManager);
             RegisterSingleInstance(ConnectManager);
             RegisterSingleInstance(ConnectManager);
 
 
-            DeviceManager = new DeviceManager(new DeviceRepository(ApplicationPaths, JsonSerializer), UserManager, FileSystemManager, LibraryMonitor, ConfigurationManager);
+            DeviceManager = new DeviceManager(new DeviceRepository(ApplicationPaths, JsonSerializer), UserManager, FileSystemManager, LibraryMonitor, ConfigurationManager, LogManager.GetLogger("DeviceManager"));
             RegisterSingleInstance(DeviceManager);
             RegisterSingleInstance(DeviceManager);
 
 
             SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager);
             SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager);

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.477</version>
+        <version>3.0.482</version>
         <title>MediaBrowser.Common.Internal</title>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
         <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.477" />
+            <dependency id="MediaBrowser.Common" version="3.0.482" />
             <dependency id="NLog" version="3.1.0.0" />
             <dependency id="NLog" version="3.1.0.0" />
             <dependency id="SimpleInjector" version="2.5.2" />
             <dependency id="SimpleInjector" version="2.5.2" />
             <dependency id="sharpcompress" version="0.10.2" />
             <dependency id="sharpcompress" version="0.10.2" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Common</id>
         <id>MediaBrowser.Common</id>
-        <version>3.0.477</version>
+        <version>3.0.482</version>
         <title>MediaBrowser.Common</title>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>

+ 1 - 1
Nuget/MediaBrowser.Model.Signed.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Model.Signed</id>
         <id>MediaBrowser.Model.Signed</id>
-        <version>3.0.477</version>
+        <version>3.0.482</version>
         <title>MediaBrowser.Model - Signed Edition</title>
         <title>MediaBrowser.Model - Signed Edition</title>
         <authors>Media Browser Team</authors>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Server.Core</id>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.477</version>
+        <version>3.0.482</version>
         <title>Media Browser.Server.Core</title>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.477" />
+            <dependency id="MediaBrowser.Common" version="3.0.482" />
         </dependencies>
         </dependencies>
     </metadata>
     </metadata>
     <files>
     <files>