diff options
| author | jluce50 <jeremyluce@gmail.com> | 2015-03-03 09:42:17 -0600 |
|---|---|---|
| committer | jluce50 <jeremyluce@gmail.com> | 2015-03-03 09:42:17 -0600 |
| commit | 6c2e01830c7234438b879af633caaf415f560a5a (patch) | |
| tree | afd595af2f85888544f437b2a367e241ade607dc /MediaBrowser.Server.Implementations | |
| parent | 7fd26410a9c49e84a146dfd77a2732b2330c3834 (diff) | |
| parent | 2fc0686c308e74654f4f7ef9ea6cf56fb61b5ff5 (diff) | |
Merge pull request #1 from MediaBrowser/dev
Dev
Diffstat (limited to 'MediaBrowser.Server.Implementations')
31 files changed, 913 insertions, 198 deletions
diff --git a/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs b/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs index 2fe5d8f74..566f4c5f4 100644 --- a/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs +++ b/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; +using System; using System.IO; using System.Linq; @@ -14,6 +15,11 @@ namespace MediaBrowser.Server.Implementations.Devices public override bool IsVisible(User user) { + if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + { + return false; + } + return GetChildren(user, true).Any() && base.IsVisible(user); } diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs b/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs index 81d4a786a..6287d0bb8 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs @@ -62,8 +62,9 @@ namespace MediaBrowser.Server.Implementations.Drawing logger.Info("Failed to read image header for {0}. Doing it the slow way.", path); } - using (var wand = new MagickWand(path)) + using (var wand = new MagickWand()) { + wand.PingImage(path); var img = wand.CurrentImage; return new ImageSize diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 85eadd73c..180faa6bb 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -350,9 +350,9 @@ namespace MediaBrowser.Server.Implementations.Drawing } /// <summary> - /// Increment this when indicator drawings change + /// Increment this when there's a change requiring caches to be invalidated /// </summary> - private const string IndicatorVersion = "2"; + private const string Version = "3"; /// <summary> /// Gets the cache file path based on a set of parameters @@ -371,29 +371,19 @@ namespace MediaBrowser.Server.Implementations.Drawing filename += "f=" + format; - var hasIndicator = false; - if (addPlayedIndicator) { filename += "pl=true"; - hasIndicator = true; } if (percentPlayed > 0) { filename += "p=" + percentPlayed; - hasIndicator = true; } if (unwatchedCount.HasValue) { filename += "p=" + unwatchedCount.Value; - hasIndicator = true; - } - - if (hasIndicator) - { - filename += "iv=" + IndicatorVersion; } if (!string.IsNullOrEmpty(backgroundColor)) @@ -401,6 +391,8 @@ namespace MediaBrowser.Server.Implementations.Drawing filename += "b=" + backgroundColor; } + filename += "v=" + Version; + return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower()); } @@ -414,6 +406,11 @@ namespace MediaBrowser.Server.Implementations.Drawing return GetImageSize(path, File.GetLastWriteTimeUtc(path)); } + public ImageSize GetImageSize(ItemImageInfo info) + { + return GetImageSize(info.Path, info.DateModified); + } + /// <summary> /// Gets the size of the image. /// </summary> @@ -421,7 +418,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// <param name="imageDateModified">The image date modified.</param> /// <returns>ImageSize.</returns> /// <exception cref="System.ArgumentNullException">path</exception> - public ImageSize GetImageSize(string path, DateTime imageDateModified) + private ImageSize GetImageSize(string path, DateTime imageDateModified) { if (string.IsNullOrEmpty(path)) { @@ -666,30 +663,6 @@ namespace MediaBrowser.Server.Implementations.Drawing return enhancedImagePath; } - private ImageFormat GetFormat(string path) - { - var extension = Path.GetExtension(path); - - if (string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase)) - { - return ImageFormat.Png; - } - if (string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase)) - { - return ImageFormat.Gif; - } - if (string.Equals(extension, ".webp", StringComparison.OrdinalIgnoreCase)) - { - return ImageFormat.Webp; - } - if (string.Equals(extension, ".bmp", StringComparison.OrdinalIgnoreCase)) - { - return ImageFormat.Bmp; - } - - return ImageFormat.Jpg; - } - /// <summary> /// Executes the image enhancers. /// </summary> diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index b15809738..75037159c 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1598,14 +1598,11 @@ namespace MediaBrowser.Server.Implementations.Dto var path = imageInfo.Path; - // See if we can avoid a file system lookup by looking for the file in ResolveArgs - var dateModified = imageInfo.DateModified; - ImageSize size; try { - size = _imageProcessor.GetImageSize(path, dateModified); + size = _imageProcessor.GetImageSize(imageInfo); } catch (FileNotFoundException) { diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs index 0b0661321..28883e9a2 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs @@ -86,6 +86,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _userManager.UserPasswordChanged += _userManager_UserPasswordChanged; _userManager.UserDeleted += _userManager_UserDeleted; _userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated; + _userManager.UserLockedOut += _userManager_UserLockedOut; //_config.ConfigurationUpdated += _config_ConfigurationUpdated; //_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; @@ -95,6 +96,16 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; } + void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name), + Type = "UserLockedOut", + UserId = e.Argument.Id.ToString("N") + }); + } + void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) { CreateLogEntry(new ActivityLogEntry @@ -482,6 +493,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _userManager.UserPasswordChanged -= _userManager_UserPasswordChanged; _userManager.UserDeleted -= _userManager_UserDeleted; _userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated; + _userManager.UserLockedOut -= _userManager_UserLockedOut; _config.ConfigurationUpdated -= _config_ConfigurationUpdated; _config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated; diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs index 37bca4ddb..f6a35973b 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs @@ -78,6 +78,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged; _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; _deviceManager.CameraImageUploaded +=_deviceManager_CameraImageUploaded; + + _userManager.UserLockedOut += _userManager_UserLockedOut; + } + + async void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e) + { + var type = NotificationType.UserLockedOut.ToString(); + + var notification = new NotificationRequest + { + NotificationType = type + }; + + notification.Variables["UserName"] = e.Argument.Name; + + await SendNotification(notification).ConfigureAwait(false); } async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e) @@ -235,7 +251,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications return; } - var notification = new NotificationRequest { NotificationType = type @@ -471,6 +486,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; _deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded; + _userManager.UserLockedOut -= _userManager_UserLockedOut; } private void DisposeLibraryUpdateTimer() diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 681d3ac5e..ecf58e4a6 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -461,10 +461,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer { return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest) { - Throttle = options.Throttle, - ThrottleLimit = options.ThrottleLimit, - MinThrottlePosition = options.MinThrottlePosition, - ThrottleCallback = options.ThrottleCallback, OnComplete = options.OnComplete }; } @@ -480,10 +476,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer return new StreamWriter(stream, contentType, _logger) { - Throttle = options.Throttle, - ThrottleLimit = options.ThrottleLimit, - MinThrottlePosition = options.MinThrottlePosition, - ThrottleCallback = options.ThrottleCallback, OnComplete = options.OnComplete }; } diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index cdd4c6d7c..8c72f9e7e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -24,10 +24,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer private long RangeLength { get; set; } private long TotalContentLength { get; set; } - public bool Throttle { get; set; } - public long ThrottleLimit { get; set; } - public long MinThrottlePosition; - public Func<long, long, long> ThrottleCallback { get; set; } public Action OnComplete { get; set; } /// <summary> @@ -165,14 +161,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="responseStream">The response stream.</param> public void WriteTo(Stream responseStream) { - if (Throttle) - { - responseStream = new ThrottledStream(responseStream, ThrottleLimit) - { - MinThrottlePosition = MinThrottlePosition, - ThrottleCallback = ThrottleCallback - }; - } WriteToInternal(responseStream); } diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs index 1db14e887..fe662542e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs @@ -35,10 +35,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer get { return _options; } } - public bool Throttle { get; set; } - public long ThrottleLimit { get; set; } - public long MinThrottlePosition; - public Func<long, long, long> ThrottleCallback { get; set; } public Action OnComplete { get; set; } /// <summary> @@ -82,14 +78,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="responseStream">The response stream.</param> public void WriteTo(Stream responseStream) { - if (Throttle) - { - responseStream = new ThrottledStream(responseStream, ThrottleLimit) - { - MinThrottlePosition = MinThrottlePosition, - ThrottleCallback = ThrottleCallback - }; - } WriteToInternal(responseStream); } diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index a45757d13..6ce989b02 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; +using System; using System.Collections.Generic; using System.Linq; @@ -47,5 +48,59 @@ namespace MediaBrowser.Server.Implementations.Library { return true; } + + public IEnumerable<MediaStream> GetMediaStreams(string mediaSourceId) + { + var list = GetMediaStreams(new MediaStreamQuery + { + ItemId = new Guid(mediaSourceId) + }); + + return GetMediaStreamsForItem(list); + } + + public IEnumerable<MediaStream> GetMediaStreams(Guid itemId) + { + var list = GetMediaStreams(new MediaStreamQuery + { + ItemId = itemId + }); + + return GetMediaStreamsForItem(list); + } + + private IEnumerable<MediaStream> GetMediaStreamsForItem(IEnumerable<MediaStream> streams) + { + var list = streams.ToList(); + + var subtitleStreams = list + .Where(i => i.Type == MediaStreamType.Subtitle) + .ToList(); + + if (subtitleStreams.Count > 0) + { + var videoStream = list.FirstOrDefault(i => i.Type == MediaStreamType.Video); + + // This is abitrary but at some point it becomes too slow to extract subtitles on the fly + // We need to learn more about when this is the case vs. when it isn't + const int maxAllowedBitrateForExternalSubtitleStream = 10000000; + + var videoBitrate = videoStream == null ? maxAllowedBitrateForExternalSubtitleStream : videoStream.BitRate ?? maxAllowedBitrateForExternalSubtitleStream; + + foreach (var subStream in subtitleStreams) + { + var supportsExternalStream = StreamSupportsExternalStream(subStream); + + if (supportsExternalStream && videoBitrate >= maxAllowedBitrateForExternalSubtitleStream) + { + supportsExternalStream = false; + } + + subStream.SupportsExternalStream = supportsExternalStream; + } + } + + return list; + } } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 7371ca5a9..3551b71b7 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -1,19 +1,18 @@ -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; -using System; -using System.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Naming.Common; using MediaBrowser.Naming.IO; using MediaBrowser.Naming.TV; using MediaBrowser.Server.Implementations.Logging; -using EpisodeInfo = MediaBrowser.Controller.Providers.EpisodeInfo; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index bf8792461..00c674436 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -97,6 +97,7 @@ namespace MediaBrowser.Server.Implementations.Library /// </summary> public event EventHandler<GenericEventArgs<User>> UserUpdated; public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated; + public event EventHandler<GenericEventArgs<User>> UserLockedOut; /// <summary> /// Called when [user updated]. @@ -259,6 +260,11 @@ namespace MediaBrowser.Server.Implementations.Library { user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; await UpdateUser(user).ConfigureAwait(false); + await UpdateInvalidLoginAttemptCount(user, 0).ConfigureAwait(false); + } + else + { + await UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1).ConfigureAwait(false); } _logger.Info("Authentication request for {0} {1}.", user.Name, (success ? "has succeeded" : "has been denied")); @@ -266,6 +272,38 @@ namespace MediaBrowser.Server.Implementations.Library return success; } + private async Task UpdateInvalidLoginAttemptCount(User user, int newValue) + { + if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0) + { + user.Policy.InvalidLoginAttemptCount = newValue; + + var maxCount = user.Policy.IsAdministrator ? + 3 : + 5; + + var fireLockout = false; + + if (newValue >= maxCount) + { + _logger.Debug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture)); + user.Policy.IsDisabled = true; + + fireLockout = true; + } + + await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false); + + if (fireLockout) + { + if (UserLockedOut != null) + { + EventHelper.FireEventIfNotNull(UserLockedOut, this, new GenericEventArgs<User>(user), _logger); + } + } + } + } + private string GetPasswordHash(User user) { return string.IsNullOrEmpty(user.Password) @@ -332,11 +370,6 @@ namespace MediaBrowser.Server.Implementations.Library { if (!user.Configuration.HasMigratedToPolicy) { - user.Policy.BlockUnratedItems = user.Configuration.BlockUnratedItems; - user.Policy.EnableContentDeletion = user.Configuration.EnableContentDeletion; - user.Policy.EnableLiveTvAccess = user.Configuration.EnableLiveTvAccess; - user.Policy.EnableLiveTvManagement = user.Configuration.EnableLiveTvManagement; - user.Policy.EnableMediaPlayback = user.Configuration.EnableMediaPlayback; user.Policy.IsAdministrator = user.Configuration.IsAdministrator; await UpdateUserPolicy(user, user.Policy, false); @@ -915,10 +948,6 @@ namespace MediaBrowser.Server.Implementations.Library } user.Configuration.IsAdministrator = user.Policy.IsAdministrator; - user.Configuration.EnableLiveTvManagement = user.Policy.EnableLiveTvManagement; - user.Configuration.EnableLiveTvAccess = user.Policy.EnableLiveTvAccess; - user.Configuration.EnableMediaPlayback = user.Policy.EnableMediaPlayback; - user.Configuration.EnableContentDeletion = user.Policy.EnableContentDeletion; await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index f1bb5c13a..6473d10d6 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -442,7 +442,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return null; } - private const string InternalVersionNumber = "3"; + private const string InternalVersionNumber = "4"; public Guid GetInternalChannelId(string serviceName, string externalId) { diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index dc80778da..2f593efcd 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -48,8 +48,10 @@ "LabelDashboardSourcePathHelp": "If running the server from source, specify the path to the dashboard-ui folder. All web client files will be served from this location.", "ButtonConvertMedia": "Convert media", "ButtonOrganize": "Organize", + "LabelPinCode": "Pin code:", "ButtonOk": "Ok", "ButtonCancel": "Cancel", + "ButtonExit": "Exit", "ButtonNew": "New", "HeaderTV": "TV", "HeaderAudio": "Audio", @@ -57,6 +59,12 @@ "HeaderPaths": "Paths", "CategorySync": "Sync", "HeaderEasyPinCode": "Easy Pin Code", + "HeaderGrownupsOnly": "Grown-ups Only!", + "DividerOr": "-- or --", + "HeaderToAccessPleaseEnterEasyPinCode": "To access, please enter your easy pin code", + "KidsModeAdultInstruction": "Click the lock icon in the bottom right to configure or leave kids mode. Your pin code will be required.", + "ButtonConfigurePinCode": "Configure pin code", + "HeaderAdultsReadHere": "Adults Read Here!", "RegisterWithPayPal": "Register with PayPal", "HeaderSyncRequiresSupporterMembership": "Sync Requires a Supporter Membership", "HeaderEnjoyDayTrial": "Enjoy a 14 Day Free Trial", @@ -670,6 +678,7 @@ "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)", "NotificationOptionCameraImageUploaded": "Camera image uploaded", + "NotificationOptionUserLockedOut": "User locked out", "SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.", "NotificationOptionServerRestartRequired": "Server restart required", "LabelNotificationEnabled": "Enable this notification", @@ -1061,6 +1070,7 @@ "OptionBox": "Box", "OptionBoxRear": "Box rear", "OptionDisc": "Disc", + "OptionIcon": "Icon", "OptionLogo": "Logo", "OptionMenu": "Menu", "OptionScreenshot": "Screenshot", @@ -1105,6 +1115,7 @@ "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "LabelRunningTimeValue": "Running time: {0}", "LabelIpAddressValue": "Ip address: {0}", + "UserLockedOutWithName": "User {0} has been locked out", "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}", "UserCreatedWithName": "User {0} has been created", "UserPasswordChangedWithName": "Password has been changed for user {0}", @@ -1114,7 +1125,7 @@ "MessageApplicationUpdated": "Media Browser Server has been updated", "AuthenticationSucceededWithUserName": "{0} successfully authenticated", "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "UserDownloadingItemWithValues": "{0} is downloading {1}", + "UserDownloadingItemWithValues": "{0} is downloading {1}", "UserStartedPlayingItemWithValues": "{0} has started playing {1}", "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}", "AppDeviceValues": "App: {0}, Device: {1}", @@ -1369,5 +1380,7 @@ "TabJobs": "Jobs", "TabSyncJobs": "Sync Jobs", "LabelTagFilterMode": "Mode:", - "LabelTagFilterAllowModeHelp": "If allowed tags are used as part of a deeply nested folder structure, content that is tagged will require parent folders to be tagged as well." + "LabelTagFilterAllowModeHelp": "If allowed tags are used as part of a deeply nested folder structure, content that is tagged will require parent folders to be tagged as well.", + "HeaderThisUserIsCurrentlyDisabled": "This user is currently disabled", + "MessageReenableUser": "See below to reenable" } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 509a274ee..54df9a86d 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -47,7 +47,7 @@ <ItemGroup> <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\ImageMagickSharp.1.0.0.2\lib\net45\ImageMagickSharp.dll</HintPath> + <HintPath>..\packages\ImageMagickSharp.1.0.0.6\lib\net45\ImageMagickSharp.dll</HintPath> </Reference> <Reference Include="MediaBrowser.Naming, Version=1.0.5509.27636, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> @@ -278,6 +278,7 @@ <Compile Include="Sorting\CommunityRatingComparer.cs" /> <Compile Include="Sorting\CriticRatingComparer.cs" /> <Compile Include="Sorting\DateCreatedComparer.cs" /> + <Compile Include="Sorting\DateLastMediaAddedComparer.cs" /> <Compile Include="Sorting\DatePlayedComparer.cs" /> <Compile Include="Sorting\GameSystemComparer.cs" /> <Compile Include="Sorting\IsFavoriteOrLikeComparer.cs" /> @@ -303,8 +304,12 @@ <Compile Include="Sorting\StudioComparer.cs" /> <Compile Include="Sorting\VideoBitRateComparer.cs" /> <Compile Include="Sync\AppSyncProvider.cs" /> - <Compile Include="Sync\CloudSyncProvider.cs" /> + <Compile Include="Sync\FolderSync\FolderSyncDataProvider.cs" /> + <Compile Include="Sync\FolderSync\FolderSyncProvider.cs" /> + <Compile Include="Sync\CloudSyncProfile.cs" /> + <Compile Include="Sync\IHasSyncProfile.cs" /> <Compile Include="Sync\MediaSync.cs" /> + <Compile Include="Sync\MultiProviderSync.cs" /> <Compile Include="Sync\SyncRegistrationInfo.cs" /> <Compile Include="Sync\SyncConfig.cs" /> <Compile Include="Sync\SyncJobProcessor.cs" /> diff --git a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs b/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs index d8acbe06c..a33fe2147 100644 --- a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs +++ b/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs @@ -143,6 +143,13 @@ namespace MediaBrowser.Server.Implementations.Notifications Type = NotificationType.CameraImageUploaded.ToString(), DefaultTitle = "A new camera image has been uploaded from {DeviceName}.", Variables = new List<string>{"DeviceName"} + }, + + new NotificationTypeInfo + { + Type = NotificationType.UserLockedOut.ToString(), + DefaultTitle = "{UserName} has been locked out.", + Variables = new List<string>{"UserName"} } }; @@ -185,6 +192,10 @@ namespace MediaBrowser.Server.Implementations.Notifications { note.Category = _localization.GetLocalizedString("CategorySync"); } + else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1) + { + note.Category = _localization.GetLocalizedString("CategoryUser"); + } else { note.Category = _localization.GetLocalizedString("CategorySystem"); diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs index e1f98c659..40b85dad1 100644 --- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs @@ -108,7 +108,12 @@ namespace MediaBrowser.Server.Implementations.Photos protected Task<Stream> GetThumbCollage(List<BaseItem> items) { - return DynamicImageHelpers.GetThumbCollage(items.Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)).ToList(), + var files = items + .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .ToList(); + + return DynamicImageHelpers.GetThumbCollage(files, FileSystem, 1600, 900, @@ -117,7 +122,12 @@ namespace MediaBrowser.Server.Implementations.Photos protected Task<Stream> GetSquareCollage(List<BaseItem> items) { - return DynamicImageHelpers.GetSquareCollage(items.Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)).ToList(), + var files = items + .Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .ToList(); + + return DynamicImageHelpers.GetSquareCollage(files, FileSystem, 800, ApplicationPaths); } diff --git a/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs b/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs index c2af9cdaf..e7cd2f4d2 100644 --- a/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs +++ b/MediaBrowser.Server.Implementations/Photos/DynamicImageHelpers.cs @@ -4,6 +4,7 @@ using MediaBrowser.Common.IO; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Photos @@ -15,6 +16,11 @@ namespace MediaBrowser.Server.Implementations.Photos int width, int height, IApplicationPaths appPaths) { + if (files.Any(string.IsNullOrWhiteSpace)) + { + throw new ArgumentException("Empty file found in files list"); + } + if (files.Count < 3) { return await GetSingleImage(files, fileSystem).ConfigureAwait(false); @@ -27,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.Photos int cellHeight = height; var index = 0; - using (var wand = new MagickWand(width, height, "transparent")) + using (var wand = new MagickWand(width, height, new PixelWand(ColorName.None, 1))) { for (var row = 0; row < rows; row++) { @@ -57,6 +63,11 @@ namespace MediaBrowser.Server.Implementations.Photos IFileSystem fileSystem, int size, IApplicationPaths appPaths) { + if (files.Any(string.IsNullOrWhiteSpace)) + { + throw new ArgumentException("Empty file found in files list"); + } + if (files.Count < 4) { return await GetSingleImage(files, fileSystem).ConfigureAwait(false); @@ -68,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.Photos int singleSize = size / 2; var index = 0; - using (var wand = new MagickWand(size, size, "transparent")) + using (var wand = new MagickWand(size, size, new PixelWand(ColorName.None, 1))) { for (var row = 0; row < rows; row++) { diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index d02ef9d27..8eef8536a 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -399,7 +399,7 @@ namespace MediaBrowser.Server.Implementations.Session Client = clientType, DeviceId = deviceId, ApplicationVersion = appVersion, - Id = Guid.NewGuid().ToString("N") + Id = key.GetMD5().ToString("N") }; sessionInfo.DeviceName = deviceName; @@ -798,6 +798,19 @@ namespace MediaBrowser.Server.Implementations.Session return session; } + private SessionInfo GetSessionToRemoteControl(string sessionId) + { + // Accept either device id or session id + var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId)); + + if (session == null) + { + throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId)); + } + + return session; + } + public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken) { var generalCommand = new GeneralCommand @@ -818,7 +831,7 @@ namespace MediaBrowser.Server.Implementations.Session public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken) { - var session = GetSession(sessionId); + var session = GetSessionToRemoteControl(sessionId); var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); @@ -828,7 +841,7 @@ namespace MediaBrowser.Server.Implementations.Session public Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken) { - var session = GetSession(sessionId); + var session = GetSessionToRemoteControl(sessionId); var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null; @@ -955,7 +968,7 @@ namespace MediaBrowser.Server.Implementations.Session public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken) { - var session = GetSession(sessionId); + var session = GetSessionToRemoteControl(sessionId); var controllingSession = GetSession(controllingSessionId); AssertCanControl(session, controllingSession); @@ -1566,11 +1579,7 @@ namespace MediaBrowser.Server.Implementations.Session if (!string.IsNullOrWhiteSpace(mediaSourceId)) { - info.MediaStreams = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery - { - ItemId = new Guid(mediaSourceId) - - }).ToList(); + info.MediaStreams = _mediaSourceManager.GetMediaStreams(mediaSourceId).ToList(); } return info; diff --git a/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs new file mode 100644 index 000000000..68cd44ec9 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -0,0 +1,70 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class DateLastMediaAddedComparer : IUserBaseItemComparer + { + /// <summary> + /// Gets or sets the user. + /// </summary> + /// <value>The user.</value> + public User User { get; set; } + + /// <summary> + /// Gets or sets the user manager. + /// </summary> + /// <value>The user manager.</value> + public IUserManager UserManager { get; set; } + + /// <summary> + /// Gets or sets the user data repository. + /// </summary> + /// <value>The user data repository.</value> + public IUserDataManager UserDataRepository { get; set; } + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetDate(x).CompareTo(GetDate(y)); + } + + /// <summary> + /// Gets the date. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>DateTime.</returns> + private DateTime GetDate(BaseItem x) + { + var folder = x as Folder; + + if (folder != null) + { + return folder.GetRecursiveChildren(User, i => !i.IsFolder) + .Select(i => i.DateCreated) + .OrderByDescending(i => i) + .FirstOrDefault(); + } + + return x.DateCreated; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.DateLastContentAdded; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs index 7605a7a50..c881591be 100644 --- a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -1,6 +1,5 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Querying; using System; diff --git a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs index d35ff8fc4..2106fc12e 100644 --- a/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs @@ -8,7 +8,7 @@ using System.Linq; namespace MediaBrowser.Server.Implementations.Sync { - public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds + public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds, IHasSyncProfile { private readonly IDeviceManager _deviceManager; @@ -42,5 +42,18 @@ namespace MediaBrowser.Server.Implementations.Sync { get { return "App Sync"; } } + + public IEnumerable<SyncTarget> GetAllSyncTargets() + { + return _deviceManager.GetDevices(new DeviceQuery + { + SupportsSync = true + + }).Items.Select(i => new SyncTarget + { + Id = i.Id, + Name = i.Name + }); + } } } diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs new file mode 100644 index 000000000..babdf3f80 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs @@ -0,0 +1,118 @@ +using MediaBrowser.Model.Dlna; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class CloudSyncProfile : DeviceProfile + { + public CloudSyncProfile(bool supportsAc3, bool supportsDca) + { + Name = "Cloud Sync"; + + MaxStreamingBitrate = 20000000; + MaxStaticBitrate = 20000000; + + var mkvAudio = "aac,mp3"; + var mp4Audio = "aac"; + + if (supportsAc3) + { + mkvAudio += ",ac3"; + mp4Audio += ",ac3"; + } + + if (supportsDca) + { + mkvAudio += ",dca"; + } + + DirectPlayProfiles = new[] + { + new DirectPlayProfile + { + Container = "mkv", + VideoCodec = "h264,mpeg4", + AudioCodec = mkvAudio, + Type = DlnaProfileType.Video + }, + new DirectPlayProfile + { + Container = "mp4,mov,m4v", + VideoCodec = "h264,mpeg4", + AudioCodec = mp4Audio, + Type = DlnaProfileType.Video + } + }; + + ContainerProfiles = new ContainerProfile[] { }; + + CodecProfiles = new[] + { + new CodecProfile + { + Type = CodecType.Video, + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.VideoBitDepth, + Value = "8", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.Height, + Value = "1080", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.RefFrames, + Value = "12", + IsRequired = false + } + } + } + }; + + SubtitleProfiles = new[] + { + new SubtitleProfile + { + Format = "srt", + Method = SubtitleDeliveryMethod.External + } + }; + + TranscodingProfiles = new[] + { + new TranscodingProfile + { + Container = "mp3", + AudioCodec = "mp3", + Type = DlnaProfileType.Audio, + Context = EncodingContext.Static + }, + + new TranscodingProfile + { + Container = "mp4", + Type = DlnaProfileType.Video, + AudioCodec = "aac", + VideoCodec = "h264", + Context = EncodingContext.Static + }, + + new TranscodingProfile + { + Container = "jpeg", + Type = DlnaProfileType.Photo, + Context = EncodingContext.Static + } + }; + + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs deleted file mode 100644 index 37caa561e..000000000 --- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProvider.cs +++ /dev/null @@ -1,59 +0,0 @@ -using MediaBrowser.Common; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.Sync -{ - public class CloudSyncProvider : IServerSyncProvider - { - private readonly ICloudSyncProvider[] _providers = {}; - - public CloudSyncProvider(IApplicationHost appHost) - { - _providers = appHost.GetExports<ICloudSyncProvider>().ToArray(); - } - - public IEnumerable<SyncTarget> GetSyncTargets(string userId) - { - return _providers.SelectMany(i => i.GetSyncTargets(userId)); - } - - public DeviceProfile GetDeviceProfile(SyncTarget target) - { - return new DeviceProfile(); - } - - public string Name - { - get { return "Cloud Sync"; } - } - - private ICloudSyncProvider GetProvider(SyncTarget target) - { - return null; - } - - public Task<List<string>> GetServerItemIds(string serverId, SyncTarget target, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task DeleteItem(string serverId, string itemId, SyncTarget target, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken) - { - var provider = GetProvider(target); - - return provider.TransferItemFile(serverId, itemId, inputFile, pathParts, target, cancellationToken); - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncDataProvider.cs b/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncDataProvider.cs new file mode 100644 index 000000000..b9008d87e --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncDataProvider.cs @@ -0,0 +1,31 @@ +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sync.FolderSync +{ + public class FolderSyncDataProvider : ISyncDataProvider + { + public Task<List<string>> GetServerItemIds(SyncTarget target, string serverId) + { + throw new NotImplementedException(); + } + + public Task AddOrUpdate(SyncTarget target, LocalItem item) + { + throw new NotImplementedException(); + } + + public Task Delete(SyncTarget target, string id) + { + throw new NotImplementedException(); + } + + public Task<LocalItem> Get(SyncTarget target, string id) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncProvider.cs b/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncProvider.cs new file mode 100644 index 000000000..3183816c8 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/FolderSync/FolderSyncProvider.cs @@ -0,0 +1,143 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sync.FolderSync +{ + public class FolderSyncProvider : IServerSyncProvider + { + private readonly IApplicationPaths _appPaths; + private readonly IUserManager _userManager; + + public FolderSyncProvider(IApplicationPaths appPaths, IUserManager userManager) + { + _appPaths = appPaths; + _userManager = userManager; + } + + public Task SendFile(string inputFile, string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken) + { + return Task.Run(() => File.Copy(inputFile, path, true), cancellationToken); + } + + public Task DeleteFile(string path, SyncTarget target, CancellationToken cancellationToken) + { + return Task.Run(() => File.Delete(path), cancellationToken); + } + + public Task<Stream> GetFile(string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken) + { + return Task.FromResult((Stream)File.OpenRead(path)); + } + + public string GetFullPath(IEnumerable<string> paths, SyncTarget target) + { + var account = GetSyncAccounts() + .FirstOrDefault(i => string.Equals(i.Id, target.Id, StringComparison.OrdinalIgnoreCase)); + + if (account == null) + { + throw new ArgumentException("Invalid SyncTarget supplied."); + } + + var list = paths.ToList(); + list.Insert(0, account.Path); + + return Path.Combine(list.ToArray()); + } + + public string GetParentDirectoryPath(string path, SyncTarget target) + { + return Path.GetDirectoryName(path); + } + + public Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target) + { + List<FileInfo> files; + + try + { + files = new DirectoryInfo(path).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList(); + } + catch (DirectoryNotFoundException) + { + files = new List<FileInfo>(); + } + + return Task.FromResult(files.Select(i => new DeviceFileInfo + { + Name = i.Name, + Path = i.FullName + + }).ToList()); + } + + public ISyncDataProvider GetDataProvider() + { + // If single instances are needed, manage them here + return new FolderSyncDataProvider(); + } + + public string Name + { + get { return "Folder Sync"; } + } + + public IEnumerable<SyncTarget> GetSyncTargets(string userId) + { + return GetSyncAccounts() + .Where(i => i.UserIds.Contains(userId, StringComparer.OrdinalIgnoreCase)) + .Select(GetSyncTarget); + } + + public IEnumerable<SyncTarget> GetAllSyncTargets() + { + return GetSyncAccounts().Select(GetSyncTarget); + } + + private SyncTarget GetSyncTarget(SyncAccount account) + { + return new SyncTarget + { + Id = account.Id, + Name = account.Name + }; + } + + private IEnumerable<SyncAccount> GetSyncAccounts() + { + return new List<SyncAccount>(); + // Dummy this up + return _userManager + .Users + .Select(i => new SyncAccount + { + Id = i.Id.ToString("N"), + UserIds = new List<string> { i.Id.ToString("N") }, + Path = Path.Combine(_appPaths.DataPath, "foldersync", i.Id.ToString("N")), + Name = i.Name + "'s Folder Sync" + }); + } + + // An internal class to manage all configured Folder Sync accounts for differnet users + class SyncAccount + { + public string Id { get; set; } + public string Name { get; set; } + public string Path { get; set; } + public List<string> UserIds { get; set; } + + public SyncAccount() + { + UserIds = new List<string>(); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs new file mode 100644 index 000000000..b7e9daf49 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Sync; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public interface IHasSyncProfile + { + /// <summary> + /// Gets the device profile. + /// </summary> + /// <param name="target">The target.</param> + /// <returns>DeviceProfile.</returns> + DeviceProfile GetDeviceProfile(SyncTarget target); + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index 349e6aa1d..0407510a8 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -1,10 +1,18 @@ -using MediaBrowser.Common.Progress; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Sync; using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -15,22 +23,25 @@ namespace MediaBrowser.Server.Implementations.Sync private readonly ISyncManager _syncManager; private readonly IServerApplicationHost _appHost; private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; - public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost) + public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost, IFileSystem fileSystem) { _logger = logger; _syncManager = syncManager; _appHost = appHost; + _fileSystem = fileSystem; } - public async Task Sync(IServerSyncProvider provider, + public async Task Sync(IServerSyncProvider provider, + ISyncDataProvider dataProvider, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken) { var serverId = _appHost.SystemId; - await SyncData(provider, serverId, target, cancellationToken).ConfigureAwait(false); + await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); progress.Report(3); var innerProgress = new ActionableProgress<double>(); @@ -40,20 +51,21 @@ namespace MediaBrowser.Server.Implementations.Sync totalProgress += 1; progress.Report(totalProgress); }); - await GetNewMedia(provider, target, serverId, innerProgress, cancellationToken); + await GetNewMedia(provider, dataProvider, target, serverId, innerProgress, cancellationToken); // Do the data sync twice so the server knows what was removed from the device - await SyncData(provider, serverId, target, cancellationToken).ConfigureAwait(false); + await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); progress.Report(100); } private async Task SyncData(IServerSyncProvider provider, + ISyncDataProvider dataProvider, string serverId, SyncTarget target, CancellationToken cancellationToken) { - var localIds = await provider.GetServerItemIds(serverId, target, cancellationToken).ConfigureAwait(false); + var localIds = await dataProvider.GetServerItemIds(target, serverId).ConfigureAwait(false); var result = await _syncManager.SyncData(new SyncDataRequest { @@ -68,23 +80,24 @@ namespace MediaBrowser.Server.Implementations.Sync { try { - await RemoveItem(provider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false); + await RemoveItem(provider, dataProvider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { - _logger.ErrorException("Error deleting item from sync target. Id: {0}", ex, itemIdToRemove); + _logger.ErrorException("Error deleting item from device. Id: {0}", ex, itemIdToRemove); } } } private async Task GetNewMedia(IServerSyncProvider provider, + ISyncDataProvider dataProvider, SyncTarget target, string serverId, IProgress<double> progress, CancellationToken cancellationToken) { - var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false); - + var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false); + var numComplete = 0; double startingPercent = 0; double percentPerItem = 1; @@ -106,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.Sync progress.Report(totalProgress); }); - await GetItem(provider, target, serverId, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); + await GetItem(provider, dataProvider, target, serverId, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); numComplete++; startingPercent = numComplete; @@ -117,6 +130,7 @@ namespace MediaBrowser.Server.Implementations.Sync } private async Task GetItem(IServerSyncProvider provider, + ISyncDataProvider dataProvider, SyncTarget target, string serverId, SyncedItem jobItem, @@ -129,6 +143,8 @@ namespace MediaBrowser.Server.Implementations.Sync var fileTransferProgress = new ActionableProgress<double>(); fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92)); + var localItem = CreateLocalItem(provider, target, libraryItem, serverId, jobItem.OriginalFileName); + await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id); var transferSuccess = false; @@ -136,10 +152,10 @@ namespace MediaBrowser.Server.Implementations.Sync try { - string[] pathParts = GetPathParts(serverId, libraryItem); + await SendFile(provider, internalSyncJobItem.OutputPath, localItem, target, cancellationToken).ConfigureAwait(false); - await provider.TransferItemFile(serverId, libraryItem.Id, internalSyncJobItem.OutputPath, pathParts, target, cancellationToken) - .ConfigureAwait(false); + // Create db record + await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false); progress.Report(92); @@ -165,18 +181,189 @@ namespace MediaBrowser.Server.Implementations.Sync } } - private Task RemoveItem(IServerSyncProvider provider, + private async Task RemoveItem(IServerSyncProvider provider, + ISyncDataProvider dataProvider, string serverId, string itemId, SyncTarget target, CancellationToken cancellationToken) { - return provider.DeleteItem(serverId, itemId, target, cancellationToken); + var localId = GetLocalId(serverId, itemId); + var localItem = await dataProvider.Get(target, localId); + + if (localItem == null) + { + return; + } + + var files = await GetFiles(provider, localItem, target); + + foreach (var file in files) + { + await provider.DeleteFile(file.Path, target, cancellationToken).ConfigureAwait(false); + } + + await dataProvider.Delete(target, localId).ConfigureAwait(false); + } + + private Task SendFile(IServerSyncProvider provider, string inputPath, LocalItem item, SyncTarget target, CancellationToken cancellationToken) + { + return provider.SendFile(inputPath, item.LocalPath, target, new Progress<double>(), cancellationToken); + } + + private string GetLocalId(string serverId, string itemId) + { + var bytes = Encoding.UTF8.GetBytes(serverId + itemId); + bytes = CreateMD5(bytes); + return BitConverter.ToString(bytes, 0, bytes.Length).Replace("-", string.Empty); + } + + private byte[] CreateMD5(byte[] value) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(value); + } + } + + public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncTarget target, BaseItemDto libraryItem, string serverId, string originalFileName) + { + var path = GetDirectoryPath(provider, libraryItem, serverId); + path.Add(GetLocalFileName(provider, libraryItem, originalFileName)); + + var localPath = provider.GetFullPath(path, target); + + foreach (var mediaSource in libraryItem.MediaSources) + { + mediaSource.Path = localPath; + mediaSource.Protocol = MediaProtocol.File; + } + + return new LocalItem + { + Item = libraryItem, + ItemId = libraryItem.Id, + ServerId = serverId, + LocalPath = localPath, + Id = GetLocalId(serverId, libraryItem.Id) + }; + } + + private List<string> GetDirectoryPath(IServerSyncProvider provider, BaseItemDto item, string serverId) + { + var parts = new List<string> + { + serverId + }; + + if (item.IsType("episode")) + { + parts.Add("TV"); + parts.Add(item.SeriesName); + + if (!string.IsNullOrWhiteSpace(item.SeasonName)) + { + parts.Add(item.SeasonName); + } + } + else if (item.IsVideo) + { + parts.Add("Videos"); + parts.Add(item.Name); + } + else if (item.IsAudio) + { + parts.Add("Music"); + + if (!string.IsNullOrWhiteSpace(item.AlbumArtist)) + { + parts.Add(item.AlbumArtist); + } + + if (!string.IsNullOrWhiteSpace(item.Album)) + { + parts.Add(item.Album); + } + } + else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) + { + parts.Add("Photos"); + + if (!string.IsNullOrWhiteSpace(item.Album)) + { + parts.Add(item.Album); + } + } + + return parts.Select(i => GetValidFilename(provider, i)).ToList(); + } + + private string GetLocalFileName(IServerSyncProvider provider, BaseItemDto item, string originalFileName) + { + var filename = originalFileName; + + if (string.IsNullOrEmpty(filename)) + { + filename = item.Name; + } + + return GetValidFilename(provider, filename); + } + + private string GetValidFilename(IServerSyncProvider provider, string filename) + { + // We can always add this method to the sync provider if it's really needed + return _fileSystem.GetValidFilename(filename); } - private string[] GetPathParts(string serverId, BaseItemDto item) + private async Task<List<ItemFileInfo>> GetFiles(IServerSyncProvider provider, LocalItem item, SyncTarget target) { - return null; + var path = item.LocalPath; + path = provider.GetParentDirectoryPath(path, target); + + var list = await provider.GetFileSystemEntries(path, target).ConfigureAwait(false); + + var itemFiles = new List<ItemFileInfo>(); + + var name = Path.GetFileNameWithoutExtension(item.LocalPath); + + foreach (var file in list.Where(f => f.Name.Contains(name))) + { + var itemFile = new ItemFileInfo + { + Path = file.Path, + Name = file.Name + }; + + if (IsSubtitleFile(file.Name)) + { + itemFile.Type = ItemFileType.Subtitles; + } + else if (!IsImageFile(file.Name)) + { + itemFile.Type = ItemFileType.Media; + } + + itemFiles.Add(itemFile); + } + + return itemFiles; + } + + private static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".webp" }; + private bool IsImageFile(string path) + { + var ext = Path.GetExtension(path) ?? string.Empty; + + return SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + } + + private static readonly string[] SupportedSubtitleExtensions = { ".srt", ".vtt" }; + private bool IsSubtitleFile(string path) + { + var ext = Path.GetExtension(path) ?? string.Empty; + + return SupportedSubtitleExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); } } } diff --git a/MediaBrowser.Server.Implementations/Sync/MultiProviderSync.cs b/MediaBrowser.Server.Implementations/Sync/MultiProviderSync.cs new file mode 100644 index 000000000..cbfa82f1d --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sync/MultiProviderSync.cs @@ -0,0 +1,69 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Progress; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Sync; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Sync; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Sync +{ + public class MultiProviderSync + { + private readonly ISyncManager _syncManager; + private readonly IServerApplicationHost _appHost; + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + + public MultiProviderSync(ISyncManager syncManager, IServerApplicationHost appHost, ILogger logger, IFileSystem fileSystem) + { + _syncManager = syncManager; + _appHost = appHost; + _logger = logger; + _fileSystem = fileSystem; + } + + public async Task Sync(IEnumerable<IServerSyncProvider> providers, IProgress<double> progress, CancellationToken cancellationToken) + { + var targets = providers + .SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple<IServerSyncProvider, SyncTarget>(i, t))) + .ToList(); + + var numComplete = 0; + double startingPercent = 0; + double percentPerItem = 1; + if (targets.Count > 0) + { + percentPerItem /= targets.Count; + } + + foreach (var target in targets) + { + cancellationToken.ThrowIfCancellationRequested(); + + var currentPercent = startingPercent; + var innerProgress = new ActionableProgress<double>(); + innerProgress.RegisterAction(pct => + { + var totalProgress = pct * percentPerItem; + totalProgress += currentPercent; + progress.Report(totalProgress); + }); + + await new MediaSync(_logger, _syncManager, _appHost, _fileSystem) + .Sync(target.Item1, target.Item1.GetDataProvider(), target.Item2, innerProgress, cancellationToken) + .ConfigureAwait(false); + + numComplete++; + startingPercent = numComplete; + startingPercent /= targets.Count; + startingPercent *= 100; + progress.Report(startingPercent); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index a2fd92bf5..8fc3651f7 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -407,6 +407,15 @@ namespace MediaBrowser.Server.Implementations.Sync .OrderBy(i => i.Name); } + private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider) + { + return provider.GetAllSyncTargets().Select(i => new SyncTarget + { + Name = i.Name, + Id = GetSyncTargetId(provider, i) + }); + } + private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider, string userId) { return provider.GetSyncTargets(userId).Select(i => new SyncTarget @@ -429,13 +438,6 @@ namespace MediaBrowser.Server.Implementations.Sync return (providerId + "-" + target.Id).GetMD5().ToString("N"); } - private ISyncProvider GetSyncProvider(SyncTarget target) - { - var providerId = target.Id.Split(new[] { '-' }, 2).First(); - - return _providers.First(i => string.Equals(providerId, GetSyncProviderId(i))); - } - private string GetSyncProviderId(ISyncProvider provider) { return (provider.GetType().Name).GetMD5().ToString("N"); @@ -543,11 +545,11 @@ namespace MediaBrowser.Server.Implementations.Sync { foreach (var provider in _providers) { - foreach (var target in GetSyncTargets(provider, null)) + foreach (var target in GetSyncTargets(provider)) { if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase)) { - return provider.GetDeviceProfile(target); + return GetDeviceProfile(provider, target); } } } @@ -555,6 +557,18 @@ namespace MediaBrowser.Server.Implementations.Sync return null; } + public DeviceProfile GetDeviceProfile(ISyncProvider provider, SyncTarget target) + { + var hasProfile = provider as IHasSyncProfile; + + if (hasProfile != null) + { + return hasProfile.GetDeviceProfile(target); + } + + return new CloudSyncProfile(true, false); + } + public async Task ReportSyncJobItemTransferred(string id) { var jobItem = _repo.GetJobItem(id); diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 41df1b471..8c530e015 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="ImageMagickSharp" version="1.0.0.2" targetFramework="net45" />
+ <package id="ImageMagickSharp" version="1.0.0.6" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.32" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.21.0" targetFramework="net45" />
<package id="morelinq" version="1.1.0" targetFramework="net45" />
|
