From f3539686bd7ff6c748a0a9441086538081fa8903 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 11 Oct 2014 16:38:13 -0400 Subject: [PATCH] add device upload options --- MediaBrowser.Api/Devices/DeviceService.cs | 107 +++++++++++ MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + .../BaseProgressiveStreamingService.cs | 19 +- .../Playback/Progressive/VideoService.cs | 2 + MediaBrowser.Api/Session/SessionsService.cs | 9 +- MediaBrowser.Api/UserService.cs | 1 - MediaBrowser.Common/Net/MimeTypes.cs | 15 ++ .../Devices/IDeviceManager.cs | 71 +++++++ .../Devices/IDeviceRepository.cs | 66 +++++++ .../MediaBrowser.Controller.csproj | 2 + .../Session/ISessionManager.cs | 2 +- MediaBrowser.Controller/Sync/ISyncManager.cs | 4 +- MediaBrowser.Dlna/DlnaManager.cs | 2 +- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 22 +-- .../MediaBrowser.Model.Portable.csproj | 18 +- .../MediaBrowser.Model.net35.csproj | 15 +- MediaBrowser.Model/ApiClient/IApiClient.cs | 35 +++- MediaBrowser.Model/ApiClient/IDevice.cs | 44 +++++ .../Devices/ContentUploadHistory.cs | 15 ++ MediaBrowser.Model/Devices/DeviceInfo.cs | 44 +++++ MediaBrowser.Model/Devices/DevicesOptions.cs | 14 ++ MediaBrowser.Model/Devices/LocalFileInfo.cs | 11 ++ .../Dlna/Profiles/AndroidProfile.cs | 6 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 6 - MediaBrowser.Model/Dlna/StreamInfo.cs | 12 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 6 +- .../Session/ClientCapabilities.cs | 7 + .../Session/SessionCapabilities.cs | 21 --- .../Music/MusicBrainzAlbumProvider.cs | 60 ++---- .../Devices/CameraUploadsFolder.cs | 68 +++++++ .../Devices/DeviceManager.cs | 149 +++++++++++++++ .../Devices/DeviceRepository.cs | 176 ++++++++++++++++++ .../Library/UserViewManager.cs | 26 +-- .../Localization/JavaScript/javascript.json | 9 +- .../Localization/Server/server.json | 10 +- ...MediaBrowser.Server.Implementations.csproj | 3 + .../Session/SessionManager.cs | 80 +++----- .../Session/WebSocketController.cs | 2 +- .../Sync/SyncManager.cs | 4 +- MediaBrowser.Server.Mono/app.config | 10 +- .../ApplicationHost.cs | 20 +- .../Api/DashboardService.cs | 2 + .../MediaBrowser.WebDashboard.csproj | 15 +- MediaBrowser.WebDashboard/app.config | 1 + MediaBrowser.WebDashboard/packages.config | 2 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- SharedVersion.cs | 3 +- 50 files changed, 1025 insertions(+), 204 deletions(-) create mode 100644 MediaBrowser.Api/Devices/DeviceService.cs create mode 100644 MediaBrowser.Controller/Devices/IDeviceManager.cs create mode 100644 MediaBrowser.Controller/Devices/IDeviceRepository.cs create mode 100644 MediaBrowser.Model/ApiClient/IDevice.cs create mode 100644 MediaBrowser.Model/Devices/ContentUploadHistory.cs create mode 100644 MediaBrowser.Model/Devices/DeviceInfo.cs create mode 100644 MediaBrowser.Model/Devices/DevicesOptions.cs create mode 100644 MediaBrowser.Model/Devices/LocalFileInfo.cs delete mode 100644 MediaBrowser.Model/Session/SessionCapabilities.cs create mode 100644 MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs create mode 100644 MediaBrowser.Server.Implementations/Devices/DeviceManager.cs create mode 100644 MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs new file mode 100644 index 0000000000..55980bc001 --- /dev/null +++ b/MediaBrowser.Api/Devices/DeviceService.cs @@ -0,0 +1,107 @@ +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Devices; +using ServiceStack; +using ServiceStack.Web; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Devices +{ + [Route("/Devices", "GET", Summary = "Gets all devices")] + public class GetDevices : IReturn> + { + [ApiMember(Name = "SupportsContentUploading", Description = "SupportsContentUploading", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool? SupportsContentUploading { get; set; } + } + + [Route("/Devices", "DELETE", Summary = "Deletes a device")] + public class DeleteDevice + { + [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] + public string Id { get; set; } + } + + [Route("/Devices/CameraUploads", "GET", Summary = "Gets camera upload history for a device")] + public class GetCameraUploads : IReturn + { + [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DeviceId { get; set; } + } + + [Route("/Devices/CameraUploads", "POST", Summary = "Uploads content")] + public class PostCameraUpload : IRequiresRequestStream, IReturnVoid + { + [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DeviceId { get; set; } + + [ApiMember(Name = "Album", Description = "Album", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Album { get; set; } + + [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Name { get; set; } + + [ApiMember(Name = "FullPath", Description = "FullPath", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string FullPath { get; set; } + + public Stream RequestStream { get; set; } + } + + [Authenticated] + public class DeviceService : BaseApiService + { + private readonly IDeviceManager _deviceManager; + + public DeviceService(IDeviceManager deviceManager) + { + _deviceManager = deviceManager; + } + + public object Get(GetDevices request) + { + var devices = _deviceManager.GetDevices(); + + if (request.SupportsContentUploading.HasValue) + { + var val = request.SupportsContentUploading.Value; + + devices = devices.Where(i => i.Capabilities.SupportsContentUploading == val); + } + + return ToOptimizedResult(devices.ToList()); + } + + public object Get(GetCameraUploads request) + { + return ToOptimizedResult(_deviceManager.GetCameraUploadHistory(request.DeviceId)); + } + + public void Delete(DeleteDevice request) + { + var task = _deviceManager.DeleteDevice(request.Id); + + Task.WaitAll(task); + } + + public void Post(PostCameraUpload request) + { + var deviceId = request.DeviceId; + + var album = Request.QueryString["Album"]; + var fullPath = Request.QueryString["FullPath"]; + var name = Request.QueryString["Name"]; + + var task = _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo + { + MimeType = Request.ContentType, + Album = album, + Name = name, + FullPath = fullPath + }); + + Task.WaitAll(task); + } + } +} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index db939366ad..732d602c65 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -71,6 +71,7 @@ + diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 50929e6f3f..94cf984d2a 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -316,17 +316,22 @@ namespace MediaBrowser.Api.Playback.Progressive CancellationToken = cancellationTokenSource.Token }; - var response = await HttpClient.GetResponse(options).ConfigureAwait(false); - - responseHeaders["Accept-Ranges"] = "none"; + if (!string.IsNullOrWhiteSpace(Request.QueryString["Range"])) + { + options.RequestHeaders["Range"] = Request.QueryString["Range"]; + } - var length = response.Headers["Content-Length"]; + var response = await HttpClient.GetResponse(options).ConfigureAwait(false); - if (!string.IsNullOrEmpty(length)) + foreach (var name in new[] { "Content-Length", "Content-Range", "Accept-Ranges" }) { - responseHeaders["Content-Length"] = length; + var val = response.Headers[name]; + if (!string.IsNullOrWhiteSpace(val)) + { + responseHeaders[name] = val; + } } - + if (isHeadRequest) { using (response.Content) diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index af3cb25eef..a64866d68a 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -32,6 +32,7 @@ namespace MediaBrowser.Api.Playback.Progressive [Route("/Videos/{Id}/stream.3gp", "GET")] [Route("/Videos/{Id}/stream.wmv", "GET")] [Route("/Videos/{Id}/stream.wtv", "GET")] + [Route("/Videos/{Id}/stream.mov", "GET")] [Route("/Videos/{Id}/stream", "GET")] [Route("/Videos/{Id}/stream.ts", "HEAD")] [Route("/Videos/{Id}/stream.webm", "HEAD")] @@ -48,6 +49,7 @@ namespace MediaBrowser.Api.Playback.Progressive [Route("/Videos/{Id}/stream.wmv", "HEAD")] [Route("/Videos/{Id}/stream.wtv", "HEAD")] [Route("/Videos/{Id}/stream.m2ts", "HEAD")] + [Route("/Videos/{Id}/stream.mov", "HEAD")] [Route("/Videos/{Id}/stream", "HEAD")] [Api(Description = "Gets a video stream")] public class GetVideoStream : VideoStreamRequest diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index e2c10c0c8c..014bedbd97 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -235,6 +235,9 @@ namespace MediaBrowser.Api.Session [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] public bool SupportsMediaControl { get; set; } + + [ApiMember(Name = "SupportsContentUploading", Description = "Determines whether camera upload is supported.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool SupportsContentUploading { get; set; } } [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] @@ -494,7 +497,7 @@ namespace MediaBrowser.Api.Session { request.Id = GetSession().Id; } - _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities + _sessionManager.ReportCapabilities(request.Id, new ClientCapabilities { PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), @@ -502,7 +505,9 @@ namespace MediaBrowser.Api.Session SupportsMediaControl = request.SupportsMediaControl, - MessageCallbackUrl = request.MessageCallbackUrl + MessageCallbackUrl = request.MessageCallbackUrl, + + SupportsContentUploading = request.SupportsContentUploading }); } } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 2c504bee11..10595d926c 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -179,7 +179,6 @@ namespace MediaBrowser.Api /// The user manager. /// The dto service. /// The session mananger. - /// xmlSerializer public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager) { _userManager = userManager; diff --git a/MediaBrowser.Common/Net/MimeTypes.cs b/MediaBrowser.Common/Net/MimeTypes.cs index 1503d0b431..a135dfa30a 100644 --- a/MediaBrowser.Common/Net/MimeTypes.cs +++ b/MediaBrowser.Common/Net/MimeTypes.cs @@ -315,5 +315,20 @@ namespace MediaBrowser.Common.Net throw new ArgumentException("Argument not supported: " + path); } + + private static readonly Dictionary MimeExtensions = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"image/jpeg", "jpg"}, + {"image/jpg", "jpg"}, + {"image/png", "png"}, + {"image/gif", "gif"}, + {"image/webp", "webp"} + }; + + public static string ToExtension(string mimeType) + { + return "." + MimeExtensions[mimeType]; + } } } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs new file mode 100644 index 0000000000..b82c39effc --- /dev/null +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -0,0 +1,71 @@ +using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Session; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Devices +{ + public interface IDeviceManager + { + /// + /// Registers the device. + /// + /// The reported identifier. + /// The name. + /// The used by user identifier. + /// Task. + Task RegisterDevice(string reportedId, string name, string usedByUserId); + + /// + /// Saves the capabilities. + /// + /// The reported identifier. + /// The capabilities. + /// Task. + Task SaveCapabilities(string reportedId, ClientCapabilities capabilities); + + /// + /// Gets the capabilities. + /// + /// The reported identifier. + /// ClientCapabilities. + ClientCapabilities GetCapabilities(string reportedId); + + /// + /// Gets the device information. + /// + /// The identifier. + /// DeviceInfo. + DeviceInfo GetDevice(string id); + + /// + /// Gets the devices. + /// + /// IEnumerable<DeviceInfo>. + IEnumerable GetDevices(); + + /// + /// Deletes the device. + /// + /// The identifier. + /// Task. + Task DeleteDevice(string id); + + /// + /// Gets the upload history. + /// + /// The device identifier. + /// ContentUploadHistory. + ContentUploadHistory GetCameraUploadHistory(string deviceId); + + /// + /// Accepts the upload. + /// + /// The device identifier. + /// The stream. + /// The file. + /// Task. + Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file); + } +} diff --git a/MediaBrowser.Controller/Devices/IDeviceRepository.cs b/MediaBrowser.Controller/Devices/IDeviceRepository.cs new file mode 100644 index 0000000000..736504da30 --- /dev/null +++ b/MediaBrowser.Controller/Devices/IDeviceRepository.cs @@ -0,0 +1,66 @@ +using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Session; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Devices +{ + public interface IDeviceRepository + { + /// + /// Registers the device. + /// + /// The device. + /// Task. + Task SaveDevice(DeviceInfo device); + + /// + /// Saves the capabilities. + /// + /// The identifier. + /// The capabilities. + /// Task. + Task SaveCapabilities(string id, ClientCapabilities capabilities); + + /// + /// Gets the capabilities. + /// + /// The identifier. + /// ClientCapabilities. + ClientCapabilities GetCapabilities(string id); + + /// + /// Gets the device information. + /// + /// The identifier. + /// DeviceInfo. + DeviceInfo GetDevice(string id); + + /// + /// Gets the devices. + /// + /// IEnumerable<DeviceInfo>. + IEnumerable GetDevices(); + + /// + /// Deletes the device. + /// + /// The identifier. + /// Task. + Task DeleteDevice(string id); + + /// + /// Gets the upload history. + /// + /// The device identifier. + /// ContentUploadHistory. + ContentUploadHistory GetCameraUploadHistory(string deviceId); + + /// + /// Saves the camera upload history. + /// + /// The device identifier. + /// The file. + void AddCameraUpload(string deviceId, LocalFileInfo file); + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index d966d4df56..a49d00e875 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -102,6 +102,8 @@ + + diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index f715ce7703..c048e3fab7 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -232,7 +232,7 @@ namespace MediaBrowser.Controller.Session /// /// The session identifier. /// The capabilities. - void ReportCapabilities(string sessionId, SessionCapabilities capabilities); + void ReportCapabilities(string sessionId, ClientCapabilities capabilities); /// /// Reports the transcoding information. diff --git a/MediaBrowser.Controller/Sync/ISyncManager.cs b/MediaBrowser.Controller/Sync/ISyncManager.cs index 1d5ab7d3e7..1e744a0872 100644 --- a/MediaBrowser.Controller/Sync/ISyncManager.cs +++ b/MediaBrowser.Controller/Sync/ISyncManager.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Controller.Entities; +using System.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Sync; using System.Collections.Generic; diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index cd14571693..1a5269b9db 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.Dlna new Windows81Profile(), //new WindowsMediaCenterProfile(), new WindowsPhoneProfile(), - new AndroidProfile(true, true), + new AndroidProfile(true, true, new[]{"baseline", "constrained baseline"}), new DirectTvProfile(), new DishHopperJoeyProfile(), new DefaultProfile() diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index f570209ad5..a60b5efa48 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -117,21 +117,21 @@ namespace MediaBrowser.Dlna.PlayTo var profile = _dlnaManager.GetProfile(device.Properties.ToDeviceIdentification()) ?? _dlnaManager.GetDefaultProfile(); - _sessionManager.ReportCapabilities(sessionInfo.Id, new SessionCapabilities + _sessionManager.ReportCapabilities(sessionInfo.Id, new ClientCapabilities { PlayableMediaTypes = profile.GetSupportedMediaTypes(), SupportedCommands = new List - { - GeneralCommandType.VolumeDown.ToString(), - GeneralCommandType.VolumeUp.ToString(), - GeneralCommandType.Mute.ToString(), - GeneralCommandType.Unmute.ToString(), - GeneralCommandType.ToggleMute.ToString(), - GeneralCommandType.SetVolume.ToString(), - GeneralCommandType.SetAudioStreamIndex.ToString(), - GeneralCommandType.SetSubtitleStreamIndex.ToString() - }, + { + GeneralCommandType.VolumeDown.ToString(), + GeneralCommandType.VolumeUp.ToString(), + GeneralCommandType.Mute.ToString(), + GeneralCommandType.Unmute.ToString(), + GeneralCommandType.ToggleMute.ToString(), + GeneralCommandType.SetVolume.ToString(), + GeneralCommandType.SetAudioStreamIndex.ToString(), + GeneralCommandType.SetSubtitleStreamIndex.ToString() + }, SupportsMediaControl = true }); diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index e74ab9b7a6..f8d2de85a3 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -95,6 +95,9 @@ ApiClient\IConnectionManager.cs + + ApiClient\IDevice.cs + ApiClient\IServerEvents.cs @@ -212,6 +215,18 @@ Connect\UserLinkType.cs + + Devices\ContentUploadHistory.cs + + + Devices\DeviceInfo.cs + + + Devices\DevicesOptions.cs + + + Devices\LocalFileInfo.cs + Dlna\AudioOptions.cs @@ -932,9 +947,6 @@ Session\PlaystateRequest.cs - - Session\SessionCapabilities.cs - Session\SessionInfoDto.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index e9f79a5031..9018627401 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -184,6 +184,18 @@ Connect\UserLinkType.cs + + Devices\ContentUploadHistory.cs + + + Devices\DeviceInfo.cs + + + Devices\DevicesOptions.cs + + + Devices\LocalFileInfo.cs + Dlna\AudioOptions.cs @@ -898,9 +910,6 @@ Session\PlaystateRequest.cs - - Session\SessionCapabilities.cs - Session\SessionInfoDto.cs diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 9f22de3acc..1d2145b3a6 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -1,5 +1,6 @@ using MediaBrowser.Model.Channels; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Devices; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; @@ -793,17 +794,23 @@ namespace MediaBrowser.Model.ApiClient /// The type of the client. string ClientName { get; set; } + /// + /// Gets the device. + /// + /// The device. + IDevice Device { get; } + /// /// Gets or sets the name of the device. /// /// The name of the device. - string DeviceName { get; set; } + string DeviceName { get; } /// /// Gets or sets the device id. /// /// The device id. - string DeviceId { get; set; } + string DeviceId { get; } /// /// Gets or sets the current user id. @@ -1319,5 +1326,29 @@ namespace MediaBrowser.Model.ApiClient /// Task. Task SendContextMessageAsync(string itemType, string itemId, string itemName, string context, CancellationToken cancellationToken); + + /// + /// Gets the content upload history. + /// + /// The device identifier. + /// Task<ContentUploadHistory>. + Task GetContentUploadHistory(string deviceId); + + /// + /// Uploads the file. + /// + /// The stream. + /// The file. + /// The cancellation token. + /// Task. + Task UploadFile(Stream stream, + LocalFileInfo file, + CancellationToken cancellationToken); + + /// + /// Gets the devices options options. + /// + /// Task<DevicesOptions>. + Task GetDevicesOptions(); } } \ No newline at end of file diff --git a/MediaBrowser.Model/ApiClient/IDevice.cs b/MediaBrowser.Model/ApiClient/IDevice.cs new file mode 100644 index 0000000000..0c0f55f9bb --- /dev/null +++ b/MediaBrowser.Model/ApiClient/IDevice.cs @@ -0,0 +1,44 @@ +using MediaBrowser.Model.Devices; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Model.ApiClient +{ + public interface IDevice + { + /// + /// Occurs when [resume from sleep]. + /// + event EventHandler ResumeFromSleep; + /// + /// 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>. + IEnumerable GetLocalPhotos(); + /// + /// Gets the local videos. + /// + /// IEnumerable<LocalFileInfo>. + IEnumerable GetLocalVideos(); + /// + /// Uploads the file. + /// + /// The file. + /// The API client. + /// The cancellation token. + /// Task. + Task UploadFile(LocalFileInfo file, IApiClient apiClient, CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.Model/Devices/ContentUploadHistory.cs b/MediaBrowser.Model/Devices/ContentUploadHistory.cs new file mode 100644 index 0000000000..cd4858d907 --- /dev/null +++ b/MediaBrowser.Model/Devices/ContentUploadHistory.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Model.Devices +{ + public class ContentUploadHistory + { + public string DeviceId { get; set; } + public List FilesUploaded { get; set; } + + public ContentUploadHistory() + { + FilesUploaded = new List(); + } + } +} diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs new file mode 100644 index 0000000000..cc622af27f --- /dev/null +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -0,0 +1,44 @@ +using System; +using MediaBrowser.Model.Session; + +namespace MediaBrowser.Model.Devices +{ + public class DeviceInfo + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + /// + /// Gets or sets the last name of the user. + /// + /// The last name of the user. + public string LastUserName { get; set; } + /// + /// Gets or sets the last user identifier. + /// + /// The last user identifier. + public string LastUserId { get; set; } + /// + /// Gets or sets the date last modified. + /// + /// The date last modified. + public DateTime DateLastModified { get; set; } + /// + /// Gets or sets the capabilities. + /// + /// The capabilities. + public ClientCapabilities Capabilities { get; set; } + + public DeviceInfo() + { + Capabilities = new ClientCapabilities(); + } + } +} diff --git a/MediaBrowser.Model/Devices/DevicesOptions.cs b/MediaBrowser.Model/Devices/DevicesOptions.cs new file mode 100644 index 0000000000..6c2082e8df --- /dev/null +++ b/MediaBrowser.Model/Devices/DevicesOptions.cs @@ -0,0 +1,14 @@ + +namespace MediaBrowser.Model.Devices +{ + public class DevicesOptions + { + public string[] EnabledCameraUploadDevices { get; set; } + public string CameraUploadPath { get; set; } + + public DevicesOptions() + { + EnabledCameraUploadDevices = new string[] { }; + } + } +} diff --git a/MediaBrowser.Model/Devices/LocalFileInfo.cs b/MediaBrowser.Model/Devices/LocalFileInfo.cs new file mode 100644 index 0000000000..f7704e0877 --- /dev/null +++ b/MediaBrowser.Model/Devices/LocalFileInfo.cs @@ -0,0 +1,11 @@ + +namespace MediaBrowser.Model.Devices +{ + public class LocalFileInfo + { + public string Name { get; set; } + public string FullPath { get; set; } + public string Album { get; set; } + public string MimeType { get; set; } + } +} diff --git a/MediaBrowser.Model/Dlna/Profiles/AndroidProfile.cs b/MediaBrowser.Model/Dlna/Profiles/AndroidProfile.cs index dfe14f1c74..0d060cda85 100644 --- a/MediaBrowser.Model/Dlna/Profiles/AndroidProfile.cs +++ b/MediaBrowser.Model/Dlna/Profiles/AndroidProfile.cs @@ -6,7 +6,9 @@ namespace MediaBrowser.Model.Dlna.Profiles [XmlRoot("Profile")] public class AndroidProfile : DefaultProfile { - public AndroidProfile(bool supportsHls, bool supportsMpegDash) + public AndroidProfile(bool supportsHls, + bool supportsMpegDash, + string[] supportedH264Profiles) { Name = "Android"; @@ -102,7 +104,7 @@ namespace MediaBrowser.Model.Dlna.Profiles Conditions = new [] { - new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, "baseline|constrained baseline"), + new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, string.Join("|", supportedH264Profiles)), new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.Width, "1920"), new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.Height, "1080"), new ProfileCondition(ProfileConditionType.LessThanEqual, ProfileConditionValue.VideoBitDepth, "8"), diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 2c49198b7f..815e8e20bf 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -733,12 +733,6 @@ namespace MediaBrowser.Model.Dlna private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream) { - // Only plain video files can be direct played - if (item.VideoType != VideoType.VideoFile) - { - return false; - } - if (profile.Container.Length > 0) { // Check container type diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 9abf446165..36dc611f26 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -76,7 +76,10 @@ namespace MediaBrowser.Model.Dlna public bool IsDirectStream { - get { return PlayMethod == PlayMethod.DirectStream; } + get { + return PlayMethod == PlayMethod.DirectStream || + PlayMethod == PlayMethod.DirectPlay; + } } public string ToUrl(string baseUrl) @@ -91,13 +94,6 @@ namespace MediaBrowser.Model.Dlna throw new ArgumentNullException(baseUrl); } - if (IsDirectStream && MediaSource != null && MediaSource.Protocol == MediaProtocol.Http) - { - if (MediaSource.RequiredHttpHeaders.Count == 0) - { - } - } - string dlnaCommand = BuildDlnaParam(this); string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container; diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index ae71f29878..e1ce7eccab 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -67,6 +67,7 @@ + @@ -95,6 +96,9 @@ + + + @@ -331,11 +335,11 @@ - + diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs index 5bee060875..cbc1501d20 100644 --- a/MediaBrowser.Model/Session/ClientCapabilities.cs +++ b/MediaBrowser.Model/Session/ClientCapabilities.cs @@ -5,8 +5,15 @@ namespace MediaBrowser.Model.Session public class ClientCapabilities { public List PlayableMediaTypes { get; set; } + public List SupportedCommands { get; set; } + public bool SupportsMediaControl { get; set; } + + public string MessageCallbackUrl { get; set; } + + public bool SupportsContentUploading { get; set; } + public ClientCapabilities() { PlayableMediaTypes = new List(); diff --git a/MediaBrowser.Model/Session/SessionCapabilities.cs b/MediaBrowser.Model/Session/SessionCapabilities.cs deleted file mode 100644 index 767df8f1c1..0000000000 --- a/MediaBrowser.Model/Session/SessionCapabilities.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Model.Session -{ - public class SessionCapabilities - { - public List PlayableMediaTypes { get; set; } - - public List SupportedCommands { get; set; } - - public bool SupportsMediaControl { get; set; } - - public string MessageCallbackUrl { get; set; } - - public SessionCapabilities() - { - PlayableMediaTypes = new List(); - SupportedCommands = new List(); - } - } -} diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index fd32dabb2d..664eff0040 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -264,10 +264,6 @@ namespace MediaBrowser.Providers.Music return node != null ? node.Value : null; } - /// - /// The _last music brainz request - /// - private DateTime _lastRequestDate = DateTime.MinValue; /// /// The _music brainz resource pool /// @@ -282,51 +278,35 @@ namespace MediaBrowser.Providers.Music /// Task{XmlDocument}. internal async Task GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken) { - await _musicBrainzResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var diff = 1500 - (DateTime.Now - _lastRequestDate).TotalMilliseconds; - - // MusicBrainz is extremely adamant about limiting to one request per second + // MusicBrainz is extremely adamant about limiting to one request per second - if (diff > 0) - { - _logger.Debug("Throttling musicbrainz by {0} ms", diff); - await Task.Delay(Convert.ToInt32(diff), cancellationToken).ConfigureAwait(false); - } + await Task.Delay(800, cancellationToken).ConfigureAwait(false); - var doc = new XmlDocument(); + var doc = new XmlDocument(); - var options = new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken, - UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion - }; + var options = new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion, + ResourcePool = _musicBrainzResourcePool + }; - if (!isSearch) - { - options.CacheMode = CacheMode.Unconditional; - options.CacheLength = TimeSpan.FromDays(7); - } + if (!isSearch) + { + options.CacheMode = CacheMode.Unconditional; + options.CacheLength = TimeSpan.FromDays(7); + } - using (var xml = await _httpClient.Get(options).ConfigureAwait(false)) + using (var xml = await _httpClient.Get(options).ConfigureAwait(false)) + { + using (var oReader = new StreamReader(xml, Encoding.UTF8)) { - using (var oReader = new StreamReader(xml, Encoding.UTF8)) - { - doc.Load(oReader); - } + doc.Load(oReader); } - - return doc; } - finally - { - _lastRequestDate = DateTime.Now; - _musicBrainzResourcePool.Release(); - } + return doc; } public int Order diff --git a/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs b/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs new file mode 100644 index 0000000000..37f72472b0 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs @@ -0,0 +1,68 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Entities; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Devices +{ + public class CameraUploadsFolder : BasePluginFolder + { + public CameraUploadsFolder() + { + Name = "Camera Uploads"; + DisplayMediaType = "CollectionFolder"; + } + + public override bool IsVisible(User user) + { + return GetChildren(user, true).Any() && + base.IsVisible(user); + } + + public override bool IsHidden + { + get + { + return true; + } + } + + public override bool IsHiddenFromUser(User user) + { + return false; + } + + public override string CollectionType + { + get { return Model.Entities.CollectionType.Photos; } + } + + public override string GetClientTypeName() + { + return typeof(CollectionFolder).Name; + } + } + + public class CameraUploadsDynamicFolder : IVirtualFolderCreator + { + private readonly IApplicationPaths _appPaths; + + public CameraUploadsDynamicFolder(IApplicationPaths appPaths) + { + _appPaths = appPaths; + } + + public BasePluginFolder GetFolder() + { + var path = Path.Combine(_appPaths.DataPath, "camerauploads"); + + Directory.CreateDirectory(path); + + return new CameraUploadsFolder + { + Path = path + }; + } + } + +} diff --git a/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs b/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs new file mode 100644 index 0000000000..55425ad7e7 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs @@ -0,0 +1,149 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Session; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Devices +{ + public class DeviceManager : IDeviceManager + { + private readonly IDeviceRepository _repo; + private readonly IUserManager _userManager; + private readonly IFileSystem _fileSystem; + private readonly ILibraryMonitor _libraryMonitor; + private readonly IConfigurationManager _config; + + public DeviceManager(IDeviceRepository repo, IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IConfigurationManager config) + { + _repo = repo; + _userManager = userManager; + _fileSystem = fileSystem; + _libraryMonitor = libraryMonitor; + _config = config; + } + + public Task RegisterDevice(string reportedId, string name, string usedByUserId) + { + var device = GetDevice(reportedId) ?? new DeviceInfo + { + Id = reportedId + }; + + device.Name = name; + + if (!string.IsNullOrWhiteSpace(usedByUserId)) + { + var user = _userManager.GetUserById(usedByUserId); + + device.LastUserId = user.Id.ToString("N"); + device.LastUserName = user.Name; + } + + device.DateLastModified = DateTime.UtcNow; + + return _repo.SaveDevice(device); + } + + public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities) + { + return _repo.SaveCapabilities(reportedId, capabilities); + } + + public ClientCapabilities GetCapabilities(string reportedId) + { + return _repo.GetCapabilities(reportedId); + } + + public DeviceInfo GetDevice(string id) + { + return _repo.GetDevice(id); + } + + public IEnumerable GetDevices() + { + return _repo.GetDevices().OrderByDescending(i => i.DateLastModified); + } + + public Task DeleteDevice(string id) + { + return _repo.DeleteDevice(id); + } + + public ContentUploadHistory GetCameraUploadHistory(string deviceId) + { + return _repo.GetCameraUploadHistory(deviceId); + } + + public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file) + { + var path = GetUploadPath(deviceId); + + if (!string.IsNullOrWhiteSpace(file.Album)) + { + path = Path.Combine(path, _fileSystem.GetValidFilename(file.Album)); + } + + Directory.CreateDirectory(path); + + path = Path.Combine(path, file.Name); + + _libraryMonitor.ReportFileSystemChangeBeginning(path); + + try + { + using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + await stream.CopyToAsync(fs).ConfigureAwait(false); + } + + _repo.AddCameraUpload(deviceId, file); + } + finally + { + _libraryMonitor.ReportFileSystemChangeComplete(path, true); + } + } + + private string GetUploadPath(string deviceId) + { + var config = _config.GetUploadOptions(); + + if (!string.IsNullOrWhiteSpace(config.CameraUploadPath)) + { + return config.CameraUploadPath; + } + + return Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads"); + } + } + + public class DevicesConfigStore : IConfigurationFactory + { + public IEnumerable GetConfigurations() + { + return new List + { + new ConfigurationStore + { + Key = "devices", + ConfigurationType = typeof(DevicesOptions) + } + }; + } + } + + public static class UploadConfigExtension + { + public static DevicesOptions GetUploadOptions(this IConfigurationManager config) + { + return config.GetConfiguration("devices"); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs b/MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs new file mode 100644 index 0000000000..eac08686f2 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs @@ -0,0 +1,176 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Session; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Devices +{ + public class DeviceRepository : IDeviceRepository + { + private readonly object _syncLock = new object(); + + private readonly IApplicationPaths _appPaths; + private readonly IJsonSerializer _json; + + private ConcurrentBag _devices; + + public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json) + { + _appPaths = appPaths; + _json = json; + } + + private string GetDevicesPath() + { + return Path.Combine(_appPaths.DataPath, "devices"); + } + + private string GetDevicePath(string id) + { + return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N")); + } + + public Task SaveDevice(DeviceInfo device) + { + var path = Path.Combine(GetDevicePath(device.Id), "device.json"); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_syncLock) + { + _json.SerializeToFile(device, path); + _devices = null; + } + return Task.FromResult(true); + } + + public Task SaveCapabilities(string reportedId, ClientCapabilities capabilities) + { + var device = GetDevice(reportedId); + device.Capabilities = capabilities; + SaveDevice(device); + + return Task.FromResult(true); + } + + public ClientCapabilities GetCapabilities(string reportedId) + { + var device = GetDevice(reportedId); + + return device == null ? null : device.Capabilities; + } + + public DeviceInfo GetDevice(string id) + { + return GetDevices().FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); + } + + public IEnumerable GetDevices() + { + if (_devices == null) + { + lock (_syncLock) + { + if (_devices == null) + { + _devices = new ConcurrentBag(LoadDevices()); + } + } + } + return _devices.ToList(); + } + + private IEnumerable LoadDevices() + { + var path = GetDevicesPath(); + + try + { + return new DirectoryInfo(path) + .EnumerateFiles("*", SearchOption.AllDirectories) + .Where(i => string.Equals(i.Name, "device.json", StringComparison.OrdinalIgnoreCase)) + .Select(i => _json.DeserializeFromFile(i.FullName)) + .ToList(); + } + catch (IOException) + { + return new List(); + } + } + + public Task DeleteDevice(string id) + { + var path = GetDevicePath(id); + + lock (_syncLock) + { + try + { + Directory.Delete(path, true); + } + catch (DirectoryNotFoundException) + { + } + + _devices = null; + } + + return Task.FromResult(true); + } + + public ContentUploadHistory GetCameraUploadHistory(string deviceId) + { + var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); + + lock (_syncLock) + { + try + { + return _json.DeserializeFromFile(path); + } + catch (IOException) + { + return new ContentUploadHistory + { + DeviceId = deviceId + }; + } + } + } + + public void AddCameraUpload(string deviceId, LocalFileInfo file) + { + var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_syncLock) + { + ContentUploadHistory history; + + try + { + history = _json.DeserializeFromFile(path); + } + catch (IOException) + { + history = new ContentUploadHistory + { + DeviceId = deviceId + }; + } + + history.DeviceId = deviceId; + history.FilesUploaded.Add(file); + + _json.SerializeToFile(history, path); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs index 4beb34e4f9..b12ae32a85 100644 --- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs @@ -60,41 +60,45 @@ namespace MediaBrowser.Server.Implementations.Library var standaloneFolders = folders.Where(i => UserView.IsExcludedFromGrouping(i) || excludeFolderIds.Contains(i.Id)).ToList(); - list.AddRange(standaloneFolders); - - var recursiveChildren = folders + var foldersWithViewTypes = folders .Except(standaloneFolders) - .SelectMany(i => i.GetRecursiveChildren(user, false)) + .OfType() .ToList(); - if (recursiveChildren.OfType().Any()) + list.AddRange(standaloneFolders); + + if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) || + foldersWithViewTypes.Any(i => string.IsNullOrWhiteSpace(i.CollectionType))) { list.Add(await GetUserView(CollectionType.TvShows, user, string.Empty, cancellationToken).ConfigureAwait(false)); } - if (recursiveChildren.OfType().Any() || - recursiveChildren.OfType().Any()) + if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) || + foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))) { list.Add(await GetUserView(CollectionType.Music, user, string.Empty, cancellationToken).ConfigureAwait(false)); } - if (recursiveChildren.OfType().Any()) + if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) || + foldersWithViewTypes.Any(i => string.IsNullOrWhiteSpace(i.CollectionType))) { list.Add(await GetUserView(CollectionType.Movies, user, string.Empty, cancellationToken).ConfigureAwait(false)); } - if (recursiveChildren.OfType().Any()) + if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Games, StringComparison.OrdinalIgnoreCase))) { list.Add(await GetUserView(CollectionType.Games, user, string.Empty, cancellationToken).ConfigureAwait(false)); } if (user.Configuration.DisplayCollectionsView && - recursiveChildren.OfType().Any()) + folders + .Except(standaloneFolders) + .SelectMany(i => i.GetRecursiveChildren(user, false)).OfType().Any()) { list.Add(await GetUserView(CollectionType.BoxSets, user, string.Empty, cancellationToken).ConfigureAwait(false)); } - if (recursiveChildren.OfType().Any()) + if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))) { list.Add(_playlists.GetPlaylistsFolder(user.Id.ToString("N"))); } diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index 9c49d22d71..c1e9ff5cd6 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -602,5 +602,12 @@ "DashboardTourNotifications": "Automatically send notifications of server events to your mobile device, email and more.", "DashboardTourScheduledTasks": "Easily manage long running operations with scheduled tasks. Decide when they run, and how often.", "DashboardTourMobile": "The Media Browser dashboard works great on smartphones and tablets. Manage your server from the palm of your hand anytime, anywhere.", - "MessageRefreshQueued": "Refresh queued" + "MessageRefreshQueued": "Refresh queued", + "TabDevices": "Devices", + "DeviceLastUsedByUserName": "Last used by {0}", + "HeaderDeleteDevice": "Delete Device", + "DeleteDeviceConfirmation": "Are you sure you with to delete this device? It will reappear the next time a user signs in with it.", + "LabelEnableCameraUploadFor": "Enable camera upload for:", + "HeaderSelectUploadPath": "Select Upload Path", + "LabelEnableCameraUploadForHelp": "Uploads will occur automatically in the background when signed into Media Browser." } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index ceea000def..fe1f84ef37 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -1215,5 +1215,13 @@ "OptionDateAddedImportTime": "Use date scanned into the library", "OptionDateAddedFileTime": "Use file creation date", "LabelDateAddedBehaviorHelp": "If a metadata value is present it will always be used before either of these options.", - "LabelNumberTrailerToPlay": "Number of trailers to play:" + "LabelNumberTrailerToPlay": "Number of trailers to play:", + "TitleDevices": "Devices", + "TabCameraUpload": "Camera Upload", + "TabDevices": "Devices", + "TitleDevices": "Devices", + "HeaderCameraUploadHelp": "Automatically upload photos and videos taken from your mobile devices into Media Browser.", + "MessageNoDevicesSupportCameraUpload": "You currently don't have any devices that support camera upload.", + "LabelUploadPath": "Upload path:", + "LabelUploadPathHelp": "Select a custom upload path, if desired. If unspecified an internal data folder will be used." } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index c90c1c029a..2d6e32a342 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -119,6 +119,9 @@ + + + diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 58a6f649cd..10c89c6138 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -64,6 +65,7 @@ namespace MediaBrowser.Server.Implementations.Session private readonly IServerApplicationHost _appHost; private readonly IAuthenticationRepository _authRepo; + private readonly IDeviceManager _deviceManager; /// /// Gets or sets the configuration manager. @@ -80,7 +82,7 @@ namespace MediaBrowser.Server.Implementations.Session public event EventHandler> AuthenticationFailed; public event EventHandler> AuthenticationSucceeded; - + /// /// Occurs when [playback start]. /// @@ -111,7 +113,7 @@ namespace MediaBrowser.Server.Implementations.Session /// The logger. /// The user repository. /// The library manager. - public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo) + public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager) { _userDataRepository = userDataRepository; _configurationManager = configurationManager; @@ -127,6 +129,7 @@ namespace MediaBrowser.Server.Implementations.Session _appHost = appHost; _httpClient = httpClient; _authRepo = authRepo; + _deviceManager = deviceManager; } /// @@ -394,7 +397,9 @@ namespace MediaBrowser.Server.Implementations.Session try { - var connection = _activeConnections.GetOrAdd(key, keyName => + SessionInfo connection; + + if (!_activeConnections.TryGetValue(key, out connection)) { var sessionInfo = new SessionInfo { @@ -411,8 +416,15 @@ namespace MediaBrowser.Server.Implementations.Session OnSessionStarted(sessionInfo); - return sessionInfo; - }); + _activeConnections.TryAdd(key, sessionInfo); + connection = sessionInfo; + + if (!string.IsNullOrEmpty(deviceId)) + { + var userIdString = userId.HasValue ? userId.Value.ToString("N") : null; + await _deviceManager.RegisterDevice(deviceId, deviceName, userIdString).ConfigureAwait(false); + } + } connection.DeviceName = deviceName; connection.UserId = userId; @@ -1226,7 +1238,7 @@ namespace MediaBrowser.Server.Implementations.Session var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.DeviceName).ConfigureAwait(false); EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs(request), _logger); - + var session = await LogSessionActivity(request.App, request.AppVersion, request.DeviceId, @@ -1234,7 +1246,7 @@ namespace MediaBrowser.Server.Implementations.Session request.RemoteEndPoint, user) .ConfigureAwait(false); - + return new AuthenticationResult { User = _userManager.GetUserDto(user, request.RemoteEndPoint), @@ -1339,7 +1351,7 @@ namespace MediaBrowser.Server.Implementations.Session /// /// The session identifier. /// The capabilities. - public void ReportCapabilities(string sessionId, SessionCapabilities capabilities) + public void ReportCapabilities(string sessionId, ClientCapabilities capabilities) { var session = GetSession(sessionId); @@ -1347,7 +1359,7 @@ namespace MediaBrowser.Server.Implementations.Session } private async void ReportCapabilities(SessionInfo session, - SessionCapabilities capabilities, + ClientCapabilities capabilities, bool saveCapabilities) { session.PlayableMediaTypes = capabilities.PlayableMediaTypes; @@ -1375,56 +1387,14 @@ namespace MediaBrowser.Server.Implementations.Session } } - private string GetCapabilitiesFilePath(string deviceId) + private ClientCapabilities GetSavedCapabilities(string deviceId) { - var filename = deviceId.GetMD5().ToString("N") + ".json"; - - return Path.Combine(_configurationManager.ApplicationPaths.CachePath, "devices", filename); - } - - private SessionCapabilities GetSavedCapabilities(string deviceId) - { - var path = GetCapabilitiesFilePath(deviceId); - - try - { - return _jsonSerializer.DeserializeFromFile(path); - } - catch (DirectoryNotFoundException) - { - return null; - } - catch (FileNotFoundException) - { - return null; - } - catch (Exception ex) - { - _logger.ErrorException("Error getting saved capabilities", ex); - return null; - } + return _deviceManager.GetCapabilities(deviceId); } - private readonly SemaphoreSlim _capabilitiesLock = new SemaphoreSlim(1, 1); - private async Task SaveCapabilities(string deviceId, SessionCapabilities capabilities) + private Task SaveCapabilities(string deviceId, ClientCapabilities capabilities) { - var path = GetCapabilitiesFilePath(deviceId); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - await _capabilitiesLock.WaitAsync().ConfigureAwait(false); - - try - { - _jsonSerializer.SerializeToFile(capabilities, path); - } - catch (Exception ex) - { - _logger.ErrorException("Error saving to {0}", ex, path); - } - finally - { - _capabilitiesLock.Release(); - } + return _deviceManager.SaveCapabilities(deviceId, capabilities); } public SessionInfoDto GetSessionInfoDto(SessionInfo session) diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs index 2d5a30f3d4..0756aa1ec5 100644 --- a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs +++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.Server.Implementations.Session } else { - var capabilities = new SessionCapabilities + var capabilities = new ClientCapabilities { PlayableMediaTypes = Session.PlayableMediaTypes, SupportedCommands = Session.SupportedCommands, diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index b5e13e306b..263bfb6ad0 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -1,9 +1,11 @@ -using MediaBrowser.Common.Extensions; +using System.IO; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Devices; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config index c5abd3a20e..f70540a767 100644 --- a/MediaBrowser.Server.Mono/app.config +++ b/MediaBrowser.Server.Mono/app.config @@ -1,4 +1,4 @@ - +
@@ -10,4 +10,12 @@ + + + + + + + + diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 17c5b108a4..8e41ef03ea 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -14,6 +14,7 @@ using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Connect; +using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -59,6 +60,7 @@ using MediaBrowser.Server.Implementations.Channels; using MediaBrowser.Server.Implementations.Collections; using MediaBrowser.Server.Implementations.Configuration; using MediaBrowser.Server.Implementations.Connect; +using MediaBrowser.Server.Implementations.Devices; using MediaBrowser.Server.Implementations.Drawing; using MediaBrowser.Server.Implementations.Dto; using MediaBrowser.Server.Implementations.EntryPoints; @@ -212,6 +214,7 @@ namespace MediaBrowser.ServerApplication private INotificationManager NotificationManager { get; set; } private ISubtitleManager SubtitleManager { get; set; } private IChapterManager ChapterManager { get; set; } + private IDeviceManager DeviceManager { get; set; } internal IUserViewManager UserViewManager { get; set; } @@ -316,8 +319,6 @@ namespace MediaBrowser.ServerApplication PerformVersionMigration(); await base.Init(progress).ConfigureAwait(false); - - MigrateModularConfigurations(); } private void PerformVersionMigration() @@ -325,16 +326,6 @@ namespace MediaBrowser.ServerApplication DeleteDeprecatedModules(); } - private void MigrateModularConfigurations() - { - var saveConfig = false; - - if (saveConfig) - { - ServerConfigurationManager.SaveConfiguration(); - } - } - private void DeleteDeprecatedModules() { try @@ -469,7 +460,10 @@ namespace MediaBrowser.ServerApplication ConnectManager = new ConnectManager(LogManager.GetLogger("Connect"), ApplicationPaths, JsonSerializer, encryptionManager, HttpClient, this, ServerConfigurationManager, UserManager); RegisterSingleInstance(ConnectManager); - SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository); + DeviceManager = new DeviceManager(new DeviceRepository(ApplicationPaths, JsonSerializer), UserManager, FileSystemManager, LibraryMonitor, ConfigurationManager); + RegisterSingleInstance(DeviceManager); + + SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager); RegisterSingleInstance(SessionManager); var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 4b1f39bf43..9c31e29408 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -564,6 +564,8 @@ namespace MediaBrowser.WebDashboard.Api "dashboardgeneral.js", "dashboardpage.js", "dashboardsync.js", + "devices.js", + "devicesupload.js", "directorybrowser.js", "dlnaprofile.js", "dlnaprofiles.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 292200bfa3..65fc46978e 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -58,8 +58,7 @@ ..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll - ..\packages\WebMarkupMin.Core.0.9.6\lib\net40\WebMarkupMin.Core.dll - True + ..\packages\WebMarkupMin.Core.0.9.7\lib\net40\WebMarkupMin.Core.dll @@ -368,6 +367,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -692,6 +697,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/MediaBrowser.WebDashboard/app.config b/MediaBrowser.WebDashboard/app.config index 8d7ee4f172..d66f0b626a 100644 --- a/MediaBrowser.WebDashboard/app.config +++ b/MediaBrowser.WebDashboard/app.config @@ -9,6 +9,7 @@ + diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 7a472fe0d0..573abbdbdd 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index b27b010d4c..41f99f47da 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.467 + 3.0.476 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index f40d4377dc..962eae0c87 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.467 + 3.0.476 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 904ec78962..992d2f2d02 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.467 + 3.0.476 MediaBrowser.Model - Signed Edition Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 45d05c2824..7c65bf3e70 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.467 + 3.0.476 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - + diff --git a/SharedVersion.cs b/SharedVersion.cs index 594ca02193..687c9204d9 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -3,5 +3,6 @@ #if (DEBUG) [assembly: AssemblyVersion("3.0.*")] #else -[assembly: AssemblyVersion("3.0.5395.0")] +[assembly: AssemblyVersion("3.0.*")] +//[assembly: AssemblyVersion("3.0.5395.0")] #endif